From 7cd2c1fd2e7a4c82dd5569cf0ee0155c3cca9101 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sat, 1 Aug 2020 21:56:05 +0200 Subject: Particles: support custom particle events in solver Previously, there were only particle-birth and time-step events. Now the solver can handle custom events. On the user level this does not change anything yet. This feature of the solver will be used by an upcoming Age Reached Event node and possibly others. When this node exists, I can finally remove the hardcoded maximum particle age. --- .../intern/simulation_collect_influences.cc | 68 +++++++- .../blender/simulation/intern/simulation_solver.cc | 192 +++++++++++++++++++-- .../intern/simulation_solver_influences.cc | 4 + .../intern/simulation_solver_influences.hh | 43 +++++ 4 files changed, 292 insertions(+), 15 deletions(-) (limited to 'source/blender/simulation') diff --git a/source/blender/simulation/intern/simulation_collect_influences.cc b/source/blender/simulation/intern/simulation_collect_influences.cc index c744defb7d5..c309d18c43a 100644 --- a/source/blender/simulation/intern/simulation_collect_influences.cc +++ b/source/blender/simulation/intern/simulation_collect_influences.cc @@ -674,12 +674,18 @@ class ParticleConditionAction : public ParticleAction { } if (action_true_ != nullptr) { - ParticleChunkContext chunk_context{true_indices.as_span(), context.particles.attributes}; + 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{false_indices.as_span(), context.particles.attributes}; + 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); } @@ -753,6 +759,63 @@ static void optimize_function_network(CollectContext &context) // WM_clipboard_text_set(network.to_dot().c_str(), false); } +class AgeReachedEvent : public ParticleEvent { + private: + std::string attribute_name_; + + public: + AgeReachedEvent(std::string attribute_name) : attribute_name_(std::move(attribute_name)) + { + } + + void filter(ParticleEventFilterContext &context) const override + { + Span birth_times = context.particles.attributes.get("Birth Time"); + Span has_been_triggered = context.particles.attributes.get(attribute_name_); + const float age = 5.0f; + + const float end_time = context.particles.integration->end_time; + for (int i : context.particles.index_mask) { + if (has_been_triggered[i]) { + continue; + } + const float birth_time = birth_times[i]; + const float trigger_time = birth_time + 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(0.0f, time_factor); + } + } + + void execute(ParticleActionContext &context) const override + { + MutableSpan dead_states = context.particles.attributes.get("Dead"); + MutableSpan has_been_triggered = context.particles.attributes.get(attribute_name_); + for (int i : context.particles.index_mask) { + dead_states[i] = true; + has_been_triggered[i] = 1; + } + } +}; + +static void collect_age_reached_events(CollectContext &context) +{ + /* TODO: Actually implement an Age Reached Event node. */ + std::string attribute_name = "Has Been Triggered"; + const AgeReachedEvent &event = context.resources.construct(AT, attribute_name); + for (const DNode *dnode : context.particle_simulation_nodes) { + StringRefNull name = get_identifier(context, *dnode); + context.influences.particle_events.add_as(name, &event); + context.influences.particle_attributes_builder.lookup_as(name)->add(attribute_name, 0); + } +} + void collect_simulation_influences(Simulation &simulation, ResourceCollector &resources, SimulationInfluences &r_influences, @@ -774,6 +837,7 @@ void collect_simulation_influences(Simulation &simulation, collect_emitters(context); collect_birth_events(context); collect_time_step_events(context); + collect_age_reached_events(context); optimize_function_network(context); diff --git a/source/blender/simulation/intern/simulation_solver.cc b/source/blender/simulation/intern/simulation_solver.cc index 3c159eb1c58..9f2766aa24f 100644 --- a/source/blender/simulation/intern/simulation_solver.cc +++ b/source/blender/simulation/intern/simulation_solver.cc @@ -121,6 +121,170 @@ static void ensure_attributes_exist(ParticleSimulationState *state, const Attrib } } +BLI_NOINLINE static void apply_remaining_diffs(ParticleChunkContext &context) +{ + BLI_assert(context.integration != nullptr); + MutableSpan positions = context.attributes.get("Position"); + MutableSpan velocities = context.attributes.get("Velocity"); + + for (int i : context.index_mask) { + positions[i] += context.integration->position_diffs[i]; + velocities[i] += context.integration->velocity_diffs[i]; + } +} + +BLI_NOINLINE static void find_next_event_per_particle( + SimulationSolveContext &solve_context, + ParticleChunkContext &particles, + Span events, + MutableSpan r_next_event_indices, + MutableSpan 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 time_factors(particles.index_mask.min_array_size(), -1.0f); + for (int event_index : events.index_range()) { + 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; + } + } + } +} + +BLI_NOINLINE static void forward_particles_to_next_event_or_end( + ParticleChunkContext &particles, Span time_factors_to_next_event) +{ + MutableSpan positions = particles.attributes.get("Position"); + MutableSpan velocities = particles.attributes.get("Velocity"); + + MutableSpan position_diffs = particles.integration->position_diffs; + MutableSpan velocity_diffs = particles.integration->velocity_diffs; + MutableSpan 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; + } +} + +BLI_NOINLINE static void group_particles_by_event( + IndexMask mask, + Span next_event_indices, + MutableSpan> 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); + } + } +} + +BLI_NOINLINE static void execute_events(SimulationSolveContext &solve_context, + ParticleChunkContext &all_particles, + Span events, + Span> particles_per_event) +{ + for (int event_index : events.index_range()) { + Span 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 time_factors_to_next_event, + Vector &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 events, + Vector &r_unfinished_pindices) +{ + int array_size = particles.index_mask.min_array_size(); + Array next_event_indices(array_size); + Array 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> 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 events = solve_context.influences.particle_events.lookup_as( + state.head.name); + if (events.size() == 0) { + apply_remaining_diffs(particles); + return; + } + + Vector unfininished_pindices = particles.index_mask.indices(); + for (int iteration : IndexRange(max_events)) { + UNUSED_VARS(iteration); + if (unfininished_pindices.is_empty()) { + break; + } + + Vector 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, @@ -132,7 +296,7 @@ BLI_NOINLINE static void simulate_particle_chunk(SimulationSolveContext &solve_c Span begin_actions = solve_context.influences.particle_time_step_begin_actions.lookup_as(state.head.name); for (const ParticleAction *action : begin_actions) { - ParticleChunkContext particles{IndexMask(particle_amount), attributes}; + ParticleChunkContext particles{state, IndexMask(particle_amount), attributes}; ParticleActionContext action_context{solve_context, particles}; action->execute(action_context); } @@ -141,30 +305,32 @@ BLI_NOINLINE static void simulate_particle_chunk(SimulationSolveContext &solve_c Span forces = solve_context.influences.particle_forces.lookup_as( state.head.name); for (const ParticleForce *force : forces) { - ParticleChunkContext particles{IndexMask(particle_amount), attributes}; + ParticleChunkContext particles{state, IndexMask(particle_amount), attributes}; ParticleForceContext particle_force_context{solve_context, particles, force_vectors}; force->add_force(particle_force_context); } - MutableSpan positions = attributes.get("Position"); MutableSpan velocities = attributes.get("Velocity"); - MutableSpan birth_times = attributes.get("Birth Time"); - MutableSpan dead_states = attributes.get("Dead"); + Array position_diffs(particle_amount); + Array velocity_diffs(particle_amount); for (int i : IndexRange(particle_amount)) { const float time_step = remaining_durations[i]; - velocities[i] += force_vectors[i] * time_step; - positions[i] += velocities[i] * time_step; - - if (end_time - birth_times[i] > 2) { - dead_states[i] = true; - } + 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 end_actions = solve_context.influences.particle_time_step_end_actions.lookup_as(state.head.name); for (const ParticleAction *action : end_actions) { - ParticleChunkContext particles{IndexMask(particle_amount), attributes}; + ParticleChunkContext particles{state, IndexMask(particle_amount), attributes}; ParticleActionContext action_context{solve_context, particles}; action->execute(action_context); } @@ -326,7 +492,7 @@ void solve_simulation_time_step(Simulation &simulation, Span actions = influences.particle_birth_actions.lookup_as( state->head.name); for (const ParticleAction *action : actions) { - ParticleChunkContext chunk_context{IndexRange(attributes.size()), attributes}; + ParticleChunkContext chunk_context{*state, IndexRange(attributes.size()), attributes}; ParticleActionContext action_context{solve_context, chunk_context}; action->execute(action_context); } diff --git a/source/blender/simulation/intern/simulation_solver_influences.cc b/source/blender/simulation/intern/simulation_solver_influences.cc index 75c2c820651..3485d7c7bfb 100644 --- a/source/blender/simulation/intern/simulation_solver_influences.cc +++ b/source/blender/simulation/intern/simulation_solver_influences.cc @@ -32,6 +32,10 @@ ParticleAction::~ParticleAction() { } +ParticleEvent::~ParticleEvent() +{ +} + DependencyAnimations::~DependencyAnimations() { } diff --git a/source/blender/simulation/intern/simulation_solver_influences.hh b/source/blender/simulation/intern/simulation_solver_influences.hh index d3c515bb6a0..f7b8affd88d 100644 --- a/source/blender/simulation/intern/simulation_solver_influences.hh +++ b/source/blender/simulation/intern/simulation_solver_influences.hh @@ -45,6 +45,7 @@ using fn::MutableAttributesRef; struct ParticleEmitterContext; struct ParticleForceContext; struct ParticleActionContext; +struct ParticleEventFilterContext; class ParticleEmitter { public: @@ -64,11 +65,19 @@ class 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 particle_forces; MultiValueMap particle_birth_actions; MultiValueMap particle_time_step_begin_actions; MultiValueMap particle_time_step_end_actions; + MultiValueMap particle_events; Map particle_attributes_builder; Vector particle_emitters; }; @@ -157,9 +166,37 @@ class ParticleAllocators { } }; +struct ParticleChunkIntegrationContext { + MutableSpan position_diffs; + MutableSpan velocity_diffs; + MutableSpan 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 remaining_durations = integration->durations; + MutableSpan position_diffs = integration->position_diffs; + Span velocities = attributes.get("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 { @@ -189,6 +226,12 @@ struct ParticleActionContext { ParticleChunkContext &particles; }; +struct ParticleEventFilterContext { + SimulationSolveContext &solve_context; + ParticleChunkContext &particles; + MutableSpan factor_dst; +}; + } // namespace blender::sim #endif /* __SIM_SIMULATION_SOLVER_INFLUENCES_HH__ */ -- cgit v1.2.3