From 396d0b5cd0bcd9dd3dfa8d9006ee9f6f91c7196d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sun, 2 Aug 2020 22:04:24 +0200 Subject: 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. --- release/scripts/startup/nodeitems_builtins.py | 3 + source/blender/blenkernel/BKE_node.h | 3 + source/blender/blenkernel/intern/node.c | 3 + source/blender/functions/intern/attributes_ref.cc | 4 + source/blender/nodes/CMakeLists.txt | 3 + source/blender/nodes/NOD_function.h | 1 + source/blender/nodes/NOD_simulation.h | 2 + source/blender/nodes/NOD_static_types.h | 4 +- .../nodes/function/nodes/node_fn_random_float.cc | 89 ++++++++++++++ .../simulation/nodes/node_sim_age_reached_event.cc | 38 ++++++ .../simulation/nodes/node_sim_kill_particle.cc | 36 ++++++ .../nodes/node_sim_particle_mesh_emitter.cc | 1 + .../simulation/intern/particle_mesh_emitter.cc | 10 ++ .../simulation/intern/particle_mesh_emitter.hh | 7 +- .../intern/simulation_collect_influences.cc | 132 +++++++++++++++------ .../blender/simulation/intern/simulation_solver.cc | 3 +- 16 files changed, 298 insertions(+), 41 deletions(-) create mode 100644 source/blender/nodes/function/nodes/node_fn_random_float.cc create mode 100644 source/blender/nodes/simulation/nodes/node_sim_age_reached_event.cc create mode 100644 source/blender/nodes/simulation/nodes/node_sim_kill_particle.cc diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 7321f4f6345..f62c2ead144 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -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=[ diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 4c55488ecd5..f1502a9b87d 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -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 /** \} */ diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c index 8d7cb90fb71..91693abd1cf 100644 --- a/source/blender/blenkernel/intern/node.c +++ b/source/blender/blenkernel/intern/node.c @@ -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) diff --git a/source/blender/functions/intern/attributes_ref.cc b/source/blender/functions/intern/attributes_ref.cc index 4686e217911..8f7f41be079 100644 --- a/source/blender/functions/intern/attributes_ref.cc +++ b/source/blender/functions/intern/attributes_ref.cc @@ -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); diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index c53e01ac80f..33b95d50cc0 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -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 diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h index 4c05da694f7..1d22e9c6811 100644 --- a/source/blender/nodes/NOD_function.h +++ b/source/blender/nodes/NOD_function.h @@ -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 } diff --git a/source/blender/nodes/NOD_simulation.h b/source/blender/nodes/NOD_simulation.h index 2947d38fe83..fe3f73517ec 100644 --- a/source/blender/nodes/NOD_simulation.h +++ b/source/blender/nodes/NOD_simulation.h @@ -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 } diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 31ce3f81450..7922a73902c 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -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 */ diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/node_fn_random_float.cc new file mode 100644 index 00000000000..2ee0830637a --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_random_float.cc @@ -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("Min"); + signature.single_input("Max"); + signature.single_input("Seed"); + signature.single_output("Value"); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + blender::fn::VSpan min_values = params.readonly_single_input(0, "Min"); + blender::fn::VSpan max_values = params.readonly_single_input(1, "Max"); + blender::fn::VSpan seeds = params.readonly_single_input(2, "Seed"); + blender::MutableSpan values = params.uninitialized_single_output(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 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(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); +} diff --git a/source/blender/nodes/simulation/nodes/node_sim_age_reached_event.cc b/source/blender/nodes/simulation/nodes/node_sim_age_reached_event.cc new file mode 100644 index 00000000000..add8c4eba4d --- /dev/null +++ b/source/blender/nodes/simulation/nodes/node_sim_age_reached_event.cc @@ -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); +} diff --git a/source/blender/nodes/simulation/nodes/node_sim_kill_particle.cc b/source/blender/nodes/simulation/nodes/node_sim_kill_particle.cc new file mode 100644 index 00000000000..793b40d9365 --- /dev/null +++ b/source/blender/nodes/simulation/nodes/node_sim_kill_particle.cc @@ -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); +} diff --git a/source/blender/nodes/simulation/nodes/node_sim_particle_mesh_emitter.cc b/source/blender/nodes/simulation/nodes/node_sim_particle_mesh_emitter.cc index 859ad81656b..5e1a6c35d52 100644 --- a/source/blender/nodes/simulation/nodes/node_sim_particle_mesh_emitter.cc +++ b/source/blender/nodes/simulation/nodes/node_sim_particle_mesh_emitter.cc @@ -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, ""}, }; diff --git a/source/blender/simulation/intern/particle_mesh_emitter.cc b/source/blender/simulation/intern/particle_mesh_emitter.cc index c1482a29cb7..26541d550eb 100644 --- a/source/blender/simulation/intern/particle_mesh_emitter.cc +++ b/source/blender/simulation/intern/particle_mesh_emitter.cc @@ -346,6 +346,16 @@ void ParticleMeshEmitter::emit(ParticleEmitterContext &context) const attributes.get("Position").copy_from(new_positions); attributes.get("Velocity").copy_from(new_velocities); attributes.get("Birth Time").copy_from(new_birth_times); + + if (action_ != nullptr) { + ParticleChunkContext particles{ + *context.solve_context.state_map.lookup(name), + IndexRange(amount), + attributes, + nullptr}; + ParticleActionContext action_context{context.solve_context, particles}; + action_->execute(action_context); + } } } diff --git a/source/blender/simulation/intern/particle_mesh_emitter.hh b/source/blender/simulation/intern/particle_mesh_emitter.hh index 601697c9986..724d79c1aec 100644 --- a/source/blender/simulation/intern/particle_mesh_emitter.hh +++ b/source/blender/simulation/intern/particle_mesh_emitter.hh @@ -28,14 +28,17 @@ class ParticleMeshEmitter final : public ParticleEmitter { std::string own_state_name_; Array particle_names_; const fn::MultiFunction &inputs_fn_; + const ParticleAction *action_; public: ParticleMeshEmitter(std::string own_state_name, Array 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) { } diff --git a/source/blender/simulation/intern/simulation_collect_influences.cc b/source/blender/simulation/intern/simulation_collect_influences.cc index c309d18c43a..818415e5d88 100644 --- a/source/blender/simulation/intern/simulation_collect_influences.cc +++ b/source/blender/simulation/intern/simulation_collect_influences.cc @@ -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 dsockets_to_compute) +{ + Vector 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 input_sockets{dnode.inputs().size()}; + Array 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( AT, Span(), 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( - 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 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 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> 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( 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 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 dead_states = context.particles.attributes.get("Dead"); + for (int i : context.particles.index_mask) { + dead_states[i] = true; + } + } +}; + static const ParticleAction *create_particle_action(CollectContext &context, const DOutputSocket &dsocket, Span 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(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 birth_times = context.particles.attributes.get("Birth Time"); - Span has_been_triggered = context.particles.attributes.get(attribute_name_); - const float age = 5.0f; + std::optional> has_been_triggered = context.particles.attributes.try_get( + attribute_name_); + if (!has_been_triggered.has_value()) { + return; + } + + ParticleFunctionEvaluator evaluator{inputs_fn_, context.solve_context, context.particles}; + evaluator.compute(); + VSpan trigger_ages = evaluator.get(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 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; } + 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(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); + 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 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( + 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(attribute_name, 0); + if (added_attribute) { + context.influences.particle_events.add_as(particle_name, &event); + } + } } } diff --git a/source/blender/simulation/intern/simulation_solver.cc b/source/blender/simulation/intern/simulation_solver.cc index 9f2766aa24f..d53ccd2bd49 100644 --- a/source/blender/simulation/intern/simulation_solver.cc +++ b/source/blender/simulation/intern/simulation_solver.cc @@ -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 time_factors(particles.index_mask.min_array_size(), -1.0f); + Array 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); -- cgit v1.2.3