Particles: improve emitter when object is animated
This commit is contained in:
@@ -46,7 +46,7 @@ static BLI_NOINLINE void compute_birth_times(float rate,
|
||||
counter++;
|
||||
const float time_offset = counter * time_between_particles;
|
||||
const float birth_time = state.last_birth_time + time_offset;
|
||||
if (birth_time > emit_interval.end()) {
|
||||
if (birth_time > emit_interval.stop()) {
|
||||
break;
|
||||
}
|
||||
if (birth_time <= emit_interval.start()) {
|
||||
@@ -217,8 +217,8 @@ static BLI_NOINLINE void sample_looptris(RandomNumberGenerator &rng,
|
||||
}
|
||||
}
|
||||
|
||||
static BLI_NOINLINE bool compute_new_particle_attributes(EmitterSettings &settings,
|
||||
TimeInterval emit_interval,
|
||||
static BLI_NOINLINE bool compute_new_particle_attributes(ParticleEmitterContext &context,
|
||||
EmitterSettings &settings,
|
||||
ParticleMeshEmitterSimulationState &state,
|
||||
Vector<float3> &r_positions,
|
||||
Vector<float3> &r_velocities,
|
||||
@@ -238,19 +238,17 @@ static BLI_NOINLINE bool compute_new_particle_attributes(EmitterSettings &settin
|
||||
return false;
|
||||
}
|
||||
|
||||
const float4x4 local_to_world = settings.object->obmat;
|
||||
float4x4 local_to_world_normal = local_to_world.inverted_affine().transposed();
|
||||
|
||||
const float start_time = emit_interval.start();
|
||||
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, emit_interval, state, r_birth_times);
|
||||
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);
|
||||
@@ -266,22 +264,36 @@ static BLI_NOINLINE bool compute_new_particle_attributes(EmitterSettings &settin
|
||||
return false;
|
||||
}
|
||||
|
||||
Array<float3> local_positions(particle_amount);
|
||||
Array<float3> local_normals(particle_amount);
|
||||
sample_looptris(rng, mesh, triangles, triangles_to_sample, local_positions, local_normals);
|
||||
r_positions.resize(particle_amount);
|
||||
r_velocities.resize(particle_amount);
|
||||
sample_looptris(rng, mesh, triangles, triangles_to_sample, r_positions, r_velocities);
|
||||
|
||||
r_positions.reserve(particle_amount);
|
||||
r_velocities.reserve(particle_amount);
|
||||
for (int i : IndexRange(particle_amount)) {
|
||||
float3 position = local_to_world * local_positions[i];
|
||||
float3 normal = local_to_world_normal.ref_3x3() * local_normals[i];
|
||||
normal.normalize();
|
||||
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);
|
||||
|
||||
r_positions.append(position);
|
||||
r_velocities.append(normal);
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
state.last_birth_time = r_birth_times.last();
|
||||
for (int i : IndexRange(particle_amount)) {
|
||||
r_velocities[i].normalize();
|
||||
}
|
||||
|
||||
state.last_birth_time = last_birth_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -317,12 +329,8 @@ void ParticleMeshEmitter::emit(ParticleEmitterContext &context) const
|
||||
Vector<float3> new_velocities;
|
||||
Vector<float> new_birth_times;
|
||||
|
||||
if (!compute_new_particle_attributes(settings,
|
||||
context.emit_interval,
|
||||
*state,
|
||||
new_positions,
|
||||
new_velocities,
|
||||
new_birth_times)) {
|
||||
if (!compute_new_particle_attributes(
|
||||
context, settings, *state, new_positions, new_velocities, new_birth_times)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,18 +26,6 @@
|
||||
|
||||
namespace blender::sim {
|
||||
|
||||
ParticleForce::~ParticleForce()
|
||||
{
|
||||
}
|
||||
|
||||
ParticleEmitter::~ParticleEmitter()
|
||||
{
|
||||
}
|
||||
|
||||
ParticleAction::~ParticleAction()
|
||||
{
|
||||
}
|
||||
|
||||
static CustomDataType cpp_to_custom_data_type(const CPPType &type)
|
||||
{
|
||||
if (type.is<float3>()) {
|
||||
@@ -178,7 +166,7 @@ BLI_NOINLINE static void simulate_existing_particles(SimulationSolveContext &sol
|
||||
|
||||
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.end());
|
||||
solve_context, state, attributes, remaining_durations, solve_context.solve_interval.stop());
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void run_emitters(SimulationSolveContext &solve_context,
|
||||
@@ -273,6 +261,7 @@ void solve_simulation_time_step(Simulation &simulation,
|
||||
Depsgraph &depsgraph,
|
||||
const SimulationInfluences &influences,
|
||||
const bke::PersistentDataHandleMap &handle_map,
|
||||
const DependencyAnimations &dependency_animations,
|
||||
float time_step)
|
||||
{
|
||||
SimulationStateMap state_map;
|
||||
@@ -285,7 +274,8 @@ void solve_simulation_time_step(Simulation &simulation,
|
||||
influences,
|
||||
TimeInterval(simulation.current_simulation_time, time_step),
|
||||
state_map,
|
||||
handle_map};
|
||||
handle_map,
|
||||
dependency_animations};
|
||||
|
||||
Span<ParticleSimulationState *> particle_simulation_states =
|
||||
state_map.lookup<ParticleSimulationState>();
|
||||
@@ -335,7 +325,7 @@ void solve_simulation_time_step(Simulation &simulation,
|
||||
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.end();
|
||||
const float end_time = solve_context.solve_interval.stop();
|
||||
for (int i : attributes.index_range()) {
|
||||
remaining_durations[i] = end_time - birth_times[i];
|
||||
}
|
||||
@@ -345,7 +335,7 @@ void solve_simulation_time_step(Simulation &simulation,
|
||||
remove_dead_and_add_new_particles(*state, allocator);
|
||||
}
|
||||
|
||||
simulation.current_simulation_time = solve_context.solve_interval.end();
|
||||
simulation.current_simulation_time = solve_context.solve_interval.stop();
|
||||
}
|
||||
|
||||
} // namespace blender::sim
|
||||
|
||||
@@ -32,6 +32,7 @@ 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
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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()
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
@@ -18,6 +18,7 @@
|
||||
#define __SIM_SIMULATION_SOLVER_INFLUENCES_HH__
|
||||
|
||||
#include "BLI_float3.hh"
|
||||
#include "BLI_float4x4.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
@@ -112,6 +113,16 @@ class SimulationStateMap {
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -119,6 +130,7 @@ struct SimulationSolveContext {
|
||||
TimeInterval solve_interval;
|
||||
const SimulationStateMap &state_map;
|
||||
const bke::PersistentDataHandleMap &handle_map;
|
||||
const DependencyAnimations &dependency_animations;
|
||||
};
|
||||
|
||||
class ParticleAllocators {
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
#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"
|
||||
|
||||
@@ -92,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)
|
||||
@@ -117,7 +244,9 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
|
||||
bke::PersistentDataHandleMap handle_map;
|
||||
LISTBASE_FOREACH (SimulationDependency *, dependency, &simulation_orig->dependencies) {
|
||||
ID *id_cow = DEG_get_evaluated_id(depsgraph, dependency->id);
|
||||
handle_map.add(dependency->handle, *id_cow);
|
||||
if (id_cow != nullptr) {
|
||||
handle_map.add(dependency->handle, *id_cow);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_frame == 1) {
|
||||
@@ -131,8 +260,16 @@ 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, handle_map, 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);
|
||||
|
||||
@@ -41,7 +41,7 @@ class TimeInterval {
|
||||
return start_;
|
||||
}
|
||||
|
||||
float end() const
|
||||
float stop() const
|
||||
{
|
||||
return start_ + duration_;
|
||||
}
|
||||
@@ -50,6 +50,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
|
||||
|
||||
Reference in New Issue
Block a user