Particles: new Age Reached Event, Kill Particle and Random Float node

The hardcoded age limit is now gone. The behavior can be implemented
with an Age Reached Event and Kill Particle node. Other utility nodes
to handle age limits of particles can be added later. Adding an
Age Limit attribute to particles on birth will be useful for some effects,
e.g. when you want to control the color or size of a particle over its
life time.

The Random Float node takes a seed currently. Different nodes will
produce different values even with the same seed. However, the same
node will generate the same random number for the same seed every
time. The "Hash" of a particle can be used as seed. Later, we'd want
to have more modes in the node to make it more user friendly.
Modes could be: Per Particle, Per Time, Per Particle Per Time,
Per Node Instance, ...
Also a Random Vector node will be useful, as it currently has to be
build using three Random Float nodes.
This commit is contained in:
Jacques Lucke 2020-08-02 22:04:24 +02:00
parent d1063575b5
commit 396d0b5cd0
16 changed files with 298 additions and 41 deletions

View File

@ -500,6 +500,7 @@ simulation_node_categories = [
SimulationNodeCategory("SIM_EVENTS", "Events", items=[
NodeItem("SimulationNodeParticleBirthEvent"),
NodeItem("SimulationNodeParticleTimeStepEvent"),
NodeItem("SimulationNodeAgeReachedEvent"),
not_implemented_node("SimulationNodeParticleMeshCollisionEvent"),
]),
SimulationNodeCategory("SIM_FORCES", "Forces", items=[
@ -508,6 +509,7 @@ simulation_node_categories = [
SimulationNodeCategory("SIM_EXECUTE", "Execute", items=[
NodeItem("SimulationNodeSetParticleAttribute"),
NodeItem("SimulationNodeExecuteCondition"),
NodeItem("SimulationNodeKillParticle"),
not_implemented_node("SimulationNodeMultiExecute"),
]),
SimulationNodeCategory("SIM_NOISE", "Noise", items=[
@ -537,6 +539,7 @@ simulation_node_categories = [
NodeItem("FunctionNodeFloatCompare"),
not_implemented_node("FunctionNodeSwitch"),
NodeItem("FunctionNodeCombineStrings"),
NodeItem("FunctionNodeRandomFloat"),
]),
SimulationNodeCategory("SIM_GROUP", "Group", items=node_group_items),
SimulationNodeCategory("SIM_LAYOUT", "Layout", items=[

View File

@ -1331,6 +1331,8 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define SIM_NODE_EMIT_PARTICLES 1009
#define SIM_NODE_TIME 1010
#define SIM_NODE_PARTICLE_ATTRIBUTE 1011
#define SIM_NODE_AGE_REACHED_EVENT 1012
#define SIM_NODE_KILL_PARTICLE 1013
/** \} */
@ -1344,6 +1346,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define FN_NODE_GROUP_INSTANCE_ID 1203
#define FN_NODE_COMBINE_STRINGS 1204
#define FN_NODE_OBJECT_TRANSFORMS 1205
#define FN_NODE_RANDOM_FLOAT 1206
/** \} */

View File

@ -4352,6 +4352,8 @@ static void registerSimulationNodes(void)
register_node_type_sim_emit_particles();
register_node_type_sim_time();
register_node_type_sim_particle_attribute();
register_node_type_sim_age_reached_event();
register_node_type_sim_kill_particle();
}
static void registerFunctionNodes(void)
@ -4362,6 +4364,7 @@ static void registerFunctionNodes(void)
register_node_type_fn_group_instance_id();
register_node_type_fn_combine_strings();
register_node_type_fn_object_transforms();
register_node_type_fn_random_float();
}
void init_nodesystem(void)

View File

@ -27,6 +27,10 @@ AttributesInfoBuilder::~AttributesInfoBuilder()
bool AttributesInfoBuilder::add(StringRef name, const CPPType &type, const void *default_value)
{
if (name.size() == 0) {
std::cout << "Warning: Tried to add an attribute with empty name.\n";
return false;
}
if (names_.add_as(name)) {
types_.append(&type);

View File

@ -136,6 +136,7 @@ set(SRC
function/nodes/node_fn_float_compare.cc
function/nodes/node_fn_group_instance_id.cc
function/nodes/node_fn_object_transforms.cc
function/nodes/node_fn_random_float.cc
function/nodes/node_fn_switch.cc
function/node_function_util.cc
@ -232,10 +233,12 @@ set(SRC
shader/node_shader_tree.c
shader/node_shader_util.c
simulation/nodes/node_sim_age_reached_event.cc
simulation/nodes/node_sim_common.cc
simulation/nodes/node_sim_emit_particles.cc
simulation/nodes/node_sim_execute_condition.cc
simulation/nodes/node_sim_force.cc
simulation/nodes/node_sim_kill_particle.cc
simulation/nodes/node_sim_multi_execute.cc
simulation/nodes/node_sim_particle_attribute.cc
simulation/nodes/node_sim_particle_birth_event.cc

View File

@ -27,6 +27,7 @@ void register_node_type_fn_switch(void);
void register_node_type_fn_group_instance_id(void);
void register_node_type_fn_combine_strings(void);
void register_node_type_fn_object_transforms(void);
void register_node_type_fn_random_float(void);
#ifdef __cplusplus
}

View File

@ -39,6 +39,8 @@ void register_node_type_sim_particle_mesh_collision_event(void);
void register_node_type_sim_emit_particles(void);
void register_node_type_sim_time(void);
void register_node_type_sim_particle_attribute(void);
void register_node_type_sim_age_reached_event(void);
void register_node_type_sim_kill_particle(void);
#ifdef __cplusplus
}

View File

@ -270,6 +270,8 @@ DefNode(SimulationNode, SIM_NODE_PARTICLE_MESH_COLLISION_EVENT, 0, "PARTIC
DefNode(SimulationNode, SIM_NODE_EMIT_PARTICLES, 0, "EMIT_PARTICLES", EmitParticles, "Emit Particles", "")
DefNode(SimulationNode, SIM_NODE_TIME, def_sim_time, "TIME", Time, "Time", "")
DefNode(SimulationNode, SIM_NODE_PARTICLE_ATTRIBUTE, def_sim_particle_attribute, "PARTICLE_ATTRIBUTE", ParticleAttribute, "Particle Attribute", "")
DefNode(SimulationNode, SIM_NODE_AGE_REACHED_EVENT, 0, "AGE_REACHED_EVENT", AgeReachedEvent, "Age Reached Event", "")
DefNode(SimulationNode, SIM_NODE_KILL_PARTICLE, 0, "KILL_PARTICLE", KillParticle, "Kill Particle", "")
DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "")
DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "")
@ -277,7 +279,7 @@ DefNode(FunctionNode, FN_NODE_SWITCH, def_fn_switch, "SWITCH",
DefNode(FunctionNode, FN_NODE_GROUP_INSTANCE_ID, 0, "GROUP_INSTANCE_ID", GroupInstanceID, "Group Instance ID", "")
DefNode(FunctionNode, FN_NODE_COMBINE_STRINGS, 0, "COMBINE_STRINGS", CombineStrings, "Combine Strings", "")
DefNode(FunctionNode, FN_NODE_OBJECT_TRANSFORMS, 0, "OBJECT_TRANSFORMS", ObjectTransforms, "Object Transforms", "")
DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
/* undefine macros */

View File

@ -0,0 +1,89 @@
/*
* 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 "node_function_util.hh"
#include "BLI_hash.h"
static bNodeSocketTemplate fn_node_random_float_in[] = {
{SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE},
{SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE},
{SOCK_INT, N_("Seed")},
{-1, ""},
};
static bNodeSocketTemplate fn_node_random_float_out[] = {
{SOCK_FLOAT, N_("Value")},
{-1, ""},
};
class RandomFloatFunction : public blender::fn::MultiFunction {
private:
uint32_t function_seed_;
public:
RandomFloatFunction(uint32_t function_seed) : function_seed_(function_seed)
{
blender::fn::MFSignatureBuilder signature = this->get_builder("Random float");
signature.single_input<float>("Min");
signature.single_input<float>("Max");
signature.single_input<int>("Seed");
signature.single_output<float>("Value");
}
void call(blender::IndexMask mask,
blender::fn::MFParams params,
blender::fn::MFContext UNUSED(context)) const override
{
blender::fn::VSpan<float> min_values = params.readonly_single_input<float>(0, "Min");
blender::fn::VSpan<float> max_values = params.readonly_single_input<float>(1, "Max");
blender::fn::VSpan<int> seeds = params.readonly_single_input<int>(2, "Seed");
blender::MutableSpan<float> values = params.uninitialized_single_output<float>(3, "Value");
for (int64_t i : mask) {
const float min_value = min_values[i];
const float max_value = max_values[i];
const int seed = seeds[i];
const float value = BLI_hash_int_01((uint32_t)seed ^ function_seed_);
values[i] = value * (max_value - min_value) + min_value;
}
}
};
static void fn_node_random_float_expand_in_mf_network(
blender::nodes::NodeMFNetworkBuilder &builder)
{
uint32_t function_seed = 1746872341u;
const blender::nodes::DNode &node = builder.dnode();
const blender::DefaultHash<blender::StringRefNull> hasher;
function_seed = 33 * function_seed + hasher(node.name());
for (const blender::nodes::DParentNode *parent = node.parent(); parent != nullptr;
parent = parent->parent()) {
function_seed = 33 * function_seed + hasher(parent->node_ref().name());
}
builder.construct_and_set_matching_fn<RandomFloatFunction>(function_seed);
}
void register_node_type_fn_random_float()
{
static bNodeType ntype;
fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0);
node_type_socket_templates(&ntype, fn_node_random_float_in, fn_node_random_float_out);
ntype.expand_in_mf_network = fn_node_random_float_expand_in_mf_network;
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,38 @@
/*
* 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 "node_simulation_util.h"
static bNodeSocketTemplate sim_node_age_reached_event_in[] = {
{SOCK_FLOAT, N_("Age"), 3, 0, 0, 0, 0, 10000000},
{SOCK_CONTROL_FLOW, N_("Execute")},
{-1, ""},
};
static bNodeSocketTemplate sim_node_age_reached_event_out[] = {
{SOCK_EVENTS, N_("Event")},
{-1, ""},
};
void register_node_type_sim_age_reached_event()
{
static bNodeType ntype;
sim_node_type_base(&ntype, SIM_NODE_AGE_REACHED_EVENT, "Age Reached Event", 0, 0);
node_type_socket_templates(
&ntype, sim_node_age_reached_event_in, sim_node_age_reached_event_out);
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,36 @@
/*
* 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 "node_simulation_util.h"
static bNodeSocketTemplate sim_node_kill_particle_in[] = {
{SOCK_CONTROL_FLOW, N_("Execute")},
{-1, ""},
};
static bNodeSocketTemplate sim_node_kill_particle_out[] = {
{SOCK_CONTROL_FLOW, N_("Execute")},
{-1, ""},
};
void register_node_type_sim_kill_particle()
{
static bNodeType ntype;
sim_node_type_base(&ntype, SIM_NODE_KILL_PARTICLE, "Kill Particle", 0, 0);
node_type_socket_templates(&ntype, sim_node_kill_particle_in, sim_node_kill_particle_out);
nodeRegisterType(&ntype);
}

View File

@ -21,6 +21,7 @@
static bNodeSocketTemplate sim_node_particle_mesh_emitter_in[] = {
{SOCK_OBJECT, N_("Object")},
{SOCK_FLOAT, N_("Rate"), 100.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX},
{SOCK_CONTROL_FLOW, N_("Execute")},
{-1, ""},
};

View File

@ -346,6 +346,16 @@ void ParticleMeshEmitter::emit(ParticleEmitterContext &context) const
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);
}
}
}

View File

@ -28,14 +28,17 @@ class ParticleMeshEmitter final : public ParticleEmitter {
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 fn::MultiFunction &inputs_fn,
const ParticleAction *action)
: own_state_name_(std::move(own_state_name)),
particle_names_(particle_names),
inputs_fn_(inputs_fn)
inputs_fn_(inputs_fn),
action_(action)
{
}

View File

@ -367,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_;
@ -401,11 +412,8 @@ static void create_forces_for_particle_simulation(CollectContext &context,
continue;
}
const MFInputSocket &force_socket = context.network_map.lookup_dummy(
origin_node.input(0, "Force"));
const ParticleFunction *particle_fn = create_particle_function_for_inputs(context,
{&force_socket});
const ParticleFunction *particle_fn = create_particle_function_for_inputs(
context, {&origin_node.input(0, "Force")});
if (particle_fn == nullptr) {
continue;
@ -434,7 +442,7 @@ static ParticleEmitter *create_particle_emitter(CollectContext &context, const D
return nullptr;
}
Array<const MFInputSocket *> input_sockets{dnode.inputs().size()};
Array<const MFInputSocket *> input_sockets{2};
for (int i : input_sockets.index_range()) {
input_sockets[i] = &context.network_map.lookup_dummy(dnode.input(i));
}
@ -446,10 +454,13 @@ static ParticleEmitter *create_particle_emitter(CollectContext &context, const D
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);
AT, own_state_name, names.as_span(), inputs_fn, birth_action);
return &emitter;
}
@ -490,15 +501,10 @@ 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);
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);
const ParticleAction *action = create_particle_action(context, execute_input, particle_names);
if (action == nullptr) {
continue;
}
@ -570,6 +576,10 @@ class SetParticleAttributeAction : public ParticleAction {
cpp_type_.copy_to_initialized_indices(
value_array.data(), attribute_array->data(), context.particles.index_mask);
}
if (attribute_name_ == "Velocity") {
context.particles.update_diffs_after_velocity_change();
}
}
};
@ -595,21 +605,28 @@ static const ParticleAction *create_set_particle_attribute_action(
CollectContext &context, const DOutputSocket &dsocket, Span<StringRefNull> particle_names)
{
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 nullptr;
return previous_action;
}
std::string attribute_name = (*names)[0];
if (attribute_name.empty()) {
return previous_action;
}
const CPPType &attribute_type = value_socket.data_type().single_type();
const ParticleFunction *inputs_fn = create_particle_function_for_inputs(context,
{&value_socket});
if (inputs_fn == nullptr) {
return nullptr;
return previous_action;
}
for (StringRef particle_name : particle_names) {
@ -620,9 +637,6 @@ static const ParticleAction *create_set_particle_attribute_action(
ParticleAction &this_action = context.resources.construct<SetParticleAttributeAction>(
AT, attribute_name, attribute_type, *inputs_fn);
const ParticleAction *previous_action = create_particle_action(
context, dnode.input(0), particle_names);
return concatenate_actions(context, {previous_action, &this_action});
}
@ -698,10 +712,9 @@ static const ParticleAction *create_particle_condition_action(CollectContext &co
Span<StringRefNull> particle_names)
{
const DNode &dnode = dsocket.node();
MFInputSocket &condition_socket = context.network_map.lookup_dummy(dnode.input(0));
const ParticleFunction *inputs_fn = create_particle_function_for_inputs(context,
{&condition_socket});
const ParticleFunction *inputs_fn = create_particle_function_for_inputs(
context, {&dnode.input(0, "Condition")});
if (inputs_fn == nullptr) {
return nullptr;
}
@ -718,17 +731,32 @@ static const ParticleAction *create_particle_condition_action(CollectContext &co
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();
if (dnode.idname() == "SimulationNodeSetParticleAttribute") {
StringRef idname = dnode.idname();
if (idname == "SimulationNodeSetParticleAttribute") {
return create_set_particle_attribute_action(context, dsocket, particle_names);
}
if (dnode.idname() == "SimulationNodeExecuteCondition") {
if (idname == "SimulationNodeExecuteCondition") {
return create_particle_condition_action(context, dsocket, particle_names);
}
if (idname == "SimulationNodeKillParticle") {
return &context.resources.construct<KillParticleAction>(AT);
}
return nullptr;
}
@ -762,25 +790,38 @@ static void optimize_function_network(CollectContext &context)
class AgeReachedEvent : public ParticleEvent {
private:
std::string attribute_name_;
const ParticleFunction &inputs_fn_;
const ParticleAction &action_;
public:
AgeReachedEvent(std::string attribute_name) : attribute_name_(std::move(attribute_name))
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");
Span<int> has_been_triggered = context.particles.attributes.get<int>(attribute_name_);
const float age = 5.0f;
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]) {
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 + age;
const float trigger_time = birth_time + trigger_age;
if (trigger_time > end_time) {
continue;
}
@ -795,24 +836,41 @@ class AgeReachedEvent : public ParticleEvent {
void execute(ParticleActionContext &context) const override
{
MutableSpan<int> dead_states = context.particles.attributes.get<int>("Dead");
MutableSpan<int> has_been_triggered = context.particles.attributes.get<int>(attribute_name_);
for (int i : context.particles.index_mask) {
dead_states[i] = true;
has_been_triggered[i] = 1;
}
action_.execute(context);
}
};
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<AgeReachedEvent>(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<int>(attribute_name, 0);
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);
}
}
}
}

View File

@ -143,8 +143,9 @@ BLI_NOINLINE static void find_next_event_per_particle(
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(), -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);