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/simulation')
-rw-r--r--source/blender/simulation/CMakeLists.txt4
-rw-r--r--source/blender/simulation/SIM_simulation_update.hh4
-rw-r--r--source/blender/simulation/intern/particle_allocator.cc11
-rw-r--r--source/blender/simulation/intern/particle_allocator.hh5
-rw-r--r--source/blender/simulation/intern/particle_function.cc28
-rw-r--r--source/blender/simulation/intern/particle_function.hh15
-rw-r--r--source/blender/simulation/intern/particle_mesh_emitter.cc362
-rw-r--r--source/blender/simulation/intern/particle_mesh_emitter.hh52
-rw-r--r--source/blender/simulation/intern/simulation_collect_influences.cc878
-rw-r--r--source/blender/simulation/intern/simulation_collect_influences.hh2
-rw-r--r--source/blender/simulation/intern/simulation_solver.cc410
-rw-r--r--source/blender/simulation/intern/simulation_solver.hh247
-rw-r--r--source/blender/simulation/intern/simulation_solver_influences.cc57
-rw-r--r--source/blender/simulation/intern/simulation_solver_influences.hh237
-rw-r--r--source/blender/simulation/intern/simulation_update.cc233
-rw-r--r--source/blender/simulation/intern/time_interval.hh40
16 files changed, 1960 insertions, 625 deletions
diff --git a/source/blender/simulation/CMakeLists.txt b/source/blender/simulation/CMakeLists.txt
index 243b056db74..cbc6ee65303 100644
--- a/source/blender/simulation/CMakeLists.txt
+++ b/source/blender/simulation/CMakeLists.txt
@@ -43,7 +43,9 @@ set(SRC
intern/implicit_eigen.cpp
intern/particle_allocator.cc
intern/particle_function.cc
+ intern/particle_mesh_emitter.cc
intern/simulation_collect_influences.cc
+ intern/simulation_solver_influences.cc
intern/simulation_solver.cc
intern/simulation_update.cc
@@ -52,7 +54,9 @@ set(SRC
intern/implicit.h
intern/particle_allocator.hh
intern/particle_function.hh
+ intern/particle_mesh_emitter.hh
intern/simulation_collect_influences.hh
+ intern/simulation_solver_influences.hh
intern/simulation_solver.hh
intern/time_interval.hh
diff --git a/source/blender/simulation/SIM_simulation_update.hh b/source/blender/simulation/SIM_simulation_update.hh
index 2c64fdec02e..7c2726f038e 100644
--- a/source/blender/simulation/SIM_simulation_update.hh
+++ b/source/blender/simulation/SIM_simulation_update.hh
@@ -26,4 +26,6 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
Scene *scene_cow,
Simulation *simulation_cow);
-}
+bool update_simulation_dependencies(Simulation *simulation);
+
+} // namespace blender::sim
diff --git a/source/blender/simulation/intern/particle_allocator.cc b/source/blender/simulation/intern/particle_allocator.cc
index eb1e998e63a..e47a6354d81 100644
--- a/source/blender/simulation/intern/particle_allocator.cc
+++ b/source/blender/simulation/intern/particle_allocator.cc
@@ -16,6 +16,8 @@
#include "particle_allocator.hh"
+#include "BLI_rand.hh"
+
namespace blender::sim {
AttributesAllocator::~AttributesAllocator()
@@ -67,8 +69,15 @@ fn::MutableAttributesRef ParticleAllocator::allocate(int size)
ids[pindex] = start_id + pindex;
}
}
+ else if (name == "Hash") {
+ MutableSpan<int> hashes = attributes.get<int>("Hash");
+ RandomNumberGenerator rng(hash_seed_ ^ (uint32_t)next_id_);
+ for (int pindex : IndexRange(size)) {
+ hashes[pindex] = (int)rng.get_uint32();
+ }
+ }
else {
- type.fill_uninitialized(info.default_of(i), attributes.get(i).buffer(), size);
+ type.fill_uninitialized(info.default_of(i), attributes.get(i).data(), size);
}
}
return attributes;
diff --git a/source/blender/simulation/intern/particle_allocator.hh b/source/blender/simulation/intern/particle_allocator.hh
index ae23c8c8238..c0bbdb845d9 100644
--- a/source/blender/simulation/intern/particle_allocator.hh
+++ b/source/blender/simulation/intern/particle_allocator.hh
@@ -69,10 +69,11 @@ class ParticleAllocator : NonCopyable, NonMovable {
private:
AttributesAllocator attributes_allocator_;
std::atomic<int> next_id_;
+ uint32_t hash_seed_;
public:
- ParticleAllocator(const fn::AttributesInfo &attributes_info, int next_id)
- : attributes_allocator_(attributes_info), next_id_(next_id)
+ ParticleAllocator(const fn::AttributesInfo &attributes_info, int next_id, uint32_t hash_seed)
+ : attributes_allocator_(attributes_info), next_id_(next_id), hash_seed_(hash_seed)
{
}
diff --git a/source/blender/simulation/intern/particle_function.cc b/source/blender/simulation/intern/particle_function.cc
index 935ef7983d9..035e6d50e21 100644
--- a/source/blender/simulation/intern/particle_function.cc
+++ b/source/blender/simulation/intern/particle_function.cc
@@ -49,19 +49,17 @@ ParticleFunction::ParticleFunction(const fn::MultiFunction *global_fn,
}
}
-ParticleFunctionEvaluator::ParticleFunctionEvaluator(
- const ParticleFunction &particle_fn,
- const SimulationSolveContext &solve_context,
- const ParticleChunkContext &particle_chunk_context)
+ParticleFunctionEvaluator::ParticleFunctionEvaluator(const ParticleFunction &particle_fn,
+ const SimulationSolveContext &solve_context,
+ const ParticleChunkContext &particles)
: particle_fn_(particle_fn),
solve_context_(solve_context),
- particle_chunk_context_(particle_chunk_context),
- mask_(particle_chunk_context_.index_mask()),
+ particles_(particles),
+ mask_(particles_.index_mask),
outputs_(particle_fn_.output_types_.size(), nullptr)
{
- global_context_.add_global_context("PersistentDataHandleMap", &solve_context_.handle_map());
- per_particle_context_.add_global_context("PersistentDataHandleMap",
- &solve_context_.handle_map());
+ global_context_.add_global_context("PersistentDataHandleMap", &solve_context_.handle_map);
+ per_particle_context_.add_global_context("PersistentDataHandleMap", &solve_context_.handle_map);
}
ParticleFunctionEvaluator::~ParticleFunctionEvaluator()
@@ -92,8 +90,10 @@ void ParticleFunctionEvaluator::compute()
fn::GVSpan ParticleFunctionEvaluator::get(int output_index, StringRef expected_name) const
{
#ifdef DEBUG
- StringRef real_name = particle_fn_.output_names_[output_index];
- BLI_assert(expected_name == real_name);
+ if (expected_name != "") {
+ StringRef real_name = particle_fn_.output_names_[output_index];
+ BLI_assert(expected_name == real_name);
+ }
BLI_assert(is_computed_);
#endif
UNUSED_VARS_NDEBUG(expected_name);
@@ -116,8 +116,9 @@ void ParticleFunctionEvaluator::compute_globals()
fn::MFParamsBuilder params(*particle_fn_.global_fn_, mask_.min_array_size());
/* Add input parameters. */
+ ParticleFunctionInputContext input_context{solve_context_, particles_};
for (const ParticleFunctionInput *input : particle_fn_.global_inputs_) {
- input->add_input(particle_chunk_context_.attributes(), params, resources_);
+ input->add_input(input_context, params, resources_);
}
/* Add output parameters. */
@@ -143,8 +144,9 @@ void ParticleFunctionEvaluator::compute_per_particle()
fn::MFParamsBuilder params(*particle_fn_.per_particle_fn_, mask_.min_array_size());
/* Add input parameters. */
+ ParticleFunctionInputContext input_context{solve_context_, particles_};
for (const ParticleFunctionInput *input : particle_fn_.per_particle_inputs_) {
- input->add_input(particle_chunk_context_.attributes(), params, resources_);
+ input->add_input(input_context, params, resources_);
}
/* Add output parameters. */
diff --git a/source/blender/simulation/intern/particle_function.hh b/source/blender/simulation/intern/particle_function.hh
index cc8ccbd8243..ead4e6f3c31 100644
--- a/source/blender/simulation/intern/particle_function.hh
+++ b/source/blender/simulation/intern/particle_function.hh
@@ -25,10 +25,15 @@
namespace blender::sim {
+struct ParticleFunctionInputContext {
+ const SimulationSolveContext &solve_context;
+ const ParticleChunkContext &particles;
+};
+
class ParticleFunctionInput {
public:
virtual ~ParticleFunctionInput() = default;
- virtual void add_input(fn::AttributesRef attributes,
+ virtual void add_input(ParticleFunctionInputContext &context,
fn::MFParamsBuilder &params,
ResourceCollector &resources) const = 0;
};
@@ -60,7 +65,7 @@ class ParticleFunctionEvaluator {
ResourceCollector resources_;
const ParticleFunction &particle_fn_;
const SimulationSolveContext &solve_context_;
- const ParticleChunkContext &particle_chunk_context_;
+ const ParticleChunkContext &particles_;
IndexMask mask_;
fn::MFContextBuilder global_context_;
fn::MFContextBuilder per_particle_context_;
@@ -70,13 +75,13 @@ class ParticleFunctionEvaluator {
public:
ParticleFunctionEvaluator(const ParticleFunction &particle_fn,
const SimulationSolveContext &solve_context,
- const ParticleChunkContext &particle_chunk_context);
+ const ParticleChunkContext &particles);
~ParticleFunctionEvaluator();
void compute();
- fn::GVSpan get(int output_index, StringRef expected_name) const;
+ fn::GVSpan get(int output_index, StringRef expected_name = "") const;
- template<typename T> fn::VSpan<T> get(int output_index, StringRef expected_name) const
+ template<typename T> fn::VSpan<T> get(int output_index, StringRef expected_name = "") const
{
return this->get(output_index, expected_name).typed<T>();
}
diff --git a/source/blender/simulation/intern/particle_mesh_emitter.cc b/source/blender/simulation/intern/particle_mesh_emitter.cc
new file mode 100644
index 00000000000..26541d550eb
--- /dev/null
+++ b/source/blender/simulation/intern/particle_mesh_emitter.cc
@@ -0,0 +1,362 @@
+/*
+ * 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.
+ */
+
+#include "particle_mesh_emitter.hh"
+
+#include "BLI_float4x4.hh"
+#include "BLI_rand.hh"
+#include "BLI_vector_adaptor.hh"
+
+#include "BKE_mesh_runtime.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+namespace blender::sim {
+
+ParticleMeshEmitter::~ParticleMeshEmitter() = default;
+
+struct EmitterSettings {
+ Object *object;
+ float rate;
+};
+
+static BLI_NOINLINE void compute_birth_times(float rate,
+ TimeInterval emit_interval,
+ ParticleMeshEmitterSimulationState &state,
+ Vector<float> &r_birth_times)
+{
+ const float time_between_particles = 1.0f / rate;
+ int counter = 0;
+ while (true) {
+ counter++;
+ const float time_offset = counter * time_between_particles;
+ const float birth_time = state.last_birth_time + time_offset;
+ if (birth_time > emit_interval.stop()) {
+ break;
+ }
+ if (birth_time <= emit_interval.start()) {
+ continue;
+ }
+ r_birth_times.append(birth_time);
+ }
+}
+
+static BLI_NOINLINE Span<MLoopTri> get_mesh_triangles(Mesh &mesh)
+{
+ const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(&mesh);
+ int amount = BKE_mesh_runtime_looptri_len(&mesh);
+ return Span(triangles, amount);
+}
+
+static BLI_NOINLINE void compute_triangle_areas(Mesh &mesh,
+ Span<MLoopTri> triangles,
+ MutableSpan<float> r_areas)
+{
+ assert_same_size(triangles, r_areas);
+
+ for (int i : triangles.index_range()) {
+ const MLoopTri &tri = triangles[i];
+
+ const float3 v1 = mesh.mvert[mesh.mloop[tri.tri[0]].v].co;
+ const float3 v2 = mesh.mvert[mesh.mloop[tri.tri[1]].v].co;
+ const float3 v3 = mesh.mvert[mesh.mloop[tri.tri[2]].v].co;
+
+ const float area = area_tri_v3(v1, v2, v3);
+ r_areas[i] = area;
+ }
+}
+
+static BLI_NOINLINE void compute_triangle_weights(Mesh &mesh,
+ Span<MLoopTri> triangles,
+ MutableSpan<float> r_weights)
+{
+ assert_same_size(triangles, r_weights);
+ compute_triangle_areas(mesh, triangles, r_weights);
+}
+
+static BLI_NOINLINE void compute_cumulative_distribution(Span<float> weights,
+ MutableSpan<float> r_cumulative_weights)
+{
+ BLI_assert(weights.size() + 1 == r_cumulative_weights.size());
+
+ r_cumulative_weights[0] = 0;
+ for (int i : weights.index_range()) {
+ r_cumulative_weights[i + 1] = r_cumulative_weights[i] + weights[i];
+ }
+}
+
+static void sample_cumulative_distribution_recursive(RandomNumberGenerator &rng,
+ int amount,
+ int start,
+ int one_after_end,
+ Span<float> cumulative_weights,
+ VectorAdaptor<int> &r_sampled_indices)
+{
+ BLI_assert(start <= one_after_end);
+ const int size = one_after_end - start;
+ if (size == 0) {
+ BLI_assert(amount == 0);
+ }
+ else if (amount == 0) {
+ return;
+ }
+ else if (size == 1) {
+ r_sampled_indices.append_n_times(start, amount);
+ }
+ else {
+ const int middle = start + size / 2;
+ const float left_weight = cumulative_weights[middle] - cumulative_weights[start];
+ const float right_weight = cumulative_weights[one_after_end] - cumulative_weights[middle];
+ BLI_assert(left_weight >= 0.0f && right_weight >= 0.0f);
+ const float weight_sum = left_weight + right_weight;
+ BLI_assert(weight_sum > 0.0f);
+
+ const float left_factor = left_weight / weight_sum;
+ const float right_factor = right_weight / weight_sum;
+
+ int left_amount = amount * left_factor;
+ int right_amount = amount * right_factor;
+
+ if (left_amount + right_amount < amount) {
+ BLI_assert(left_amount + right_amount + 1 == amount);
+ const float weight_per_item = weight_sum / amount;
+ const float total_remaining_weight = weight_sum -
+ (left_amount + right_amount) * weight_per_item;
+ const float left_remaining_weight = left_weight - left_amount * weight_per_item;
+ const float left_remaining_factor = left_remaining_weight / total_remaining_weight;
+ if (rng.get_float() < left_remaining_factor) {
+ left_amount++;
+ }
+ else {
+ right_amount++;
+ }
+ }
+
+ sample_cumulative_distribution_recursive(
+ rng, left_amount, start, middle, cumulative_weights, r_sampled_indices);
+ sample_cumulative_distribution_recursive(
+ rng, right_amount, middle, one_after_end, cumulative_weights, r_sampled_indices);
+ }
+}
+
+static BLI_NOINLINE void sample_cumulative_distribution(RandomNumberGenerator &rng,
+ Span<float> cumulative_weights,
+ MutableSpan<int> r_samples)
+{
+ VectorAdaptor<int> sampled_indices(r_samples);
+ sample_cumulative_distribution_recursive(rng,
+ r_samples.size(),
+ 0,
+ cumulative_weights.size() - 1,
+ cumulative_weights,
+ sampled_indices);
+ BLI_assert(sampled_indices.is_full());
+}
+
+static BLI_NOINLINE bool sample_weighted_buckets(RandomNumberGenerator &rng,
+ Span<float> weights,
+ MutableSpan<int> r_samples)
+{
+ Array<float> cumulative_weights(weights.size() + 1);
+ compute_cumulative_distribution(weights, cumulative_weights);
+
+ if (r_samples.size() > 0 && cumulative_weights.as_span().last() == 0.0f) {
+ /* All weights are zero. */
+ return false;
+ }
+
+ sample_cumulative_distribution(rng, cumulative_weights, r_samples);
+ return true;
+}
+
+static BLI_NOINLINE void sample_looptris(RandomNumberGenerator &rng,
+ Mesh &mesh,
+ Span<MLoopTri> triangles,
+ Span<int> triangles_to_sample,
+ MutableSpan<float3> r_sampled_positions,
+ MutableSpan<float3> r_sampled_normals)
+{
+ assert_same_size(triangles_to_sample, r_sampled_positions, r_sampled_normals);
+
+ MLoop *loops = mesh.mloop;
+ MVert *verts = mesh.mvert;
+
+ for (uint i : triangles_to_sample.index_range()) {
+ const uint triangle_index = triangles_to_sample[i];
+ const MLoopTri &triangle = triangles[triangle_index];
+
+ const float3 v1 = verts[loops[triangle.tri[0]].v].co;
+ const float3 v2 = verts[loops[triangle.tri[1]].v].co;
+ const float3 v3 = verts[loops[triangle.tri[2]].v].co;
+
+ const float3 bary_coords = rng.get_barycentric_coordinates();
+
+ float3 position;
+ interp_v3_v3v3v3(position, v1, v2, v3, bary_coords);
+
+ float3 normal;
+ normal_tri_v3(normal, v1, v2, v3);
+
+ r_sampled_positions[i] = position;
+ r_sampled_normals[i] = normal;
+ }
+}
+
+static BLI_NOINLINE bool compute_new_particle_attributes(ParticleEmitterContext &context,
+ EmitterSettings &settings,
+ ParticleMeshEmitterSimulationState &state,
+ Vector<float3> &r_positions,
+ Vector<float3> &r_velocities,
+ Vector<float> &r_birth_times)
+{
+ if (settings.object == nullptr) {
+ return false;
+ }
+ if (settings.rate <= 0.000001f) {
+ return false;
+ }
+ if (settings.object->type != OB_MESH) {
+ return false;
+ }
+ Mesh &mesh = *(Mesh *)settings.object->data;
+ if (mesh.totvert == 0) {
+ return false;
+ }
+
+ const float start_time = context.emit_interval.start();
+ const uint32_t seed = DefaultHash<StringRef>{}(state.head.name);
+ RandomNumberGenerator rng{(*(uint32_t *)&start_time) ^ seed};
+
+ compute_birth_times(settings.rate, context.emit_interval, state, r_birth_times);
+ const int particle_amount = r_birth_times.size();
+ if (particle_amount == 0) {
+ return false;
+ }
+
+ const float last_birth_time = r_birth_times.last();
+ rng.shuffle(r_birth_times.as_mutable_span());
+
+ Span<MLoopTri> triangles = get_mesh_triangles(mesh);
+ if (triangles.is_empty()) {
+ return false;
+ }
+
+ Array<float> triangle_weights(triangles.size());
+ compute_triangle_weights(mesh, triangles, triangle_weights);
+
+ Array<int> triangles_to_sample(particle_amount);
+ if (!sample_weighted_buckets(rng, triangle_weights, triangles_to_sample)) {
+ return false;
+ }
+
+ r_positions.resize(particle_amount);
+ r_velocities.resize(particle_amount);
+ sample_looptris(rng, mesh, triangles, triangles_to_sample, r_positions, r_velocities);
+
+ if (context.solve_context.dependency_animations.is_object_transform_changing(*settings.object)) {
+ Array<float4x4> local_to_world_matrices(particle_amount);
+ context.solve_context.dependency_animations.get_object_transforms(
+ *settings.object, r_birth_times, local_to_world_matrices);
+
+ for (int i : IndexRange(particle_amount)) {
+ const float4x4 &position_to_world = local_to_world_matrices[i];
+ const float4x4 normal_to_world = position_to_world.inverted_transposed_affine();
+ r_positions[i] = position_to_world * r_positions[i];
+ r_velocities[i] = normal_to_world * r_velocities[i];
+ }
+ }
+ else {
+ const float4x4 position_to_world = settings.object->obmat;
+ const float4x4 normal_to_world = position_to_world.inverted_transposed_affine();
+ for (int i : IndexRange(particle_amount)) {
+ r_positions[i] = position_to_world * r_positions[i];
+ r_velocities[i] = normal_to_world * r_velocities[i];
+ }
+ }
+
+ for (int i : IndexRange(particle_amount)) {
+ r_velocities[i].normalize();
+ }
+
+ state.last_birth_time = last_birth_time;
+ return true;
+}
+
+static BLI_NOINLINE EmitterSettings compute_settings(const fn::MultiFunction &inputs_fn,
+ ParticleEmitterContext &context)
+{
+ EmitterSettings parameters;
+
+ fn::MFContextBuilder mf_context;
+ mf_context.add_global_context("PersistentDataHandleMap", &context.solve_context.handle_map);
+
+ fn::MFParamsBuilder mf_params{inputs_fn, 1};
+ bke::PersistentObjectHandle object_handle;
+ mf_params.add_uninitialized_single_output(&object_handle, "Object");
+ mf_params.add_uninitialized_single_output(&parameters.rate, "Rate");
+
+ inputs_fn.call(IndexRange(1), mf_params, mf_context);
+
+ parameters.object = context.solve_context.handle_map.lookup(object_handle);
+ return parameters;
+}
+
+void ParticleMeshEmitter::emit(ParticleEmitterContext &context) const
+{
+ auto *state = context.lookup_state<ParticleMeshEmitterSimulationState>(own_state_name_);
+ if (state == nullptr) {
+ return;
+ }
+
+ EmitterSettings settings = compute_settings(inputs_fn_, context);
+
+ Vector<float3> new_positions;
+ Vector<float3> new_velocities;
+ Vector<float> new_birth_times;
+
+ if (!compute_new_particle_attributes(
+ context, settings, *state, new_positions, new_velocities, new_birth_times)) {
+ return;
+ }
+
+ for (StringRef name : particle_names_) {
+ ParticleAllocator *allocator = context.try_get_particle_allocator(name);
+ if (allocator == nullptr) {
+ continue;
+ }
+
+ int amount = new_positions.size();
+ fn::MutableAttributesRef attributes = allocator->allocate(amount);
+
+ attributes.get<float3>("Position").copy_from(new_positions);
+ attributes.get<float3>("Velocity").copy_from(new_velocities);
+ attributes.get<float>("Birth Time").copy_from(new_birth_times);
+
+ if (action_ != nullptr) {
+ ParticleChunkContext particles{
+ *context.solve_context.state_map.lookup<ParticleSimulationState>(name),
+ IndexRange(amount),
+ attributes,
+ nullptr};
+ ParticleActionContext action_context{context.solve_context, particles};
+ action_->execute(action_context);
+ }
+ }
+}
+
+} // namespace blender::sim
diff --git a/source/blender/simulation/intern/particle_mesh_emitter.hh b/source/blender/simulation/intern/particle_mesh_emitter.hh
new file mode 100644
index 00000000000..724d79c1aec
--- /dev/null
+++ b/source/blender/simulation/intern/particle_mesh_emitter.hh
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#ifndef __SIM_PARTICLE_MESH_EMITTER_HH__
+#define __SIM_PARTICLE_MESH_EMITTER_HH__
+
+#include "simulation_solver_influences.hh"
+
+#include "FN_multi_function.hh"
+
+namespace blender::sim {
+
+class ParticleMeshEmitter final : public ParticleEmitter {
+ private:
+ std::string own_state_name_;
+ Array<std::string> particle_names_;
+ const fn::MultiFunction &inputs_fn_;
+ const ParticleAction *action_;
+
+ public:
+ ParticleMeshEmitter(std::string own_state_name,
+ Array<std::string> particle_names,
+ const fn::MultiFunction &inputs_fn,
+ const ParticleAction *action)
+ : own_state_name_(std::move(own_state_name)),
+ particle_names_(particle_names),
+ inputs_fn_(inputs_fn),
+ action_(action)
+ {
+ }
+
+ ~ParticleMeshEmitter();
+
+ void emit(ParticleEmitterContext &context) const override;
+};
+
+} // namespace blender::sim
+
+#endif /* __SIM_PARTICLE_MESH_EMITTER_HH__ */
diff --git a/source/blender/simulation/intern/simulation_collect_influences.cc b/source/blender/simulation/intern/simulation_collect_influences.cc
index 764e587d157..818415e5d88 100644
--- a/source/blender/simulation/intern/simulation_collect_influences.cc
+++ b/source/blender/simulation/intern/simulation_collect_influences.cc
@@ -16,6 +16,7 @@
#include "simulation_collect_influences.hh"
#include "particle_function.hh"
+#include "particle_mesh_emitter.hh"
#include "FN_attributes_ref.hh"
#include "FN_multi_function_network_evaluation.hh"
@@ -23,38 +24,125 @@
#include "NOD_node_tree_multi_function.hh"
+#include "DEG_depsgraph_query.h"
+
+#include "BLI_hash.h"
#include "BLI_rand.hh"
+#include "BLI_set.hh"
namespace blender::sim {
+using fn::GVSpan;
+using fn::MFContextBuilder;
+using fn::MFDataType;
+using fn::MFDummyNode;
+using fn::MFFunctionNode;
+using fn::MFInputSocket;
+using fn::MFNetwork;
+using fn::MFNetworkEvaluator;
+using fn::MFNode;
+using fn::MFOutputSocket;
+using fn::MFParamsBuilder;
+using fn::MFParamType;
+using fn::MultiFunction;
+using fn::VSpan;
+using nodes::DerivedNodeTree;
+using nodes::DInputSocket;
+using nodes::DNode;
+using nodes::DOutputSocket;
+using nodes::DParentNode;
+using nodes::MFNetworkTreeMap;
+using nodes::NodeTreeRefMap;
+
struct DummyDataSources {
- Map<const fn::MFOutputSocket *, std::string> particle_attributes;
+ Map<const MFOutputSocket *, std::string> particle_attributes;
+ Set<const MFOutputSocket *> simulation_time;
+ Set<const MFOutputSocket *> scene_time;
};
extern "C" {
void WM_clipboard_text_set(const char *buf, bool selection);
}
-static std::string dnode_to_path(const nodes::DNode &dnode)
+static std::string dnode_to_path(const DNode &dnode)
{
std::string path;
- for (const nodes::DParentNode *parent = dnode.parent(); parent; parent = parent->parent()) {
+ for (const DParentNode *parent = dnode.parent(); parent; parent = parent->parent()) {
path = parent->node_ref().name() + "/" + path;
}
path = path + dnode.name();
return path;
}
-static Span<const nodes::DNode *> get_particle_simulation_nodes(const nodes::DerivedNodeTree &tree)
+struct CollectContext : NonCopyable, NonMovable {
+ SimulationInfluences &influences;
+ RequiredStates &required_states;
+ ResourceCollector &resources;
+ MFNetworkTreeMap &network_map;
+ MFNetwork &network;
+ const DerivedNodeTree &tree;
+
+ DummyDataSources data_sources;
+ Span<const DNode *> particle_simulation_nodes;
+ Map<const DNode *, std::string> node_paths;
+
+ CollectContext(SimulationInfluences &influences,
+ RequiredStates &required_states,
+ ResourceCollector &resources,
+ MFNetworkTreeMap &network_map)
+ : influences(influences),
+ required_states(required_states),
+ resources(resources),
+ network_map(network_map),
+ network(network_map.network()),
+ tree(network_map.tree())
+ {
+ particle_simulation_nodes = tree.nodes_by_type("SimulationNodeParticleSimulation");
+ }
+};
+
+static const ParticleAction *create_particle_action(CollectContext &context,
+ const DOutputSocket &dsocket,
+ Span<StringRefNull> particle_names);
+
+static const ParticleAction *create_particle_action(CollectContext &context,
+ const DInputSocket &dsocket,
+ Span<StringRefNull> particle_names)
+{
+ BLI_assert(dsocket.bsocket()->type == SOCK_CONTROL_FLOW);
+ if (dsocket.linked_sockets().size() != 1) {
+ return nullptr;
+ }
+ return create_particle_action(context, *dsocket.linked_sockets()[0], particle_names);
+}
+
+static StringRefNull get_identifier(CollectContext &context, const DNode &dnode)
+{
+ return context.node_paths.lookup_or_add_cb(&dnode, [&]() { return dnode_to_path(dnode); });
+}
+
+static Span<const DNode *> nodes_by_type(CollectContext &context, StringRefNull idname)
{
- return tree.nodes_by_type("SimulationNodeParticleSimulation");
+ return context.tree.nodes_by_type(idname);
+}
+
+static Array<StringRefNull> find_linked_particle_simulations(CollectContext &context,
+ const DOutputSocket &output_socket)
+{
+ VectorSet<StringRefNull> names;
+ for (const DInputSocket *target_socket : output_socket.linked_sockets()) {
+ if (target_socket->node().idname() == "SimulationNodeParticleSimulation") {
+ names.add(get_identifier(context, target_socket->node()));
+ }
+ }
+ return names.as_span();
}
/* Returns true on success. */
-static bool compute_global_inputs(nodes::MFNetworkTreeMap &network_map,
+static bool compute_global_inputs(MFNetworkTreeMap &network_map,
ResourceCollector &resources,
- Span<const fn::MFInputSocket *> sockets,
- MutableSpan<fn::GMutableSpan> r_results)
+ Span<const MFInputSocket *> sockets,
+ MutableSpan<GMutableSpan> r_results)
{
int amount = sockets.size();
if (amount == 0) {
@@ -65,28 +153,28 @@ static bool compute_global_inputs(nodes::MFNetworkTreeMap &network_map,
return false;
}
- fn::MFNetworkEvaluator network_fn{{}, sockets};
- fn::MFParamsBuilder params{network_fn, 1};
+ MFNetworkEvaluator network_fn{{}, sockets};
+ MFParamsBuilder params{network_fn, 1};
for (int param_index : network_fn.param_indices()) {
- fn::MFParamType param_type = network_fn.param_type(param_index);
- BLI_assert(param_type.category() == fn::MFParamType::Category::SingleOutput); /* For now. */
- const fn::CPPType &type = param_type.data_type().single_type();
+ MFParamType param_type = network_fn.param_type(param_index);
+ BLI_assert(param_type.category() == MFParamType::Category::SingleOutput); /* For now. */
+ const CPPType &type = param_type.data_type().single_type();
void *buffer = resources.linear_allocator().allocate(type.size(), type.alignment());
resources.add(buffer, type.destruct_cb(), AT);
- fn::GMutableSpan span{type, buffer, 1};
+ GMutableSpan span{type, buffer, 1};
r_results[param_index] = span;
params.add_uninitialized_single_output(span);
}
- fn::MFContextBuilder context;
+ MFContextBuilder context;
network_fn.call(IndexRange(1), params, context);
return true;
}
static std::optional<Array<std::string>> compute_global_string_inputs(
- nodes::MFNetworkTreeMap &network_map, Span<const fn::MFInputSocket *> sockets)
+ MFNetworkTreeMap &network_map, Span<const MFInputSocket *> sockets)
{
ResourceCollector local_resources;
- Array<fn::GMutableSpan> computed_values(sockets.size(), NoInitialization());
+ Array<GMutableSpan> computed_values(sockets.size(), NoInitialization());
if (!compute_global_inputs(network_map, local_resources, sockets, computed_values)) {
return {};
}
@@ -98,107 +186,177 @@ static std::optional<Array<std::string>> compute_global_string_inputs(
return strings;
}
-static void find_and_deduplicate_particle_attribute_nodes(nodes::MFNetworkTreeMap &network_map,
- DummyDataSources &r_data_sources)
+/**
+ * This will find all the particle attribute input nodes. Then it will compute the attribute names
+ * by evaluating the network (those names should not depend on per particle data). In the end,
+ * input nodes that access the same attribute are combined.
+ */
+static void prepare_particle_attribute_nodes(CollectContext &context)
{
- fn::MFNetwork &network = network_map.network();
- const nodes::DerivedNodeTree &tree = network_map.tree();
-
- Span<const nodes::DNode *> attribute_dnodes = tree.nodes_by_type(
- "SimulationNodeParticleAttribute");
+ Span<const DNode *> attribute_dnodes = nodes_by_type(context, "SimulationNodeParticleAttribute");
- Vector<fn::MFInputSocket *> name_sockets;
- for (const nodes::DNode *dnode : attribute_dnodes) {
- fn::MFInputSocket &name_socket = network_map.lookup_dummy(dnode->input(0));
+ Vector<MFInputSocket *> name_sockets;
+ for (const DNode *dnode : attribute_dnodes) {
+ MFInputSocket &name_socket = context.network_map.lookup_dummy(dnode->input(0));
name_sockets.append(&name_socket);
}
- std::optional<Array<std::string>> attribute_names = compute_global_string_inputs(network_map,
- name_sockets);
+ std::optional<Array<std::string>> attribute_names = compute_global_string_inputs(
+ context.network_map, name_sockets);
if (!attribute_names.has_value()) {
return;
}
- Map<std::pair<std::string, fn::MFDataType>, Vector<fn::MFNode *>>
- attribute_nodes_by_name_and_type;
+ MultiValueMap<std::pair<std::string, MFDataType>, MFNode *> attribute_nodes_by_name_and_type;
for (int i : attribute_names->index_range()) {
- attribute_nodes_by_name_and_type
- .lookup_or_add_default(
- {(*attribute_names)[i], name_sockets[i]->node().output(0).data_type()})
- .append(&name_sockets[i]->node());
+ attribute_nodes_by_name_and_type.add(
+ {(*attribute_names)[i], name_sockets[i]->node().output(0).data_type()},
+ &name_sockets[i]->node());
}
- Map<const fn::MFOutputSocket *, std::string> attribute_inputs;
+ Map<const MFOutputSocket *, std::string> attribute_inputs;
for (auto item : attribute_nodes_by_name_and_type.items()) {
StringRef attribute_name = item.key.first;
- fn::MFDataType data_type = item.key.second;
- Span<fn::MFNode *> nodes = item.value;
+ MFDataType data_type = item.key.second;
+ Span<MFNode *> nodes = item.value;
- fn::MFOutputSocket &new_attribute_socket = network.add_input(
+ MFOutputSocket &new_attribute_socket = context.network.add_input(
"Attribute '" + attribute_name + "'", data_type);
- for (fn::MFNode *node : nodes) {
- network.relink(node->output(0), new_attribute_socket);
+ for (MFNode *node : nodes) {
+ context.network.relink(node->output(0), new_attribute_socket);
}
- network.remove(nodes);
+ context.network.remove(nodes);
- r_data_sources.particle_attributes.add_new(&new_attribute_socket, attribute_name);
+ context.data_sources.particle_attributes.add_new(&new_attribute_socket, attribute_name);
+ }
+}
+
+static void prepare_time_input_nodes(CollectContext &context)
+{
+ Span<const DNode *> time_input_dnodes = nodes_by_type(context, "SimulationNodeTime");
+ Vector<const DNode *> simulation_time_inputs;
+ Vector<const DNode *> scene_time_inputs;
+ for (const DNode *dnode : time_input_dnodes) {
+ NodeSimInputTimeType type = (NodeSimInputTimeType)dnode->node_ref().bnode()->custom1;
+ switch (type) {
+ case NODE_SIM_INPUT_SIMULATION_TIME: {
+ simulation_time_inputs.append(dnode);
+ break;
+ }
+ case NODE_SIM_INPUT_SCENE_TIME: {
+ scene_time_inputs.append(dnode);
+ break;
+ }
+ }
+ }
+
+ if (simulation_time_inputs.size() > 0) {
+ MFOutputSocket &new_socket = context.network.add_input("Simulation Time",
+ MFDataType::ForSingle<float>());
+ for (const DNode *dnode : simulation_time_inputs) {
+ MFOutputSocket &old_socket = context.network_map.lookup_dummy(dnode->output(0));
+ context.network.relink(old_socket, new_socket);
+ context.network.remove(old_socket.node());
+ }
+ context.data_sources.simulation_time.add(&new_socket);
+ }
+ if (scene_time_inputs.size() > 0) {
+ MFOutputSocket &new_socket = context.network.add_input("Scene Time",
+ MFDataType::ForSingle<float>());
+ for (const DNode *dnode : scene_time_inputs) {
+ MFOutputSocket &old_socket = context.network_map.lookup_dummy(dnode->output(0));
+ context.network.relink(old_socket, new_socket);
+ context.network.remove(old_socket.node());
+ }
+ context.data_sources.scene_time.add(&new_socket);
}
}
class ParticleAttributeInput : public ParticleFunctionInput {
private:
std::string attribute_name_;
- const fn::CPPType &attribute_type_;
+ const CPPType &attribute_type_;
public:
- ParticleAttributeInput(std::string attribute_name, const fn::CPPType &attribute_type)
+ ParticleAttributeInput(std::string attribute_name, const CPPType &attribute_type)
: attribute_name_(std::move(attribute_name)), attribute_type_(attribute_type)
{
}
- void add_input(fn::AttributesRef attributes,
- fn::MFParamsBuilder &params,
+ void add_input(ParticleFunctionInputContext &context,
+ MFParamsBuilder &params,
ResourceCollector &UNUSED(resources)) const override
{
- std::optional<fn::GSpan> span = attributes.try_get(attribute_name_, attribute_type_);
+ std::optional<GSpan> span = context.particles.attributes.try_get(attribute_name_,
+ attribute_type_);
if (span.has_value()) {
params.add_readonly_single_input(*span);
}
else {
- params.add_readonly_single_input(fn::GVSpan::FromDefault(attribute_type_));
+ params.add_readonly_single_input(GVSpan::FromDefault(attribute_type_));
}
}
};
+class SceneTimeInput : public ParticleFunctionInput {
+ void add_input(ParticleFunctionInputContext &context,
+ MFParamsBuilder &params,
+ ResourceCollector &resources) const override
+ {
+ const float time = DEG_get_ctime(&context.solve_context.depsgraph);
+ float *time_ptr = &resources.construct<float>(AT, time);
+ params.add_readonly_single_input(time_ptr);
+ }
+};
+
+class SimulationTimeInput : public ParticleFunctionInput {
+ void add_input(ParticleFunctionInputContext &context,
+ MFParamsBuilder &params,
+ ResourceCollector &resources) const override
+ {
+ /* TODO: Vary this per particle. */
+ const float time = context.solve_context.solve_interval.stop();
+ float *time_ptr = &resources.construct<float>(AT, time);
+ params.add_readonly_single_input(time_ptr);
+ }
+};
+
static const ParticleFunction *create_particle_function_for_inputs(
- Span<const fn::MFInputSocket *> sockets_to_compute,
- ResourceCollector &resources,
- DummyDataSources &data_sources)
+ CollectContext &context, Span<const MFInputSocket *> sockets_to_compute)
{
BLI_assert(sockets_to_compute.size() >= 1);
- const fn::MFNetwork &network = sockets_to_compute[0]->node().network();
+ const MFNetwork &network = sockets_to_compute[0]->node().network();
- VectorSet<const fn::MFOutputSocket *> dummy_deps;
- VectorSet<const fn::MFInputSocket *> unlinked_input_deps;
+ VectorSet<const MFOutputSocket *> dummy_deps;
+ VectorSet<const MFInputSocket *> unlinked_input_deps;
network.find_dependencies(sockets_to_compute, dummy_deps, unlinked_input_deps);
BLI_assert(unlinked_input_deps.size() == 0);
Vector<const ParticleFunctionInput *> per_particle_inputs;
- for (const fn::MFOutputSocket *socket : dummy_deps) {
- const std::string *attribute_name = data_sources.particle_attributes.lookup_ptr(socket);
- if (attribute_name == nullptr) {
- return nullptr;
+ for (const MFOutputSocket *socket : dummy_deps) {
+ if (context.data_sources.particle_attributes.contains(socket)) {
+ const std::string *attribute_name = context.data_sources.particle_attributes.lookup_ptr(
+ socket);
+ if (attribute_name == nullptr) {
+ return nullptr;
+ }
+ per_particle_inputs.append(&context.resources.construct<ParticleAttributeInput>(
+ AT, *attribute_name, socket->data_type().single_type()));
+ }
+ else if (context.data_sources.scene_time.contains(socket)) {
+ per_particle_inputs.append(&context.resources.construct<SceneTimeInput>(AT));
+ }
+ else if (context.data_sources.simulation_time.contains(socket)) {
+ per_particle_inputs.append(&context.resources.construct<SimulationTimeInput>(AT));
}
- per_particle_inputs.append(&resources.construct<ParticleAttributeInput>(
- AT, *attribute_name, socket->data_type().single_type()));
}
- const fn::MultiFunction &per_particle_fn = resources.construct<fn::MFNetworkEvaluator>(
+ const MultiFunction &per_particle_fn = context.resources.construct<MFNetworkEvaluator>(
AT, dummy_deps.as_span(), sockets_to_compute);
Array<bool> output_is_global(sockets_to_compute.size(), false);
- const ParticleFunction &particle_fn = resources.construct<ParticleFunction>(
+ const ParticleFunction &particle_fn = context.resources.construct<ParticleFunction>(
AT,
nullptr,
&per_particle_fn,
@@ -209,6 +367,17 @@ static const ParticleFunction *create_particle_function_for_inputs(
return &particle_fn;
}
+static const ParticleFunction *create_particle_function_for_inputs(
+ CollectContext &context, Span<const DInputSocket *> dsockets_to_compute)
+{
+ Vector<const MFInputSocket *> sockets_to_compute;
+ for (const DInputSocket *dsocket : dsockets_to_compute) {
+ const MFInputSocket &socket = context.network_map.lookup_dummy(*dsocket);
+ sockets_to_compute.append(&socket);
+ }
+ return create_particle_function_for_inputs(context, sockets_to_compute);
+}
+
class ParticleFunctionForce : public ParticleForce {
private:
const ParticleFunction &particle_fn_;
@@ -220,13 +389,12 @@ class ParticleFunctionForce : public ParticleForce {
void add_force(ParticleForceContext &context) const override
{
- IndexMask mask = context.particle_chunk().index_mask();
- MutableSpan<float3> r_combined_force = context.force_dst();
+ IndexMask mask = context.particles.index_mask;
+ MutableSpan<float3> r_combined_force = context.force_dst;
- ParticleFunctionEvaluator evaluator{
- particle_fn_, context.solve_context(), context.particle_chunk()};
+ ParticleFunctionEvaluator evaluator{particle_fn_, context.solve_context, context.particles};
evaluator.compute();
- fn::VSpan<float3> forces = evaluator.get<float3>(0, "Force");
+ VSpan<float3> forces = evaluator.get<float3>(0, "Force");
for (int64_t i : mask) {
r_combined_force[i] += forces[i];
@@ -234,218 +402,473 @@ class ParticleFunctionForce : public ParticleForce {
}
};
-static Vector<const ParticleForce *> create_forces_for_particle_simulation(
- const nodes::DNode &simulation_node,
- nodes::MFNetworkTreeMap &network_map,
- ResourceCollector &resources,
- DummyDataSources &data_sources)
+static void create_forces_for_particle_simulation(CollectContext &context,
+ const DNode &simulation_node)
{
Vector<const ParticleForce *> forces;
- for (const nodes::DOutputSocket *origin_socket :
- simulation_node.input(2, "Forces").linked_sockets()) {
- const nodes::DNode &origin_node = origin_socket->node();
+ for (const DOutputSocket *origin_socket : simulation_node.input(2, "Forces").linked_sockets()) {
+ const DNode &origin_node = origin_socket->node();
if (origin_node.idname() != "SimulationNodeForce") {
continue;
}
- const fn::MFInputSocket &force_socket = network_map.lookup_dummy(
- origin_node.input(0, "Force"));
-
const ParticleFunction *particle_fn = create_particle_function_for_inputs(
- {&force_socket}, resources, data_sources);
+ context, {&origin_node.input(0, "Force")});
if (particle_fn == nullptr) {
continue;
}
- const ParticleForce &force = resources.construct<ParticleFunctionForce>(AT, *particle_fn);
+ const ParticleForce &force = context.resources.construct<ParticleFunctionForce>(AT,
+ *particle_fn);
forces.append(&force);
}
- return forces;
+
+ StringRef particle_name = get_identifier(context, simulation_node);
+ context.influences.particle_forces.add_multiple_as(particle_name, forces);
+}
+
+static void collect_forces(CollectContext &context)
+{
+ for (const DNode *dnode : context.particle_simulation_nodes) {
+ create_forces_for_particle_simulation(context, *dnode);
+ }
+}
+
+static ParticleEmitter *create_particle_emitter(CollectContext &context, const DNode &dnode)
+{
+ Array<StringRefNull> names = find_linked_particle_simulations(context, dnode.output(0));
+ if (names.size() == 0) {
+ return nullptr;
+ }
+
+ Array<const MFInputSocket *> input_sockets{2};
+ for (int i : input_sockets.index_range()) {
+ input_sockets[i] = &context.network_map.lookup_dummy(dnode.input(i));
+ }
+
+ if (context.network.have_dummy_or_unlinked_dependencies(input_sockets)) {
+ return nullptr;
+ }
+
+ MultiFunction &inputs_fn = context.resources.construct<MFNetworkEvaluator>(
+ AT, Span<const MFOutputSocket *>(), input_sockets.as_span());
+
+ const ParticleAction *birth_action = create_particle_action(
+ context, dnode.input(2, "Execute"), names);
+
+ StringRefNull own_state_name = get_identifier(context, dnode);
+ context.required_states.add(own_state_name, SIM_TYPE_NAME_PARTICLE_MESH_EMITTER);
+ ParticleEmitter &emitter = context.resources.construct<ParticleMeshEmitter>(
+ AT, own_state_name, names.as_span(), inputs_fn, birth_action);
+ return &emitter;
+}
+
+static void collect_emitters(CollectContext &context)
+{
+ for (const DNode *dnode : nodes_by_type(context, "SimulationNodeParticleMeshEmitter")) {
+ ParticleEmitter *emitter = create_particle_emitter(context, *dnode);
+ if (emitter != nullptr) {
+ context.influences.particle_emitters.append(emitter);
+ }
+ }
}
-static void collect_forces(nodes::MFNetworkTreeMap &network_map,
- ResourceCollector &resources,
- DummyDataSources &data_sources,
- SimulationInfluences &r_influences)
+static void collect_birth_events(CollectContext &context)
{
- for (const nodes::DNode *dnode : get_particle_simulation_nodes(network_map.tree())) {
- std::string name = dnode_to_path(*dnode);
- Vector<const ParticleForce *> forces = create_forces_for_particle_simulation(
- *dnode, network_map, resources, data_sources);
- r_influences.particle_forces.add_new(std::move(name), std::move(forces));
+ for (const DNode *event_dnode : nodes_by_type(context, "SimulationNodeParticleBirthEvent")) {
+ const DInputSocket &execute_input = event_dnode->input(0);
+ if (execute_input.linked_sockets().size() != 1) {
+ continue;
+ }
+
+ Array<StringRefNull> particle_names = find_linked_particle_simulations(context,
+ event_dnode->output(0));
+
+ const DOutputSocket &execute_source = *execute_input.linked_sockets()[0];
+ const ParticleAction *action = create_particle_action(context, execute_source, particle_names);
+ if (action == nullptr) {
+ continue;
+ }
+
+ for (StringRefNull particle_name : particle_names) {
+ context.influences.particle_birth_actions.add_as(particle_name, action);
+ }
+ }
+}
+
+static void collect_time_step_events(CollectContext &context)
+{
+ for (const DNode *event_dnode : nodes_by_type(context, "SimulationNodeParticleTimeStepEvent")) {
+ const DInputSocket &execute_input = event_dnode->input(0);
+ Array<StringRefNull> particle_names = find_linked_particle_simulations(context,
+ event_dnode->output(0));
+
+ const ParticleAction *action = create_particle_action(context, execute_input, particle_names);
+ if (action == nullptr) {
+ continue;
+ }
+
+ NodeSimParticleTimeStepEventType type =
+ (NodeSimParticleTimeStepEventType)event_dnode->node_ref().bnode()->custom1;
+ if (type == NODE_PARTICLE_TIME_STEP_EVENT_BEGIN) {
+ for (StringRefNull particle_name : particle_names) {
+ context.influences.particle_time_step_begin_actions.add_as(particle_name, action);
+ }
+ }
+ else {
+ for (StringRefNull particle_name : particle_names) {
+ context.influences.particle_time_step_end_actions.add_as(particle_name, action);
+ }
+ }
}
}
-class MyBasicEmitter : public ParticleEmitter {
+class SequenceParticleAction : public ParticleAction {
private:
- Array<std::string> names_;
- std::string my_state_;
- const fn::MultiFunction &inputs_fn_;
- uint32_t seed_;
+ Vector<const ParticleAction *> actions_;
public:
- MyBasicEmitter(Array<std::string> names,
- std::string my_state,
- const fn::MultiFunction &inputs_fn,
- uint32_t seed)
- : names_(std::move(names)),
- my_state_(std::move(my_state)),
- inputs_fn_(inputs_fn),
- seed_(seed)
+ SequenceParticleAction(Span<const ParticleAction *> actions) : actions_(std::move(actions))
{
}
- void emit(ParticleEmitterContext &context) const override
+ void execute(ParticleActionContext &context) const override
{
- auto *state = context.solve_context().state_map().lookup<ParticleMeshEmitterSimulationState>(
- my_state_);
- if (state == nullptr) {
- return;
+ for (const ParticleAction *action : actions_) {
+ action->execute(context);
}
+ }
+};
- fn::MFContextBuilder mf_context;
- mf_context.add_global_context("PersistentDataHandleMap",
- &context.solve_context().handle_map());
+class SetParticleAttributeAction : public ParticleAction {
+ private:
+ std::string attribute_name_;
+ const CPPType &cpp_type_;
+ const ParticleFunction &inputs_fn_;
- fn::MFParamsBuilder mf_params{inputs_fn_, 1};
- bke::PersistentObjectHandle object_handle;
- float rate;
- mf_params.add_uninitialized_single_output(&object_handle);
- mf_params.add_uninitialized_single_output(&rate);
- inputs_fn_.call(IndexRange(1), mf_params, mf_context);
+ public:
+ SetParticleAttributeAction(std::string attribute_name,
+ const CPPType &cpp_type,
+ const ParticleFunction &inputs_fn)
+ : attribute_name_(std::move(attribute_name)), cpp_type_(cpp_type), inputs_fn_(inputs_fn)
+ {
+ }
- const Object *object = context.solve_context().handle_map().lookup(object_handle);
- if (object == nullptr) {
+ void execute(ParticleActionContext &context) const override
+ {
+ std::optional<GMutableSpan> attribute_array = context.particles.attributes.try_get(
+ attribute_name_, cpp_type_);
+ if (!attribute_array.has_value()) {
return;
}
- Vector<float3> new_positions;
- Vector<float3> new_velocities;
- Vector<float> new_birth_times;
-
- TimeInterval time_interval = context.simulation_time_interval();
- float start_time = time_interval.start();
- RandomNumberGenerator rng{(*(uint32_t *)&start_time) ^ seed_};
+ ParticleFunctionEvaluator evaluator{inputs_fn_, context.solve_context, context.particles};
+ evaluator.compute();
+ GVSpan values = evaluator.get(0);
- const float time_between_particles = 1.0f / rate;
- while (state->last_birth_time + time_between_particles < time_interval.end()) {
- new_positions.append(rng.get_unit_float3() * 0.3 + float3(object->loc));
- new_velocities.append(rng.get_unit_float3());
- const float birth_time = state->last_birth_time + time_between_particles;
- new_birth_times.append(birth_time);
- state->last_birth_time = birth_time;
+ if (values.is_single_element()) {
+ cpp_type_.fill_initialized_indices(
+ values.as_single_element(), attribute_array->data(), context.particles.index_mask);
+ }
+ else {
+ GSpan value_array = values.as_full_array();
+ cpp_type_.copy_to_initialized_indices(
+ value_array.data(), attribute_array->data(), context.particles.index_mask);
}
- for (StringRef name : names_) {
- ParticleAllocator *allocator = context.try_get_particle_allocator(name);
- if (allocator == nullptr) {
- return;
- }
-
- int amount = new_positions.size();
- fn::MutableAttributesRef attributes = allocator->allocate(amount);
-
- initialized_copy_n(new_positions.data(), amount, attributes.get<float3>("Position").data());
- initialized_copy_n(new_velocities.data(), amount, attributes.get<float3>("Velocity").data());
- initialized_copy_n(
- new_birth_times.data(), amount, attributes.get<float>("Birth Time").data());
+ if (attribute_name_ == "Velocity") {
+ context.particles.update_diffs_after_velocity_change();
}
}
};
-static Vector<const nodes::DNode *> find_linked_particle_simulations(
- const nodes::DOutputSocket &output_socket)
+static const ParticleAction *concatenate_actions(CollectContext &context,
+ Span<const ParticleAction *> actions)
{
- Vector<const nodes::DNode *> simulation_nodes;
- for (const nodes::DInputSocket *target_socket : output_socket.linked_sockets()) {
- if (target_socket->node().idname() == "SimulationNodeParticleSimulation") {
- simulation_nodes.append(&target_socket->node());
+ Vector<const ParticleAction *> non_null_actions;
+ for (const ParticleAction *action : actions) {
+ if (action != nullptr) {
+ non_null_actions.append(action);
}
}
- return simulation_nodes;
+ if (non_null_actions.size() == 0) {
+ return nullptr;
+ }
+ if (non_null_actions.size() == 1) {
+ return non_null_actions[0];
+ }
+ return &context.resources.construct<SequenceParticleAction>(AT, std::move(non_null_actions));
}
-static ParticleEmitter *create_particle_emitter(const nodes::DNode &dnode,
- ResourceCollector &resources,
- nodes::MFNetworkTreeMap &network_map,
- RequiredStates &r_required_states)
+static const ParticleAction *create_set_particle_attribute_action(
+ CollectContext &context, const DOutputSocket &dsocket, Span<StringRefNull> particle_names)
{
- Vector<const nodes::DNode *> simulation_dnodes = find_linked_particle_simulations(
- dnode.output(0));
- if (simulation_dnodes.size() == 0) {
- return nullptr;
+ const DNode &dnode = dsocket.node();
+
+ const ParticleAction *previous_action = create_particle_action(
+ context, dnode.input(0), particle_names);
+
+ MFInputSocket &name_socket = context.network_map.lookup_dummy(dnode.input(1));
+ MFInputSocket &value_socket = name_socket.node().input(1);
+ std::optional<Array<std::string>> names = compute_global_string_inputs(context.network_map,
+ {&name_socket});
+ if (!names.has_value()) {
+ return previous_action;
}
- Array<std::string> names{simulation_dnodes.size()};
- for (int i : simulation_dnodes.index_range()) {
- names[i] = dnode_to_path(*simulation_dnodes[i]);
+ std::string attribute_name = (*names)[0];
+ if (attribute_name.empty()) {
+ return previous_action;
}
+ const CPPType &attribute_type = value_socket.data_type().single_type();
- Array<const fn::MFInputSocket *> input_sockets{dnode.inputs().size()};
- for (int i : input_sockets.index_range()) {
- input_sockets[i] = &network_map.lookup_dummy(dnode.input(i));
+ const ParticleFunction *inputs_fn = create_particle_function_for_inputs(context,
+ {&value_socket});
+ if (inputs_fn == nullptr) {
+ return previous_action;
}
- if (network_map.network().have_dummy_or_unlinked_dependencies(input_sockets)) {
- return nullptr;
+ for (StringRef particle_name : particle_names) {
+ context.influences.particle_attributes_builder.lookup_as(particle_name)
+ ->add(attribute_name, attribute_type);
}
- fn::MultiFunction &inputs_fn = resources.construct<fn::MFNetworkEvaluator>(
- AT, Span<const fn::MFOutputSocket *>(), input_sockets.as_span());
+ ParticleAction &this_action = context.resources.construct<SetParticleAttributeAction>(
+ AT, attribute_name, attribute_type, *inputs_fn);
- std::string my_state_name = dnode_to_path(dnode);
- r_required_states.add(my_state_name, SIM_TYPE_NAME_PARTICLE_MESH_EMITTER);
- uint32_t seed = DefaultHash<std::string>{}(my_state_name);
- ParticleEmitter &emitter = resources.construct<MyBasicEmitter>(
- AT, std::move(names), std::move(my_state_name), inputs_fn, seed);
- return &emitter;
+ return concatenate_actions(context, {previous_action, &this_action});
}
-static void collect_emitters(nodes::MFNetworkTreeMap &network_map,
- ResourceCollector &resources,
- SimulationInfluences &r_influences,
- RequiredStates &r_required_states)
+class ParticleConditionAction : public ParticleAction {
+ private:
+ const ParticleFunction &inputs_fn_;
+ const ParticleAction *action_true_;
+ const ParticleAction *action_false_;
+
+ public:
+ ParticleConditionAction(const ParticleFunction &inputs_fn,
+ const ParticleAction *action_true,
+ const ParticleAction *action_false)
+ : inputs_fn_(inputs_fn), action_true_(action_true), action_false_(action_false)
+ {
+ }
+
+ void execute(ParticleActionContext &context) const override
+ {
+ ParticleFunctionEvaluator evaluator{inputs_fn_, context.solve_context, context.particles};
+ evaluator.compute();
+ VSpan<bool> conditions = evaluator.get<bool>(0, "Condition");
+
+ if (conditions.is_single_element()) {
+ const bool condition = conditions.as_single_element();
+ if (condition) {
+ if (action_true_ != nullptr) {
+ action_true_->execute(context);
+ }
+ }
+ else {
+ if (action_false_ != nullptr) {
+ action_false_->execute(context);
+ }
+ }
+ }
+ else {
+ Span<bool> conditions_array = conditions.as_full_array();
+
+ Vector<int64_t> true_indices;
+ Vector<int64_t> false_indices;
+ for (int i : context.particles.index_mask) {
+ if (conditions_array[i]) {
+ true_indices.append(i);
+ }
+ else {
+ false_indices.append(i);
+ }
+ }
+
+ if (action_true_ != nullptr) {
+ ParticleChunkContext chunk_context{context.particles.state,
+ true_indices.as_span(),
+ context.particles.attributes,
+ context.particles.integration};
+ ParticleActionContext action_context{context.solve_context, chunk_context};
+ action_true_->execute(action_context);
+ }
+ if (action_false_ != nullptr) {
+ ParticleChunkContext chunk_context{context.particles.state,
+ false_indices.as_span(),
+ context.particles.attributes,
+ context.particles.integration};
+ ParticleActionContext action_context{context.solve_context, chunk_context};
+ action_false_->execute(action_context);
+ }
+ }
+ }
+};
+
+static const ParticleAction *create_particle_condition_action(CollectContext &context,
+ const DOutputSocket &dsocket,
+ Span<StringRefNull> particle_names)
{
- for (const nodes::DNode *dnode :
- network_map.tree().nodes_by_type("SimulationNodeParticleMeshEmitter")) {
- ParticleEmitter *emitter = create_particle_emitter(
- *dnode, resources, network_map, r_required_states);
- if (emitter != nullptr) {
- r_influences.particle_emitters.append(emitter);
+ const DNode &dnode = dsocket.node();
+
+ const ParticleFunction *inputs_fn = create_particle_function_for_inputs(
+ context, {&dnode.input(0, "Condition")});
+ if (inputs_fn == nullptr) {
+ return nullptr;
+ }
+
+ const ParticleAction *true_action = create_particle_action(
+ context, dnode.input(1), particle_names);
+ const ParticleAction *false_action = create_particle_action(
+ context, dnode.input(2), particle_names);
+
+ if (true_action == nullptr && false_action == nullptr) {
+ return nullptr;
+ }
+ return &context.resources.construct<ParticleConditionAction>(
+ AT, *inputs_fn, true_action, false_action);
+}
+
+class KillParticleAction : public ParticleAction {
+ public:
+ void execute(ParticleActionContext &context) const override
+ {
+ MutableSpan<int> dead_states = context.particles.attributes.get<int>("Dead");
+ for (int i : context.particles.index_mask) {
+ dead_states[i] = true;
}
}
+};
+
+static const ParticleAction *create_particle_action(CollectContext &context,
+ const DOutputSocket &dsocket,
+ Span<StringRefNull> particle_names)
+{
+ const DNode &dnode = dsocket.node();
+ StringRef idname = dnode.idname();
+ if (idname == "SimulationNodeSetParticleAttribute") {
+ return create_set_particle_attribute_action(context, dsocket, particle_names);
+ }
+ if (idname == "SimulationNodeExecuteCondition") {
+ return create_particle_condition_action(context, dsocket, particle_names);
+ }
+ if (idname == "SimulationNodeKillParticle") {
+ return &context.resources.construct<KillParticleAction>(AT);
+ }
+ return nullptr;
}
-static void prepare_particle_attribute_builders(nodes::MFNetworkTreeMap &network_map,
- ResourceCollector &resources,
- SimulationInfluences &r_influences)
+static void initialize_particle_attribute_builders(CollectContext &context)
{
- for (const nodes::DNode *dnode : get_particle_simulation_nodes(network_map.tree())) {
- std::string name = dnode_to_path(*dnode);
- fn::AttributesInfoBuilder &builder = resources.construct<fn::AttributesInfoBuilder>(AT);
- builder.add<float3>("Position", {0, 0, 0});
- builder.add<float3>("Velocity", {0, 0, 0});
- builder.add<int>("ID", 0);
+ for (const DNode *dnode : context.particle_simulation_nodes) {
+ StringRef name = get_identifier(context, *dnode);
+ AttributesInfoBuilder &attributes_builder = context.resources.construct<AttributesInfoBuilder>(
+ AT);
+ attributes_builder.add<float3>("Position", {0, 0, 0});
+ attributes_builder.add<float3>("Velocity", {0, 0, 0});
+ attributes_builder.add<int>("ID", 0);
/* TODO: Use bool property, but need to add CD_PROP_BOOL first. */
- builder.add<int>("Dead", 0);
- builder.add<float>("Birth Time", 0.0f);
- r_influences.particle_attributes_builder.add_new(std::move(name), &builder);
+ attributes_builder.add<int>("Dead", 0);
+ /* TODO: Use uint32_t, but we don't have a corresponding custom property type. */
+ attributes_builder.add<int>("Hash", 0);
+ attributes_builder.add<float>("Birth Time", 0.0f);
+ attributes_builder.add<float>("Radius", 0.02f);
+ context.influences.particle_attributes_builder.add_new(name, &attributes_builder);
}
}
-static void find_used_data_blocks(const nodes::DerivedNodeTree &tree,
- SimulationInfluences &r_influences)
+static void optimize_function_network(CollectContext &context)
+{
+ fn::mf_network_optimization::constant_folding(context.network, context.resources);
+ fn::mf_network_optimization::common_subnetwork_elimination(context.network);
+ fn::mf_network_optimization::dead_node_removal(context.network);
+ // WM_clipboard_text_set(network.to_dot().c_str(), false);
+}
+
+class AgeReachedEvent : public ParticleEvent {
+ private:
+ std::string attribute_name_;
+ const ParticleFunction &inputs_fn_;
+ const ParticleAction &action_;
+
+ public:
+ AgeReachedEvent(std::string attribute_name,
+ const ParticleFunction &inputs_fn,
+ const ParticleAction &action)
+ : attribute_name_(std::move(attribute_name)), inputs_fn_(inputs_fn), action_(action)
+ {
+ }
+
+ void filter(ParticleEventFilterContext &context) const override
+ {
+ Span<float> birth_times = context.particles.attributes.get<float>("Birth Time");
+ std::optional<Span<int>> has_been_triggered = context.particles.attributes.try_get<int>(
+ attribute_name_);
+ if (!has_been_triggered.has_value()) {
+ return;
+ }
+
+ ParticleFunctionEvaluator evaluator{inputs_fn_, context.solve_context, context.particles};
+ evaluator.compute();
+ VSpan<float> trigger_ages = evaluator.get<float>(0, "Age");
+
+ const float end_time = context.particles.integration->end_time;
+ for (int i : context.particles.index_mask) {
+ if ((*has_been_triggered)[i]) {
+ continue;
+ }
+ const float trigger_age = trigger_ages[i];
+ const float birth_time = birth_times[i];
+ const float trigger_time = birth_time + trigger_age;
+ if (trigger_time > end_time) {
+ continue;
+ }
+
+ const float duration = context.particles.integration->durations[i];
+ TimeInterval interval(end_time - duration, duration);
+ const float time_factor = interval.safe_factor_at_time(trigger_time);
+
+ context.factor_dst[i] = std::max<float>(0.0f, time_factor);
+ }
+ }
+
+ void execute(ParticleActionContext &context) const override
+ {
+ MutableSpan<int> has_been_triggered = context.particles.attributes.get<int>(attribute_name_);
+ for (int i : context.particles.index_mask) {
+ has_been_triggered[i] = 1;
+ }
+ action_.execute(context);
+ }
+};
+
+static void collect_age_reached_events(CollectContext &context)
{
- const bNodeSocketType *socktype = nodeSocketTypeFind("NodeSocketObject");
- BLI_assert(socktype != nullptr);
-
- for (const nodes::DInputSocket *dsocket : tree.input_sockets()) {
- const bNodeSocket *bsocket = dsocket->bsocket();
- if (bsocket->typeinfo == socktype) {
- Object *value = ((const bNodeSocketValueObject *)bsocket->default_value)->value;
- if (value != nullptr) {
- r_influences.used_data_blocks.add(&value->id);
+ for (const DNode *dnode : nodes_by_type(context, "SimulationNodeAgeReachedEvent")) {
+ const DInputSocket &age_input = dnode->input(0, "Age");
+ const DInputSocket &execute_input = dnode->input(1, "Execute");
+ Array<StringRefNull> particle_names = find_linked_particle_simulations(context,
+ dnode->output(0));
+ const ParticleAction *action = create_particle_action(context, execute_input, particle_names);
+ if (action == nullptr) {
+ continue;
+ }
+ const ParticleFunction *inputs_fn = create_particle_function_for_inputs(context, {&age_input});
+ if (inputs_fn == nullptr) {
+ continue;
+ }
+
+ std::string attribute_name = get_identifier(context, *dnode);
+ const ParticleEvent &event = context.resources.construct<AgeReachedEvent>(
+ AT, attribute_name, *inputs_fn, *action);
+ for (StringRefNull particle_name : particle_names) {
+ const bool added_attribute = context.influences.particle_attributes_builder
+ .lookup_as(particle_name)
+ ->add<int>(attribute_name, 0);
+ if (added_attribute) {
+ context.influences.particle_events.add_as(particle_name, &event);
}
}
}
@@ -456,30 +879,29 @@ void collect_simulation_influences(Simulation &simulation,
SimulationInfluences &r_influences,
RequiredStates &r_required_states)
{
- nodes::NodeTreeRefMap tree_refs;
- const nodes::DerivedNodeTree tree{simulation.nodetree, tree_refs};
+ NodeTreeRefMap tree_refs;
+ const DerivedNodeTree tree{simulation.nodetree, tree_refs};
- fn::MFNetwork &network = resources.construct<fn::MFNetwork>(AT);
- nodes::MFNetworkTreeMap network_map = insert_node_tree_into_mf_network(network, tree, resources);
+ MFNetwork &network = resources.construct<MFNetwork>(AT);
+ MFNetworkTreeMap network_map = insert_node_tree_into_mf_network(network, tree, resources);
- prepare_particle_attribute_builders(network_map, resources, r_influences);
+ CollectContext context{r_influences, r_required_states, resources, network_map};
+ initialize_particle_attribute_builders(context);
- DummyDataSources data_sources;
- find_and_deduplicate_particle_attribute_nodes(network_map, data_sources);
+ prepare_particle_attribute_nodes(context);
+ prepare_time_input_nodes(context);
- fn::mf_network_optimization::constant_folding(network, resources);
- fn::mf_network_optimization::common_subnetwork_elimination(network);
- fn::mf_network_optimization::dead_node_removal(network);
- // WM_clipboard_text_set(network.to_dot().c_str(), false);
+ collect_forces(context);
+ collect_emitters(context);
+ collect_birth_events(context);
+ collect_time_step_events(context);
+ collect_age_reached_events(context);
- collect_forces(network_map, resources, data_sources, r_influences);
- collect_emitters(network_map, resources, r_influences, r_required_states);
+ optimize_function_network(context);
- for (const nodes::DNode *dnode : get_particle_simulation_nodes(tree)) {
- r_required_states.add(dnode_to_path(*dnode), SIM_TYPE_NAME_PARTICLE_SIMULATION);
+ for (const DNode *dnode : context.particle_simulation_nodes) {
+ r_required_states.add(get_identifier(context, *dnode), SIM_TYPE_NAME_PARTICLE_SIMULATION);
}
-
- find_used_data_blocks(tree, r_influences);
}
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_collect_influences.hh b/source/blender/simulation/intern/simulation_collect_influences.hh
index 5035461191e..8673a308b04 100644
--- a/source/blender/simulation/intern/simulation_collect_influences.hh
+++ b/source/blender/simulation/intern/simulation_collect_influences.hh
@@ -20,7 +20,7 @@
#include "BLI_resource_collector.hh"
-#include "simulation_solver.hh"
+#include "simulation_solver_influences.hh"
namespace blender::sim {
diff --git a/source/blender/simulation/intern/simulation_solver.cc b/source/blender/simulation/intern/simulation_solver.cc
index ee7a8d40035..d53ccd2bd49 100644
--- a/source/blender/simulation/intern/simulation_solver.cc
+++ b/source/blender/simulation/intern/simulation_solver.cc
@@ -17,23 +17,16 @@
#include "simulation_solver.hh"
#include "BKE_customdata.h"
-#include "BKE_lib_id.h"
#include "BKE_persistent_data_handle.hh"
#include "BLI_rand.hh"
#include "BLI_set.hh"
-namespace blender::sim {
-
-ParticleForce::~ParticleForce()
-{
-}
+#include "DEG_depsgraph_query.h"
-ParticleEmitter::~ParticleEmitter()
-{
-}
+namespace blender::sim {
-static CustomDataType cpp_to_custom_data_type(const fn::CPPType &type)
+static CustomDataType cpp_to_custom_data_type(const CPPType &type)
{
if (type.is<float3>()) {
return CD_PROP_FLOAT3;
@@ -48,18 +41,18 @@ static CustomDataType cpp_to_custom_data_type(const fn::CPPType &type)
return CD_PROP_FLOAT;
}
-static const fn::CPPType &custom_to_cpp_data_type(CustomDataType type)
+static const CPPType &custom_to_cpp_data_type(CustomDataType type)
{
switch (type) {
case CD_PROP_FLOAT3:
- return fn::CPPType::get<float3>();
+ return CPPType::get<float3>();
case CD_PROP_FLOAT:
- return fn::CPPType::get<float>();
+ return CPPType::get<float>();
case CD_PROP_INT32:
- return fn::CPPType::get<int32_t>();
+ return CPPType::get<int32_t>();
default:
BLI_assert(false);
- return fn::CPPType::get<float>();
+ return CPPType::get<float>();
}
}
@@ -67,33 +60,33 @@ class CustomDataAttributesRef {
private:
Array<void *> buffers_;
int64_t size_;
- const fn::AttributesInfo &info_;
+ const AttributesInfo &info_;
public:
- CustomDataAttributesRef(CustomData &custom_data, int64_t size, const fn::AttributesInfo &info)
+ CustomDataAttributesRef(CustomData &custom_data, int64_t size, const AttributesInfo &info)
: buffers_(info.size(), nullptr), size_(size), info_(info)
{
for (int attribute_index : info.index_range()) {
StringRefNull name = info.name_of(attribute_index);
- const fn::CPPType &cpp_type = info.type_of(attribute_index);
+ const CPPType &cpp_type = info.type_of(attribute_index);
CustomDataType custom_type = cpp_to_custom_data_type(cpp_type);
void *data = CustomData_get_layer_named(&custom_data, custom_type, name.c_str());
buffers_[attribute_index] = data;
}
}
- operator fn::MutableAttributesRef()
+ operator MutableAttributesRef()
{
- return fn::MutableAttributesRef(info_, buffers_, size_);
+ return MutableAttributesRef(info_, buffers_, size_);
}
- operator fn::AttributesRef() const
+ operator AttributesRef() const
{
- return fn::AttributesRef(info_, buffers_, size_);
+ return AttributesRef(info_, buffers_, size_);
}
};
-static void ensure_attributes_exist(ParticleSimulationState *state, const fn::AttributesInfo &info)
+static void ensure_attributes_exist(ParticleSimulationState *state, const AttributesInfo &info)
{
bool found_layer_to_remove;
do {
@@ -101,7 +94,7 @@ static void ensure_attributes_exist(ParticleSimulationState *state, const fn::At
for (int layer_index = 0; layer_index < state->attributes.totlayer; layer_index++) {
CustomDataLayer *layer = &state->attributes.layers[layer_index];
BLI_assert(layer->name != nullptr);
- const fn::CPPType &cpp_type = custom_to_cpp_data_type((CustomDataType)layer->type);
+ const CPPType &cpp_type = custom_to_cpp_data_type((CustomDataType)layer->type);
StringRefNull name = layer->name;
if (!info.has_attribute(name, cpp_type)) {
found_layer_to_remove = true;
@@ -113,7 +106,7 @@ static void ensure_attributes_exist(ParticleSimulationState *state, const fn::At
for (int attribute_index : info.index_range()) {
StringRefNull attribute_name = info.name_of(attribute_index);
- const fn::CPPType &cpp_type = info.type_of(attribute_index);
+ const CPPType &cpp_type = info.type_of(attribute_index);
CustomDataType custom_type = cpp_to_custom_data_type(cpp_type);
if (CustomData_get_layer_named(&state->attributes, custom_type, attribute_name.c_str()) ==
nullptr) {
@@ -128,50 +121,241 @@ static void ensure_attributes_exist(ParticleSimulationState *state, const fn::At
}
}
-BLI_NOINLINE static void simulate_existing_particles(SimulationSolveContext &solve_context,
- ParticleSimulationState &state,
- const fn::AttributesInfo &attributes_info)
+BLI_NOINLINE static void apply_remaining_diffs(ParticleChunkContext &context)
{
- CustomDataAttributesRef custom_data_attributes{
- state.attributes, state.tot_particles, attributes_info};
- fn::MutableAttributesRef attributes = custom_data_attributes;
+ BLI_assert(context.integration != nullptr);
+ MutableSpan<float3> positions = context.attributes.get<float3>("Position");
+ MutableSpan<float3> velocities = context.attributes.get<float3>("Velocity");
+
+ for (int i : context.index_mask) {
+ positions[i] += context.integration->position_diffs[i];
+ velocities[i] += context.integration->velocity_diffs[i];
+ }
+}
- Array<float3> force_vectors{state.tot_particles, {0, 0, 0}};
- const Vector<const ParticleForce *> *forces =
- solve_context.influences().particle_forces.lookup_ptr(state.head.name);
+BLI_NOINLINE static void find_next_event_per_particle(
+ SimulationSolveContext &solve_context,
+ ParticleChunkContext &particles,
+ Span<const ParticleEvent *> events,
+ MutableSpan<int> r_next_event_indices,
+ MutableSpan<float> r_time_factors_to_next_event)
+{
+ r_next_event_indices.fill_indices(particles.index_mask, -1);
+ r_time_factors_to_next_event.fill_indices(particles.index_mask, 1.0f);
+
+ Array<float> time_factors(particles.index_mask.min_array_size());
+ for (int event_index : events.index_range()) {
+ time_factors.fill(-1.0f);
+ ParticleEventFilterContext event_context{solve_context, particles, time_factors};
+ const ParticleEvent &event = *events[event_index];
+ event.filter(event_context);
+
+ for (int i : particles.index_mask) {
+ const float time_factor = time_factors[i];
+ const float previously_smallest_time_factor = r_time_factors_to_next_event[i];
+ if (time_factor >= 0.0f && time_factor <= previously_smallest_time_factor) {
+ r_time_factors_to_next_event[i] = time_factor;
+ r_next_event_indices[i] = event_index;
+ }
+ }
+ }
+}
- if (forces != nullptr) {
- ParticleChunkContext particle_chunk_context{IndexMask(state.tot_particles), attributes};
- ParticleForceContext particle_force_context{
- solve_context, particle_chunk_context, force_vectors};
+BLI_NOINLINE static void forward_particles_to_next_event_or_end(
+ ParticleChunkContext &particles, Span<float> time_factors_to_next_event)
+{
+ MutableSpan<float3> positions = particles.attributes.get<float3>("Position");
+ MutableSpan<float3> velocities = particles.attributes.get<float3>("Velocity");
+
+ MutableSpan<float3> position_diffs = particles.integration->position_diffs;
+ MutableSpan<float3> velocity_diffs = particles.integration->velocity_diffs;
+ MutableSpan<float> durations = particles.integration->durations;
+
+ for (int i : particles.index_mask) {
+ const float time_factor = time_factors_to_next_event[i];
+ positions[i] += position_diffs[i] * time_factor;
+ velocities[i] += velocity_diffs[i] * time_factor;
+
+ const float remaining_time_factor = 1.0f - time_factor;
+ position_diffs[i] *= remaining_time_factor;
+ velocity_diffs[i] *= remaining_time_factor;
+ durations[i] *= remaining_time_factor;
+ }
+}
- for (const ParticleForce *force : *forces) {
- force->add_force(particle_force_context);
+BLI_NOINLINE static void group_particles_by_event(
+ IndexMask mask,
+ Span<int> next_event_indices,
+ MutableSpan<Vector<int64_t>> r_particles_per_event)
+{
+ for (int i : mask) {
+ int event_index = next_event_indices[i];
+ if (event_index >= 0) {
+ r_particles_per_event[event_index].append(i);
}
}
+}
- MutableSpan<float3> positions = attributes.get<float3>("Position");
- MutableSpan<float3> velocities = attributes.get<float3>("Velocity");
- MutableSpan<float> birth_times = attributes.get<float>("Birth Time");
- MutableSpan<int> dead_states = attributes.get<int>("Dead");
- float end_time = solve_context.solve_interval().end();
- float time_step = solve_context.solve_interval().duration();
- for (int i : positions.index_range()) {
- velocities[i] += force_vectors[i] * time_step;
- positions[i] += velocities[i] * time_step;
-
- if (end_time - birth_times[i] > 2) {
- dead_states[i] = true;
+BLI_NOINLINE static void execute_events(SimulationSolveContext &solve_context,
+ ParticleChunkContext &all_particles,
+ Span<const ParticleEvent *> events,
+ Span<Vector<int64_t>> particles_per_event)
+{
+ for (int event_index : events.index_range()) {
+ Span<int64_t> pindices = particles_per_event[event_index];
+ if (pindices.is_empty()) {
+ continue;
+ }
+
+ const ParticleEvent &event = *events[event_index];
+ ParticleChunkContext particles{
+ all_particles.state, pindices, all_particles.attributes, all_particles.integration};
+ ParticleActionContext action_context{solve_context, particles};
+ event.execute(action_context);
+ }
+}
+
+BLI_NOINLINE static void find_unfinished_particles(IndexMask index_mask,
+ Span<float> time_factors_to_next_event,
+ Vector<int64_t> &r_unfinished_pindices)
+{
+ for (int i : index_mask) {
+ float time_factor = time_factors_to_next_event[i];
+ if (time_factor < 1.0f) {
+ r_unfinished_pindices.append(i);
+ }
+ }
+}
+
+BLI_NOINLINE static void simulate_to_next_event(SimulationSolveContext &solve_context,
+ ParticleChunkContext &particles,
+ Span<const ParticleEvent *> events,
+ Vector<int64_t> &r_unfinished_pindices)
+{
+ int array_size = particles.index_mask.min_array_size();
+ Array<int> next_event_indices(array_size);
+ Array<float> time_factors_to_next_event(array_size);
+
+ find_next_event_per_particle(
+ solve_context, particles, events, next_event_indices, time_factors_to_next_event);
+
+ forward_particles_to_next_event_or_end(particles, time_factors_to_next_event);
+
+ Array<Vector<int64_t>> particles_per_event(events.size());
+ group_particles_by_event(particles.index_mask, next_event_indices, particles_per_event);
+
+ execute_events(solve_context, particles, events, particles_per_event);
+ find_unfinished_particles(
+ particles.index_mask, time_factors_to_next_event, r_unfinished_pindices);
+}
+
+BLI_NOINLINE static void simulate_with_max_n_events(SimulationSolveContext &solve_context,
+ ParticleSimulationState &state,
+ ParticleChunkContext &particles,
+ int max_events)
+{
+ Span<const ParticleEvent *> events = solve_context.influences.particle_events.lookup_as(
+ state.head.name);
+ if (events.size() == 0) {
+ apply_remaining_diffs(particles);
+ return;
+ }
+
+ Vector<int64_t> unfininished_pindices = particles.index_mask.indices();
+ for (int iteration : IndexRange(max_events)) {
+ UNUSED_VARS(iteration);
+ if (unfininished_pindices.is_empty()) {
+ break;
}
+
+ Vector<int64_t> new_unfinished_pindices;
+ ParticleChunkContext remaining_particles{particles.state,
+ unfininished_pindices.as_span(),
+ particles.attributes,
+ particles.integration};
+ simulate_to_next_event(solve_context, remaining_particles, events, new_unfinished_pindices);
+ unfininished_pindices = std::move(new_unfinished_pindices);
+ }
+
+ if (!unfininished_pindices.is_empty()) {
+ ParticleChunkContext remaining_particles{particles.state,
+ unfininished_pindices.as_span(),
+ particles.attributes,
+ particles.integration};
+ apply_remaining_diffs(remaining_particles);
}
}
+BLI_NOINLINE static void simulate_particle_chunk(SimulationSolveContext &solve_context,
+ ParticleSimulationState &state,
+ MutableAttributesRef attributes,
+ MutableSpan<float> remaining_durations,
+ float end_time)
+{
+ int particle_amount = attributes.size();
+
+ Span<const ParticleAction *> begin_actions =
+ solve_context.influences.particle_time_step_begin_actions.lookup_as(state.head.name);
+ for (const ParticleAction *action : begin_actions) {
+ ParticleChunkContext particles{state, IndexMask(particle_amount), attributes};
+ ParticleActionContext action_context{solve_context, particles};
+ action->execute(action_context);
+ }
+
+ Array<float3> force_vectors{particle_amount, {0, 0, 0}};
+ Span<const ParticleForce *> forces = solve_context.influences.particle_forces.lookup_as(
+ state.head.name);
+ for (const ParticleForce *force : forces) {
+ ParticleChunkContext particles{state, IndexMask(particle_amount), attributes};
+ ParticleForceContext particle_force_context{solve_context, particles, force_vectors};
+ force->add_force(particle_force_context);
+ }
+
+ MutableSpan<float3> velocities = attributes.get<float3>("Velocity");
+
+ Array<float3> position_diffs(particle_amount);
+ Array<float3> velocity_diffs(particle_amount);
+ for (int i : IndexRange(particle_amount)) {
+ const float time_step = remaining_durations[i];
+ velocity_diffs[i] = force_vectors[i] * time_step;
+ position_diffs[i] = (velocities[i] + velocity_diffs[i] / 2.0f) * time_step;
+ }
+
+ ParticleChunkIntegrationContext integration_context = {
+ position_diffs, velocity_diffs, remaining_durations, end_time};
+ ParticleChunkContext particle_chunk_context{
+ state, IndexMask(particle_amount), attributes, &integration_context};
+
+ simulate_with_max_n_events(solve_context, state, particle_chunk_context, 10);
+
+ Span<const ParticleAction *> end_actions =
+ solve_context.influences.particle_time_step_end_actions.lookup_as(state.head.name);
+ for (const ParticleAction *action : end_actions) {
+ ParticleChunkContext particles{state, IndexMask(particle_amount), attributes};
+ ParticleActionContext action_context{solve_context, particles};
+ action->execute(action_context);
+ }
+}
+
+BLI_NOINLINE static void simulate_existing_particles(SimulationSolveContext &solve_context,
+ ParticleSimulationState &state,
+ const AttributesInfo &attributes_info)
+{
+ CustomDataAttributesRef custom_data_attributes{
+ state.attributes, state.tot_particles, attributes_info};
+ MutableAttributesRef attributes = custom_data_attributes;
+
+ Array<float> remaining_durations(state.tot_particles, solve_context.solve_interval.duration());
+ simulate_particle_chunk(
+ solve_context, state, attributes, remaining_durations, solve_context.solve_interval.stop());
+}
+
BLI_NOINLINE static void run_emitters(SimulationSolveContext &solve_context,
ParticleAllocators &particle_allocators)
{
- for (const ParticleEmitter *emitter : solve_context.influences().particle_emitters) {
+ for (const ParticleEmitter *emitter : solve_context.influences.particle_emitters) {
ParticleEmitterContext emitter_context{
- solve_context, particle_allocators, solve_context.solve_interval()};
+ solve_context, particle_allocators, solve_context.solve_interval};
emitter->emit(emitter_context);
}
}
@@ -181,10 +365,10 @@ BLI_NOINLINE static int count_particles_after_time_step(ParticleSimulationState
{
CustomDataAttributesRef custom_data_attributes{
state.attributes, state.tot_particles, allocator.attributes_info()};
- fn::MutableAttributesRef attributes = custom_data_attributes;
+ MutableAttributesRef attributes = custom_data_attributes;
int new_particle_amount = attributes.get<int>("Dead").count(0);
- for (fn::MutableAttributesRef emitted_attributes : allocator.get_allocations()) {
+ for (MutableAttributesRef emitted_attributes : allocator.get_allocations()) {
new_particle_amount += emitted_attributes.get<int>("Dead").count(0);
}
@@ -199,7 +383,7 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
CustomDataAttributesRef custom_data_attributes{
state.attributes, state.tot_particles, allocator.attributes_info()};
- Vector<fn::MutableAttributesRef> particle_sources;
+ Vector<MutableAttributesRef> particle_sources;
particle_sources.append(custom_data_attributes);
particle_sources.extend(allocator.get_allocations());
@@ -211,16 +395,16 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
dead_layer = &layer;
continue;
}
- const fn::CPPType &cpp_type = custom_to_cpp_data_type((CustomDataType)layer.type);
- fn::GMutableSpan new_buffer{
+ const CPPType &cpp_type = custom_to_cpp_data_type((CustomDataType)layer.type);
+ GMutableSpan new_buffer{
cpp_type,
MEM_mallocN_aligned(new_particle_amount * cpp_type.size(), cpp_type.alignment(), AT),
new_particle_amount};
int current = 0;
- for (fn::MutableAttributesRef attributes : particle_sources) {
+ for (MutableAttributesRef attributes : particle_sources) {
Span<int> dead_states = attributes.get<int>("Dead");
- fn::GSpan source_buffer = attributes.get(name);
+ GSpan source_buffer = attributes.get(name);
BLI_assert(source_buffer.type() == cpp_type);
for (int i : attributes.index_range()) {
if (dead_states[i] == 0) {
@@ -233,7 +417,7 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
if (layer.data != nullptr) {
MEM_freeN(layer.data);
}
- layer.data = new_buffer.buffer();
+ layer.data = new_buffer.data();
}
BLI_assert(dead_layer != nullptr);
@@ -246,56 +430,10 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
state.next_particle_id += allocator.total_allocated();
}
-static void update_persistent_data_handles(Simulation &simulation,
- const VectorSet<ID *> &used_data_blocks)
-{
- Set<ID *> contained_ids;
- Set<int> used_handles;
-
- /* Remove handles that have been invalidated. */
- LISTBASE_FOREACH_MUTABLE (
- PersistentDataHandleItem *, handle_item, &simulation.persistent_data_handles) {
- if (handle_item->id == nullptr) {
- BLI_remlink(&simulation.persistent_data_handles, handle_item);
- continue;
- }
- if (!used_data_blocks.contains(handle_item->id)) {
- id_us_min(handle_item->id);
- BLI_remlink(&simulation.persistent_data_handles, handle_item);
- MEM_freeN(handle_item);
- continue;
- }
- contained_ids.add_new(handle_item->id);
- used_handles.add_new(handle_item->handle);
- }
-
- /* Add new handles that are not in the list yet. */
- int next_handle = 0;
- for (ID *id : used_data_blocks) {
- if (contained_ids.contains(id)) {
- continue;
- }
-
- /* Find the next available handle. */
- while (used_handles.contains(next_handle)) {
- next_handle++;
- }
- used_handles.add_new(next_handle);
-
- PersistentDataHandleItem *handle_item = (PersistentDataHandleItem *)MEM_callocN(
- sizeof(*handle_item), AT);
- /* Cannot store const pointers in DNA. */
- id_us_plus(id);
- handle_item->id = id;
- handle_item->handle = next_handle;
-
- BLI_addtail(&simulation.persistent_data_handles, handle_item);
- }
-}
-
void initialize_simulation_states(Simulation &simulation,
Depsgraph &UNUSED(depsgraph),
- const SimulationInfluences &UNUSED(influences))
+ const SimulationInfluences &UNUSED(influences),
+ const bke::PersistentDataHandleMap &UNUSED(handle_map))
{
simulation.current_simulation_time = 0.0f;
}
@@ -303,15 +441,10 @@ void initialize_simulation_states(Simulation &simulation,
void solve_simulation_time_step(Simulation &simulation,
Depsgraph &depsgraph,
const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map,
+ const DependencyAnimations &dependency_animations,
float time_step)
{
- update_persistent_data_handles(simulation, influences.used_data_blocks);
-
- bke::PersistentDataHandleMap handle_map;
- LISTBASE_FOREACH (PersistentDataHandleItem *, handle, &simulation.persistent_data_handles) {
- handle_map.add(handle->handle, *handle->id);
- }
-
SimulationStateMap state_map;
LISTBASE_FOREACH (SimulationState *, state, &simulation.states) {
state_map.add(state);
@@ -322,30 +455,32 @@ void solve_simulation_time_step(Simulation &simulation,
influences,
TimeInterval(simulation.current_simulation_time, time_step),
state_map,
- handle_map};
- TimeInterval simulation_time_interval{simulation.current_simulation_time, time_step};
+ handle_map,
+ dependency_animations};
Span<ParticleSimulationState *> particle_simulation_states =
state_map.lookup<ParticleSimulationState>();
- Map<std::string, std::unique_ptr<fn::AttributesInfo>> attribute_infos;
+ Map<std::string, std::unique_ptr<AttributesInfo>> attribute_infos;
Map<std::string, std::unique_ptr<ParticleAllocator>> particle_allocators_map;
for (ParticleSimulationState *state : particle_simulation_states) {
- const fn::AttributesInfoBuilder &builder = *influences.particle_attributes_builder.lookup_as(
+ const AttributesInfoBuilder &builder = *influences.particle_attributes_builder.lookup_as(
state->head.name);
- auto info = std::make_unique<fn::AttributesInfo>(builder);
+ auto info = std::make_unique<AttributesInfo>(builder);
ensure_attributes_exist(state, *info);
+ uint32_t hash_seed = DefaultHash<StringRef>{}(state->head.name);
particle_allocators_map.add_new(
- state->head.name, std::make_unique<ParticleAllocator>(*info, state->next_particle_id));
+ state->head.name,
+ std::make_unique<ParticleAllocator>(*info, state->next_particle_id, hash_seed));
attribute_infos.add_new(state->head.name, std::move(info));
}
ParticleAllocators particle_allocators{particle_allocators_map};
for (ParticleSimulationState *state : particle_simulation_states) {
- const fn::AttributesInfo &attributes_info = *attribute_infos.lookup_as(state->head.name);
+ const AttributesInfo &attributes_info = *attribute_infos.lookup_as(state->head.name);
simulate_existing_particles(solve_context, *state, attributes_info);
}
@@ -353,10 +488,35 @@ void solve_simulation_time_step(Simulation &simulation,
for (ParticleSimulationState *state : particle_simulation_states) {
ParticleAllocator &allocator = *particle_allocators.try_get_allocator(state->head.name);
+
+ for (MutableAttributesRef attributes : allocator.get_allocations()) {
+ Span<const ParticleAction *> actions = influences.particle_birth_actions.lookup_as(
+ state->head.name);
+ for (const ParticleAction *action : actions) {
+ ParticleChunkContext chunk_context{*state, IndexRange(attributes.size()), attributes};
+ ParticleActionContext action_context{solve_context, chunk_context};
+ action->execute(action_context);
+ }
+ }
+ }
+
+ for (ParticleSimulationState *state : particle_simulation_states) {
+ ParticleAllocator &allocator = *particle_allocators.try_get_allocator(state->head.name);
+
+ for (MutableAttributesRef attributes : allocator.get_allocations()) {
+ Array<float> remaining_durations(attributes.size());
+ Span<float> birth_times = attributes.get<float>("Birth Time");
+ const float end_time = solve_context.solve_interval.stop();
+ for (int i : attributes.index_range()) {
+ remaining_durations[i] = end_time - birth_times[i];
+ }
+ simulate_particle_chunk(solve_context, *state, attributes, remaining_durations, end_time);
+ }
+
remove_dead_and_add_new_particles(*state, allocator);
}
- simulation.current_simulation_time = simulation_time_interval.end();
+ simulation.current_simulation_time = solve_context.solve_interval.stop();
}
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_solver.hh b/source/blender/simulation/intern/simulation_solver.hh
index 3fc61f579e3..2cf8eb2aa27 100644
--- a/source/blender/simulation/intern/simulation_solver.hh
+++ b/source/blender/simulation/intern/simulation_solver.hh
@@ -16,259 +16,22 @@
#pragma once
-#include "BLI_float3.hh"
-#include "BLI_span.hh"
-
-#include "DNA_simulation_types.h"
-
-#include "FN_attributes_ref.hh"
-
-#include "BKE_persistent_data_handle.hh"
-#include "BKE_simulation.h"
-
-#include "particle_allocator.hh"
-#include "time_interval.hh"
+#include "simulation_collect_influences.hh"
struct Depsgraph;
namespace blender::sim {
-class ParticleEmitterContext;
-class ParticleForceContext;
-
-class ParticleEmitter {
- public:
- virtual ~ParticleEmitter();
- virtual void emit(ParticleEmitterContext &context) const = 0;
-};
-
-class ParticleForce {
- public:
- virtual ~ParticleForce();
- virtual void add_force(ParticleForceContext &context) const = 0;
-};
-
-struct SimulationInfluences {
- Map<std::string, Vector<const ParticleForce *>> particle_forces;
- Map<std::string, fn::AttributesInfoBuilder *> particle_attributes_builder;
- Vector<const ParticleEmitter *> particle_emitters;
- VectorSet<ID *> used_data_blocks;
-};
-
-class SimulationStateMap {
- private:
- Map<StringRefNull, SimulationState *> states_by_name_;
- Map<StringRefNull, Vector<SimulationState *>> states_by_type_;
-
- public:
- void add(SimulationState *state)
- {
- states_by_name_.add_new(state->name, state);
- states_by_type_.lookup_or_add_default(state->type).append(state);
- }
-
- template<typename StateType> StateType *lookup(StringRef name) const
- {
- const char *type = BKE_simulation_get_state_type_name<StateType>();
- return (StateType *)this->lookup_name_type(name, type);
- }
-
- template<typename StateType> Span<StateType *> lookup() const
- {
- const char *type = BKE_simulation_get_state_type_name<StateType>();
- return this->lookup_type(type).cast<StateType *>();
- }
-
- SimulationState *lookup_name_type(StringRef name, StringRef type) const
- {
- SimulationState *state = states_by_name_.lookup_default_as(name, nullptr);
- if (state == nullptr) {
- return nullptr;
- }
- if (state->type == type) {
- return state;
- }
- return nullptr;
- }
-
- Span<SimulationState *> lookup_type(StringRef type) const
- {
- const Vector<SimulationState *> *states = states_by_type_.lookup_ptr_as(type);
- if (states == nullptr) {
- return {};
- }
- else {
- return states->as_span();
- }
- }
-};
-
-class SimulationSolveContext {
- private:
- Simulation &simulation_;
- Depsgraph &depsgraph_;
- const SimulationInfluences &influences_;
- TimeInterval solve_interval_;
- const SimulationStateMap &state_map_;
- const bke::PersistentDataHandleMap &id_handle_map_;
-
- public:
- SimulationSolveContext(Simulation &simulation,
- Depsgraph &depsgraph,
- const SimulationInfluences &influences,
- TimeInterval solve_interval,
- const SimulationStateMap &state_map,
- const bke::PersistentDataHandleMap &handle_map)
- : simulation_(simulation),
- depsgraph_(depsgraph),
- influences_(influences),
- solve_interval_(solve_interval),
- state_map_(state_map),
- id_handle_map_(handle_map)
- {
- }
-
- TimeInterval solve_interval() const
- {
- return solve_interval_;
- }
-
- const SimulationInfluences &influences() const
- {
- return influences_;
- }
-
- const bke::PersistentDataHandleMap &handle_map() const
- {
- return id_handle_map_;
- }
-
- const SimulationStateMap &state_map() const
- {
- return state_map_;
- }
-};
-
-class ParticleAllocators {
- private:
- Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators_;
-
- public:
- ParticleAllocators(Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators)
- : allocators_(allocators)
- {
- }
-
- ParticleAllocator *try_get_allocator(StringRef particle_simulation_name)
- {
- auto *ptr = allocators_.lookup_ptr_as(particle_simulation_name);
- if (ptr != nullptr) {
- return ptr->get();
- }
- else {
- return nullptr;
- }
- }
-};
-
-class ParticleChunkContext {
- private:
- IndexMask index_mask_;
- fn::MutableAttributesRef attributes_;
-
- public:
- ParticleChunkContext(IndexMask index_mask, fn::MutableAttributesRef attributes)
- : index_mask_(index_mask), attributes_(attributes)
- {
- }
-
- IndexMask index_mask() const
- {
- return index_mask_;
- }
-
- fn::MutableAttributesRef attributes()
- {
- return attributes_;
- }
-
- fn::AttributesRef attributes() const
- {
- return attributes_;
- }
-};
-
-class ParticleEmitterContext {
- private:
- SimulationSolveContext &solve_context_;
- ParticleAllocators &particle_allocators_;
- TimeInterval simulation_time_interval_;
-
- public:
- ParticleEmitterContext(SimulationSolveContext &solve_context,
- ParticleAllocators &particle_allocators,
- TimeInterval simulation_time_interval)
- : solve_context_(solve_context),
- particle_allocators_(particle_allocators),
- simulation_time_interval_(simulation_time_interval)
- {
- }
-
- SimulationSolveContext &solve_context()
- {
- return solve_context_;
- }
-
- ParticleAllocator *try_get_particle_allocator(StringRef particle_simulation_name)
- {
- return particle_allocators_.try_get_allocator(particle_simulation_name);
- }
-
- TimeInterval simulation_time_interval() const
- {
- return simulation_time_interval_;
- }
-};
-
-class ParticleForceContext {
- private:
- SimulationSolveContext &solve_context_;
- const ParticleChunkContext &particle_chunk_context_;
- MutableSpan<float3> force_dst_;
-
- public:
- ParticleForceContext(SimulationSolveContext &solve_context,
- const ParticleChunkContext &particle_chunk_context,
- MutableSpan<float3> force_dst)
- : solve_context_(solve_context),
- particle_chunk_context_(particle_chunk_context),
- force_dst_(force_dst)
- {
- }
-
- SimulationSolveContext &solve_context()
- {
- return solve_context_;
- }
-
- const ParticleChunkContext &particle_chunk() const
- {
- return particle_chunk_context_;
- }
-
- MutableSpan<float3> force_dst()
- {
- return force_dst_;
- }
-};
-
void initialize_simulation_states(Simulation &simulation,
Depsgraph &depsgraph,
- const SimulationInfluences &influences);
+ const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map);
void solve_simulation_time_step(Simulation &simulation,
Depsgraph &depsgraph,
const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map,
+ const DependencyAnimations &dependency_animations,
float time_step);
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_solver_influences.cc b/source/blender/simulation/intern/simulation_solver_influences.cc
new file mode 100644
index 00000000000..3485d7c7bfb
--- /dev/null
+++ b/source/blender/simulation/intern/simulation_solver_influences.cc
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include "simulation_solver_influences.hh"
+
+#include "DNA_object_types.h"
+
+namespace blender::sim {
+
+ParticleForce::~ParticleForce()
+{
+}
+
+ParticleEmitter::~ParticleEmitter()
+{
+}
+
+ParticleAction::~ParticleAction()
+{
+}
+
+ParticleEvent::~ParticleEvent()
+{
+}
+
+DependencyAnimations::~DependencyAnimations()
+{
+}
+
+bool DependencyAnimations::is_object_transform_changing(Object &UNUSED(object)) const
+{
+ return false;
+}
+
+void DependencyAnimations::get_object_transforms(Object &object,
+ Span<float> simulation_times,
+ MutableSpan<float4x4> r_transforms) const
+{
+ assert_same_size(simulation_times, r_transforms);
+ float4x4 world_matrix = object.obmat;
+ r_transforms.fill(world_matrix);
+}
+
+} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_solver_influences.hh b/source/blender/simulation/intern/simulation_solver_influences.hh
new file mode 100644
index 00000000000..f7b8affd88d
--- /dev/null
+++ b/source/blender/simulation/intern/simulation_solver_influences.hh
@@ -0,0 +1,237 @@
+/*
+ * 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.
+ */
+
+#ifndef __SIM_SIMULATION_SOLVER_INFLUENCES_HH__
+#define __SIM_SIMULATION_SOLVER_INFLUENCES_HH__
+
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_multi_value_map.hh"
+#include "BLI_span.hh"
+
+#include "DNA_simulation_types.h"
+
+#include "FN_attributes_ref.hh"
+
+#include "BKE_persistent_data_handle.hh"
+#include "BKE_simulation.h"
+
+#include "particle_allocator.hh"
+#include "time_interval.hh"
+
+namespace blender::sim {
+
+using fn::AttributesInfo;
+using fn::AttributesInfoBuilder;
+using fn::AttributesRef;
+using fn::CPPType;
+using fn::GMutableSpan;
+using fn::GSpan;
+using fn::MutableAttributesRef;
+
+struct ParticleEmitterContext;
+struct ParticleForceContext;
+struct ParticleActionContext;
+struct ParticleEventFilterContext;
+
+class ParticleEmitter {
+ public:
+ virtual ~ParticleEmitter();
+ virtual void emit(ParticleEmitterContext &context) const = 0;
+};
+
+class ParticleForce {
+ public:
+ virtual ~ParticleForce();
+ virtual void add_force(ParticleForceContext &context) const = 0;
+};
+
+class ParticleAction {
+ public:
+ virtual ~ParticleAction();
+ virtual void execute(ParticleActionContext &context) const = 0;
+};
+
+class ParticleEvent {
+ public:
+ virtual ~ParticleEvent();
+ virtual void filter(ParticleEventFilterContext &context) const = 0;
+ virtual void execute(ParticleActionContext &context) const = 0;
+};
+
+struct SimulationInfluences {
+ MultiValueMap<std::string, const ParticleForce *> particle_forces;
+ MultiValueMap<std::string, const ParticleAction *> particle_birth_actions;
+ MultiValueMap<std::string, const ParticleAction *> particle_time_step_begin_actions;
+ MultiValueMap<std::string, const ParticleAction *> particle_time_step_end_actions;
+ MultiValueMap<std::string, const ParticleEvent *> particle_events;
+ Map<std::string, AttributesInfoBuilder *> particle_attributes_builder;
+ Vector<const ParticleEmitter *> particle_emitters;
+};
+
+class SimulationStateMap {
+ private:
+ Map<StringRefNull, SimulationState *> states_by_name_;
+ MultiValueMap<StringRefNull, SimulationState *> states_by_type_;
+
+ public:
+ void add(SimulationState *state)
+ {
+ states_by_name_.add_new(state->name, state);
+ states_by_type_.add(state->type, state);
+ }
+
+ template<typename StateType> StateType *lookup(StringRef name) const
+ {
+ const char *type = BKE_simulation_get_state_type_name<StateType>();
+ return (StateType *)this->lookup_name_type(name, type);
+ }
+
+ template<typename StateType> Span<StateType *> lookup() const
+ {
+ const char *type = BKE_simulation_get_state_type_name<StateType>();
+ return this->lookup_type(type).cast<StateType *>();
+ }
+
+ SimulationState *lookup_name_type(StringRef name, StringRef type) const
+ {
+ SimulationState *state = states_by_name_.lookup_default_as(name, nullptr);
+ if (state == nullptr) {
+ return nullptr;
+ }
+ if (state->type == type) {
+ return state;
+ }
+ return nullptr;
+ }
+
+ Span<SimulationState *> lookup_type(StringRef type) const
+ {
+ return states_by_type_.lookup_as(type);
+ }
+};
+
+class DependencyAnimations {
+ public:
+ ~DependencyAnimations();
+
+ virtual bool is_object_transform_changing(Object &object) const;
+ virtual void get_object_transforms(Object &object,
+ Span<float> simulation_times,
+ MutableSpan<float4x4> r_transforms) const;
+};
+
+struct SimulationSolveContext {
+ Simulation &simulation;
+ Depsgraph &depsgraph;
+ const SimulationInfluences &influences;
+ TimeInterval solve_interval;
+ const SimulationStateMap &state_map;
+ const bke::PersistentDataHandleMap &handle_map;
+ const DependencyAnimations &dependency_animations;
+};
+
+class ParticleAllocators {
+ private:
+ Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators_;
+
+ public:
+ ParticleAllocators(Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators)
+ : allocators_(allocators)
+ {
+ }
+
+ ParticleAllocator *try_get_allocator(StringRef particle_simulation_name)
+ {
+ auto *ptr = allocators_.lookup_ptr_as(particle_simulation_name);
+ if (ptr != nullptr) {
+ return ptr->get();
+ }
+ else {
+ return nullptr;
+ }
+ }
+};
+
+struct ParticleChunkIntegrationContext {
+ MutableSpan<float3> position_diffs;
+ MutableSpan<float3> velocity_diffs;
+ MutableSpan<float> durations;
+ float end_time;
+};
+
+struct ParticleChunkContext {
+ ParticleSimulationState &state;
+ IndexMask index_mask;
+ MutableAttributesRef attributes;
+ ParticleChunkIntegrationContext *integration = nullptr;
+
+ void update_diffs_after_velocity_change()
+ {
+ if (integration == nullptr) {
+ return;
+ }
+
+ Span<float> remaining_durations = integration->durations;
+ MutableSpan<float3> position_diffs = integration->position_diffs;
+ Span<float3> velocities = attributes.get<float3>("Velocity");
+
+ for (int i : index_mask) {
+ const float duration = remaining_durations[i];
+ /* This is certainly not a perfect way to "re-integrate" the velocity, but it should be good
+ * enough for most use cases. Changing the velocity in an instant is not physically correct
+ * anyway. */
+ position_diffs[i] = velocities[i] * duration;
+ }
+ }
+};
+
+struct ParticleEmitterContext {
+ SimulationSolveContext &solve_context;
+ ParticleAllocators &particle_allocators;
+ TimeInterval emit_interval;
+
+ template<typename StateType> StateType *lookup_state(StringRef name)
+ {
+ return solve_context.state_map.lookup<StateType>(name);
+ }
+
+ ParticleAllocator *try_get_particle_allocator(StringRef particle_simulation_name)
+ {
+ return particle_allocators.try_get_allocator(particle_simulation_name);
+ }
+};
+
+struct ParticleForceContext {
+ SimulationSolveContext &solve_context;
+ ParticleChunkContext &particles;
+ MutableSpan<float3> force_dst;
+};
+
+struct ParticleActionContext {
+ SimulationSolveContext &solve_context;
+ ParticleChunkContext &particles;
+};
+
+struct ParticleEventFilterContext {
+ SimulationSolveContext &solve_context;
+ ParticleChunkContext &particles;
+ MutableSpan<float> factor_dst;
+};
+
+} // namespace blender::sim
+
+#endif /* __SIM_SIMULATION_SOLVER_INFLUENCES_HH__ */
diff --git a/source/blender/simulation/intern/simulation_update.cc b/source/blender/simulation/intern/simulation_update.cc
index 09219e0238f..32b582977d0 100644
--- a/source/blender/simulation/intern/simulation_update.cc
+++ b/source/blender/simulation/intern/simulation_update.cc
@@ -17,8 +17,11 @@
#include "SIM_simulation_update.hh"
#include "BKE_customdata.h"
+#include "BKE_lib_id.h"
+#include "BKE_object.h"
#include "BKE_simulation.h"
+#include "DNA_modifier_types.h"
#include "DNA_scene_types.h"
#include "DNA_simulation_types.h"
@@ -29,8 +32,11 @@
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_rand.h"
+#include "BLI_set.hh"
#include "BLI_vector.hh"
+#include "NOD_node_tree_dependencies.hh"
+
#include "particle_function.hh"
#include "simulation_collect_influences.hh"
#include "simulation_solver.hh"
@@ -88,6 +94,131 @@ static void update_simulation_state_list(Simulation *simulation,
add_missing_states(simulation, required_states);
}
+class SampledDependencyAnimations : public DependencyAnimations {
+ private:
+ TimeInterval simulation_time_interval_;
+ MultiValueMap<Object *, float4x4> object_transforms_cache_;
+
+ public:
+ SampledDependencyAnimations(TimeInterval simulation_time_interval)
+ : simulation_time_interval_(simulation_time_interval)
+ {
+ }
+
+ void add_object_transforms(Object &object, Span<float4x4> transforms)
+ {
+ object_transforms_cache_.add_multiple(&object, transforms);
+ }
+
+ bool is_object_transform_changing(Object &object) const
+ {
+ return object_transforms_cache_.lookup(&object).size() >= 2;
+ }
+
+ void get_object_transforms(Object &object,
+ Span<float> simulation_times,
+ MutableSpan<float4x4> r_transforms) const
+ {
+ assert_same_size(simulation_times, r_transforms);
+ Span<float4x4> cached_transforms = object_transforms_cache_.lookup(&object);
+ if (cached_transforms.size() == 0) {
+ r_transforms.fill(object.obmat);
+ return;
+ }
+ if (cached_transforms.size() == 1) {
+ r_transforms.fill(cached_transforms[0]);
+ return;
+ }
+
+ for (int i : simulation_times.index_range()) {
+ const float simulation_time = simulation_times[i];
+ if (simulation_time <= simulation_time_interval_.start()) {
+ r_transforms[i] = cached_transforms.first();
+ continue;
+ }
+ if (simulation_time >= simulation_time_interval_.stop()) {
+ r_transforms[i] = cached_transforms.last();
+ continue;
+ }
+ const float factor = simulation_time_interval_.factor_at_time(simulation_time);
+ BLI_assert(factor > 0.0f && factor < 1.0f);
+ const float scaled_factor = factor * (cached_transforms.size() - 1);
+ const int lower_sample = (int)scaled_factor;
+ const int upper_sample = lower_sample + 1;
+ const float mix_factor = scaled_factor - lower_sample;
+ r_transforms[i] = float4x4::interpolate(
+ cached_transforms[lower_sample], cached_transforms[upper_sample], mix_factor);
+ }
+ }
+};
+
+static void sample_object_transforms(Object &object,
+ Depsgraph &depsgraph,
+ Scene &scene,
+ TimeInterval scene_frame_interval,
+ MutableSpan<float4x4> r_transforms)
+{
+ if (r_transforms.size() == 0) {
+ return;
+ }
+ if (r_transforms.size() == 1) {
+ r_transforms[0] = object.obmat;
+ return;
+ }
+
+ Array<float> frames(r_transforms.size());
+ scene_frame_interval.compute_uniform_samples(frames);
+
+ for (int i : frames.index_range()) {
+ float frame = frames[i];
+ const int recursion_depth = 5;
+ BKE_object_modifier_update_subframe(
+ &depsgraph, &scene, &object, false, recursion_depth, frame, eModifierType_None);
+ r_transforms[i] = object.obmat;
+ }
+}
+
+template<typename T> static bool all_values_equal(Span<T> values)
+{
+ if (values.size() == 0) {
+ return true;
+ }
+ for (const T &value : values.drop_front(1)) {
+ if (value != values[0]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void prepare_dependency_animations(Depsgraph &depsgraph,
+ Scene &scene,
+ Simulation &simulation,
+ TimeInterval scene_frame_interval,
+ SampledDependencyAnimations &r_dependency_animations)
+{
+ LISTBASE_FOREACH (SimulationDependency *, dependency, &simulation.dependencies) {
+ ID *id_cow = DEG_get_evaluated_id(&depsgraph, dependency->id);
+ if (id_cow == nullptr) {
+ continue;
+ }
+ if (GS(id_cow->name) != ID_OB) {
+ continue;
+ }
+ Object &object_cow = *(Object *)id_cow;
+ constexpr int sample_count = 10;
+ Array<float4x4, sample_count> transforms(sample_count);
+ sample_object_transforms(object_cow, depsgraph, scene, scene_frame_interval, transforms);
+
+ /* If all samples are the same, only store one. */
+ Span<float4x4> transforms_to_use = (all_values_equal(transforms.as_span())) ?
+ transforms.as_span().take_front(1) :
+ transforms.as_span();
+
+ r_dependency_animations.add_object_transforms(object_cow, transforms_to_use);
+ }
+}
+
void update_simulation_in_depsgraph(Depsgraph *depsgraph,
Scene *scene_cow,
Simulation *simulation_cow)
@@ -108,13 +239,20 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
SimulationInfluences influences;
RequiredStates required_states;
- /* TODO: Use simulation_cow, but need to add depsgraph relations before that. */
- collect_simulation_influences(*simulation_orig, resources, influences, required_states);
+ collect_simulation_influences(*simulation_cow, resources, influences, required_states);
+
+ bke::PersistentDataHandleMap handle_map;
+ LISTBASE_FOREACH (SimulationDependency *, dependency, &simulation_orig->dependencies) {
+ ID *id_cow = DEG_get_evaluated_id(depsgraph, dependency->id);
+ if (id_cow != nullptr) {
+ handle_map.add(dependency->handle, *id_cow);
+ }
+ }
if (current_frame == 1) {
reinitialize_empty_simulation_states(simulation_orig, required_states);
- initialize_simulation_states(*simulation_orig, *depsgraph, influences);
+ initialize_simulation_states(*simulation_orig, *depsgraph, influences, handle_map);
simulation_orig->current_frame = 1;
copy_states_to_cow(simulation_orig, simulation_cow);
@@ -122,12 +260,97 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
else if (current_frame == simulation_orig->current_frame + 1) {
update_simulation_state_list(simulation_orig, required_states);
- float time_step = 1.0f / 24.0f;
- solve_simulation_time_step(*simulation_orig, *depsgraph, influences, time_step);
+ const float fps = scene_cow->r.frs_sec / scene_cow->r.frs_sec_base;
+ const float time_step = 1.0f / fps;
+ TimeInterval scene_frame_interval(current_frame - 1, 1);
+ TimeInterval simulation_time_interval(simulation_orig->current_simulation_time, time_step);
+ SampledDependencyAnimations dependency_animations{simulation_time_interval};
+ prepare_dependency_animations(
+ *depsgraph, *scene_cow, *simulation_orig, scene_frame_interval, dependency_animations);
+
+ solve_simulation_time_step(
+ *simulation_orig, *depsgraph, influences, handle_map, dependency_animations, time_step);
simulation_orig->current_frame = current_frame;
copy_states_to_cow(simulation_orig, simulation_cow);
}
}
+/* Returns true when dependencies have changed. */
+bool update_simulation_dependencies(Simulation *simulation)
+{
+ nodes::NodeTreeDependencies dependencies = nodes::find_node_tree_dependencies(
+ *simulation->nodetree);
+
+ ListBase *dependency_list = &simulation->dependencies;
+
+ bool dependencies_changed = false;
+
+ Map<ID *, SimulationDependency *> dependency_by_id;
+ Map<SimulationDependency *, int> old_flag_by_dependency;
+ Set<int> used_handles;
+
+ /* Remove unused handle items and clear flags that are reinitialized later. */
+ LISTBASE_FOREACH_MUTABLE (SimulationDependency *, dependency, dependency_list) {
+ if (dependencies.depends_on(dependency->id)) {
+ dependency_by_id.add_new(dependency->id, dependency);
+ used_handles.add_new(dependency->handle);
+ old_flag_by_dependency.add_new(dependency, dependency->flag);
+ dependency->flag &= ~(SIM_DEPENDS_ON_TRANSFORM | SIM_DEPENDS_ON_GEOMETRY);
+ }
+ else {
+ if (dependency->id != nullptr) {
+ id_us_min(dependency->id);
+ }
+ BLI_remlink(dependency_list, dependency);
+ MEM_freeN(dependency);
+ dependencies_changed = true;
+ }
+ }
+
+ /* Add handle items for new id dependencies. */
+ int next_handle = 0;
+ for (ID *id : dependencies.id_dependencies()) {
+ dependency_by_id.lookup_or_add_cb(id, [&]() {
+ while (used_handles.contains(next_handle)) {
+ next_handle++;
+ }
+ used_handles.add_new(next_handle);
+
+ SimulationDependency *dependency = (SimulationDependency *)MEM_callocN(sizeof(*dependency),
+ AT);
+ id_us_plus(id);
+ dependency->id = id;
+ dependency->handle = next_handle;
+ BLI_addtail(dependency_list, dependency);
+
+ return dependency;
+ });
+ }
+
+ /* Set appropriate dependency flags. */
+ for (Object *object : dependencies.transform_dependencies()) {
+ SimulationDependency *dependency = dependency_by_id.lookup(&object->id);
+ dependency->flag |= SIM_DEPENDS_ON_TRANSFORM;
+ }
+ for (Object *object : dependencies.geometry_dependencies()) {
+ SimulationDependency *dependency = dependency_by_id.lookup(&object->id);
+ dependency->flag |= SIM_DEPENDS_ON_GEOMETRY;
+ }
+
+ if (!dependencies_changed) {
+ /* Check if any flags have changed. */
+ LISTBASE_FOREACH (SimulationDependency *, dependency, dependency_list) {
+ uint32_t old_flag = old_flag_by_dependency.lookup_default(dependency, 0);
+ uint32_t new_flag = dependency->flag;
+ if (old_flag != new_flag) {
+ dependencies_changed = true;
+ break;
+ }
+ }
+ }
+
+ return dependencies_changed;
+}
+
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/time_interval.hh b/source/blender/simulation/intern/time_interval.hh
index 6f13634ed06..034628fa9be 100644
--- a/source/blender/simulation/intern/time_interval.hh
+++ b/source/blender/simulation/intern/time_interval.hh
@@ -21,7 +21,7 @@
namespace blender::sim {
/**
- * The start time is inclusive and the end time is exclusive. The duration is zero, the interval
+ * The start time is exclusive and the end time is inclusive. If the duration is zero, the interval
* describes a single point in time.
*/
class TimeInterval {
@@ -40,7 +40,7 @@ class TimeInterval {
return start_;
}
- float end() const
+ float stop() const
{
return start_ + duration_;
}
@@ -49,6 +49,42 @@ class TimeInterval {
{
return duration_;
}
+
+ float time_at_factor(float factor) const
+ {
+ return start_ + factor * duration_;
+ }
+
+ float factor_at_time(float time) const
+ {
+ BLI_assert(duration_ > 0.0f);
+ return (time - start_) / duration_;
+ }
+
+ float safe_factor_at_time(float time) const
+ {
+ if (duration_ > 0.0f) {
+ return this->factor_at_time(time);
+ }
+ return 0.0f;
+ }
+
+ void compute_uniform_samples(MutableSpan<float> r_samples) const
+ {
+ int64_t amount = r_samples.size();
+ if (amount == 0) {
+ return;
+ }
+ if (amount == 1) {
+ r_samples[0] = this->time_at_factor(0.5f);
+ return;
+ }
+
+ const float step = duration_ / (float)(amount - 1);
+ for (int64_t i : r_samples.index_range()) {
+ r_samples[i] = start_ + i * step;
+ }
+ }
};
} // namespace blender::sim