This adds `char *simulation_bake_directory` to the nodes modifier. The path is automatically generated the first time the modifier is baked. It is _not_ automatically changed afterwards. The path is relative to the .blend file by default. For now, the path is not exposed in the UI or Python API. This fixes issues where renaming objects/modifiers can cause the baked data to not work anymore. Pull Request: https://projects.blender.org/blender/blender/pulls/108201
228 lines
6.4 KiB
C++
228 lines
6.4 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BKE_curves.hh"
|
|
#include "BKE_simulation_state.hh"
|
|
#include "BKE_simulation_state_serialize.hh"
|
|
|
|
#include "DNA_curves_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_pointcloud_types.h"
|
|
|
|
#include "BLI_fileops.hh"
|
|
#include "BLI_hash_md5.h"
|
|
#include "BLI_path_util.h"
|
|
|
|
namespace blender::bke::sim {
|
|
|
|
GeometrySimulationStateItem::GeometrySimulationStateItem(GeometrySet geometry)
|
|
: geometry(std::move(geometry))
|
|
{
|
|
}
|
|
|
|
PrimitiveSimulationStateItem::PrimitiveSimulationStateItem(const CPPType &type, const void *value)
|
|
: type_(type)
|
|
{
|
|
value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
|
|
type.copy_construct(value, value_);
|
|
}
|
|
|
|
PrimitiveSimulationStateItem::~PrimitiveSimulationStateItem()
|
|
{
|
|
type_.destruct(value_);
|
|
MEM_freeN(value_);
|
|
}
|
|
|
|
StringSimulationStateItem::StringSimulationStateItem(std::string value) : value_(std::move(value))
|
|
{
|
|
}
|
|
|
|
void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bake_dir)
|
|
{
|
|
if (failed_finding_bake_) {
|
|
return;
|
|
}
|
|
|
|
char meta_dir[FILE_MAX];
|
|
BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir.c_str(), "meta");
|
|
char bdata_dir[FILE_MAX];
|
|
BLI_path_join(bdata_dir, sizeof(bdata_dir), absolute_bake_dir.c_str(), "bdata");
|
|
|
|
if (!BLI_is_dir(meta_dir) || !BLI_is_dir(bdata_dir)) {
|
|
failed_finding_bake_ = true;
|
|
return;
|
|
}
|
|
|
|
direntry *dir_entries = nullptr;
|
|
const int dir_entries_num = BLI_filelist_dir_contents(meta_dir, &dir_entries);
|
|
BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); });
|
|
|
|
if (dir_entries_num == 0) {
|
|
failed_finding_bake_ = true;
|
|
return;
|
|
}
|
|
|
|
this->reset();
|
|
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
|
|
for (const int i : IndexRange(dir_entries_num)) {
|
|
const direntry &dir_entry = dir_entries[i];
|
|
const StringRefNull dir_entry_path = dir_entry.path;
|
|
if (!dir_entry_path.endswith(".json")) {
|
|
continue;
|
|
}
|
|
char modified_file_name[FILE_MAX];
|
|
STRNCPY(modified_file_name, dir_entry.relname);
|
|
BLI_str_replace_char(modified_file_name, '_', '.');
|
|
|
|
const SubFrame frame = std::stof(modified_file_name);
|
|
|
|
auto new_state_at_frame = std::make_unique<ModifierSimulationStateAtFrame>();
|
|
new_state_at_frame->frame = frame;
|
|
new_state_at_frame->state.bdata_dir_ = bdata_dir;
|
|
new_state_at_frame->state.meta_path_ = dir_entry.path;
|
|
new_state_at_frame->state.owner_ = this;
|
|
states_at_frames_.append(std::move(new_state_at_frame));
|
|
}
|
|
}
|
|
|
|
bdata_sharing_ = std::make_unique<BDataSharing>();
|
|
|
|
cache_state_ = CacheState::Baked;
|
|
}
|
|
|
|
bool ModifierSimulationCache::has_state_at_frame(const SubFrame &frame) const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
for (const auto &item : states_at_frames_) {
|
|
if (item->frame == frame) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ModifierSimulationCache::has_states() const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
return !states_at_frames_.is_empty();
|
|
}
|
|
|
|
const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame(
|
|
const SubFrame &frame) const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
for (const auto &item : states_at_frames_) {
|
|
if (item->frame == frame) {
|
|
return &item->state;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write(
|
|
const SubFrame &frame)
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
for (const auto &item : states_at_frames_) {
|
|
if (item->frame == frame) {
|
|
return item->state;
|
|
}
|
|
}
|
|
states_at_frames_.append(std::make_unique<ModifierSimulationStateAtFrame>());
|
|
states_at_frames_.last()->frame = frame;
|
|
states_at_frames_.last()->state.owner_ = this;
|
|
return states_at_frames_.last()->state;
|
|
}
|
|
|
|
StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
StatesAroundFrame states_around_frame;
|
|
for (const auto &item : states_at_frames_) {
|
|
if (item->frame < frame) {
|
|
if (states_around_frame.prev == nullptr || item->frame > states_around_frame.prev->frame) {
|
|
states_around_frame.prev = item.get();
|
|
}
|
|
}
|
|
if (item->frame == frame) {
|
|
if (states_around_frame.current == nullptr) {
|
|
states_around_frame.current = item.get();
|
|
}
|
|
}
|
|
if (item->frame > frame) {
|
|
if (states_around_frame.next == nullptr || item->frame < states_around_frame.next->frame) {
|
|
states_around_frame.next = item.get();
|
|
}
|
|
}
|
|
}
|
|
return states_around_frame;
|
|
}
|
|
|
|
const SimulationZoneState *ModifierSimulationState::get_zone_state(
|
|
const SimulationZoneID &zone_id) const
|
|
{
|
|
std::lock_guard lock{mutex_};
|
|
if (auto *ptr = zone_states_.lookup_ptr(zone_id)) {
|
|
return ptr->get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SimulationZoneState &ModifierSimulationState::get_zone_state_for_write(
|
|
const SimulationZoneID &zone_id)
|
|
{
|
|
std::lock_guard lock{mutex_};
|
|
return *zone_states_.lookup_or_add_cb(zone_id,
|
|
[]() { return std::make_unique<SimulationZoneState>(); });
|
|
}
|
|
|
|
void ModifierSimulationState::ensure_bake_loaded() const
|
|
{
|
|
std::scoped_lock lock{mutex_};
|
|
if (bake_loaded_) {
|
|
return;
|
|
}
|
|
if (!meta_path_ || !bdata_dir_) {
|
|
return;
|
|
}
|
|
|
|
const std::shared_ptr<io::serialize::Value> io_root_value = io::serialize::read_json_file(
|
|
*meta_path_);
|
|
if (!io_root_value) {
|
|
return;
|
|
}
|
|
const DictionaryValue *io_root = io_root_value->as_dictionary_value();
|
|
if (!io_root) {
|
|
return;
|
|
}
|
|
|
|
const DiskBDataReader bdata_reader{*bdata_dir_};
|
|
deserialize_modifier_simulation_state(*io_root,
|
|
bdata_reader,
|
|
*owner_->bdata_sharing_,
|
|
const_cast<ModifierSimulationState &>(*this));
|
|
bake_loaded_ = true;
|
|
}
|
|
|
|
void ModifierSimulationCache::clear_prev_states()
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
std::unique_ptr<ModifierSimulationStateAtFrame> temp = std::move(states_at_frames_.last());
|
|
states_at_frames_.clear_and_shrink();
|
|
bdata_sharing_.reset();
|
|
states_at_frames_.append(std::move(temp));
|
|
}
|
|
|
|
void ModifierSimulationCache::reset()
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
states_at_frames_.clear();
|
|
bdata_sharing_.reset();
|
|
cache_state_ = CacheState::Valid;
|
|
}
|
|
|
|
} // namespace blender::bke::sim
|