/* * 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_collect_influences.hh" #include "particle_function.hh" #include "particle_mesh_emitter.hh" #include "FN_attributes_ref.hh" #include "FN_multi_function_network_evaluation.hh" #include "FN_multi_function_network_optimization.hh" #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 particle_attributes; Set simulation_time; Set scene_time; }; extern "C" { void WM_clipboard_text_set(const char *buf, bool selection); } static std::string dnode_to_path(const DNode &dnode) { std::string path; for (const DParentNode *parent = dnode.parent(); parent; parent = parent->parent()) { path = parent->node_ref().name() + "/" + path; } path = path + dnode.name(); return path; } struct CollectContext : NonCopyable, NonMovable { SimulationInfluences &influences; RequiredStates &required_states; ResourceCollector &resources; MFNetworkTreeMap &network_map; MFNetwork &network; const DerivedNodeTree &tree; DummyDataSources data_sources; Span particle_simulation_nodes; Map 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 particle_names); static const ParticleAction *create_particle_action(CollectContext &context, const DInputSocket &dsocket, Span 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 nodes_by_type(CollectContext &context, StringRefNull idname) { return context.tree.nodes_by_type(idname); } static Array find_linked_particle_simulations(CollectContext &context, const DOutputSocket &output_socket) { VectorSet 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(MFNetworkTreeMap &network_map, ResourceCollector &resources, Span sockets, MutableSpan r_results) { int amount = sockets.size(); if (amount == 0) { return true; } if (network_map.network().have_dummy_or_unlinked_dependencies(sockets)) { return false; } MFNetworkEvaluator network_fn{{}, sockets}; MFParamsBuilder params{network_fn, 1}; for (int param_index : network_fn.param_indices()) { 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); GMutableSpan span{type, buffer, 1}; r_results[param_index] = span; params.add_uninitialized_single_output(span); } MFContextBuilder context; network_fn.call(IndexRange(1), params, context); return true; } static std::optional> compute_global_string_inputs( MFNetworkTreeMap &network_map, Span sockets) { ResourceCollector local_resources; Array computed_values(sockets.size(), NoInitialization()); if (!compute_global_inputs(network_map, local_resources, sockets, computed_values)) { return {}; } Array strings(sockets.size()); for (int i : sockets.index_range()) { strings[i] = std::move(computed_values[i].typed()[0]); } return strings; } /** * 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) { Span attribute_dnodes = nodes_by_type(context, "SimulationNodeParticleAttribute"); Vector 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> attribute_names = compute_global_string_inputs( context.network_map, name_sockets); if (!attribute_names.has_value()) { return; } MultiValueMap, MFNode *> attribute_nodes_by_name_and_type; for (int i : attribute_names->index_range()) { attribute_nodes_by_name_and_type.add( {(*attribute_names)[i], name_sockets[i]->node().output(0).data_type()}, &name_sockets[i]->node()); } Map attribute_inputs; for (auto item : attribute_nodes_by_name_and_type.items()) { StringRef attribute_name = item.key.first; MFDataType data_type = item.key.second; Span nodes = item.value; MFOutputSocket &new_attribute_socket = context.network.add_input( "Attribute '" + attribute_name + "'", data_type); for (MFNode *node : nodes) { context.network.relink(node->output(0), new_attribute_socket); } context.network.remove(nodes); context.data_sources.particle_attributes.add_new(&new_attribute_socket, attribute_name); } } static void prepare_time_input_nodes(CollectContext &context) { Span time_input_dnodes = nodes_by_type(context, "SimulationNodeTime"); Vector simulation_time_inputs; Vector 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()); 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()); 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 CPPType &attribute_type_; public: ParticleAttributeInput(std::string attribute_name, const CPPType &attribute_type) : attribute_name_(std::move(attribute_name)), attribute_type_(attribute_type) { } void add_input(ParticleFunctionInputContext &context, MFParamsBuilder ¶ms, ResourceCollector &UNUSED(resources)) const override { std::optional 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(GVSpan::FromDefault(attribute_type_)); } } }; class SceneTimeInput : public ParticleFunctionInput { void add_input(ParticleFunctionInputContext &context, MFParamsBuilder ¶ms, ResourceCollector &resources) const override { const float time = DEG_get_ctime(&context.solve_context.depsgraph); float *time_ptr = &resources.construct(AT, time); params.add_readonly_single_input(time_ptr); } }; class SimulationTimeInput : public ParticleFunctionInput { void add_input(ParticleFunctionInputContext &context, MFParamsBuilder ¶ms, ResourceCollector &resources) const override { /* TODO: Vary this per particle. */ const float time = context.solve_context.solve_interval.stop(); float *time_ptr = &resources.construct(AT, time); params.add_readonly_single_input(time_ptr); } }; static const ParticleFunction *create_particle_function_for_inputs( CollectContext &context, Span sockets_to_compute) { BLI_assert(sockets_to_compute.size() >= 1); const MFNetwork &network = sockets_to_compute[0]->node().network(); VectorSet dummy_deps; VectorSet unlinked_input_deps; network.find_dependencies(sockets_to_compute, dummy_deps, unlinked_input_deps); BLI_assert(unlinked_input_deps.size() == 0); Vector per_particle_inputs; 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( AT, *attribute_name, socket->data_type().single_type())); } else if (context.data_sources.scene_time.contains(socket)) { per_particle_inputs.append(&context.resources.construct(AT)); } else if (context.data_sources.simulation_time.contains(socket)) { per_particle_inputs.append(&context.resources.construct(AT)); } } const MultiFunction &per_particle_fn = context.resources.construct( AT, dummy_deps.as_span(), sockets_to_compute); Array output_is_global(sockets_to_compute.size(), false); const ParticleFunction &particle_fn = context.resources.construct( AT, nullptr, &per_particle_fn, Span(), per_particle_inputs.as_span(), output_is_global.as_span()); return &particle_fn; } class ParticleFunctionForce : public ParticleForce { private: const ParticleFunction &particle_fn_; public: ParticleFunctionForce(const ParticleFunction &particle_fn) : particle_fn_(particle_fn) { } void add_force(ParticleForceContext &context) const override { IndexMask mask = context.particles.index_mask; MutableSpan r_combined_force = context.force_dst; ParticleFunctionEvaluator evaluator{particle_fn_, context.solve_context, context.particles}; evaluator.compute(); VSpan forces = evaluator.get(0, "Force"); for (int64_t i : mask) { r_combined_force[i] += forces[i]; } } }; static void create_forces_for_particle_simulation(CollectContext &context, const DNode &simulation_node) { Vector forces; 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 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}); if (particle_fn == nullptr) { continue; } const ParticleForce &force = context.resources.construct(AT, *particle_fn); forces.append(&force); } 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 names = find_linked_particle_simulations(context, dnode.output(0)); if (names.size() == 0) { return nullptr; } Array input_sockets{dnode.inputs().size()}; 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( AT, Span(), input_sockets.as_span()); 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); 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_birth_events(CollectContext &context) { 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 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); 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); 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 SequenceParticleAction : public ParticleAction { private: Vector actions_; public: SequenceParticleAction(Span actions) : actions_(std::move(actions)) { } void execute(ParticleActionContext &context) const override { for (const ParticleAction *action : actions_) { action->execute(context); } } }; class SetParticleAttributeAction : public ParticleAction { private: std::string attribute_name_; const CPPType &cpp_type_; const ParticleFunction &inputs_fn_; 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) { } void execute(ParticleActionContext &context) const override { std::optional attribute_array = context.particles.attributes.try_get( attribute_name_, cpp_type_); if (!attribute_array.has_value()) { return; } ParticleFunctionEvaluator evaluator{inputs_fn_, context.solve_context, context.particles}; evaluator.compute(); GVSpan values = evaluator.get(0); 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); } } }; static const ParticleAction *concatenate_actions(CollectContext &context, Span actions) { Vector non_null_actions; for (const ParticleAction *action : actions) { if (action != nullptr) { non_null_actions.append(action); } } if (non_null_actions.size() == 0) { return nullptr; } if (non_null_actions.size() == 1) { return non_null_actions[0]; } return &context.resources.construct(AT, std::move(non_null_actions)); } static const ParticleAction *create_set_particle_attribute_action( CollectContext &context, const DOutputSocket &dsocket, Span particle_names) { const DNode &dnode = dsocket.node(); 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; } std::string attribute_name = (*names)[0]; 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; } for (StringRef particle_name : particle_names) { context.influences.particle_attributes_builder.lookup_as(particle_name) ->add(attribute_name, attribute_type); } 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}); } 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 conditions = evaluator.get(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 conditions_array = conditions.as_full_array(); Vector true_indices; Vector 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{true_indices.as_span(), context.particles.attributes}; 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}; 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 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}); 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( AT, *inputs_fn, true_action, false_action); } static const ParticleAction *create_particle_action(CollectContext &context, const DOutputSocket &dsocket, Span particle_names) { const DNode &dnode = dsocket.node(); if (dnode.idname() == "SimulationNodeSetParticleAttribute") { return create_set_particle_attribute_action(context, dsocket, particle_names); } if (dnode.idname() == "SimulationNodeExecuteCondition") { return create_particle_condition_action(context, dsocket, particle_names); } return nullptr; } static void initialize_particle_attribute_builders(CollectContext &context) { for (const DNode *dnode : context.particle_simulation_nodes) { StringRef name = get_identifier(context, *dnode); AttributesInfoBuilder &attributes_builder = context.resources.construct( AT); attributes_builder.add("Position", {0, 0, 0}); attributes_builder.add("Velocity", {0, 0, 0}); attributes_builder.add("ID", 0); /* TODO: Use bool property, but need to add CD_PROP_BOOL first. */ attributes_builder.add("Dead", 0); /* TODO: Use uint32_t, but we don't have a corresponding custom property type. */ attributes_builder.add("Hash", 0); attributes_builder.add("Birth Time", 0.0f); context.influences.particle_attributes_builder.add_new(name, &attributes_builder); } } 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); } void collect_simulation_influences(Simulation &simulation, ResourceCollector &resources, SimulationInfluences &r_influences, RequiredStates &r_required_states) { NodeTreeRefMap tree_refs; const DerivedNodeTree tree{simulation.nodetree, tree_refs}; MFNetwork &network = resources.construct(AT); MFNetworkTreeMap network_map = insert_node_tree_into_mf_network(network, tree, resources); CollectContext context{r_influences, r_required_states, resources, network_map}; initialize_particle_attribute_builders(context); prepare_particle_attribute_nodes(context); prepare_time_input_nodes(context); collect_forces(context); collect_emitters(context); collect_birth_events(context); collect_time_step_events(context); optimize_function_network(context); for (const DNode *dnode : context.particle_simulation_nodes) { r_required_states.add(get_identifier(context, *dnode), SIM_TYPE_NAME_PARTICLE_SIMULATION); } } } // namespace blender::sim