Blender V5.0
volume_render.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
8
9#include "MEM_guardedalloc.h"
10
11#include "BLI_array.hh"
12#include "BLI_math_matrix.h"
13#include "BLI_math_vector.h"
15#include "BLI_task.hh"
16#include "BLI_vector.hh"
17
18#include "DNA_volume_types.h"
19
20#include "BKE_volume_grid.hh"
21#include "BKE_volume_openvdb.hh"
22#include "BKE_volume_render.hh"
23
24#ifdef WITH_OPENVDB
25# include <openvdb/openvdb.h>
26# include <openvdb/tools/Dense.h>
27#endif
28
29/* Dense Voxels */
30
31#ifdef WITH_OPENVDB
32
33template<typename GridType, typename VoxelType>
34static void extract_dense_voxels(const openvdb::GridBase &grid,
35 const openvdb::CoordBBox bbox,
36 VoxelType *r_voxels)
37{
38 BLI_assert(grid.isType<GridType>());
39 blender::threading::memory_bandwidth_bound_task(bbox.volume() * sizeof(VoxelType), [&]() {
40 openvdb::tools::Dense<VoxelType, openvdb::tools::LayoutXYZ> dense(bbox, r_voxels);
41 openvdb::tools::copyToDense(static_cast<const GridType &>(grid), dense);
42 });
43}
44
45static void extract_dense_float_voxels(const VolumeGridType grid_type,
46 const openvdb::GridBase &grid,
47 const openvdb::CoordBBox &bbox,
48 float *r_voxels)
49{
50 switch (grid_type) {
52 extract_dense_voxels<openvdb::BoolGrid, float>(grid, bbox, r_voxels);
53 return;
54 }
55 case VOLUME_GRID_FLOAT: {
56 extract_dense_voxels<openvdb::FloatGrid, float>(grid, bbox, r_voxels);
57 return;
58 }
59 case VOLUME_GRID_DOUBLE: {
60 extract_dense_voxels<openvdb::DoubleGrid, float>(grid, bbox, r_voxels);
61 return;
62 }
63 case VOLUME_GRID_INT: {
64 extract_dense_voxels<openvdb::Int32Grid, float>(grid, bbox, r_voxels);
65 return;
66 }
67 case VOLUME_GRID_INT64: {
68 extract_dense_voxels<openvdb::Int64Grid, float>(grid, bbox, r_voxels);
69 return;
70 }
71 case VOLUME_GRID_MASK: {
72 extract_dense_voxels<openvdb::MaskGrid, float>(grid, bbox, r_voxels);
73 return;
74 }
76 extract_dense_voxels<openvdb::Vec3fGrid, openvdb::Vec3f>(
77 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
78 return;
79 }
81 extract_dense_voxels<openvdb::Vec3dGrid, openvdb::Vec3f>(
82 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
83 return;
84 }
86 extract_dense_voxels<openvdb::Vec3IGrid, openvdb::Vec3f>(
87 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
88 return;
89 }
92 /* Zero channels to copy. */
93 break;
94 }
95}
96
97static void create_texture_to_object_matrix(const openvdb::Mat4d &grid_transform,
98 const openvdb::CoordBBox &bbox,
99 float r_texture_to_object[4][4])
100{
101 float index_to_object[4][4];
102 memcpy(index_to_object, openvdb::Mat4s(grid_transform).asPointer(), sizeof(index_to_object));
103
104 float texture_to_index[4][4];
105 const openvdb::Vec3f loc = bbox.min().asVec3s() - openvdb::Vec3s(0.5f);
106 const openvdb::Vec3f size = bbox.dim().asVec3s();
107 size_to_mat4(texture_to_index, size.asV());
108 copy_v3_v3(texture_to_index[3], loc.asV());
109
110 mul_m4_m4m4(r_texture_to_object, index_to_object, texture_to_index);
111}
112
113#endif
114
116 const blender::bke::VolumeGridData *volume_grid,
117 DenseFloatVolumeGrid *r_dense_grid)
118{
119#ifdef WITH_OPENVDB
120 const VolumeGridType grid_type = volume_grid->grid_type();
121 blender::bke::VolumeTreeAccessToken tree_token;
122 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
123
124 const openvdb::CoordBBox bbox = grid.evalActiveVoxelBoundingBox();
125 if (bbox.empty()) {
126 return false;
127 }
128 const std::array<int64_t, 6> bbox_indices = {UNPACK3(openvdb::math::Abs(bbox.min())),
129 UNPACK3(openvdb::math::Abs(bbox.max()))};
130 const int64_t max_bbox_index = *std::max_element(bbox_indices.begin(), bbox_indices.end());
131 if (max_bbox_index > (1 << 30)) {
132 /* There is an integer overflow when trying to extract dense voxels when the indices are very
133 * large. */
134 return false;
135 }
136
137 const openvdb::Vec3i resolution = bbox.dim().asVec3i();
138 const int64_t num_voxels = int64_t(resolution[0]) * int64_t(resolution[1]) *
139 int64_t(resolution[2]);
140 const int channels = blender::bke::volume_grid::get_channels_num(grid_type);
141 float *voxels = MEM_malloc_arrayN<float>(size_t(channels) * size_t(num_voxels), __func__);
142 if (voxels == nullptr) {
143 return false;
144 }
145
146 extract_dense_float_voxels(grid_type, grid, bbox, voxels);
147 create_texture_to_object_matrix(grid.transform().baseMap()->getAffineMap()->getMat4(),
148 bbox,
149 r_dense_grid->texture_to_object);
150
151 r_dense_grid->voxels = voxels;
152 r_dense_grid->channels = channels;
153 copy_v3_v3_int(r_dense_grid->resolution, resolution.asV());
154 return true;
155#endif
156 UNUSED_VARS(volume, volume_grid, r_dense_grid);
157 return false;
158}
159
161{
162 if (dense_grid->voxels != nullptr) {
163 MEM_freeN(dense_grid->voxels);
164 }
165}
166
167/* Wireframe */
168
169#ifdef WITH_OPENVDB
170
172template<typename GridType>
173static blender::Vector<openvdb::CoordBBox> get_bounding_boxes(const GridType &grid,
174 const bool coarse)
175{
176 using TreeType = typename GridType::TreeType;
177 using Depth2Type = typename TreeType::RootNodeType::ChildNodeType::ChildNodeType;
178 using NodeCIter = typename TreeType::NodeCIter;
179
181 const int depth = coarse ? 2 : 3;
182
183 NodeCIter iter = grid.tree().cbeginNode();
184 iter.setMaxDepth(depth);
185
186 for (; iter; ++iter) {
187 if (iter.getDepth() != depth) {
188 continue;
189 }
190
191 openvdb::CoordBBox box;
192 if (depth == 2) {
193 /* Internal node at depth 2. */
194 const Depth2Type *node = nullptr;
195 iter.getNode(node);
196 if (node) {
197 node->evalActiveBoundingBox(box, false);
198 }
199 else {
200 continue;
201 }
202 }
203 else {
204 /* Leaf node. */
205 if (!iter.getBoundingBox(box)) {
206 continue;
207 }
208 }
209
210 /* +1 to convert from exclusive to inclusive bounds. */
211 box.max() = box.max().offsetBy(1);
212
213 boxes.append(box);
214 }
215
216 return boxes;
217}
218
219struct GetBoundingBoxesOp {
220 const openvdb::GridBase &grid;
221 const bool coarse;
222
223 template<typename GridType> blender::Vector<openvdb::CoordBBox> operator()()
224 {
225 return get_bounding_boxes(static_cast<const GridType &>(grid), coarse);
226 }
227};
228
229static blender::Vector<openvdb::CoordBBox> get_bounding_boxes(VolumeGridType grid_type,
230 const openvdb::GridBase &grid,
231 const bool coarse)
232{
233 GetBoundingBoxesOp op{grid, coarse};
234 return BKE_volume_grid_type_operation(grid_type, op);
235}
236
237static void boxes_to_center_points(blender::Span<openvdb::CoordBBox> boxes,
238 const openvdb::math::Transform &transform,
240{
241 BLI_assert(boxes.size() == r_verts.size());
242 for (const int i : boxes.index_range()) {
243 openvdb::Vec3d center = transform.indexToWorld(boxes[i].getCenter());
244 r_verts[i] = blender::float3(center[0], center[1], center[2]);
245 }
246}
247
248static void boxes_to_corner_points(blender::Span<openvdb::CoordBBox> boxes,
249 const openvdb::math::Transform &transform,
251{
252 BLI_assert(boxes.size() * 8 == r_verts.size());
253 for (const int i : boxes.index_range()) {
254 const openvdb::CoordBBox &box = boxes[i];
255
256 /* The ordering of the corner points is lexicographic. */
257 std::array<openvdb::Coord, 8> corners;
258 box.getCornerPoints(corners.data());
259
260 for (int j = 0; j < 8; j++) {
261 openvdb::Coord corner_i = corners[j];
262 openvdb::Vec3d corner_d = transform.indexToWorld(corner_i);
263 r_verts[8 * i + j] = blender::float3(corner_d[0], corner_d[1], corner_d[2]);
264 }
265 }
266}
267
268static void boxes_to_edge_mesh(blender::Span<openvdb::CoordBBox> boxes,
269 const openvdb::math::Transform &transform,
271 blender::Vector<std::array<int, 2>> &r_edges)
272{
273 /* TODO: Deduplicate edges, hide flat edges? */
274
275 const int box_edges[12][2] = {
276 {0, 1},
277 {0, 2},
278 {0, 4},
279 {1, 3},
280 {1, 5},
281 {2, 3},
282 {2, 6},
283 {3, 7},
284 {4, 5},
285 {4, 6},
286 {5, 7},
287 {6, 7},
288 };
289
290 int vert_offset = r_verts.size();
291 int edge_offset = r_edges.size();
292
293 const int vert_amount = 8 * boxes.size();
294 const int edge_amount = 12 * boxes.size();
295
296 r_verts.resize(r_verts.size() + vert_amount);
297 r_edges.resize(r_edges.size() + edge_amount);
298 boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount));
299
300 for (int i = 0; i < boxes.size(); i++) {
301 for (int j = 0; j < 12; j++) {
302 r_edges[edge_offset + j] = {vert_offset + box_edges[j][0], vert_offset + box_edges[j][1]};
303 }
304 vert_offset += 8;
305 edge_offset += 12;
306 }
307}
308
309static void boxes_to_cube_mesh(blender::Span<openvdb::CoordBBox> boxes,
310 const openvdb::math::Transform &transform,
312 blender::Vector<std::array<int, 3>> &r_tris)
313{
314 const int box_tris[12][3] = {
315 {0, 1, 4},
316 {4, 1, 5},
317 {0, 2, 1},
318 {1, 2, 3},
319 {1, 3, 5},
320 {5, 3, 7},
321 {6, 4, 5},
322 {7, 5, 6},
323 {2, 0, 4},
324 {2, 4, 6},
325 {3, 7, 2},
326 {6, 2, 7},
327 };
328
329 int vert_offset = r_verts.size();
330 int tri_offset = r_tris.size();
331
332 const int vert_amount = 8 * boxes.size();
333 const int tri_amount = 12 * boxes.size();
334
335 r_verts.resize(r_verts.size() + vert_amount);
336 r_tris.resize(r_tris.size() + tri_amount);
337 boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount));
338
339 for (int i = 0; i < boxes.size(); i++) {
340 for (int j = 0; j < 12; j++) {
341 r_tris[tri_offset + j] = {vert_offset + box_tris[j][0],
342 vert_offset + box_tris[j][1],
343 vert_offset + box_tris[j][2]};
344 }
345 vert_offset += 8;
346 tri_offset += 12;
347 }
348}
349
350#endif
351
353 const blender::bke::VolumeGridData *volume_grid,
355 void *cb_userdata)
356{
358 cb(cb_userdata, nullptr, nullptr, 0, 0);
359 return;
360 }
361
362#ifdef WITH_OPENVDB
363 blender::bke::VolumeTreeAccessToken tree_token;
364 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
365
367 /* Bounding box. */
368 openvdb::CoordBBox box;
371 if (grid.baseTree().evalLeafBoundingBox(box)) {
372 boxes_to_edge_mesh({box}, grid.transform(), verts, edges);
373 }
374 cb(cb_userdata,
375 (float (*)[3])verts.data(),
376 (int (*)[2])edges.data(),
377 verts.size(),
378 edges.size());
379 }
380 else {
381 blender::Vector<openvdb::CoordBBox> boxes = get_bounding_boxes(
382 volume_grid->grid_type(),
383 grid,
385
388
390 verts.resize(boxes.size());
391 boxes_to_center_points(boxes, grid.transform(), verts);
392 }
393 else {
394 boxes_to_edge_mesh(boxes, grid.transform(), verts, edges);
395 }
396
397 cb(cb_userdata,
398 (float (*)[3])verts.data(),
399 (int (*)[2])edges.data(),
400 verts.size(),
401 edges.size());
402 }
403
404#else
405 UNUSED_VARS(volume, volume_grid);
406 cb(cb_userdata, nullptr, nullptr, 0, 0);
407#endif
408}
409
410#ifdef WITH_OPENVDB
411static void grow_triangles(blender::MutableSpan<blender::float3> verts,
412 blender::Span<std::array<int, 3>> tris,
413 const float factor)
414{
415 /* Compute the offset for every vertex based on the connected edges.
416 * This formula simply tries increases the length of all edges. */
417 blender::Array<blender::float3> offsets(verts.size(), {0, 0, 0});
418 blender::Array<float> weights(verts.size(), 0.0f);
419 for (const std::array<int, 3> &tri : tris) {
420 offsets[tri[0]] += factor * (2 * verts[tri[0]] - verts[tri[1]] - verts[tri[2]]);
421 offsets[tri[1]] += factor * (2 * verts[tri[1]] - verts[tri[0]] - verts[tri[2]]);
422 offsets[tri[2]] += factor * (2 * verts[tri[2]] - verts[tri[0]] - verts[tri[1]]);
423 weights[tri[0]] += 1.0;
424 weights[tri[1]] += 1.0;
425 weights[tri[2]] += 1.0;
426 }
427 /* Apply the computed offsets. */
428 for (const int i : verts.index_range()) {
429 if (weights[i] > 0.0f) {
430 verts[i] += offsets[i] / weights[i];
431 }
432 }
433}
434#endif /* WITH_OPENVDB */
435
437 const blender::bke::VolumeGridData *volume_grid,
439 void *cb_userdata)
440{
441#ifdef WITH_OPENVDB
442 blender::bke::VolumeTreeAccessToken tree_token;
443 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
444 blender::Vector<openvdb::CoordBBox> boxes = get_bounding_boxes(
445 volume_grid->grid_type(), grid, true);
446
449 boxes_to_cube_mesh(boxes, grid.transform(), verts, tris);
450
451 /* By slightly scaling the individual boxes up, we can avoid some artifacts when drawing the
452 * selection outline. */
453 const float offset_factor = 0.01f;
454 grow_triangles(verts, tris, offset_factor);
455
456 cb(cb_userdata, (float (*)[3])verts.data(), (int (*)[3])tris.data(), verts.size(), tris.size());
457#else
458 UNUSED_VARS(volume_grid);
459 cb(cb_userdata, nullptr, nullptr, 0, 0);
460#endif
461}
462
463/* Render */
464
465float BKE_volume_density_scale(const Volume *volume, const float matrix[4][4])
466{
467 if (volume->render.space == VOLUME_SPACE_OBJECT) {
468 float unit[3] = {1.0f, 1.0f, 1.0f};
469 normalize_v3(unit);
470 mul_mat3_m4_v3(matrix, unit);
471 return 1.0f / len_v3(unit);
472 }
473
474 return 1.0f;
475}
VolumeGridType
@ VOLUME_GRID_VECTOR_FLOAT
@ VOLUME_GRID_MASK
@ VOLUME_GRID_VECTOR_DOUBLE
@ VOLUME_GRID_VECTOR_INT
@ VOLUME_GRID_UNKNOWN
@ VOLUME_GRID_DOUBLE
@ VOLUME_GRID_BOOLEAN
@ VOLUME_GRID_INT
@ VOLUME_GRID_INT64
@ VOLUME_GRID_POINTS
@ VOLUME_GRID_FLOAT
Volume data-block rendering and viewport drawing utilities.
void(*)(void *userdata, float(*verts)[3], int(*tris)[3], int totvert, int tottris) BKE_volume_selection_surface_cb
void(*)( void *userdata, const float(*verts)[3], const int(*edges)[2], int totvert, int totedge) BKE_volume_wireframe_cb
#define BLI_assert(a)
Definition BLI_assert.h:46
void mul_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4])
void size_to_mat4(float R[4][4], const float size[3])
void mul_mat3_m4_v3(const float mat[4][4], float r[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_v3_int(int r[3], const int a[3])
MINLINE float normalize_v3(float n[3])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
#define UNUSED_VARS(...)
#define UNPACK3(a)
@ VOLUME_SPACE_OBJECT
@ VOLUME_WIREFRAME_NONE
@ VOLUME_WIREFRAME_POINTS
@ VOLUME_WIREFRAME_BOUNDS
@ VOLUME_WIREFRAME_COARSE
Read Guarded memory(de)allocation.
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
SIMD_FORCE_INLINE btVector3 operator()(const btVector3 &x) const
Return the transform of the vector.
Definition btTransform.h:90
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
static float verts[][3]
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
int get_channels_num(VolumeGridType type)
std::array< VecBase< T, 3 >, 8 > corners(const Bounds< VecBase< T, 3 > > &bounds)
void memory_bandwidth_bound_task(const int64_t approximate_bytes_touched, const Function &function)
Definition BLI_task.hh:265
VecBase< float, 3 > float3
VolumeRender render
VolumeDisplay display
i
Definition text_draw.cc:230
void BKE_volume_dense_float_grid_clear(DenseFloatVolumeGrid *dense_grid)
bool BKE_volume_grid_dense_floats(const Volume *volume, const blender::bke::VolumeGridData *volume_grid, DenseFloatVolumeGrid *r_dense_grid)
float BKE_volume_density_scale(const Volume *volume, const float matrix[4][4])
void BKE_volume_grid_selection_surface(const Volume *, const blender::bke::VolumeGridData *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata)
void BKE_volume_grid_wireframe(const Volume *volume, const blender::bke::VolumeGridData *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata)