diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index f8168e99052..560007204c4 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -475,7 +475,8 @@ class NODE_MT_category_GEO_POINT(Menu): def draw(self, context): layout = self.layout - node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInVolume") + if context.preferences.experimental.use_new_volume_nodes: + node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInGrid") node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsOnFaces") layout.separator() node_add_menu.add_node_type(layout, "GeometryNodePoints") @@ -717,6 +718,8 @@ class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu): def draw(self, context): layout = self.layout 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.draw_assets_for_catalog(layout, "Volume/Operations") diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index c8bb5969792..595b5c1c3d3 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1333,6 +1333,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_MESH_TO_DENSITY_GRID 2126 #define GEO_NODE_MESH_TO_SDF_GRID 2127 #define GEO_NODE_POINTS_TO_SDF_GRID 2128 +#define GEO_NODE_GRID_TO_MESH 2129 +#define GEO_NODE_DISTRIBUTE_POINTS_IN_GRID 2130 /** \} */ diff --git a/source/blender/blenkernel/BKE_volume_to_mesh.hh b/source/blender/blenkernel/BKE_volume_to_mesh.hh index 5dc5ada8e95..40fd6f4d76f 100644 --- a/source/blender/blenkernel/BKE_volume_to_mesh.hh +++ b/source/blender/blenkernel/BKE_volume_to_mesh.hh @@ -44,6 +44,8 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid, float threshold, float adaptivity); +Mesh *volume_grid_to_mesh(const openvdb::GridBase &grid, float threshold, float adaptivity); + /** * Convert an OpenVDB volume grid to corresponding mesh data: vertex positions and quad and * triangle indices. diff --git a/source/blender/blenkernel/intern/volume_to_mesh.cc b/source/blender/blenkernel/intern/volume_to_mesh.cc index 1160e5f0f2d..7eeec20da59 100644 --- a/source/blender/blenkernel/intern/volume_to_mesh.cc +++ b/source/blender/blenkernel/intern/volume_to_mesh.cc @@ -184,6 +184,13 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid, return mesh; } +Mesh *volume_grid_to_mesh(const openvdb::GridBase &grid, + const float threshold, + const float adaptivity) +{ + return volume_to_mesh(grid, {VOLUME_TO_MESH_RESOLUTION_MODE_GRID}, threshold, adaptivity); +} + #endif /* WITH_OPENVDB */ } // namespace blender::bke diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 7269b2220b4..7e84c785a89 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -326,6 +326,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, 0, "CURVE_OF_POINT DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, 0, "POINTS_OF_CURVE", PointsOfCurve, "Points of Curve", "Retrieve a point index within a curve") DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "Translate and rotate curves based on changes between the object's original and evaluated surface mesh") DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "Remove selected elements of a geometry") +DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_GRID, 0, "DISTRIBUTE_POINTS_IN_GRID", DistributePointsInGrid, "Distribute Points in Grid", "Generate points inside a volume grid") DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME, 0, "DISTRIBUTE_POINTS_IN_VOLUME", DistributePointsInVolume, "Distribute Points in Volume", "Generate points inside a volume") DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "Generate points spread out on the surface of a mesh") DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "Convert Faces into vertices and vertices into faces") @@ -341,6 +342,7 @@ DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fi DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction") DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large") DefNode(GeometryNode, GEO_NODE_GET_NAMED_GRID, 0, "GET_NAMED_GRID", GetNamedGrid, "Get Named Grid", "Get volume grid from a volume geometry with the specified name") +DefNode(GeometryNode, GEO_NODE_GRID_TO_MESH, 0, "GRID_TO_MESH", GridToMesh, "Grid to Mesh", "Generate a mesh on the \"surface\" of a volume grid") DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image") DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture") DefNode(GeometryNode, GEO_NODE_IMAGE, def_geo_image, "IMAGE", InputImage, "Image", "Input image") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index e9cb3adcc0f..a5ad0fddd4e 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -64,6 +64,7 @@ set(SRC nodes/node_geo_curve_trim.cc nodes/node_geo_deform_curves_on_surface.cc nodes/node_geo_delete_geometry.cc + nodes/node_geo_distribute_points_in_grid.cc nodes/node_geo_distribute_points_in_volume.cc nodes/node_geo_distribute_points_on_faces.cc nodes/node_geo_dual_mesh.cc @@ -78,6 +79,7 @@ set(SRC nodes/node_geo_flip_faces.cc nodes/node_geo_geometry_to_instance.cc nodes/node_geo_get_named_grid.cc + nodes/node_geo_grid_to_mesh.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_distribute_points_in_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc new file mode 100644 index 00000000000..2f2271b3d65 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc @@ -0,0 +1,277 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB +# include +# include +# include +#endif + +#include "DNA_node_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_pointcloud.hh" +#include "BKE_volume.hh" +#include "BKE_volume_grid.hh" + +#include "NOD_rna_define.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "GEO_randomize.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_distribute_points_in_grid_cc { + +enum class DistributeMode { + Random = 0, + Grid = 1, +}; + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value(); + b.add_input("Density") + .default_value(1.0f) + .min(0.0f) + .max(100000.0f) + .subtype(PROP_NONE) + .description( + "When combined with each voxel's value, determines the number of points to sample per " + "unit volume"); + b.add_input("Seed").min(-10000).max(10000).description( + "Seed used by the random number generator to generate random points"); + b.add_input("Spacing") + .default_value({0.3, 0.3, 0.3}) + .min(0.0001f) + .subtype(PROP_XYZ) + .description("Spacing between grid points"); + b.add_input("Threshold") + .default_value(0.1f) + .min(0.0f) + .max(FLT_MAX) + .description("Minimum density of a voxel to contain a grid point"); + b.add_output("Points").propagate_all(); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = int16_t(DistributeMode::Random); +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const auto mode = DistributeMode(node->custom1); + + bNodeSocket *sock_density = static_cast(node->inputs.first)->next; + bNodeSocket *sock_seed = sock_density->next; + bNodeSocket *sock_spacing = sock_seed->next; + bNodeSocket *sock_threshold = sock_spacing->next; + + bke::nodeSetSocketAvailability(ntree, sock_density, mode == DistributeMode::Random); + bke::nodeSetSocketAvailability(ntree, sock_seed, mode == DistributeMode::Random); + bke::nodeSetSocketAvailability(ntree, sock_spacing, mode == DistributeMode::Grid); + bke::nodeSetSocketAvailability(ntree, sock_threshold, mode == DistributeMode::Grid); +} + +#ifdef WITH_OPENVDB +/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */ +class PositionsVDBWrapper { + private: + float3 offset_fix_; + Vector &vector_; + + public: + PositionsVDBWrapper(Vector &vector, const float3 &offset_fix) + : offset_fix_(offset_fix), vector_(vector) + { + } + PositionsVDBWrapper(const PositionsVDBWrapper &wrapper) = default; + + void add(const openvdb::Vec3R &pos) + { + vector_.append(float3(float(pos[0]), float(pos[1]), float(pos[2])) + offset_fix_); + } +}; + +/* Use #std::mt19937 as a random number generator. It has a very long period and thus there should + * be no visible patterns in the generated points. */ +using RNGType = std::mt19937; +/* Non-uniform scatter allows the amount of points to be scaled with the volume's density. */ +using NonUniformPointScatterVDB = + openvdb::tools::NonUniformPointScatter; + +static void point_scatter_density_random(const openvdb::FloatGrid &grid, + const float density, + const int seed, + Vector &r_positions) +{ + /* Offset points by half a voxel so that grid points are aligned with world grid points. */ + const float3 offset_fix = {0.5f * float(grid.voxelSize().x()), + 0.5f * float(grid.voxelSize().y()), + 0.5f * float(grid.voxelSize().z())}; + /* Setup and call into OpenVDB's point scatter API. */ + PositionsVDBWrapper vdb_position_wrapper(r_positions, offset_fix); + RNGType random_generator(seed); + NonUniformPointScatterVDB point_scatter(vdb_position_wrapper, density, random_generator); + point_scatter(grid); +} + +static void point_scatter_density_grid(const openvdb::FloatGrid &grid, + const float3 spacing, + const float threshold, + Vector &r_positions) +{ + const openvdb::Vec3d half_voxel(0.5, 0.5, 0.5); + const openvdb::Vec3d voxel_spacing(double(spacing.x) / grid.voxelSize().x(), + double(spacing.y) / grid.voxelSize().y(), + double(spacing.z) / grid.voxelSize().z()); + + /* Abort if spacing is zero. */ + const double min_spacing = std::min(voxel_spacing.x(), + std::min(voxel_spacing.y(), voxel_spacing.z())); + if (std::abs(min_spacing) < 0.0001) { + return; + } + + /* Iterate through tiles and voxels on the grid. */ + for (openvdb::FloatGrid::ValueOnCIter cell = grid.cbeginValueOn(); cell; ++cell) { + /* Check if the cell's value meets the minimum threshold. */ + if (cell.getValue() < threshold) { + continue; + } + /* Compute the bounding box of each tile/voxel. */ + const openvdb::CoordBBox bbox = cell.getBoundingBox(); + const openvdb::Vec3d box_min = bbox.min().asVec3d() - half_voxel; + const openvdb::Vec3d box_max = bbox.max().asVec3d() + half_voxel; + + /* Pick a starting point rounded up to the nearest possible point. */ + double abs_spacing_x = std::abs(voxel_spacing.x()); + double abs_spacing_y = std::abs(voxel_spacing.y()); + double abs_spacing_z = std::abs(voxel_spacing.z()); + const openvdb::Vec3d start(ceil(box_min.x() / abs_spacing_x) * abs_spacing_x, + ceil(box_min.y() / abs_spacing_y) * abs_spacing_y, + ceil(box_min.z() / abs_spacing_z) * abs_spacing_z); + + /* Iterate through all possible points in box. */ + for (double x = start.x(); x < box_max.x(); x += abs_spacing_x) { + for (double y = start.y(); y < box_max.y(); y += abs_spacing_y) { + for (double z = start.z(); z < box_max.z(); z += abs_spacing_z) { + /* Transform with grid matrix and add point. */ + const openvdb::Vec3d idx_pos(x, y, z); + const openvdb::Vec3d local_pos = grid.indexToWorld(idx_pos + half_voxel); + r_positions.append({float(local_pos.x()), float(local_pos.y()), float(local_pos.z())}); + } + } + } + } +} + +#endif /* WITH_OPENVDB */ + +static void node_geo_exec(GeoNodeExecParams params) +{ +#ifdef WITH_OPENVDB + const bke::VolumeGrid volume_grid = params.extract_input>("Grid"); + if (!volume_grid) { + params.set_default_remaining_outputs(); + return; + } + + bke::VolumeTreeAccessToken tree_token; + const openvdb::GridBase &base_grid = volume_grid.grid(tree_token); + if (!base_grid.isType()) { + params.set_default_remaining_outputs(); + return; + } + const openvdb::FloatGrid &grid = static_cast(base_grid); + + const DistributeMode mode = DistributeMode(params.node().custom1); + + float density; + int seed; + float3 spacing{0, 0, 0}; + float threshold; + if (mode == DistributeMode::Random) { + density = params.extract_input("Density"); + seed = params.extract_input("Seed"); + } + else if (mode == DistributeMode::Grid) { + spacing = params.extract_input("Spacing"); + threshold = params.extract_input("Threshold"); + } + + Vector positions; + switch (mode) { + case DistributeMode::Random: + point_scatter_density_random(grid, density, seed, positions); + break; + case DistributeMode::Grid: + point_scatter_density_grid(grid, spacing, threshold, positions); + break; + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); + pointcloud->positions_for_write().copy_from(positions); + + geometry::debug_randomize_point_order(pointcloud); + + params.set_output("Points", GeometrySet::from_pointcloud(pointcloud)); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_rna(StructRNA *srna) +{ + static const EnumPropertyItem mode_items[] = { + {int(DistributeMode::Random), + "DENSITY_RANDOM", + 0, + "Random", + "Distribute points randomly inside of the volume"}, + {int(DistributeMode::Grid), + "DENSITY_GRID", + 0, + "Grid", + "Distribute the points in a grid pattern inside of the volume"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_node_enum(srna, + "mode", + "Distribution Method", + "Method to use for scattering points", + mode_items, + NOD_inline_enum_accessors(custom1), + int(DistributeMode::Random)); +} + +static void node_register() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, + GEO_NODE_DISTRIBUTE_POINTS_IN_GRID, + "Distribute Points in Grid", + NODE_CLASS_GEOMETRY); + ntype.initfunc = node_init; + ntype.updatefunc = node_update; + blender::bke::node_type_size(&ntype, 170, 100, 320); + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + ntype.draw_buttons = node_layout; + nodeRegisterType(&ntype); + + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_distribute_points_in_grid_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc new file mode 100644 index 00000000000..fb7c5198516 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc @@ -0,0 +1,56 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_material.h" +#include "BKE_volume_grid.hh" +#include "BKE_volume_to_mesh.hh" + +#include "GEO_randomize.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_grid_to_mesh_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Grid").hide_value(); + b.add_input("Threshold") + .default_value(0.1f) + .description("Values larger than the threshold are inside the generated mesh"); + b.add_input("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output("Mesh"); +} + +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; + Mesh *mesh = bke::volume_grid_to_mesh(grid.get().grid(tree_token), + params.extract_input("Threshold"), + params.extract_input("Adaptivity")); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + geometry::debug_randomize_mesh_order(mesh); + params.set_output("Mesh", GeometrySet::from_mesh(mesh)); +#else + node_geo_exec_with_missing_openvdb(params); +#endif +} + +static void node_register() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_GRID_TO_MESH, "Grid to Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_grid_to_mesh_cc