Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenkernel/intern/volume.cc')
-rw-r--r--source/blender/blenkernel/intern/volume.cc1242
1 files changed, 1242 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc
new file mode 100644
index 00000000000..6fb44e91388
--- /dev/null
+++ b/source/blender/blenkernel/intern/volume.cc
@@ -0,0 +1,1242 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/volume.cc
+ * \ingroup bke
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_defaults.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_volume_types.h"
+
+#include "BLI_compiler_compat.h"
+#include "BLI_fileops.h"
+#include "BLI_ghash.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_animsys.h"
+#include "BKE_idtype.h"
+#include "BKE_global.h"
+#include "BKE_lib_id.h"
+#include "BKE_lib_query.h"
+#include "BKE_lib_remap.h"
+#include "BKE_main.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+#include "BKE_packedFile.h"
+#include "BKE_scene.h"
+#include "BKE_volume.h"
+
+#include "BLT_translation.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "CLG_log.h"
+
+static CLG_LogRef LOG = {"bke.volume"};
+
+#define VOLUME_FRAME_NONE INT_MAX
+
+#ifdef WITH_OPENVDB
+# include <atomic>
+# include <list>
+# include <mutex>
+# include <unordered_set>
+
+# include <openvdb/openvdb.h>
+# include <openvdb/points/PointDataGrid.h>
+
+/* Global Volume File Cache
+ *
+ * Global cache of grids read from VDB files. This is used for sharing grids
+ * between multiple volume datablocks with the same filepath, and sharing grids
+ * between original and copy-on-write datablocks created by the depsgraph.
+ *
+ * There are two types of users. Some datablocks only need the grid metadata,
+ * example an original datablock volume showing the list of grids in the
+ * properties editor. Other datablocks also need the tree and voxel data, for
+ * rendering for example. So, depending on the users the grid in the cache may
+ * have a tree or not.
+ *
+ * When the number of users drops to zero, the grid data is immediately deleted.
+ *
+ * TODO: also add a cache for OpenVDB files rather than individual grids,
+ * so getting the list of grids is also cached.
+ * TODO: Further, we could cache openvdb::io::File so that loading a grid
+ * does not re-open it every time. But then we have to take care not to run
+ * out of file descriptors or prevent other applications from writing to it.
+ */
+
+struct VolumeFileCache {
+ /* Cache Entry */
+ struct Entry {
+ Entry(const std::string &filepath, const openvdb::GridBase::Ptr &grid)
+ : filepath(filepath),
+ grid_name(grid->getName()),
+ grid(grid),
+ is_loaded(false),
+ num_metadata_users(0),
+ num_tree_users(0)
+ {
+ }
+
+ Entry(const Entry &other)
+ : filepath(other.filepath),
+ grid_name(other.grid_name),
+ grid(other.grid),
+ is_loaded(other.is_loaded),
+ num_metadata_users(0),
+ num_tree_users(0)
+ {
+ }
+
+ /* Unique key: filename + grid name. */
+ std::string filepath;
+ std::string grid_name;
+
+ /* OpenVDB grid. */
+ openvdb::GridBase::Ptr grid;
+ /* Has the grid tree been loaded? */
+ bool is_loaded;
+ /* Error message if an error occured during loading. */
+ std::string error_msg;
+ /* User counting. */
+ int num_metadata_users;
+ int num_tree_users;
+ /* Mutex for on-demand reading of tree. */
+ std::mutex mutex;
+ };
+
+ struct EntryHasher {
+ std::size_t operator()(const Entry &entry) const
+ {
+ std::hash<std::string> string_hasher;
+ return BLI_ghashutil_combine_hash(string_hasher(entry.filepath),
+ string_hasher(entry.grid_name));
+ }
+ };
+
+ struct EntryEqual {
+ bool operator()(const Entry &a, const Entry &b) const
+ {
+ return a.filepath == b.filepath && a.grid_name == b.grid_name;
+ }
+ };
+
+ /* Cache */
+ VolumeFileCache()
+ {
+ }
+
+ ~VolumeFileCache()
+ {
+ assert(cache.size() == 0);
+ }
+
+ Entry *add_metadata_user(const Entry &template_entry)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ EntrySet::iterator it = cache.find(template_entry);
+ if (it == cache.end()) {
+ it = cache.emplace(template_entry).first;
+ }
+
+ /* Casting const away is weak, but it's convenient having key and value in one. */
+ Entry &entry = (Entry &)*it;
+ entry.num_metadata_users++;
+
+ /* Note: pointers to unordered_set values are not invalidated when adding
+ * or removing other values. */
+ return &entry;
+ }
+
+ void copy_user(Entry &entry, const bool tree_user)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ if (tree_user) {
+ entry.num_tree_users++;
+ }
+ else {
+ entry.num_metadata_users++;
+ }
+ }
+
+ void remove_user(Entry &entry, const bool tree_user)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ if (tree_user) {
+ entry.num_tree_users--;
+ }
+ else {
+ entry.num_metadata_users--;
+ }
+ update_for_remove_user(entry);
+ }
+
+ void change_to_tree_user(Entry &entry)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ entry.num_tree_users++;
+ entry.num_metadata_users--;
+ update_for_remove_user(entry);
+ }
+
+ void change_to_metadata_user(Entry &entry)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ entry.num_metadata_users++;
+ entry.num_tree_users--;
+ update_for_remove_user(entry);
+ }
+
+ protected:
+ void update_for_remove_user(Entry &entry)
+ {
+ if (entry.num_metadata_users + entry.num_tree_users == 0) {
+ cache.erase(entry);
+ }
+ else if (entry.num_tree_users == 0) {
+ entry.grid->clear();
+ entry.is_loaded = false;
+ }
+ }
+
+ /* Cache contents */
+ typedef std::unordered_set<Entry, EntryHasher, EntryEqual> EntrySet;
+ EntrySet cache;
+ /* Mutex for multithreaded access. */
+ std::mutex mutex;
+} GLOBAL_CACHE;
+
+/* VolumeGrid
+ *
+ * Wrapper around OpenVDB grid. Grids loaded from OpenVDB files are always
+ * stored in the global cache. Procedurally generated grids are not. */
+
+struct VolumeGrid {
+ VolumeGrid(const VolumeFileCache::Entry &template_entry) : entry(NULL), is_loaded(false)
+ {
+ entry = GLOBAL_CACHE.add_metadata_user(template_entry);
+ vdb = entry->grid;
+ }
+
+ VolumeGrid(const openvdb::GridBase::Ptr &vdb) : vdb(vdb), entry(NULL), is_loaded(true)
+ {
+ }
+
+ VolumeGrid(const VolumeGrid &other)
+ : vdb(other.vdb), entry(other.entry), is_loaded(other.is_loaded)
+ {
+ if (entry) {
+ GLOBAL_CACHE.copy_user(*entry, is_loaded);
+ }
+ }
+
+ ~VolumeGrid()
+ {
+ if (entry) {
+ GLOBAL_CACHE.remove_user(*entry, is_loaded);
+ }
+ }
+
+ void load(const char *volume_name, const char *filepath)
+ {
+ /* If already loaded or not file-backed, nothing to do. */
+ if (is_loaded || entry == NULL) {
+ return;
+ }
+
+ /* Double-checked lock. */
+ std::lock_guard<std::mutex> lock(entry->mutex);
+ if (is_loaded) {
+ return;
+ }
+
+ /* Change metadata user to tree user. */
+ GLOBAL_CACHE.change_to_tree_user(*entry);
+
+ /* If already loaded by another user, nothing further to do. */
+ if (entry->is_loaded) {
+ is_loaded = true;
+ return;
+ }
+
+ /* Load grid from file. */
+ CLOG_INFO(&LOG, 1, "Volume %s: load grid '%s'", volume_name, name());
+
+ openvdb::io::File file(filepath);
+
+ try {
+ file.setCopyMaxBytes(0);
+ file.open();
+ openvdb::GridBase::Ptr vdb_grid = file.readGrid(name());
+ entry->grid->setTree(vdb_grid->baseTreePtr());
+ }
+ catch (const openvdb::IoError &e) {
+ entry->error_msg = e.what();
+ }
+
+ std::atomic_thread_fence(std::memory_order_release);
+ entry->is_loaded = true;
+ is_loaded = true;
+ }
+
+ void unload(const char *volume_name)
+ {
+ /* Not loaded or not file-backed, nothing to do. */
+ if (!is_loaded || entry == NULL) {
+ return;
+ }
+
+ /* Double-checked lock. */
+ std::lock_guard<std::mutex> lock(entry->mutex);
+ if (!is_loaded) {
+ return;
+ }
+
+ CLOG_INFO(&LOG, 1, "Volume %s: unload grid '%s'", volume_name, name());
+
+ /* Change tree user to metadata user. */
+ GLOBAL_CACHE.change_to_metadata_user(*entry);
+
+ /* Indicate we no longer have a tree. The actual grid may still
+ * have it due to another user. */
+ std::atomic_thread_fence(std::memory_order_release);
+ is_loaded = false;
+ }
+
+ void clear_reference(const char *UNUSED(volume_name))
+ {
+ /* Clear any reference to a grid in the file cache. */
+ vdb = vdb->copyGridWithNewTree();
+ if (entry) {
+ GLOBAL_CACHE.remove_user(*entry, is_loaded);
+ entry = NULL;
+ }
+ is_loaded = true;
+ }
+
+ void duplicate_reference(const char *volume_name, const char *filepath)
+ {
+ /* Make a deep copy of the grid and remove any reference to a grid in the
+ * file cache. Load file grid into memory first if needed. */
+ load(volume_name, filepath);
+ /* TODO: avoid deep copy if we are the only user. */
+ vdb = vdb->deepCopyGrid();
+ if (entry) {
+ GLOBAL_CACHE.remove_user(*entry, is_loaded);
+ entry = NULL;
+ }
+ is_loaded = true;
+ }
+
+ const char *name() const
+ {
+ /* Don't use vdb.getName() since it copies the string, we want a pointer to the
+ * original so it doesn't get freed out of scope. */
+ openvdb::StringMetadata::ConstPtr name_meta = vdb->getMetadata<openvdb::StringMetadata>(
+ openvdb::GridBase::META_GRID_NAME);
+ return (name_meta) ? name_meta->value().c_str() : "";
+ }
+
+ const char *error_message() const
+ {
+ if (is_loaded && entry && !entry->error_msg.empty()) {
+ return entry->error_msg.c_str();
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /* OpenVDB grid. */
+ openvdb::GridBase::Ptr vdb;
+ /* File cache entry. */
+ VolumeFileCache::Entry *entry;
+ /* Indicates if the tree has been loaded for this grid. Note that vdb.tree()
+ * may actually be loaded by another user while this is false. But only after
+ * calling load() and is_loaded changes to true is it safe to access. */
+ bool is_loaded;
+};
+
+/* Volume Grid Vector
+ *
+ * List of grids contained in a volume datablock. This is runtime-only data,
+ * the actual grids are always saved in a VDB file. */
+
+struct VolumeGridVector : public std::list<VolumeGrid> {
+ VolumeGridVector()
+ {
+ filepath[0] = '\0';
+ }
+
+ VolumeGridVector(const VolumeGridVector &other)
+ : std::list<VolumeGrid>(other), error_msg(other.error_msg), metadata(other.metadata)
+ {
+ memcpy(filepath, other.filepath, sizeof(filepath));
+ }
+
+ bool is_loaded() const
+ {
+ return filepath[0] != '\0';
+ }
+
+ void clear_all()
+ {
+ std::list<VolumeGrid>::clear();
+ filepath[0] = '\0';
+ error_msg.clear();
+ metadata.reset();
+ }
+
+ /* Absolute file path that grids have been loaded from. */
+ char filepath[FILE_MAX];
+ /* File loading error message. */
+ std::string error_msg;
+ /* File Metadata. */
+ openvdb::MetaMap::Ptr metadata;
+ /* Mutex for file loading of grids list. */
+ std::mutex mutex;
+};
+#endif
+
+/* Module */
+
+void BKE_volumes_init()
+{
+#ifdef WITH_OPENVDB
+ openvdb::initialize();
+#endif
+}
+
+/* Volume datablock */
+
+static void volume_init_data(ID *id)
+{
+ Volume *volume = (Volume *)id;
+ BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(volume, id));
+
+ MEMCPY_STRUCT_AFTER(volume, DNA_struct_default_get(Volume), id);
+
+ BKE_volume_init_grids(volume);
+}
+
+void BKE_volume_init_grids(Volume *volume)
+{
+#ifdef WITH_OPENVDB
+ if (volume->runtime.grids == NULL) {
+ volume->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector);
+ }
+#else
+ UNUSED_VARS(volume);
+#endif
+}
+
+void *BKE_volume_add(Main *bmain, const char *name)
+{
+ Volume *volume = (Volume *)BKE_libblock_alloc(bmain, ID_VO, name, 0);
+
+ volume_init_data(&volume->id);
+
+ return volume;
+}
+
+static void volume_copy_data(Main *UNUSED(bmain),
+ ID *id_dst,
+ const ID *id_src,
+ const int UNUSED(flag))
+{
+ Volume *volume_dst = (Volume *)id_dst;
+ const Volume *volume_src = (const Volume *)id_src;
+
+ if (volume_src->packedfile) {
+ volume_dst->packedfile = BKE_packedfile_duplicate(volume_src->packedfile);
+ }
+
+ volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
+#ifdef WITH_OPENVDB
+ if (volume_src->runtime.grids) {
+ const VolumeGridVector &grids_src = *(volume_src->runtime.grids);
+ volume_dst->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector, grids_src);
+ }
+#endif
+}
+
+Volume *BKE_volume_copy(Main *bmain, const Volume *volume)
+{
+ Volume *volume_copy;
+ BKE_id_copy(bmain, &volume->id, (ID **)&volume_copy);
+ return volume_copy;
+}
+
+static void volume_make_local(Main *bmain, ID *id, const int flags)
+{
+ BKE_lib_id_make_local_generic(bmain, id, flags);
+}
+
+static void volume_free_data(ID *id)
+{
+ Volume *volume = (Volume *)id;
+ BKE_animdata_free(&volume->id, false);
+ BKE_volume_batch_cache_free(volume);
+ MEM_SAFE_FREE(volume->mat);
+#ifdef WITH_OPENVDB
+ OBJECT_GUARDED_SAFE_DELETE(volume->runtime.grids, VolumeGridVector);
+#endif
+}
+
+IDTypeInfo IDType_ID_VO = {
+ /* id_code */ ID_VO,
+ /* id_filter */ FILTER_ID_VO,
+ /* main_listbase_index */ INDEX_ID_VO,
+ /* struct_size */ sizeof(Volume),
+ /* name */ "Volume",
+ /* name_plural */ "volumes",
+ /* translation_context */ BLT_I18NCONTEXT_ID_VOLUME,
+ /* flags */ 0,
+
+ /* init_data */ volume_init_data,
+ /* copy_data */ volume_copy_data,
+ /* free_data */ volume_free_data,
+ /* make_local */ volume_make_local,
+};
+
+/* Sequence */
+
+static int volume_sequence_frame(const Depsgraph *depsgraph, const Volume *volume)
+{
+ if (!volume->is_sequence) {
+ return 0;
+ }
+
+ char filepath[FILE_MAX];
+ STRNCPY(filepath, volume->filepath);
+ int path_frame, path_digits;
+ if (!(volume->is_sequence && BLI_path_frame_get(filepath, &path_frame, &path_digits))) {
+ return 0;
+ }
+
+ const int scene_frame = DEG_get_ctime(depsgraph);
+ const VolumeSequenceMode mode = (VolumeSequenceMode)volume->sequence_mode;
+ const int frame_duration = volume->frame_duration;
+ const int frame_start = volume->frame_start;
+ const int frame_offset = volume->frame_offset;
+
+ if (frame_duration == 0) {
+ return VOLUME_FRAME_NONE;
+ }
+
+ int frame = scene_frame - frame_start + 1;
+
+ switch (mode) {
+ case VOLUME_SEQUENCE_CLIP: {
+ if (frame < 1 || frame > frame_duration) {
+ return VOLUME_FRAME_NONE;
+ }
+ break;
+ }
+ case VOLUME_SEQUENCE_EXTEND: {
+ frame = clamp_i(frame, 1, frame_duration);
+ break;
+ }
+ case VOLUME_SEQUENCE_REPEAT: {
+ frame = frame % frame_duration;
+ if (frame < 0) {
+ frame += frame_duration;
+ }
+ if (frame == 0) {
+ frame = frame_duration;
+ }
+ break;
+ }
+ case VOLUME_SEQUENCE_PING_PONG: {
+ const int pingpong_duration = frame_duration * 2 - 2;
+ frame = frame % pingpong_duration;
+ if (frame < 0) {
+ frame += pingpong_duration;
+ }
+ if (frame == 0) {
+ frame = pingpong_duration;
+ }
+ if (frame > frame_duration) {
+ frame = frame_duration * 2 - frame;
+ }
+ break;
+ }
+ }
+
+ /* Important to apply after, else we cant loop on e.g. frames 100 - 110. */
+ frame += frame_offset;
+
+ return frame;
+}
+
+static void volume_filepath_get(const Main *bmain, const Volume *volume, char r_filepath[FILE_MAX])
+{
+ BLI_strncpy(r_filepath, volume->filepath, FILE_MAX);
+ BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &volume->id));
+
+ int path_frame, path_digits;
+ if (volume->is_sequence && BLI_path_frame_get(r_filepath, &path_frame, &path_digits)) {
+ char ext[32];
+ BLI_path_frame_strip(r_filepath, ext);
+ BLI_path_frame(r_filepath, volume->runtime.frame, path_digits);
+ BLI_path_extension_ensure(r_filepath, FILE_MAX, ext);
+ }
+}
+
+/* File Load */
+
+bool BKE_volume_is_loaded(const Volume *volume)
+{
+#ifdef WITH_OPENVDB
+ /* Test if there is a file to load, or if already loaded. */
+ return (volume->filepath[0] == '\0' || volume->runtime.grids->is_loaded());
+#else
+ UNUSED_VARS(volume);
+ return true;
+#endif
+}
+
+bool BKE_volume_load(Volume *volume, Main *bmain)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+
+ if (volume->runtime.frame == VOLUME_FRAME_NONE) {
+ /* Skip loading this frame, outside of sequence range. */
+ return true;
+ }
+
+ if (BKE_volume_is_loaded(volume)) {
+ return grids.error_msg.empty();
+ }
+
+ /* Double-checked lock. */
+ std::lock_guard<std::mutex> lock(grids.mutex);
+ if (BKE_volume_is_loaded(volume)) {
+ return grids.error_msg.empty();
+ }
+
+ /* Get absolute file path at current frame. */
+ const char *volume_name = volume->id.name + 2;
+ volume_filepath_get(bmain, volume, grids.filepath);
+
+ CLOG_INFO(&LOG, 1, "Volume %s: load %s", volume_name, grids.filepath);
+
+ /* Test if file exists. */
+ if (!BLI_exists(grids.filepath)) {
+ char filename[FILE_MAX];
+ BLI_split_file_part(grids.filepath, filename, sizeof(filename));
+ grids.error_msg = filename + std::string(" not found");
+ CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str());
+ return false;
+ }
+
+ /* Test if file exists. */
+ if (!BLI_exists(grids.filepath)) {
+ char filename[FILE_MAX];
+ BLI_split_file_part(grids.filepath, filename, sizeof(filename));
+ grids.error_msg = filename + std::string(" not found");
+ CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str());
+ return false;
+ }
+
+ /* Open OpenVDB file. */
+ openvdb::io::File file(grids.filepath);
+ openvdb::GridPtrVec vdb_grids;
+
+ try {
+ file.setCopyMaxBytes(0);
+ file.open();
+ vdb_grids = *(file.readAllGridMetadata());
+ grids.metadata = file.getMetadata();
+ }
+ catch (const openvdb::IoError &e) {
+ grids.error_msg = e.what();
+ CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str());
+ }
+
+ /* Add grids read from file to own vector, filtering out any NULL pointers. */
+ for (const openvdb::GridBase::Ptr vdb_grid : vdb_grids) {
+ if (vdb_grid) {
+ VolumeFileCache::Entry template_entry(grids.filepath, vdb_grid);
+ grids.emplace_back(template_entry);
+ }
+ }
+
+ return grids.error_msg.empty();
+#else
+ UNUSED_VARS(bmain, volume);
+ return true;
+#endif
+}
+
+void BKE_volume_unload(Volume *volume)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ if (grids.filepath[0] != '\0') {
+ const char *volume_name = volume->id.name + 2;
+ CLOG_INFO(&LOG, 1, "Volume %s: unload", volume_name);
+ grids.clear_all();
+ }
+#else
+ UNUSED_VARS(volume);
+#endif
+}
+
+BoundBox *BKE_volume_boundbox_get(Object *ob)
+{
+ BLI_assert(ob->type == OB_VOLUME);
+
+ if (ob->runtime.bb != NULL && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) {
+ return ob->runtime.bb;
+ }
+
+ if (ob->runtime.bb == NULL) {
+ Volume *volume = (Volume *)ob->data;
+
+ ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "volume boundbox");
+
+ float min[3], max[3];
+ bool have_minmax = false;
+ INIT_MINMAX(min, max);
+
+ /* TODO: if we know the volume is going to be displayed, it may be good to
+ * load it as part of dependency graph evaluation for better threading. We
+ * could also share the bounding box computation in the global volume cache. */
+ if (BKE_volume_load(volume, G.main)) {
+ const int num_grids = BKE_volume_num_grids(volume);
+
+ for (int i = 0; i < num_grids; i++) {
+ VolumeGrid *grid = BKE_volume_grid_get(volume, i);
+ float grid_min[3], grid_max[3];
+
+ BKE_volume_grid_load(volume, grid);
+ if (BKE_volume_grid_bounds(grid, grid_min, grid_max)) {
+ DO_MIN(grid_min, min);
+ DO_MAX(grid_max, max);
+ have_minmax = true;
+ }
+ }
+ }
+
+ if (!have_minmax) {
+ min[0] = min[1] = min[2] = -1.0f;
+ max[0] = max[1] = max[2] = 1.0f;
+ }
+
+ BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
+ }
+
+ return ob->runtime.bb;
+}
+
+bool BKE_volume_is_y_up(const Volume *volume)
+{
+ /* Simple heuristic for common files to open the right way up. */
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ if (grids.metadata) {
+ openvdb::StringMetadata::ConstPtr creator =
+ grids.metadata->getMetadata<openvdb::StringMetadata>("creator");
+ if (!creator) {
+ creator = grids.metadata->getMetadata<openvdb::StringMetadata>("Creator");
+ }
+ return (creator && creator->str().rfind("Houdini", 0) == 0);
+ }
+#else
+ UNUSED_VARS(volume);
+#endif
+
+ return false;
+}
+
+bool BKE_volume_is_points_only(const Volume *volume)
+{
+ int num_grids = BKE_volume_num_grids(volume);
+ if (num_grids == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < num_grids; i++) {
+ VolumeGrid *grid = BKE_volume_grid_get(volume, i);
+ if (BKE_volume_grid_type(grid) != VOLUME_GRID_POINTS) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Dependency Graph */
+
+static Volume *volume_evaluate_modifiers(struct Depsgraph *UNUSED(depsgraph),
+ struct Scene *UNUSED(scene),
+ Object *UNUSED(object),
+ Volume *volume_input)
+{
+ return volume_input;
+}
+
+void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume)
+{
+ /* TODO: can we avoid modifier re-evaluation when frame did not change? */
+ int frame = volume_sequence_frame(depsgraph, volume);
+ if (frame != volume->runtime.frame) {
+ BKE_volume_unload(volume);
+ volume->runtime.frame = frame;
+ }
+
+ /* Flush back to original. */
+ if (DEG_is_active(depsgraph)) {
+ Volume *volume_orig = (Volume *)DEG_get_original_id(&volume->id);
+ volume_orig->runtime.frame = volume->runtime.frame;
+ }
+}
+
+void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Object *object)
+{
+ /* Free any evaluated data and restore original data. */
+ BKE_object_free_derived_caches(object);
+
+ /* Evaluate modifiers. */
+ Volume *volume = (Volume *)object->data;
+ Volume *volume_eval = volume_evaluate_modifiers(depsgraph, scene, object, volume);
+
+ /* Assign evaluated object. */
+ const bool is_owned = (volume != volume_eval);
+ BKE_object_eval_assign_data(object, &volume_eval->id, is_owned);
+}
+
+void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath)
+{
+#ifdef WITH_OPENVDB
+ /* Restore grids after datablock was re-copied from original by depsgraph,
+ * we don't want to load them again if possible. */
+ BLI_assert(volume->id.tag & LIB_TAG_COPIED_ON_WRITE);
+ BLI_assert(volume->runtime.grids != NULL && grids != NULL);
+
+ if (!grids->is_loaded()) {
+ /* No grids loaded in CoW datablock, nothing lost by discarding. */
+ OBJECT_GUARDED_DELETE(grids, VolumeGridVector);
+ }
+ else if (!STREQ(volume->filepath, filepath)) {
+ /* Filepath changed, discard grids from CoW datablock. */
+ OBJECT_GUARDED_DELETE(grids, VolumeGridVector);
+ }
+ else {
+ /* Keep grids from CoW datablock. We might still unload them a little
+ * later in BKE_volume_eval_geometry if the frame changes. */
+ OBJECT_GUARDED_DELETE(volume->runtime.grids, VolumeGridVector);
+ volume->runtime.grids = grids;
+ }
+#else
+ UNUSED_VARS(volume, grids);
+#endif
+}
+
+/* Draw Cache */
+
+void (*BKE_volume_batch_cache_dirty_tag_cb)(Volume *volume, int mode) = NULL;
+void (*BKE_volume_batch_cache_free_cb)(Volume *volume) = NULL;
+
+void BKE_volume_batch_cache_dirty_tag(Volume *volume, int mode)
+{
+ if (volume->batch_cache) {
+ BKE_volume_batch_cache_dirty_tag_cb(volume, mode);
+ }
+}
+
+void BKE_volume_batch_cache_free(Volume *volume)
+{
+ if (volume->batch_cache) {
+ BKE_volume_batch_cache_free_cb(volume);
+ }
+}
+
+/* Grids */
+
+int BKE_volume_num_grids(const Volume *volume)
+{
+#ifdef WITH_OPENVDB
+ return volume->runtime.grids->size();
+#else
+ UNUSED_VARS(volume);
+ return 0;
+#endif
+}
+
+const char *BKE_volume_grids_error_msg(const Volume *volume)
+{
+#ifdef WITH_OPENVDB
+ return volume->runtime.grids->error_msg.c_str();
+#else
+ UNUSED_VARS(volume);
+ return "";
+#endif
+}
+
+VolumeGrid *BKE_volume_grid_get(const Volume *volume, int grid_index)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ for (VolumeGrid &grid : grids) {
+ if (grid_index-- == 0) {
+ return &grid;
+ }
+ }
+ return NULL;
+#else
+ UNUSED_VARS(volume, grid_index);
+ return NULL;
+#endif
+}
+
+VolumeGrid *BKE_volume_grid_active_get(const Volume *volume)
+{
+ const int num_grids = BKE_volume_num_grids(volume);
+ if (num_grids == 0) {
+ return NULL;
+ }
+
+ const int index = clamp_i(volume->active_grid, 0, num_grids - 1);
+ return BKE_volume_grid_get(volume, index);
+}
+
+VolumeGrid *BKE_volume_grid_find(const Volume *volume, const char *name)
+{
+ int num_grids = BKE_volume_num_grids(volume);
+ for (int i = 0; i < num_grids; i++) {
+ VolumeGrid *grid = BKE_volume_grid_get(volume, i);
+ if (STREQ(BKE_volume_grid_name(grid), name)) {
+ return grid;
+ }
+ }
+
+ return NULL;
+}
+
+/* Grid Loading */
+
+bool BKE_volume_grid_load(const Volume *volume, VolumeGrid *grid)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ const char *volume_name = volume->id.name + 2;
+ grid->load(volume_name, grids.filepath);
+ const char *error_msg = grid->error_message();
+ if (error_msg) {
+ grids.error_msg = error_msg;
+ return false;
+ }
+ return true;
+#else
+ UNUSED_VARS(volume, grid);
+ return true;
+#endif
+}
+
+void BKE_volume_grid_unload(const Volume *volume, VolumeGrid *grid)
+{
+#ifdef WITH_OPENVDB
+ const char *volume_name = volume->id.name + 2;
+ grid->unload(volume_name);
+#else
+ UNUSED_VARS(grid);
+#endif
+}
+
+bool BKE_volume_grid_is_loaded(const VolumeGrid *grid)
+{
+#ifdef WITH_OPENVDB
+ return grid->is_loaded;
+#else
+ UNUSED_VARS(grid);
+ return true;
+#endif
+}
+
+/* Grid Metadata */
+
+const char *BKE_volume_grid_name(const VolumeGrid *volume_grid)
+{
+#ifdef WITH_OPENVDB
+ return volume_grid->name();
+#else
+ UNUSED_VARS(volume_grid);
+ return "density";
+#endif
+}
+
+VolumeGridType BKE_volume_grid_type(const VolumeGrid *volume_grid)
+{
+#ifdef WITH_OPENVDB
+ const openvdb::GridBase::Ptr &grid = volume_grid->vdb;
+
+ if (grid->isType<openvdb::FloatGrid>()) {
+ return VOLUME_GRID_FLOAT;
+ }
+ else if (grid->isType<openvdb::Vec3fGrid>()) {
+ return VOLUME_GRID_VECTOR_FLOAT;
+ }
+ else if (grid->isType<openvdb::BoolGrid>()) {
+ return VOLUME_GRID_BOOLEAN;
+ }
+ else if (grid->isType<openvdb::DoubleGrid>()) {
+ return VOLUME_GRID_DOUBLE;
+ }
+ else if (grid->isType<openvdb::Int32Grid>()) {
+ return VOLUME_GRID_INT;
+ }
+ else if (grid->isType<openvdb::Int64Grid>()) {
+ return VOLUME_GRID_INT64;
+ }
+ else if (grid->isType<openvdb::Vec3IGrid>()) {
+ return VOLUME_GRID_VECTOR_INT;
+ }
+ else if (grid->isType<openvdb::Vec3dGrid>()) {
+ return VOLUME_GRID_VECTOR_DOUBLE;
+ }
+ else if (grid->isType<openvdb::StringGrid>()) {
+ return VOLUME_GRID_STRING;
+ }
+ else if (grid->isType<openvdb::MaskGrid>()) {
+ return VOLUME_GRID_MASK;
+ }
+ else if (grid->isType<openvdb::points::PointDataGrid>()) {
+ return VOLUME_GRID_POINTS;
+ }
+#else
+ UNUSED_VARS(volume_grid);
+#endif
+
+ return VOLUME_GRID_UNKNOWN;
+}
+
+int BKE_volume_grid_channels(const VolumeGrid *grid)
+{
+ switch (BKE_volume_grid_type(grid)) {
+ case VOLUME_GRID_BOOLEAN:
+ case VOLUME_GRID_FLOAT:
+ case VOLUME_GRID_DOUBLE:
+ case VOLUME_GRID_INT:
+ case VOLUME_GRID_INT64:
+ case VOLUME_GRID_MASK:
+ return 1;
+ case VOLUME_GRID_VECTOR_FLOAT:
+ case VOLUME_GRID_VECTOR_DOUBLE:
+ case VOLUME_GRID_VECTOR_INT:
+ return 3;
+ case VOLUME_GRID_STRING:
+ case VOLUME_GRID_POINTS:
+ case VOLUME_GRID_UNKNOWN:
+ return 0;
+ }
+
+ return 0;
+}
+
+/* Transformation from index space to object space. */
+void BKE_volume_grid_transform_matrix(const VolumeGrid *volume_grid, float mat[4][4])
+{
+#ifdef WITH_OPENVDB
+ const openvdb::GridBase::Ptr &grid = volume_grid->vdb;
+ const openvdb::math::Transform &transform = grid->transform();
+
+ /* Perspective not supported for now, getAffineMap() will leave out the
+ * perspective part of the transform. */
+ openvdb::math::Mat4f matrix = transform.baseMap()->getAffineMap()->getMat4();
+ /* Blender column-major and OpenVDB right-multiplication conventions match. */
+ for (int col = 0; col < 4; col++) {
+ for (int row = 0; row < 4; row++) {
+ mat[col][row] = matrix(col, row);
+ }
+ }
+#else
+ unit_m4(mat);
+ UNUSED_VARS(volume_grid);
+#endif
+}
+
+/* Grid Tree and Voxels */
+
+bool BKE_volume_grid_bounds(const VolumeGrid *volume_grid, float min[3], float max[3])
+{
+#ifdef WITH_OPENVDB
+ /* TODO: we can get this from grid metadata in some cases? */
+ const openvdb::GridBase::Ptr &grid = volume_grid->vdb;
+ BLI_assert(BKE_volume_grid_is_loaded(volume_grid));
+
+ openvdb::CoordBBox coordbbox;
+ if (!grid->baseTree().evalLeafBoundingBox(coordbbox)) {
+ INIT_MINMAX(min, max);
+ return false;
+ }
+
+ openvdb::BBoxd bbox = grid->transform().indexToWorld(coordbbox);
+ min[0] = (float)bbox.min().x();
+ min[1] = (float)bbox.min().y();
+ min[2] = (float)bbox.min().z();
+ max[0] = (float)bbox.max().x();
+ max[1] = (float)bbox.max().y();
+ max[2] = (float)bbox.max().z();
+ return true;
+#else
+ UNUSED_VARS(volume_grid);
+ INIT_MINMAX(min, max);
+ return false;
+#endif
+}
+
+/* Volume Editing */
+
+Volume *BKE_volume_new_for_eval(const Volume *volume_src)
+{
+ Volume *volume_dst = (Volume *)BKE_id_new_nomain(ID_VO, NULL);
+
+ STRNCPY(volume_dst->id.name, volume_src->id.name);
+ volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
+ volume_dst->totcol = volume_src->totcol;
+ BKE_volume_init_grids(volume_dst);
+
+ return volume_dst;
+}
+
+Volume *BKE_volume_copy_for_eval(Volume *volume_src, bool reference)
+{
+ int flags = LIB_ID_COPY_LOCALIZE;
+
+ if (reference) {
+ flags |= LIB_ID_COPY_CD_REFERENCE;
+ }
+
+ Volume *result;
+ BKE_id_copy_ex(NULL, &volume_src->id, (ID **)&result, flags);
+ result->filepath[0] = '\0';
+
+ return result;
+}
+
+VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType type)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ BLI_assert(BKE_volume_grid_find(volume, name) == NULL);
+
+ openvdb::GridBase::Ptr vdb_grid;
+ switch (type) {
+ case VOLUME_GRID_FLOAT:
+ vdb_grid = openvdb::FloatGrid::create();
+ break;
+ case VOLUME_GRID_VECTOR_FLOAT:
+ vdb_grid = openvdb::Vec3fGrid::create();
+ break;
+ case VOLUME_GRID_BOOLEAN:
+ vdb_grid = openvdb::BoolGrid::create();
+ break;
+ case VOLUME_GRID_DOUBLE:
+ vdb_grid = openvdb::DoubleGrid::create();
+ break;
+ case VOLUME_GRID_INT:
+ vdb_grid = openvdb::Int32Grid::create();
+ break;
+ case VOLUME_GRID_INT64:
+ vdb_grid = openvdb::Int64Grid::create();
+ break;
+ case VOLUME_GRID_VECTOR_INT:
+ vdb_grid = openvdb::Vec3IGrid::create();
+ break;
+ case VOLUME_GRID_VECTOR_DOUBLE:
+ vdb_grid = openvdb::Vec3dGrid::create();
+ break;
+ case VOLUME_GRID_STRING:
+ vdb_grid = openvdb::StringGrid::create();
+ break;
+ case VOLUME_GRID_MASK:
+ vdb_grid = openvdb::MaskGrid::create();
+ break;
+ case VOLUME_GRID_POINTS:
+ case VOLUME_GRID_UNKNOWN:
+ return NULL;
+ }
+
+ vdb_grid->setName(name);
+ grids.emplace_back(vdb_grid);
+ return &grids.back();
+#else
+ UNUSED_VARS(volume, name, type);
+ return NULL;
+#endif
+}
+
+void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid)
+{
+#ifdef WITH_OPENVDB
+ VolumeGridVector &grids = *volume->runtime.grids;
+ for (VolumeGridVector::iterator it = grids.begin(); it != grids.end(); it++) {
+ if (&*it == grid) {
+ grids.erase(it);
+ break;
+ }
+ }
+#else
+ UNUSED_VARS(volume, grid);
+#endif
+}
+
+/* OpenVDB Grid Access */
+
+#ifdef WITH_OPENVDB
+openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const VolumeGrid *grid)
+{
+ return grid->vdb;
+}
+
+openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const Volume *volume,
+ VolumeGrid *grid)
+{
+ BKE_volume_grid_load(volume, grid);
+ return grid->vdb;
+}
+
+openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const Volume *volume,
+ VolumeGrid *grid,
+ const bool clear)
+{
+ const char *volume_name = volume->id.name + 2;
+ if (clear) {
+ grid->clear_reference(volume_name);
+ }
+ else {
+ VolumeGridVector &grids = *volume->runtime.grids;
+ grid->duplicate_reference(volume_name, grids.filepath);
+ }
+
+ return grid->vdb;
+}
+#endif