From 0a77a57c4ef190903f463ecc7ae67486b28e3672 Mon Sep 17 00:00:00 2001 From: Brady Johnston Date: Thu, 2 Oct 2025 16:28:46 +0200 Subject: [PATCH] Geometry Nodes: Add OpenVDB grid operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds four new grid operator nodes that wrap OpenVDB's differential operators. All nodes take a grid input and output a grid, potentially with a different data type. New nodes: - Grid Curl: Calculate curl of vector field (Vec3 → Vec3) - Grid Divergence: Calculate divergence of vector field (Vec3 → Float) - Grid Gradient: Calculate gradient of scalar field (Float → Vec3) - Grid Laplacian: Calculate Laplacian of scalar field (Float → Float) These operators enable vector calculus operations on volume grids for effects like flow analysis, vortex detection, etc. Co-authored-by: Hans Goudey Pull Request: https://projects.blender.org/blender/blender/pulls/146845 --- .../startup/bl_ui/node_add_menu_geometry.py | 4 ++ .../blender/makesrna/intern/rna_nodetree.cc | 4 ++ source/blender/nodes/geometry/CMakeLists.txt | 4 ++ .../geometry/nodes/node_geo_grid_curl.cc | 51 +++++++++++++++++++ .../nodes/node_geo_grid_divergence.cc | 51 +++++++++++++++++++ .../geometry/nodes/node_geo_grid_gradient.cc | 51 +++++++++++++++++++ .../geometry/nodes/node_geo_grid_laplacian.cc | 50 ++++++++++++++++++ .../volume/grid_operators.blend | 3 ++ 8 files changed, 218 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_grid_curl.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_grid_divergence.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_grid_gradient.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_grid_laplacian.cc create mode 100644 tests/files/modeling/geometry_nodes/volume/grid_operators.blend diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 13bb615e988..4f675b16591 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -935,6 +935,10 @@ class NODE_MT_gn_volume_sample_base(node_add_menu.NodeMenu): layout = self.layout self.node_operator(layout, "GeometryNodeSampleGrid") self.node_operator(layout, "GeometryNodeSampleGridIndex") + self.node_operator(layout, "GeometryNodeGridCurl") + self.node_operator(layout, "GeometryNodeGridDivergence") + self.node_operator(layout, "GeometryNodeGridGradient") + self.node_operator(layout, "GeometryNodeGridLaplacian") self.draw_assets_for_catalog(layout, self.menu_path) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 6ef0b62afa8..057875222cd 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10165,7 +10165,11 @@ static void rna_def_nodes(BlenderRNA *brna) define("GeometryNode", "GeometryNodeGizmoLinear"); define("GeometryNode", "GeometryNodeGizmoTransform", rna_def_geo_gizmo_transform); define("GeometryNode", "GeometryNodeGreasePencilToCurves"); + define("GeometryNode", "GeometryNodeGridCurl"); + define("GeometryNode", "GeometryNodeGridDivergence"); + define("GeometryNode", "GeometryNodeGridGradient"); define("GeometryNode", "GeometryNodeGridInfo"); + define("GeometryNode", "GeometryNodeGridLaplacian"); define("GeometryNode", "GeometryNodeGridToMesh"); define("GeometryNode", "GeometryNodeImageInfo"); define("GeometryNode", "GeometryNodeImageTexture", def_geo_image_texture); diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 3679aa158b4..40c1652adac 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -96,7 +96,11 @@ set(SRC nodes/node_geo_gizmo_linear.cc nodes/node_geo_gizmo_transform.cc nodes/node_geo_grease_pencil_to_curves.cc + nodes/node_geo_grid_curl.cc + nodes/node_geo_grid_divergence.cc + nodes/node_geo_grid_gradient.cc nodes/node_geo_grid_info.cc + nodes/node_geo_grid_laplacian.cc nodes/node_geo_grid_to_mesh.cc nodes/node_geo_image.cc nodes/node_geo_image_info.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_curl.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_curl.cc new file mode 100644 index 00000000000..8f1cffc7c06 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_curl.cc @@ -0,0 +1,51 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" + +#include "node_geometry_util.hh" + +#include "openvdb/tools/GridOperators.h" + +namespace blender::nodes::node_geo_grid_curl_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); + b.add_output("Curl").structure_type(StructureType::Grid); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const bke::VolumeGrid grid = params.extract_input>("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + const openvdb::Vec3SGrid &vdb_grid = grid.grid(tree_token); + openvdb::Vec3SGrid::Ptr curl_vdb_grid = openvdb::tools::curl(vdb_grid); + params.set_output("Curl", bke::VolumeGrid(std::move(curl_vdb_grid))); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridCurl"); + ntype.ui_name = "Grid Curl"; + ntype.ui_description = + "Calculate the magnitude and direction of circulation of a directional vector grid"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node; + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_curl_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_divergence.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_divergence.cc new file mode 100644 index 00000000000..b75d85e88e6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_divergence.cc @@ -0,0 +1,51 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" + +#include "node_geometry_util.hh" + +#include "openvdb/tools/GridOperators.h" + +namespace blender::nodes::node_geo_grid_divergence_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); + b.add_output("Divergence").structure_type(StructureType::Grid); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const bke::VolumeGrid grid = params.extract_input>("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + const openvdb::Vec3SGrid &vdb_grid = grid.grid(tree_token); + openvdb::FloatGrid::Ptr divergence_vdb_grid = openvdb::tools::divergence(vdb_grid); + params.set_output("Divergence", bke::VolumeGrid(std::move(divergence_vdb_grid))); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridDivergence"); + ntype.ui_name = "Grid Divergence"; + ntype.ui_description = + "Calculate the flow into and out of each point of a directional vector grid"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node; + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_divergence_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_gradient.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_gradient.cc new file mode 100644 index 00000000000..1cbd635df32 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_gradient.cc @@ -0,0 +1,51 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" + +#include "node_geometry_util.hh" + +#include "openvdb/tools/GridOperators.h" + +namespace blender::nodes::node_geo_grid_gradient_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); + b.add_output("Gradient").structure_type(StructureType::Grid); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const bke::VolumeGrid grid = params.extract_input>("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + const openvdb::FloatGrid &vdb_grid = grid.grid(tree_token); + openvdb::Vec3SGrid::Ptr gradient_vdb_grid = openvdb::tools::gradient(vdb_grid); + params.set_output("Gradient", bke::VolumeGrid(std::move(gradient_vdb_grid))); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridGradient"); + ntype.ui_name = "Grid Gradient"; + ntype.ui_description = + "Calculate the direction and magnitude of the change in values of a scalar grid"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node; + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_gradient_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_laplacian.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_laplacian.cc new file mode 100644 index 00000000000..981e92a713d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_laplacian.cc @@ -0,0 +1,50 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" + +#include "node_geometry_util.hh" + +#include "openvdb/tools/GridOperators.h" + +namespace blender::nodes::node_geo_grid_laplacian_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); + b.add_output("Laplacian").structure_type(StructureType::Grid); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const bke::VolumeGrid grid = params.extract_input>("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + const openvdb::FloatGrid &vdb_grid = grid.grid(tree_token); + openvdb::FloatGrid::Ptr laplacian_vdb_grid = openvdb::tools::laplacian(vdb_grid); + params.set_output("Laplacian", bke::VolumeGrid(std::move(laplacian_vdb_grid))); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridLaplacian"); + ntype.ui_name = "Grid Laplacian"; + ntype.ui_description = "Compute the divergence of the gradient of the input grid"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node; + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_laplacian_cc diff --git a/tests/files/modeling/geometry_nodes/volume/grid_operators.blend b/tests/files/modeling/geometry_nodes/volume/grid_operators.blend new file mode 100644 index 00000000000..53b01f00c60 --- /dev/null +++ b/tests/files/modeling/geometry_nodes/volume/grid_operators.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfa185f6056ceaebad80ba8897fb282831a6eebe06815b8035060a2d602ee7f8 +size 324578