diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 920c5154d6d..52d3d3e77d3 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -720,6 +720,7 @@ class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu): node_add_menu.add_node_type(layout, "GeometryNodeVolumeToMesh") if context.preferences.experimental.use_new_volume_nodes: node_add_menu.add_node_type(layout, "GeometryNodeGridToMesh") + node_add_menu.add_node_type(layout, "GeometryNodeSDFGridBoolean") node_add_menu.draw_assets_for_catalog(layout, "Volume/Operations") diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index 0264cad5d15..7baefcc2b72 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1277,6 +1277,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index #define GEO_NODE_POINTS_TO_SDF_GRID 2128 #define GEO_NODE_GRID_TO_MESH 2129 #define GEO_NODE_DISTRIBUTE_POINTS_IN_GRID 2130 +#define GEO_NODE_SDF_GRID_BOOLEAN 2131 /** \} */ diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index 68f7653ef54..8d9ae7f6a21 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -50,6 +50,7 @@ set(SRC intern/trim_curves.cc intern/uv_pack.cc intern/uv_parametrizer.cc + intern/volume_grid_resample.cc GEO_add_curves_on_mesh.hh GEO_curve_constraints.hh diff --git a/source/blender/geometry/GEO_volume_grid_resample.hh b/source/blender/geometry/GEO_volume_grid_resample.hh new file mode 100644 index 00000000000..2ed2c565145 --- /dev/null +++ b/source/blender/geometry/GEO_volume_grid_resample.hh @@ -0,0 +1,18 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include "BKE_volume_grid_fwd.hh" + +namespace blender::geometry { + +openvdb::FloatGrid &resample_sdf_grid_if_necessary(bke::VolumeGrid &volume_grid, + bke::VolumeTreeAccessToken &tree_token, + const openvdb::math::Transform &transform, + std::shared_ptr &storage); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/volume_grid_resample.cc b/source/blender/geometry/intern/volume_grid_resample.cc new file mode 100644 index 00000000000..dbcb33026c0 --- /dev/null +++ b/source/blender/geometry/intern/volume_grid_resample.cc @@ -0,0 +1,38 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB + +# include "BKE_volume_grid.hh" + +# include "GEO_volume_grid_resample.hh" + +# include +# include + +namespace blender::geometry { + +openvdb::FloatGrid &resample_sdf_grid_if_necessary(bke::VolumeGrid &volume_grid, + bke::VolumeTreeAccessToken &tree_token, + const openvdb::math::Transform &transform, + std::shared_ptr &storage) +{ + const openvdb::FloatGrid &grid = volume_grid.grid(tree_token); + if (grid.transform() == transform) { + return volume_grid.grid_for_write(tree_token); + } + + storage = openvdb::FloatGrid::create(); + storage->setTransform(transform.copy()); + + /* TODO: Using #doResampleToMatch when the transform is affine and non-scaled may be faster. */ + openvdb::tools::resampleToMatch(grid, *storage); + openvdb::tools::pruneLevelSet(storage->tree()); + + return *storage; +} + +} // namespace blender::geometry + +#endif diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 7e84c785a89..fff18d44a50 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -434,6 +434,7 @@ DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST, 0, "SAMPLE_NEAREST", SampleNeares DefNode(GeometryNode, GEO_NODE_SAMPLE_UV_SURFACE, 0, "SAMPLE_UV_SURFACE", SampleUVSurface, "Sample UV Surface", "Calculate the interpolated values of a mesh attribute at a UV coordinate") DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, 0, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "Scale groups of connected edges and faces") DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "Scale geometry instances in local or global space") +DefNode(GeometryNode, GEO_NODE_SDF_GRID_BOOLEAN, 0, "SDF_GRID_BOOLEAN", SDFGridBoolean, "SDF Grid Boolean", "Cut, subtract, or join multiple SDF volume grid inputs") DefNode(GeometryNode, GEO_NODE_SELF_OBJECT, 0, "SELF_OBJECT", SelfObject, "Self Object", "Retrieve the object that contains the geometry nodes modifier currently being executed") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS",SeparateComponents, "Separate Components", "Split a geometry into a separate output for each type of data in the geometry") DefNode(GeometryNode, GEO_NODE_SEPARATE_GEOMETRY, 0, "SEPARATE_GEOMETRY", SeparateGeometry, "Separate Geometry", "Split a geometry into two geometry outputs based on a selection") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 73275876e92..a67074d6160 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -169,6 +169,7 @@ set(SRC nodes/node_geo_sample_uv_surface.cc nodes/node_geo_scale_elements.cc nodes/node_geo_scale_instances.cc + nodes/node_geo_sdf_grid_boolean.cc nodes/node_geo_self_object.cc nodes/node_geo_separate_components.cc nodes/node_geo_separate_geometry.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc new file mode 100644 index 00000000000..43d831abc01 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc @@ -0,0 +1,178 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB +# include +# include +#endif + +#include "BKE_volume_grid.hh" + +#include "GEO_volume_grid_resample.hh" + +#include "NOD_rna_define.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_sdf_grid_boolean_cc { + +enum class Operation { + Intersect = 0, + Union = 1, + Difference = 2, +}; + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid 1").hide_value(); + b.add_input("Grid 2").hide_value().multi_input().make_available( + [](bNode &node) { node.custom1 = int16_t(Operation::Difference); }); + b.add_output("Grid").hide_value(); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "operation", UI_ITEM_NONE, "", ICON_NONE); +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + bNodeSocket *grid_1_socket = static_cast(node->inputs.first); + bNodeSocket *grid_2_socket = grid_1_socket->next; + switch (Operation(node->custom1)) { + case Operation::Intersect: + case Operation::Union: + bke::nodeSetSocketAvailability(ntree, grid_1_socket, false); + node_sock_label(grid_2_socket, "Grid"); + break; + case Operation::Difference: + bke::nodeSetSocketAvailability(ntree, grid_1_socket, true); + node_sock_label(grid_2_socket, "Grid 2"); + break; + } +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = int16_t(Operation::Difference); +} + +#ifdef WITH_OPENVDB +static void get_float_grids(MutableSpan values, + Vector> &grids) +{ + for (SocketValueVariant &input : values) { + if (auto grid = input.extract>()) { + grids.append(std::move(grid)); + } + } +} +#endif + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const Operation operation = Operation(params.node().custom1); + + Vector inputs = params.extract_input>("Grid 2"); + Vector> operands; + switch (operation) { + case Operation::Intersect: + case Operation::Union: + get_float_grids(inputs, operands); + break; + case Operation::Difference: + if (auto grid = params.extract_input>("Grid 1")) { + operands.append(std::move(grid)); + } + get_float_grids(inputs, operands); + break; + } + + if (operands.is_empty()) { + params.set_default_remaining_outputs(); + return; + } + + bke::VolumeTreeAccessToken result_token; + openvdb::FloatGrid &result_grid = operands.first().grid_for_write(result_token); + const openvdb::math::Transform &transform = result_grid.transform(); + + for (bke::VolumeGrid &volume_grid : operands.as_mutable_span().drop_front(1)) { + bke::VolumeTreeAccessToken tree_token; + std::shared_ptr resampled_storage; + openvdb::FloatGrid &grid = geometry::resample_sdf_grid_if_necessary( + volume_grid, tree_token, transform, resampled_storage); + + try { + switch (operation) { + case Operation::Intersect: + openvdb::tools::csgIntersection(result_grid, grid); + break; + case Operation::Union: + openvdb::tools::csgUnion(result_grid, grid); + break; + case Operation::Difference: + openvdb::tools::csgDifference(result_grid, grid); + break; + } + } + catch (const openvdb::ValueError & /*ex*/) { + /* May happen if a grid is empty. */ + params.set_default_remaining_outputs(); + return; + } + } + + params.set_output("Grid", std::move(operands.first())); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_rna(StructRNA *srna) +{ + static const EnumPropertyItem operation_items[] = { + {int(Operation::Intersect), + "INTERSECT", + 0, + "Intersect", + "Keep the part of the grids that is common between all operands"}, + {int(Operation::Union), "UNION", 0, "Union", "Combine grids in an additive way"}, + {int(Operation::Difference), + "DIFFERENCE", + 0, + "Difference", + "Combine grids in a subtractive way"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_node_enum(srna, + "operation", + "Operation", + "", + operation_items, + NOD_inline_enum_accessors(custom1), + int(Operation::Difference)); +} + +static void node_register() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_SDF_GRID_BOOLEAN, "SDF Grid Boolean", NODE_CLASS_GEOMETRY); + ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.draw_buttons = node_layout; + ntype.updatefunc = node_update; + ntype.geometry_node_execute = node_geo_exec; + ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node; + nodeRegisterType(&ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_sdf_grid_boolean_cc