diff options
author | Iyad Ahmed <iyadahmed2001> | 2022-09-19 18:13:55 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2022-09-19 18:14:08 +0300 |
commit | b6e26a410cd29f32da1e8112607a61f29c2863c4 (patch) | |
tree | 84e442d59e6e367e02961b17b79ab1d77ce7a509 /source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc | |
parent | 0e6a8df6df28fc785ad25aef91a4599865d156f0 (diff) |
Geometry Nodes: Distribute Points in Volume
This commit adds a node to distribute points inside of volume grids.
The "Random" mode usese OpenVDB's "point scatter" implementation, and
there is also a "Grid" mode for uniform distributions. Both methods
operate on all of the float grids in the volume, using every voxel with
a value higher than the threshold. The random method is not stable as
the input volume deforms.
Based on a patch by Angus Stanton (@abstanton), which was based on a
patch by Kenzie (@kenziemac130).
Differential Revision: https://developer.blender.org/D15375
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc new file mode 100644 index 00000000000..24f81a81b3e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB +# include <openvdb/openvdb.h> +# include <openvdb/tools/Interpolation.h> +# include <openvdb/tools/PointScatter.h> +#endif + +#include "DNA_node_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_pointcloud.h" +#include "BKE_volume.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +NODE_STORAGE_FUNCS(NodeGeometryDistributePointsInVolume) + +static void geo_node_distribute_points_in_volume_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Volume")).supported_type(GEO_COMPONENT_TYPE_VOLUME); + b.add_input<decl::Float>(N_("Density")) + .default_value(1.0f) + .min(0.0f) + .max(100000.0f) + .subtype(PROP_NONE) + .description(N_("Number of points to sample per unit volume")); + b.add_input<decl::Int>(N_("Seed")) + .min(-10000) + .max(10000) + .description(N_("Seed used by the random number generator to generate random points")); + b.add_input<decl::Vector>(N_("Spacing")) + .default_value({0.3, 0.3, 0.3}) + .min(0.0001f) + .subtype(PROP_XYZ) + .description(N_("Spacing between grid points")); + b.add_input<decl::Float>(N_("Threshold")) + .default_value(0.1f) + .min(0.0f) + .max(FLT_MAX) + .description(N_("Minimum density of a volume cell to contain a grid point")); + b.add_output<decl::Geometry>(N_("Points")); +} + +static void geo_node_distribute_points_in_volume_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void node_distribute_points_in_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryDistributePointsInVolume *data = MEM_cnew<NodeGeometryDistributePointsInVolume>( + __func__); + data->mode = GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM; + node->storage = data; +} + +static void node_distribute_points_in_volume_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryDistributePointsInVolume &storage = node_storage(*node); + GeometryNodeDistributePointsInVolumeMode mode = + static_cast<GeometryNodeDistributePointsInVolumeMode>(storage.mode); + + bNodeSocket *sock_density = ((bNodeSocket *)(node->inputs.first))->next; + bNodeSocket *sock_seed = sock_density->next; + bNodeSocket *sock_spacing = sock_seed->next; + bNodeSocket *sock_threshold = sock_spacing->next; + + nodeSetSocketAvailability( + ntree, sock_density, mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM); + nodeSetSocketAvailability( + ntree, sock_seed, mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM); + nodeSetSocketAvailability( + ntree, sock_spacing, mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID); + nodeSetSocketAvailability( + ntree, sock_threshold, mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID); +} + +#ifdef WITH_OPENVDB +/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */ +class PositionsVDBWrapper { + private: + float3 offset_fix_; + Vector<float3> &vector_; + + public: + PositionsVDBWrapper(Vector<float3> &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<PositionsVDBWrapper, RNGType>; + +static void point_scatter_density_random(const openvdb::FloatGrid &grid, + const float density, + const int seed, + Vector<float3> &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 = PositionsVDBWrapper(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<float3> &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 geo_node_distribute_points_in_volume_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Volume"); + + const NodeGeometryDistributePointsInVolume &storage = node_storage(params.node()); + const GeometryNodeDistributePointsInVolumeMode mode = + static_cast<GeometryNodeDistributePointsInVolumeMode>(storage.mode); + +#ifdef WITH_OPENVDB + + float density; + int seed; + float3 spacing{0, 0, 0}; + float threshold; + + if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM) { + density = params.extract_input<float>("Density"); + seed = params.extract_input<int>("Seed"); + } + else if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID) { + spacing = params.extract_input<float3>("Spacing"); + threshold = params.extract_input<float>("Threshold"); + } + + geometry_set_in.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_volume()) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + const VolumeComponent *component = geometry_set.get_component_for_read<VolumeComponent>(); + const Volume *volume = component->get_for_read(); + + Vector<float3> positions; + + for (const int i : IndexRange(BKE_volume_num_grids(volume))) { + const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); + if (volume_grid == nullptr) { + continue; + } + + openvdb::GridBase::ConstPtr base_grid = BKE_volume_grid_openvdb_for_read(volume, + volume_grid); + if (!base_grid) { + continue; + } + + if (!base_grid->isType<openvdb::FloatGrid>()) { + continue; + } + + const openvdb::FloatGrid::ConstPtr grid = openvdb::gridConstPtrCast<openvdb::FloatGrid>( + base_grid); + + if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM) { + point_scatter_density_random(*grid, density, seed, positions); + } + else if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID) { + point_scatter_density_grid(*grid, spacing, threshold, positions); + } + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); + bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write(); + bke::SpanAttributeWriter<float3> point_positions = + point_attributes.lookup_or_add_for_write_only_span<float3>("position", ATTR_DOMAIN_POINT); + bke::SpanAttributeWriter<float> point_radii = + point_attributes.lookup_or_add_for_write_only_span<float>("radius", ATTR_DOMAIN_POINT); + + point_positions.span.copy_from(positions); + point_radii.span.fill(0.05f); + point_positions.finish(); + point_radii.finish(); + + geometry_set.replace_pointcloud(pointcloud); + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + }); + + params.set_output("Points", std::move(geometry_set_in)); + +#else /* WITH_OPENVDB */ + params.error_message_add(NodeWarningType::Error, TIP_("Blender is compiled without OpenVDB")); +#endif /* !WITH_OPENVDB */ +} +} // namespace blender::nodes + +void register_node_type_geo_distribute_points_in_volume() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, + GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME, + "Distribute Points in Volume", + NODE_CLASS_GEOMETRY); + node_type_storage(&ntype, + "NodeGeometryDistributePointsInVolume", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::node_distribute_points_in_volume_init); + node_type_update(&ntype, blender::nodes::node_distribute_points_in_volume_update); + node_type_size(&ntype, 170, 100, 320); + ntype.declare = blender::nodes::geo_node_distribute_points_in_volume_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_distribute_points_in_volume_exec; + ntype.draw_buttons = blender::nodes::geo_node_distribute_points_in_volume_layout; + nodeRegisterType(&ntype); +} |