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