diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index a3eacf8fe4d..14c1b9d2a94 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -869,6 +869,7 @@ const bTheme U_theme_default = { .nodeclass_layout = RGBA(0x6c696fff), .nodeclass_geometry = RGBA(0x00d6a3ff), .nodeclass_attribute = RGBA(0x001566ff), + .node_zone_simulation = RGBA(0x66416233), .movie = RGBA(0x0f0f0fcc), .gp_vertex_size = 3, .gp_vertex = RGBA(0x97979700), diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index fcc44f48b30..c942f7b9fb2 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -5,6 +5,10 @@ from bpy.types import Operator from bpy.app.translations import pgettext_data as data_ +from bpy.props import ( + EnumProperty, +) + def build_default_empty_geometry_node_group(name): group = bpy.data.node_groups.new(name, 'GeometryNodeTree') @@ -242,8 +246,102 @@ class NewGeometryNodeTreeAssign(Operator): return {'FINISHED'} +class SimulationZoneOperator: + input_node_type = 'GeometryNodeSimulationInput' + output_node_type = 'GeometryNodeSimulationOutput' + + @classmethod + def get_output_node(cls, context): + node = context.active_node + if node.bl_idname == cls.input_node_type: + return node.paired_output + if node.bl_idname == cls.output_node_type: + return node + + @classmethod + def poll(cls, context): + space = context.space_data + # Needs active node editor and a tree. + if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library: + return False + node = context.active_node + if node is None or node.bl_idname not in [cls.input_node_type, cls.output_node_type]: + return False + if cls.get_output_node(context) is None: + return False + return True + + +class SimulationZoneItemAddOperator(SimulationZoneOperator, Operator): + """Add a state item to the simulation zone""" + bl_idname = "node.simulation_zone_item_add" + bl_label = "Add State Item" + bl_options = {'REGISTER', 'UNDO'} + + default_socket_type = 'GEOMETRY' + + def execute(self, context): + node = self.get_output_node(context) + state_items = node.state_items + + # Remember index to move the item. + dst_index = min(node.active_index + 1, len(state_items)) + # Empty name so it is based on the type only. + state_items.new(self.default_socket_type, "") + state_items.move(len(state_items) - 1, dst_index) + node.active_index = dst_index + + return {'FINISHED'} + + +class SimulationZoneItemRemoveOperator(SimulationZoneOperator, Operator): + """Remove a state item from the simulation zone""" + bl_idname = "node.simulation_zone_item_remove" + bl_label = "Remove State Item" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + node = self.get_output_node(context) + state_items = node.state_items + + if node.active_item: + state_items.remove(node.active_item) + node.active_index = min(node.active_index, len(state_items) - 1) + + return {'FINISHED'} + + +class SimulationZoneItemMoveOperator(SimulationZoneOperator, Operator): + """Move a simulation state item up or down in the list""" + bl_idname = "node.simulation_zone_item_move" + bl_label = "Move State Item" + bl_options = {'REGISTER', 'UNDO'} + + direction: EnumProperty( + name="Direction", + items=[('UP', "Up", ""), ('DOWN', "Down", "")], + default='UP', + ) + + def execute(self, context): + node = self.get_output_node(context) + state_items = node.state_items + + if self.direction == 'UP' and node.active_index > 0: + state_items.move(node.active_index, node.active_index - 1) + node.active_index = node.active_index - 1 + elif self.direction == 'DOWN' and node.active_index < len(state_items) - 1: + state_items.move(node.active_index, node.active_index + 1) + node.active_index = node.active_index + 1 + + return {'FINISHED'} + + classes = ( NewGeometryNodesModifier, NewGeometryNodeTreeAssign, MoveModifierToNodes, + SimulationZoneItemAddOperator, + SimulationZoneItemRemoveOperator, + SimulationZoneItemMoveOperator, ) diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index c57f482146f..854db8f0ce7 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -9,8 +9,12 @@ from bpy.types import ( from bpy.props import ( BoolProperty, CollectionProperty, + FloatVectorProperty, StringProperty, ) +from mathutils import ( + Vector, +) from bpy.app.translations import pgettext_tip as tip_ @@ -27,10 +31,6 @@ class NodeSetting(PropertyGroup): # Base class for node "Add" operators. class NodeAddOperator: - type: StringProperty( - name="Node Type", - description="Node type", - ) use_transform: BoolProperty( name="Use Transform", description="Start transform operator after inserting the node", @@ -56,21 +56,18 @@ class NodeAddOperator: else: space.cursor_location = tree.view_center - # XXX explicit node_type argument is usually not necessary, - # but required to make search operator work: - # add_search has to override the 'type' property - # since it's hardcoded in bpy_operator_wrap.c ... - def create_node(self, context, node_type=None): + # Deselect all nodes in the tree. + @staticmethod + def deselect_nodes(context): space = context.space_data tree = space.edit_tree - - if node_type is None: - node_type = self.type - - # select only the new node for n in tree.nodes: n.select = False + def create_node(self, context, node_type): + space = context.space_data + tree = space.edit_tree + try: node = tree.nodes.new(type=node_type) except RuntimeError as e: @@ -109,14 +106,6 @@ class NodeAddOperator: return (space and (space.type == 'NODE_EDITOR') and space.edit_tree and not space.edit_tree.library) - # Default execute simply adds a node - def execute(self, context): - if self.properties.is_property_set("type"): - self.create_node(context) - return {'FINISHED'} - else: - return {'CANCELLED'} - # Default invoke stores the mouse position to place the node correctly # and optionally invokes the transform operator def invoke(self, context, event): @@ -129,6 +118,28 @@ class NodeAddOperator: return result + +# Simple basic operator for adding a node. +class NODE_OT_add_node(NodeAddOperator, Operator): + """Add a node to the active tree""" + bl_idname = "node.add_node" + bl_label = "Add Node" + bl_options = {'REGISTER', 'UNDO'} + + type: StringProperty( + name="Node Type", + description="Node type", + ) + + # Default execute simply adds a node. + def execute(self, context): + if self.properties.is_property_set("type"): + self.deselect_nodes(context) + self.create_node(context, self.type) + return {'FINISHED'} + else: + return {'CANCELLED'} + @classmethod def description(cls, _context, properties): nodetype = properties["type"] @@ -139,13 +150,46 @@ class NodeAddOperator: return "" -# Simple basic operator for adding a node -class NODE_OT_add_node(NodeAddOperator, Operator): - """Add a node to the active tree""" - bl_idname = "node.add_node" - bl_label = "Add Node" +class NODE_OT_add_simulation_zone(NodeAddOperator, Operator): + """Add simulation zone input and output nodes to the active tree""" + bl_idname = "node.add_simulation_zone" + bl_label = "Add Simulation Zone" bl_options = {'REGISTER', 'UNDO'} + input_node_type = "GeometryNodeSimulationInput" + output_node_type = "GeometryNodeSimulationOutput" + offset: FloatVectorProperty( + name="Offset", + description="Offset of nodes from the cursor when added", + size=2, + default=(150, 0), + ) + + def execute(self, context): + space = context.space_data + tree = space.edit_tree + + props = self.properties + + self.deselect_nodes(context) + input_node = self.create_node(context, self.input_node_type) + output_node = self.create_node(context, self.output_node_type) + if input_node is None or output_node is None: + return {'CANCELLED'} + + # Simulation input must be paired with the output. + input_node.pair_with_output(output_node) + + input_node.location -= Vector(self.offset) + output_node.location += Vector(self.offset) + + # Connect geometry sockets by default. + from_socket = input_node.outputs.get("Geometry") + to_socket = output_node.inputs.get("Geometry") + tree.links.new(to_socket, from_socket) + + return {'FINISHED'} + class NODE_OT_collapse_hide_unused_toggle(Operator): """Toggle collapsed nodes and hide unused sockets""" @@ -202,6 +246,7 @@ classes = ( NodeSetting, NODE_OT_add_node, + NODE_OT_add_simulation_zone, NODE_OT_collapse_hide_unused_toggle, NODE_OT_tree_path_parent, ) diff --git a/scripts/startup/bl_ui/__init__.py b/scripts/startup/bl_ui/__init__.py index 7d20962ae4e..93500c4779c 100644 --- a/scripts/startup/bl_ui/__init__.py +++ b/scripts/startup/bl_ui/__init__.py @@ -41,6 +41,7 @@ _modules = [ "properties_physics_common", "properties_physics_dynamicpaint", "properties_physics_field", + "properties_physics_geometry_nodes", "properties_physics_rigidbody", "properties_physics_rigidbody_constraint", "properties_physics_fluid", diff --git a/scripts/startup/bl_ui/node_add_menu.py b/scripts/startup/bl_ui/node_add_menu.py index bbf71dcd5fd..d6aa41cd531 100644 --- a/scripts/startup/bl_ui/node_add_menu.py +++ b/scripts/startup/bl_ui/node_add_menu.py @@ -57,6 +57,18 @@ def draw_root_assets(layout): layout.menu_contents("NODE_MT_node_add_root_catalogs") +def add_simulation_zone(layout, label): + """Add simulation zone to a menu.""" + target_bl_rna = bpy.types.Node.bl_rna_get_subclass("GeometryNodeSimulationOutput") + if target_bl_rna: + translation_context = target_bl_rna.translation_context + else: + translation_context = i18n_contexts.default + props = layout.operator("node.add_simulation_zone", text=label, text_ctxt=translation_context) + props.use_transform = True + return props + + classes = ( ) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index d673f6941b2..2544c1d5f96 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -464,6 +464,16 @@ class NODE_MT_category_GEO_POINT(Menu): node_add_menu.draw_assets_for_catalog(layout, self.bl_label) +class NODE_MT_category_simulation(Menu): + bl_idname = "NODE_MT_category_simulation" + bl_label = "Simulation" + + def draw(self, _context): + layout = self.layout + node_add_menu.add_simulation_zone(layout, label="Simulation Zone") + node_add_menu.draw_assets_for_catalog(layout, self.bl_label) + + class NODE_MT_category_GEO_TEXT(Menu): bl_idname = "NODE_MT_category_GEO_TEXT" bl_label = "Text" @@ -646,6 +656,8 @@ class NODE_MT_geometry_node_add_all(Menu): layout.menu("NODE_MT_category_GEO_POINT") layout.menu("NODE_MT_category_GEO_VOLUME") layout.separator() + layout.menu("NODE_MT_category_simulation") + layout.separator() layout.menu("NODE_MT_geometry_node_GEO_MATERIAL") layout.menu("NODE_MT_category_GEO_TEXTURE") layout.menu("NODE_MT_category_GEO_UTILITIES") @@ -685,6 +697,7 @@ classes = ( NODE_MT_category_PRIMITIVES_MESH, NODE_MT_geometry_node_mesh_topology, NODE_MT_category_GEO_POINT, + NODE_MT_category_simulation, NODE_MT_category_GEO_VOLUME, NODE_MT_geometry_node_GEO_MATERIAL, NODE_MT_category_GEO_TEXTURE, diff --git a/scripts/startup/bl_ui/properties_physics_geometry_nodes.py b/scripts/startup/bl_ui/properties_physics_geometry_nodes.py new file mode 100644 index 00000000000..c51fba5f035 --- /dev/null +++ b/scripts/startup/bl_ui/properties_physics_geometry_nodes.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +from bpy.types import ( + Panel, +) +from bpy.app.translations import pgettext_iface as iface_ + + +class PHYSICS_PT_geometry_nodes(Panel): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "physics" + bl_label = "Simulation Nodes" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def geometry_nodes_objects(cls, context): + for ob in context.selected_editable_objects: + if any([modifier.type == 'NODES' for modifier in ob.modifiers]): + yield ob + + @classmethod + def poll(cls, context): + return any(cls.geometry_nodes_objects(context)) + + def draw(self, context): + layout = self.layout + + if len(context.selected_editable_objects) > 1: + bake_text = iface_("Bake Selected") + else: + bake_text = iface_("Bake") + + row = layout.row(align=True) + row.operator("object.simulation_nodes_cache_bake", text=bake_text).selected = True + row.operator("object.simulation_nodes_cache_delete", text="", icon='TRASH').selected = True + + +classes = ( + PHYSICS_PT_geometry_nodes, +) + + +if __name__ == "__main__": # only for live edit. + from bpy.utils import register_class + for cls in classes: + register_class(cls) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index e16c6e1e77c..0c94be84012 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -955,6 +955,85 @@ class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel): self.draw_socket_list(context, "OUT", "outputs", "active_output") +class NODE_UL_simulation_zone_items(bpy.types.UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, _active_propname, _index): + if self.layout_type in {'DEFAULT', 'COMPACT'}: + row = layout.row(align=True) + + row.template_node_socket(color=item.color) + row.prop(item, "name", text="", emboss=False, icon_value=icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.template_node_socket(color=item.color) + + +class NODE_PT_simulation_zone_items(Panel): + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_category = "Node" + bl_label = "Simulation State" + + input_node_type = 'GeometryNodeSimulationInput' + output_node_type = 'GeometryNodeSimulationOutput' + + @classmethod + def get_output_node(cls, context): + node = context.active_node + if node.bl_idname == cls.input_node_type: + return node.paired_output + if node.bl_idname == cls.output_node_type: + return node + + @classmethod + def poll(cls, context): + snode = context.space_data + if snode is None: + return False + node = context.active_node + if node is None or node.bl_idname not in [cls.input_node_type, cls.output_node_type]: + return False + if cls.get_output_node(context) is None: + return False + return True + + def draw(self, context): + layout = self.layout + + output_node = self.get_output_node(context) + + split = layout.row() + + split.template_list( + "NODE_UL_simulation_zone_items", + "", + output_node, + "state_items", + output_node, + "active_index") + + ops_col = split.column() + + add_remove_col = ops_col.column(align=True) + add_remove_col.operator("node.simulation_zone_item_add", icon='ADD', text="") + add_remove_col.operator("node.simulation_zone_item_remove", icon='REMOVE', text="") + + ops_col.separator() + + up_down_col = ops_col.column(align=True) + props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_UP', text="") + props.direction = 'UP' + props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_DOWN', text="") + props.direction = 'DOWN' + + active_item = output_node.active_item + if active_item is not None: + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(active_item, "socket_type") + if active_item.socket_type in {'VECTOR', 'INT', 'BOOLEAN', 'FLOAT', 'RGBA'}: + layout.prop(active_item, "attribute_domain") + + # Grease Pencil properties class NODE_PT_annotation(AnnotationDataPanel, Panel): bl_space_type = 'NODE_EDITOR' @@ -1019,6 +1098,8 @@ classes = ( NODE_UL_interface_sockets, NODE_PT_node_tree_interface_inputs, NODE_PT_node_tree_interface_outputs, + NODE_UL_simulation_zone_items, + NODE_PT_simulation_zone_items, node_panel(EEVEE_MATERIAL_PT_settings), node_panel(MATERIAL_PT_viewport), diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 02e2c2b484d..d66fd20f8ed 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -191,6 +191,11 @@ struct GeometrySet { * access to their data, which might be freed later if this geometry set outlasts the data. */ void ensure_owns_direct_data(); + /** + * Same as #ensure_owns_direct_data but also turns object/collection instances into geometry + * instances so that they can be owned. + */ + void ensure_owns_all_data(); using AttributeForeachCallback = blender::FunctionRef; + class bNodeTreeRuntime : NonCopyable, NonMovable { public: /** @@ -89,7 +94,7 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { * allow simpler and more cache friendly iteration. Supports lookup by integer or by node. * Unlike other caches, this is maintained eagerly while changing the tree. */ - VectorSet nodes_by_id; + NodeIDVectorSet nodes_by_id; /** Execution data. * @@ -136,6 +141,9 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { */ mutable std::atomic allow_use_dirty_topology_cache = 0; + CacheMutex tree_zones_cache_mutex; + std::unique_ptr tree_zones; + /** Only valid when #topology_cache_is_dirty is false. */ Vector links; Vector sockets; @@ -361,12 +369,14 @@ inline blender::Span bNodeTree::all_nodes() inline bNode *bNodeTree::node_by_id(const int32_t identifier) { + BLI_assert(identifier >= 0); bNode *const *node = this->runtime->nodes_by_id.lookup_key_ptr_as(identifier); return node ? *node : nullptr; } inline const bNode *bNodeTree::node_by_id(const int32_t identifier) const { + BLI_assert(identifier >= 0); const bNode *const *node = this->runtime->nodes_by_id.lookup_key_ptr_as(identifier); return node ? *node : nullptr; } diff --git a/source/blender/blenkernel/BKE_node_tree_zones.hh b/source/blender/blenkernel/BKE_node_tree_zones.hh new file mode 100644 index 00000000000..c9df7be99ed --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_zones.hh @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include "DNA_node_types.h" + +#include "BLI_vector.hh" + +namespace blender::bke::node_tree_zones { + +struct TreeZone { + TreeZones *owner = nullptr; + /** Index of the zone in the array of all zones in a node tree. */ + int index = -1; + /** Zero for top level zones, one for a nested zone, and so on. */ + int depth = -1; + /** Input node of the zone. */ + const bNode *input_node = nullptr; + /** Output node of the zone. */ + const bNode *output_node = nullptr; + /** Direct parent of the zone. If this is null, this is a top level zone. */ + TreeZone *parent_zone = nullptr; + /** Direct children zones. Does not contain recursively nested zones. */ + Vector child_zones; + /** Direct children nodes. Does not contain recursively nested nodes. */ + Vector child_nodes; + + bool contains_node_recursively(const bNode &node) const; +}; + +class TreeZones { + public: + Vector> zones; + Map parent_zone_by_node_id; +}; + +const TreeZones *get_tree_zones(const bNodeTree &tree); + +} // namespace blender::bke::node_tree_zones diff --git a/source/blender/blenkernel/BKE_simulation_state.hh b/source/blender/blenkernel/BKE_simulation_state.hh new file mode 100644 index 00000000000..db059764abc --- /dev/null +++ b/source/blender/blenkernel/BKE_simulation_state.hh @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_geometry_set.hh" + +#include "BLI_map.hh" +#include "BLI_sub_frame.hh" + +namespace blender::bke::sim { + +class BDataSharing; +class ModifierSimulationCache; + +class SimulationStateItem { + public: + virtual ~SimulationStateItem() = default; +}; + +class GeometrySimulationStateItem : public SimulationStateItem { + private: + GeometrySet geometry_; + + public: + GeometrySimulationStateItem(GeometrySet geometry); + + const GeometrySet &geometry() const + { + return geometry_; + } + + GeometrySet &geometry() + { + return geometry_; + } +}; + +class AttributeSimulationStateItem : public SimulationStateItem { + private: + std::string name_; + + public: + AttributeSimulationStateItem(std::string name) : name_(std::move(name)) {} + + StringRefNull name() const + { + return name_; + } +}; + +class PrimitiveSimulationStateItem : public SimulationStateItem { + private: + const CPPType &type_; + void *value_; + + public: + PrimitiveSimulationStateItem(const CPPType &type, const void *value); + ~PrimitiveSimulationStateItem(); + + const void *value() const + { + return value_; + } + + const CPPType &type() const + { + return type_; + } +}; + +class StringSimulationStateItem : public SimulationStateItem { + private: + std::string value_; + + public: + StringSimulationStateItem(std::string value); + + StringRefNull value() const + { + return value_; + } +}; + +class SimulationZoneState { + public: + Map> item_by_identifier; +}; + +struct SimulationZoneID { + Vector node_ids; + + uint64_t hash() const + { + return get_default_hash(this->node_ids); + } + + friend bool operator==(const SimulationZoneID &a, const SimulationZoneID &b) + { + return a.node_ids == b.node_ids; + } +}; + +class ModifierSimulationState { + private: + mutable bool bake_loaded_; + + public: + ModifierSimulationCache *owner_; + mutable std::mutex mutex_; + Map> zone_states_; + std::optional meta_path_; + std::optional bdata_dir_; + + const SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id) const; + SimulationZoneState &get_zone_state_for_write(const SimulationZoneID &zone_id); + void ensure_bake_loaded() const; +}; + +struct ModifierSimulationStateAtFrame { + SubFrame frame; + ModifierSimulationState state; +}; + +enum class CacheState { + Valid, + Invalid, + Baked, +}; + +struct StatesAroundFrame { + const ModifierSimulationStateAtFrame *prev = nullptr; + const ModifierSimulationStateAtFrame *current = nullptr; + const ModifierSimulationStateAtFrame *next = nullptr; +}; + +class ModifierSimulationCache { + private: + Vector> states_at_frames_; + std::unique_ptr bdata_sharing_; + + friend ModifierSimulationState; + + public: + CacheState cache_state_ = CacheState::Valid; + bool failed_finding_bake_ = false; + + void try_discover_bake(StringRefNull meta_dir, StringRefNull bdata_dir); + + bool has_state_at_frame(const SubFrame &frame) const; + bool has_states() const; + const ModifierSimulationState *get_state_at_exact_frame(const SubFrame &frame) const; + ModifierSimulationState &get_state_at_frame_for_write(const SubFrame &frame); + StatesAroundFrame get_states_around_frame(const SubFrame &frame) const; + + void invalidate() + { + cache_state_ = CacheState::Invalid; + } + + CacheState cache_state() const + { + return cache_state_; + } + + void reset(); +}; + +} // namespace blender::bke::sim diff --git a/source/blender/blenkernel/BKE_simulation_state_serialize.hh b/source/blender/blenkernel/BKE_simulation_state_serialize.hh new file mode 100644 index 00000000000..d9cfc773514 --- /dev/null +++ b/source/blender/blenkernel/BKE_simulation_state_serialize.hh @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_simulation_state.hh" + +#include "BLI_serialize.hh" + +struct Main; +struct ModifierData; + +namespace blender { +class fstream; +} + +namespace blender::bke::sim { + +using DictionaryValue = io::serialize::DictionaryValue; +using DictionaryValuePtr = std::shared_ptr; + +/** + * Reference to a slice of memory typically stored on disk. + */ +struct BDataSlice { + std::string name; + IndexRange range; + + DictionaryValuePtr serialize() const; + static std::optional deserialize(const io::serialize::DictionaryValue &io_slice); +}; + +/** + * Abstract base class for loading binary data. + */ +class BDataReader { + public: + /** + * Read the data from the given slice into the provided memory buffer. + * \return True on success, otherwise false. + */ + [[nodiscard]] virtual bool read(const BDataSlice &slice, void *r_data) const = 0; +}; + +/** + * Abstract base class for writing binary data. + */ +class BDataWriter { + public: + /** + * Write the provided binary data. + * \return Slice where the data has been written to. + */ + virtual BDataSlice write(const void *data, int64_t size) = 0; +}; + +/** + * Allows for simple data deduplication when writing or reading data by making use of implicit + * sharing. + */ +class BDataSharing { + private: + struct StoredByRuntimeValue { + /** + * Version of the shared data that was written before. This is needed because the data might + * be changed later without changing the #ImplicitSharingInfo pointer. + */ + int64_t sharing_info_version; + /** + * Identifier of the stored data. This includes information for where the data is stored (a + * #BDataSlice) and optionally information for how it is loaded (e.g. endian information). + */ + DictionaryValuePtr io_data; + }; + + /** + * Map used to detect when some data has already been written. It keeps a weak reference to + * #ImplicitSharingInfo, allowing it to check for equality of two arrays just by comparing the + * sharing info's pointer and version. + */ + Map stored_by_runtime_; + + /** + * Use a mutex so that #read_shared can be implemented in a thread-safe way. + */ + mutable std::mutex mutex_; + /** + * Map used to detect when some data has been previously loaded. This keeps strong + * references to #ImplicitSharingInfo. + */ + mutable Map runtime_by_stored_; + + public: + ~BDataSharing(); + + /** + * Check if the data referenced by `sharing_info` has been written before. If yes, return the + * identifier for the previously written data. Otherwise, write the data now and store the + * identifier for later use. + * \return Identifier that indicates from where the data has been written. + */ + [[nodiscard]] DictionaryValuePtr write_shared(const ImplicitSharingInfo *sharing_info, + FunctionRef write_fn); + + /** + * Check if the data identified by `io_data` has been read before or load it now. + * \return Shared ownership to the read data, or none if there was an error. + */ + [[nodiscard]] std::optional read_shared( + const DictionaryValue &io_data, + FunctionRef()> read_fn) const; +}; + +/** + * A specific #BDataReader that reads from disk. + */ +class DiskBDataReader : public BDataReader { + private: + const std::string bdata_dir_; + mutable std::mutex mutex_; + mutable Map> open_input_streams_; + + public: + DiskBDataReader(std::string bdata_dir); + [[nodiscard]] bool read(const BDataSlice &slice, void *r_data) const override; +}; + +/** + * A specific #BDataWriter that writes to a file on disk. + */ +class DiskBDataWriter : public BDataWriter { + private: + /** Name of the file that data is written to. */ + std::string bdata_name_; + /** File handle. */ + std::ostream &bdata_file_; + /** Current position in the file. */ + int64_t current_offset_; + + public: + DiskBDataWriter(std::string bdata_name, std::ostream &bdata_file, int64_t current_offset); + + BDataSlice write(const void *data, int64_t size) override; +}; + +/** + * Get the directory that contains all baked simulation data for the given modifier. This is a + * parent directory of the two directories below. + */ +std::string get_bake_directory(const Main &bmain, const Object &object, const ModifierData &md); +std::string get_bdata_directory(const Main &bmain, const Object &object, const ModifierData &md); +std::string get_meta_directory(const Main &bmain, const Object &object, const ModifierData &md); + +/** + * Encode the simulation state in a #DictionaryValue which also contains references to external + * binary data that has been written using #bdata_writer. + */ +void serialize_modifier_simulation_state(const ModifierSimulationState &state, + BDataWriter &bdata_writer, + BDataSharing &bdata_sharing, + DictionaryValue &r_io_root); +/** + * Fill the simulation state by parsing the provided #DictionaryValue which also contains + * references to external binary data that is read using #bdata_reader. + */ +void deserialize_modifier_simulation_state(const DictionaryValue &io_root, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing, + ModifierSimulationState &r_state); + +} // namespace blender::bke::sim diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 66562ef0702..c8b26220e91 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -232,6 +232,7 @@ set(SRC intern/node_tree_anonymous_attributes.cc intern/node_tree_field_inferencing.cc intern/node_tree_update.cc + intern/node_tree_zones.cc intern/object.cc intern/object_deform.c intern/object_dupli.cc @@ -265,6 +266,8 @@ set(SRC intern/shader_fx.c intern/shrinkwrap.cc intern/simulation.cc + intern/simulation_state.cc + intern/simulation_state_serialize.cc intern/softbody.c intern/sound.c intern/speaker.c @@ -442,6 +445,7 @@ set(SRC BKE_node.h BKE_node_runtime.hh BKE_node_tree_update.h + BKE_node_tree_zones.hh BKE_object.h BKE_object_deform.h BKE_object_facemap.h @@ -464,6 +468,8 @@ set(SRC BKE_shader_fx.h BKE_shrinkwrap.h BKE_simulation.h + BKE_simulation_state.hh + BKE_simulation_state_serialize.hh BKE_softbody.h BKE_sound.h BKE_speaker.h diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 9731223c76f..9f068d84723 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -281,6 +281,14 @@ void GeometrySet::ensure_owns_direct_data() } } +void GeometrySet::ensure_owns_all_data() +{ + if (Instances *instances = this->get_instances_for_write()) { + instances->ensure_geometry_instances(); + } + this->ensure_owns_direct_data(); +} + bool GeometrySet::owns_direct_data() const { for (const GeometryComponentPtr &component_ptr : components_) { diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 180b535b77b..5582ddc45e0 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -65,6 +65,7 @@ #include "BKE_node.h" #include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" +#include "BKE_node_tree_zones.hh" #include "BKE_type_conversions.hh" #include "RNA_access.h" @@ -175,6 +176,10 @@ static void ntree_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, cons } } + for (bNode *node : ntree_dst->all_nodes()) { + nodeDeclarationEnsure(ntree_dst, node); + } + /* copy interface sockets */ BLI_listbase_clear(&ntree_dst->inputs); LISTBASE_FOREACH (const bNodeSocket *, src_socket, &ntree_src->inputs) { @@ -609,6 +614,14 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) BLO_write_struct(writer, NodeImageLayer, sock->storage); } } + if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + const NodeGeometrySimulationOutput &storage = + *static_cast(node->storage); + BLO_write_struct_array(writer, NodeSimulationItem, storage.items_num, storage.items); + for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { + BLO_write_string(writer, item.name); + } + } } LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { @@ -784,6 +797,16 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) BLO_read_data_address(reader, &storage->string); break; } + case GEO_NODE_SIMULATION_OUTPUT: { + NodeGeometrySimulationOutput &storage = *static_cast( + node->storage); + BLO_read_data_address(reader, &storage.items); + for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { + BLO_read_data_address(reader, &item.name); + } + break; + } + default: break; } @@ -1139,7 +1162,12 @@ static void node_init(const bContext *C, bNodeTree *ntree, bNode *node) BLI_strncpy(node->name, DATA_(ntype->ui_name), NODE_MAXSTR); nodeUniqueName(ntree, node); - node_add_sockets_from_type(ntree, node, ntype); + /* Generally sockets should be added after the initialization, because the set of sockets might + * depend on node properties. */ + const bool add_sockets_before_init = node->type == CMP_NODE_R_LAYERS; + if (add_sockets_before_init) { + node_add_sockets_from_type(ntree, node, ntype); + } if (ntype->initfunc != nullptr) { ntype->initfunc(ntree, node); @@ -1149,6 +1177,10 @@ static void node_init(const bContext *C, bNodeTree *ntree, bNode *node) ntree->typeinfo->node_add_init(ntree, node); } + if (!add_sockets_before_init) { + node_add_sockets_from_type(ntree, node, ntype); + } + if (node->id) { id_us_plus(node->id); } @@ -2300,7 +2332,8 @@ bNode *nodeAddNode(const bContext *C, bNodeTree *ntree, const char *idname) BKE_ntree_update_tag_node_new(ntree, node); - if (ELEM(node->type, GEO_NODE_INPUT_SCENE_TIME, GEO_NODE_SELF_OBJECT)) { + if (ELEM(node->type, GEO_NODE_INPUT_SCENE_TIME, GEO_NODE_SELF_OBJECT, GEO_NODE_SIMULATION_INPUT)) + { DEG_relations_tag_update(CTX_data_main(C)); } @@ -2424,11 +2457,6 @@ bNode *node_copy_with_mapping(bNodeTree *dst_tree, node_dst->typeinfo->copyfunc_api(&ptr, &node_src); } - /* Reset the declaration of the new node in real tree. */ - if (dst_tree != nullptr) { - nodeDeclarationEnsure(dst_tree, node_dst); - } - return node_dst; } @@ -3265,7 +3293,9 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, const bool do_id /* Also update relations for the scene time node, which causes a dependency * on time that users expect to be removed when the node is removed. */ - if (node_has_id || ELEM(node->type, GEO_NODE_INPUT_SCENE_TIME, GEO_NODE_SELF_OBJECT)) { + if (node_has_id || + ELEM(node->type, GEO_NODE_INPUT_SCENE_TIME, GEO_NODE_SELF_OBJECT, GEO_NODE_SIMULATION_INPUT)) + { if (bmain != nullptr) { DEG_relations_tag_update(bmain); } @@ -3826,8 +3856,7 @@ bool nodeDeclarationEnsureOnOutdatedNode(bNodeTree *ntree, bNode *node) if (node->typeinfo->declare_dynamic) { BLI_assert(ntree != nullptr); BLI_assert(node != nullptr); - node->runtime->declaration = new blender::nodes::NodeDeclaration(); - blender::nodes::build_node_declaration_dynamic(*ntree, *node, *node->runtime->declaration); + blender::nodes::update_node_declaration_and_sockets(*ntree, *node); return true; } if (node->typeinfo->declare) { diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index b496348db73..530cc3c07e7 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -255,7 +255,37 @@ struct ToposortNodeState { bool is_in_stack = false; }; -static void toposort_from_start_node(const ToposortDirection direction, +static Vector get_implicit_origin_nodes(const bNodeTree &ntree, bNode &node) +{ + Vector origin_nodes; + if (node.type == GEO_NODE_SIMULATION_OUTPUT) { + for (const bNode *sim_input_node : + ntree.runtime->nodes_by_type.lookup(nodeTypeFind("GeometryNodeSimulationInput"))) + { + const auto &storage = *static_cast( + sim_input_node->storage); + if (storage.output_node_id == node.identifier) { + origin_nodes.append(sim_input_node); + } + } + } + return origin_nodes; +} + +static Vector get_implicit_target_nodes(const bNodeTree &ntree, bNode &node) +{ + Vector target_nodes; + if (node.type == GEO_NODE_SIMULATION_INPUT) { + const auto &storage = *static_cast(node.storage); + if (const bNode *sim_output_node = ntree.node_by_id(storage.output_node_id)) { + target_nodes.append(sim_output_node); + } + } + return target_nodes; +} + +static void toposort_from_start_node(const bNodeTree &ntree, + const ToposortDirection direction, bNode &start_node, MutableSpan node_states, Vector &r_sorted_nodes, @@ -265,6 +295,7 @@ static void toposort_from_start_node(const ToposortDirection direction, bNode *node; int socket_index = 0; int link_index = 0; + int implicit_link_index = 0; }; Stack nodes_to_check; @@ -273,6 +304,25 @@ static void toposort_from_start_node(const ToposortDirection direction, while (!nodes_to_check.is_empty()) { Item &item = nodes_to_check.peek(); bNode &node = *item.node; + bool pushed_node = false; + + auto handle_linked_node = [&](bNode &linked_node) { + ToposortNodeState &linked_node_state = node_states[linked_node.index()]; + if (linked_node_state.is_done) { + /* The linked node has already been visited. */ + return true; + } + if (linked_node_state.is_in_stack) { + r_cycle_detected = true; + } + else { + nodes_to_check.push({&linked_node}); + linked_node_state.is_in_stack = true; + pushed_node = true; + } + return false; + }; + const Span sockets = (direction == ToposortDirection::LeftToRight) ? node.runtime->inputs : node.runtime->outputs; @@ -297,24 +347,38 @@ static void toposort_from_start_node(const ToposortDirection direction, } bNodeSocket &linked_socket = *socket.runtime->directly_linked_sockets[item.link_index]; bNode &linked_node = *linked_socket.runtime->owner_node; - ToposortNodeState &linked_node_state = node_states[linked_node.index()]; - if (linked_node_state.is_done) { + if (handle_linked_node(linked_node)) { /* The linked node has already been visited. */ item.link_index++; continue; } - if (linked_node_state.is_in_stack) { - r_cycle_detected = true; - } - else { - nodes_to_check.push({&linked_node}); - linked_node_state.is_in_stack = true; - } break; } - /* If no other element has been pushed, the current node can be pushed to the sorted list. */ - if (&item == &nodes_to_check.peek()) { + if (!pushed_node) { + /* Some nodes are internally linked without an explicit `bNodeLink`. The toposort should + * still order them correctly and find cycles. */ + const Vector implicitly_linked_nodes = + (direction == ToposortDirection::LeftToRight) ? get_implicit_origin_nodes(ntree, node) : + get_implicit_target_nodes(ntree, node); + while (true) { + if (item.implicit_link_index == implicitly_linked_nodes.size()) { + /* All implicitly linked nodes have already been visited. */ + break; + } + const bNode &linked_node = *implicitly_linked_nodes[item.implicit_link_index]; + if (handle_linked_node(const_cast(linked_node))) { + /* The implicitly linked node has already been visited. */ + item.implicit_link_index++; + continue; + } + break; + } + } + + /* If no other element has been pushed, the current node can be pushed to the sorted list. + */ + if (!pushed_node) { ToposortNodeState &node_state = node_states[node.index()]; node_state.is_done = true; node_state.is_in_stack = false; @@ -347,7 +411,8 @@ static void update_toposort(const bNodeTree &ntree, /* Ignore non-start nodes. */ continue; } - toposort_from_start_node(direction, *node, node_states, r_sorted_nodes, r_cycle_detected); + toposort_from_start_node( + ntree, direction, *node, node_states, r_sorted_nodes, r_cycle_detected); } if (r_sorted_nodes.size() < tree_runtime.nodes_by_id.size()) { @@ -358,7 +423,8 @@ static void update_toposort(const bNodeTree &ntree, continue; } /* Start toposort at this node which is somewhere in the middle of a loop. */ - toposort_from_start_node(direction, *node, node_states, r_sorted_nodes, r_cycle_detected); + toposort_from_start_node( + ntree, direction, *node, node_states, r_sorted_nodes, r_cycle_detected); } } @@ -426,10 +492,10 @@ static void ensure_topology_cache(const bNodeTree &ntree) update_socket_vectors_and_owner_node(ntree); update_internal_link_inputs(ntree); update_directly_linked_links_and_sockets(ntree); + update_nodes_by_type(ntree); threading::parallel_invoke( tree_runtime.nodes_by_id.size() > 32, [&]() { update_logical_origins(ntree); }, - [&]() { update_nodes_by_type(ntree); }, [&]() { update_sockets_by_identifier(ntree); }, [&]() { update_toposort(ntree, diff --git a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc index b3ff255602b..37bac45e348 100644 --- a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc +++ b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc @@ -51,6 +51,44 @@ static const aal::RelationsInNode &get_relations_in_node(const bNode &node, Reso return geometry_relations; } } + if (ELEM(node.type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_SIMULATION_OUTPUT)) { + aal::RelationsInNode &relations = scope.construct(); + { + /* Add eval relations. */ + int last_geometry_index = -1; + for (const int i : node.input_sockets().index_range()) { + const bNodeSocket &socket = node.input_socket(i); + if (socket.type == SOCK_GEOMETRY) { + last_geometry_index = i; + } + else if (socket_is_field(socket)) { + if (last_geometry_index != -1) { + relations.eval_relations.append({i, last_geometry_index}); + } + } + } + } + + { + /* Add available relations. */ + int last_geometry_index = -1; + for (const int i : node.output_sockets().index_range()) { + const bNodeSocket &socket = node.output_socket(i); + if (socket.type == SOCK_GEOMETRY) { + last_geometry_index = i; + } + else if (socket_is_field(socket)) { + if (last_geometry_index == -1) { + relations.available_on_none.append(i); + } + else { + relations.available_relations.append({i, last_geometry_index}); + } + } + } + } + return relations; + } if (const NodeDeclaration *node_decl = node.declaration()) { if (const aal::RelationsInNode *relations = node_decl->anonymous_attribute_relations()) { return *relations; diff --git a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc index 309c1c50b81..0ea8ba5486c 100644 --- a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc @@ -258,6 +258,103 @@ static OutputFieldDependency find_group_output_dependencies( return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); } +/** Result of syncing two field states. */ +enum class eFieldStateSyncResult : char { + /* Nothing changed. */ + NONE = 0, + /* State A has been modified. */ + CHANGED_A = (1 << 0), + /* State B has been modified. */ + CHANGED_B = (1 << 1), +}; +ENUM_OPERATORS(eFieldStateSyncResult, eFieldStateSyncResult::CHANGED_B) + +/** + * Compare both field states and select the most compatible. + * Afterwards both field states will be the same. + * \return eFieldStateSyncResult flags indicating which field states have changed. + */ +static eFieldStateSyncResult sync_field_states(SocketFieldState &a, SocketFieldState &b) +{ + const bool requires_single = a.requires_single || b.requires_single; + const bool is_single = a.is_single && b.is_single; + + eFieldStateSyncResult res = eFieldStateSyncResult::NONE; + if (a.requires_single != requires_single || a.is_single != is_single) { + res |= eFieldStateSyncResult::CHANGED_A; + } + if (b.requires_single != requires_single || b.is_single != is_single) { + res |= eFieldStateSyncResult::CHANGED_B; + } + + a.requires_single = requires_single; + b.requires_single = requires_single; + a.is_single = is_single; + b.is_single = is_single; + + return res; +} + +/** + * Compare field states of simulation nodes sockets and select the most compatible. + * Afterwards all field states will be the same. + * \return eFieldStateSyncResult flags indicating which field states have changed. + */ +static eFieldStateSyncResult simulation_nodes_field_state_sync( + const bNode &input_node, + const bNode &output_node, + const MutableSpan field_state_by_socket_id) +{ + eFieldStateSyncResult res = eFieldStateSyncResult::NONE; + for (const int i : output_node.output_sockets().index_range()) { + /* First input node output is Delta Time which does not appear in the output node outputs. */ + const bNodeSocket &input_socket = input_node.output_socket(i + 1); + const bNodeSocket &output_socket = output_node.output_socket(i); + SocketFieldState &input_state = field_state_by_socket_id[input_socket.index_in_tree()]; + SocketFieldState &output_state = field_state_by_socket_id[output_socket.index_in_tree()]; + res |= sync_field_states(input_state, output_state); + } + return res; +} + +static bool propagate_special_data_requirements( + const bNodeTree &tree, + const bNode &node, + const MutableSpan field_state_by_socket_id) +{ + tree.ensure_topology_cache(); + + bool need_update = false; + + /* Sync field state between simulation nodes and schedule another pass if necessary. */ + if (node.type == GEO_NODE_SIMULATION_INPUT) { + const NodeGeometrySimulationInput &data = *static_cast( + node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync( + node, *output_node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) { + need_update = true; + } + } + } + else if (node.type == GEO_NODE_SIMULATION_OUTPUT) { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + const NodeGeometrySimulationInput &data = *static_cast( + input_node->storage); + if (node.identifier == data.output_node_id) { + const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync( + *input_node, node, field_state_by_socket_id); + if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) { + need_update = true; + } + } + } + } + + return need_update; +} + static void propagate_data_requirements_from_right_to_left( const bNodeTree &tree, const Span interface_by_node, @@ -265,70 +362,85 @@ static void propagate_data_requirements_from_right_to_left( { const Span toposort_result = tree.toposort_right_to_left(); - for (const bNode *node : toposort_result) { - const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()]; + while (true) { + /* Node updates may require sevaral passes due to cyclic dependencies caused by simulation + * input/output nodes. */ + bool need_update = false; - for (const bNodeSocket *output_socket : node->output_sockets()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()]; + for (const bNode *node : toposort_result) { + const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()]; - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; + for (const bNodeSocket *output_socket : node->output_sockets()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()]; - if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { - continue; - } - if (field_dependency.field_type() == OutputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - continue; - } + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; - /* The output is required to be a single value when it is connected to any input that does - * not support fields. */ - for (const bNodeSocket *target_socket : output_socket->directly_linked_sockets()) { - if (target_socket->is_available()) { - state.requires_single |= - field_state_by_socket_id[target_socket->index_in_tree()].requires_single; + if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { + continue; + } + if (field_dependency.field_type() == OutputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; + continue; } - } - if (state.requires_single) { - bool any_input_is_field_implicitly = false; - const Vector connected_inputs = gather_input_socket_dependencies( - field_dependency, *node); - for (const bNodeSocket *input_socket : connected_inputs) { - if (!input_socket->is_available()) { - continue; + /* The output is required to be a single value when it is connected to any input that does + * not support fields. */ + for (const bNodeSocket *target_socket : output_socket->directly_linked_sockets()) { + if (target_socket->is_available()) { + state.requires_single |= + field_state_by_socket_id[target_socket->index_in_tree()].requires_single; } - if (inferencing_interface.inputs[input_socket->index()] == - InputSocketFieldType::Implicit) { - if (!input_socket->is_logically_linked()) { - any_input_is_field_implicitly = true; - break; + } + + if (state.requires_single) { + bool any_input_is_field_implicitly = false; + const Vector connected_inputs = gather_input_socket_dependencies( + field_dependency, *node); + for (const bNodeSocket *input_socket : connected_inputs) { + if (!input_socket->is_available()) { + continue; + } + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + if (!input_socket->is_logically_linked()) { + any_input_is_field_implicitly = true; + break; + } + } + } + if (any_input_is_field_implicitly) { + /* This output isn't a single value actually. */ + state.requires_single = false; + } + else { + /* If the output is required to be a single value, the connected inputs in the same + * node must not be fields as well. */ + for (const bNodeSocket *input_socket : connected_inputs) { + field_state_by_socket_id[input_socket->index_in_tree()].requires_single = true; } } } - if (any_input_is_field_implicitly) { - /* This output isn't a single value actually. */ - state.requires_single = false; - } - else { - /* If the output is required to be a single value, the connected inputs in the same node - * must not be fields as well. */ - for (const bNodeSocket *input_socket : connected_inputs) { - field_state_by_socket_id[input_socket->index_in_tree()].requires_single = true; - } + } + + /* Some inputs do not require fields independent of what the outputs are connected to. */ + for (const bNodeSocket *input_socket : node->input_sockets()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()]; + if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; } } + + /* Find reverse dependencies and resolve conflicts, which may require another pass. */ + if (propagate_special_data_requirements(tree, *node, field_state_by_socket_id)) { + need_update = true; + } } - /* Some inputs do not require fields independent of what the outputs are connected to. */ - for (const bNodeSocket *input_socket : node->input_sockets()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()]; - if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - } + if (!need_update) { + break; } } } @@ -384,68 +496,82 @@ static void propagate_field_status_from_left_to_right( { const Span toposort_result = tree.toposort_left_to_right(); - for (const bNode *node : toposort_result) { - if (node->type == NODE_GROUP_INPUT) { - continue; - } + while (true) { + /* Node updates may require sevaral passes due to cyclic dependencies. */ + bool need_update = false; - const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()]; - - /* Update field state of input sockets, also taking into account linked origin sockets. */ - for (const bNodeSocket *input_socket : node->input_sockets()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()]; - if (state.is_always_single) { - state.is_single = true; + for (const bNode *node : toposort_result) { + if (node->type == NODE_GROUP_INPUT) { continue; } - state.is_single = true; - if (!input_socket->is_directly_linked()) { - if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::Implicit) - { - state.is_single = false; + + const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()]; + + /* Update field state of input sockets, also taking into account linked origin sockets. */ + for (const bNodeSocket *input_socket : node->input_sockets()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()]; + if (state.is_always_single) { + state.is_single = true; + continue; } - } - else { - for (const bNodeSocket *origin_socket : input_socket->directly_linked_sockets()) { - if (!field_state_by_socket_id[origin_socket->index_in_tree()].is_single) { + state.is_single = true; + if (!input_socket->is_directly_linked()) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { state.is_single = false; - break; } } - } - } - - /* Update field state of output sockets, also taking into account input sockets. */ - for (const bNodeSocket *output_socket : node->output_sockets()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()]; - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; - - switch (field_dependency.field_type()) { - case OutputSocketFieldType::None: { - state.is_single = true; - break; - } - case OutputSocketFieldType::FieldSource: { - state.is_single = false; - state.is_field_source = true; - break; - } - case OutputSocketFieldType::PartiallyDependent: - case OutputSocketFieldType::DependentField: { - for (const bNodeSocket *input_socket : - gather_input_socket_dependencies(field_dependency, *node)) { - if (!input_socket->is_available()) { - continue; - } - if (!field_state_by_socket_id[input_socket->index_in_tree()].is_single) { + else { + for (const bNodeSocket *origin_socket : input_socket->directly_linked_sockets()) { + if (!field_state_by_socket_id[origin_socket->index_in_tree()].is_single) { state.is_single = false; break; } } - break; } } + + /* Update field state of output sockets, also taking into account input sockets. */ + for (const bNodeSocket *output_socket : node->output_sockets()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()]; + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + switch (field_dependency.field_type()) { + case OutputSocketFieldType::None: { + state.is_single = true; + break; + } + case OutputSocketFieldType::FieldSource: { + state.is_single = false; + state.is_field_source = true; + break; + } + case OutputSocketFieldType::PartiallyDependent: + case OutputSocketFieldType::DependentField: { + for (const bNodeSocket *input_socket : + gather_input_socket_dependencies(field_dependency, *node)) { + if (!input_socket->is_available()) { + continue; + } + if (!field_state_by_socket_id[input_socket->index_in_tree()].is_single) { + state.is_single = false; + break; + } + } + break; + } + } + } + + /* Find reverse dependencies and resolve conflicts, which may require another pass. */ + if (propagate_special_data_requirements(tree, *node, field_state_by_socket_id)) { + need_update = true; + } + } + + if (!need_update) { + break; } } } diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 0da51c56564..edc56c58486 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -53,6 +53,7 @@ static void add_tree_tag(bNodeTree *ntree, const eNodeTreeChangedFlag flag) { ntree->runtime->changed_flag |= flag; ntree->runtime->topology_cache_mutex.tag_dirty(); + ntree->runtime->tree_zones_cache_mutex.tag_dirty(); } static void add_node_tag(bNodeTree *ntree, bNode *node, const eNodeTreeChangedFlag flag) @@ -572,6 +573,16 @@ class NodeTreeMainUpdater { return true; } } + /* Check paired simulation zone nodes. */ + if (node.type == GEO_NODE_SIMULATION_INPUT) { + const NodeGeometrySimulationInput *data = static_cast( + node.storage); + if (const bNode *output_node = ntree.node_by_id(data->output_node_id)) { + if (output_node->runtime->changed_flag & NTREE_CHANGED_NODE_PROPERTY) { + return true; + } + } + } return false; } diff --git a/source/blender/blenkernel/intern/node_tree_zones.cc b/source/blender/blenkernel/intern/node_tree_zones.cc new file mode 100644 index 00000000000..f8301e3f8d8 --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_zones.cc @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_node.h" +#include "BKE_node_runtime.hh" +#include "BKE_node_tree_zones.hh" + +#include "BLI_bit_group_vector.hh" +#include "BLI_bit_span_ops.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +namespace blender::bke::node_tree_zones { + +static void update_zone_depths(TreeZone &zone) +{ + if (zone.depth >= 0) { + return; + } + if (zone.parent_zone == nullptr) { + zone.depth = 0; + return; + } + update_zone_depths(*zone.parent_zone); + zone.depth = zone.parent_zone->depth + 1; +} + +static Vector> find_zone_nodes( + const bNodeTree &tree, TreeZones &owner, Map &r_zone_by_inout_node) +{ + Vector> zones; + for (const bNode *node : tree.nodes_by_type("GeometryNodeSimulationOutput")) { + auto zone = std::make_unique(); + zone->owner = &owner; + zone->index = zones.size(); + zone->output_node = node; + r_zone_by_inout_node.add(node, zone.get()); + zones.append_and_get_index(std::move(zone)); + } + for (const bNode *node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + const auto &storage = *static_cast(node->storage); + if (const bNode *sim_output_node = tree.node_by_id(storage.output_node_id)) { + if (TreeZone *zone = r_zone_by_inout_node.lookup_default(sim_output_node, nullptr)) { + zone->input_node = node; + r_zone_by_inout_node.add(node, zone); + } + } + } + return zones; +} + +struct ZoneRelation { + TreeZone *parent; + TreeZone *child; +}; + +static Vector get_direct_zone_relations( + const Span> all_zones, + const BitGroupVector<> &depend_on_input_flag_array) +{ + Vector zone_relations; + + /* Gather all relations, even the transitive once. */ + for (const std::unique_ptr &zone : all_zones) { + const int zone_i = zone->index; + for (const bNode *node : {zone->output_node}) { + if (node == nullptr) { + continue; + } + const BoundedBitSpan depend_on_input_flags = depend_on_input_flag_array[node->index()]; + bits::foreach_1_index(depend_on_input_flags, [&](const int parent_zone_i) { + if (parent_zone_i != zone_i) { + zone_relations.append({all_zones[parent_zone_i].get(), zone.get()}); + } + }); + } + } + + /* Remove transitive relations. This is a brute force algorithm currently. */ + Vector transitive_relations; + for (const int a : zone_relations.index_range()) { + const ZoneRelation &relation_a = zone_relations[a]; + for (const int b : zone_relations.index_range()) { + if (a == b) { + continue; + } + const ZoneRelation &relation_b = zone_relations[b]; + for (const int c : zone_relations.index_range()) { + if (a == c || b == c) { + continue; + } + const ZoneRelation &relation_c = zone_relations[c]; + if (relation_a.child == relation_b.parent && relation_a.parent == relation_c.parent && + relation_b.child == relation_c.child) + { + transitive_relations.append_non_duplicates(c); + } + } + } + } + std::sort(transitive_relations.begin(), transitive_relations.end(), std::greater<>()); + for (const int i : transitive_relations) { + zone_relations.remove_and_reorder(i); + } + + return zone_relations; +} + +static void update_parent_zone_per_node(const Span all_nodes, + const Span> all_zones, + const BitGroupVector<> &depend_on_input_flag_array, + Map &r_parent_zone_by_node_id) +{ + for (const int node_i : all_nodes.index_range()) { + const bNode &node = *all_nodes[node_i]; + const BoundedBitSpan depend_on_input_flags = depend_on_input_flag_array[node_i]; + TreeZone *parent_zone = nullptr; + bits::foreach_1_index(depend_on_input_flags, [&](const int parent_zone_i) { + TreeZone *zone = all_zones[parent_zone_i].get(); + if (ELEM(&node, zone->input_node, zone->output_node)) { + return; + } + if (parent_zone == nullptr || zone->depth > parent_zone->depth) { + parent_zone = zone; + } + }); + if (parent_zone != nullptr) { + r_parent_zone_by_node_id.add(node.identifier, parent_zone->index); + } + } +} + +static std::unique_ptr discover_tree_zones(const bNodeTree &tree) +{ + if (tree.has_available_link_cycle()) { + return {}; + } + + std::unique_ptr tree_zones = std::make_unique(); + + const Span all_nodes = tree.all_nodes(); + Map zone_by_inout_node; + tree_zones->zones = find_zone_nodes(tree, *tree_zones, zone_by_inout_node); + + const int zones_num = tree_zones->zones.size(); + const int nodes_num = all_nodes.size(); + /* A bit for every node-zone-combination. The bit is set when the node is in the zone. */ + BitGroupVector<> depend_on_input_flag_array(nodes_num, zones_num, false); + /* The bit is set when the node depends on the output of the zone. */ + BitGroupVector<> depend_on_output_flag_array(nodes_num, zones_num, false); + + const Span sorted_nodes = tree.toposort_left_to_right(); + for (const bNode *node : sorted_nodes) { + const int node_i = node->index(); + MutableBoundedBitSpan depend_on_input_flags = depend_on_input_flag_array[node_i]; + MutableBoundedBitSpan depend_on_output_flags = depend_on_output_flag_array[node_i]; + + /* Forward all bits from the nodes to the left. */ + for (const bNodeSocket *input_socket : node->input_sockets()) { + if (!input_socket->is_available()) { + continue; + } + for (const bNodeLink *link : input_socket->directly_linked_links()) { + if (link->is_muted()) { + continue; + } + const bNode &from_node = *link->fromnode; + const int from_node_i = from_node.index(); + depend_on_input_flags |= depend_on_input_flag_array[from_node_i]; + depend_on_output_flags |= depend_on_output_flag_array[from_node_i]; + } + } + if (node->type == GEO_NODE_SIMULATION_INPUT) { + if (const TreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) { + /* Now entering a zone, so set the corresponding bit. */ + depend_on_input_flags[zone->index].set(); + } + } + else if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + if (const TreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) { + /* The output is implicitly linked to the input, so also propagate the bits from there. */ + if (const bNode *zone_input_node = zone->input_node) { + const int input_node_i = zone_input_node->index(); + depend_on_input_flags |= depend_on_input_flag_array[input_node_i]; + depend_on_output_flags |= depend_on_output_flag_array[input_node_i]; + } + /* Now exiting a zone, so change the bits accordingly. */ + depend_on_input_flags[zone->index].reset(); + depend_on_output_flags[zone->index].set(); + } + } + + if (bits::has_common_set_bits(depend_on_input_flags, depend_on_output_flags)) { + /* A node can not be inside and after a zone at the same time. */ + return {}; + } + } + + const Vector zone_relations = get_direct_zone_relations( + tree_zones->zones, depend_on_input_flag_array); + + /* Set parent and child pointers in zones. */ + for (const ZoneRelation &relation : zone_relations) { + relation.parent->child_zones.append(relation.child); + BLI_assert(relation.child->parent_zone == nullptr); + relation.child->parent_zone = relation.parent; + } + + /* Update depths. */ + for (std::unique_ptr &zone : tree_zones->zones) { + update_zone_depths(*zone); + } + + update_parent_zone_per_node(all_nodes, + tree_zones->zones, + depend_on_input_flag_array, + tree_zones->parent_zone_by_node_id); + + for (const int node_i : all_nodes.index_range()) { + const bNode *node = all_nodes[node_i]; + const int parent_zone_i = tree_zones->parent_zone_by_node_id.lookup_default(node->identifier, + -1); + if (parent_zone_i != -1) { + tree_zones->zones[parent_zone_i]->child_nodes.append(node); + } + } + + return tree_zones; +} + +const TreeZones *get_tree_zones(const bNodeTree &tree) +{ + tree.runtime->tree_zones_cache_mutex.ensure( + [&]() { tree.runtime->tree_zones = discover_tree_zones(tree); }); + return tree.runtime->tree_zones.get(); +} + +bool TreeZone::contains_node_recursively(const bNode &node) const +{ + const TreeZones *zones = this->owner; + const int parent_zone_i = zones->parent_zone_by_node_id.lookup_default(node.identifier, -1); + if (parent_zone_i == -1) { + return false; + } + for (const TreeZone *zone = zones->zones[parent_zone_i].get(); zone; zone = zone->parent_zone) { + if (zone == this) { + return true; + } + } + return false; +} + +} // namespace blender::bke::node_tree_zones diff --git a/source/blender/blenkernel/intern/simulation_state.cc b/source/blender/blenkernel/intern/simulation_state.cc new file mode 100644 index 00000000000..703a174648c --- /dev/null +++ b/source/blender/blenkernel/intern/simulation_state.cc @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" +#include "BKE_simulation_state.hh" +#include "BKE_simulation_state_serialize.hh" + +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_pointcloud_types.h" + +#include "BLI_fileops.hh" +#include "BLI_hash_md5.h" +#include "BLI_path_util.h" + +namespace blender::bke::sim { + +GeometrySimulationStateItem::GeometrySimulationStateItem(GeometrySet geometry) + : geometry_(std::move(geometry)) +{ +} + +PrimitiveSimulationStateItem::PrimitiveSimulationStateItem(const CPPType &type, const void *value) + : type_(type) +{ + value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_construct(value, value_); +} + +PrimitiveSimulationStateItem::~PrimitiveSimulationStateItem() +{ + type_.destruct(value_); + MEM_freeN(value_); +} + +StringSimulationStateItem::StringSimulationStateItem(std::string value) : value_(std::move(value)) +{ +} + +void ModifierSimulationCache::try_discover_bake(const StringRefNull meta_dir, + const StringRefNull bdata_dir) +{ + if (failed_finding_bake_) { + return; + } + if (!BLI_is_dir(meta_dir.c_str()) || !BLI_is_dir(bdata_dir.c_str())) { + failed_finding_bake_ = true; + return; + } + + direntry *dir_entries = nullptr; + const int dir_entries_num = BLI_filelist_dir_contents(meta_dir.c_str(), &dir_entries); + BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); }); + + if (dir_entries_num == 0) { + failed_finding_bake_ = true; + return; + } + + this->reset(); + + for (const int i : IndexRange(dir_entries_num)) { + const direntry &dir_entry = dir_entries[i]; + const StringRefNull dir_entry_path = dir_entry.path; + if (!dir_entry_path.endswith(".json")) { + continue; + } + char modified_file_name[FILENAME_MAX]; + BLI_strncpy(modified_file_name, dir_entry.relname, sizeof(modified_file_name)); + BLI_str_replace_char(modified_file_name, '_', '.'); + + const SubFrame frame = std::stof(modified_file_name); + + auto new_state_at_frame = std::make_unique(); + new_state_at_frame->frame = frame; + new_state_at_frame->state.bdata_dir_ = bdata_dir; + new_state_at_frame->state.meta_path_ = dir_entry.path; + new_state_at_frame->state.owner_ = this; + states_at_frames_.append(std::move(new_state_at_frame)); + } + + bdata_sharing_ = std::make_unique(); + + cache_state_ = CacheState::Baked; +} + +bool ModifierSimulationCache::has_state_at_frame(const SubFrame &frame) const +{ + for (const auto &item : states_at_frames_) { + if (item->frame == frame) { + return true; + } + } + return false; +} + +bool ModifierSimulationCache::has_states() const +{ + return !states_at_frames_.is_empty(); +} + +const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame( + const SubFrame &frame) const +{ + for (const auto &item : states_at_frames_) { + if (item->frame == frame) { + return &item->state; + } + } + return nullptr; +} + +ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write( + const SubFrame &frame) +{ + for (const auto &item : states_at_frames_) { + if (item->frame == frame) { + return item->state; + } + } + states_at_frames_.append(std::make_unique()); + states_at_frames_.last()->frame = frame; + states_at_frames_.last()->state.owner_ = this; + return states_at_frames_.last()->state; +} + +StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const +{ + StatesAroundFrame states_around_frame; + for (const auto &item : states_at_frames_) { + if (item->frame < frame) { + if (states_around_frame.prev == nullptr || item->frame > states_around_frame.prev->frame) { + states_around_frame.prev = item.get(); + } + } + if (item->frame == frame) { + if (states_around_frame.current == nullptr) { + states_around_frame.current = item.get(); + } + } + if (item->frame > frame) { + if (states_around_frame.next == nullptr || item->frame < states_around_frame.next->frame) { + states_around_frame.next = item.get(); + } + } + } + return states_around_frame; +} + +const SimulationZoneState *ModifierSimulationState::get_zone_state( + const SimulationZoneID &zone_id) const +{ + std::lock_guard lock{mutex_}; + if (auto *ptr = zone_states_.lookup_ptr(zone_id)) { + return ptr->get(); + } + return nullptr; +} + +SimulationZoneState &ModifierSimulationState::get_zone_state_for_write( + const SimulationZoneID &zone_id) +{ + std::lock_guard lock{mutex_}; + return *zone_states_.lookup_or_add_cb(zone_id, + []() { return std::make_unique(); }); +} + +void ModifierSimulationState::ensure_bake_loaded() const +{ + std::scoped_lock lock{mutex_}; + if (bake_loaded_) { + return; + } + if (!meta_path_ || !bdata_dir_) { + return; + } + + const std::shared_ptr io_root_value = io::serialize::read_json_file( + *meta_path_); + if (!io_root_value) { + return; + } + const DictionaryValue *io_root = io_root_value->as_dictionary_value(); + if (!io_root) { + return; + } + + const DiskBDataReader bdata_reader{*bdata_dir_}; + deserialize_modifier_simulation_state(*io_root, + bdata_reader, + *owner_->bdata_sharing_, + const_cast(*this)); + bake_loaded_ = true; +} + +void ModifierSimulationCache::reset() +{ + states_at_frames_.clear(); + bdata_sharing_.reset(); + cache_state_ = CacheState::Valid; +} + +} // namespace blender::bke::sim diff --git a/source/blender/blenkernel/intern/simulation_state_serialize.cc b/source/blender/blenkernel/intern/simulation_state_serialize.cc new file mode 100644 index 00000000000..9294c94d888 --- /dev/null +++ b/source/blender/blenkernel/intern/simulation_state_serialize.cc @@ -0,0 +1,1219 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" +#include "BKE_instances.hh" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_mesh.hh" +#include "BKE_pointcloud.h" +#include "BKE_simulation_state_serialize.hh" + +#include "DNA_material_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BLI_endian_defines.h" +#include "BLI_endian_switch.h" +#include "BLI_fileops.hh" +#include "BLI_math_matrix_types.hh" +#include "BLI_path_util.h" + +#include "RNA_access.h" +#include "RNA_enum_types.h" + +namespace blender::bke::sim { + +/** + * Turn the name into something that can be used as file name. It does not necessarily have to be + * human readible, but it can help if it is at least partially readible. + */ +static std::string escape_name(const StringRef name) +{ + std::stringstream ss; + for (const char c : name) { + /* Only some letters allowed. Digits are not because they could lead to name collisions. */ + if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) { + ss << c; + } + else { + ss << int(c); + } + } + return ss.str(); +} + +static std::string get_blendcache_directory(const Main &bmain) +{ + StringRefNull blend_file_path = BKE_main_blendfile_path(&bmain); + char blend_directory[FILE_MAX]; + char blend_name[FILE_MAX]; + BLI_path_split_dir_file(blend_file_path.c_str(), + blend_directory, + sizeof(blend_directory), + blend_name, + sizeof(blend_name)); + const int64_t type_start_index = StringRef(blend_name).rfind("."); + if (type_start_index == StringRef::not_found) { + return ""; + } + blend_name[type_start_index] = '\0'; + const std::string blendcache_name = "blendcache_" + StringRef(blend_name); + + char blendcache_dir[FILE_MAX]; + BLI_path_join(blendcache_dir, sizeof(blendcache_dir), blend_directory, blendcache_name.c_str()); + return blendcache_dir; +} + +static std::string get_modifier_sim_name(const Object &object, const ModifierData &md) +{ + const std::string object_name_escaped = escape_name(object.id.name + 2); + const std::string modifier_name_escaped = escape_name(md.name); + return "sim_" + object_name_escaped + "_" + modifier_name_escaped; +} + +std::string get_bake_directory(const Main &bmain, const Object &object, const ModifierData &md) +{ + char bdata_dir[FILE_MAX]; + BLI_path_join(bdata_dir, + sizeof(bdata_dir), + get_blendcache_directory(bmain).c_str(), + get_modifier_sim_name(object, md).c_str()); + return bdata_dir; +} + +std::string get_bdata_directory(const Main &bmain, const Object &object, const ModifierData &md) +{ + char bdata_dir[FILE_MAX]; + BLI_path_join( + bdata_dir, sizeof(bdata_dir), get_bake_directory(bmain, object, md).c_str(), "bdata"); + return bdata_dir; +} + +std::string get_meta_directory(const Main &bmain, const Object &object, const ModifierData &md) +{ + char meta_dir[FILE_MAX]; + BLI_path_join(meta_dir, sizeof(meta_dir), get_bake_directory(bmain, object, md).c_str(), "meta"); + return meta_dir; +} + +std::shared_ptr BDataSlice::serialize() const +{ + auto io_slice = std::make_shared(); + io_slice->append_str("name", this->name); + io_slice->append_int("start", range.start()); + io_slice->append_int("size", range.size()); + return io_slice; +} + +std::optional BDataSlice::deserialize(const DictionaryValue &io_slice) +{ + const std::optional name = io_slice.lookup_str("name"); + const std::optional start = io_slice.lookup_int("start"); + const std::optional size = io_slice.lookup_int("size"); + if (!name || !start || !size) { + return std::nullopt; + } + + return BDataSlice{*name, {*start, *size}}; +} + +static StringRefNull get_endian_io_name(const int endian) +{ + if (endian == L_ENDIAN) { + return "little"; + } + BLI_assert(endian == B_ENDIAN); + return "big"; +} + +static StringRefNull get_domain_io_name(const eAttrDomain domain) +{ + const char *io_name = "unknown"; + RNA_enum_id_from_value(rna_enum_attribute_domain_items, domain, &io_name); + return io_name; +} + +static StringRefNull get_data_type_io_name(const eCustomDataType data_type) +{ + const char *io_name = "unknown"; + RNA_enum_id_from_value(rna_enum_attribute_type_items, data_type, &io_name); + return io_name; +} + +static std::optional get_domain_from_io_name(const StringRefNull io_name) +{ + int domain; + if (!RNA_enum_value_from_identifier(rna_enum_attribute_domain_items, io_name.c_str(), &domain)) { + return std::nullopt; + } + return eAttrDomain(domain); +} + +static std::optional get_data_type_from_io_name(const StringRefNull io_name) +{ + int domain; + if (!RNA_enum_value_from_identifier(rna_enum_attribute_type_items, io_name.c_str(), &domain)) { + return std::nullopt; + } + return eCustomDataType(domain); +} + +/** + * Write the data and remember which endianness the data had. + */ +static std::shared_ptr write_bdata_raw_data_with_endian( + BDataWriter &bdata_writer, const void *data, const int64_t size_in_bytes) +{ + auto io_data = bdata_writer.write(data, size_in_bytes).serialize(); + if (ENDIAN_ORDER == B_ENDIAN) { + io_data->append_str("endian", get_endian_io_name(ENDIAN_ORDER)); + } + return io_data; +} + +/** + * Read data of an into an array and optionally perform an endian switch if necessary. + */ +[[nodiscard]] static bool read_bdata_raw_data_with_endian(const BDataReader &bdata_reader, + const DictionaryValue &io_data, + const int64_t element_size, + const int64_t elements_num, + void *r_data) +{ + const std::optional slice = BDataSlice::deserialize(io_data); + if (!slice) { + return false; + } + if (slice->range.size() != element_size * elements_num) { + return false; + } + if (!bdata_reader.read(*slice, r_data)) { + return false; + } + const StringRefNull stored_endian = io_data.lookup_str("endian").value_or("little"); + const StringRefNull current_endian = get_endian_io_name(ENDIAN_ORDER); + const bool need_endian_switch = stored_endian != current_endian; + if (need_endian_switch) { + switch (element_size) { + case 1: + break; + case 2: + BLI_endian_switch_uint16_array(static_cast(r_data), elements_num); + break; + case 4: + BLI_endian_switch_uint32_array(static_cast(r_data), elements_num); + break; + case 8: + BLI_endian_switch_uint64_array(static_cast(r_data), elements_num); + break; + default: + return false; + } + } + return true; +} + +/** Write bytes ignoring endianness. */ +static std::shared_ptr write_bdata_raw_bytes(BDataWriter &bdata_writer, + const void *data, + const int64_t size_in_bytes) +{ + return bdata_writer.write(data, size_in_bytes).serialize(); +} + +/** Read bytes ignoring endianness. */ +[[nodiscard]] static bool read_bdata_raw_bytes(const BDataReader &bdata_reader, + const DictionaryValue &io_data, + const int64_t bytes_num, + void *r_data) +{ + const std::optional slice = BDataSlice::deserialize(io_data); + if (!slice) { + return false; + } + if (slice->range.size() != bytes_num) { + return false; + } + return bdata_reader.read(*slice, r_data); +} + +static std::shared_ptr write_bdata_simple_gspan(BDataWriter &bdata_writer, + const GSpan data) +{ + const CPPType &type = data.type(); + BLI_assert(type.is_trivial()); + if (type.size() == 1 || type.is()) { + return write_bdata_raw_bytes(bdata_writer, data.data(), data.size_in_bytes()); + } + return write_bdata_raw_data_with_endian(bdata_writer, data.data(), data.size_in_bytes()); +} + +[[nodiscard]] static bool read_bdata_simple_gspan(const BDataReader &bdata_reader, + const DictionaryValue &io_data, + GMutableSpan r_data) +{ + const CPPType &type = r_data.type(); + BLI_assert(type.is_trivial()); + if (type.size() == 1 || type.is()) { + return read_bdata_raw_bytes(bdata_reader, io_data, r_data.size_in_bytes(), r_data.data()); + } + if (type.is_any()) { + return read_bdata_raw_data_with_endian( + bdata_reader, io_data, type.size(), r_data.size(), r_data.data()); + } + if (type.is_any()) { + return read_bdata_raw_data_with_endian( + bdata_reader, io_data, sizeof(int32_t), r_data.size() * 2, r_data.data()); + } + if (type.is()) { + return read_bdata_raw_data_with_endian( + bdata_reader, io_data, sizeof(float), r_data.size() * 3, r_data.data()); + } + if (type.is()) { + return read_bdata_raw_data_with_endian( + bdata_reader, io_data, sizeof(float), r_data.size() * 16, r_data.data()); + } + if (type.is()) { + return read_bdata_raw_data_with_endian( + bdata_reader, io_data, sizeof(float), r_data.size() * 4, r_data.data()); + } + return false; +} + +static std::shared_ptr write_bdata_shared_simple_gspan( + BDataWriter &bdata_writer, + BDataSharing &bdata_sharing, + const GSpan data, + const ImplicitSharingInfo *sharing_info) +{ + return bdata_sharing.write_shared( + sharing_info, [&]() { return write_bdata_simple_gspan(bdata_writer, data); }); +} + +[[nodiscard]] static const void *read_bdata_shared_simple_gspan( + const DictionaryValue &io_data, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing, + const CPPType &cpp_type, + const int size, + const ImplicitSharingInfo **r_sharing_info) +{ + const std::optional sharing_info_and_data = + bdata_sharing.read_shared(io_data, [&]() -> std::optional { + void *data_mem = MEM_mallocN_aligned( + size * cpp_type.size(), cpp_type.alignment(), __func__); + if (!read_bdata_simple_gspan(bdata_reader, io_data, {cpp_type, data_mem, size})) { + MEM_freeN(data_mem); + return std::nullopt; + } + return ImplicitSharingInfoAndData{implicit_sharing::info_for_mem_free(data_mem), data_mem}; + }); + if (!sharing_info_and_data) { + *r_sharing_info = nullptr; + return nullptr; + } + *r_sharing_info = sharing_info_and_data->sharing_info; + return sharing_info_and_data->data; +} + +template +[[nodiscard]] static bool read_bdata_shared_simple_span(const DictionaryValue &io_data, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing, + const int size, + T **r_data, + const ImplicitSharingInfo **r_sharing_info) +{ + *r_data = const_cast(static_cast(read_bdata_shared_simple_gspan( + io_data, bdata_reader, bdata_sharing, CPPType::get(), size, r_sharing_info))); + return *r_data != nullptr; +} + +[[nodiscard]] static bool load_attributes(const io::serialize::ArrayValue &io_attributes, + bke::MutableAttributeAccessor &attributes, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + for (const auto &io_attribute_value : io_attributes.elements()) { + const auto *io_attribute = io_attribute_value->as_dictionary_value(); + if (!io_attribute) { + return false; + } + const std::optional name = io_attribute->lookup_str("name"); + const std::optional domain_str = io_attribute->lookup_str("domain"); + const std::optional type_str = io_attribute->lookup_str("type"); + auto io_data = io_attribute->lookup_dict("data"); + if (!name || !domain_str || !type_str || !io_data) { + return false; + } + + const std::optional domain = get_domain_from_io_name(*domain_str); + const std::optional data_type = get_data_type_from_io_name(*type_str); + if (!domain || !data_type) { + return false; + } + const CPPType *cpp_type = custom_data_type_to_cpp_type(*data_type); + if (!cpp_type) { + return false; + } + const int domain_size = attributes.domain_size(*domain); + const ImplicitSharingInfo *attribute_sharing_info; + const void *attribute_data = read_bdata_shared_simple_gspan( + *io_data, bdata_reader, bdata_sharing, *cpp_type, domain_size, &attribute_sharing_info); + if (!attribute_data) { + return false; + } + BLI_SCOPED_DEFER([&]() { attribute_sharing_info->remove_user_and_delete_if_last(); }); + + if (attributes.contains(*name)) { + /* If the attribute exists already, copy the values over to the existing array. */ + bke::GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_only_span( + *name, *domain, *data_type); + if (!attribute) { + return false; + } + cpp_type->copy_assign_n(attribute_data, attribute.span.data(), domain_size); + attribute.finish(); + } + else { + /* Add a new attribute that shares the data. */ + if (!attributes.add(*name, + *domain, + *data_type, + AttributeInitShared(attribute_data, *attribute_sharing_info))) + { + return false; + } + } + } + return true; +} + +static PointCloud *try_load_pointcloud(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + const DictionaryValue *io_pointcloud = io_geometry.lookup_dict("pointcloud"); + if (!io_pointcloud) { + return nullptr; + } + const io::serialize::ArrayValue *io_attributes = io_pointcloud->lookup_array("attributes"); + if (!io_attributes) { + return nullptr; + } + PointCloud *pointcloud = BKE_pointcloud_new_nomain(0); + CustomData_free_layer_named(&pointcloud->pdata, "position", 0); + pointcloud->totpoint = io_pointcloud->lookup_int("num_points").value_or(0); + + auto cancel = [&]() { + BKE_id_free(nullptr, pointcloud); + return nullptr; + }; + + bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write(); + if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) { + return cancel(); + } + return pointcloud; +} + +static Curves *try_load_curves(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + const DictionaryValue *io_curves = io_geometry.lookup_dict("curves"); + if (!io_curves) { + return nullptr; + } + + const io::serialize::ArrayValue *io_attributes = io_curves->lookup_array("attributes"); + if (!io_attributes) { + return nullptr; + } + + Curves *curves_id = bke::curves_new_nomain(0, 0); + bke::CurvesGeometry &curves = curves_id->geometry.wrap(); + CustomData_free_layer_named(&curves.point_data, "position", 0); + curves.point_num = io_curves->lookup_int("num_points").value_or(0); + curves.curve_num = io_curves->lookup_int("num_curves").value_or(0); + + auto cancel = [&]() { + BKE_id_free(nullptr, curves_id); + return nullptr; + }; + + if (curves.curves_num() > 0) { + const auto io_curve_offsets = io_curves->lookup_dict("curve_offsets"); + if (!io_curve_offsets) { + return cancel(); + } + if (!read_bdata_shared_simple_span(*io_curve_offsets, + bdata_reader, + bdata_sharing, + curves.curves_num() + 1, + &curves.curve_offsets, + &curves.runtime->curve_offsets_sharing_info)) + { + return cancel(); + } + } + + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) { + return cancel(); + } + + return curves_id; +} + +static Mesh *try_load_mesh(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + const DictionaryValue *io_mesh = io_geometry.lookup_dict("mesh"); + if (!io_mesh) { + return nullptr; + } + + const io::serialize::ArrayValue *io_attributes = io_mesh->lookup_array("attributes"); + if (!io_attributes) { + return nullptr; + } + + Mesh *mesh = BKE_mesh_new_nomain(0, 0, 0, 0); + CustomData_free_layer_named(&mesh->vdata, "position", 0); + CustomData_free_layer_named(&mesh->edata, ".edge_verts", 0); + CustomData_free_layer_named(&mesh->ldata, ".corner_vert", 0); + CustomData_free_layer_named(&mesh->ldata, ".corner_edge", 0); + mesh->totvert = io_mesh->lookup_int("num_vertices").value_or(0); + mesh->totedge = io_mesh->lookup_int("num_edges").value_or(0); + mesh->totpoly = io_mesh->lookup_int("num_polygons").value_or(0); + mesh->totloop = io_mesh->lookup_int("num_corners").value_or(0); + + auto cancel = [&]() { + BKE_id_free(nullptr, mesh); + return nullptr; + }; + + if (mesh->totpoly > 0) { + const auto io_poly_offsets = io_mesh->lookup_dict("poly_offsets"); + if (!io_poly_offsets) { + return cancel(); + } + if (!read_bdata_shared_simple_span(*io_poly_offsets, + bdata_reader, + bdata_sharing, + mesh->totpoly + 1, + &mesh->poly_offset_indices, + &mesh->runtime->poly_offsets_sharing_info)) + { + return cancel(); + } + } + + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) { + return cancel(); + } + + return mesh; +} + +static GeometrySet load_geometry(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing); + +static std::unique_ptr try_load_instances(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + const DictionaryValue *io_instances = io_geometry.lookup_dict("instances"); + if (!io_instances) { + return nullptr; + } + const int num_instances = io_instances->lookup_int("num_instances").value_or(0); + if (num_instances == 0) { + return nullptr; + } + const io::serialize::ArrayValue *io_attributes = io_instances->lookup_array("attributes"); + if (!io_attributes) { + return nullptr; + } + const io::serialize::ArrayValue *io_references = io_instances->lookup_array("references"); + if (!io_references) { + return nullptr; + } + + std::unique_ptr instances = std::make_unique(); + instances->resize(num_instances); + + for (const auto &io_reference_value : io_references->elements()) { + const DictionaryValue *io_reference = io_reference_value->as_dictionary_value(); + GeometrySet reference_geometry; + if (io_reference) { + reference_geometry = load_geometry(*io_reference, bdata_reader, bdata_sharing); + } + instances->add_reference(std::move(reference_geometry)); + } + + const auto io_transforms = io_instances->lookup_dict("transforms"); + if (!io_transforms) { + return {}; + } + if (!read_bdata_simple_gspan(bdata_reader, *io_transforms, instances->transforms())) { + return {}; + } + + const auto io_handles = io_instances->lookup_dict("handles"); + if (!io_handles) { + return {}; + } + if (!read_bdata_simple_gspan(bdata_reader, *io_handles, instances->reference_handles())) { + return {}; + } + + bke::MutableAttributeAccessor attributes = instances->attributes_for_write(); + if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) { + return {}; + } + + return instances; +} + +static GeometrySet load_geometry(const DictionaryValue &io_geometry, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing) +{ + GeometrySet geometry; + geometry.replace_mesh(try_load_mesh(io_geometry, bdata_reader, bdata_sharing)); + geometry.replace_pointcloud(try_load_pointcloud(io_geometry, bdata_reader, bdata_sharing)); + geometry.replace_curves(try_load_curves(io_geometry, bdata_reader, bdata_sharing)); + geometry.replace_instances( + try_load_instances(io_geometry, bdata_reader, bdata_sharing).release()); + return geometry; +} + +static std::shared_ptr serialize_material_slots( + const Span material_slots) +{ + auto io_materials = std::make_shared(); + for (const Material *material : material_slots) { + if (material == nullptr) { + io_materials->append_null(); + } + else { + auto io_material = io_materials->append_dict(); + io_material->append_str("name", material->id.name + 2); + if (material->id.lib != nullptr) { + io_material->append_str("lib_name", material->id.lib->id.name + 2); + } + } + } + return io_materials; +} + +static std::shared_ptr serialize_attributes( + const bke::AttributeAccessor &attributes, + BDataWriter &bdata_writer, + BDataSharing &bdata_sharing, + const Set &attributes_to_ignore) +{ + auto io_attributes = std::make_shared(); + attributes.for_all( + [&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) { + BLI_assert(!attribute_id.is_anonymous()); + if (attributes_to_ignore.contains_as(attribute_id.name())) { + return true; + } + + auto io_attribute = io_attributes->append_dict(); + + io_attribute->append_str("name", attribute_id.name()); + + const StringRefNull domain_name = get_domain_io_name(meta_data.domain); + io_attribute->append_str("domain", domain_name); + + const StringRefNull type_name = get_data_type_io_name(meta_data.data_type); + io_attribute->append_str("type", type_name); + + const bke::GAttributeReader attribute = attributes.lookup(attribute_id); + const GVArraySpan attribute_span(attribute.varray); + io_attribute->append("data", + write_bdata_shared_simple_gspan( + bdata_writer, + bdata_sharing, + attribute_span, + attribute.varray.is_span() ? attribute.sharing_info : nullptr)); + return true; + }); + return io_attributes; +} + +static std::shared_ptr serialize_geometry_set(const GeometrySet &geometry, + BDataWriter &bdata_writer, + BDataSharing &bdata_sharing) +{ + auto io_geometry = std::make_shared(); + if (geometry.has_mesh()) { + const Mesh &mesh = *geometry.get_mesh_for_read(); + auto io_mesh = io_geometry->append_dict("mesh"); + + io_mesh->append_int("num_vertices", mesh.totvert); + io_mesh->append_int("num_edges", mesh.totedge); + io_mesh->append_int("num_polygons", mesh.totpoly); + io_mesh->append_int("num_corners", mesh.totloop); + + if (mesh.totpoly > 0) { + io_mesh->append("poly_offsets", + write_bdata_shared_simple_gspan(bdata_writer, + bdata_sharing, + mesh.poly_offsets(), + mesh.runtime->poly_offsets_sharing_info)); + } + + auto io_materials = serialize_material_slots({mesh.mat, mesh.totcol}); + io_mesh->append("materials", io_materials); + + auto io_attributes = serialize_attributes(mesh.attributes(), bdata_writer, bdata_sharing, {}); + io_mesh->append("attributes", io_attributes); + } + if (geometry.has_pointcloud()) { + const PointCloud &pointcloud = *geometry.get_pointcloud_for_read(); + auto io_pointcloud = io_geometry->append_dict("pointcloud"); + + io_pointcloud->append_int("num_points", pointcloud.totpoint); + + auto io_materials = serialize_material_slots({pointcloud.mat, pointcloud.totcol}); + io_pointcloud->append("materials", io_materials); + + auto io_attributes = serialize_attributes( + pointcloud.attributes(), bdata_writer, bdata_sharing, {}); + io_pointcloud->append("attributes", io_attributes); + } + if (geometry.has_curves()) { + const Curves &curves_id = *geometry.get_curves_for_read(); + const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + + auto io_curves = io_geometry->append_dict("curves"); + + io_curves->append_int("num_points", curves.point_num); + io_curves->append_int("num_curves", curves.curve_num); + + if (curves.curve_num > 0) { + io_curves->append( + "curve_offsets", + write_bdata_shared_simple_gspan(bdata_writer, + bdata_sharing, + curves.offsets(), + curves.runtime->curve_offsets_sharing_info)); + } + + auto io_materials = serialize_material_slots({curves_id.mat, curves_id.totcol}); + io_curves->append("materials", io_materials); + + auto io_attributes = serialize_attributes( + curves.attributes(), bdata_writer, bdata_sharing, {}); + io_curves->append("attributes", io_attributes); + } + if (geometry.has_instances()) { + const bke::Instances &instances = *geometry.get_instances_for_read(); + auto io_instances = io_geometry->append_dict("instances"); + + io_instances->append_int("num_instances", instances.instances_num()); + + auto io_references = io_instances->append_array("references"); + for (const bke::InstanceReference &reference : instances.references()) { + BLI_assert(reference.type() == bke::InstanceReference::Type::GeometrySet); + io_references->append( + serialize_geometry_set(reference.geometry_set(), bdata_writer, bdata_sharing)); + } + + io_instances->append("transforms", + write_bdata_simple_gspan(bdata_writer, instances.transforms())); + io_instances->append("handles", + write_bdata_simple_gspan(bdata_writer, instances.reference_handles())); + + auto io_attributes = serialize_attributes( + instances.attributes(), bdata_writer, bdata_sharing, {"position"}); + io_instances->append("attributes", io_attributes); + } + return io_geometry; +} + +static std::shared_ptr serialize_float_array(const Span values) +{ + auto io_value = std::make_shared(); + for (const float value : values) { + io_value->append_double(value); + } + return io_value; +} + +static std::shared_ptr serialize_int_array(const Span values) +{ + auto io_value = std::make_shared(); + for (const int value : values) { + io_value->append_int(value); + } + return io_value; +} + +static std::shared_ptr serialize_primitive_value( + const eCustomDataType data_type, const void *value_ptr) +{ + switch (data_type) { + case CD_PROP_FLOAT: { + const float value = *static_cast(value_ptr); + return std::make_shared(value); + } + case CD_PROP_FLOAT2: { + const float2 value = *static_cast(value_ptr); + return serialize_float_array({&value.x, 2}); + } + case CD_PROP_FLOAT3: { + const float3 value = *static_cast(value_ptr); + return serialize_float_array({&value.x, 3}); + } + case CD_PROP_BOOL: { + const bool value = *static_cast(value_ptr); + return std::make_shared(value); + } + case CD_PROP_INT32: { + const int value = *static_cast(value_ptr); + return std::make_shared(value); + } + case CD_PROP_INT32_2D: { + const int2 value = *static_cast(value_ptr); + return serialize_int_array({&value.x, 2}); + } + case CD_PROP_BYTE_COLOR: { + const ColorGeometry4b value = *static_cast(value_ptr); + const int4 value_int{&value.r}; + return serialize_int_array({&value_int.x, 4}); + } + case CD_PROP_COLOR: { + const ColorGeometry4f value = *static_cast(value_ptr); + return serialize_float_array({&value.r, 4}); + } + default: + break; + } + BLI_assert_unreachable(); + return {}; +} + +void serialize_modifier_simulation_state(const ModifierSimulationState &state, + BDataWriter &bdata_writer, + BDataSharing &bdata_sharing, + DictionaryValue &r_io_root) +{ + r_io_root.append_int("version", 1); + auto io_zones = r_io_root.append_array("zones"); + + for (const auto item : state.zone_states_.items()) { + const SimulationZoneID &zone_id = item.key; + const SimulationZoneState &zone_state = *item.value; + + auto io_zone = io_zones->append_dict(); + + auto io_zone_id = io_zone->append_array("zone_id"); + + for (const int node_id : zone_id.node_ids) { + io_zone_id->append_int(node_id); + } + + auto io_state_items = io_zone->append_array("state_items"); + for (const MapItem> &state_item_with_id : + zone_state.item_by_identifier.items()) + { + auto io_state_item = io_state_items->append_dict(); + + io_state_item->append_int("id", state_item_with_id.key); + + if (const GeometrySimulationStateItem *geometry_state_item = + dynamic_cast(state_item_with_id.value.get())) + { + io_state_item->append_str("type", "GEOMETRY"); + + const GeometrySet &geometry = geometry_state_item->geometry(); + + auto io_geometry = serialize_geometry_set(geometry, bdata_writer, bdata_sharing); + io_state_item->append("data", io_geometry); + } + else if (const AttributeSimulationStateItem *attribute_state_item = + dynamic_cast( + state_item_with_id.value.get())) + { + io_state_item->append_str("type", "ATTRIBUTE"); + io_state_item->append_str("name", attribute_state_item->name()); + } + else if (const StringSimulationStateItem *string_state_item = + dynamic_cast(state_item_with_id.value.get())) + { + io_state_item->append_str("type", "STRING"); + const StringRefNull str = string_state_item->value(); + /* Small strings are inlined, larger strings are stored separately. */ + const int64_t bdata_threshold = 100; + if (str.size() < bdata_threshold) { + io_state_item->append_str("data", string_state_item->value()); + } + else { + io_state_item->append("data", + write_bdata_raw_bytes(bdata_writer, str.data(), str.size())); + } + } + else if (const PrimitiveSimulationStateItem *primitive_state_item = + dynamic_cast( + state_item_with_id.value.get())) + { + const eCustomDataType data_type = cpp_type_to_custom_data_type( + primitive_state_item->type()); + io_state_item->append_str("type", get_data_type_io_name(data_type)); + auto io_data = serialize_primitive_value(data_type, primitive_state_item->value()); + io_state_item->append("data", std::move(io_data)); + } + } + } +} + +template +[[nodiscard]] static bool deserialize_typed_array( + const io::serialize::Value &io_value, + FunctionRef(const io::serialize::Value &io_element)> fn, + MutableSpan r_values) +{ + const io::serialize::ArrayValue *io_array = io_value.as_array_value(); + if (!io_array) { + return false; + } + if (io_array->elements().size() != r_values.size()) { + return false; + } + for (const int i : r_values.index_range()) { + const io::serialize::Value &io_element = *io_array->elements()[i]; + std::optional element = fn(io_element); + if (!element) { + return false; + } + r_values[i] = std::move(*element); + } + return true; +} + +template static std::optional deserialize_int(const io::serialize::Value &io_value) +{ + const io::serialize::IntValue *io_int = io_value.as_int_value(); + if (!io_int) { + return std::nullopt; + } + const int64_t value = io_int->value(); + if (value < std::numeric_limits::min()) { + return std::nullopt; + } + if (value > std::numeric_limits::max()) { + return std::nullopt; + } + return value; +} + +static std::optional deserialize_float(const io::serialize::Value &io_value) +{ + if (const io::serialize::DoubleValue *io_double = io_value.as_double_value()) { + return io_double->value(); + } + if (const io::serialize::IntValue *io_int = io_value.as_int_value()) { + return io_int->value(); + } + return std::nullopt; +} + +[[nodiscard]] static bool deserialize_float_array(const io::serialize::Value &io_value, + MutableSpan r_values) +{ + return deserialize_typed_array(io_value, deserialize_float, r_values); +} + +template +[[nodiscard]] static bool deserialize_int_array(const io::serialize::Value &io_value, + MutableSpan r_values) +{ + static_assert(std::is_integral_v); + return deserialize_typed_array(io_value, deserialize_int, r_values); +} + +[[nodiscard]] static bool deserialize_primitive_value(const io::serialize::Value &io_value, + const eCustomDataType type, + void *r_value) +{ + switch (type) { + case CD_PROP_FLOAT: { + const std::optional value = deserialize_float(io_value); + if (!value) { + return false; + } + *static_cast(r_value) = *value; + return true; + } + case CD_PROP_FLOAT2: { + return deserialize_float_array(io_value, {static_cast(r_value), 2}); + } + case CD_PROP_FLOAT3: { + return deserialize_float_array(io_value, {static_cast(r_value), 3}); + } + case CD_PROP_BOOL: { + if (const io::serialize::BooleanValue *io_value_boolean = io_value.as_boolean_value()) { + *static_cast(r_value) = io_value_boolean->value(); + return true; + } + return false; + } + case CD_PROP_INT32: { + const std::optional value = deserialize_int(io_value); + if (!value) { + return false; + } + *static_cast(r_value) = *value; + return true; + } + case CD_PROP_INT32_2D: { + return deserialize_int_array(io_value, {static_cast(r_value), 2}); + } + case CD_PROP_BYTE_COLOR: { + return deserialize_int_array(io_value, {static_cast(r_value), 4}); + } + case CD_PROP_COLOR: { + return deserialize_float_array(io_value, {static_cast(r_value), 4}); + } + default: + break; + } + return false; +} + +void deserialize_modifier_simulation_state(const DictionaryValue &io_root, + const BDataReader &bdata_reader, + const BDataSharing &bdata_sharing, + ModifierSimulationState &r_state) +{ + io::serialize::JsonFormatter formatter; + const std::optional version = io_root.lookup_int("version"); + if (!version) { + return; + } + if (*version != 1) { + return; + } + const io::serialize::ArrayValue *io_zones = io_root.lookup_array("zones"); + if (!io_zones) { + return; + } + for (const auto &io_zone_value : io_zones->elements()) { + const DictionaryValue *io_zone = io_zone_value->as_dictionary_value(); + if (!io_zone) { + continue; + } + const io::serialize::ArrayValue *io_zone_id = io_zone->lookup_array("zone_id"); + bke::sim::SimulationZoneID zone_id; + for (const auto &io_zone_id_element : io_zone_id->elements()) { + const io::serialize::IntValue *io_node_id = io_zone_id_element->as_int_value(); + if (!io_node_id) { + continue; + } + zone_id.node_ids.append(io_node_id->value()); + } + + const io::serialize::ArrayValue *io_state_items = io_zone->lookup_array("state_items"); + if (!io_state_items) { + continue; + } + + auto zone_state = std::make_unique(); + + for (const auto &io_state_item_value : io_state_items->elements()) { + const DictionaryValue *io_state_item = io_state_item_value->as_dictionary_value(); + if (!io_state_item) { + continue; + } + const std::optional state_item_id = io_state_item->lookup_int("id"); + if (!state_item_id) { + continue; + } + const std::optional state_item_type = io_state_item->lookup_str("type"); + if (!state_item_type) { + continue; + } + std::unique_ptr new_state_item; + if (*state_item_type == StringRef("GEOMETRY")) { + const DictionaryValue *io_geometry = io_state_item->lookup_dict("data"); + if (!io_geometry) { + continue; + } + GeometrySet geometry = load_geometry(*io_geometry, bdata_reader, bdata_sharing); + new_state_item = std::make_unique( + std::move(geometry)); + } + else if (*state_item_type == StringRef("ATTRIBUTE")) { + const DictionaryValue *io_attribute = io_state_item; + if (!io_attribute) { + continue; + } + std::optional name = io_attribute->lookup_str("name"); + if (!name) { + continue; + } + new_state_item = std::make_unique(std::move(*name)); + } + else if (*state_item_type == StringRef("STRING")) { + const std::shared_ptr *io_data = io_state_item->lookup("data"); + if (!io_data) { + continue; + } + if (io_data->get()->type() == io::serialize::eValueType::String) { + const io::serialize::StringValue &io_string = *io_data->get()->as_string_value(); + new_state_item = std::make_unique( + io_string.value()); + } + else if (const io::serialize::DictionaryValue *io_string = + io_data->get()->as_dictionary_value()) { + const std::optional size = io_string->lookup_int("size"); + if (!size) { + continue; + } + std::string str; + str.resize(*size); + if (!read_bdata_raw_bytes(bdata_reader, *io_string, *size, str.data())) { + continue; + } + new_state_item = std::make_unique(std::move(str)); + } + } + else { + const std::shared_ptr *io_data = io_state_item->lookup("data"); + if (!io_data) { + continue; + } + const std::optional data_type = get_data_type_from_io_name( + *state_item_type); + if (data_type) { + const CPPType &cpp_type = *custom_data_type_to_cpp_type(*data_type); + BUFFER_FOR_CPP_TYPE_VALUE(cpp_type, buffer); + if (!deserialize_primitive_value(**io_data, *data_type, buffer)) { + continue; + } + BLI_SCOPED_DEFER([&]() { cpp_type.destruct(buffer); }); + new_state_item = std::make_unique(cpp_type, buffer); + } + } + BLI_assert(new_state_item); + zone_state->item_by_identifier.add(*state_item_id, std::move(new_state_item)); + } + + r_state.zone_states_.add_overwrite(zone_id, std::move(zone_state)); + } +} + +DiskBDataReader::DiskBDataReader(std::string bdata_dir) : bdata_dir_(std::move(bdata_dir)) {} + +[[nodiscard]] bool DiskBDataReader::read(const BDataSlice &slice, void *r_data) const +{ + if (slice.range.is_empty()) { + return true; + } + + char bdata_path[FILE_MAX]; + BLI_path_join(bdata_path, sizeof(bdata_path), bdata_dir_.c_str(), slice.name.c_str()); + + std::lock_guard lock{mutex_}; + std::unique_ptr &bdata_file = open_input_streams_.lookup_or_add_cb_as( + bdata_path, + [&]() { return std::make_unique(bdata_path, std::ios::in | std::ios::binary); }); + bdata_file->seekg(slice.range.start()); + bdata_file->read(static_cast(r_data), slice.range.size()); + if (bdata_file->gcount() != slice.range.size()) { + return false; + } + return true; +} + +DiskBDataWriter::DiskBDataWriter(std::string bdata_name, + std::ostream &bdata_file, + const int64_t current_offset) + : bdata_name_(std::move(bdata_name)), bdata_file_(bdata_file), current_offset_(current_offset) +{ +} + +BDataSlice DiskBDataWriter::write(const void *data, const int64_t size) +{ + const int64_t old_offset = current_offset_; + bdata_file_.write(static_cast(data), size); + current_offset_ += size; + return {bdata_name_, {old_offset, size}}; +} + +BDataSharing::~BDataSharing() +{ + for (const ImplicitSharingInfo *sharing_info : stored_by_runtime_.keys()) { + sharing_info->remove_weak_user_and_delete_if_last(); + } + for (const ImplicitSharingInfoAndData &value : runtime_by_stored_.values()) { + if (value.sharing_info) { + value.sharing_info->remove_user_and_delete_if_last(); + } + } +} + +DictionaryValuePtr BDataSharing::write_shared(const ImplicitSharingInfo *sharing_info, + FunctionRef write_fn) +{ + if (sharing_info == nullptr) { + return write_fn(); + } + return stored_by_runtime_.add_or_modify( + sharing_info, + /* Create new value. */ + [&](StoredByRuntimeValue *value) { + new (value) StoredByRuntimeValue(); + value->io_data = write_fn(); + value->sharing_info_version = sharing_info->version(); + sharing_info->add_weak_user(); + return value->io_data; + }, + /* Potentially modify existing value. */ + [&](StoredByRuntimeValue *value) { + const int64_t new_version = sharing_info->version(); + BLI_assert(value->sharing_info_version <= new_version); + if (value->sharing_info_version < new_version) { + value->io_data = write_fn(); + value->sharing_info_version = new_version; + } + return value->io_data; + }); +} + +std::optional BDataSharing::read_shared( + const DictionaryValue &io_data, + FunctionRef()> read_fn) const +{ + std::lock_guard lock{mutex_}; + + io::serialize::JsonFormatter formatter; + std::stringstream ss; + formatter.serialize(ss, io_data); + const std::string key = ss.str(); + + if (const ImplicitSharingInfoAndData *shared_data = runtime_by_stored_.lookup_ptr(key)) { + shared_data->sharing_info->add_user(); + return *shared_data; + } + std::optional data = read_fn(); + if (!data) { + return std::nullopt; + } + if (data->sharing_info != nullptr) { + data->sharing_info->add_user(); + runtime_by_stored_.add_new(key, *data); + } + return data; +} + +} // namespace blender::bke::sim diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 2172a44886a..d5ccd368f3b 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -193,6 +193,14 @@ class ImplicitSharingMixin : public ImplicitSharingInfo { virtual void delete_self() = 0; }; +/** + * Utility that contains sharing information and the data that is shared. + */ +struct ImplicitSharingInfoAndData { + const ImplicitSharingInfo *sharing_info = nullptr; + const void *data = nullptr; +}; + namespace implicit_sharing { namespace detail { diff --git a/source/blender/blenlib/BLI_sub_frame.hh b/source/blender/blenlib/BLI_sub_frame.hh new file mode 100644 index 00000000000..32cc0658ecb --- /dev/null +++ b/source/blender/blenlib/BLI_sub_frame.hh @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_assert.h" +#include "BLI_math_base.h" + +namespace blender { + +/** + * Contains an integer frame number and a subframe float in the range [0, 1). + */ +struct SubFrame { + private: + int frame_; + float subframe_; + + public: + SubFrame(const int frame = 0, float subframe = 0.0f) : frame_(frame), subframe_(subframe) + { + BLI_assert(subframe >= 0.0f); + BLI_assert(subframe < 1.0f); + } + + SubFrame(const float frame) : SubFrame(int(floorf(frame)), fractf(frame)) {} + + int frame() const + { + return frame_; + } + + float subframe() const + { + return subframe_; + } + + explicit operator float() const + { + return float(frame_) + float(subframe_); + } + + explicit operator double() const + { + return double(frame_) + double(subframe_); + } + + static SubFrame min() + { + return {INT32_MIN, 0.0f}; + } + + static SubFrame max() + { + return {INT32_MAX, std::nexttowardf(1.0f, 0.0)}; + } + + friend bool operator==(const SubFrame &a, const SubFrame &b) + { + return a.frame_ == b.frame_ && a.subframe_ == b.subframe_; + } + + friend bool operator!=(const SubFrame &a, const SubFrame &b) + { + return !(a == b); + } + + friend bool operator<(const SubFrame &a, const SubFrame &b) + { + return a.frame_ < b.frame_ || (a.frame_ == b.frame_ && a.subframe_ < b.subframe_); + } + + friend bool operator<=(const SubFrame &a, const SubFrame &b) + { + return a.frame_ <= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ <= b.subframe_); + } + + friend bool operator>(const SubFrame &a, const SubFrame &b) + { + return a.frame_ > b.frame_ || (a.frame_ == b.frame_ && a.subframe_ > b.subframe_); + } + + friend bool operator>=(const SubFrame &a, const SubFrame &b) + { + return a.frame_ >= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ >= b.subframe_); + } + + friend std::ostream &operator<<(std::ostream &stream, const SubFrame &a) + { + return stream << float(a); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index d9014c614df..1ddadeec864 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -352,6 +352,7 @@ set(SRC BLI_string_search.h BLI_string_utf8.h BLI_string_utils.h + BLI_sub_frame.hh BLI_sys_types.h BLI_system.h BLI_task.h diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 3371924be7d..f5533edf72f 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -4329,6 +4329,83 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) /* Rename Grease Pencil weight draw brush. */ do_versions_rename_id(bmain, ID_BR, "Draw Weight", "Weight Draw"); + + /* Identifier generation for simulation node sockets changed. + * Update identifiers so links are not removed during validation. + * This only affects files that have been created using simulation nodes before they were first + * officially released. */ + if (!DNA_struct_elem_find(fd->filesdna, "NodeSimulationItem", "int", "identifier")) { + static auto set_socket_identifiers = + [](bNode *node, const bNode *output_node, int extra_outputs) { + const NodeGeometrySimulationOutput *output_data = + static_cast(output_node->storage); + /* Includes extension socket. */ + BLI_assert(BLI_listbase_count(&node->inputs) == output_data->items_num + 1); + /* Includes extension socket. */ + BLI_assert(BLI_listbase_count(&node->outputs) == + output_data->items_num + 1 + extra_outputs); + + int i; + LISTBASE_FOREACH_INDEX (bNodeSocket *, sock, &node->inputs, i) { + /* Skip extension socket. */ + if (i >= output_data->items_num) { + break; + } + BLI_snprintf(sock->identifier, + sizeof(sock->identifier), + "Item_%d", + output_data->items[i].identifier); + } + LISTBASE_FOREACH_INDEX (bNodeSocket *, sock, &node->outputs, i) { + const int item_i = i - extra_outputs; + /* Skip extra outputs. */ + if (i < extra_outputs) { + continue; + } + /* Skip extension socket. */ + if (item_i >= output_data->items_num) { + break; + } + BLI_snprintf(sock->identifier, + sizeof(sock->identifier), + "Item_%d", + output_data->items[i - extra_outputs].identifier); + } + }; + + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + /* Initialize item identifiers. */ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + NodeGeometrySimulationOutput *data = static_cast( + node->storage); + for (int i = 0; i < data->items_num; ++i) { + data->items[i].identifier = i; + } + data->next_identifier = data->items_num; + } + } + + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == GEO_NODE_SIMULATION_INPUT) { + const NodeGeometrySimulationInput *input_data = + static_cast(node->storage); + LISTBASE_FOREACH (bNode *, output_node, &ntree->nodes) { + if (output_node->identifier == input_data->output_node_id) { + /* 'Delta Time' socket in input nodes */ + set_socket_identifiers(node, output_node, 1); + break; + } + } + } + if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + set_socket_identifiers(node, node, 0); + } + } + } + } + } } /* fcm->name was never used to store modifier name so it has always been an empty string. Now diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 702ccca363f..1f62b887ec3 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -108,6 +108,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) */ { /* Keep this block, even when empty. */ + FROM_DEFAULT_V4_UCHAR(space_node.node_zone_simulation); } #undef FROM_DEFAULT_V4_UCHAR diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index dfde4294db1..44d76076994 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -87,6 +87,7 @@ #include "BKE_scene.h" #include "BKE_shader_fx.h" #include "BKE_simulation.h" +#include "BKE_simulation_state.hh" #include "BKE_sound.h" #include "BKE_tracking.h" #include "BKE_volume.h" @@ -906,6 +907,20 @@ void DepsgraphNodeBuilder::build_object_modifiers(Object *object) LISTBASE_FOREACH (ModifierData *, modifier, &object->modifiers) { OperationNode *modifier_node = add_operation_node( &object->id, NodeType::GEOMETRY, OperationCode::MODIFIER, nullptr, modifier->name); + if (modifier->type == eModifierType_Nodes) { + modifier_node->evaluate = [nmd = reinterpret_cast(modifier), + modifier_node](::Depsgraph *depsgraph) { + if (!DEG_is_active(depsgraph)) { + return; + } + if (modifier_node->flag & DEPSOP_FLAG_USER_MODIFIED) { + if (nmd->simulation_cache && + nmd->simulation_cache->cache_state() == bke::sim::CacheState::Valid) { + nmd->simulation_cache->invalidate(); + } + } + }; + } /* Mute modifier mode if the modifier is not enabled for the dependency graph mode. * This handles static (non-animated) mode of the modifier. */ diff --git a/source/blender/editors/include/ED_node.h b/source/blender/editors/include/ED_node.h index 5ae213afa36..cd5d1a06d7a 100644 --- a/source/blender/editors/include/ED_node.h +++ b/source/blender/editors/include/ED_node.h @@ -73,6 +73,7 @@ void ED_init_node_socket_type_virtual(struct bNodeSocketType *stype); void ED_node_sample_set(const float col[4]); void ED_node_draw_snap( struct View2D *v2d, const float cent[2], float size, NodeBorder border, unsigned int pos); +void ED_node_type_draw_color(const char *idname, float *r_color); /* node_draw.cc */ diff --git a/source/blender/editors/include/UI_resources.h b/source/blender/editors/include/UI_resources.h index 5e2eba20e30..14cf889ed6d 100644 --- a/source/blender/editors/include/UI_resources.h +++ b/source/blender/editors/include/UI_resources.h @@ -179,6 +179,8 @@ typedef enum ThemeColorID { TH_NODE_GEOMETRY, TH_NODE_ATTRIBUTE, + TH_NODE_ZONE_SIMULATION, + TH_CONSOLE_OUTPUT, TH_CONSOLE_INPUT, TH_CONSOLE_INFO, diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index 30eae24505c..cbcd3757f93 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -641,6 +641,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_NODE_GRID_LEVELS: cp = &ts->grid_levels; break; + case TH_NODE_ZONE_SIMULATION: + cp = ts->node_zone_simulation; + break; case TH_SEQ_MOVIE: cp = ts->movie; diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 9ed1f7046b7..f7de8e2689c 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRC object_add.cc object_bake.cc object_bake_api.cc + object_bake_simulation.cc object_collection.c object_constraint.c object_data_transfer.c diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc new file mode 100644 index 00000000000..e68679f976d --- /dev/null +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include +#include + +#include "BLI_endian_defines.h" +#include "BLI_endian_switch.h" +#include "BLI_fileops.hh" +#include "BLI_path_util.h" +#include "BLI_serialize.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "DNA_curves_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_pointcloud_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_global.h" +#include "BKE_instances.hh" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_mesh.hh" +#include "BKE_object.h" +#include "BKE_pointcloud.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_simulation_state.hh" +#include "BKE_simulation_state_serialize.hh" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "object_intern.h" + +namespace blender::ed::object::bake_simulation { + +static bool bake_simulation_poll(bContext *C) +{ + if (!ED_operator_object_active(C)) { + return false; + } + Main *bmain = CTX_data_main(C); + const StringRefNull path = BKE_main_blendfile_path(bmain); + if (path.is_empty()) { + CTX_wm_operator_poll_msg_set(C, "File has to be saved"); + return false; + } + return true; +} + +struct ModifierBakeData { + NodesModifierData *nmd; + std::string meta_dir; + std::string bdata_dir; + std::unique_ptr bdata_sharing; +}; + +struct ObjectBakeData { + Object *object; + Vector modifiers; +}; + +struct BakeSimulationJob { + wmWindowManager *wm; + Main *bmain; + Depsgraph *depsgraph; + Scene *scene; + Vector objects; +}; + +static void bake_simulation_job_startjob(void *customdata, + bool *stop, + bool *do_update, + float *progress) +{ + using namespace bke::sim; + + BakeSimulationJob &job = *static_cast(customdata); + G.is_rendering = true; + G.is_break = false; + WM_set_locked_interface(job.wm, true); + + Vector objects_to_bake; + for (Object *object : job.objects) { + if (!BKE_id_is_editable(job.bmain, &object->id)) { + continue; + } + ObjectBakeData bake_data; + bake_data.object = object; + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = reinterpret_cast(md); + if (nmd->simulation_cache != nullptr) { + nmd->simulation_cache->reset(); + } + bake_data.modifiers.append({nmd, + bke::sim::get_meta_directory(*job.bmain, *object, *md), + bke::sim::get_bdata_directory(*job.bmain, *object, *md), + std::make_unique()}); + } + } + objects_to_bake.append(std::move(bake_data)); + } + + *progress = 0.0f; + *do_update = true; + + const float frame_step_size = 1.0f; + const float progress_per_frame = 1.0f / (float(job.scene->r.efra - job.scene->r.sfra + 1) / + frame_step_size); + const int old_frame = job.scene->r.cfra; + + for (float frame_f = job.scene->r.sfra; frame_f <= job.scene->r.efra; frame_f += frame_step_size) + { + const SubFrame frame{frame_f}; + + if (G.is_break || (stop != nullptr && *stop)) { + break; + } + + job.scene->r.cfra = frame.frame(); + job.scene->r.subframe = frame.subframe(); + + char frame_file_c_str[64]; + BLI_snprintf(frame_file_c_str, sizeof(frame_file_c_str), "%011.5f", double(frame)); + BLI_str_replace_char(frame_file_c_str, '.', '_'); + const StringRefNull frame_file_str = frame_file_c_str; + + BKE_scene_graph_update_for_newframe(job.depsgraph); + + for (ObjectBakeData &object_bake_data : objects_to_bake) { + for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) { + NodesModifierData &nmd = *modifier_bake_data.nmd; + if (nmd.simulation_cache == nullptr) { + continue; + } + ModifierSimulationCache &sim_cache = *nmd.simulation_cache; + const ModifierSimulationState *sim_state = sim_cache.get_state_at_exact_frame(frame); + if (sim_state == nullptr) { + continue; + } + + const std::string bdata_file_name = frame_file_str + ".bdata"; + const std::string meta_file_name = frame_file_str + ".json"; + + char bdata_path[FILE_MAX]; + BLI_path_join(bdata_path, + sizeof(bdata_path), + modifier_bake_data.bdata_dir.c_str(), + bdata_file_name.c_str()); + char meta_path[FILE_MAX]; + BLI_path_join(meta_path, + sizeof(meta_path), + modifier_bake_data.meta_dir.c_str(), + meta_file_name.c_str()); + + BLI_file_ensure_parent_dir_exists(bdata_path); + fstream bdata_file{bdata_path, std::ios::out | std::ios::binary}; + bke::sim::DiskBDataWriter bdata_writer{bdata_file_name, bdata_file, 0}; + + io::serialize::DictionaryValue io_root; + bke::sim::serialize_modifier_simulation_state( + *sim_state, bdata_writer, *modifier_bake_data.bdata_sharing, io_root); + + BLI_file_ensure_parent_dir_exists(meta_path); + io::serialize::write_json_file(meta_path, io_root); + } + } + + *progress += progress_per_frame; + *do_update = true; + } + + for (ObjectBakeData &object_bake_data : objects_to_bake) { + for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) { + NodesModifierData &nmd = *modifier_bake_data.nmd; + if (nmd.simulation_cache) { + /* Tag the caches as being baked so that they are not changed anymore. */ + nmd.simulation_cache->cache_state_ = CacheState::Baked; + } + } + DEG_id_tag_update(&object_bake_data.object->id, ID_RECALC_GEOMETRY); + } + + job.scene->r.cfra = old_frame; + DEG_time_tag_update(job.bmain); + + *progress = 1.0f; + *do_update = true; +} + +static void bake_simulation_job_endjob(void *customdata) +{ + BakeSimulationJob &job = *static_cast(customdata); + WM_set_locked_interface(job.wm, false); + G.is_rendering = false; + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr); +} + +static int bake_simulation_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) +{ + wmWindowManager *wm = CTX_wm_manager(C); + Scene *scene = CTX_data_scene(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Main *bmain = CTX_data_main(C); + + BakeSimulationJob *job = MEM_new(__func__); + job->wm = wm; + job->bmain = bmain; + job->depsgraph = depsgraph; + job->scene = scene; + + if (RNA_boolean_get(op->ptr, "selected")) { + CTX_DATA_BEGIN (C, Object *, object, selected_objects) { + job->objects.append(object); + } + CTX_DATA_END; + } + else { + if (Object *object = CTX_data_active_object(C)) { + job->objects.append(object); + } + } + + wmJob *wm_job = WM_jobs_get(wm, + CTX_wm_window(C), + CTX_data_scene(C), + "Bake Simulation Nodes", + WM_JOB_PROGRESS, + WM_JOB_TYPE_BAKE_SIMULATION_NODES); + + WM_jobs_customdata_set( + wm_job, job, [](void *job) { MEM_delete(static_cast(job)); }); + WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER); + WM_jobs_callbacks( + wm_job, bake_simulation_job_startjob, nullptr, nullptr, bake_simulation_job_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int bake_simulation_modal(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/) +{ + if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_BAKE_SIMULATION_NODES)) { + return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; + } + return OPERATOR_PASS_THROUGH; +} + +static int delete_baked_simulation_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + + Vector objects; + if (RNA_boolean_get(op->ptr, "selected")) { + CTX_DATA_BEGIN (C, Object *, object, selected_objects) { + objects.append(object); + } + CTX_DATA_END; + } + else { + if (Object *object = CTX_data_active_object(C)) { + objects.append(object); + } + } + + if (objects.is_empty()) { + return OPERATOR_CANCELLED; + } + + for (Object *object : objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = reinterpret_cast(md); + const std::string bake_directory = bke::sim::get_bake_directory(*bmain, *object, *md); + if (BLI_exists(bake_directory.c_str())) { + if (BLI_delete(bake_directory.c_str(), true, true)) { + BKE_reportf(op->reports, + RPT_ERROR, + "Failed to remove bake directory %s", + bake_directory.c_str()); + } + } + if (nmd->simulation_cache != nullptr) { + nmd->simulation_cache->reset(); + } + } + } + + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + } + + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, nullptr); + + return OPERATOR_FINISHED; +} + +} // namespace blender::ed::object::bake_simulation + +void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot) +{ + using namespace blender::ed::object::bake_simulation; + + ot->name = "Bake Simulation"; + ot->description = "Bake simulations in geometry nodes modifiers"; + ot->idname = __func__; + + ot->invoke = bake_simulation_invoke; + ot->modal = bake_simulation_modal; + ot->poll = bake_simulation_poll; + + RNA_def_boolean(ot->srna, "selected", false, "Selected", "Bake cache on all selected objects"); +} + +void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot) +{ + using namespace blender::ed::object::bake_simulation; + + ot->name = "Delete Cached Simulation"; + ot->description = "Delete cached/baked simulations in geometry nodes modifiers"; + ot->idname = __func__; + + ot->exec = delete_baked_simulation_exec; + ot->poll = ED_operator_object_active; + + RNA_def_boolean(ot->srna, "selected", false, "Selected", "Delete cache on all selected objects"); +} diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 65b9674c6ee..78d0a3c83c1 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -338,6 +338,11 @@ void OBJECT_OT_collection_objects_select(struct wmOperatorType *ot); void OBJECT_OT_bake_image(wmOperatorType *ot); void OBJECT_OT_bake(wmOperatorType *ot); +/* object_bake_simulation.cc */ + +void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot); +void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot); + /* object_random.c */ void TRANSFORM_OT_vertex_random(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index 2e454c12d96..5785ddee5a7 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -260,6 +260,8 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_bake_image); WM_operatortype_append(OBJECT_OT_bake); + WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_bake); + WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_delete); WM_operatortype_append(OBJECT_OT_drop_named_material); WM_operatortype_append(OBJECT_OT_drop_geometry_nodes); WM_operatortype_append(OBJECT_OT_unlink_data); diff --git a/source/blender/editors/space_action/action_draw.cc b/source/blender/editors/space_action/action_draw.cc index 37ba92045d2..19b41f9873c 100644 --- a/source/blender/editors/space_action/action_draw.cc +++ b/source/blender/editors/space_action/action_draw.cc @@ -21,6 +21,7 @@ #include "DNA_anim_types.h" #include "DNA_cachefile_types.h" #include "DNA_gpencil_legacy_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -28,6 +29,7 @@ #include "BKE_action.h" #include "BKE_context.h" #include "BKE_pointcache.h" +#include "BKE_simulation_state.hh" /* Everything from source (BIF, BDR, BSE) ------------------------------ */ @@ -679,6 +681,51 @@ static void timeline_cache_draw_single(PTCacheID *pid, float y_offset, float hei GPU_matrix_pop(); } +static void timeline_cache_draw_simulation_nodes( + const Scene &scene, + const blender::bke::sim::ModifierSimulationCache &cache, + const float y_offset, + const float height, + const uint pos_id) +{ + GPU_matrix_push(); + GPU_matrix_translate_2f(0.0, (float)V2D_SCROLL_HANDLE_HEIGHT + y_offset); + GPU_matrix_scale_2f(1.0, height); + + float color[4]; + switch (cache.cache_state()) { + case blender::bke::sim::CacheState::Invalid: { + copy_v4_fl4(color, 0.8, 0.8, 0.2, 0.3); + break; + } + case blender::bke::sim::CacheState::Valid: { + copy_v4_fl4(color, 0.8, 0.8, 0.2, 1.0); + break; + } + case blender::bke::sim::CacheState::Baked: { + copy_v4_fl4(color, 1.0, 0.6, 0.2, 1.0); + break; + } + } + + immUniformColor4fv(color); + + const int start_frame = scene.r.sfra; + const int end_frame = scene.r.efra; + const int frames_num = end_frame - start_frame + 1; + const blender::IndexRange frames_range(start_frame, frames_num); + + immBeginAtMost(GPU_PRIM_TRIS, frames_num * 6); + for (const int frame : frames_range) { + if (cache.has_state_at_frame(frame)) { + immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f); + } + } + immEnd(); + + GPU_matrix_pop(); +} + void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Scene *scene) { if ((saction->cache_display & TIME_CACHE_DISPLAY) == 0 || ob == nullptr) { @@ -710,6 +757,16 @@ void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Sce y_offset += cache_draw_height; } + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Nodes) { + const NodesModifierData *nmd = reinterpret_cast(md); + if (nmd->simulation_cache != nullptr) { + timeline_cache_draw_simulation_nodes( + *scene, *nmd->simulation_cache, y_offset, cache_draw_height, pos_id); + y_offset += cache_draw_height; + } + } + } GPU_blend(GPU_BLEND_NONE); immUnbindProgram(); diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 4e2c4f8b8af..c6ffea163eb 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -12,6 +12,7 @@ set(INC ../../depsgraph ../../draw ../../functions + ../../geometry ../../gpu ../../imbuf ../../makesdna diff --git a/source/blender/editors/space_node/clipboard.cc b/source/blender/editors/space_node/clipboard.cc index 68f2f988bcb..7f23b12067e 100644 --- a/source/blender/editors/space_node/clipboard.cc +++ b/source/blender/editors/space_node/clipboard.cc @@ -16,6 +16,8 @@ #include "ED_render.h" #include "ED_screen.h" +#include "NOD_socket.h" + #include "RNA_access.h" #include "RNA_define.h" @@ -182,6 +184,33 @@ void NODE_OT_clipboard_copy(wmOperatorType *ot) /** \name Paste * \{ */ +static void remap_pairing(bNodeTree &dst_tree, const Map &node_map) +{ + /* We don't have the old tree for looking up output nodes by ID, + * so we have to build a map first to find copied output nodes in the new tree. */ + Map dst_output_node_map; + for (const auto &item : node_map.items()) { + if (item.key->type == GEO_NODE_SIMULATION_OUTPUT) { + dst_output_node_map.add_new(item.key->identifier, item.value); + } + } + + for (bNode *dst_node : node_map.values()) { + if (dst_node->type == GEO_NODE_SIMULATION_INPUT) { + NodeGeometrySimulationInput &data = *static_cast( + dst_node->storage); + if (const bNode *output_node = dst_output_node_map.lookup_default(data.output_node_id, + nullptr)) { + data.output_node_id = output_node->identifier; + } + else { + data.output_node_id = 0; + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + } + } +} + static int node_clipboard_paste_exec(bContext *C, wmOperator *op) { SpaceNode &snode = *CTX_wm_space_node(C); @@ -285,6 +314,12 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op) } } + for (bNode *new_node : node_map.values()) { + nodeDeclarationEnsure(&tree, new_node); + } + + remap_pairing(tree, node_map); + tree.ensure_topology_cache(); for (bNode *new_node : node_map.values()) { /* Update multi input socket indices in case all connected nodes weren't copied. */ diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 7c8de13ca69..18e7bf921f5 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1498,6 +1498,23 @@ void ED_init_node_socket_type_virtual(bNodeSocketType *stype) stype->draw_color = node_socket_virtual_draw_color; } +void ED_node_type_draw_color(const char *idname, float *r_color) +{ + using namespace blender::ed::space_node; + + const bNodeSocketType *typeinfo = nodeSocketTypeFind(idname); + if (!typeinfo || typeinfo->type == SOCK_CUSTOM) { + r_color[0] = 0.0f; + r_color[1] = 0.0f; + r_color[2] = 0.0f; + r_color[3] = 0.0f; + return; + } + + BLI_assert(typeinfo->type < ARRAY_SIZE(std_node_socket_colors)); + copy_v4_v4(r_color, std_node_socket_colors[typeinfo->type]); +} + namespace blender::ed::space_node { /* ************** Generic drawing ************** */ @@ -2220,8 +2237,8 @@ void node_draw_link(const bContext &C, node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3, selected); } -static std::array node_link_bezier_points_dragged(const SpaceNode &snode, - const bNodeLink &link) +std::array node_link_bezier_points_dragged(const SpaceNode &snode, + const bNodeLink &link) { const float2 cursor = snode.runtime->cursor * UI_SCALE_FAC; std::array points; diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 34ab80edb5d..590fc592a30 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -22,6 +22,7 @@ #include "DNA_world_types.h" #include "BLI_array.hh" +#include "BLI_convexhull_2d.h" #include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_span.hh" @@ -32,12 +33,15 @@ #include "BKE_compute_contexts.hh" #include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" #include "BKE_node_runtime.hh" #include "BKE_node_tree_update.h" +#include "BKE_node_tree_zones.hh" #include "BKE_object.h" #include "BKE_type_conversions.hh" @@ -80,11 +84,15 @@ #include "FN_field.hh" #include "FN_field_cpp_type.hh" +#include "GEO_fillet_curves.hh" + #include "../interface/interface_intern.hh" /* TODO: Remove */ #include "node_intern.hh" /* own include */ namespace geo_log = blender::nodes::geo_eval_log; +using blender::bke::node_tree_zones::TreeZone; +using blender::bke::node_tree_zones::TreeZones; /** * This is passed to many functions which draw the node editor. @@ -2149,7 +2157,9 @@ static void node_draw_basis(const bContext &C, } /* Shadow. */ - node_draw_shadow(snode, node, BASIS_RAD, 1.0f); + if (!ELEM(node.type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_SIMULATION_OUTPUT)) { + node_draw_shadow(snode, node, BASIS_RAD, 1.0f); + } const rctf &rct = node.runtime->totr; float color[4]; @@ -2423,6 +2433,10 @@ static void node_draw_basis(const bContext &C, else if (nodeTypeUndefined(&node)) { UI_GetThemeColor4fv(TH_REDALERT, color_outline); } + else if (ELEM(node.type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_SIMULATION_OUTPUT)) { + UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, color_outline); + color_outline[3] = 1.0f; + } else { UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline); } @@ -3036,6 +3050,173 @@ static void node_draw(const bContext &C, } } +static void add_rect_corner_positions(Vector &positions, const rctf &rect) +{ + positions.append({rect.xmin, rect.ymin}); + positions.append({rect.xmin, rect.ymax}); + positions.append({rect.xmax, rect.ymin}); + positions.append({rect.xmax, rect.ymax}); +} + +static void find_bounds_by_zone_recursive(const SpaceNode &snode, + const TreeZone &zone, + const Span> all_zones, + MutableSpan> r_bounds_by_zone) +{ + const float node_padding = UI_UNIT_X; + const float zone_padding = 0.3f * UI_UNIT_X; + + Vector &bounds = r_bounds_by_zone[zone.index]; + if (!bounds.is_empty()) { + return; + } + + Vector possible_bounds; + for (const TreeZone *child_zone : zone.child_zones) { + find_bounds_by_zone_recursive(snode, *child_zone, all_zones, r_bounds_by_zone); + const Span child_bounds = r_bounds_by_zone[child_zone->index]; + for (const float2 &pos : child_bounds) { + rctf rect; + BLI_rctf_init_pt_radius(&rect, pos, zone_padding); + add_rect_corner_positions(possible_bounds, rect); + } + } + for (const bNode *child_node : zone.child_nodes) { + rctf rect = child_node->runtime->totr; + BLI_rctf_pad(&rect, node_padding, node_padding); + add_rect_corner_positions(possible_bounds, rect); + } + if (zone.input_node) { + const rctf &totr = zone.input_node->runtime->totr; + rctf rect = totr; + BLI_rctf_pad(&rect, node_padding, node_padding); + rect.xmin = math::interpolate(totr.xmin, totr.xmax, 0.25f); + add_rect_corner_positions(possible_bounds, rect); + } + if (zone.output_node) { + const rctf &totr = zone.output_node->runtime->totr; + rctf rect = totr; + BLI_rctf_pad(&rect, node_padding, node_padding); + rect.xmax = math::interpolate(totr.xmin, totr.xmax, 0.75f); + add_rect_corner_positions(possible_bounds, rect); + } + + if (snode.runtime->linkdrag) { + for (const bNodeLink &link : snode.runtime->linkdrag->links) { + if (link.fromnode == nullptr) { + continue; + } + if (zone.contains_node_recursively(*link.fromnode) || zone.input_node == link.fromnode) { + const float2 pos = node_link_bezier_points_dragged(snode, link)[3]; + rctf rect; + BLI_rctf_init_pt_radius(&rect, pos, node_padding); + add_rect_corner_positions(possible_bounds, rect); + } + } + } + + Vector convex_indices(possible_bounds.size()); + const int convex_positions_num = BLI_convexhull_2d( + reinterpret_cast(possible_bounds.data()), + possible_bounds.size(), + convex_indices.data()); + convex_indices.resize(convex_positions_num); + + for (const int i : convex_indices) { + bounds.append(possible_bounds[i]); + } +} + +static void node_draw_zones(TreeDrawContext & /*tree_draw_ctx*/, + const ARegion ®ion, + const SpaceNode &snode, + const bNodeTree &ntree) +{ + const TreeZones *zones = bke::node_tree_zones::get_tree_zones(ntree); + if (zones == nullptr) { + return; + } + + Array> bounds_by_zone(zones->zones.size()); + Array fillet_curve_by_zone(zones->zones.size()); + + for (const int zone_i : zones->zones.index_range()) { + const TreeZone &zone = *zones->zones[zone_i]; + + find_bounds_by_zone_recursive(snode, zone, zones->zones, bounds_by_zone); + const Span boundary_positions = bounds_by_zone[zone_i]; + const int boundary_positions_num = boundary_positions.size(); + + bke::CurvesGeometry boundary_curve(boundary_positions_num, 1); + boundary_curve.cyclic_for_write().first() = true; + boundary_curve.fill_curve_types(CURVE_TYPE_POLY); + MutableSpan boundary_curve_positions = boundary_curve.positions_for_write(); + boundary_curve.offsets_for_write().copy_from({0, boundary_positions_num}); + for (const int i : boundary_positions.index_range()) { + boundary_curve_positions[i] = float3(boundary_positions[i], 0.0f); + } + + fillet_curve_by_zone[zone_i] = geometry::fillet_curves_poly( + boundary_curve, + IndexRange(1), + VArray::ForSingle(BASIS_RAD, boundary_positions_num), + VArray::ForSingle(5, boundary_positions_num), + true, + {}); + } + + const View2D &v2d = region.v2d; + float scale; + UI_view2d_scale_get(&v2d, &scale, nullptr); + float line_width = 1.0f * scale; + float viewport[4] = {}; + GPU_viewport_size_get_f(viewport); + float zone_color[4]; + UI_GetThemeColor4fv(TH_NODE_ZONE_SIMULATION, zone_color); + + const uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + /* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */ + for (const int zone_i : zones->zones.index_range()) { + if (zone_color[3] == 0.0f) { + break; + } + const Span fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions(); + /* Draw the background. */ + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformThemeColorBlend(TH_BACK, TH_NODE_ZONE_SIMULATION, zone_color[3]); + + immBegin(GPU_PRIM_TRI_FAN, fillet_boundary_positions.size() + 1); + for (const float3 &p : fillet_boundary_positions) { + immVertex3fv(pos, p); + } + immVertex3fv(pos, fillet_boundary_positions[0]); + immEnd(); + + immUnbindProgram(); + } + + for (const int zone_i : zones->zones.index_range()) { + const Span fillet_boundary_positions = fillet_curve_by_zone[zone_i].positions(); + /* Draw the contour lines. */ + immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); + + immUniform2fv("viewportSize", &viewport[2]); + immUniform1f("lineWidth", line_width * U.pixelsize); + + immUniformThemeColorAlpha(TH_NODE_ZONE_SIMULATION, 1.0f); + immBegin(GPU_PRIM_LINE_STRIP, fillet_boundary_positions.size() + 1); + for (const float3 &p : fillet_boundary_positions) { + immVertex3fv(pos, p); + } + immVertex3fv(pos, fillet_boundary_positions[0]); + immEnd(); + + immUnbindProgram(); + } +} + #define USE_DRAW_TOT_UPDATE static void node_draw_nodetree(const bContext &C, @@ -3203,6 +3384,7 @@ static void draw_nodetree(const bContext &C, } node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks); + node_draw_zones(tree_draw_ctx, region, *snode, ntree); node_draw_nodetree(C, tree_draw_ctx, region, *snode, ntree, nodes, blocks, parent_key); } @@ -3229,6 +3411,9 @@ static void draw_background_color(const SpaceNode &snode) void node_draw_space(const bContext &C, ARegion ®ion) { + if (G.is_rendering) { + return; + } wmWindow *win = CTX_wm_window(&C); SpaceNode &snode = *CTX_wm_space_node(&C); View2D &v2d = region.v2d; diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 30a0fabe672..9d0decae542 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -65,6 +65,7 @@ #include "NOD_composite.h" #include "NOD_geometry.h" #include "NOD_shader.h" +#include "NOD_socket.h" #include "NOD_texture.h" #include "node_intern.hh" /* own include */ @@ -1250,6 +1251,33 @@ static void node_duplicate_reparent_recursive(bNodeTree *ntree, } } +static void remap_pairing(bNodeTree &dst_tree, const Map &node_map) +{ + /* We don't have the old tree for looking up output nodes by ID, + * so we have to build a map first to find copied output nodes in the new tree. */ + Map dst_output_node_map; + for (const auto &item : node_map.items()) { + if (item.key->type == GEO_NODE_SIMULATION_OUTPUT) { + dst_output_node_map.add_new(item.key->identifier, item.value); + } + } + + for (bNode *dst_node : node_map.values()) { + if (dst_node->type == GEO_NODE_SIMULATION_INPUT) { + NodeGeometrySimulationInput *data = static_cast( + dst_node->storage); + if (const bNode *output_node = dst_output_node_map.lookup_default(data->output_node_id, + nullptr)) { + data->output_node_id = output_node->identifier; + } + else { + data->output_node_id = 0; + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + } + } +} + static int node_duplicate_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -1323,6 +1351,10 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) } } + for (bNode *node : node_map.values()) { + nodeDeclarationEnsure(ntree, node); + } + /* Clear flags for recursive depth-first iteration. */ for (bNode *node : ntree->all_nodes()) { node->flag &= ~NODE_TEST; @@ -1334,6 +1366,8 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) } } + remap_pairing(*ntree, node_map); + /* Deselect old nodes, select the copies instead. */ for (const auto item : node_map.items()) { bNode *src_node = item.key; @@ -1768,6 +1802,9 @@ static int node_delete_exec(bContext *C, wmOperator * /*op*/) ED_preview_kill_jobs(CTX_wm_manager(C), bmain); + /* Delete paired nodes as well. */ + node_select_paired(*snode->edittree); + LISTBASE_FOREACH_MUTABLE (bNode *, node, &snode->edittree->nodes) { if (node->flag & SELECT) { nodeRemoveNode(bmain, snode->edittree, node, true); @@ -1855,6 +1892,9 @@ static int node_delete_reconnect_exec(bContext *C, wmOperator * /*op*/) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); + /* Delete paired nodes as well. */ + node_select_paired(*snode->edittree); + LISTBASE_FOREACH_MUTABLE (bNode *, node, &snode->edittree->nodes) { if (node->flag & SELECT) { nodeInternalRelink(snode->edittree, node); diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index be31abb57df..4f4b5fcf3ce 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -440,6 +440,28 @@ void NODE_OT_group_ungroup(wmOperatorType *ot) /** \name Separate Operator * \{ */ +static void remap_pairing(bNodeTree &dst_tree, const Set &nodes) +{ + for (bNode *dst_node : nodes) { + if (dst_node->type == GEO_NODE_SIMULATION_INPUT) { + NodeGeometrySimulationInput &data = *static_cast( + dst_node->storage); + /* XXX Technically this is not correct because the output_node_id is only valid + * in the original node group tree and we'd have map old IDs to new nodes first. + * The ungroup operator does not build a node map, it just expects node IDs to + * remain unchanged, which is probably true most of the time because they are + * mostly random numbers out of the uint32_t range. */ + if (const bNode *output_node = dst_tree.node_by_id(data.output_node_id)) { + data.output_node_id = output_node->identifier; + } + else { + data.output_node_id = 0; + blender::nodes::update_node_declaration_and_sockets(dst_tree, *dst_node); + } + } + } +} + /** * \return True if successful. */ @@ -457,10 +479,13 @@ static bool node_group_separate_selected( nodes_to_move.remove_if( [](const bNode *node) { return node->is_group_input() || node->is_group_output(); }); + Set new_nodes; + for (bNode *node : nodes_to_move) { bNode *newnode; if (make_copy) { newnode = bke::node_copy_with_mapping(&ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map); + new_nodes.add_new(newnode); } else { newnode = node; @@ -525,6 +550,10 @@ static bool node_group_separate_selected( } } + for (bNode *node : new_nodes) { + nodeDeclarationEnsure(&ntree, node); + } + /* and copy across the animation, * note that the animation data's action can be nullptr here */ if (ngroup.adt) { @@ -537,6 +566,8 @@ static bool node_group_separate_selected( } } + remap_pairing(ntree, new_nodes); + BKE_ntree_update_tag_all(&ntree); if (!make_copy) { BKE_ntree_update_tag_all(&ngroup); diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index a67cf3aecd0..8a03fb37634 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -190,6 +190,10 @@ void node_socket_select(bNode *node, bNodeSocket &sock); void node_socket_deselect(bNode *node, bNodeSocket &sock, bool deselect_node); void node_deselect_all_input_sockets(bNodeTree &node_tree, bool deselect_nodes); void node_deselect_all_output_sockets(bNodeTree &node_tree, bool deselect_nodes); +/** + * Select nodes that are paired to a selected node. + */ +void node_select_paired(bNodeTree &node_tree); void node_select_single(bContext &C, bNode &node); void NODE_OT_select(wmOperatorType *ot); @@ -247,6 +251,8 @@ void node_draw_link_bezier(const bContext &C, int th_col3, bool selected); +std::array node_link_bezier_points_dragged(const SpaceNode &snode, + const bNodeLink &link); void node_link_bezier_points_evaluated(const bNodeLink &link, std::array &coords); diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index e07aadbe57d..d40ad77bcf3 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -309,6 +309,21 @@ void node_deselect_all_output_sockets(bNodeTree &node_tree, const bool deselect_ } } +void node_select_paired(bNodeTree &node_tree) +{ + for (bNode *input_node : node_tree.nodes_by_type("GeometryNodeSimulationInput")) { + const auto *storage = static_cast(input_node->storage); + if (bNode *output_node = node_tree.node_by_id(storage->output_node_id)) { + if (input_node->flag & NODE_SELECT) { + output_node->flag |= NODE_SELECT; + } + if (output_node->flag & NODE_SELECT) { + input_node->flag |= NODE_SELECT; + } + } + } +} + VectorSet get_selected_nodes(bNodeTree &node_tree) { VectorSet selected_nodes; diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index da65898f551..1fb67802460 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -10,6 +10,15 @@ #include "DNA_listBase.h" #include "DNA_session_uuid_types.h" +#ifdef __cplusplus +namespace blender::bke::sim { +class ModifierSimulationCache; +} +using ModifierSimulationCacheHandle = blender::bke::sim::ModifierSimulationCache; +#else +typedef struct ModifierSimulationCacheHandle ModifierSimulationCacheHandle; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -2316,7 +2325,8 @@ typedef struct NodesModifierData { * This can be used to help the user to debug a node tree. */ void *runtime_eval_log; - void *_pad1; + + ModifierSimulationCacheHandle *simulation_cache; } NodesModifierData; typedef struct MeshToVolumeModifierData { diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 41ca249505a..49eb4621191 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -16,6 +16,8 @@ #ifdef __cplusplus namespace blender { template class Span; +template class MutableSpan; +class IndexRange; class StringRef; class StringRefNull; } // namespace blender @@ -1594,6 +1596,39 @@ typedef struct NodeGeometryUVUnwrap { uint8_t method; } NodeGeometryUVUnwrap; +typedef struct NodeSimulationItem { + char *name; + /** #eNodeSocketDatatype. */ + short socket_type; + /** #eAttrDomain. */ + short attribute_domain; + /** + * Generates unique identifier for sockets which stays the same even when the item order or + * names change. + */ + int identifier; +} NodeSimulationItem; + +typedef struct NodeGeometrySimulationInput { + /** bNode.identifier of the corresponding output node. */ + int32_t output_node_id; +} NodeGeometrySimulationInput; + +typedef struct NodeGeometrySimulationOutput { + NodeSimulationItem *items; + int items_num; + int active_index; + /** Number to give unique IDs to state items. */ + int next_identifier; + int _pad; + +#ifdef __cplusplus + blender::Span items_span() const; + blender::MutableSpan items_span_for_write(); + blender::IndexRange items_range() const; +#endif +} NodeGeometrySimulationOutput; + typedef struct NodeGeometryDistributePointsInVolume { /* GeometryNodePointDistributeVolumeMode. */ uint8_t mode; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 805c5de70aa..e44b6f13dd7 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -344,6 +344,9 @@ typedef struct ThemeSpace { unsigned char nodeclass_pattern[4], nodeclass_layout[4]; unsigned char nodeclass_geometry[4], nodeclass_attribute[4]; + unsigned char node_zone_simulation[4]; + char _pad8[4]; + /** For sequence editor. */ unsigned char movie[4], movieclip[4], mask[4], image[4], scene[4], audio[4]; unsigned char effect[4], transition[4], meta[4], text_strip[4], color_strip[4]; diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index df72ae7b48e..0aca68bf2ad 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -216,6 +216,9 @@ bool RNA_enum_name(const EnumPropertyItem *item, int value, const char **r_name) bool RNA_enum_description(const EnumPropertyItem *item, int value, const char **description); int RNA_enum_from_value(const EnumPropertyItem *item, int value); int RNA_enum_from_identifier(const EnumPropertyItem *item, const char *identifier); +bool RNA_enum_value_from_identifier(const EnumPropertyItem *item, + const char *identifier, + int *r_value); /** * Take care using this with translated enums, * prefer #RNA_enum_from_identifier where possible. diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 6001dca7d2d..43413571ef8 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -1761,6 +1761,18 @@ int RNA_enum_from_identifier(const EnumPropertyItem *item, const char *identifie return -1; } +bool RNA_enum_value_from_identifier(const EnumPropertyItem *item, + const char *identifier, + int *r_value) +{ + const int i = RNA_enum_from_identifier(item, identifier); + if (i == -1) { + return false; + } + *r_value = item[i].value; + return true; +} + int RNA_enum_from_name(const EnumPropertyItem *item, const char *name) { int i = 0; diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 210c163f876..9de260c198a 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -52,6 +52,8 @@ #include "RE_texture.h" #include "NOD_composite.h" +#include "NOD_geometry.h" +#include "NOD_socket.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -4090,6 +4092,167 @@ static void rna_NodeCryptomatte_update_remove(Main *bmain, Scene *scene, Pointer rna_Node_update(bmain, scene, ptr); } +static void rna_SimulationStateItem_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + NodeSimulationItem *item = (NodeSimulationItem *)ptr->data; + bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item); + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); +} + +static bool rna_SimulationStateItem_socket_type_supported(const EnumPropertyItem *item) +{ + return NOD_geometry_simulation_output_item_socket_type_supported( + (eNodeSocketDatatype)item->value); +} + +static const EnumPropertyItem *rna_SimulationStateItem_socket_type_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + *r_free = true; + return itemf_function_check(node_socket_data_type_items, + rna_SimulationStateItem_socket_type_supported); +} + +static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + NodeSimulationItem *item = (NodeSimulationItem *)ptr->data; + bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item); + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + + const char *defname = nodeStaticSocketLabel(item->socket_type, 0); + NOD_geometry_simulation_output_item_set_unique_name(sim, item, value, defname); +} + +static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values) +{ + NodeSimulationItem *item = (NodeSimulationItem *)ptr->data; + + const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0); + ED_node_type_draw_color(socket_type_idname, values); +} + +static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *ptr) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNode *node = (bNode *)ptr->data; + bNode *output_node = NOD_geometry_simulation_input_get_paired_output(ntree, node); + PointerRNA r_ptr; + RNA_pointer_create(&ntree->id, &RNA_Node, output_node, &r_ptr); + return r_ptr; +} + +static bool rna_GeometryNodeSimulationInput_pair_with_output( + ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node) +{ + bNodeTree *ntree = (bNodeTree *)id; + + if (!NOD_geometry_simulation_input_pair_with_output(ntree, node, output_node)) { + BKE_reportf(reports, + RPT_ERROR, + "Failed to pair simulation input node %s with output node %s", + node->name, + output_node->name); + return false; + } + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + + return true; +} + +static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new( + ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) +{ + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + NodeSimulationItem *item = NOD_geometry_simulation_output_add_item( + sim, (short)socket_type, name); + + if (item == NULL) { + BKE_report(reports, RPT_ERROR, "Unable to create socket"); + } + else { + bNodeTree *ntree = (bNodeTree *)id; + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } + + return item; +} + +static void rna_NodeGeometrySimulationOutput_items_remove( + ID *id, bNode *node, Main *bmain, ReportList *reports, NodeSimulationItem *item) +{ + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + if (!NOD_geometry_simulation_output_contains_item(sim, item)) { + BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name); + } + else { + NOD_geometry_simulation_output_remove_item(sim, item); + + bNodeTree *ntree = (bNodeTree *)id; + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } +} + +static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Main *bmain) +{ + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + NOD_geometry_simulation_output_clear_items(sim); + + bNodeTree *ntree = (bNodeTree *)id; + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static void rna_NodeGeometrySimulationOutput_items_move( + ID *id, bNode *node, Main *bmain, int from_index, int to_index) +{ + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + + if (from_index < 0 || from_index >= sim->items_num || to_index < 0 || to_index >= sim->items_num) + { + return; + } + + NOD_geometry_simulation_output_move_item(sim, from_index, to_index); + + bNodeTree *ntree = (bNodeTree *)id; + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *ptr) +{ + bNode *node = (bNode *)ptr->data; + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + NodeSimulationItem *item = NOD_geometry_simulation_output_get_active_item(sim); + PointerRNA r_ptr; + RNA_pointer_create(ptr->owner_id, &RNA_SimulationStateItem, item, &r_ptr); + return r_ptr; +} + +static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr, + PointerRNA value, + ReportList *UNUSED(reports)) +{ + bNode *node = (bNode *)ptr->data; + NodeGeometrySimulationOutput *sim = (NodeGeometrySimulationOutput *)node->storage; + NOD_geometry_simulation_output_set_active_item(sim, (NodeSimulationItem *)value.data); +} + /* ******** Node Socket Types ******** */ static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter) @@ -9739,6 +9902,150 @@ static void def_geo_set_curve_normal(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_simulation_input(StructRNA *srna) +{ + PropertyRNA *prop; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_struct_sdna_from(srna, "NodeGeometrySimulationInput", "storage"); + + prop = RNA_def_property(srna, "paired_output", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "Node"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs( + prop, "rna_NodeGeometrySimulationInput_paired_output_get", NULL, NULL, NULL); + RNA_def_property_ui_text( + prop, "Paired Output", "Simulation output node that this input node is paired with"); + + func = RNA_def_function( + srna, "pair_with_output", "rna_GeometryNodeSimulationInput_pair_with_output"); + RNA_def_function_ui_description(func, "Pair a simulation input node with an output node."); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS | FUNC_USE_CONTEXT); + parm = RNA_def_pointer( + func, "output_node", "GeometryNode", "Output Node", "Simulation output node to pair with"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + /* return value */ + parm = RNA_def_boolean( + func, "result", false, "Result", "True if pairing the node was successful"); + RNA_def_function_return(func, parm); +} + +static void rna_def_simulation_state_item(BlenderRNA *brna) +{ + PropertyRNA *prop; + + StructRNA *srna = RNA_def_struct(brna, "SimulationStateItem", NULL); + RNA_def_struct_ui_text(srna, "Simulation Item", ""); + RNA_def_struct_sdna(srna, "NodeSimulationItem"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_SimulationStateItem_name_set"); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_update"); + + prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, node_socket_data_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_SimulationStateItem_socket_type_itemf"); + RNA_def_property_ui_text(prop, "Socket Type", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_update"); + + prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_ui_text( + prop, + "Attribute Domain", + "Attribute domain where the attribute domain is stored in the simulation state"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_update"); + + prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 4); + RNA_def_property_float_funcs(prop, "rna_SimulationStateItem_color_get", NULL, NULL); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Color", "Color of the corresponding socket type in the node editor"); +} + +static void rna_def_geo_simulation_output_items(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *parm; + FunctionRNA *func; + + srna = RNA_def_struct(brna, "NodeGeometrySimulationOutputItems", NULL); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Items", "Collection of simulation items"); + + func = RNA_def_function(srna, "new", "rna_NodeGeometrySimulationOutput_items_new"); + RNA_def_function_ui_description(func, "Add a item to this simulation zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_enum(func, + "socket_type", + node_socket_data_type_items, + SOCK_GEOMETRY, + "Socket Type", + "Socket type of the item"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + /* return value */ + parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "New item"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_NodeGeometrySimulationOutput_items_remove"); + RNA_def_function_ui_description(func, "Remove an item from this simulation zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "The item to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + + func = RNA_def_function(srna, "clear", "rna_NodeGeometrySimulationOutput_items_clear"); + RNA_def_function_ui_description(func, "Remove all items from this simulation zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + + func = RNA_def_function(srna, "move", "rna_NodeGeometrySimulationOutput_items_move"); + RNA_def_function_ui_description(func, "Move an item to another position"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + parm = RNA_def_int( + func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int( + func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); +} + +static void def_geo_simulation_output(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometrySimulationOutput", "storage"); + + prop = RNA_def_property(srna, "state_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "items", "items_num"); + RNA_def_property_struct_type(prop, "SimulationStateItem"); + RNA_def_property_ui_text(prop, "Items", ""); + RNA_def_property_srna(prop, "NodeGeometrySimulationOutputItems"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, NULL, "active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE, NULL); + + prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "SimulationStateItem"); + RNA_def_property_pointer_funcs(prop, + "rna_NodeGeometrySimulationOutput_active_item_get", + "rna_NodeGeometrySimulationOutput_active_item_set", + NULL, + NULL); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_update(prop, NC_NODE, NULL); +} + static void def_geo_curve_handle_type_selection(StructRNA *srna) { PropertyRNA *prop; @@ -13098,6 +13405,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_compositor_node(brna); rna_def_texture_node(brna); rna_def_geometry_node(brna); + rna_def_simulation_state_item(brna); rna_def_function_node(brna); rna_def_nodetree(brna); @@ -13159,6 +13467,7 @@ void RNA_def_nodetree(BlenderRNA *brna) /* special socket types */ rna_def_cmp_output_file_slot_file(brna); rna_def_cmp_output_file_slot_layer(brna); + rna_def_geo_simulation_output_items(brna); rna_def_node_instance_hash(brna); } diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 40fb9b138f5..9ad586733fb 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -3057,6 +3057,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna) RNA_def_property_array(prop, 3); RNA_def_property_ui_text(prop, "Attribute Node", ""); RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + + prop = RNA_def_property(srna, "simulation_zone", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, NULL, "node_zone_simulation"); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Simulation Zone", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); } static void rna_def_userdef_theme_space_buts(BlenderRNA *brna) diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 938f2ccd748..5bda6655c2b 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -54,6 +54,8 @@ #include "BKE_pointcloud.h" #include "BKE_screen.h" #include "BKE_simulation.h" +#include "BKE_simulation_state.hh" +#include "BKE_simulation_state_serialize.hh" #include "BKE_workspace.h" #include "BLO_read_write.h" @@ -304,6 +306,9 @@ static bool check_tree_for_time_node(const bNodeTree &tree, Set(node->id)) { if (check_tree_for_time_node(*sub_tree, checked_groups)) { @@ -1125,6 +1130,96 @@ static void store_output_attributes(GeometrySet &geometry, store_computed_output_attributes(geometry, attributes_to_store); } +static void prepare_simulation_states_for_evaluation(const NodesModifierData &nmd, + NodesModifierData &nmd_orig, + const ModifierEvalContext &ctx, + nodes::GeoNodesModifierData &exec_data) +{ + const Main *bmain = DEG_get_bmain(ctx.depsgraph); + const SubFrame current_frame = DEG_get_ctime(ctx.depsgraph); + const Scene *scene = DEG_get_input_scene(ctx.depsgraph); + const SubFrame start_frame = scene->r.sfra; + const bool is_start_frame = current_frame == start_frame; + + if (DEG_is_active(ctx.depsgraph)) { + if (nmd_orig.simulation_cache == nullptr) { + nmd_orig.simulation_cache = MEM_new(__func__); + } + + { + /* Try to use baked data. */ + const StringRefNull bmain_path = BKE_main_blendfile_path(bmain); + if (nmd_orig.simulation_cache->cache_state() != bke::sim::CacheState::Baked && + !bmain_path.is_empty()) + { + nmd_orig.simulation_cache->try_discover_bake( + bke::sim::get_meta_directory(*bmain, *ctx.object, nmd.modifier), + bke::sim::get_bdata_directory(*bmain, *ctx.object, nmd.modifier)); + } + } + + { + /* Reset cached data if necessary. */ + const bke::sim::StatesAroundFrame sim_states = + nmd_orig.simulation_cache->get_states_around_frame(current_frame); + if (nmd_orig.simulation_cache->cache_state() == bke::sim::CacheState::Invalid && + (current_frame == start_frame || + (sim_states.current == nullptr && sim_states.prev == nullptr && + sim_states.next != nullptr))) + { + nmd_orig.simulation_cache->reset(); + } + } + /* Decide if a new simulation state should be created in this evaluation. */ + const bke::sim::StatesAroundFrame sim_states = + nmd_orig.simulation_cache->get_states_around_frame(current_frame); + if (nmd_orig.simulation_cache->cache_state() != bke::sim::CacheState::Baked) { + if (sim_states.current == nullptr) { + if (is_start_frame || !nmd_orig.simulation_cache->has_states()) { + bke::sim::ModifierSimulationState ¤t_sim_state = + nmd_orig.simulation_cache->get_state_at_frame_for_write(current_frame); + exec_data.current_simulation_state_for_write = ¤t_sim_state; + exec_data.simulation_time_delta = 0.0f; + if (!is_start_frame) { + /* When starting a new simulation at another frame than the start frame, it can't match + * what would be baked, so invalidate it immediately. */ + nmd_orig.simulation_cache->invalidate(); + } + } + else if (sim_states.prev != nullptr && sim_states.next == nullptr) { + const float delta_frames = float(current_frame) - float(sim_states.prev->frame); + if (delta_frames <= 1.0f) { + bke::sim::ModifierSimulationState ¤t_sim_state = + nmd_orig.simulation_cache->get_state_at_frame_for_write(current_frame); + exec_data.current_simulation_state_for_write = ¤t_sim_state; + const float delta_seconds = delta_frames / FPS; + exec_data.simulation_time_delta = delta_seconds; + } + } + } + } + } + + /* Load read-only states to give nodes access to cached data. */ + const bke::sim::StatesAroundFrame sim_states = + nmd_orig.simulation_cache->get_states_around_frame(current_frame); + if (sim_states.current) { + sim_states.current->state.ensure_bake_loaded(); + exec_data.current_simulation_state = &sim_states.current->state; + } + if (sim_states.prev) { + sim_states.prev->state.ensure_bake_loaded(); + exec_data.prev_simulation_state = &sim_states.prev->state; + if (sim_states.next) { + sim_states.next->state.ensure_bake_loaded(); + exec_data.next_simulation_state = &sim_states.next->state; + exec_data.simulation_state_mix_factor = + (float(current_frame) - float(sim_states.prev->frame)) / + (float(sim_states.next->frame) - float(sim_states.prev->frame)); + } + } +} + /** * Evaluate a node group to compute the output geometry. */ @@ -1135,6 +1230,9 @@ static GeometrySet compute_geometry(const bNodeTree &btree, NodesModifierData *nmd, const ModifierEvalContext *ctx) { + NodesModifierData *nmd_orig = reinterpret_cast( + BKE_modifier_get_original(ctx->object, &nmd->modifier)); + const nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping; Vector graph_inputs = mapping.group_input_sockets; @@ -1160,6 +1258,8 @@ static GeometrySet compute_geometry(const bNodeTree &btree, geo_nodes_modifier_data.self_object = ctx->object; auto eval_log = std::make_unique(); + prepare_simulation_states_for_evaluation(*nmd, *nmd_orig, *ctx, geo_nodes_modifier_data); + Set socket_log_contexts; if (logging_enabled(ctx)) { geo_nodes_modifier_data.eval_log = eval_log.get(); @@ -1240,8 +1340,6 @@ static GeometrySet compute_geometry(const bNodeTree &btree, } if (logging_enabled(ctx)) { - NodesModifierData *nmd_orig = reinterpret_cast( - BKE_modifier_get_original(ctx->object, &nmd->modifier)); delete static_cast(nmd_orig->runtime_eval_log); nmd_orig->runtime_eval_log = eval_log.release(); } @@ -1928,6 +2026,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) IDP_BlendDataRead(reader, &nmd->settings.properties); } nmd->runtime_eval_log = nullptr; + nmd->simulation_cache = nullptr; } static void copyData(const ModifierData *md, ModifierData *target, const int flag) @@ -1938,6 +2037,7 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla BKE_modifier_copydata_generic(md, target, flag); tnmd->runtime_eval_log = nullptr; + tnmd->simulation_cache = nullptr; if (nmd->settings.properties != nullptr) { tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag); @@ -1952,6 +2052,8 @@ static void freeData(ModifierData *md) nmd->settings.properties = nullptr; } + MEM_delete(nmd->simulation_cache); + clear_runtime_data(nmd); } diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 357258118a9..c0360168deb 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -12,6 +12,74 @@ extern struct bNodeTreeType *ntreeType_Geometry; void register_node_type_geo_custom_group(bNodeType *ntype); +/* -------------------------------------------------------------------- */ +/** \name Simulation Input Node + * \{ */ + +struct bNode *NOD_geometry_simulation_input_get_paired_output( + struct bNodeTree *node_tree, const struct bNode *simulation_input_node); + +/** + * Pair a simulation input node with an output node. + * \return True if pairing the node was successful. + */ +bool NOD_geometry_simulation_input_pair_with_output(const struct bNodeTree *node_tree, + struct bNode *simulation_input_node, + const struct bNode *simulation_output_node); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Simulation Output Node + * \{ */ + +bool NOD_geometry_simulation_output_item_socket_type_supported(eNodeSocketDatatype socket_type); + +/** + * Set a unique item name. + * \return True if the unique name differs from the original name. + */ +bool NOD_geometry_simulation_output_item_set_unique_name(struct NodeGeometrySimulationOutput *sim, + struct NodeSimulationItem *item, + const char *name, + const char *defname); + +/** + * Find the node owning this simulation state item. + */ +bNode *NOD_geometry_simulation_output_find_node_by_item(struct bNodeTree *ntree, + const struct NodeSimulationItem *item); + +bool NOD_geometry_simulation_output_contains_item(struct NodeGeometrySimulationOutput *sim, + const struct NodeSimulationItem *item); +struct NodeSimulationItem *NOD_geometry_simulation_output_get_active_item( + struct NodeGeometrySimulationOutput *sim); +void NOD_geometry_simulation_output_set_active_item(struct NodeGeometrySimulationOutput *sim, + struct NodeSimulationItem *item); +struct NodeSimulationItem *NOD_geometry_simulation_output_find_item( + struct NodeGeometrySimulationOutput *sim, const char *name); +struct NodeSimulationItem *NOD_geometry_simulation_output_add_item( + struct NodeGeometrySimulationOutput *sim, short socket_type, const char *name); +struct NodeSimulationItem *NOD_geometry_simulation_output_insert_item( + struct NodeGeometrySimulationOutput *sim, short socket_type, const char *name, int index); +struct NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket( + struct NodeGeometrySimulationOutput *sim, + const struct bNode *from_node, + const struct bNodeSocket *from_sock); +struct NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket( + struct NodeGeometrySimulationOutput *sim, + const struct bNode *from_node, + const struct bNodeSocket *from_sock, + int index); +void NOD_geometry_simulation_output_remove_item(struct NodeGeometrySimulationOutput *sim, + struct NodeSimulationItem *item); +void NOD_geometry_simulation_output_clear_items(struct NodeGeometrySimulationOutput *sim); +void NOD_geometry_simulation_output_move_item(struct NodeGeometrySimulationOutput *sim, + int from_index, + int to_index); + +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 6a584662553..d6364d44417 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -26,6 +26,8 @@ #include "BLI_compute_context.hh" +#include "BKE_simulation_state.hh" + struct Object; struct Depsgraph; @@ -44,6 +46,16 @@ struct GeoNodesModifierData { Depsgraph *depsgraph = nullptr; /** Optional logger. */ geo_eval_log::GeoModifierLog *eval_log = nullptr; + + /** Read-only simulation states around the current frame. */ + const bke::sim::ModifierSimulationState *current_simulation_state = nullptr; + const bke::sim::ModifierSimulationState *prev_simulation_state = nullptr; + const bke::sim::ModifierSimulationState *next_simulation_state = nullptr; + float simulation_state_mix_factor = 0.0f; + /** Used when the evaluation should create a new simulation state. */ + bke::sim::ModifierSimulationState *current_simulation_state_for_write = nullptr; + float simulation_time_delta = 0.0f; + /** * Some nodes should be executed even when their output is not used (e.g. active viewer nodes and * the node groups they are contained in). @@ -149,6 +161,7 @@ struct GeometryNodeLazyFunctionGraphMapping { */ Map group_node_map; Map viewer_node_map; + Map sim_output_node_map; /* Indexed by #bNodeSocket::index_in_all_outputs. */ Array lf_input_index_for_output_bsocket_usage; @@ -228,8 +241,34 @@ class GeometryNodesLazyFunctionLogger : public fn::lazy_function::GraphExecutor: const lf::Context &context) const override; }; +std::unique_ptr get_simulation_output_lazy_function( + const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); +std::unique_ptr get_simulation_input_lazy_function( + const bNodeTree &node_tree, + const bNode &node, + GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); std::unique_ptr get_switch_node_lazy_function(const bNode &node); +bke::sim::SimulationZoneID get_simulation_zone_id(const ComputeContext &context, + const int output_node_id); + +/** + * An anonymous attribute created by a node. + */ +class NodeAnonymousAttributeID : public bke::AnonymousAttributeID { + std::string long_name_; + std::string socket_name_; + + public: + NodeAnonymousAttributeID(const Object &object, + const ComputeContext &compute_context, + const bNode &bnode, + const StringRef identifier, + const StringRef name); + + std::string user_name() const override; +}; + /** * Tells the lazy-function graph evaluator which nodes have side effects based on the current * context. For example, the same viewer node can have side effects in one context, but not in diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 1bd9896261e..fc03ca39d6b 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -419,6 +419,8 @@ DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Se DefNode(GeometryNode, GEO_NODE_SET_SHADE_SMOOTH, 0, "SET_SHADE_SMOOTH", SetShadeSmooth, "Set Shade Smooth", "Control the smoothness of mesh normals around each face by changing the \"shade smooth\" attribute") DefNode(GeometryNode, GEO_NODE_SET_SPLINE_CYCLIC, 0, "SET_SPLINE_CYCLIC", SetSplineCyclic, "Set Spline Cyclic", "Control whether each spline loops back on itself by changing the \"cyclic\" attribute") DefNode(GeometryNode, GEO_NODE_SET_SPLINE_RESOLUTION, 0, "SET_SPLINE_RESOLUTION", SetSplineResolution, "Set Spline Resolution", "Control how many evaluated points should be generated on every curve segment") +DefNode(GeometryNode, GEO_NODE_SIMULATION_INPUT, def_geo_simulation_input, "SIMULATION_INPUT", SimulationInput, "Simulation Input", "") +DefNode(GeometryNode, GEO_NODE_SIMULATION_OUTPUT, def_geo_simulation_output, "SIMULATION_OUTPUT", SimulationOutput, "Simulation Output", "") DefNode(GeometryNode, GEO_NODE_SPLIT_EDGES, 0, "SPLIT_EDGES", SplitEdges, "Split Edges", "Duplicate mesh edges and break connections with the surrounding faces") DefNode(GeometryNode, GEO_NODE_STORE_NAMED_ATTRIBUTE, def_geo_store_named_attribute, "STORE_NAMED_ATTRIBUTE", StoreNamedAttribute, "Store Named Attribute", "Store the result of a field on a geometry as an attribute with the specified name") DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "Join Strings", "Combine any number of input strings") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 93c7165e839..0e675fe58df 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -170,6 +170,8 @@ set(SRC nodes/node_geo_set_shade_smooth.cc nodes/node_geo_set_spline_cyclic.cc nodes/node_geo_set_spline_resolution.cc + nodes/node_geo_simulation_input.cc + nodes/node_geo_simulation_output.cc nodes/node_geo_store_named_attribute.cc nodes/node_geo_string_join.cc nodes/node_geo_string_to_curves.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index ef78585a132..acf5c5981f1 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -154,6 +154,8 @@ void register_geometry_nodes() register_node_type_geo_set_shade_smooth(); register_node_type_geo_set_spline_cyclic(); register_node_type_geo_set_spline_resolution(); + register_node_type_geo_simulation_input(); + register_node_type_geo_simulation_output(); register_node_type_geo_store_named_attribute(); register_node_type_geo_string_join(); register_node_type_geo_string_to_curves(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index d3a77f9bb93..ff69c68a9b0 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -152,6 +152,8 @@ void register_node_type_geo_set_position(); void register_node_type_geo_set_shade_smooth(); void register_node_type_geo_set_spline_cyclic(); void register_node_type_geo_set_spline_resolution(); +void register_node_type_geo_simulation_input(); +void register_node_type_geo_simulation_output(); void register_node_type_geo_store_named_attribute(); void register_node_type_geo_string_join(); void register_node_type_geo_string_to_curves(); diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index c6524762207..7d94fe13195 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -133,4 +133,20 @@ class FieldAtIndexInput final : public bke::GeometryFieldInput { } }; +std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item); + +void socket_declarations_for_simulation_items(Span items, + NodeDeclaration &r_declaration); +const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type); +const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item); +void values_to_simulation_state(const Span node_simulation_items, + const Span input_values, + bke::sim::SimulationZoneState &r_zone_state); +void simulation_state_to_values(const Span node_simulation_items, + const bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &sim_output_node, + Span r_output_values); + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc new file mode 100644 index 00000000000..045a610f9b5 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_compute_contexts.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_geometry.h" +#include "NOD_socket.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_simulation_input_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySimulationInput); + +class LazyFunctionForSimulationInputNode final : public LazyFunction { + const bNode &node_; + int32_t output_node_id_; + Span simulation_items_; + + public: + LazyFunctionForSimulationInputNode(const bNodeTree &node_tree, + const bNode &node, + GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info) + : node_(node) + { + debug_name_ = "Simulation Input"; + output_node_id_ = node_storage(node).output_node_id; + const bNode &output_node = *node_tree.node_by_id(output_node_id_); + const NodeGeometrySimulationOutput &storage = *static_cast( + output_node.storage); + simulation_items_ = {storage.items, storage.items_num}; + + MutableSpan lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket; + lf_index_by_bsocket[node.output_socket(0).index_in_tree()] = outputs_.append_and_get_index_as( + "Delta Time", CPPType::get>()); + + for (const int i : simulation_items_.index_range()) { + const NodeSimulationItem &item = simulation_items_[i]; + const bNodeSocket &input_bsocket = node.input_socket(i); + const bNodeSocket &output_bsocket = node.output_socket(i + 1); + + const CPPType &type = get_simulation_item_cpp_type(item); + + lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as( + item.name, type, lf::ValueUsage::Maybe); + lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as( + item.name, type); + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const final + { + const GeoNodesLFUserData &user_data = *static_cast( + context.user_data); + const GeoNodesModifierData &modifier_data = *user_data.modifier_data; + if (modifier_data.current_simulation_state == nullptr) { + params.set_default_remaining_outputs(); + return; + } + + if (!params.output_was_set(0)) { + const float delta_time = modifier_data.simulation_time_delta; + params.set_output(0, fn::ValueOrField(delta_time)); + } + + const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(*user_data.compute_context, + output_node_id_); + + const bke::sim::SimulationZoneState *prev_zone_state = + modifier_data.prev_simulation_state == nullptr ? + nullptr : + modifier_data.prev_simulation_state->get_zone_state(zone_id); + + std::optional initial_prev_zone_state; + if (prev_zone_state == nullptr) { + Array input_values(simulation_items_.size(), nullptr); + for (const int i : simulation_items_.index_range()) { + input_values[i] = params.try_get_input_data_ptr_or_request(i); + } + if (input_values.as_span().contains(nullptr)) { + /* Wait until all inputs are available. */ + return; + } + + initial_prev_zone_state.emplace(); + values_to_simulation_state(simulation_items_, input_values, *initial_prev_zone_state); + prev_zone_state = &*initial_prev_zone_state; + } + + Array output_values(simulation_items_.size()); + for (const int i : simulation_items_.index_range()) { + output_values[i] = params.get_output_data_ptr(i + 1); + } + simulation_state_to_values(simulation_items_, + *prev_zone_state, + *modifier_data.self_object, + *user_data.compute_context, + node_, + output_values); + for (const int i : simulation_items_.index_range()) { + params.output_set(i + 1); + } + } +}; + +} // namespace blender::nodes::node_geo_simulation_input_cc + +namespace blender::nodes { + +std::unique_ptr get_simulation_input_lazy_function( + const bNodeTree &node_tree, + const bNode &node, + GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info) +{ + namespace file_ns = blender::nodes::node_geo_simulation_input_cc; + BLI_assert(node.type == GEO_NODE_SIMULATION_INPUT); + return std::make_unique( + node_tree, node, own_lf_graph_info); +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_simulation_input_cc { + +static void node_declare_dynamic(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration) +{ + const bNode *output_node = node_tree.node_by_id(node_storage(node).output_node_id); + if (!output_node) { + return; + } + + std::unique_ptr delta_time = std::make_unique(); + delta_time->identifier = "Delta Time"; + delta_time->name = DATA_("Delta Time"); + delta_time->in_out = SOCK_OUT; + r_declaration.outputs.append(std::move(delta_time)); + + const NodeGeometrySimulationOutput &storage = *static_cast( + output_node->storage); + socket_declarations_for_simulation_items({storage.items, storage.items_num}, r_declaration); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometrySimulationInput *data = MEM_cnew(__func__); + /* Needs to be initialized for the node to work. */ + data->output_node_id = 0; + node->storage = data; +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + const bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); + if (!output_node) { + return true; + } + + NodeGeometrySimulationOutput &storage = *static_cast( + output_node->storage); + + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->fromsock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket( + node, SOCK_IN, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + else { + BLI_assert(link->fromnode == node); + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->tonode, link->tosock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket( + node, SOCK_OUT, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + return true; +} + +} // namespace blender::nodes::node_geo_simulation_input_cc + +void register_node_type_geo_simulation_input() +{ + namespace file_ns = blender::nodes::node_geo_simulation_input_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_SIMULATION_INPUT, "Simulation Input", NODE_CLASS_INTERFACE); + ntype.initfunc = file_ns::node_init; + ntype.declare_dynamic = file_ns::node_declare_dynamic; + ntype.insert_link = file_ns::node_insert_link; + ntype.gather_add_node_search_ops = nullptr; + ntype.gather_link_search_ops = nullptr; + node_type_storage(&ntype, + "NodeGeometrySimulationInput", + node_free_standard_storage, + node_copy_standard_storage); + nodeRegisterType(&ntype); +} + +bNode *NOD_geometry_simulation_input_get_paired_output(bNodeTree *node_tree, + const bNode *simulation_input_node) +{ + namespace file_ns = blender::nodes::node_geo_simulation_input_cc; + + const NodeGeometrySimulationInput &data = file_ns::node_storage(*simulation_input_node); + return node_tree->node_by_id(data.output_node_id); +} + +bool NOD_geometry_simulation_input_pair_with_output(const bNodeTree *node_tree, + bNode *sim_input_node, + const bNode *sim_output_node) +{ + namespace file_ns = blender::nodes::node_geo_simulation_input_cc; + + BLI_assert(sim_input_node->type == GEO_NODE_SIMULATION_INPUT); + if (sim_output_node->type != GEO_NODE_SIMULATION_OUTPUT) { + return false; + } + + /* Allow only one input paired to an output. */ + for (const bNode *other_input_node : node_tree->nodes_by_type("GeometryNodeSimulationInput")) { + if (other_input_node != sim_input_node) { + const NodeGeometrySimulationInput &other_storage = file_ns::node_storage(*other_input_node); + if (other_storage.output_node_id == sim_output_node->identifier) { + return false; + } + } + } + + NodeGeometrySimulationInput &storage = file_ns::node_storage(*sim_input_node); + storage.output_node_id = sim_output_node->identifier; + return true; +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc new file mode 100644 index 00000000000..a116e02e4dd --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -0,0 +1,893 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_string_utils.h" + +#include "BKE_compute_contexts.hh" +#include "BKE_curves.hh" +#include "BKE_instances.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" + +#include "NOD_common.h" +#include "NOD_socket.h" + +#include "FN_field_cpp_type.hh" + +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +#include "NOD_add_node_search.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item) +{ + return "Item_" + std::to_string(item.identifier); +} + +static std::unique_ptr socket_declaration_for_simulation_item( + const NodeSimulationItem &item, const eNodeSocketInOut in_out, const int index) +{ + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + BLI_assert(NOD_geometry_simulation_output_item_socket_type_supported(socket_type)); + + std::unique_ptr decl; + switch (socket_type) { + case SOCK_FLOAT: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index}); + break; + case SOCK_VECTOR: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index}); + break; + case SOCK_RGBA: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index}); + break; + case SOCK_BOOLEAN: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index}); + break; + case SOCK_INT: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index}); + break; + case SOCK_STRING: + decl = std::make_unique(); + break; + case SOCK_GEOMETRY: + decl = std::make_unique(); + break; + default: + BLI_assert_unreachable(); + } + + decl->name = item.name ? item.name : ""; + decl->identifier = socket_identifier_for_simulation_item(item); + decl->in_out = in_out; + return decl; +} + +void socket_declarations_for_simulation_items(const Span items, + NodeDeclaration &r_declaration) +{ + for (const int i : items.index_range()) { + const NodeSimulationItem &item = items[i]; + r_declaration.inputs.append(socket_declaration_for_simulation_item(item, SOCK_IN, i)); + r_declaration.outputs.append(socket_declaration_for_simulation_item(item, SOCK_OUT, i)); + } + r_declaration.inputs.append(decl::create_extend_declaration(SOCK_IN)); + r_declaration.outputs.append(decl::create_extend_declaration(SOCK_OUT)); +} + +struct SimulationItemsUniqueNameArgs { + NodeGeometrySimulationOutput *sim; + const NodeSimulationItem *item; +}; + +static bool simulation_items_unique_name_check(void *arg, const char *name) +{ + const SimulationItemsUniqueNameArgs &args = *static_cast( + arg); + for (const NodeSimulationItem &item : args.sim->items_span()) { + if (&item != args.item) { + if (STREQ(item.name, name)) { + return true; + } + } + } + if (STREQ(name, "Delta Time")) { + return true; + } + return false; +} + +const CPPType &get_simulation_item_cpp_type(const eNodeSocketDatatype socket_type) +{ + const char *socket_idname = nodeStaticSocketType(socket_type, 0); + const bNodeSocketType *typeinfo = nodeSocketTypeFind(socket_idname); + BLI_assert(typeinfo); + BLI_assert(typeinfo->geometry_nodes_cpp_type); + return *typeinfo->geometry_nodes_cpp_type; +} + +const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item) +{ + return get_simulation_item_cpp_type(eNodeSocketDatatype(item.socket_type)); +} + +static void cleanup_geometry_for_simulation_state(GeometrySet &main_geometry) +{ + main_geometry.modify_geometry_sets([&](GeometrySet &geometry) { + if (Mesh *mesh = geometry.get_mesh_for_write()) { + mesh->attributes_for_write().remove_anonymous(); + } + if (Curves *curves = geometry.get_curves_for_write()) { + curves->geometry.wrap().attributes_for_write().remove_anonymous(); + } + if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { + pointcloud->attributes_for_write().remove_anonymous(); + } + if (bke::Instances *instances = geometry.get_instances_for_write()) { + instances->attributes_for_write().remove_anonymous(); + } + geometry.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_INSTANCES}); + }); +} + +void simulation_state_to_values(const Span node_simulation_items, + const bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &node, + Span r_output_values) +{ + /* Some attributes stored in the simulation state become anonymous attributes in geometry nodes. + * This maps attribute names to their corresponding anonymous attribute ids. */ + Map attribute_map; + Vector geometries; + + for (const int i : node_simulation_items.index_range()) { + const NodeSimulationItem &item = node_simulation_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const CPPType &cpp_type = get_simulation_item_cpp_type(socket_type); + + void *r_output_value = r_output_values[i]; + + if (!zone_state.item_by_identifier.contains(item.identifier)) { + cpp_type.value_initialize(r_output_value); + continue; + } + const bke::sim::SimulationStateItem &state_item = *zone_state.item_by_identifier.lookup( + item.identifier); + + switch (socket_type) { + case SOCK_GEOMETRY: { + if (const auto *geo_state_item = + dynamic_cast(&state_item)) + { + GeometrySet *geometry = new (r_output_value) GeometrySet(geo_state_item->geometry()); + geometries.append(geometry); + } + else { + cpp_type.value_initialize(r_output_value); + } + break; + } + case SOCK_FLOAT: + case SOCK_VECTOR: + case SOCK_INT: + case SOCK_BOOLEAN: + case SOCK_RGBA: { + const fn::ValueOrFieldCPPType &value_or_field_type = + *fn::ValueOrFieldCPPType::get_from_self(cpp_type); + if (const auto *primitive_state_item = + dynamic_cast(&state_item)) + { + if (primitive_state_item->type() == value_or_field_type.value) { + value_or_field_type.construct_from_value(r_output_value, + primitive_state_item->value()); + } + else { + cpp_type.value_initialize(r_output_value); + } + } + else if (const auto *attribute_state_item = + dynamic_cast(&state_item)) + { + AnonymousAttributeIDPtr attribute_id = MEM_new( + __func__, + self_object, + compute_context, + node, + std::to_string(item.identifier), + item.name); + GField field{std::make_shared( + attribute_id, value_or_field_type.value, node.label_or_name())}; + value_or_field_type.construct_from_field(r_output_value, std::move(field)); + attribute_map.add(attribute_state_item->name(), std::move(attribute_id)); + } + else { + cpp_type.value_initialize(r_output_value); + } + break; + } + case SOCK_STRING: { + if (const auto *string_state_item = + dynamic_cast(&state_item)) + { + new (r_output_value) ValueOrField(string_state_item->value()); + } + else { + cpp_type.value_initialize(r_output_value); + } + break; + } + default: { + cpp_type.value_initialize(r_output_value); + break; + } + } + } + + /* Make some attributes anonymous. */ + for (GeometrySet *geometry : geometries) { + for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_INSTANCES}) + { + if (!geometry->has(type)) { + continue; + } + GeometryComponent &component = geometry->get_component_for_write(type); + MutableAttributeAccessor attributes = *component.attributes_for_write(); + for (const MapItem &attribute_item : + attribute_map.items()) { + attributes.rename(attribute_item.key, *attribute_item.value); + } + } + } +} + +void values_to_simulation_state(const Span node_simulation_items, + const Span input_values, + bke::sim::SimulationZoneState &r_zone_state) +{ + Vector stored_geometries; + + for (const int i : node_simulation_items.index_range()) { + const NodeSimulationItem &item = node_simulation_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + void *input_value = input_values[i]; + + std::unique_ptr state_item; + switch (socket_type) { + case SOCK_GEOMETRY: { + GeometrySet &geometry = *static_cast(input_value); + auto geometry_state_item = std::make_unique( + std::move(geometry)); + stored_geometries.append(&geometry_state_item->geometry()); + state_item = std::move(geometry_state_item); + break; + } + case SOCK_FLOAT: + case SOCK_VECTOR: + case SOCK_INT: + case SOCK_BOOLEAN: + case SOCK_RGBA: { + const CPPType &type = get_simulation_item_cpp_type(item); + const fn::ValueOrFieldCPPType &value_or_field_type = + *fn::ValueOrFieldCPPType::get_from_self(type); + if (value_or_field_type.is_field(input_value)) { + /* Fields are evaluated and stored as attributes. */ + if (!stored_geometries.is_empty()) { + /* Possible things to consider: + * - Store attributes on multiple/all geometries. + * - If the attribute is an anonymous attribute, just rename it for the simulation + * state, without considering the domain. This would allow e.g. having the attribute + * only on some parts of the geometry set. + */ + GeometrySet &geometry = *stored_geometries.last(); + const GField &field = *value_or_field_type.get_field_ptr(input_value); + const eAttrDomain domain = eAttrDomain(item.attribute_domain); + const std::string attribute_name = ".sim_" + std::to_string(item.identifier); + if (geometry.has_pointcloud()) { + PointCloudComponent &component = + geometry.get_component_for_write(); + bke::try_capture_field_on_geometry(component, attribute_name, domain, field); + } + if (geometry.has_mesh()) { + MeshComponent &component = geometry.get_component_for_write(); + bke::try_capture_field_on_geometry(component, attribute_name, domain, field); + } + if (geometry.has_curves()) { + CurveComponent &component = geometry.get_component_for_write(); + bke::try_capture_field_on_geometry(component, attribute_name, domain, field); + } + if (geometry.has_instances()) { + InstancesComponent &component = + geometry.get_component_for_write(); + bke::try_capture_field_on_geometry(component, attribute_name, domain, field); + } + state_item = std::make_unique(attribute_name); + } + } + else { + const void *value = value_or_field_type.get_value_ptr(input_value); + state_item = std::make_unique( + value_or_field_type.value, value); + } + break; + } + case SOCK_STRING: { + const ValueOrField &value = *static_cast *>( + input_value); + state_item = std::make_unique(value.as_value()); + break; + } + default: + break; + } + + if (state_item) { + r_zone_state.item_by_identifier.add_new(item.identifier, std::move(state_item)); + } + } + + for (GeometrySet *geometry : stored_geometries) { + cleanup_geometry_for_simulation_state(*geometry); + geometry->ensure_owns_all_data(); + } +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_simulation_output_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySimulationOutput); + +struct EvalData { + bool is_first_evaluation = true; +}; + +class LazyFunctionForSimulationOutputNode final : public LazyFunction { + const bNode &node_; + Span simulation_items_; + + public: + LazyFunctionForSimulationOutputNode(const bNode &node, + GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info) + : node_(node) + { + debug_name_ = "Simulation Output"; + const NodeGeometrySimulationOutput &storage = node_storage(node); + simulation_items_ = {storage.items, storage.items_num}; + + MutableSpan lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket; + + for (const int i : simulation_items_.index_range()) { + const NodeSimulationItem &item = simulation_items_[i]; + const bNodeSocket &input_bsocket = node.input_socket(i); + const bNodeSocket &output_bsocket = node.output_socket(i); + + const CPPType &type = get_simulation_item_cpp_type(item); + + lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as( + item.name, type, lf::ValueUsage::Maybe); + lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as( + item.name, type); + } + } + + void *init_storage(LinearAllocator<> &allocator) const + { + return allocator.construct().get(); + } + + void destruct_storage(void *storage) const + { + std::destroy_at(static_cast(storage)); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const final + { + GeoNodesLFUserData &user_data = *static_cast(context.user_data); + GeoNodesModifierData &modifier_data = *user_data.modifier_data; + EvalData &eval_data = *static_cast(context.storage); + BLI_SCOPED_DEFER([&]() { eval_data.is_first_evaluation = false; }); + + const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(*user_data.compute_context, + node_.identifier); + + const bke::sim::SimulationZoneState *current_zone_state = + modifier_data.current_simulation_state ? + modifier_data.current_simulation_state->get_zone_state(zone_id) : + nullptr; + if (eval_data.is_first_evaluation && current_zone_state != nullptr) { + /* Common case when data is cached already. */ + this->output_cached_state( + params, *modifier_data.self_object, *user_data.compute_context, *current_zone_state); + return; + } + + if (modifier_data.current_simulation_state_for_write == nullptr) { + const bke::sim::SimulationZoneState *prev_zone_state = + modifier_data.prev_simulation_state ? + modifier_data.prev_simulation_state->get_zone_state(zone_id) : + nullptr; + if (prev_zone_state == nullptr) { + /* There is no previous simulation state and we also don't create a new one, so just output + * defaults. */ + params.set_default_remaining_outputs(); + return; + } + const bke::sim::SimulationZoneState *next_zone_state = + modifier_data.next_simulation_state ? + modifier_data.next_simulation_state->get_zone_state(zone_id) : + nullptr; + if (next_zone_state == nullptr) { + /* Output the last cached simulation state. */ + this->output_cached_state( + params, *modifier_data.self_object, *user_data.compute_context, *prev_zone_state); + return; + } + /* A previous and next frame is cached already, but the current frame is not. */ + this->output_mixed_cached_state(params, + *modifier_data.self_object, + *user_data.compute_context, + *prev_zone_state, + *next_zone_state, + modifier_data.simulation_state_mix_factor); + return; + } + + bke::sim::SimulationZoneState &new_zone_state = + modifier_data.current_simulation_state_for_write->get_zone_state_for_write(zone_id); + if (eval_data.is_first_evaluation) { + new_zone_state.item_by_identifier.clear(); + } + + Array input_values(simulation_items_.size(), nullptr); + for (const int i : simulation_items_.index_range()) { + input_values[i] = params.try_get_input_data_ptr_or_request(i); + } + if (input_values.as_span().contains(nullptr)) { + /* Wait until all inputs are available. */ + return; + } + values_to_simulation_state(simulation_items_, input_values, new_zone_state); + this->output_cached_state( + params, *modifier_data.self_object, *user_data.compute_context, new_zone_state); + } + + void output_cached_state(lf::Params ¶ms, + const Object &self_object, + const ComputeContext &compute_context, + const bke::sim::SimulationZoneState &state) const + { + Array output_values(simulation_items_.size()); + for (const int i : simulation_items_.index_range()) { + output_values[i] = params.get_output_data_ptr(i); + } + simulation_state_to_values( + simulation_items_, state, self_object, compute_context, node_, output_values); + for (const int i : simulation_items_.index_range()) { + params.output_set(i); + } + } + + void output_mixed_cached_state(lf::Params ¶ms, + const Object &self_object, + const ComputeContext &compute_context, + const bke::sim::SimulationZoneState &prev_state, + const bke::sim::SimulationZoneState &next_state, + const float mix_factor) const + { + /* TODO: Implement subframe mixing. */ + this->output_cached_state(params, self_object, compute_context, prev_state); + UNUSED_VARS(next_state, mix_factor); + } +}; + +} // namespace blender::nodes::node_geo_simulation_output_cc + +namespace blender::nodes { + +std::unique_ptr get_simulation_output_lazy_function( + const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info) +{ + namespace file_ns = blender::nodes::node_geo_simulation_output_cc; + BLI_assert(node.type == GEO_NODE_SIMULATION_OUTPUT); + return std::make_unique(node, own_lf_graph_info); +} + +bke::sim::SimulationZoneID get_simulation_zone_id(const ComputeContext &compute_context, + const int output_node_id) +{ + bke::sim::SimulationZoneID zone_id; + for (const ComputeContext *context = &compute_context; context != nullptr; + context = context->parent()) + { + if (const auto *node_context = dynamic_cast(context)) { + zone_id.node_ids.append(node_context->node_id()); + } + } + std::reverse(zone_id.node_ids.begin(), zone_id.node_ids.end()); + zone_id.node_ids.append(output_node_id); + return zone_id; +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_simulation_output_cc { + +static void node_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclaration &r_declaration) +{ + const NodeGeometrySimulationOutput &storage = node_storage(node); + socket_declarations_for_simulation_items({storage.items, storage.items_num}, r_declaration); +} + +static void search_node_add_ops(GatherAddNodeSearchParams ¶ms) +{ + AddNodeItem item; + item.ui_name = IFACE_("Simulation Zone"); + item.description = TIP_("Add a new simulation input and output nodes to the node tree"); + item.add_fn = [](const bContext &C, bNodeTree &node_tree, float2 cursor) { + bNode *input = nodeAddNode(&C, &node_tree, "GeometryNodeSimulationInput"); + bNode *output = nodeAddNode(&C, &node_tree, "GeometryNodeSimulationOutput"); + static_cast(input->storage)->output_node_id = + output->identifier; + + NodeSimulationItem &item = node_storage(*output).items[0]; + + update_node_declaration_and_sockets(node_tree, *input); + update_node_declaration_and_sockets(node_tree, *output); + + nodeAddLink( + &node_tree, + input, + nodeFindSocket(input, SOCK_OUT, socket_identifier_for_simulation_item(item).c_str()), + output, + nodeFindSocket(output, SOCK_IN, socket_identifier_for_simulation_item(item).c_str())); + + input->locx = cursor.x / UI_SCALE_FAC - 150; + input->locy = cursor.y / UI_SCALE_FAC + 20; + output->locx = cursor.x / UI_SCALE_FAC + 150; + output->locy = cursor.y / UI_SCALE_FAC + 20; + + return Vector({input, output}); + }; + params.add_item(std::move(item)); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometrySimulationOutput *data = MEM_cnew(__func__); + + data->next_identifier = 0; + + data->items = MEM_cnew_array(1, __func__); + data->items[0].name = BLI_strdup(DATA_("Geometry")); + data->items[0].socket_type = SOCK_GEOMETRY; + data->items[0].identifier = data->next_identifier++; + data->items_num = 1; + + node->storage = data; +} + +static void node_free_storage(bNode *node) +{ + if (!node->storage) { + return; + } + NodeGeometrySimulationOutput &storage = node_storage(*node); + for (NodeSimulationItem &item : MutableSpan(storage.items, storage.items_num)) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage.items); + MEM_freeN(node->storage); +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometrySimulationOutput &src_storage = node_storage(*src_node); + NodeGeometrySimulationOutput *dst_storage = MEM_cnew(__func__); + + dst_storage->items = MEM_cnew_array(src_storage.items_num, __func__); + dst_storage->items_num = src_storage.items_num; + dst_storage->active_index = src_storage.active_index; + dst_storage->next_identifier = src_storage.next_identifier; + for (const int i : IndexRange(src_storage.items_num)) { + if (char *name = src_storage.items[i].name) { + dst_storage->items[i].identifier = src_storage.items[i].identifier; + dst_storage->items[i].name = BLI_strdup(name); + dst_storage->items[i].socket_type = src_storage.items[i].socket_type; + dst_storage->items[i].attribute_domain = src_storage.items[i].attribute_domain; + } + } + + dst_node->storage = dst_storage; +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + NodeGeometrySimulationOutput &storage = node_storage(*node); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->fromsock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket( + node, SOCK_IN, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + else { + BLI_assert(link->fromnode == node); + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->tosock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket( + node, SOCK_OUT, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + return true; +} + +} // namespace blender::nodes::node_geo_simulation_output_cc + +void register_node_type_geo_simulation_output() +{ + namespace file_ns = blender::nodes::node_geo_simulation_output_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SIMULATION_OUTPUT, "Simulation Output", NODE_CLASS_INTERFACE); + ntype.initfunc = file_ns::node_init; + ntype.declare_dynamic = file_ns::node_declare_dynamic; + ntype.gather_add_node_search_ops = file_ns::search_node_add_ops; + ntype.gather_link_search_ops = nullptr; + ntype.insert_link = file_ns::node_insert_link; + node_type_storage(&ntype, + "NodeGeometrySimulationOutput", + file_ns::node_free_storage, + file_ns::node_copy_storage); + nodeRegisterType(&ntype); +} + +blender::Span NodeGeometrySimulationOutput::items_span() const +{ + return blender::Span(items, items_num); +} + +blender::MutableSpan NodeGeometrySimulationOutput::items_span_for_write() +{ + return blender::MutableSpan(items, items_num); +} + +blender::IndexRange NodeGeometrySimulationOutput::items_range() const +{ + return blender::IndexRange(items_num); +} + +bool NOD_geometry_simulation_output_item_socket_type_supported( + const eNodeSocketDatatype socket_type) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY); +} + +bNode *NOD_geometry_simulation_output_find_node_by_item(bNodeTree *ntree, + const NodeSimulationItem *item) +{ + ntree->ensure_topology_cache(); + for (bNode *node : ntree->nodes_by_type("GeometryNodeSimulationOutput")) { + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + if (sim->items_span().contains_ptr(item)) { + return node; + } + } + return nullptr; +} + +bool NOD_geometry_simulation_output_item_set_unique_name(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item, + const char *name, + const char *defname) +{ + char unique_name[MAX_NAME + 4]; + BLI_strncpy(unique_name, name, sizeof(unique_name)); + + blender::nodes::SimulationItemsUniqueNameArgs args{sim, item}; + const bool name_changed = BLI_uniquename_cb(blender::nodes::simulation_items_unique_name_check, + &args, + defname, + '.', + unique_name, + ARRAY_SIZE(unique_name)); + item->name = BLI_strdup(unique_name); + return name_changed; +} + +bool NOD_geometry_simulation_output_contains_item(NodeGeometrySimulationOutput *sim, + const NodeSimulationItem *item) +{ + return sim->items_span().contains_ptr(item); +} + +NodeSimulationItem *NOD_geometry_simulation_output_get_active_item( + NodeGeometrySimulationOutput *sim) +{ + if (!sim->items_range().contains(sim->active_index)) { + return nullptr; + } + return &sim->items[sim->active_index]; +} + +void NOD_geometry_simulation_output_set_active_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item) +{ + if (sim->items_span().contains_ptr(item)) { + sim->active_index = item - sim->items; + } +} + +NodeSimulationItem *NOD_geometry_simulation_output_find_item(NodeGeometrySimulationOutput *sim, + const char *name) +{ + for (NodeSimulationItem &item : sim->items_span_for_write()) { + if (STREQ(item.name, name)) { + return &item; + } + } + return nullptr; +} + +NodeSimulationItem *NOD_geometry_simulation_output_add_item(NodeGeometrySimulationOutput *sim, + const short socket_type, + const char *name) +{ + return NOD_geometry_simulation_output_insert_item(sim, socket_type, name, sim->items_num); +} + +NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimulationOutput *sim, + const short socket_type, + const char *name, + int index) +{ + if (!NOD_geometry_simulation_output_item_socket_type_supported(eNodeSocketDatatype(socket_type))) + { + return nullptr; + } + + NodeSimulationItem *old_items = sim->items; + sim->items = MEM_cnew_array(sim->items_num + 1, __func__); + for (const int i : blender::IndexRange(index)) { + sim->items[i] = old_items[i]; + } + for (const int i : blender::IndexRange(index, sim->items_num - index)) { + sim->items[i + 1] = old_items[i]; + } + + const char *defname = nodeStaticSocketLabel(socket_type, 0); + NodeSimulationItem &added_item = sim->items[index]; + added_item.identifier = sim->next_identifier++; + NOD_geometry_simulation_output_item_set_unique_name(sim, &added_item, name, defname); + added_item.socket_type = socket_type; + + sim->items_num++; + MEM_SAFE_FREE(old_items); + + return &added_item; +} + +NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket( + NodeGeometrySimulationOutput *sim, const bNode * /*from_node*/, const bNodeSocket *from_sock) +{ + return NOD_geometry_simulation_output_insert_item( + sim, from_sock->type, from_sock->name, sim->items_num); +} + +NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket( + NodeGeometrySimulationOutput *sim, + const bNode * /*from_node*/, + const bNodeSocket *from_sock, + int index) +{ + return NOD_geometry_simulation_output_insert_item(sim, from_sock->type, from_sock->name, index); +} + +void NOD_geometry_simulation_output_remove_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item) +{ + const int index = item - sim->items; + if (index < 0 || index >= sim->items_num) { + return; + } + + NodeSimulationItem *old_items = sim->items; + sim->items = MEM_cnew_array(sim->items_num - 1, __func__); + for (const int i : blender::IndexRange(index)) { + sim->items[i] = old_items[i]; + } + for (const int i : blender::IndexRange(index, sim->items_num - index).drop_front(1)) { + sim->items[i - 1] = old_items[i]; + } + + MEM_SAFE_FREE(old_items[index].name); + + sim->items_num--; + MEM_SAFE_FREE(old_items); +} + +void NOD_geometry_simulation_output_clear_items(struct NodeGeometrySimulationOutput *sim) +{ + for (NodeSimulationItem &item : sim->items_span_for_write()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(sim->items); + sim->items = nullptr; + sim->items_num = 0; +} + +void NOD_geometry_simulation_output_move_item(NodeGeometrySimulationOutput *sim, + int from_index, + int to_index) +{ + BLI_assert(from_index >= 0 && from_index < sim->items_num); + BLI_assert(to_index >= 0 && to_index < sim->items_num); + + if (from_index == to_index) { + return; + } + + if (from_index < to_index) { + const NodeSimulationItem tmp = sim->items[from_index]; + for (int i = from_index; i < to_index; ++i) { + sim->items[i] = sim->items[i + 1]; + } + sim->items[to_index] = tmp; + } + else /* from_index > to_index */ { + const NodeSimulationItem tmp = sim->items[from_index]; + for (int i = from_index; i > to_index; --i) { + sim->items[i] = sim->items[i - 1]; + } + sim->items[to_index] = tmp; + } +} diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 682e254af48..c19623d60ae 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -105,42 +105,33 @@ static void lazy_function_interface_from_node(const bNode &node, } } -/** - * An anonymous attribute created by a node. - */ -class NodeAnonymousAttributeID : public AnonymousAttributeID { - std::string long_name_; - std::string socket_name_; - - public: - NodeAnonymousAttributeID(const Object &object, - const ComputeContext &compute_context, - const bNode &bnode, - const StringRef identifier, - const StringRef name) - : socket_name_(name) +NodeAnonymousAttributeID::NodeAnonymousAttributeID(const Object &object, + const ComputeContext &compute_context, + const bNode &bnode, + const StringRef identifier, + const StringRef name) + : socket_name_(name) +{ + const ComputeContextHash &hash = compute_context.hash(); { - const ComputeContextHash &hash = compute_context.hash(); - { - std::stringstream ss; - ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier; - long_name_ = ss.str(); - } - { - uint64_t hash_result[2]; - BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result); - std::stringstream ss; - ss << ".a_" << std::hex << hash_result[0] << hash_result[1]; - name_ = ss.str(); - BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME); - } + std::stringstream ss; + ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier; + long_name_ = ss.str(); } - - std::string user_name() const override { - return socket_name_; + uint64_t hash_result[2]; + BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result); + std::stringstream ss; + ss << ".a_" << std::hex << hash_result[0] << hash_result[1]; + name_ = ss.str(); + BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME); } -}; +} + +std::string NodeAnonymousAttributeID::user_name() const +{ + return socket_name_; +} /** * Used for most normal geometry nodes like Subdivision Surface and Set Position. @@ -846,6 +837,31 @@ class LazyFunctionForViewerInputUsage : public LazyFunction { } }; +class LazyFunctionForSimulationInputsUsage : public LazyFunction { + private: + const bNode &sim_output_node_; + + public: + LazyFunctionForSimulationInputsUsage(const bNode &sim_output_node) + : sim_output_node_(sim_output_node) + { + debug_name_ = "Simulation Inputs Usage"; + outputs_.append_as("Is Initialization", CPPType::get()); + outputs_.append_as("Do Simulation Step", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + const GeoNodesLFUserData &user_data = *static_cast(context.user_data); + const GeoNodesModifierData &modifier_data = *user_data.modifier_data; + + params.set_output(0, + modifier_data.current_simulation_state_for_write != nullptr && + modifier_data.prev_simulation_state == nullptr); + params.set_output(1, modifier_data.current_simulation_state_for_write != nullptr); + } +}; + /** * This lazy-function wraps a group node. Internally it just executes the lazy-function graph of * the referenced group. @@ -1370,6 +1386,10 @@ struct GeometryNodesLazyFunctionGraphBuilder { * All group input nodes are combined into one dummy node in the lazy-function graph. */ lf::DummyNode *group_input_lf_node_; + /** + * A #LazyFunctionForSimulationInputsUsage for each simulation zone. + */ + Map simulation_inputs_usage_nodes_; friend class UsedSocketVisualizeOptions; @@ -1508,6 +1528,14 @@ struct GeometryNodesLazyFunctionGraphBuilder { this->handle_viewer_node(*bnode); break; } + case GEO_NODE_SIMULATION_INPUT: { + this->handle_simulation_input_node(btree_, *bnode); + break; + } + case GEO_NODE_SIMULATION_OUTPUT: { + this->handle_simulation_output_node(*bnode); + break; + } case GEO_NODE_SWITCH: { this->handle_switch_node(*bnode); break; @@ -1785,6 +1813,60 @@ struct GeometryNodesLazyFunctionGraphBuilder { mapping_->viewer_node_map.add(&bnode, &lf_node); } + void handle_simulation_input_node(const bNodeTree &node_tree, const bNode &bnode) + { + const NodeGeometrySimulationInput *storage = static_cast( + bnode.storage); + if (node_tree.node_by_id(storage->output_node_id) == nullptr) { + return; + } + + std::unique_ptr lazy_function = get_simulation_input_lazy_function( + node_tree, bnode, *lf_graph_info_); + lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const int i : bnode.input_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.input_socket(i); + lf::InputSocket &lf_socket = lf_node.input( + mapping_->lf_index_by_bsocket[bsocket.index_in_tree()]); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : bnode.output_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.output_socket(i); + lf::OutputSocket &lf_socket = lf_node.output( + mapping_->lf_index_by_bsocket[bsocket.index_in_tree()]); + output_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_simulation_output_node(const bNode &bnode) + { + std::unique_ptr lazy_function = get_simulation_output_lazy_function( + bnode, *lf_graph_info_); + lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const int i : bnode.input_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.input_socket(i); + lf::InputSocket &lf_socket = lf_node.input( + mapping_->lf_index_by_bsocket[bsocket.index_in_tree()]); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : bnode.output_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.output_socket(i); + lf::OutputSocket &lf_socket = lf_node.output( + mapping_->lf_index_by_bsocket[bsocket.index_in_tree()]); + output_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + + mapping_->sim_output_node_map.add(&bnode, &lf_node); + } + void handle_switch_node(const bNode &bnode) { std::unique_ptr lazy_function = get_switch_node_lazy_function(bnode); @@ -2136,6 +2218,14 @@ struct GeometryNodesLazyFunctionGraphBuilder { this->build_viewer_node_socket_usage(*bnode); break; } + case GEO_NODE_SIMULATION_INPUT: { + this->build_simulation_input_socket_usage(*bnode); + break; + } + case GEO_NODE_SIMULATION_OUTPUT: { + this->build_simulation_output_socket_usage(*bnode); + break; + } case NODE_GROUP: case NODE_CUSTOM_GROUP: { this->build_group_node_socket_usage(*bnode, or_socket_usages_cache); @@ -2296,6 +2386,44 @@ struct GeometryNodesLazyFunctionGraphBuilder { } } + void build_simulation_input_socket_usage(const bNode &bnode) + { + const NodeGeometrySimulationInput *storage = static_cast( + bnode.storage); + const bNode *sim_output_node = btree_.node_by_id(storage->output_node_id); + if (sim_output_node == nullptr) { + return; + } + lf::Node &lf_node = this->get_simulation_inputs_usage_node(*sim_output_node); + for (const bNodeSocket *bsocket : bnode.input_sockets()) { + if (bsocket->is_available()) { + socket_is_used_map_[bsocket->index_in_tree()] = &lf_node.output(0); + } + } + } + + void build_simulation_output_socket_usage(const bNode &bnode) + { + lf::Node &lf_node = this->get_simulation_inputs_usage_node(bnode); + for (const bNodeSocket *bsocket : bnode.input_sockets()) { + if (bsocket->is_available()) { + socket_is_used_map_[bsocket->index_in_tree()] = &lf_node.output(1); + } + } + } + + lf::Node &get_simulation_inputs_usage_node(const bNode &sim_output_bnode) + { + BLI_assert(sim_output_bnode.type == GEO_NODE_SIMULATION_OUTPUT); + return *simulation_inputs_usage_nodes_.lookup_or_add_cb(&sim_output_bnode, [&]() { + auto lazy_function = std::make_unique( + sim_output_bnode); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + return &lf_node; + }); + } + void build_group_node_socket_usage(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) { diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 751eeccec96..1734c2d0e64 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1516,6 +1516,7 @@ typedef enum eWM_JobType { WM_JOB_TYPE_LINEART, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL, WM_JOB_TYPE_SEQ_DRAG_DROP_PREVIEW, + WM_JOB_TYPE_BAKE_SIMULATION_NODES, /* add as needed, bake, seq proxy build * if having hard coded values is a problem */ } eWM_JobType;