diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 7e0d618ec8c..3f21ac6d32e 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -955,6 +955,8 @@ class NODE_MT_gn_volume_operations_base(node_add_menu.NodeMenu): self.node_operator(layout, "GeometryNodeGridToMesh") self.node_operator(layout, "GeometryNodeSDFGridBoolean") self.node_operator(layout, "GeometryNodeFieldToGrid") + self.node_operator(layout, "GeometryNodeGridPrune") + self.node_operator(layout, "GeometryNodeGridVoxelize") self.draw_assets_for_catalog(layout, self.menu_path) diff --git a/source/blender/blenkernel/BKE_volume_grid_process.hh b/source/blender/blenkernel/BKE_volume_grid_process.hh index ba6476f2a8a..39e7ab0a6f9 100644 --- a/source/blender/blenkernel/BKE_volume_grid_process.hh +++ b/source/blender/blenkernel/BKE_volume_grid_process.hh @@ -112,6 +112,9 @@ void set_mask_leaf_buffer_from_bools(openvdb::BoolGrid &grid, void set_grid_background(openvdb::GridBase &grid_base, const GPointer value); +/** See #openvdb::tools::pruneInactive. */ +void prune_inactive(openvdb::GridBase &grid_base); + } // namespace blender::bke::volume_grid /** \} */ diff --git a/source/blender/blenkernel/intern/volume_grid.cc b/source/blender/blenkernel/intern/volume_grid.cc index b4566d80f0d..e5e5fd08218 100644 --- a/source/blender/blenkernel/intern/volume_grid.cc +++ b/source/blender/blenkernel/intern/volume_grid.cc @@ -12,6 +12,7 @@ #ifdef WITH_OPENVDB # include +# include #endif namespace blender::bke::volume_grid { @@ -800,6 +801,11 @@ void set_grid_background(openvdb::GridBase &grid_base, const GPointer value) }); } +void prune_inactive(openvdb::GridBase &grid_base) +{ + to_typed_grid(grid_base, [&](auto &grid) { openvdb::tools::pruneInactive(grid.tree()); }); +} + #endif /* WITH_OPENVDB */ } // namespace blender::bke::volume_grid diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index d94cb85112d..2203a40beb4 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10170,7 +10170,9 @@ static void rna_def_nodes(BlenderRNA *brna) define("GeometryNode", "GeometryNodeGridGradient"); define("GeometryNode", "GeometryNodeGridInfo"); define("GeometryNode", "GeometryNodeGridLaplacian"); + define("GeometryNode", "GeometryNodeGridPrune"); define("GeometryNode", "GeometryNodeGridToMesh"); + define("GeometryNode", "GeometryNodeGridVoxelize"); define("GeometryNode", "GeometryNodeImageInfo"); define("GeometryNode", "GeometryNodeImageTexture", def_geo_image_texture); define("GeometryNode", "GeometryNodeImportCSV"); diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index bfc13f15a17..93d03761997 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -12,6 +12,7 @@ #include "BKE_texture.h" #include "BKE_volume.hh" #include "BKE_volume_grid.hh" +#include "BKE_volume_grid_process.hh" #include "BKE_volume_openvdb.hh" #include "BLT_translation.hh" @@ -235,7 +236,7 @@ struct DisplaceGridOp { * slowing down subsequent operations. */ typename GridType::ValueType prune_tolerance{0}; openvdb::tools::deactivate(*temp_grid, temp_grid->background(), prune_tolerance); - openvdb::tools::prune(temp_grid->tree()); + blender::bke::volume_grid::prune_inactive(*temp_grid); /* Overwrite the old volume grid with the new grid. */ grid.clear(); diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 026294cd523..931a34aa238 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -101,7 +101,9 @@ set(SRC nodes/node_geo_grid_gradient.cc nodes/node_geo_grid_info.cc nodes/node_geo_grid_laplacian.cc + nodes/node_geo_grid_prune.cc nodes/node_geo_grid_to_mesh.cc + nodes/node_geo_grid_voxelize.cc nodes/node_geo_image.cc nodes/node_geo_image_info.cc nodes/node_geo_image_texture.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_prune.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_prune.cc new file mode 100644 index 00000000000..30c34a7e45a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_prune.cc @@ -0,0 +1,250 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" +#include "BKE_volume_grid_process.hh" + +#include "NOD_rna_define.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "node_geometry_util.hh" + +#ifdef WITH_OPENVDB +# include "openvdb/tools/Prune.h" +#endif + +namespace blender::nodes::node_geo_grid_prune_cc { + +enum class Mode : int16_t { + Inactive = 0, + Threshold = 1, + SDF = 2, +}; + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.use_custom_socket_order(); + b.allow_any_socket_order(); + b.add_default_layout(); + const bNode *node = b.node_or_null(); + if (!node) { + return; + } + const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); + b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid); + b.add_output(data_type, "Grid").structure_type(StructureType::Grid).align_with_previous(); + static EnumPropertyItem mode_items[] = { + {int(Mode::Inactive), + "INACTIVE", + 0, + "Inactive", + "Turn inactive voxels and tiles into inactive background tiles"}, + {int(Mode::Threshold), + "THRESHOLD", + 0, + "Threshold", + "Turn regions where all voxels have the same value and active state (within a tolerance " + "threshold) into inactive background tiles"}, + {int(Mode::SDF), + "SDF", + 0, + "SDF", + "Replace inactive tiles with inactive nodes. Faster than tolerance-based pruning, useful " + "for cases like narrow-band SDF grids with only inside or outside background values."}, + {0, nullptr, 0, nullptr, nullptr}, + }; + b.add_input("Mode") + .static_items(mode_items) + .default_value(MenuValue(Mode::Threshold)) + .structure_type(StructureType::Single) + .optional_label(); + if (data_type != SOCK_BOOLEAN) { + auto &threshold = b.add_input(data_type, "Threshold") + .structure_type(StructureType::Single) + .usage_by_single_menu(int(Mode::Threshold)); + switch (data_type) { + case SOCK_FLOAT: { + auto &threshold_typed = static_cast(threshold); + threshold_typed.min(0.0f).default_value(0.01f); + break; + } + case SOCK_VECTOR: { + auto &threshold_typed = static_cast(threshold); + threshold_typed.min(0.0f).default_value(float3(0.01f)); + break; + } + case SOCK_INT: { + auto &threshold_typed = static_cast(threshold); + threshold_typed.min(0).default_value(0); + break; + } + default: + BLI_assert_unreachable(); + } + } +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +static std::optional node_type_for_socket_type(const bNodeSocket &socket) +{ + switch (socket.type) { + case SOCK_FLOAT: + return SOCK_FLOAT; + case SOCK_BOOLEAN: + return SOCK_BOOLEAN; + case SOCK_INT: + return SOCK_INT; + case SOCK_VECTOR: + case SOCK_RGBA: + return SOCK_VECTOR; + default: + return std::nullopt; + } +} + +static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms) +{ + if (!USER_EXPERIMENTAL_TEST(&U, use_new_volume_nodes)) { + return; + } + const std::optional data_type = node_type_for_socket_type( + params.other_socket()); + if (!data_type) { + return; + } + params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeGridPrune"); + node.custom1 = *data_type; + params.update_and_connect_available_socket(node, "Grid"); + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + bke::GVolumeGrid grid = params.extract_input("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + openvdb::GridBase &grid_base = grid.get_for_write().grid_for_write(tree_token); + switch (params.extract_input("Mode")) { + case Mode::Inactive: { + bke::volume_grid::prune_inactive(grid_base); + break; + } + case Mode::Threshold: { + const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base); + switch (grid_type) { + case VOLUME_GRID_BOOLEAN: { + auto &grid = static_cast(grid_base); + openvdb::tools::prune(grid.tree()); + break; + } + case VOLUME_GRID_MASK: { + auto &grid = static_cast(grid_base); + openvdb::tools::prune(grid.tree()); + break; + } + case VOLUME_GRID_FLOAT: { + auto &grid = static_cast(grid_base); + const float threshold = params.extract_input("Threshold"); + openvdb::tools::prune(grid.tree(), threshold); + break; + } + case VOLUME_GRID_INT: { + auto &grid = static_cast(grid_base); + const int threshold = params.extract_input("Threshold"); + openvdb::tools::prune(grid.tree(), threshold); + break; + } + case VOLUME_GRID_VECTOR_FLOAT: { + auto &grid = static_cast(grid_base); + const float3 threshold = params.extract_input("Threshold"); + openvdb::tools::prune(grid.tree(), + openvdb::Vec3s(threshold.x, threshold.y, threshold.z)); + break; + } + case VOLUME_GRID_UNKNOWN: + case VOLUME_GRID_DOUBLE: + case VOLUME_GRID_INT64: + case VOLUME_GRID_VECTOR_DOUBLE: + case VOLUME_GRID_VECTOR_INT: + case VOLUME_GRID_POINTS: { + params.error_message_add(NodeWarningType::Error, "Unsupported grid type"); + break; + } + } + break; + } + case Mode::SDF: { + const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base); + BKE_volume_grid_type_to_static_type(grid_type, [&](auto type_tag) { + using GridT = typename decltype(type_tag)::type; + if constexpr (bke::volume_grid::is_supported_grid_type) { + if constexpr (std::is_scalar_v) { + GridT &grid = static_cast(grid_base); + openvdb::tools::pruneLevelSet(grid.tree()); + } + } + else { + BLI_assert_unreachable(); + } + }); + break; + } + } + params.set_output("Grid", std::move(grid)); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = SOCK_FLOAT; +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum(srna, + "data_type", + "Data Type", + "Node socket data type", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_FLOAT, + grid_socket_type_items_filter_fn); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridPrune"); + ntype.ui_name = "Prune Grid"; + ntype.ui_description = + "Make the storage of a volume grid more efficient by collapsing data into tiles or inner " + "nodes"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.draw_buttons = node_layout; + ntype.initfunc = node_init; + ntype.gather_link_search_ops = node_gather_link_search_ops; + ntype.geometry_node_execute = node_geo_exec; + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_prune_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_voxelize.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_voxelize.cc new file mode 100644 index 00000000000..5f85d72c465 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_voxelize.cc @@ -0,0 +1,126 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" +#include "BKE_volume_grid_process.hh" + +#include "NOD_rna_define.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_grid_voxelize_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.use_custom_socket_order(); + b.allow_any_socket_order(); + b.add_default_layout(); + const bNode *node = b.node_or_null(); + if (!node) { + return; + } + const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); + b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid); + b.add_output(data_type, "Grid").structure_type(StructureType::Grid).align_with_previous(); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +static std::optional node_type_for_socket_type(const bNodeSocket &socket) +{ + switch (socket.type) { + case SOCK_FLOAT: + return SOCK_FLOAT; + case SOCK_BOOLEAN: + return SOCK_BOOLEAN; + case SOCK_INT: + return SOCK_INT; + case SOCK_VECTOR: + case SOCK_RGBA: + return SOCK_VECTOR; + default: + return std::nullopt; + } +} + +static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms) +{ + if (!USER_EXPERIMENTAL_TEST(&U, use_new_volume_nodes)) { + return; + } + const std::optional data_type = node_type_for_socket_type( + params.other_socket()); + if (!data_type) { + return; + } + params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeGridVoxelize"); + node.custom1 = *data_type; + params.update_and_connect_available_socket(node, "Grid"); + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + bke::GVolumeGrid grid = params.extract_input("Grid"); + if (!grid) { + params.set_default_remaining_outputs(); + return; + } + bke::VolumeTreeAccessToken tree_token; + openvdb::GridBase &vdb_grid = grid.get_for_write().grid_for_write(tree_token); + bke::volume_grid::to_typed_grid(vdb_grid, + [&](auto &grid) { grid.tree().voxelizeActiveTiles(); }); + params.set_output("Grid", std::move(grid)); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = SOCK_FLOAT; +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum(srna, + "data_type", + "Data Type", + "Node socket data type", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_FLOAT, + grid_socket_type_items_filter_fn); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeGridVoxelize"); + ntype.ui_name = "Voxelize Grid"; + ntype.ui_description = + "Remove sparseness from a volume grid by making the active tiles into voxels"; + ntype.nclass = NODE_CLASS_GEOMETRY; + ntype.declare = node_declare; + ntype.draw_buttons = node_layout; + ntype.initfunc = node_init; + ntype.gather_link_search_ops = node_gather_link_search_ops; + ntype.geometry_node_execute = node_geo_exec; + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_voxelize_cc