Blender V5.0
node_geo_distribute_points_in_volume.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#ifdef WITH_OPENVDB
6# include <openvdb/openvdb.h>
7# include <openvdb/tools/Interpolation.h>
8# include <openvdb/tools/PointScatter.h>
9
10# include <algorithm>
11#endif
12
13#include "DNA_node_types.h"
15
16#include "BKE_pointcloud.hh"
17#include "BKE_volume.hh"
18#include "BKE_volume_grid.hh"
19
21#include "GEO_randomize.hh"
22
23#include "node_geometry_util.hh"
24
26
28
29static const EnumPropertyItem mode_items[] = {
31 "DENSITY_RANDOM",
32 0,
33 N_("Random"),
34 N_("Distribute points randomly inside of the volume")},
36 "DENSITY_GRID",
37 0,
38 N_("Grid"),
39 N_("Distribute the points in a grid pattern inside of the volume")},
40 {0, nullptr, 0, nullptr, nullptr},
41};
42
44{
45 b.add_input<decl::Geometry>("Volume")
46 .supported_type(GeometryComponent::Type::Volume)
48 .description("Volume with fog grids that points are scattered in");
49 b.add_input<decl::Menu>("Mode")
50 .static_items(mode_items)
52 .description("Method to use for scattering points");
53 b.add_input<decl::Float>("Density")
54 .default_value(1.0f)
55 .min(0.0f)
56 .max(100000.0f)
58 .description("Number of points to sample per unit volume")
60 b.add_input<decl::Int>("Seed")
61 .min(-10000)
62 .max(10000)
63 .description("Seed used by the random number generator to generate random points")
65 b.add_input<decl::Vector>("Spacing")
66 .default_value({0.3, 0.3, 0.3})
67 .min(0.0001f)
69 .description("Spacing between grid points")
71 b.add_input<decl::Float>("Threshold")
72 .default_value(0.1f)
73 .min(0.0f)
74 .max(FLT_MAX)
75 .description("Minimum density of a volume cell to contain a grid point")
77 b.add_output<decl::Geometry>("Points").propagate_all();
78}
79
80static void node_init(bNodeTree * /*tree*/, bNode *node)
81{
82 /* Still used for forward compatibility. */
84}
85
86#ifdef WITH_OPENVDB
87/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */
88class PositionsVDBWrapper {
89 private:
90 Vector<float3> &vector_;
91
92 public:
93 PositionsVDBWrapper(Vector<float3> &vector) : vector_(vector) {}
94 PositionsVDBWrapper(const PositionsVDBWrapper &wrapper) = default;
95
96 void add(const openvdb::Vec3R &pos)
97 {
98 vector_.append(float3(float(pos[0]), float(pos[1]), float(pos[2])));
99 }
100};
101
102/* Use #std::mt19937 as a random number generator,
103 * it has a very long period and thus there should be no visible patterns in the generated points.
104 */
105using RNGType = std::mt19937;
106/* Non-uniform scatter allows the amount of points to be scaled with the volume's density. */
107using NonUniformPointScatterVDB =
108 openvdb::tools::NonUniformPointScatter<PositionsVDBWrapper, RNGType>;
109
110static void point_scatter_density_random(const openvdb::FloatGrid &grid,
111 const float density,
112 const int seed,
113 Vector<float3> &r_positions)
114{
115 /* Setup and call into OpenVDB's point scatter API. */
116 PositionsVDBWrapper vdb_position_wrapper = PositionsVDBWrapper(r_positions);
117 RNGType random_generator(seed);
118 NonUniformPointScatterVDB point_scatter(vdb_position_wrapper, density, random_generator);
119 point_scatter(grid);
120}
121
122static void point_scatter_density_grid(const openvdb::FloatGrid &grid,
123 const float3 spacing,
124 const float threshold,
125 Vector<float3> &r_positions)
126{
127 const openvdb::Vec3d half_voxel(0.5, 0.5, 0.5);
128 const openvdb::Vec3d voxel_spacing(double(spacing.x) / grid.voxelSize().x(),
129 double(spacing.y) / grid.voxelSize().y(),
130 double(spacing.z) / grid.voxelSize().z());
131
132 /* Abort if spacing is zero. */
133 const double min_spacing = std::min({voxel_spacing.x(), voxel_spacing.y(), voxel_spacing.z()});
134 if (std::abs(min_spacing) < 0.0001) {
135 return;
136 }
137
138 /* Iterate through tiles and voxels on the grid. */
139 for (openvdb::FloatGrid::ValueOnCIter cell = grid.cbeginValueOn(); cell; ++cell) {
140 /* Check if the cell's value meets the minimum threshold. */
141 if (cell.getValue() < threshold) {
142 continue;
143 }
144 /* Compute the bounding box of each tile/voxel. */
145 const openvdb::CoordBBox bbox = cell.getBoundingBox();
146 const openvdb::Vec3d box_min = bbox.min().asVec3d() - half_voxel;
147 const openvdb::Vec3d box_max = bbox.max().asVec3d() + half_voxel;
148
149 /* Pick a starting point rounded up to the nearest possible point. */
150 double abs_spacing_x = std::abs(voxel_spacing.x());
151 double abs_spacing_y = std::abs(voxel_spacing.y());
152 double abs_spacing_z = std::abs(voxel_spacing.z());
153 const openvdb::Vec3d start(ceil(box_min.x() / abs_spacing_x) * abs_spacing_x,
154 ceil(box_min.y() / abs_spacing_y) * abs_spacing_y,
155 ceil(box_min.z() / abs_spacing_z) * abs_spacing_z);
156
157 /* Iterate through all possible points in box. */
158 for (double x = start.x(); x < box_max.x(); x += abs_spacing_x) {
159 for (double y = start.y(); y < box_max.y(); y += abs_spacing_y) {
160 for (double z = start.z(); z < box_max.z(); z += abs_spacing_z) {
161 /* Transform with grid matrix and add point. */
162 const openvdb::Vec3d idx_pos(x, y, z);
163 const openvdb::Vec3d local_pos = grid.indexToWorld(idx_pos);
164 r_positions.append({float(local_pos.x()), float(local_pos.y()), float(local_pos.z())});
165 }
166 }
167 }
168 }
169}
170
171#endif /* WITH_OPENVDB */
172
174{
175#ifdef WITH_OPENVDB
176 GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume");
177 const auto mode = params.extract_input<GeometryNodeDistributePointsInVolumeMode>("Mode");
178
179 float density;
180 int seed;
181 float3 spacing{0, 0, 0};
182 float threshold;
184 density = params.extract_input<float>("Density");
185 seed = params.extract_input<int>("Seed");
186 }
188 spacing = params.extract_input<float3>("Spacing");
189 threshold = params.extract_input<float>("Threshold");
190 }
191
192 geometry::foreach_real_geometry(geometry_set, [&](GeometrySet &geometry_set) {
193 if (!geometry_set.has_volume()) {
194 geometry_set.keep_only({GeometryComponent::Type::Edit});
195 return;
196 }
197 const VolumeComponent *component = geometry_set.get_component<VolumeComponent>();
198 const Volume *volume = component->get();
199 BKE_volume_load(volume, params.bmain());
200
201 Vector<float3> positions;
202
203 for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
204 const bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i);
205 if (volume_grid == nullptr) {
206 continue;
207 }
208
209 bke::VolumeTreeAccessToken tree_token;
210 const openvdb::GridBase &base_grid = volume_grid->grid(tree_token);
211
212 if (!base_grid.isType<openvdb::FloatGrid>()) {
213 continue;
214 }
215
216 const openvdb::FloatGrid &grid = static_cast<const openvdb::FloatGrid &>(base_grid);
217
219 point_scatter_density_random(grid, density, seed, positions);
220 }
222 point_scatter_density_grid(grid, spacing, threshold, positions);
223 }
224 }
225
226 PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size());
227 bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
228 pointcloud->positions_for_write().copy_from(positions);
230 point_attributes.lookup_or_add_for_write_only_span<float>("radius", AttrDomain::Point);
231
232 point_radii.span.fill(0.05f);
233 point_radii.finish();
234
236
237 geometry_set.replace_pointcloud(pointcloud);
238 geometry_set.keep_only({GeometryComponent::Type::PointCloud, GeometryComponent::Type::Edit});
239 });
240
241 params.set_output("Points", std::move(geometry_set));
242
243#else
245#endif
246}
247
248static void node_register()
249{
250 static blender::bke::bNodeType ntype;
252 &ntype, "GeometryNodeDistributePointsInVolume", GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME);
253 ntype.ui_name = "Distribute Points in Volume";
254 ntype.ui_description = "Generate points inside a volume";
255 ntype.enum_name_legacy = "DISTRIBUTE_POINTS_IN_VOLUME";
258 "NodeGeometryDistributePointsInVolume",
261 ntype.initfunc = node_init;
262 blender::bke::node_type_size(ntype, 170, 100, 320);
263 ntype.declare = node_declare;
266}
268
269} // namespace blender::nodes::node_geo_distribute_points_in_volume_cc
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME
General operations for point clouds.
PointCloud * BKE_pointcloud_new_nomain(int totpoint)
Volume data-block.
int BKE_volume_num_grids(const Volume *volume)
bool BKE_volume_load(const Volume *volume, const Main *bmain)
const blender::bke::VolumeGridData * BKE_volume_grid_get(const Volume *volume, int grid_index)
#define BLT_I18NCONTEXT_ID_ID
GeometryNodeDistributePointsInVolumeMode
@ GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID
@ GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_XYZ
Definition RNA_types.hh:269
@ PROP_NONE
Definition RNA_types.hh:233
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
static unsigned long seed
Definition btSoftBody.h:39
void append(const T &value)
int64_t size() const
void append(const T &value)
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
std::optional< std::string > translation_context
nullptr float
uint pos
#define ceil
VecBase< float, 3 > float3
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
static void add(blender::Map< std::string, std::string > &messages, Message &msg)
Definition msgfmt.cc:222
void node_type_size(bNodeType &ntype, int width, int minwidth, int maxwidth)
Definition node.cc:5384
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
void foreach_real_geometry(bke::GeometrySet &geometry, FunctionRef< void(bke::GeometrySet &geometry_set)> fn)
void debug_randomize_point_order(PointCloud *pointcloud)
Definition randomize.cc:242
void node_geo_exec_with_missing_openvdb(GeoNodeExecParams &params)
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
#define min(a, b)
Definition sort.cc:36
#define FLT_MAX
Definition stdcycles.h:14
void * storage
void keep_only(Span< GeometryComponent::Type > component_types)
void replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const GeometryComponent * get_component(GeometryComponent::Type component_type) const
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
float z
Definition sky_math.h:136
float y
Definition sky_math.h:136
float x
Definition sky_math.h:136
i
Definition text_draw.cc:230
#define N_(msgid)