Blender V5.0
blenkernel/intern/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
8
9#include <optional>
10
11#include "MEM_guardedalloc.h"
12
13#include "DNA_defaults.h"
14#include "DNA_material_types.h"
15#include "DNA_object_types.h"
16#include "DNA_scene_types.h"
17#include "DNA_volume_types.h"
18
19#include "BLI_bounds.hh"
20#include "BLI_fileops.h"
21#include "BLI_index_range.hh"
22#include "BLI_math_base.h"
25#include "BLI_path_utils.hh"
26#include "BLI_string.h"
27#include "BLI_string_ref.hh"
28#include "BLI_utildefines.h"
29
30#include "BKE_anim_data.hh"
32#include "BKE_bpath.hh"
33#include "BKE_geometry_set.hh"
34#include "BKE_global.hh"
35#include "BKE_idtype.hh"
36#include "BKE_lib_id.hh"
37#include "BKE_lib_query.hh"
38#include "BKE_lib_remap.hh"
39#include "BKE_library.hh"
40#include "BKE_main.hh"
41#include "BKE_modifier.hh"
42#include "BKE_object.hh"
43#include "BKE_object_types.hh"
44#include "BKE_packedFile.hh"
45#include "BKE_report.hh"
46#include "BKE_scene.hh"
47#include "BKE_volume.hh"
48#include "BKE_volume_grid.hh"
50#include "BKE_volume_openvdb.hh"
51
52#include "BLT_translation.hh"
53
55
56#include "BLO_read_write.hh"
57
58#include "CLG_log.h"
59
60#ifdef WITH_OPENVDB
61static CLG_LogRef LOG = {"geom.volume"};
62#endif
63
64#define VOLUME_FRAME_NONE INT_MAX
65
66using blender::float3;
71using blender::bke::GVolumeGrid;
72
73#ifdef WITH_OPENVDB
74# include <list>
75
76# include <openvdb/openvdb.h>
77# include <openvdb/points/PointDataGrid.h>
78# include <openvdb/tools/GridTransformer.h>
79
80/* Volume Grid Vector
81 *
82 * List of grids contained in a volume datablock. This is runtime-only data,
83 * the actual grids are always saved in a VDB file. */
84
85struct VolumeGridVector : public std::list<GVolumeGrid> {
86 VolumeGridVector() : metadata(new openvdb::MetaMap())
87 {
88 filepath[0] = '\0';
89 }
90
91 VolumeGridVector(const VolumeGridVector &other)
92 : std::list<GVolumeGrid>(other), error_msg(other.error_msg), metadata(other.metadata)
93 {
94 memcpy(filepath, other.filepath, sizeof(filepath));
95 }
96
97 bool is_loaded() const
98 {
99 return filepath[0] != '\0';
100 }
101
102 void clear_all()
103 {
104 std::list<GVolumeGrid>::clear();
105 filepath[0] = '\0';
106 error_msg.clear();
107 metadata.reset();
108 }
109
110 /* Mutex for file loading of grids list. `const` write access to the fields after this must be
111 * protected by locking with this mutex. */
112 mutable blender::Mutex mutex;
113 /* Absolute file path that grids have been loaded from. */
114 char filepath[FILE_MAX];
115 /* File loading error message. */
116 std::string error_msg;
117 /* File Metadata. */
118 openvdb::MetaMap::Ptr metadata;
119};
120#endif
121
122/* Module */
123
125{
126#ifdef WITH_OPENVDB
127 openvdb::initialize();
128#endif
129}
130
131/* Volume datablock */
132
133static void volume_init_data(ID *id)
134{
135 Volume *volume = (Volume *)id;
137
139
140 volume->runtime = MEM_new<blender::bke::VolumeRuntime>(__func__);
141
142 BKE_volume_init_grids(volume);
143
144 STRNCPY(volume->velocity_grid, "velocity");
145}
146
147static void volume_copy_data(Main * /*bmain*/,
148 std::optional<Library *> /*owner_library*/,
149 ID *id_dst,
150 const ID *id_src,
151 const int /*flag*/)
152{
153 Volume *volume_dst = (Volume *)id_dst;
154 const Volume *volume_src = (const Volume *)id_src;
155 volume_dst->runtime = MEM_new<blender::bke::VolumeRuntime>(__func__);
156
157 if (volume_src->packedfile) {
158 volume_dst->packedfile = BKE_packedfile_duplicate(volume_src->packedfile);
159 }
160
161 volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
162#ifdef WITH_OPENVDB
163 if (volume_src->runtime->grids) {
164 const VolumeGridVector &grids_src = *(volume_src->runtime->grids);
165 volume_dst->runtime->grids = MEM_new<VolumeGridVector>(__func__, grids_src);
166 }
167#endif
168
169 volume_dst->runtime->frame = volume_src->runtime->frame;
170 STRNCPY(volume_dst->runtime->velocity_x_grid, volume_src->runtime->velocity_x_grid);
171 STRNCPY(volume_dst->runtime->velocity_y_grid, volume_src->runtime->velocity_y_grid);
172 STRNCPY(volume_dst->runtime->velocity_z_grid, volume_src->runtime->velocity_z_grid);
173
174 if (volume_src->runtime->bake_materials) {
175 volume_dst->runtime->bake_materials = std::make_unique<blender::bke::bake::BakeMaterialsList>(
176 *volume_src->runtime->bake_materials);
177 }
178
179 volume_dst->batch_cache = nullptr;
180}
181
182static void volume_free_data(ID *id)
183{
184 Volume *volume = (Volume *)id;
185 BKE_animdata_free(&volume->id, false);
187 MEM_SAFE_FREE(volume->mat);
188 if (volume->packedfile) {
190 volume->packedfile = nullptr;
191 }
192#ifdef WITH_OPENVDB
193 MEM_delete(volume->runtime->grids);
194 volume->runtime->grids = nullptr;
195 /* Deleting the volume might have made some grids completely unused, so they can be freed. */
196 blender::bke::volume_grid::file_cache::unload_unused();
197#endif
198 MEM_delete(volume->runtime);
199}
200
202{
203 Volume *volume = (Volume *)id;
204 for (int i = 0; i < volume->totcol; i++) {
206 }
207}
208
209static void volume_foreach_cache(ID *id,
210 IDTypeForeachCacheFunctionCallback function_callback,
211 void *user_data)
212{
213 Volume *volume = (Volume *)id;
214 IDCacheKey key = {
215 /*id_session_uid*/ id->session_uid,
216 /*identifier*/ 1,
217 };
218
219 function_callback(id, &key, (void **)&volume->runtime->grids, 0, user_data);
220}
221
222static void volume_foreach_path(ID *id, BPathForeachPathData *bpath_data)
223{
224 Volume *volume = reinterpret_cast<Volume *>(id);
225
226 if (volume->packedfile != nullptr &&
227 (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0)
228 {
229 return;
230 }
231
232 BKE_bpath_foreach_path_fixed_process(bpath_data, volume->filepath, sizeof(volume->filepath));
233}
234
235static void volume_blend_write(BlendWriter *writer, ID *id, const void *id_address)
236{
237 Volume *volume = (Volume *)id;
238 const bool is_undo = BLO_write_is_undo(writer);
239
240 /* Do not store packed files in case this is a library override ID. */
241 if (ID_IS_OVERRIDE_LIBRARY(volume) && !is_undo) {
242 volume->packedfile = nullptr;
243 }
244
245 /* write LibData */
246 BLO_write_id_struct(writer, Volume, id_address, &volume->id);
247 BKE_id_blend_write(writer, &volume->id);
248
249 /* direct data */
250 BLO_write_pointer_array(writer, volume->totcol, volume->mat);
251
252 BKE_packedfile_blend_write(writer, volume->packedfile);
253}
254
256{
257 Volume *volume = (Volume *)id;
258 volume->runtime = MEM_new<blender::bke::VolumeRuntime>(__func__);
259
260 BKE_packedfile_blend_read(reader, &volume->packedfile, volume->filepath);
261 volume->runtime->frame = 0;
262
263 /* materials */
264 BLO_read_pointer_array(reader, volume->totcol, (void **)&volume->mat);
265}
266
268{
269 Volume *volume = reinterpret_cast<Volume *>(id);
270
271 /* Needs to be done *after* cache pointers are restored (call to
272 * `foreach_cache`/`blo_cache_storage_entry_restore_in_new`), easier for now to do it in
273 * lib_link... */
274 BKE_volume_init_grids(volume);
275}
276
278 /*id_code*/ Volume::id_type,
279 /*id_filter*/ FILTER_ID_VO,
280 /*dependencies_id_types*/ FILTER_ID_MA,
281 /*main_listbase_index*/ INDEX_ID_VO,
282 /*struct_size*/ sizeof(Volume),
283 /*name*/ "Volume",
284 /*name_plural*/ N_("volumes"),
285 /*translation_context*/ BLT_I18NCONTEXT_ID_VOLUME,
287 /*asset_type_info*/ nullptr,
288
289 /*init_data*/ volume_init_data,
290 /*copy_data*/ volume_copy_data,
291 /*free_data*/ volume_free_data,
292 /*make_local*/ nullptr,
293 /*foreach_id*/ volume_foreach_id,
294 /*foreach_cache*/ volume_foreach_cache,
295 /*foreach_path*/ volume_foreach_path,
296 /*foreach_working_space_color*/ nullptr,
297 /*owner_pointer_get*/ nullptr,
298
299 /*blend_write*/ volume_blend_write,
300 /*blend_read_data*/ volume_blend_read_data,
301 /*blend_read_after_liblink*/ volume_blend_read_after_liblink,
302
303 /*blend_read_undo_preserve*/ nullptr,
304
305 /*lib_override_apply_post*/ nullptr,
306};
307
309{
310#ifdef WITH_OPENVDB
311 if (volume->runtime->grids == nullptr) {
312 volume->runtime->grids = MEM_new<VolumeGridVector>(__func__);
313 }
314#else
315 UNUSED_VARS(volume);
316#endif
317}
318
319Volume *BKE_volume_add(Main *bmain, const char *name)
320{
321 Volume *volume = BKE_id_new<Volume>(bmain, name);
322
323 return volume;
324}
325
326/* Sequence */
327
328static int volume_sequence_frame(const Depsgraph *depsgraph, const Volume *volume)
329{
330 if (!volume->is_sequence) {
331 return 0;
332 }
333
334 int path_frame, path_digits;
335 if (!(volume->is_sequence && BLI_path_frame_get(volume->filepath, &path_frame, &path_digits))) {
336 return 0;
337 }
338
339 const int scene_frame = DEG_get_ctime(depsgraph);
341 const int frame_duration = volume->frame_duration;
342 const int frame_start = volume->frame_start;
343 const int frame_offset = volume->frame_offset;
344
345 if (frame_duration == 0) {
346 return VOLUME_FRAME_NONE;
347 }
348
349 int frame = scene_frame - frame_start + 1;
350
351 switch (mode) {
353 if (frame < 1 || frame > frame_duration) {
354 return VOLUME_FRAME_NONE;
355 }
356 break;
357 }
359 frame = clamp_i(frame, 1, frame_duration);
360 break;
361 }
363 frame = frame % frame_duration;
364 if (frame < 0) {
365 frame += frame_duration;
366 }
367 if (frame == 0) {
368 frame = frame_duration;
369 }
370 break;
371 }
373 const int pingpong_duration = frame_duration * 2 - 2;
374 frame = frame % pingpong_duration;
375 if (frame < 0) {
376 frame += pingpong_duration;
377 }
378 if (frame == 0) {
379 frame = pingpong_duration;
380 }
381 if (frame > frame_duration) {
382 frame = frame_duration * 2 - frame;
383 }
384 break;
385 }
386 }
387
388 /* Important to apply after, else we can't loop on e.g. frames 100 - 110. */
389 frame += frame_offset;
390
391 return frame;
392}
393
394#ifdef WITH_OPENVDB
395static void volume_filepath_get(const Main *bmain, const Volume *volume, char r_filepath[FILE_MAX])
396{
397 BLI_strncpy(r_filepath, volume->filepath, FILE_MAX);
398 BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &volume->id));
399
400 int path_frame, path_digits;
401 if (volume->is_sequence && BLI_path_frame_get(r_filepath, &path_frame, &path_digits)) {
402 char ext[32];
403 BLI_path_frame_strip(r_filepath, ext, sizeof(ext));
404 BLI_path_frame(r_filepath, FILE_MAX, volume->runtime->frame, path_digits);
405 BLI_path_extension_ensure(r_filepath, FILE_MAX, ext);
406 }
407}
408#endif
409
410/* File Load */
411
412bool BKE_volume_is_loaded(const Volume *volume)
413{
414#ifdef WITH_OPENVDB
415 /* Test if there is a file to load, or if already loaded. */
416 return (volume->filepath[0] == '\0' || volume->runtime->grids->is_loaded());
417#else
418 UNUSED_VARS(volume);
419 return true;
420#endif
421}
422
423bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const StringRef ref_base_name)
424{
425 const std::string base_name = ref_base_name;
426
427 if (BKE_volume_grid_find(volume, base_name)) {
428 STRNCPY(volume->velocity_grid, base_name.c_str());
429 volume->runtime->velocity_x_grid[0] = '\0';
430 volume->runtime->velocity_y_grid[0] = '\0';
431 volume->runtime->velocity_z_grid[0] = '\0';
432 return true;
433 }
434
435 /* It could be that the velocity grid is split in multiple grids, try with known postfixes. */
436 const StringRefNull postfixes[][3] = {{"x", "y", "z"}, {".x", ".y", ".z"}, {"_x", "_y", "_z"}};
437
438 for (const StringRefNull *postfix : postfixes) {
439 bool found = true;
440 for (int i = 0; i < 3; i++) {
441 std::string post_fixed_name = ref_base_name + postfix[i];
442 if (!BKE_volume_grid_find(volume, post_fixed_name)) {
443 found = false;
444 break;
445 }
446 }
447
448 if (!found) {
449 continue;
450 }
451
452 /* Save the base name as well. */
453 STRNCPY(volume->velocity_grid, base_name.c_str());
454 STRNCPY(volume->runtime->velocity_x_grid, (ref_base_name + postfix[0]).c_str());
455 STRNCPY(volume->runtime->velocity_y_grid, (ref_base_name + postfix[1]).c_str());
456 STRNCPY(volume->runtime->velocity_z_grid, (ref_base_name + postfix[2]).c_str());
457 return true;
458 }
459
460 /* Reset to avoid potential issues. */
461 volume->velocity_grid[0] = '\0';
462 volume->runtime->velocity_x_grid[0] = '\0';
463 volume->runtime->velocity_y_grid[0] = '\0';
464 volume->runtime->velocity_z_grid[0] = '\0';
465 return false;
466}
467
468bool BKE_volume_load(const Volume *volume, const Main *bmain)
469{
470#ifdef WITH_OPENVDB
471 const VolumeGridVector &const_grids = *volume->runtime->grids;
472
473 if (volume->runtime->frame == VOLUME_FRAME_NONE) {
474 /* Skip loading this frame, outside of sequence range. */
475 return true;
476 }
477
478 if (BKE_volume_is_loaded(volume)) {
479 return const_grids.error_msg.empty();
480 }
481
482 /* Double-checked lock. */
483 std::lock_guard lock(const_grids.mutex);
484 if (BKE_volume_is_loaded(volume)) {
485 return const_grids.error_msg.empty();
486 }
487
488 /* Guarded by the lock, we can continue to access the grid vector,
489 * adding error messages or a new grid, etc. */
490 VolumeGridVector &grids = const_cast<VolumeGridVector &>(const_grids);
491
492 /* Get absolute file path at current frame. */
493 const char *volume_name = volume->id.name + 2;
494 char filepath[FILE_MAX];
495 volume_filepath_get(bmain, volume, filepath);
496
497 CLOG_INFO(&LOG, "Volume %s: load %s", volume_name, filepath);
498
499 /* Test if file exists. */
500 if (!BLI_exists(filepath)) {
501 grids.error_msg = BLI_path_basename(filepath) + std::string(" not found");
502 CLOG_INFO(&LOG, "Volume %s: %s", volume_name, grids.error_msg.c_str());
503 return false;
504 }
505
506 blender::bke::volume_grid::file_cache::GridsFromFile grids_from_file =
507 blender::bke::volume_grid::file_cache::get_all_grids_from_file(filepath, 0);
508
509 if (!grids_from_file.error_message.empty()) {
510 grids.error_msg = grids_from_file.error_message;
511 CLOG_INFO(&LOG, "Volume %s: %s", volume_name, grids.error_msg.c_str());
512 return false;
513 }
514
515 grids.metadata = std::move(grids_from_file.file_meta_data);
516 for (GVolumeGrid &volume_grid : grids_from_file.grids) {
517 grids.emplace_back(std::move(volume_grid));
518 }
519
520 /* Try to detect the velocity grid. */
521 const char *common_velocity_names[] = {"velocity", "vel", "v"};
522 for (const char *common_velocity_name : common_velocity_names) {
523 if (BKE_volume_set_velocity_grid_by_name(const_cast<Volume *>(volume), common_velocity_name)) {
524 break;
525 }
526 }
527
528 STRNCPY(grids.filepath, filepath);
529
530 return grids.error_msg.empty();
531#else
532 UNUSED_VARS(bmain, volume);
533 return true;
534#endif
535}
536
538{
539#ifdef WITH_OPENVDB
540 VolumeGridVector &grids = *volume->runtime->grids;
541 if (grids.filepath[0] != '\0') {
542 const char *volume_name = volume->id.name + 2;
543 CLOG_INFO(&LOG, "Volume %s: unload", volume_name);
544 grids.clear_all();
545 }
546#else
547 UNUSED_VARS(volume);
548#endif
549}
550
551/* File Save */
552
553bool BKE_volume_save(const Volume *volume,
554 const Main *bmain,
555 ReportList *reports,
556 const char *filepath)
557{
558#ifdef WITH_OPENVDB
559 if (!BKE_volume_load(volume, bmain)) {
560 BKE_reportf(reports, RPT_ERROR, "Could not load volume for writing");
561 return false;
562 }
563
564 VolumeGridVector &grids = *volume->runtime->grids;
565 openvdb::GridCPtrVec vdb_grids;
566
567 /* Tree users need to be kept alive for as long as the grids may be accessed. */
569
570 for (const GVolumeGrid &grid : grids) {
571 tree_tokens.append_as();
572 vdb_grids.push_back(grid->grid_ptr(tree_tokens.last()));
573 }
574
575 try {
576 openvdb::io::File file(filepath);
577 file.write(vdb_grids, *grids.metadata);
578 file.close();
579 }
580 catch (const openvdb::IoError &e) {
581 BKE_reportf(reports, RPT_ERROR, "Could not write volume: %s", e.what());
582 return false;
583 }
584 catch (...) {
585 BKE_reportf(reports, RPT_ERROR, "Could not write volume: Unknown error writing VDB file");
586 return false;
587 }
588
589 return true;
590#else
591 UNUSED_VARS(volume, bmain, reports, filepath);
592 return false;
593#endif
594}
595
597{
598#ifdef WITH_OPENVDB
599 if (const VolumeGridVector *grids = volume.runtime->grids) {
600 for (const GVolumeGrid &grid : *grids) {
601 grid->count_memory(memory);
602 }
603 }
604#else
605 UNUSED_VARS(volume, memory);
606#endif
607}
608
609std::optional<blender::Bounds<blender::float3>> BKE_volume_min_max(const Volume *volume)
610{
611#ifdef WITH_OPENVDB
612 /* TODO: if we know the volume is going to be displayed, it may be good to
613 * load it as part of dependency graph evaluation for better threading. We
614 * could also share the bounding box computation in the global volume cache. */
615 if (BKE_volume_load(const_cast<Volume *>(volume), G.main)) {
616 std::optional<blender::Bounds<blender::float3>> result;
617 for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
618 const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i);
619 blender::bke::VolumeTreeAccessToken tree_token;
621 BKE_volume_grid_bounds(volume_grid->grid_ptr(tree_token)));
622 }
623 return result;
624 }
625#else
626 UNUSED_VARS(volume);
627#endif
628 return std::nullopt;
629}
630
631bool BKE_volume_is_y_up(const Volume *volume)
632{
633 /* Simple heuristic for common files to open the right way up. */
634#ifdef WITH_OPENVDB
635 VolumeGridVector &grids = *volume->runtime->grids;
636 if (grids.metadata) {
637 openvdb::StringMetadata::ConstPtr creator =
638 grids.metadata->getMetadata<openvdb::StringMetadata>("creator");
639 if (!creator) {
640 creator = grids.metadata->getMetadata<openvdb::StringMetadata>("Creator");
641 }
642 return (creator && creator->str().rfind("Houdini", 0) == 0);
643 }
644#else
645 UNUSED_VARS(volume);
646#endif
647
648 return false;
649}
650
652{
653 int num_grids = BKE_volume_num_grids(volume);
654 if (num_grids == 0) {
655 return false;
656 }
657
658 for (int i = 0; i < num_grids; i++) {
659 const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
661 return false;
662 }
663 }
664
665 return true;
666}
667
668/* Dependency Graph */
669
670static void volume_update_simplify_level(Main *bmain, Volume *volume, const Depsgraph *depsgraph)
671{
672#ifdef WITH_OPENVDB
673 const int simplify_level = BKE_volume_simplify_level(depsgraph);
674
675 /* Replace grids with the new simplify level variants from the cache. */
676 if (BKE_volume_load(volume, bmain)) {
677 VolumeGridVector &grids = *volume->runtime->grids;
678 std::list<GVolumeGrid> new_grids;
679 for (const GVolumeGrid &old_grid : grids) {
680 GVolumeGrid simple_grid = blender::bke::volume_grid::file_cache::get_grid_from_file(
681 grids.filepath, old_grid->name(), simplify_level);
682 BLI_assert(simple_grid);
683 new_grids.push_back(std::move(simple_grid));
684 }
685 grids.swap(new_grids);
686 }
687#else
688 UNUSED_VARS(bmain, volume, depsgraph);
689#endif
690}
691
693 Scene *scene,
694 Object *object,
695 blender::bke::GeometrySet &geometry_set)
696{
697 /* Modifier evaluation modes. */
698 const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER);
699 const int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
700 ModifierApplyFlag apply_flag = use_render ? MOD_APPLY_RENDER : MOD_APPLY_USECACHE;
701 const ModifierEvalContext mectx = {depsgraph, object, apply_flag};
702
704
705 /* Get effective list of modifiers to execute. Some effects like shape keys
706 * are added as virtual modifiers before the user created modifiers. */
707 VirtualModifierData virtual_modifier_data;
708 ModifierData *md = BKE_modifiers_get_virtual_modifierlist(object, &virtual_modifier_data);
709
710 /* Evaluate modifiers. */
711 for (; md; md = md->next) {
713
714 if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
715 continue;
716 }
717
718 blender::bke::ScopedModifierTimer modifier_timer{*md};
719
720 if (mti->modify_geometry_set) {
721 mti->modify_geometry_set(md, &mectx, &geometry_set);
722 }
723 }
724}
725
727{
728 Main *bmain = DEG_get_bmain(depsgraph);
729
730 /* TODO: can we avoid modifier re-evaluation when frame did not change? */
731 int frame = volume_sequence_frame(depsgraph, volume);
732 if (frame != volume->runtime->frame) {
733 BKE_volume_unload(volume);
734 volume->runtime->frame = frame;
735 }
736
738
739 /* Flush back to original. */
741 Volume *volume_orig = DEG_get_original(volume);
742 if (volume_orig->runtime->frame != volume->runtime->frame) {
743 BKE_volume_unload(volume_orig);
744 volume_orig->runtime->frame = volume->runtime->frame;
745 }
746 }
747}
748
750{
751 if (!geometry_set.has<blender::bke::VolumeComponent>()) {
752 return nullptr;
753 }
754 auto &volume_component = geometry_set.get_component_for_write<blender::bke::VolumeComponent>();
755 Volume *volume = volume_component.release();
756 if (volume != nullptr) {
757 /* Add back, but only as read-only non-owning component. */
758 volume_component.replace(volume, blender::bke::GeometryOwnershipType::ReadOnly);
759 }
760 else {
761 /* The component was empty, we can remove it. */
763 }
764 return volume;
765}
766
767void BKE_volume_data_update(Depsgraph *depsgraph, Scene *scene, Object *object)
768{
769 /* Free any evaluated data and restore original data. */
771
772 /* Evaluate modifiers. */
773 Volume *volume = (Volume *)object->data;
774 blender::bke::GeometrySet geometry_set;
776 volume_evaluate_modifiers(depsgraph, scene, object, geometry_set);
777
778 Volume *volume_eval = take_volume_ownership_from_geometry_set(geometry_set);
779
780 /* If the geometry set did not contain a volume, we still create an empty one. */
781 if (volume_eval == nullptr) {
782 volume_eval = BKE_volume_new_for_eval(volume);
783 }
784
785 /* Assign evaluated object. */
786 const bool eval_is_owned = (volume != volume_eval);
787 BKE_object_eval_assign_data(object, &volume_eval->id, eval_is_owned);
788 object->runtime->geometry_set_eval = new blender::bke::GeometrySet(std::move(geometry_set));
789}
790
791void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath)
792{
793#ifdef WITH_OPENVDB
794 /* Restore grids after datablock was re-copied from original by depsgraph,
795 * we don't want to load them again if possible. */
797 BLI_assert(volume->runtime->grids != nullptr && grids != nullptr);
798
799 if (!grids->is_loaded()) {
800 /* No grids loaded in evaluated datablock, nothing lost by discarding. */
801 MEM_delete(grids);
802 }
803 else if (!STREQ(volume->filepath, filepath)) {
804 /* Filepath changed, discard grids from evaluated datablock. */
805 MEM_delete(grids);
806 }
807 else {
808 /* Keep grids from evaluated datablock. We might still unload them a little
809 * later in BKE_volume_eval_geometry if the frame changes. */
810 MEM_delete(volume->runtime->grids);
811 volume->runtime->grids = grids;
812 }
813#else
814 UNUSED_VARS(volume, grids, filepath);
815#endif
816}
817
818/* Draw Cache */
819
820void (*BKE_volume_batch_cache_dirty_tag_cb)(Volume *volume, int mode) = nullptr;
821void (*BKE_volume_batch_cache_free_cb)(Volume *volume) = nullptr;
822
824{
825 if (volume->batch_cache) {
827 }
828}
829
831{
832 if (volume->batch_cache) {
834 }
835}
836
837/* Grids */
838
839int BKE_volume_num_grids(const Volume *volume)
840{
841#ifdef WITH_OPENVDB
842 return volume->runtime->grids->size();
843#else
844 UNUSED_VARS(volume);
845 return 0;
846#endif
847}
848
849const char *BKE_volume_grids_error_msg(const Volume *volume)
850{
851#ifdef WITH_OPENVDB
852 return volume->runtime->grids->error_msg.c_str();
853#else
854 UNUSED_VARS(volume);
855 return "";
856#endif
857}
858
859const char *BKE_volume_grids_frame_filepath(const Volume *volume)
860{
861#ifdef WITH_OPENVDB
862 return volume->runtime->grids->filepath;
863#else
864 UNUSED_VARS(volume);
865 return "";
866#endif
867}
868
869const blender::bke::VolumeGridData *BKE_volume_grid_get(const Volume *volume, int grid_index)
870{
871#ifdef WITH_OPENVDB
872 const VolumeGridVector &grids = *volume->runtime->grids;
873 for (const GVolumeGrid &grid : grids) {
874 if (grid_index-- == 0) {
875 return &grid.get();
876 }
877 }
878 return nullptr;
879#else
880 UNUSED_VARS(volume, grid_index);
881 return nullptr;
882#endif
883}
884
885blender::bke::VolumeGridData *BKE_volume_grid_get_for_write(Volume *volume, int grid_index)
886{
887#ifdef WITH_OPENVDB
888 VolumeGridVector &grids = *volume->runtime->grids;
889 for (GVolumeGrid &grid_ptr : grids) {
890 if (grid_index-- == 0) {
891 return &grid_ptr.get_for_write();
892 }
893 }
894 return nullptr;
895#else
896 UNUSED_VARS(volume, grid_index);
897 return nullptr;
898#endif
899}
900
901const blender::bke::VolumeGridData *BKE_volume_grid_active_get_for_read(const Volume *volume)
902{
903 const int num_grids = BKE_volume_num_grids(volume);
904 if (num_grids == 0) {
905 return nullptr;
906 }
907
908 const int index = clamp_i(volume->active_grid, 0, num_grids - 1);
909 return BKE_volume_grid_get(volume, index);
910}
911
912const blender::bke::VolumeGridData *BKE_volume_grid_find(const Volume *volume,
913 const StringRef name)
914{
915 int num_grids = BKE_volume_num_grids(volume);
916 for (int i = 0; i < num_grids; i++) {
917 const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
919 return grid;
920 }
921 }
922
923 return nullptr;
924}
925
926blender::bke::VolumeGridData *BKE_volume_grid_find_for_write(Volume *volume, const StringRef name)
927{
928 int num_grids = BKE_volume_num_grids(volume);
929 for (int i = 0; i < num_grids; i++) {
930 const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
932 return BKE_volume_grid_get_for_write(volume, i);
933 }
934 }
935
936 return nullptr;
937}
938
939/* Grid Tree and Voxels */
940
941/* Volume Editing */
942
944{
945 Volume *volume_dst = BKE_id_new_nomain<Volume>(nullptr);
946
947 STRNCPY(volume_dst->id.name, volume_src->id.name);
948 volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
949 volume_dst->totcol = volume_src->totcol;
950 volume_dst->render = volume_src->render;
951 volume_dst->display = volume_src->display;
952
953 return volume_dst;
954}
955
957{
958 return reinterpret_cast<Volume *>(
959 BKE_id_copy_ex(nullptr, &volume_src->id, nullptr, LIB_ID_COPY_LOCALIZE));
960}
961
962#ifdef WITH_OPENVDB
963struct CreateGridOp {
964 template<typename GridType> typename openvdb::GridBase::Ptr operator()()
965 {
966 if constexpr (std::is_same_v<GridType, openvdb::points::PointDataGrid>) {
967 return {};
968 }
969 else {
970 return GridType::create();
971 }
972 }
973};
974#endif
975
976#ifdef WITH_OPENVDB
977blender::bke::VolumeGridData *BKE_volume_grid_add_vdb(Volume &volume,
978 const StringRef name,
979 openvdb::GridBase::Ptr vdb_grid)
980{
981 VolumeGridVector &grids = *volume.runtime->grids;
982 BLI_assert(BKE_volume_grid_find(&volume, name) == nullptr);
984
985 vdb_grid->setName(name);
986 grids.emplace_back(GVolumeGrid(std::move(vdb_grid)));
987 return &grids.back().get_for_write();
988}
989
990void BKE_volume_metadata_set(Volume &volume, openvdb::MetaMap::Ptr metadata)
991{
992 volume.runtime->grids->metadata = metadata;
993}
994#endif
995
996void BKE_volume_grid_remove(Volume *volume, const blender::bke::VolumeGridData *grid)
997{
998#ifdef WITH_OPENVDB
999 VolumeGridVector &grids = *volume->runtime->grids;
1000 for (VolumeGridVector::iterator it = grids.begin(); it != grids.end(); it++) {
1001 if (&it->get() == grid) {
1002 grids.erase(it);
1003 break;
1004 }
1005 }
1006#else
1007 UNUSED_VARS(volume, grid);
1008#endif
1009}
1010
1011void BKE_volume_grid_add(Volume *volume, const blender::bke::VolumeGridData &grid)
1012{
1013#ifdef WITH_OPENVDB
1014 VolumeGridVector &grids = *volume->runtime->grids;
1015 grids.push_back(GVolumeGrid(&grid));
1016#else
1017 UNUSED_VARS(volume, grid);
1018#endif
1019}
1020
1022{
1023#ifdef WITH_OPENVDB
1024 /* Limit taken from openvdb/math/Maps.h. */
1025 return std::abs(determinant) >= 3.0 * openvdb::math::Tolerance<double>::value();
1026#else
1028 return true;
1029#endif
1030}
1031
1033{
1034 return BKE_volume_grid_determinant_valid(voxel_size[0] * voxel_size[1] * voxel_size[2]);
1035}
1036
1041
1043{
1045 const Scene *scene = DEG_get_input_scene(depsgraph);
1046 if (scene->r.mode & R_SIMPLIFY) {
1047 const float simplify = scene->r.simplify_volumes;
1048 if (simplify == 0.0f) {
1049 /* log2 is not defined at 0.0f, so just use some high simplify level. */
1050 return 16;
1051 }
1052 return ceilf(-log2(simplify));
1053 }
1054 }
1055 return 0;
1056}
1057
1059{
1061 const Scene *scene = DEG_get_input_scene(depsgraph);
1062 if (scene->r.mode & R_SIMPLIFY) {
1063 return scene->r.simplify_volumes;
1064 }
1065 }
1066 return 1.0f;
1067}
1068
1069/* OpenVDB Grid Access */
1070
1071#ifdef WITH_OPENVDB
1072
1073std::optional<blender::Bounds<float3>> BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid)
1074{
1075 /* TODO: we can get this from grid metadata in some cases? */
1076 openvdb::CoordBBox coordbbox;
1077 if (!grid->baseTree().evalLeafBoundingBox(coordbbox)) {
1078 return std::nullopt;
1079 }
1080
1081 openvdb::BBoxd index_bbox = {
1082 openvdb::BBoxd(coordbbox.min().asVec3d(), coordbbox.max().asVec3d())};
1083 /* Add half voxel padding that is expected by volume rendering code. */
1084 index_bbox.expand(0.5);
1085
1086 const openvdb::BBoxd bbox = grid->transform().indexToWorld(index_bbox);
1087 return blender::Bounds<float3>{float3(bbox.min().asPointer()), float3(bbox.max().asPointer())};
1088}
1089
1090openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid,
1092{
1093 openvdb::math::Transform::Ptr grid_transform = grid->transform().copy();
1094 grid_transform->postMult(openvdb::Mat4d((float *)transform.ptr()));
1095
1096 /* Create a transformed grid. The underlying tree is shared. */
1097 return grid->copyGridReplacingTransform(grid_transform);
1098}
1099
1100blender::float4x4 BKE_volume_transform_to_blender(const openvdb::math::Transform &transform)
1101{
1102 /* Perspective not supported for now, getAffineMap() will leave out the
1103 * perspective part of the transform. */
1104 const openvdb::math::Mat4f matrix = transform.baseMap()->getAffineMap()->getMat4();
1105 /* Blender column-major and OpenVDB right-multiplication conventions match. */
1107 for (int col = 0; col < 4; col++) {
1108 for (int row = 0; row < 4; row++) {
1109 result[col][row] = matrix(col, row);
1110 }
1111 }
1112 return result;
1113}
1114
1115openvdb::math::Transform BKE_volume_transform_to_openvdb(const blender::float4x4 &transform)
1116{
1117 openvdb::math::Mat4f matrix_openvdb;
1118 for (int col = 0; col < 4; col++) {
1119 for (int row = 0; row < 4; row++) {
1120 matrix_openvdb(col, row) = transform[col][row];
1121 }
1122 }
1123 return openvdb::math::Transform(std::make_shared<openvdb::math::AffineMap>(matrix_openvdb));
1124}
1125
1126/* Changing the resolution of a grid. */
1127
1132template<typename GridType>
1133static typename GridType::Ptr create_grid_with_changed_resolution(const GridType &old_grid,
1134 const float resolution_factor)
1135{
1136 BLI_assert(resolution_factor > 0.0f);
1137
1138 openvdb::Mat4R xform;
1139 xform.setToScale(openvdb::Vec3d(resolution_factor));
1140 openvdb::tools::GridTransformer transformer{xform};
1141
1142 typename GridType::Ptr new_grid = old_grid.copyWithNewTree();
1143 transformer.transformGrid<openvdb::tools::BoxSampler>(old_grid, *new_grid);
1144 new_grid->transform() = old_grid.transform();
1145 new_grid->transform().preScale(1.0f / resolution_factor);
1146 new_grid->transform().postTranslate(-new_grid->voxelSize() / 2.0f);
1147 return new_grid;
1148}
1149
1150struct CreateGridWithChangedResolutionOp {
1151 const openvdb::GridBase &grid;
1152 const float resolution_factor;
1153
1154 template<typename GridType> typename openvdb::GridBase::Ptr operator()()
1155 {
1156 return create_grid_with_changed_resolution(static_cast<const GridType &>(grid),
1157 resolution_factor);
1158 }
1159};
1160
1161openvdb::GridBase::Ptr BKE_volume_grid_create_with_changed_resolution(
1162 const VolumeGridType grid_type,
1163 const openvdb::GridBase &old_grid,
1164 const float resolution_factor)
1165{
1166 CreateGridWithChangedResolutionOp op{old_grid, resolution_factor};
1167 return BKE_volume_grid_type_operation(grid_type, op);
1168}
1169
1170#endif
void BKE_animdata_free(ID *id, bool do_id_user)
Definition anim_data.cc:188
bool BKE_bpath_foreach_path_fixed_process(BPathForeachPathData *bpath_data, char *path, size_t path_maxncpy)
Definition bpath.cc:125
@ BKE_BPATH_FOREACH_PATH_SKIP_PACKED
Definition BKE_bpath.hh:42
void(*)(ID *id, const IDCacheKey *cache_key, void **cache_p, uint flags, void *user_data) IDTypeForeachCacheFunctionCallback
IDTypeInfo IDType_ID_VO
@ IDTYPE_FLAGS_APPEND_IS_REUSABLE
Definition BKE_idtype.hh:47
ID * BKE_id_copy_ex(Main *bmain, const ID *id, ID **new_id_p, int flag)
Definition lib_id.cc:777
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1514
@ LIB_ID_COPY_LOCALIZE
void * BKE_id_new_nomain(short type, const char *name)
Definition lib_id.cc:1519
void BKE_id_blend_write(BlendWriter *writer, ID *id)
Definition lib_id.cc:2631
#define BKE_LIB_FOREACHID_PROCESS_IDSUPER(data_, id_super_, cb_flag_)
@ IDWALK_CB_USER
void BKE_modifiers_clear_errors(Object *ob)
bool BKE_modifier_is_enabled(const Scene *scene, ModifierData *md, int required_mode)
const ModifierTypeInfo * BKE_modifier_get_info(ModifierType type)
ModifierData * BKE_modifiers_get_virtual_modifierlist(const Object *ob, VirtualModifierData *data)
ModifierApplyFlag
@ MOD_APPLY_USECACHE
@ MOD_APPLY_RENDER
General operations, lookup, etc. for blender objects.
void BKE_object_eval_assign_data(Object *object, ID *data, bool is_owned)
void BKE_object_free_derived_caches(Object *ob)
PackedFile * BKE_packedfile_duplicate(const PackedFile *pf_src)
void BKE_packedfile_free(PackedFile *pf)
void BKE_packedfile_blend_write(BlendWriter *writer, const PackedFile *pf)
void BKE_packedfile_blend_read(BlendDataReader *reader, PackedFile **pf_p, blender::StringRefNull filepath)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_ERROR
Definition BKE_report.hh:39
Volume data-block.
void(* BKE_volume_batch_cache_dirty_tag_cb)(Volume *volume, int mode)
void(* BKE_volume_batch_cache_free_cb)(Volume *volume)
VolumeGridType
@ VOLUME_GRID_UNKNOWN
@ VOLUME_GRID_POINTS
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
MINLINE int clamp_i(int value, int min, int max)
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
bool void BLI_path_frame_strip(char *path, char *r_ext, size_t ext_maxncpy) ATTR_NONNULL(1
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAX
bool BLI_path_frame_get(const char *path, int *r_frame, int *r_digits_len) ATTR_NONNULL(1
bool BLI_path_extension_ensure(char *path, size_t path_maxncpy, const char *ext) ATTR_NONNULL(1
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define UNUSED_VARS(...)
#define MEMCMP_STRUCT_AFTER_IS_ZERO(struct_var, member)
#define MEMCPY_STRUCT_AFTER(struct_dst, struct_src, member)
#define STREQ(a, b)
#define BLO_write_id_struct(writer, struct_name, id_address, id)
void BLO_write_pointer_array(BlendWriter *writer, int64_t num, const void *data_ptr)
void BLO_read_pointer_array(BlendDataReader *reader, int64_t array_size, void **ptr_p)
Definition readfile.cc:5880
bool BLO_write_is_undo(BlendWriter *writer)
#define BLT_I18NCONTEXT_ID_VOLUME
#define CLOG_INFO(clg_ref,...)
Definition CLG_log.h:190
@ DAG_EVAL_RENDER
bool DEG_is_active(const Depsgraph *depsgraph)
Definition depsgraph.cc:323
float DEG_get_ctime(const Depsgraph *graph)
eEvaluationMode DEG_get_mode(const Depsgraph *graph)
Main * DEG_get_bmain(const Depsgraph *graph)
T * DEG_get_original(T *id)
Scene * DEG_get_input_scene(const Depsgraph *graph)
@ ID_TAG_COPIED_ON_EVAL
Definition DNA_ID.h:997
#define FILTER_ID_MA
Definition DNA_ID.h:1208
#define ID_BLEND_PATH(_bmain, _id)
Definition DNA_ID.h:685
@ INDEX_ID_VO
Definition DNA_ID.h:1330
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:730
#define FILTER_ID_VO
Definition DNA_ID.h:1230
#define DNA_struct_default_get(struct_name)
@ eModifierMode_Render
@ eModifierMode_Realtime
Object is a sort of wrapper for general info.
@ R_SIMPLIFY
VolumeSequenceMode
@ VOLUME_SEQUENCE_REPEAT
@ VOLUME_SEQUENCE_CLIP
@ VOLUME_SEQUENCE_EXTEND
@ VOLUME_SEQUENCE_PING_PONG
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
volatile int lock
const blender::bke::VolumeGridData * BKE_volume_grid_active_get_for_read(const Volume *volume)
void BKE_volume_grid_add(Volume *volume, const blender::bke::VolumeGridData &grid)
#define VOLUME_FRAME_NONE
static void volume_blend_read_after_liblink(BlendLibReader *, ID *id)
bool BKE_volume_is_loaded(const Volume *volume)
void BKE_volume_batch_cache_free(Volume *volume)
bool BKE_volume_is_y_up(const Volume *volume)
int BKE_volume_num_grids(const Volume *volume)
Volume * BKE_volume_copy_for_eval(const Volume *volume_src)
bool BKE_volume_save(const Volume *volume, const Main *bmain, ReportList *reports, const char *filepath)
void BKE_volume_batch_cache_dirty_tag(Volume *volume, int mode)
bool BKE_volume_load(const Volume *volume, const Main *bmain)
blender::bke::VolumeGridData * BKE_volume_grid_get_for_write(Volume *volume, int grid_index)
bool BKE_volume_is_points_only(const Volume *volume)
static void volume_init_data(ID *id)
static void volume_copy_data(Main *, std::optional< Library * >, ID *id_dst, const ID *id_src, const int)
Volume * BKE_volume_add(Main *bmain, const char *name)
static void volume_evaluate_modifiers(Depsgraph *depsgraph, Scene *scene, Object *object, blender::bke::GeometrySet &geometry_set)
std::optional< blender::Bounds< blender::float3 > > BKE_volume_min_max(const Volume *volume)
bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const StringRef ref_base_name)
static int volume_sequence_frame(const Depsgraph *depsgraph, const Volume *volume)
void(* BKE_volume_batch_cache_dirty_tag_cb)(Volume *volume, int mode)
bool BKE_volume_grid_determinant_valid(const double determinant)
static void volume_foreach_path(ID *id, BPathForeachPathData *bpath_data)
Volume * BKE_volume_new_for_eval(const Volume *volume_src)
void BKE_volumes_init()
static void volume_foreach_cache(ID *id, IDTypeForeachCacheFunctionCallback function_callback, void *user_data)
void BKE_volume_grid_remove(Volume *volume, const blender::bke::VolumeGridData *grid)
int BKE_volume_simplify_level(const Depsgraph *depsgraph)
void BKE_volume_eval_geometry(Depsgraph *depsgraph, Volume *volume)
blender::bke::VolumeGridData * BKE_volume_grid_find_for_write(Volume *volume, const StringRef name)
static void volume_free_data(ID *id)
const char * BKE_volume_grids_frame_filepath(const Volume *volume)
float BKE_volume_simplify_factor(const Depsgraph *depsgraph)
static void volume_update_simplify_level(Main *bmain, Volume *volume, const Depsgraph *depsgraph)
const blender::bke::VolumeGridData * BKE_volume_grid_get(const Volume *volume, int grid_index)
void BKE_volume_unload(Volume *volume)
void(* BKE_volume_batch_cache_free_cb)(Volume *volume)
static void volume_blend_write(BlendWriter *writer, ID *id, const void *id_address)
static void volume_blend_read_data(BlendDataReader *reader, ID *id)
void BKE_volume_count_memory(const Volume &volume, blender::MemoryCounter &memory)
bool BKE_volume_voxel_size_valid(const float3 &voxel_size)
static void volume_foreach_id(ID *id, LibraryForeachIDData *data)
const blender::bke::VolumeGridData * BKE_volume_grid_find(const Volume *volume, const StringRef name)
bool BKE_volume_grid_transform_valid(const float4x4 &transform)
static Volume * take_volume_ownership_from_geometry_set(blender::bke::GeometrySet &geometry_set)
const char * BKE_volume_grids_error_msg(const Volume *volume)
void BKE_volume_data_update(Depsgraph *depsgraph, Scene *scene, Object *object)
void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath)
void BKE_volume_init_grids(Volume *volume)
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
BPy_StructRNA * depsgraph
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
SIMD_FORCE_INLINE btVector3 operator()(const btVector3 &x) const
Return the transform of the vector.
Definition btTransform.h:90
const T & last(const int64_t n=0) const
void append_as(ForwardValue &&...value)
ThreadMutex mutex
uint col
float determinant(MatBase< C, R >) RET
#define log2
#define LOG(level)
Definition log.h:97
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
#define G(x, y, z)
std::string get_name(const VolumeGridData &grid)
VolumeGridType get_type(const VolumeGridData &grid)
Bounds< T > merge(const Bounds< T > &a, const Bounds< T > &b)
Definition BLI_bounds.hh:26
T determinant(const MatBase< T, Size, Size > &mat)
MatBase< float, 4, 4 > float4x4
std::mutex Mutex
Definition BLI_mutex.hh:47
VecBase< float, 3 > float3
const char * name
#define ceilf
eBPathForeachFlag flag
Definition BKE_bpath.hh:102
Definition DNA_ID.h:414
int tag
Definition DNA_ID.h:442
char name[258]
Definition DNA_ID.h:432
struct ModifierData * next
void(* modify_geometry_set)(ModifierData *md, const ModifierEvalContext *ctx, blender::bke::GeometrySet *geometry_set)
float simplify_volumes
struct RenderData r
int frame_duration
char filepath[1024]
VolumeRuntimeHandle * runtime
char is_sequence
void * batch_cache
struct PackedFile * packedfile
char velocity_grid[64]
struct Material ** mat
VolumeRender render
VolumeDisplay display
char sequence_mode
void replace_volume(Volume *volume, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
GeometryComponent & get_component_for_write(GeometryComponent::Type component_type)
bool has(const GeometryComponent::Type component_type) const
void remove(const GeometryComponent::Type component_type)
i
Definition text_draw.cc:230
#define N_(msgid)