diff options
247 files changed, 28157 insertions, 17 deletions
diff --git a/build_files/cmake/Modules/FindLLVM.cmake b/build_files/cmake/Modules/FindLLVM.cmake index 141a91c0508..6d34142425f 100644 --- a/build_files/cmake/Modules/FindLLVM.cmake +++ b/build_files/cmake/Modules/FindLLVM.cmake @@ -49,6 +49,7 @@ if(NOT LLVM_ROOT_DIR) OUTPUT_VARIABLE LLVM_ROOT_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) set(LLVM_ROOT_DIR ${LLVM_ROOT_DIR} CACHE PATH "Path to the LLVM installation") + set(LLVM_INCLUDE_DIRS ${LLVM_ROOT_DIR}/include CACHE PATH "Path to the LLVM include directory") endif() if(NOT LLVM_LIBPATH) execute_process(COMMAND ${LLVM_CONFIG} --libdir diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 5af2bd22222..d72557fc3d6 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -33,6 +33,7 @@ _modules = [ "file", "image", "mesh", + "modifiers", "node", "object", "object_align", diff --git a/release/scripts/startup/bl_operators/modifiers.py b/release/scripts/startup/bl_operators/modifiers.py new file mode 100644 index 00000000000..be3f2e6b6fe --- /dev/null +++ b/release/scripts/startup/bl_operators/modifiers.py @@ -0,0 +1,91 @@ +import bpy +from bpy.props import ( + StringProperty, +) + +class ModifierOperator: + object_name: StringProperty() + modifier_name: StringProperty() + + def get_modifier(self): + ob = bpy.data.objects.get(self.object_name) + if ob is None: + return None + return ob.modifiers.get(self.modifier_name) + +class NewDeformationFunction(bpy.types.Operator, ModifierOperator): + bl_idname = "fn.new_deformation_function" + bl_label = "New Deformation Function" + + def execute(self, context): + mod = self.get_modifier() + if mod is None: + return {'CANCELLED'} + + from nodes.node_operators import new_function_tree + tree = new_function_tree("Deformation Function", [ + ("Vector", "Old Position"), + ("Float", "Control 1"), + ("Integer", "Control 2") + ], [ + ("Vector", "New Position"), + ]) + + tree.new_link( + tree.get_input_nodes()[0].outputs[0], + tree.get_output_nodes()[0].inputs[0]) + + mod.function_tree = tree + return {'FINISHED'} + +class NewPointGeneratorFunction(bpy.types.Operator, ModifierOperator): + bl_idname = "fn.new_point_generator_function" + bl_label = "New Point Generator Function" + + def execute(self, context): + mod = self.get_modifier() + if mod is None: + return {'CANCELLED'} + + from nodes.node_operators import new_function_tree + tree = new_function_tree("Point Generator", [ + ("Float", "Control 1"), + ("Integer", "Control 2"), + ], [ + ("Vector List", "Points"), + ]) + + mod.function_tree = tree + return {'FINISHED'} + +class NewParticleSimulationTree(bpy.types.Operator, ModifierOperator): + bl_idname = "fn.new_particle_simulation_tree" + bl_label = "New Particle Simulation Tree" + + def execute(self, context): + mod = self.get_modifier() + if mod is None: + return {'CANCELLED'} + + tree = bpy.data.node_groups.new("Particle Simulation", "FunctionTree") + + type_node = tree.nodes.new("fn_ParticleSystemNode") + + emitter_node = tree.nodes.new("fn_InitialGridEmitterNode") + emitter_node.location = (-250, 200) + + gravity_node = tree.nodes.new("fn_ForceNode") + gravity_node.inputs[0].value = (0, 0, -1) + gravity_node.location = (-250, -100) + + tree.links.new(emitter_node.outputs[0], type_node.inputs[0]) + tree.links.new(gravity_node.outputs[0], type_node.inputs[0]) + + mod.node_tree = tree + return {'FINISHED'} + +classes = ( + NewDeformationFunction, + NewPointGeneratorFunction, + NewParticleSimulationTree, +) diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py index 62e19129923..5a1935f915a 100644 --- a/release/scripts/startup/bl_ui/properties_data_modifier.py +++ b/release/scripts/startup/bl_ui/properties_data_modifier.py @@ -410,7 +410,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): row.prop(md, "mid_level") row.prop(md, "strength") - def DYNAMIC_PAINT(self, layout, _ob, _md): + def DYNAMIC_PAINT(self, layout, _ob, md): layout.label(text="Settings are inside the Physics tab") def EDGE_SPLIT(self, layout, _ob, md): @@ -1803,6 +1803,42 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): col.prop(md, "thresh", text="Threshold") col.prop(md, "face_influence") + def FUNCTION_DEFORM(self, layout, ob, md): + layout.prop(md, "control1") + layout.prop(md, "control2") + + row = layout.row(align=True) + row.prop(md, "function_tree") + props = row.operator("fn.new_deformation_function", text="", icon="ADD") + props.object_name = ob.name + props.modifier_name = md.name + + def FUNCTION_POINTS(self, layout, ob, md): + layout.prop(md, "control1") + layout.prop(md, "control2") + + row = layout.row(align=True) + row.prop(md, "function_tree") + props = row.operator("fn.new_point_generator_function", text="", icon="ADD") + props.object_name = ob.name + props.modifier_name = md.name + + def BPARTICLES(self, layout, ob, md): + row = layout.row(align=True) + row.prop(md, "node_tree") + props = row.operator("fn.new_particle_simulation_tree", text="", icon="ADD") + props.object_name = ob.name + props.modifier_name = md.name + + layout.operator("object.bparticles_clear_cache", text="Clear Cache") + + layout.prop(md, "output_type") + + def BPARTICLES_OUTPUT(self, layout, ob, md): + layout.prop(md, "source_object") + layout.prop(md, "source_particle_system", icon="PHYSICS") + layout.prop(md, "output_type") + class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel): bl_label = "Modifiers" diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index bdda0ebbe9a..327b08e866d 100644 --- a/release/scripts/startup/bl_ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -207,6 +207,11 @@ class NODE_MT_add(bpy.types.Menu): def draw(self, context): layout = self.layout + from nodes.base import BaseTree + tree = context.space_data.node_tree + if isinstance(tree, BaseTree): + return + layout.operator_context = 'INVOKE_DEFAULT' props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') props.use_transform = True diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 76e80fdb414..243d187cd78 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1229,6 +1229,7 @@ class USERPREF_PT_file_paths_data(FilePathsPanel, Panel): col.prop(paths, "script_directory", text="Scripts") col.prop(paths, "sound_directory", text="Sounds") col.prop(paths, "temporary_directory", text="Temporary Files") + col.prop(paths, "nodelib_directory", text="Nodelib Files") class USERPREF_PT_file_paths_render(FilePathsPanel, Panel): diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index f38bbb46f85..af86a55dfe7 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2194,6 +2194,10 @@ class VIEW3D_MT_add(Menu): layout.separator() + layout.operator("fn.new_particle_system", text="Particle Simulation", icon='MOD_PARTICLES') + + layout.separator() + if VIEW3D_MT_camera_add.is_extended(): layout.menu("VIEW3D_MT_camera_add", icon='OUTLINER_OB_CAMERA') else: diff --git a/release/scripts/startup/nodes/__init__.py b/release/scripts/startup/nodes/__init__.py new file mode 100644 index 00000000000..80b317f02ab --- /dev/null +++ b/release/scripts/startup/nodes/__init__.py @@ -0,0 +1,3 @@ +from . auto_load import init, register, unregister + +init() diff --git a/release/scripts/startup/nodes/auto_load.py b/release/scripts/startup/nodes/auto_load.py new file mode 100644 index 00000000000..e8d213072f6 --- /dev/null +++ b/release/scripts/startup/nodes/auto_load.py @@ -0,0 +1,138 @@ +import os +import bpy +import sys +import typing +import inspect +import pkgutil +import importlib +from pathlib import Path + +__all__ = ( + "init", + "register", + "unregister", +) + +modules = None +ordered_classes = None + +def init(): + global modules + global ordered_classes + + modules = get_all_submodules(Path(__file__).parent) + ordered_classes = get_ordered_classes_to_register(modules) + +def register(): + for cls in ordered_classes: + bpy.utils.register_class(cls) + + for module in modules: + if module.__name__ == __name__: + continue + if hasattr(module, "register"): + module.register() + +def unregister(): + for cls in reversed(ordered_classes): + bpy.utils.unregister_class(cls) + + for module in modules: + if module.__name__ == __name__: + continue + if hasattr(module, "unregister"): + module.unregister() + + +# Import modules +################################################# + +def get_all_submodules(directory): + return list(iter_submodules(directory, directory.name)) + +def iter_submodules(path, package_name): + for name in sorted(iter_submodule_names(path)): + yield importlib.import_module("." + name, package_name) + +def iter_submodule_names(path, root=""): + for _, module_name, is_package in pkgutil.iter_modules([str(path)]): + if is_package: + sub_path = path / module_name + sub_root = root + module_name + "." + yield from iter_submodule_names(sub_path, sub_root) + else: + yield root + module_name + + +# Find classes to register +################################################# + +def get_ordered_classes_to_register(modules): + return toposort(get_register_deps_dict(modules)) + +def get_register_deps_dict(modules): + deps_dict = {} + classes_to_register = set(iter_classes_to_register(modules)) + for cls in classes_to_register: + deps_dict[cls] = set(iter_own_register_deps(cls, classes_to_register)) + return deps_dict + +def iter_own_register_deps(cls, own_classes): + yield from (dep for dep in iter_register_deps(cls) if dep in own_classes) + +def iter_register_deps(cls): + for value in typing.get_type_hints(cls, {}, {}).values(): + dependency = get_dependency_from_annotation(value) + if dependency is not None: + yield dependency + +def get_dependency_from_annotation(value): + if isinstance(value, tuple) and len(value) == 2: + if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty): + return value[1]["type"] + return None + +def iter_classes_to_register(modules): + base_types = get_register_base_types() + for cls in get_classes_in_modules(modules): + if any(base in base_types for base in cls.__bases__): + if not getattr(cls, "is_registered", False): + yield cls + +def get_classes_in_modules(modules): + classes = set() + for module in modules: + for cls in iter_classes_in_module(module): + classes.add(cls) + return classes + +def iter_classes_in_module(module): + for value in module.__dict__.values(): + if inspect.isclass(value): + yield value + +def get_register_base_types(): + return set(getattr(bpy.types, name) for name in [ + "Panel", "Operator", "PropertyGroup", + "AddonPreferences", "Header", "Menu", + "Node", "NodeSocket", "NodeTree", + "UIList", "RenderEngine" + ]) + + +# Find order to register to solve dependencies +################################################# + +def toposort(deps_dict): + sorted_list = [] + sorted_values = set() + while len(deps_dict) > 0: + unsorted = [] + for value, deps in deps_dict.items(): + if len(deps) == 0: + sorted_list.append(value) + sorted_values.add(value) + else: + unsorted.append(value) + deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted} + return sorted_list diff --git a/release/scripts/startup/nodes/base.py b/release/scripts/startup/nodes/base.py new file mode 100644 index 00000000000..eea1846c8b7 --- /dev/null +++ b/release/scripts/startup/nodes/base.py @@ -0,0 +1,303 @@ +import bpy +from bpy.props import * +from . utils.generic import iter_subclasses_recursive +import itertools +from collections import defaultdict + +class BaseTree: + def new_link(self, a, b): + if a.is_output: + self.links.new(a, b) + else: + self.links.new(b, a) + + def update(self): + self.sync() + + def sync(self): + from . sync import sync_trees_and_dependent_trees + sync_trees_and_dependent_trees({self}) + + +class SocketValueStates: + def __init__(self, node): + self.node = node + self.input_value_storage = dict() + + def store_current(self): + for socket in self.node.inputs: + if not isinstance(socket, DataSocket): + continue + storage_id = (socket.data_type, socket.identifier) + self.input_value_storage[storage_id] = socket.get_state() + + def try_load(self): + for socket in self.node.inputs: + if not isinstance(socket, DataSocket): + continue + storage_id = (socket.data_type, socket.identifier) + if storage_id in self.input_value_storage: + socket.restore_state(self.input_value_storage[storage_id]) + + +def get_new_node_identifier(): + import uuid + return str(uuid.uuid4()) + + +class BaseNode: + search_terms = tuple() + search_terms_only = False + + identifier: StringProperty() + + def init(self, context): + self.identifier = get_new_node_identifier() + + from . sync import skip_syncing + with skip_syncing(): + self.init_props() + builder = self.get_node_builder() + builder.initialize_decls() + builder.build() + + def init_props(self): + pass + + @classmethod + def get_search_terms(cls): + if not cls.search_terms_only: + yield (cls.bl_label, dict()) + yield from cls.search_terms + + def sync_tree(self, context=None): + self.tree.sync() + + def rebuild(self): + from . sync import skip_syncing + with skip_syncing(): + self.socket_value_states.store_current() + linkage_state = LinkageState(self) + + self.rebuild_fast() + + self.socket_value_states.try_load() + linkage_state.try_restore() + + def rebuild_fast(self): + from . sync import skip_syncing + with skip_syncing(): + builder = self.get_node_builder() + builder.build() + _decl_map_per_node[self] = builder.get_sockets_decl_map() + + @property + def tree(self): + return self.id_data + + def get_node_builder(self): + from . node_builder import NodeBuilder + builder = NodeBuilder(self) + self.declaration(builder) + return builder + + def declaration(self, builder): + raise NotImplementedError() + + def draw_buttons(self, context, layout): + self.draw(layout) + for decl in self.decl_map.iter_decls(): + decl.draw_node(layout) + + def draw_buttons_ext(self, context, layout): + self.draw_advanced(layout) + + def draw(self, layout): + pass + + def draw_advanced(self, layout): + pass + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + decl.draw_socket(layout, socket, index_in_decl) + + def draw_label(self): + if self.hide: + return self.draw_closed_label() + else: + return self.bl_label + + def draw_closed_label(self): + return self.bl_label + + def iter_directly_used_trees(self): + return + yield + + def invoke_function(self, + layout, function_name, text, + *, icon="NONE", settings=tuple()): + assert isinstance(settings, tuple) + props = layout.operator("fn.node_operator", text=text, icon=icon) + self._set_common_invoke_props(props, function_name, settings) + + def invoke_type_selection(self, + layout, function_name, text, + *, mode="ALL", icon="NONE", settings=tuple()): + assert isinstance(settings, tuple) + props = layout.operator("fn.node_data_type_selector", text=text, icon=icon) + self._set_common_invoke_props(props, function_name, settings) + props.mode = mode + + def invoke_group_selector(self, + layout, function_name, text, + *, icon="NONE", settings=tuple()): + assert isinstance(settings, tuple) + props = layout.operator("fn.node_group_selector", text=text, icon=icon) + self._set_common_invoke_props(props, function_name, settings) + + def _set_common_invoke_props(self, props, function_name, settings): + props.tree_name = self.id_data.name + props.node_name = self.name + props.function_name = function_name + props.settings_repr = repr(settings) + + @classmethod + def iter_final_subclasses(cls): + yield from filter(lambda x: issubclass(x, bpy.types.Node), iter_subclasses_recursive(cls)) + + def find_input(self, identifier): + for socket in self.inputs: + if socket.identifier == identifier: + return socket + else: + return None + + def find_output(self, identifier): + for socket in self.outputs: + if socket.identifier == identifier: + return socket + else: + return None + + def find_socket(self, identifier, is_output): + if is_output: + return self.find_output(identifier) + else: + return self.find_input(identifier) + + def iter_sockets(self): + yield from self.inputs + yield from self.outputs + + # Storage + ######################### + + @property + def decl_map(self): + if self not in _decl_map_per_node: + builder = self.get_node_builder() + _decl_map_per_node[self] = builder.get_sockets_decl_map() + return _decl_map_per_node[self] + + @property + def socket_value_states(self): + if self not in _socket_value_states_per_node: + _socket_value_states_per_node[self] = SocketValueStates(self) + return _socket_value_states_per_node[self] + + def free(self): + if self in _decl_map_per_node: + del _decl_map_per_node[self] + + def copy(self, src_node): + self.identifier = get_new_node_identifier() + self.duplicate(src_node) + + def duplicate(self, src_node): + pass + + +class BaseSocket: + color = (0, 0, 0, 0) + + def draw_color(self, context, node): + return self.color + + def draw(self, context, layout, node, text): + decl, index = self.get_decl_with_index(node) + node.draw_socket(layout, self, text, decl, index) + + def draw_self(self, layout, node, text): + layout.label(text=text) + + def get_index(self, node): + if self.is_output: + return tuple(node.outputs).index(self) + else: + return tuple(node.inputs).index(self) + + def to_id(self, node): + return (node, self.is_output, self.identifier) + + def get_decl(self, node): + return node.decl_map.get_decl_by_socket(self) + + def get_decl_with_index(self, node): + decl_map = node.decl_map + decl = decl_map.get_decl_by_socket(self) + index = decl_map.get_socket_index_in_decl(self) + return decl, index + +class FunctionNode(BaseNode): + pass + +class SimulationNode(BaseNode): + pass + +class DataSocket(BaseSocket): + def draw_self(self, layout, node, text): + if not (self.is_linked or self.is_output) and hasattr(self, "draw_property"): + self.draw_property(layout, node, text) + else: + layout.label(text=text) + + def get_state(self): + return None + + def restore_state(self, state): + pass + +class LinkageState: + def __init__(self, node): + self.node = node + self.tree = node.tree + self.links_per_input = defaultdict(set) + self.links_per_output = defaultdict(set) + + for link in self.tree.links: + if link.from_node == node: + self.links_per_output[link.from_socket.identifier].add(link.to_socket) + if link.to_node == node: + self.links_per_input[link.to_socket.identifier].add(link.from_socket) + + def try_restore(self): + tree = self.tree + for socket in self.node.inputs: + for from_socket in self.links_per_input[socket.identifier]: + tree.links.new(socket, from_socket) + for socket in self.node.outputs: + for to_socket in self.links_per_output[socket.identifier]: + tree.links.new(to_socket, socket) + + +_decl_map_per_node = {} +_socket_value_states_per_node = {} + +@bpy.app.handlers.persistent +def clear_cached_node_states(_): + _decl_map_per_node.clear() + _socket_value_states_per_node.clear() + +def register(): + bpy.app.handlers.load_pre.append(clear_cached_node_states) diff --git a/release/scripts/startup/nodes/bparticle_nodes/__init__.py b/release/scripts/startup/nodes/bparticle_nodes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/__init__.py diff --git a/release/scripts/startup/nodes/bparticle_nodes/always_execute.py b/release/scripts/startup/nodes/bparticle_nodes/always_execute.py new file mode 100644 index 00000000000..61772696bed --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/always_execute.py @@ -0,0 +1,26 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + + +class AlwaysExecuteNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_AlwaysExecuteNode" + bl_label = "Always Execute" + + execute__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.execute_input("execute", "Execute", "execute__prop") + builder.influences_output("influence", "Influence") + + +class MultiExecuteNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_MultiExecuteNode" + bl_label = "Multi Execute" + + execute__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.execute_input("execute", "Execute", "execute__prop") + builder.execute_output("execute", "Execute") diff --git a/release/scripts/startup/nodes/bparticle_nodes/combine_influences.py b/release/scripts/startup/nodes/bparticle_nodes/combine_influences.py new file mode 100644 index 00000000000..9cdc5ac1ddd --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/combine_influences.py @@ -0,0 +1,11 @@ +import bpy +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class CombineInfluencesNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_CombineInfluencesNode" + bl_label = "Combine Influences" + + def declaration(self, builder: NodeBuilder): + builder.influences_input("influences", "Influences") + builder.influences_output("influences", "Influences") diff --git a/release/scripts/startup/nodes/bparticle_nodes/condition.py b/release/scripts/startup/nodes/bparticle_nodes/condition.py new file mode 100644 index 00000000000..09de1ea6823 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/condition.py @@ -0,0 +1,18 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class ParticleConditionNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_ParticleConditionNode" + bl_label = "Particle Condition" + + execute_if_true__prop: NodeBuilder.ExecuteInputProperty() + execute_if_false__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("condition", "Condition", "Boolean") + builder.execute_input("execute_if_true", "Execute If True", "execute_if_true__prop") + builder.execute_input("execute_if_false", "Execute If False", "execute_if_false__prop") + + builder.execute_output("execute", "Execute") diff --git a/release/scripts/startup/nodes/bparticle_nodes/custom_attributes.py b/release/scripts/startup/nodes/bparticle_nodes/custom_attributes.py new file mode 100644 index 00000000000..780dab0bd54 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/custom_attributes.py @@ -0,0 +1,46 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode, FunctionNode +from .. node_builder import NodeBuilder + +class SetParticleAttributeNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_SetParticleAttributeNode" + bl_label = "Set Attribute" + + attribute_type: StringProperty( + name="Attribute Type", + default="Float", + update=SimulationNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("name", "Name", "Text", default="My Attribute", display_name=False) + builder.fixed_input("value", "Value", self.attribute_type) + builder.execute_output("execute", "Execute") + + def draw(self, layout): + self.invoke_type_selection(layout, "set_type", "Select Type", mode="BASE", icon="SETTINGS") + + def set_type(self, data_type): + self.attribute_type = data_type + + +class GetParticleAttributeNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetParticleAttributeNode" + bl_label = "Get Attribute" + + attribute_type: StringProperty( + name="Attribute Type", + default="Float", + update=SimulationNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("name", "Name", "Text", default="My Attribute", display_name=False) + builder.fixed_output("value", "Value", self.attribute_type) + + def draw(self, layout): + self.invoke_type_selection(layout, "set_type", "Select Type", mode="BASE", icon="SETTINGS") + + def set_type(self, data_type): + self.attribute_type = data_type diff --git a/release/scripts/startup/nodes/bparticle_nodes/custom_emitter.py b/release/scripts/startup/nodes/bparticle_nodes/custom_emitter.py new file mode 100644 index 00000000000..fc5d92adbc5 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/custom_emitter.py @@ -0,0 +1,169 @@ +import bpy +import uuid +from bpy.props import * +from .. base import SimulationNode, DataSocket, FunctionNode +from .. node_builder import NodeBuilder +from .. types import type_infos +from .. sync import skip_syncing + + +class CustomEmitterAttribute(bpy.types.PropertyGroup): + def sync_tree(self, context): + self.id_data.sync() + + attribute_name: StringProperty(update=sync_tree) + attribute_type: StringProperty(update=sync_tree) + identifier: StringProperty() + is_list: NodeBuilder.VectorizedProperty() + + +class CustomEmitter(bpy.types.Node, SimulationNode): + bl_idname = "fn_CustomEmitterNode" + bl_label = "Custom Emitter" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + attributes: CollectionProperty( + type=CustomEmitterAttribute, + ) + + birth_time_mode: EnumProperty( + name="Birth Time Mode", + items=[ + ("NONE", "None", "Manually specify birth times of every particle", "NONE", 0), + ("BEGIN", "Begin", "Spawn particles at the beginning of each time step", "NONE", 1), + ("END", "End", "Spawn particles at the end of each time step", "NONE", 2), + ("RANDOM", "Random", "Spawn particles at random moments in the time step", "NONE", 3), + ("LINEAR", "Linear", "Distribute particles linearly in each time step", "NONE", 4), + ], + default="END", + ) + + def init_props(self): + self.add_attribute("Vector", "Position") + + def declaration(self, builder: NodeBuilder): + for i, item in enumerate(self.attributes): + builder.vectorized_input( + item.identifier, + f"attributes[{i}].is_list", + item.attribute_name, + item.attribute_name, + item.attribute_type) + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.influences_output("emitter", "Emitter") + + def draw(self, layout): + layout.prop(self, "birth_time_mode", text="Birth") + self.invoke_type_selection(layout, "add_attribute", "Add Attribute", mode="BASE") + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + if isinstance(socket, DataSocket): + index = list(self.inputs).index(socket) + item = self.attributes[index] + col = layout.column(align=True) + row = col.row(align=True) + row.prop(item, "attribute_name", text="") + self.invoke_type_selection(row, "set_attribute_type", "", + icon="SETTINGS", mode="BASE", settings=(index, )) + self.invoke_function(row, "remove_attribute", "", icon="X", settings=(index, )) + if not socket.is_linked and hasattr(socket, "draw_property"): + socket.draw_property(col, self, "") + else: + decl.draw_socket(layout, socket, index_in_decl) + + def add_attribute(self, data_type, name="My Attribute"): + with skip_syncing(): + item = self.attributes.add() + item.identifier = str(uuid.uuid4()) + item.attribute_type = data_type + item.attribute_name = name + + self.sync_tree() + + def remove_attribute(self, index): + self.attributes.remove(index) + self.sync_tree() + + def set_attribute_type(self, data_type, index): + self.attributes[index].attribute_type = data_type + +class EmitterTimeInfoNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_EmitterTimeInfoNode" + bl_label = "Emitter Time Info" + + def declaration(self, builder: NodeBuilder): + builder.fixed_output("duration", "Duration", "Float") + builder.fixed_output("begin", "Begin", "Float") + builder.fixed_output("end", "End", "Float") + builder.fixed_output("step", "Step", "Integer") + + +class SpawnParticlesAttribute(bpy.types.PropertyGroup): + def sync_tree(self, context): + self.id_data.sync() + + attribute_name: StringProperty(update=sync_tree) + attribute_type: StringProperty(update=sync_tree) + identifier: StringProperty() + is_list: NodeBuilder.VectorizedProperty() + + +class SpawnParticlesNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_SpawnParticlesNode" + bl_label = "Spawn Particles" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + attributes: CollectionProperty( + type=SpawnParticlesAttribute, + ) + + def init_props(self): + self.add_attribute("Vector", "Position") + + def declaration(self, builder: NodeBuilder): + for i, item in enumerate(self.attributes): + builder.vectorized_input( + item.identifier, + f"attributes[{i}].is_list", + item.attribute_name, + item.attribute_name, + item.attribute_type) + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.execute_output("execute", "Execute") + builder.influences_output("spawn_system", "Spawn System") + + def draw(self, layout): + self.invoke_type_selection(layout, "add_attribute", "Add Attribute", mode="BASE") + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + if isinstance(socket, DataSocket): + index = list(self.inputs).index(socket) + item = self.attributes[index] + col = layout.column(align=True) + row = col.row(align=True) + row.prop(item, "attribute_name", text="") + self.invoke_type_selection(row, "set_attribute_type", "", + icon="SETTINGS", mode="BASE", settings=(index, )) + self.invoke_function(row, "remove_attribute", "", icon="X", settings=(index, )) + if not socket.is_linked and hasattr(socket, "draw_property"): + socket.draw_property(col, self, "") + else: + decl.draw_socket(layout, socket, index_in_decl) + + def add_attribute(self, data_type, name="My Attribute"): + with skip_syncing(): + item = self.attributes.add() + item.identifier = str(uuid.uuid4()) + item.attribute_type = data_type + item.attribute_name = name + + self.sync_tree() + + def remove_attribute(self, index): + self.attributes.remove(index) + self.sync_tree() + + def set_attribute_type(self, data_type, index): + self.attributes[index].attribute_type = data_type diff --git a/release/scripts/startup/nodes/bparticle_nodes/events.py b/release/scripts/startup/nodes/bparticle_nodes/events.py new file mode 100644 index 00000000000..9fc4e9ef81f --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/events.py @@ -0,0 +1,60 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode, FunctionNode +from .. node_builder import NodeBuilder + + +class AgeReachedEventNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_AgeReachedEventNode" + bl_label = "Age Reached Event" + + execute_on_event__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("age", "Age", "Float", default=3) + builder.execute_input("execute_on_event", "Execute on Event", "execute_on_event__prop") + + builder.influences_output("event", "Event") + + +class MeshCollisionEventNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_MeshCollisionEventNode" + bl_label = "Mesh Collision Event" + + execute_on_event__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("object", "Object", "Object") + builder.execute_input("execute_on_event", "Execute on Event", "execute_on_event__prop") + + builder.influences_output("event", "Event") + + +class CustomEventNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_CustomEventNode" + bl_label = "Custom Event" + + execute_on_event__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("condition", "Condition", "Boolean") + builder.fixed_input("time_factor", "Time Factor", "Float", default=1.0) + builder.execute_input("execute_on_event", "Execute on Event", "execute_on_event__prop") + + builder.influences_output("event", "Event") + + +class EventFilterEndTimeNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_EventFilterEndTimeNode" + bl_label = "Event Filter End Time" + + def declaration(self, builder: NodeBuilder): + builder.fixed_output("end_time", "End Time", "Float") + + +class EventFilterDurationNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_EventFilterDurationNode" + bl_label = "Event Filter Duration" + + def declaration(self, builder: NodeBuilder): + builder.fixed_output("duration", "Duration", "Float") diff --git a/release/scripts/startup/nodes/bparticle_nodes/forces.py b/release/scripts/startup/nodes/bparticle_nodes/forces.py new file mode 100644 index 00000000000..f423334787c --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/forces.py @@ -0,0 +1,13 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + + +class ForceNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_ForceNode" + bl_label = "Force" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("force", "Force", "Vector") + builder.influences_output("force", "Force") diff --git a/release/scripts/startup/nodes/bparticle_nodes/initial_grid_emitter.py b/release/scripts/startup/nodes/bparticle_nodes/initial_grid_emitter.py new file mode 100644 index 00000000000..55510ed4182 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/initial_grid_emitter.py @@ -0,0 +1,19 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class InitialGridEmitterNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_InitialGridEmitterNode" + bl_label = "Initial Grid Emitter" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("amount_x", "Amount X", "Integer", default=50) + builder.fixed_input("amount_y", "Amount Y", "Integer", default=50) + builder.fixed_input("step_x", "Step X", "Float", default=0.2) + builder.fixed_input("step_y", "Step Y", "Float", default=0.2) + builder.fixed_input("size", "Size", "Float", default=0.01) + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.influences_output("emitter", "Emitter") diff --git a/release/scripts/startup/nodes/bparticle_nodes/mesh_emitter.py b/release/scripts/startup/nodes/bparticle_nodes/mesh_emitter.py new file mode 100644 index 00000000000..6db4141109d --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/mesh_emitter.py @@ -0,0 +1,32 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class MeshEmitterNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_MeshEmitterNode" + bl_label = "Mesh Emitter" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + density_mode: EnumProperty( + name="Density Mode", + items=[ + ('UNIFORM', "Uniform", "", 'NONE', 0), + ('VERTEX_WEIGHTS', "Vertex Weights", "", 'NONE', 1), + ], + update=SimulationNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("object", "Object", "Object") + builder.fixed_input("rate", "Rate", "Float", default=10) + + if self.density_mode == 'VERTEX_WEIGHTS': + builder.fixed_input("density_vertex_group", "Density Group", "Text") + + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.influences_output("emitter", "Emitter") + + def draw(self, layout): + layout.prop(self, "density_mode") diff --git a/release/scripts/startup/nodes/bparticle_nodes/particle_system.py b/release/scripts/startup/nodes/bparticle_nodes/particle_system.py new file mode 100644 index 00000000000..9e77b058108 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/particle_system.py @@ -0,0 +1,15 @@ +import bpy +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class ParticleSystemNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_ParticleSystemNode" + bl_label = "Particle System" + + def declaration(self, builder: NodeBuilder): + builder.background_color((0.8, 0.5, 0.4)) + + builder.influences_input("influences", "Influences") + + def draw(self, layout): + layout.prop(self, "name", text="", icon="PHYSICS") diff --git a/release/scripts/startup/nodes/bparticle_nodes/point_emitter.py b/release/scripts/startup/nodes/bparticle_nodes/point_emitter.py new file mode 100644 index 00000000000..fd4dac80117 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/point_emitter.py @@ -0,0 +1,17 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class PointEmitterNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_PointEmitterNode" + bl_label = "Point Emitter" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("position", "Position", "Vector") + builder.fixed_input("velocity", "Velocity", "Vector", default=(1, 0, 0)) + builder.fixed_input("size", "Size", "Float", default=0.01) + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.influences_output("emitter", "Emitter") diff --git a/release/scripts/startup/nodes/bparticle_nodes/size_over_time.py b/release/scripts/startup/nodes/bparticle_nodes/size_over_time.py new file mode 100644 index 00000000000..4c3a10a33b4 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/size_over_time.py @@ -0,0 +1,13 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class SizeOverTimeNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_SizeOverTimeNode" + bl_label = "Size Over Time" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("final_size", "Final Size", "Float", default=0.0) + builder.fixed_input("final_age", "Final Age", "Float", default=3) + builder.influences_output("influence", "Influence") diff --git a/release/scripts/startup/nodes/bparticle_nodes/trails.py b/release/scripts/startup/nodes/bparticle_nodes/trails.py new file mode 100644 index 00000000000..3a63c8d1743 --- /dev/null +++ b/release/scripts/startup/nodes/bparticle_nodes/trails.py @@ -0,0 +1,16 @@ +import bpy +from bpy.props import * +from .. base import SimulationNode +from .. node_builder import NodeBuilder + +class ParticleTrailsNode(bpy.types.Node, SimulationNode): + bl_idname = "fn_ParticleTrailsNode" + bl_label = "Particle Trails" + + execute_on_birth__prop: NodeBuilder.ExecuteInputProperty() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("rate", "Rate", "Float", default=20) + builder.execute_input("execute_on_birth", "Execute on Birth", "execute_on_birth__prop") + builder.influences_output("main_system", "Main System") + builder.influences_output("trail_system", "Trail System") diff --git a/release/scripts/startup/nodes/declaration/__init__.py b/release/scripts/startup/nodes/declaration/__init__.py new file mode 100644 index 00000000000..6bc2b447579 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/__init__.py @@ -0,0 +1,12 @@ +from . base import NoDefaultValue +from . fixed_type import FixedSocketDecl +from . dynamic_list import ListSocketDecl +from . base_list_variadic import BaseListVariadic +from . vectorized import VectorizedInputDecl, VectorizedOutputDecl + +from . bparticles import ( + InfluencesSocketDecl, + ExecuteOutputDecl, + ExecuteInputDecl, + ExecuteInputListDecl, +) diff --git a/release/scripts/startup/nodes/declaration/base.py b/release/scripts/startup/nodes/declaration/base.py new file mode 100644 index 00000000000..54900b460ea --- /dev/null +++ b/release/scripts/startup/nodes/declaration/base.py @@ -0,0 +1,38 @@ +NoDefaultValue = object() + +class SocketDeclBase: + def init(self): + pass + + def build(self, node_sockets): + raise NotImplementedError() + + def init_default(self, node_sockets): + pass + + def amount(self): + raise NotImplementedError() + + def validate(self, sockets): + raise NotImplementedError() + + def draw_node(self, layout): + pass + + def draw_socket(self, layout, socket, index): + socket.draw_self(layout, self, socket.name) + + def operator_socket_call(self, own_socket, other_socket): + pass + + def _data_socket_test(self, socket, name, data_type, identifier): + from .. base import DataSocket + if not isinstance(socket, DataSocket): + return False + if socket.name != name: + return False + if socket.data_type != data_type: + return False + if socket.identifier != identifier: + return False + return True diff --git a/release/scripts/startup/nodes/declaration/base_list_variadic.py b/release/scripts/startup/nodes/declaration/base_list_variadic.py new file mode 100644 index 00000000000..e1e2cd02ce2 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/base_list_variadic.py @@ -0,0 +1,152 @@ +import bpy +import uuid +from bpy.props import * +from . base import SocketDeclBase +from .. base import DataSocket +from .. types import type_infos +from .. sockets import OperatorSocket + +class BaseListVariadic(SocketDeclBase): + def __init__(self, node, identifier: str, prop_name: str, base_type: str, default_amount: int): + self.node = node + self.identifier_suffix = identifier + self.prop_name = prop_name + self.base_type = base_type + self.list_type = type_infos.to_list(base_type) + self.default_amount = default_amount + + def init(self): + collection = self.get_collection() + for _ in range(self.default_amount): + item = collection.add() + item.state = "BASE" + item.identifier = str(uuid.uuid4()) + + def build(self, node_sockets): + return list(self._build(node_sockets)) + + def _build(self, node_sockets): + for item in self.get_collection(): + data_type = self.base_type if item.state == "BASE" else self.list_type + yield type_infos.build( + data_type, + node_sockets, + "", + item.identifier) + yield node_sockets.new("fn_OperatorSocket", "Operator") + + def validate(self, sockets): + collection = self.get_collection() + if len(sockets) != len(collection) + 1: + return False + + for socket, item in zip(sockets[:-1], collection): + data_type = self.base_type if item.state == "BASE" else self.list_type + if not self._data_socket_test(socket, "", data_type, item.identifier): + return False + + if sockets[-1].bl_idname != "fn_OperatorSocket": + return False + + return True + + def draw_socket(self, layout, socket, index): + if isinstance(socket, OperatorSocket): + props = layout.operator("fn.new_base_list_variadic_input", text="New Input", icon='ADD') + props.tree_name = self.node.tree.name + props.node_name = self.node.name + props.prop_name = self.prop_name + else: + row = layout.row(align=True) + socket.draw_self(row, self.node, str(index)) + props = row.operator("fn.remove_base_list_variadic_input", text="", icon='X') + props.tree_name = self.node.tree.name + props.node_name = self.node.name + props.prop_name = self.prop_name + props.index = index + + def operator_socket_call(self, own_socket, linked_socket, connected_sockets): + if len(connected_sockets) != 1: + return + connected_socket = next(iter(connected_sockets)) + if not isinstance(connected_socket, DataSocket): + return + + is_output = own_socket.is_output + origin_data_type = connected_socket.data_type + + if type_infos.is_link_allowed(origin_data_type, self.base_type): + state = "BASE" + elif type_infos.is_link_allowed(origin_data_type, self.list_type): + state = "LIST" + else: + return + + collection = self.get_collection() + item = collection.add() + item.state = state + item.identifier = str(uuid.uuid4()) + + self.node.rebuild() + + new_socket = self.node.find_socket(item.identifier, is_output) + self.node.tree.new_link(linked_socket, new_socket) + + def amount(self): + return len(self.get_collection()) + 1 + + def get_collection(self): + return getattr(self.node, self.prop_name) + + @classmethod + def Property(cls): + return CollectionProperty(type=BaseListVariadicPropertyGroup) + +class BaseListVariadicPropertyGroup(bpy.types.PropertyGroup): + bl_idname = "fn_BaseListVariadicPropertyGroup" + + state: EnumProperty( + default="BASE", + items=[ + ("BASE", "Base", "", "NONE", 0), + ("LIST", "Base", "", "NONE", 1)]) + identifier: StringProperty() + +class NewBaseListVariadicInputOperator(bpy.types.Operator): + bl_idname = "fn.new_base_list_variadic_input" + bl_label = "New Pack List Input" + bl_options = {'INTERNAL'} + + tree_name: StringProperty() + node_name: StringProperty() + prop_name: StringProperty() + + def execute(self, context): + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + collection = getattr(node, self.prop_name) + + item = collection.add() + item.state = "BASE" + item.identifier = str(uuid.uuid4()) + + tree.sync() + return {'FINISHED'} + +class RemoveBaseListVariadicInputOperator(bpy.types.Operator): + bl_idname = "fn.remove_base_list_variadic_input" + bl_label = "Remove Pack List Input" + bl_options = {'INTERNAL'} + + tree_name: StringProperty() + node_name: StringProperty() + prop_name: StringProperty() + index: IntProperty() + + def execute(self, context): + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + collection = getattr(node, self.prop_name) + collection.remove(self.index) + tree.sync() + return {'FINISHED'} diff --git a/release/scripts/startup/nodes/declaration/bparticles.py b/release/scripts/startup/nodes/declaration/bparticles.py new file mode 100644 index 00000000000..425569648e8 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/bparticles.py @@ -0,0 +1,180 @@ +import bpy +import uuid +from bpy.props import * +from . base import SocketDeclBase +from .. sockets import OperatorSocket, ExecuteSocket + +MAX_LINK_LIMIT = 4095 + +class InfluencesSocketDecl(SocketDeclBase): + def __init__(self, node, identifier: str, display_name: str): + self.node = node + self.identifier = identifier + self.display_name = display_name + + def build(self, node_sockets): + socket = node_sockets.new("fn_InfluencesSocket", self.display_name, identifier=self.identifier) + socket.link_limit = MAX_LINK_LIMIT + socket.display_shape = 'DIAMOND' + return [socket] + + def validate(self, sockets): + if len(sockets) != 1: + return False + socket = sockets[0] + if socket.bl_idname != "fn_InfluencesSocket": + return False + if socket.name != self.display_name: + return False + if socket.link_limit != MAX_LINK_LIMIT: + return False + return True + + def amount(self): + return 1 + +class ExecuteOutputDecl(SocketDeclBase): + def __init__(self, node, identifier: str, display_name: str): + self.node = node + self.identifier = identifier + self.display_name = display_name + + def build(self, node_sockets): + socket = node_sockets.new("fn_ExecuteSocket", self.display_name, identifier=self.identifier) + socket.display_shape = 'SQUARE' + return [socket] + + def amount(self): + return 1 + + def validate(self, sockets): + if len(sockets) != 1: + return False + socket = sockets[0] + if socket.name != self.display_name: + return False + elif socket.identifier != self.identifier: + return False + elif socket.bl_idname != "fn_ExecuteSocket": + return False + return True + + +class ExecuteInputDecl(SocketDeclBase): + def __init__(self, node, identifier: str, display_name: str): + self.node = node + self.identifier = identifier + self.display_name = display_name + + def build(self, node_sockets): + socket = node_sockets.new("fn_ExecuteSocket", self.display_name, identifier=self.identifier) + socket.display_shape = "SQUARE" + return [socket] + + def amount(self): + return 1 + + def validate(self, sockets): + if len(sockets) != 1: + return False + if sockets[0].bl_idname != "fn_ExecuteSocket": + return False + return True + + +class ExecuteInputListDecl(SocketDeclBase): + def __init__(self, node, identifier: str, prop_name: str, display_name: str): + self.node = node + self.identifier = identifier + self.display_name = display_name + self.prop_name = prop_name + + def build(self, node_sockets): + return list(self._build(node_sockets)) + + def _build(self, node_sockets): + items = self.get_items() + for i, item in enumerate(items): + socket = node_sockets.new( + "fn_ExecuteSocket", + self.display_name if i == 0 else "Then", + identifier=item.identifier) + socket.display_shape = 'SQUARE' + yield socket + socket = node_sockets.new( + "fn_OperatorSocket", + self.display_name) + socket.display_shape = 'SQUARE' + yield socket + + def amount(self): + return len(self.get_items()) + 1 + + def get_items(self): + return getattr(self.node, self.prop_name) + + def validate(self, sockets): + if len(sockets) != self.amount(): + return False + + for socket, item in zip(sockets[:-1], self.get_items()): + if socket.identifier != item.identifier: + return False + elif socket.bl_idname != "fn_ExecuteSocket": + return False + + if not isinstance(sockets[-1], OperatorSocket): + return False + if not sockets[-1].name == self.display_name: + return False + + return True + + def draw_socket(self, layout, socket, index): + row = layout.row(align=True) + if index == 0: + row.label(text=self.display_name) + else: + row.label(text="Then") + if isinstance(socket, ExecuteSocket): + props = row.operator("fn.remove_execute_socket", text="", icon="X") + props.tree_name = socket.id_data.name + props.node_name = self.node.name + props.prop_name = self.prop_name + props.index = index + + def operator_socket_call(self, own_socket, linked_socket, connected_sockets): + item = self.get_items().add() + item.identifier = str(uuid.uuid4()) + + self.node.rebuild() + + new_socket = self.node.find_socket(item.identifier, False) + self.node.tree.new_link(linked_socket, new_socket) + + @classmethod + def Property(cls): + return CollectionProperty(type=ExecuteInputItem) + + +class ExecuteInputItem(bpy.types.PropertyGroup): + identifier: StringProperty() + + +class RemoveExecuteSocketOperator(bpy.types.Operator): + bl_idname = "fn.remove_execute_socket" + bl_label = "Remove Execute Socket" + bl_options = {'INTERNAL'} + + tree_name: StringProperty() + node_name: StringProperty() + prop_name: StringProperty() + index: IntProperty() + + def execute(self, context): + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + collection = getattr(node, self.prop_name) + collection.remove(self.index) + tree.sync() + return {'FINISHED'} diff --git a/release/scripts/startup/nodes/declaration/dynamic_list.py b/release/scripts/startup/nodes/declaration/dynamic_list.py new file mode 100644 index 00000000000..1ad1f1d8b32 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/dynamic_list.py @@ -0,0 +1,41 @@ +from bpy.props import * +from . base import SocketDeclBase +from .. types import type_infos + +class ListSocketDecl(SocketDeclBase): + def __init__(self, node, identifier: str, display_name: str, prop_name: str, list_or_base: str): + self.node = node + self.identifier = identifier + self.display_name = display_name + self.prop_name = prop_name + self.list_or_base = list_or_base + + def build(self, node_sockets): + data_type = self.get_data_type() + return [type_infos.build( + data_type, + node_sockets, + self.display_name, + self.identifier)] + + def validate(self, sockets): + if len(sockets) != 1: + return False + return self._data_socket_test(sockets[0], + self.display_name, self.get_data_type(), self.identifier) + + def get_data_type(self): + base_type = getattr(self.node, self.prop_name) + if self.list_or_base == "BASE": + return base_type + elif self.list_or_base == "LIST": + return type_infos.to_list(base_type) + else: + assert False + + def amount(self): + return 1 + + @classmethod + def Property(cls): + return StringProperty(default="Float") diff --git a/release/scripts/startup/nodes/declaration/fixed_type.py b/release/scripts/startup/nodes/declaration/fixed_type.py new file mode 100644 index 00000000000..49c62858e90 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/fixed_type.py @@ -0,0 +1,38 @@ +from . base import SocketDeclBase, NoDefaultValue +from .. types import type_infos + +class FixedSocketDecl(SocketDeclBase): + def __init__(self, node, identifier: str, display_name: str, data_type: str, default, socket_settings: dict): + self.node = node + self.identifier = identifier + self.display_name = display_name + self.data_type = data_type + self.default = default + self.socket_settings = socket_settings + + def build(self, node_sockets): + socket = type_infos.build(self.data_type, + node_sockets, + self.display_name, + self.identifier) + for name, value in self.socket_settings.items(): + setattr(socket, name, value) + return [socket] + + def init_default(self, node_sockets): + if self.default is not NoDefaultValue: + socket = node_sockets[0] + socket.restore_state(self.default) + + def validate(self, sockets): + if len(sockets) != 1: + return False + socket = sockets[0] + for name, value in self.socket_settings.items(): + if getattr(socket, name) != value: + return False + return self._data_socket_test(sockets[0], + self.display_name, self.data_type, self.identifier) + + def amount(self): + return 1 diff --git a/release/scripts/startup/nodes/declaration/vectorized.py b/release/scripts/startup/nodes/declaration/vectorized.py new file mode 100644 index 00000000000..27e608ccce8 --- /dev/null +++ b/release/scripts/startup/nodes/declaration/vectorized.py @@ -0,0 +1,94 @@ +import bpy +from bpy.props import * +from . base import SocketDeclBase, NoDefaultValue +from .. types import type_infos +from .. utils.generic import getattr_recursive + +class VectorizedDeclBase: + def build(self, node_sockets): + data_type, name = self.get_type_and_name() + socket = type_infos.build( + data_type, + node_sockets, + name, + self.identifier) + for prop_name, value in self.socket_settings.items(): + setattr(socket, prop_name, value) + return [socket] + + def validate(self, sockets): + if len(sockets) != 1: + return False + + socket = sockets[0] + for prop_name, value in self.socket_settings.items(): + if getattr(socket, prop_name) != value: + return False + + data_type, name = self.get_type_and_name() + return self._data_socket_test(socket, + name, data_type, self.identifier) + + def amount(self): + return 1 + + def get_type_and_name(self): + if self.is_vectorized(): + return self.list_type, self.list_name + else: + return self.base_type, self.base_name + + +class VectorizedInputDecl(VectorizedDeclBase, SocketDeclBase): + def __init__(self, + node, identifier, prop_name, + base_name, list_name, + base_type, default, socket_settings): + self.node = node + self.identifier = identifier + self.prop_name = prop_name + self.base_name = base_name + self.list_name = list_name + self.base_type = base_type + self.list_type = type_infos.to_list(base_type) + self.default = default + self.socket_settings = socket_settings + + def init_default(self, node_sockets): + if self.default is not NoDefaultValue: + socket = node_sockets[0] + socket.restore_state(self.default) + + def is_vectorized(self): + stored = getattr_recursive(self.node, self.prop_name) + if stored == "BASE": + return False + elif stored == "LIST": + return True + else: + assert False + + @staticmethod + def Property(): + return StringProperty(default="BASE") + + +class VectorizedOutputDecl(VectorizedDeclBase, SocketDeclBase): + def __init__(self, + node, identifier, input_prop_names, + base_name, list_name, + base_type, socket_settings): + self.node = node + self.identifier = identifier + self.input_prop_names = input_prop_names + self.base_name = base_name + self.list_name = list_name + self.base_type = base_type + self.list_type = type_infos.to_list(base_type) + self.socket_settings = socket_settings + + def is_vectorized(self): + for prop_name in self.input_prop_names: + if getattr_recursive(self.node, prop_name) == "LIST": + return True + return False diff --git a/release/scripts/startup/nodes/file_load.py b/release/scripts/startup/nodes/file_load.py new file mode 100644 index 00000000000..6783756c7f5 --- /dev/null +++ b/release/scripts/startup/nodes/file_load.py @@ -0,0 +1,14 @@ +import bpy +from bpy.app.handlers import persistent + +@persistent +def file_load_handler(dummy): + from . sync import sync_trees_and_dependent_trees + node_trees = set(tree for tree in bpy.data.node_groups if tree.bl_idname == "FunctionTree") + sync_trees_and_dependent_trees(node_trees) + +def register(): + bpy.app.handlers.load_post.append(file_load_handler) + +def unregister(): + bpy.app.handlers.load_post.remove(file_load_handler) diff --git a/release/scripts/startup/nodes/function_nodes/__init__.py b/release/scripts/startup/nodes/function_nodes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/__init__.py diff --git a/release/scripts/startup/nodes/function_nodes/color.py b/release/scripts/startup/nodes/function_nodes/color.py new file mode 100644 index 00000000000..1125f8dcd28 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/color.py @@ -0,0 +1,58 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class SeparateColorNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_SeparateColorNode" + bl_label = "Separate Color" + + use_list__color: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input( + "color", "use_list__color", + "Color", "Colors", "Color") + + builder.vectorized_output( + "red", ["use_list__color"], + "Red", "Red", "Float") + builder.vectorized_output( + "green", ["use_list__color"], + "Green", "Green", "Float") + builder.vectorized_output( + "blue", ["use_list__color"], + "Blue", "Blue", "Float") + builder.vectorized_output( + "alpha", ["use_list__color"], + "Alpha", "Alpha", "Float") + + +class CombineColorNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_CombineColorNode" + bl_label = "Combine Color" + + use_list__red: NodeBuilder.VectorizedProperty() + use_list__green: NodeBuilder.VectorizedProperty() + use_list__blue: NodeBuilder.VectorizedProperty() + use_list__alpha: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input( + "red", "use_list__red", + "Red", "Red", "Float") + builder.vectorized_input( + "green", "use_list__green", + "Green", "Green", "Float") + builder.vectorized_input( + "blue", "use_list__blue", + "Blue", "Blue", "Float") + builder.vectorized_input( + "alpha", "use_list__alpha", + "Alpha", "Alpha", "Float", + default=1.0) + + builder.vectorized_output( + "color", ["use_list__red", "use_list__green", "use_list__blue", "use_list__alpha"], + "Color", "Colors", "Color") diff --git a/release/scripts/startup/nodes/function_nodes/float_range.py b/release/scripts/startup/nodes/function_nodes/float_range.py new file mode 100644 index 00000000000..cfd9b9cd311 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/float_range.py @@ -0,0 +1,31 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + +class FloatRangeNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_FloatRangeNode" + bl_label = "Float Range" + + mode: EnumProperty( + name="Mode", + items=[ + ("AMOUNT_START_STEP", "Amount / Start / Step", "", "NONE", 0), + ("AMOUNT_START_STOP", "Amount / Start / Stop", "", "NONE", 1), + ], + default="AMOUNT_START_STOP", + update=FunctionNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("amount", "Amount", "Integer", default=10) + builder.fixed_input("start", "Start", "Float") + if self.mode == "AMOUNT_START_STEP": + builder.fixed_input("step", "Step", "Float") + elif self.mode == "AMOUNT_START_STOP": + builder.fixed_input("stop", "Stop", "Float", default=1) + + builder.fixed_output("list", "List", "Float List") + + def draw(self, layout): + layout.prop(self, "mode", text="") diff --git a/release/scripts/startup/nodes/function_nodes/groups.py b/release/scripts/startup/nodes/function_nodes/groups.py new file mode 100644 index 00000000000..544105fa82d --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/groups.py @@ -0,0 +1,524 @@ +import bpy +from bpy.props import * +from .. types import type_infos +from .. base import BaseNode, FunctionNode, DataSocket +from .. function_tree import FunctionTree +from .. node_builder import NodeBuilder +from .. ui import NodeSidebarPanel +from .. utils.pie_menu_helper import PieMenuHelper +from .. sync import skip_syncing + +interface_type_items = [ + ("DATA", "Data", "Some data type like integer or vector", "NONE", 0), + ("EXECUTE", "Control Flow", "", "NONE", 1), + ("INFLUENCES", "Influences", "", "NONE", 2), +] + +class GroupInputNode(bpy.types.Node, BaseNode): + bl_idname = "fn_GroupInputNode" + bl_label = "Group Input" + + input_name: StringProperty( + default="Name", + update=BaseNode.sync_tree, + ) + + sort_index: IntProperty() + + display_settings: BoolProperty( + name="Display Settings", + default=False, + ) + + interface_type: EnumProperty( + items=interface_type_items, + default="DATA", + update= BaseNode.sync_tree, + ) + + data_type: StringProperty( + default="Float", + update=BaseNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + if self.interface_type == "DATA": + builder.fixed_output("value", "Value", self.data_type) + elif self.interface_type == "EXECUTE": + builder.execute_output("execute", "Execute") + elif self.interface_type == "INFLUENCES": + builder.influences_output("influences", "Influences") + else: + assert False + + def draw(self, layout): + if not self.display_settings: + return + + layout.prop(self, "interface_type", text="") + + if self.interface_type == "DATA": + if hasattr(self.outputs[0], "draw_property"): + self.outputs[0].draw_property(layout, self, "Default") + + self.invoke_type_selection(layout, "set_data_type", "Select Type") + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + row = layout.row(align=True) + row.prop(self, "input_name", text="") + row.prop(self, "display_settings", text="", icon="SETTINGS") + + def draw_closed_label(self): + return self.input_name + " (Input)" + + def set_data_type(self, data_type): + self.data_type = data_type + + +class GroupOutputNode(bpy.types.Node, BaseNode): + bl_idname = "fn_GroupOutputNode" + bl_label = "Group Output" + + sort_index: IntProperty() + + display_settings: BoolProperty( + name="Display Settings", + default=False, + ) + + output_name: StringProperty( + default="Name", + update=BaseNode.sync_tree, + ) + + interface_type: EnumProperty( + items=interface_type_items, + default="DATA", + update=BaseNode.sync_tree, + ) + + data_type: StringProperty( + default="Float", + update=BaseNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + if self.interface_type == "DATA": + builder.fixed_input("value", "Value", self.data_type) + elif self.interface_type == "EXECUTE": + builder.single_execute_input("execute", "Execute") + elif self.interface_type == "INFLUENCES": + builder.influences_input("influences", "Influences") + + def draw(self, layout): + if not self.display_settings: + return + + layout.prop(self, "interface_type", text="") + + if self.interface_type == "DATA": + self.invoke_type_selection(layout, "set_type_type", "Select Type") + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + row = layout.row(align=True) + row.prop(self, "output_name", text="") + row.prop(self, "display_settings", text="", icon="SETTINGS") + + def draw_closed_label(self): + return self.output_name + " (Output)" + + def set_type_type(self, data_type): + self.data_type = data_type + +class GroupNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GroupNode" + bl_label = "Group" + bl_icon = "NODETREE" + + node_group: PointerProperty( + type=bpy.types.NodeTree, + update=FunctionNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + if not isinstance(self.node_group, FunctionTree): + return + + for input_node in self.node_group.get_input_nodes(): + if input_node.interface_type == "DATA": + builder.fixed_input( + input_node.identifier, + input_node.input_name, + input_node.data_type, + default=input_node.outputs[0].get_state()) + elif input_node.interface_type == "EXECUTE": + builder.single_execute_input(input_node.identifier, input_node.input_name) + elif input_node.interface_type == "INFLUENCES": + builder.influences_input(input_node.identifier, input_node.input_name) + else: + assert False + + for output_node in self.node_group.get_output_nodes(): + if output_node.interface_type == "DATA": + builder.fixed_output( + output_node.identifier, + output_node.output_name, + output_node.data_type) + elif output_node.interface_type == "EXECUTE": + builder.execute_output(output_node.identifier, output_node.output_name) + elif output_node.interface_type == "INFLUENCES": + builder.influences_output(output_node.identifier, output_node.output_name) + else: + assert False + + def draw(self, layout): + layout.scale_y = 1.3 + if self.node_group is None: + self.invoke_group_selector(layout, "set_group", "Select Group", icon="NODETREE") + elif not isinstance(self.node_group, FunctionTree): + layout.label(text="Group not found!", icon="ERROR") + self.invoke_group_selector(layout, "set_group", "Change Group", icon="NODETREE") + + def draw_advanced(self, layout): + col = layout.column() + text = "Select Group" if self.node_group is None else self.node_group.name + col.scale_y = 1.3 + self.invoke_group_selector(col, "set_group", text, icon="NODETREE") + + def draw_label(self): + if self.node_group is None: + return "(G) -" + else: + return "(G) " + self.node_group.name + + def set_group(self, group): + self.node_group = group + + def iter_directly_used_trees(self): + if self.node_group is not None: + yield self.node_group + + +class GroupInterfacePanel(bpy.types.Panel, NodeSidebarPanel): + bl_idname = "FN_PT_group_interface_panel" + bl_label = "Group Interface" + + @classmethod + def poll(self, context): + try: return isinstance(context.space_data.edit_tree, FunctionTree) + except: return False + + def draw(self, context): + layout = self.layout + tree = context.space_data.edit_tree + draw_group_interface_panel(layout, tree) + + +def draw_group_interface_panel(layout, tree): + col = layout.column(align=True) + col.label(text="Inputs:") + box = col.box().column(align=True) + for i, node in enumerate(tree.get_input_nodes()): + row = box.row(align=True) + row.prop(node, "input_name", text="") + + props = row.operator("fn.move_group_interface", text="", icon="TRIA_UP") + props.is_input = True + props.from_index = i + props.offset = -1 + + props = row.operator("fn.move_group_interface", text="", icon="TRIA_DOWN") + props.is_input = True + props.from_index = i + props.offset = 1 + + col = layout.column(align=True) + col.label(text="Outputs:") + box = col.box().column(align=True) + for i, node in enumerate(tree.get_output_nodes()): + row = box.row(align=True) + row.prop(node, "output_name", text="") + + props = row.operator("fn.move_group_interface", text="", icon="TRIA_UP") + props.is_input = False + props.from_index = i + props.offset = -1 + + props = row.operator("fn.move_group_interface", text="", icon="TRIA_DOWN") + props.is_input = False + props.from_index = i + props.offset = 1 + + +class MoveGroupInterface(bpy.types.Operator): + bl_idname = "fn.move_group_interface" + bl_label = "Move Group Interface" + + is_input: BoolProperty() + from_index: IntProperty() + offset: IntProperty() + + def execute(self, context): + tree = context.space_data.node_tree + + if self.is_input: + nodes = tree.get_input_nodes() + else: + nodes = tree.get_output_nodes() + + from_index = self.from_index + to_index = min(max(self.from_index + self.offset, 0), len(nodes) - 1) + + nodes[from_index], nodes[to_index] = nodes[to_index], nodes[from_index] + + with skip_syncing(): + for i, node in enumerate(nodes): + node.sort_index = i + tree.sync() + + return {"FINISHED"} + + +def update_sort_indices(tree): + for i, node in enumerate(tree.get_input_nodes()): + node.sort_index = i + for i, node in enumerate(tree.get_output_nodes()): + node.sort_index = i + + +class ManageGroupPieMenu(bpy.types.Menu, PieMenuHelper): + bl_idname = "FN_MT_manage_group_pie" + bl_label = "Manage Group" + + @classmethod + def poll(cls, context): + try: + return isinstance(context.space_data.node_tree, FunctionTree) + except: + return False + + def draw_top(self, layout): + layout.operator("fn.open_group_management_popup", text="Group Management") + + def draw_left(self, layout): + node = bpy.context.active_node + if node is None: + self.empty(layout) + return + + possible_inputs = [(i, socket) for i, socket in enumerate(node.inputs) + if socket_can_become_group_input(socket)] + + if len(possible_inputs) == 0: + self.empty(layout, "No inputs.") + elif len(possible_inputs) == 1: + props = layout.operator("fn.create_group_input_for_socket", text="New Group Input") + props.input_index = possible_inputs[0][0] + else: + layout.operator("fn.create_group_input_for_socket_invoker", text="New Group Input") + + def draw_right(self, layout): + node = bpy.context.active_node + if node is None: + self.empty(layout) + return + + possible_outputs = [(i, socket) for i, socket in enumerate(node.outputs) + if socket_can_become_group_output(socket)] + + if len(possible_outputs) == 0: + self.empty(layout, "No outputs.") + elif len(possible_outputs) == 1: + props = layout.operator("fn.create_group_output_for_socket", text="New Group Output") + props.output_index = possible_outputs[0][0] + else: + layout.operator("fn.create_group_output_for_socket_invoker", text="New Group Output") + + +class OpenGroupManagementPopup(bpy.types.Operator): + bl_idname = "fn.open_group_management_popup" + bl_label = "Group Management" + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + draw_group_interface_panel(self.layout, context.space_data.node_tree) + + def execute(self, context): + return {"INTERFACE"} + + +class CreateGroupInputForSocketInvoker(bpy.types.Operator): + bl_idname = "fn.create_group_input_for_socket_invoker" + bl_label = "Create Group Input for Socket Invoker" + + def invoke(self, context, event): + context.window_manager.popup_menu(self.draw_menu) + return {"CANCELLED"} + + @staticmethod + def draw_menu(menu, context): + node = bpy.context.active_node + if node is None: + return + + layout = menu.layout.column() + layout.operator_context = "INVOKE_DEFAULT" + + for i, socket in enumerate(node.inputs): + if socket_can_become_group_input(socket): + props = layout.operator("fn.create_group_input_for_socket", text=socket.name) + props.input_index = i + + +class CreateGroupOutputForSocketInvoker(bpy.types.Operator): + bl_idname = "fn.create_group_output_for_socket_invoker" + bl_label = "Create Group Output for Socket Invoker" + + def invoke(self, context, event): + context.window_manager.popup_menu(self.draw_menu) + return {"CANCELLED"} + + @staticmethod + def draw_menu(menu, context): + node = bpy.context.active_node + if node is None: + return + + layout = menu.layout.column() + layout.operator_context = "INVOKE_DEFAULT" + + for i, socket in enumerate(node.outputs): + if socket_can_become_group_output(socket): + props = layout.operator("fn.create_group_output_for_socket", text=socket.name) + props.output_index = i + + +class CreateGroupInputForSocket(bpy.types.Operator): + bl_idname = "fn.create_group_input_for_socket" + bl_label = "Create Group Input for Socket" + + input_index: IntProperty() + + def invoke(self, context, event): + tree = context.space_data.node_tree + node = context.active_node + socket = node.inputs[self.input_index] + + node.select = False + + with skip_syncing(): + new_node = tree.nodes.new(type="fn_GroupInputNode") + new_node.sort_index = 1000 + new_node.input_name = socket.name + update_sort_indices(tree) + + if isinstance(socket, DataSocket): + new_node.interface_type = "DATA" + new_node.data_type = socket.data_type + elif socket.bl_idname == "fn_ExecuteSocket": + new_node.interface_type = "EXECUTE" + elif socket.bl_idname == "fn_InfluencesSocket": + new_node.interface_type = "INFLUENCES" + new_node.rebuild() + + new_node.select = True + new_node.parent = node.parent + new_node.location = node.location + new_node.location.x -= 200 + + new_node.outputs[0].restore_state(socket.get_state()) + tree.new_link(new_node.outputs[0], socket) + + tree.sync() + bpy.ops.node.translate_attach("INVOKE_DEFAULT") + return {"FINISHED"} + + +class CreateGroupOutputForSocket(bpy.types.Operator): + bl_idname = "fn.create_group_output_for_socket" + bl_label = "Create Group Output for Socket" + + output_index: IntProperty() + + def invoke(self, context, event): + tree = context.space_data.node_tree + node = context.active_node + socket = node.outputs[self.output_index] + + node.select = False + + with skip_syncing(): + new_node = tree.nodes.new(type="fn_GroupOutputNode") + new_node.sort_index = 1000 + update_sort_indices(tree) + + new_node.output_name = socket.name + if isinstance(socket, DataSocket): + new_node.interface_type = "DATA" + new_node.data_type = socket.data_type + elif socket.bl_idname == "fn_ExecuteSocket": + new_node.interface_type = "EXECUTE" + elif socket.bl_idname == "fn_InfluencesSocket": + new_node.interface_type = "INFLUENCES" + new_node.rebuild() + + new_node.select = True + new_node.parent = node.parent + new_node.location = node.location + new_node.location.x += 200 + + tree.new_link(new_node.inputs[0], socket) + + tree.sync() + bpy.ops.node.translate_attach("INVOKE_DEFAULT") + return {"FINISHED"} + + +class OpenCloseGroupOperator(bpy.types.Operator): + bl_idname = "fn.open_close_group" + bl_label = "Open/Close Group" + bl_options = {"INTERNAL"} + + @classmethod + def poll(cls, context): + try: return context.space_data.node_tree.bl_idname == "FunctionTree" + except: return False + + def invoke(self, context, event): + space_data = context.space_data + active_node = context.active_node + if isinstance(active_node, GroupNode) and active_node.node_group is not None and active_node.select: + space_data.path.append(active_node.node_group, node=active_node) + else: + space_data.path.pop() + return {"FINISHED"} + + +def socket_can_become_group_input(socket): + return socket.bl_idname != "fn_OperatorSocket" and not socket.is_linked + +def socket_can_become_group_output(socket): + return socket.bl_idname != "fn_OperatorSocket" + +keymap = None + +def register(): + global keymap + + if not bpy.app.background: + keymap = bpy.context.window_manager.keyconfigs.addon.keymaps.new( + name="Node Editor", space_type="NODE_EDITOR") + + kmi = keymap.keymap_items.new("wm.call_menu_pie", type="V", value="PRESS") + kmi.properties.name = "FN_MT_manage_group_pie" + + keymap.keymap_items.new("fn.open_close_group", type="TAB", value="PRESS") + +def unregister(): + global keymap + + if not bpy.app.background: + bpy.context.window_manager.keyconfigs.addon.keymaps.remove(keymap) + keymap = None diff --git a/release/scripts/startup/nodes/function_nodes/list.py b/release/scripts/startup/nodes/function_nodes/list.py new file mode 100644 index 00000000000..c2a688e41ed --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/list.py @@ -0,0 +1,69 @@ +import bpy +from bpy.props import * +from .. types import type_infos +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class GetListElementNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetListElementNode" + bl_label = "Get List Element" + + active_type: NodeBuilder.DynamicListProperty() + + def declaration(self, builder: NodeBuilder): + builder.dynamic_list_input("list", "List", "active_type") + builder.fixed_input("index", "Index", "Integer") + builder.dynamic_base_input("fallback", "Fallback", "active_type") + builder.dynamic_base_output("value", "Value", "active_type") + + +class GetListElementsNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetListElementsNode" + bl_label = "Get List Elements" + + active_type: NodeBuilder.DynamicListProperty() + + def declaration(self, builder: NodeBuilder): + builder.dynamic_list_input("list", "List", "active_type") + builder.fixed_input("indices", "Indices", "Integer List") + builder.dynamic_base_input("fallback", "Fallback", "active_type") + builder.dynamic_list_output("values", "Values", "active_type") + + +class ListLengthNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_ListLengthNode" + bl_label = "List Length" + + active_type: NodeBuilder.DynamicListProperty() + + def declaration(self, builder: NodeBuilder): + builder.dynamic_list_input("list", "List", "active_type") + builder.fixed_output("length", "Length", "Integer") + + +class PackListNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_PackListNode" + bl_label = "Pack List" + + active_type: StringProperty( + default="Float", + update=FunctionNode.sync_tree) + + variadic: NodeBuilder.BaseListVariadicProperty() + + def declaration(self, builder): + builder.base_list_variadic_input("inputs", "variadic", self.active_type) + builder.fixed_output("output", "List", type_infos.to_list(self.active_type)) + + def draw_advanced(self, layout): + self.invoke_type_selection(layout, "set_type", "Change Type", mode="BASE") + + def set_type(self, data_type): + self.active_type = data_type + + @classmethod + def get_search_terms(cls): + for list_type in type_infos.iter_list_types(): + base_type = type_infos.to_base(list_type) + yield ("Pack " + list_type, {"active_type" : base_type}) diff --git a/release/scripts/startup/nodes/function_nodes/math.py b/release/scripts/startup/nodes/function_nodes/math.py new file mode 100644 index 00000000000..b8c3e79f526 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/math.py @@ -0,0 +1,129 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + +def create_variadic_math_node(data_type, idname, label): + class MathNode(bpy.types.Node, FunctionNode): + bl_idname = idname + bl_label = label + + variadic: NodeBuilder.BaseListVariadicProperty() + + def declaration(self, builder: NodeBuilder): + builder.base_list_variadic_input("inputs", "variadic", data_type) + + if NodeBuilder.BaseListVariadicPropertyHasList(self.variadic): + builder.fixed_output("result", "Result", data_type + " List") + else: + builder.fixed_output("result", "Result", data_type) + + return MathNode + +def create_single_type_two_inputs_math_node(data_type, idname, label): + return create_two_inputs_math_node(data_type, data_type, data_type, idname, label) + +def create_two_inputs_math_node(input_type1, input_type2, output_type, idname, label): + class MathNode(bpy.types.Node, FunctionNode): + bl_idname = idname + bl_label = label + + use_list__a: NodeBuilder.VectorizedProperty() + use_list__b: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("a", "use_list__a", "A", "A", input_type1) + builder.vectorized_input("b", "use_list__b", "B", "B", input_type2) + builder.vectorized_output("result", ["use_list__a", "use_list__b"], "Result", "Result", output_type) + + return MathNode + +def create_single_input_math_node(input_type, output_type, idname, label): + + class MathNode(bpy.types.Node, FunctionNode): + bl_idname = idname + bl_label = label + + use_list: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("input", "use_list", "Value", "Values", input_type) + builder.vectorized_output("output", ["use_list"], "Result", "Result", output_type) + + return MathNode + +AddFloatsNode = create_variadic_math_node("Float", "fn_AddFloatsNode", "Add Floats") +MultiplyFloatsNode = create_variadic_math_node("Float", "fn_MultiplyFloatsNode", "Multiply Floats") +MinimumFloatsNode = create_variadic_math_node("Float", "fn_MinimumFloatsNode", "Minimum Floats") +MaximumFloatsNode = create_variadic_math_node("Float", "fn_MaximumFloatsNode", "Maximum Floats") + +SubtractFloatsNode = create_single_type_two_inputs_math_node("Float", "fn_SubtractFloatsNode", "Subtract Floats") +DivideFloatsNode = create_single_type_two_inputs_math_node("Float", "fn_DivideFloatsNode", "Divide Floats") +PowerFloatsNode = create_single_type_two_inputs_math_node("Float", "fn_PowerFloatsNode", "Power Floats") + +SqrtFloatNode = create_single_input_math_node("Float", "Float", "fn_SqrtFloatNode", "Sqrt Float") +AbsFloatNode = create_single_input_math_node("Float", "Float", "fn_AbsoluteFloatNode", "Absolute Float") +SineFloatNode = create_single_input_math_node("Float", "Float", "fn_SineFloatNode", "Sine") +CosineFloatNode = create_single_input_math_node("Float", "Float", "fn_CosineFloatNode", "Cosine") + +CeilFloatNode = create_single_input_math_node("Float", "Float", "fn_CeilFloatNode", "Ceil Float") +FloorFloatNode = create_single_input_math_node("Float", "Float", "fn_FloorFloatNode", "Floor Float") + +AddVectorsNode = create_variadic_math_node("Vector", "fn_AddVectorsNode", "Add Vectors") +SubtractVectorsNode = create_single_type_two_inputs_math_node("Vector", "fn_SubtractVectorsNode", "Subtract Vectors") +MultiplyVectorsNode = create_variadic_math_node("Vector", "fn_MultiplyVectorsNode", "Multiply Vectors") +DivideVectorsNode = create_single_type_two_inputs_math_node("Vector", "fn_DivideVectorsNode", "Divide Vectors") +MultiplyVectorWithFloatNode = create_two_inputs_math_node("Vector", "Float", "Vector", "fn_MultiplyVectorWithFloatNode", "Multiply Vector with Float") + +VectorCrossProductNode = create_single_type_two_inputs_math_node("Vector", "fn_VectorCrossProductNode", "Cross Product") +VectorReflectNode = create_single_type_two_inputs_math_node("Vector", "fn_ReflectVectorNode", "Reflect Vector") +VectorProjectNode = create_single_type_two_inputs_math_node("Vector", "fn_ProjectVectorNode", "Project Vector") +VectorDotProductNode = create_two_inputs_math_node("Vector", "Vector", "Float", "fn_VectorDotProductNode", "Dot Product") +VectorDistanceNode = create_two_inputs_math_node("Vector", "Vector", "Float", "fn_VectorDistanceNode", "Vector Distance") +NormalizeVectorNode = create_single_input_math_node("Vector", "Vector", "fn_NormalizeVectorNode", "Normalize Vector") +VectorLengthNode = create_single_input_math_node("Vector", "Float", "fn_VectorLengthNode", "Vector Length") + +BooleanAndNode = create_variadic_math_node("Boolean", "fn_BooleanAndNode", "And") +BooleanOrNode = create_variadic_math_node("Boolean", "fn_BooleanOrNode", "Or") +BooleanNotNode = create_single_input_math_node("Boolean", "Boolean", "fn_BooleanNotNode", "Not") + +LessThanFloatNode = create_two_inputs_math_node("Float", "Float", "Boolean", "fn_LessThanFloatNode", "Less Than Float") +GreaterThanFloatNode = create_two_inputs_math_node("Float", "Float", "Boolean", "fn_GreaterThanFloatNode", "Greater Than Float") + +class MapRangeNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_MapRangeNode" + bl_label = "Map Range" + + clamp: BoolProperty( + name="Clamp", + default=True, + ) + + use_list__value: NodeBuilder.VectorizedProperty() + use_list__from_min: NodeBuilder.VectorizedProperty() + use_list__from_max: NodeBuilder.VectorizedProperty() + use_list__to_min: NodeBuilder.VectorizedProperty() + use_list__to_max: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("value", "use_list__value", "Value", "Values", "Float") + builder.vectorized_input("from_min", "use_list__from_min", "From Min", "From Min", "Float") + builder.vectorized_input("from_max", "use_list__from_max", "From Max", "From Max", "Float") + builder.vectorized_input("to_min", "use_list__to_min", "To Min", "To Min", "Float") + builder.vectorized_input("to_max", "use_list__to_max", "To Max", "To Max", "Float") + builder.vectorized_output("value", [ + "use_list__value", "use_list__from_min", "use_list__from_max", + "use_list__to_min", "use_list__to_max"], "Value", "Values", "Float") + + def draw(self, layout): + layout.prop(self, "clamp") + +class FloatClampNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_FloatClampNode" + bl_label = "Clamp" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("value", "Value", "Float") + builder.fixed_input("min", "Min", "Float", default=0) + builder.fixed_input("max", "Max", "Float", default=1) + builder.fixed_output("value", "Value", "Float") diff --git a/release/scripts/startup/nodes/function_nodes/node_instance_identifier.py b/release/scripts/startup/nodes/function_nodes/node_instance_identifier.py new file mode 100644 index 00000000000..62b8d6641e6 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/node_instance_identifier.py @@ -0,0 +1,12 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class NodeInstanceIdentifierNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_NodeInstanceIdentifierNode" + bl_label = "Node Instance Identifier" + + def declaration(self, builder): + builder.fixed_output("identifier", "Identifier", "Text") diff --git a/release/scripts/startup/nodes/function_nodes/noise.py b/release/scripts/startup/nodes/function_nodes/noise.py new file mode 100644 index 00000000000..5be3becf899 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/noise.py @@ -0,0 +1,143 @@ +import bpy +import random +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class PerlinNoiseNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_PerlinNoiseNode" + bl_label = "Perlin Noise" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("position", "Position", "Vector") + builder.fixed_input("amplitude", "Amplitude", "Float", default=1) + builder.fixed_input("scale", "Scale", "Float", default=1) + builder.fixed_output("noise_1d", "Noise", "Float") + builder.fixed_output("noise_3d", "Noise", "Vector") + + +class RandomFloatNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_RandomFloatNode" + bl_label = "Random Float" + + node_seed: IntProperty( + name="Node Seed", + ) + + def init_props(self): + self.node_seed = new_node_seed() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("min", "Min", "Float", default=0) + builder.fixed_input("max", "Max", "Float", default=1) + builder.fixed_input("seed", "Seed", "Integer") + builder.fixed_output("value", "Value", "Float") + + def draw_advanced(self, layout): + layout.prop(self, "node_seed") + + def duplicate(self, src_node): + self.node_seed = new_node_seed() + + +class RandomFloatsNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_RandomFloatsNode" + bl_label = "Random Floats" + + node_seed: IntProperty( + name="Node Seed", + ) + + def init_props(self): + self.node_seed = new_node_seed() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("amount", "Amount", "Integer", default=10) + builder.fixed_input("min", "Min", "Float") + builder.fixed_input("max", "Max", "Float", default=1) + builder.fixed_input("seed", "Seed", "Integer") + builder.fixed_output("values", "Values", "Float List") + + def draw_advanced(self, layout): + layout.prop(self, "node_seed") + + def duplicate(self, src_node): + self.node_seed = new_node_seed() + +random_vector_mode_items = [ + ("UNIFORM_IN_CUBE", "Uniform in Cube", "Generate a vector that is somewhere in the volume of a cube", "NONE", 0), + ("UNIFORM_ON_SPHERE", "Uniform on Sphere", "Generate a vector that is somewhere on the surface of a sphere", 1), + ("UNIFORM_IN_SPHERE", "Uniform in Sphere", "Generate a vector that is somewhere in the volume of a sphere", 2), +] + +class RandomVectorNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_RandomVectorNode" + bl_label = "Random Vector" + + node_seed: IntProperty( + name="Node Seed", + ) + + mode: EnumProperty( + name="Mode", + items=random_vector_mode_items, + default="UNIFORM_IN_CUBE", + ) + + use_list__factor: NodeBuilder.VectorizedProperty() + use_list__seed: NodeBuilder.VectorizedProperty() + + def init_props(self): + self.node_seed = new_node_seed() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("factor", "use_list__factor", "Factor", "Factors", "Vector", default=(1, 1, 1)) + builder.vectorized_input("seed", "use_list__seed", "Seed", "Seeds", "Integer") + builder.vectorized_output("vector", ["use_list__factor", "use_list__seed"], "Vector", "Vectors", "Vector") + + def draw(self, layout): + layout.prop(self, "mode", text="") + + def draw_advanced(self, layout): + layout.prop(self, "node_seed") + + def duplicate(self, src_node): + self.node_seed = new_node_seed() + + +class RandomVectorsNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_RandomVectorsNode" + bl_label = "Random Vectors" + + node_seed: IntProperty( + name="Node Seed", + ) + + mode: EnumProperty( + name="Mode", + items=random_vector_mode_items, + default="UNIFORM_IN_CUBE", + ) + + def init_props(self): + self.node_seed = new_node_seed() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("amount", "Amount", "Integer", default=10) + builder.fixed_input("factor", "Factor", "Vector", default=(1, 1, 1)) + builder.fixed_input("seed", "Seed", "Integer") + builder.fixed_output("vectors", "Vectors", "Vector List") + + def draw(self, layout): + layout.prop(self, "mode", text="") + + def draw_advanced(self, layout): + layout.prop(self, "node_seed") + + def duplicate(self, src_node): + self.node_seed = new_node_seed() + + +def new_node_seed(): + return random.randint(0, 10000) diff --git a/release/scripts/startup/nodes/function_nodes/object_mesh.py b/release/scripts/startup/nodes/function_nodes/object_mesh.py new file mode 100644 index 00000000000..c1848e56f52 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/object_mesh.py @@ -0,0 +1,113 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + +class ObjectMeshNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_ObjectMeshNode" + bl_label = "Object Mesh" + + def declaration(self, builder): + builder.fixed_input("object", "Object", "Object", display_name=False) + builder.fixed_output("vertex_locations", "Vertex Locations", "Vector List") + + +class VertexInfo(bpy.types.Node, FunctionNode): + bl_idname = "fn_VertexInfoNode" + bl_label = "Vertex Info" + + def declaration(self, builder): + builder.fixed_output("position", "Position", "Vector") + + +class ClosestLocationOnObjectNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_ClosestLocationOnObjectNode" + bl_label = "Closest Location on Object" + + use_list__object: NodeBuilder.VectorizedProperty() + use_list__position: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("object", "use_list__object", "Object", "Objects", "Object", display_name=False) + builder.vectorized_input("position", "use_list__position", "Position", "Positions", "Vector") + + vectorize_props = ["use_list__object", "use_list__position"] + builder.vectorized_output("closest_hook", vectorize_props, "Closest Hook", "Closest Hooks", "Surface Hook") + builder.vectorized_output("closest_position", vectorize_props, "Closest Position", "Closest Positions", "Vector") + builder.vectorized_output("closest_normal", vectorize_props, "Closest Normal", "Closest Normals", "Vector") + + +class GetPositionOnSurfaceNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetPositionOnSurfaceNode" + bl_label = "Get Position on Surface" + + use_list__surface_hook: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("surface_hook", "use_list__surface_hook", "Surface Hook", "Surface Hooks", "Surface Hook") + builder.vectorized_output("position", ["use_list__surface_hook"], "Position", "Positions", "Vector") + + +class GetNormalOnSurfaceNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetNormalOnSurfaceNode" + bl_label = "Get Normal on Surface" + + use_list__surface_hook: NodeBuilder.VectorizedProperty() + + def declaration(self, builder): + builder.vectorized_input("surface_hook", "use_list__surface_hook", "Surface Hook", "Surface Hooks", "Surface Hook") + builder.vectorized_output("normal", ["use_list__surface_hook"], "Normal", "Normals", "Vector") + + +class GetWeightOnSurfaceNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetWeightOnSurfaceNode" + bl_label = "Get Weight on Surface" + + use_list__surface_hook: NodeBuilder.VectorizedProperty() + use_list__vertex_group_name: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("surface_hook", "use_list__surface_hook", "Surface Hook", "Surface Hooks", "Surface Hook") + builder.vectorized_input("vertex_group_name", "use_list__vertex_group_name", "Name", "Names", "Text", + default="Group", display_name=False, display_icon="GROUP_VERTEX") + builder.vectorized_output("weight", ["use_list__surface_hook", "use_list__vertex_group_name"], "Weight", "Weights", "Float") + + +class GetImageColorOnSurfaceNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_GetImageColorOnSurfaceNode" + bl_label = "Get Image Color on Surface" + + use_list__surface_hook: NodeBuilder.VectorizedProperty() + use_list__image: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("surface_hook", "use_list__surface_hook", "Surface Hook", "Surface Hooks", "Surface Hook") + builder.vectorized_input("image", "use_list__image", "Image", "Images", "Image", display_name=False) + builder.vectorized_output("color", ["use_list__surface_hook", "use_list__image"], "Color", "Colors", "Color") + + +class SampleObjectSurfaceNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_SampleObjectSurfaceNode" + bl_label = "Sample Object Surface" + + weight_mode: EnumProperty( + name="Weight Mode", + items=[ + ("UNIFORM", "Uniform", "", "NONE", 0), + ("VERTEX_WEIGHTS", "Vertex Weights", "", "NONE", 1), + ], + default="UNIFORM", + update=FunctionNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("object", "Object", "Object", display_name=False) + builder.fixed_input("amount", "Amount", "Integer", default=10) + builder.fixed_input("seed", "Seed", "Integer") + if self.weight_mode == "VERTEX_WEIGHTS": + builder.fixed_input("vertex_group_name", "Vertex Group", "Text", default="Group") + + builder.fixed_output("surface_hooks", "Surface Hooks", "Surface Hook List") + + def draw(self, layout): + layout.prop(self, "weight_mode", text="") diff --git a/release/scripts/startup/nodes/function_nodes/object_transforms.py b/release/scripts/startup/nodes/function_nodes/object_transforms.py new file mode 100644 index 00000000000..cbc06c04fc4 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/object_transforms.py @@ -0,0 +1,11 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode + +class ObjectTransformsNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_ObjectTransformsNode" + bl_label = "Object Transforms" + + def declaration(self, builder): + builder.fixed_input("object", "Object", "Object") + builder.fixed_output("location", "Location", "Vector") diff --git a/release/scripts/startup/nodes/function_nodes/switch.py b/release/scripts/startup/nodes/function_nodes/switch.py new file mode 100644 index 00000000000..0788d3ab899 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/switch.py @@ -0,0 +1,79 @@ +import bpy +import uuid +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + +class SwitchNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_SwitchNode" + bl_label = "Switch" + + data_type: StringProperty( + default="Float", + update=FunctionNode.sync_tree + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("condition", "Condition", "Boolean") + builder.fixed_input("true", "True", self.data_type) + builder.fixed_input("false", "False", self.data_type) + builder.fixed_output("result", "Result", self.data_type) + + def draw(self, layout): + self.invoke_type_selection(layout, "set_type", "Change Type") + + def set_type(self, data_type): + self.data_type = data_type + + +class SelectNodeItem(bpy.types.PropertyGroup): + identifier: StringProperty() + +class SelectNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_SelectNode" + bl_label = "Select" + + data_type: StringProperty( + default="Float", + update=FunctionNode.sync_tree, + ) + + input_items: CollectionProperty( + type=SelectNodeItem, + ) + + def init_props(self): + self.add_input() + self.add_input() + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("select", "Select", "Integer") + for i, item in enumerate(self.input_items): + builder.fixed_input(item.identifier, str(i), self.data_type) + builder.fixed_input("fallback", "Fallback", self.data_type) + builder.fixed_output("result", "Result", self.data_type) + + def draw(self, layout): + self.invoke_type_selection(layout, "set_type", "Change Type") + self.invoke_function(layout, "add_input", "Add Input") + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + if len(socket.name) <= 3: + index = int(socket.name) + row = layout.row(align=True) + decl.draw_socket(row, socket, index_in_decl) + self.invoke_function(row, "remove_input", "", icon="X", settings=(index, )) + else: + decl.draw_socket(layout, socket, index_in_decl) + + def set_type(self, data_type): + self.data_type = data_type + + def add_input(self): + item = self.input_items.add() + item.identifier = str(uuid.uuid4()) + self.sync_tree() + + def remove_input(self, index): + self.input_items.remove(index) + self.sync_tree() diff --git a/release/scripts/startup/nodes/function_nodes/text.py b/release/scripts/startup/nodes/function_nodes/text.py new file mode 100644 index 00000000000..e70b66a4439 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/text.py @@ -0,0 +1,20 @@ +import bpy +from .. node_builder import NodeBuilder +from .. base import FunctionNode + +class TextLengthNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_TextLengthNode" + bl_label = "Text Length" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("text", "Text", "Text") + builder.fixed_output("length", "Length", "Integer") + + +class JoinTextListNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_JoinTextListNode" + bl_label = "Join Text List" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("texts", "Texts", "Text List") + builder.fixed_output("text", "Text", "Text") diff --git a/release/scripts/startup/nodes/function_nodes/time.py b/release/scripts/startup/nodes/function_nodes/time.py new file mode 100644 index 00000000000..596d0c4d7c9 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/time.py @@ -0,0 +1,10 @@ +import bpy +from .. node_builder import NodeBuilder +from .. base import FunctionNode + +class TimeInfoNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_TimeInfoNode" + bl_label = "Time Info" + + def declaration(self, builder: NodeBuilder): + builder.fixed_output("frame", "Frame", "Float") diff --git a/release/scripts/startup/nodes/function_nodes/value.py b/release/scripts/startup/nodes/function_nodes/value.py new file mode 100644 index 00000000000..d386c6c7f0b --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/value.py @@ -0,0 +1,29 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class ValueNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_ValueNode" + bl_label = "Value" + + data_type: StringProperty( + name="Data Type", + default="Float", + update=FunctionNode.sync_tree, + ) + + def declaration(self, builder: NodeBuilder): + builder.fixed_output("value", "Value", self.data_type) + + def draw_socket(self, layout, socket, text, decl, index_in_decl): + row = layout.row(align=True) + if hasattr(socket, "draw_property"): + socket.draw_property(row, self, "") + else: + row.label(text=text) + self.invoke_type_selection(row, "set_type", text="", icon="SETTINGS") + + def set_type(self, data_type): + self.data_type = data_type diff --git a/release/scripts/startup/nodes/function_nodes/vector.py b/release/scripts/startup/nodes/function_nodes/vector.py new file mode 100644 index 00000000000..34c99a0d333 --- /dev/null +++ b/release/scripts/startup/nodes/function_nodes/vector.py @@ -0,0 +1,71 @@ +import bpy +from bpy.props import * +from .. base import FunctionNode +from .. node_builder import NodeBuilder + + +class VectorFromValueNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_VectorFromValueNode" + bl_label = "Vector from Value" + + use_list__value: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input("value", "use_list__value", "Value", "Values", "Float") + builder.vectorized_output("vector", ["use_list__value"], "Vector", "Vectors", "Vector") + + +class CombineVectorNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_CombineVectorNode" + bl_label = "Combine Vector" + + use_list__x: NodeBuilder.VectorizedProperty() + use_list__y: NodeBuilder.VectorizedProperty() + use_list__z: NodeBuilder.VectorizedProperty() + + def declaration(self, builder): + builder.vectorized_input( + "x", "use_list__x", + "X", "X", "Float") + builder.vectorized_input( + "y", "use_list__y", + "Y", "Y", "Float") + builder.vectorized_input( + "z", "use_list__z", + "Z", "Z", "Float") + + builder.vectorized_output( + "vector", ["use_list__x", "use_list__y", "use_list__z"], + "Vector", "Vectors", "Vector") + + +class SeparateVectorNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_SeparateVectorNode" + bl_label = "Separate Vector" + + use_list__vector: NodeBuilder.VectorizedProperty() + + def declaration(self, builder: NodeBuilder): + builder.vectorized_input( + "vector", "use_list__vector", + "Vector", "Vectors", "Vector") + + builder.vectorized_output( + "x", ["use_list__vector"], + "X", "X", "Float") + builder.vectorized_output( + "y", ["use_list__vector"], + "Y", "Y", "Float") + builder.vectorized_output( + "z", ["use_list__vector"], + "Z", "Z", "Float") + + +class FindNonClosePointsNode(bpy.types.Node, FunctionNode): + bl_idname = "fn_FindNonClosePointsNode" + bl_label = "Find Non Close Point" + + def declaration(self, builder: NodeBuilder): + builder.fixed_input("points", "Points", "Vector List") + builder.fixed_input("min_distance", "Min Distance", "Float", default=0.1) + builder.fixed_output("indices", "Indices", "Integer List") diff --git a/release/scripts/startup/nodes/function_tree.py b/release/scripts/startup/nodes/function_tree.py new file mode 100644 index 00000000000..ce53bd3d76a --- /dev/null +++ b/release/scripts/startup/nodes/function_tree.py @@ -0,0 +1,57 @@ +import bpy +from collections import namedtuple + +from . base import BaseTree, BaseNode +from . graph import DirectedGraphBuilder, DirectedGraph + +class FunctionTree(bpy.types.NodeTree, BaseTree): + bl_idname = "FunctionTree" + bl_icon = "MOD_DATA_TRANSFER" + bl_label = "Function Nodes" + + def get_input_nodes(self): + input_nodes = [node for node in self.nodes if node.bl_idname == "fn_GroupInputNode"] + sorted_input_nodes = sorted(input_nodes, key=lambda node: (node.sort_index, node.name)) + return sorted_input_nodes + + def get_output_nodes(self): + output_nodes = [node for node in self.nodes if node.bl_idname == "fn_GroupOutputNode"] + sorted_output_nodes = sorted(output_nodes, key=lambda node: (node.sort_index, node.name)) + return sorted_output_nodes + + def get_directly_used_trees(self): + trees = set() + for node in self.nodes: + if isinstance(node, BaseNode): + trees.update(node.iter_directly_used_trees()) + return trees + + def find_callable_trees(self): + used_by_trees = FunctionTree.BuildInvertedCallGraph().reachable(self) + trees = [tree for tree in bpy.data.node_groups + if isinstance(tree, FunctionTree) and tree not in used_by_trees] + return trees + + @staticmethod + def BuildTreeCallGraph() -> DirectedGraph: + ''' + Every vertex is a tree. + Every edge (A, B) means: Tree A uses tree B. + ''' + builder = DirectedGraphBuilder() + for tree in bpy.data.node_groups: + if isinstance(tree, FunctionTree): + builder.add_vertex(tree) + for dependency_tree in tree.get_directly_used_trees(): + builder.add_directed_edge( + from_v=tree, + to_v=dependency_tree) + return builder.build() + + @staticmethod + def BuildInvertedCallGraph() -> DirectedGraph: + ''' + Builds a directed graph in which every tree is a vertex. + Every edge (A, B) means: Changes in A might affect B. + ''' + return FunctionTree.BuildTreeCallGraph().inverted() diff --git a/release/scripts/startup/nodes/graph.py b/release/scripts/startup/nodes/graph.py new file mode 100644 index 00000000000..f005da463ed --- /dev/null +++ b/release/scripts/startup/nodes/graph.py @@ -0,0 +1,87 @@ +from collections import namedtuple, defaultdict + +class DirectedGraph: + def __init__(self, V, E): + assert all(isinstance(e, tuple) for e in E) + assert all(v1 in V and v2 in V for v1, v2 in E) + self.V = set(V) + self.E = set(E) + + self.outgoing = defaultdict(set) + self.incoming = defaultdict(set) + self.neighbors = defaultdict(set) + for v1, v2 in E: + self.outgoing[v1].add(v2) + self.incoming[v2].add(v1) + self.neighbors[v1].add(v2) + self.neighbors[v2].add(v1) + + def inverted(self): + return DirectedGraph(self.V, [(v2, v1) for v1, v2 in self.E]) + + def reachable(self, start_verts): + return self._reachable(start_verts, self.outgoing) + + def reachable_inversed(self, start_verts): + return self._reachable(start_verts, self.incoming) + + def connected(self, start_verts): + return self._reachable(start_verts, self.neighbors) + + def _reachable(self, start_verts, next_map): + if start_verts in self.V: + start_verts = (start_verts, ) + assert all(v in self.V for v in start_verts) + + verts_to_check = set(start_verts) + found_verts = set() + while len(verts_to_check) > 0: + v = verts_to_check.pop() + found_verts.add(v) + for prev_v in next_map[v]: + if prev_v not in found_verts: + verts_to_check.add(prev_v) + return found_verts + + def toposort(self): + return self.toposort_partial(self.V) + + def toposort_partial(self, verts_to_sort): + verts_to_sort = set(verts_to_sort) + sorted_verts = list() + temp_marked_verts = set() + finished_verts = set() + + def visit(v): + if v in finished_verts: + return + if v in temp_marked_verts: + raise Exception("not a DAG") + temp_marked_verts.add(v) + for prev_v in self.incoming[v]: + visit(prev_v) + finished_verts.add(v) + if v in verts_to_sort: + sorted_verts.append(v) + + for v in verts_to_sort: + visit(v) + + return tuple(sorted_verts) + + +class DirectedGraphBuilder: + def __init__(self): + self.V = set() + self.E = set() + + def add_vertex(self, v): + self.V.add(v) + + def add_directed_edge(self, from_v, to_v): + self.V.add(from_v) + self.V.add(to_v) + self.E.add((from_v, to_v)) + + def build(self): + return DirectedGraph(self.V, self.E) diff --git a/release/scripts/startup/nodes/inferencing.py b/release/scripts/startup/nodes/inferencing.py new file mode 100644 index 00000000000..71bdbce0d40 --- /dev/null +++ b/release/scripts/startup/nodes/inferencing.py @@ -0,0 +1,291 @@ +from collections import namedtuple, defaultdict +from . utils.graph import iter_connected_components +from . types import type_infos +from . tree_data import TreeData + +from . declaration import ( + FixedSocketDecl, + ListSocketDecl, + BaseListVariadic, + VectorizedInputDecl, + VectorizedOutputDecl, +) + +DecisionID = namedtuple("DecisionID", ("node", "prop_name")) + +def get_inferencing_decisions(tree_data: TreeData): + list_decisions = make_list_decisions(tree_data) + vector_decisions = make_vector_decisions(tree_data, list_decisions) + base_list_variadic_decisions = make_base_list_variadic_decisions(tree_data, list_decisions, vector_decisions) + + decisions = dict() + decisions.update(list_decisions) + decisions.update(vector_decisions) + decisions.update(base_list_variadic_decisions) + return decisions + + +# Inference list type decisions +################################################# + +def make_list_decisions(tree_data): + decision_users = get_list_decision_ids_with_users(tree_data) + decision_links = get_list_decision_links(tree_data) + + decisions = dict() + + for component in iter_connected_components(decision_users.keys(), decision_links): + possible_types = set(iter_possible_list_component_types( + component, decision_users, tree_data)) + + if len(possible_types) == 1: + base_type = next(iter(possible_types)) + for decision_id in component: + decisions[decision_id] = base_type + + return decisions + +def get_list_decision_ids_with_users(tree_data): + decision_users = defaultdict(lambda: {"BASE": [], "LIST": []}) + + for node in tree_data.iter_nodes(): + for decl, sockets in node.decl_map.iter_decl_with_sockets(): + if isinstance(decl, ListSocketDecl): + decision_id = DecisionID(node, decl.prop_name) + decision_users[decision_id][decl.list_or_base].append(sockets[0]) + + return decision_users + +def get_list_decision_links(tree_data): + linked_decisions = defaultdict(set) + + for from_socket, to_socket in tree_data.iter_connections(): + from_node = tree_data.get_node(from_socket) + to_node = tree_data.get_node(to_socket) + from_decl = from_socket.get_decl(from_node) + to_decl = to_socket.get_decl(to_node) + if isinstance(from_decl, ListSocketDecl) and isinstance(to_decl, ListSocketDecl): + if from_decl.list_or_base == to_decl.list_or_base: + from_decision_id = DecisionID(from_node, from_decl.prop_name) + to_decision_id = DecisionID(to_node, to_decl.prop_name) + linked_decisions[from_decision_id].add(to_decision_id) + linked_decisions[to_decision_id].add(from_decision_id) + + return linked_decisions + +def iter_possible_list_component_types(component, decision_users, tree_data): + for decision_id in component: + for socket in decision_users[decision_id]["LIST"]: + for other_node, other_socket in tree_data.iter_connected_sockets_with_nodes(socket): + other_decl = other_socket.get_decl(other_node) + if data_sockets_are_static(other_decl): + data_type = other_socket.data_type + if type_infos.is_list(data_type): + yield type_infos.to_base(data_type) + elif isinstance(other_decl, BaseListVariadic): + yield other_decl.base_type + elif isinstance(other_decl, VectorizedInputDecl): + yield other_decl.base_type + elif isinstance(other_decl, VectorizedOutputDecl): + yield other_decl.base_type + for socket in decision_users[decision_id]["BASE"]: + for other_node, other_socket in tree_data.iter_connected_sockets_with_nodes(socket): + other_decl = other_socket.get_decl(other_node) + if data_sockets_are_static(other_decl): + data_type = other_socket.data_type + if type_infos.is_base(data_type): + yield data_type + elif isinstance(other_decl, BaseListVariadic): + yield other_decl.base_type + elif isinstance(other_decl, VectorizedInputDecl): + yield other_decl.base_type + elif isinstance(other_decl, VectorizedOutputDecl): + yield other_decl.base_type + + +# Inference vectorization decisions +######################################## + +def make_vector_decisions(tree_data, list_decisions): + graph, input_sockets, output_sockets = get_vector_decisions_graph(tree_data) + + decisions = dict() + decision_ids_with_collision = set() + + for initial_decision_id, decision in iter_obligatory_vector_decisions(graph, input_sockets, output_sockets, tree_data, list_decisions): + for decision_id in graph.reachable(initial_decision_id): + if decision_id in decisions: + if decisions[decision_id] != decision: + decision_ids_with_collision.add(decision_id) + else: + decisions[decision_id] = decision + + for decision_id in graph.V: + decisions.setdefault(decision_id, "BASE") + + while len(decision_ids_with_collision) > 0: + collision_decision_id = decision_ids_with_collision.pop() + connected_decision_ids = graph.connected(collision_decision_id) + for decision_id in connected_decision_ids: + decisions.pop(decision_id, None) + decision_ids_with_collision.discard(decision_id) + + return decisions + +def get_vector_decisions_graph(tree_data): + ''' + Builds a directed graph. + Vertices in that graph are decision IDs. + A directed edge (A, B) means: If A is a list, then B has to be a list. + ''' + from . graph import DirectedGraphBuilder + builder = DirectedGraphBuilder() + input_sockets = set() + output_sockets = set() + + for node in tree_data.iter_nodes(): + for decl, sockets in node.decl_map.iter_decl_with_sockets(): + if isinstance(decl, VectorizedInputDecl): + decision_id = DecisionID(node, decl.prop_name) + builder.add_vertex(decision_id) + input_sockets.add(sockets[0]) + elif isinstance(decl, VectorizedOutputDecl): + output_sockets.add(sockets[0]) + + for from_socket, to_socket in tree_data.iter_connections(): + from_node = tree_data.get_node(from_socket) + to_node = tree_data.get_node(to_socket) + + from_decl = from_socket.get_decl(from_node) + to_decl = to_socket.get_decl(to_node) + + if isinstance(from_decl, VectorizedOutputDecl) and isinstance(to_decl, VectorizedInputDecl): + for prop_name in from_decl.input_prop_names: + from_decision_id = DecisionID(from_node, prop_name) + to_decision_id = DecisionID(to_node, to_decl.prop_name) + builder.add_directed_edge(from_decision_id, to_decision_id) + + return builder.build(), input_sockets, output_sockets + +def iter_obligatory_vector_decisions(graph, input_sockets, output_sockets, tree_data, list_decisions): + for socket in input_sockets: + other_node, other_socket = tree_data.try_get_origin_with_node(socket) + if other_node is None: + continue + + node = tree_data.get_node(socket) + decl = socket.get_decl(node) + decision_id = DecisionID(node, decl.prop_name) + + other_decl = other_socket.get_decl(other_node) + if data_sockets_are_static(other_decl): + other_data_type = other_socket.data_type + if type_infos.is_list(other_data_type) and type_infos.is_link_allowed(other_data_type, decl.list_type): + yield decision_id, "LIST" + elif isinstance(other_decl, ListSocketDecl): + if other_decl.list_or_base == "LIST": + list_decision_id = DecisionID(other_node, other_decl.prop_name) + if list_decision_id in list_decisions: + other_base_type = list_decisions[list_decision_id] + if type_infos.is_link_allowed(other_base_type, decl.base_type): + yield decision_id, "LIST" + else: + old_data_type = other_socket.data_type + if type_infos.is_link_allowed(old_data_type, decl.list_type): + yield decision_id, "LIST" + + for socket in output_sockets: + node = tree_data.get_node(socket) + decl = socket.get_decl(node) + decision_ids = [DecisionID(node, p) for p in decl.input_prop_names] + + for other_node, other_socket in tree_data.iter_connected_sockets_with_nodes(socket): + other_decl = other_socket.get_decl(other_node) + if data_sockets_are_static(other_decl): + other_data_type = other_socket.data_type + if type_infos.is_base(other_data_type) and type_infos.is_link_allowed(other_data_type, decl.base_type): + for decision_id in decision_ids: + yield decision_id, "BASE" + elif isinstance(other_decl, ListSocketDecl): + if other_decl.list_or_base == "BASE": + list_decision_id = DecisionID(other_node, other_decl.prop_name) + if list_decision_id in list_decisions: + other_base_type = list_decisions[list_decision_id] + if type_infos.is_link_allowed(decl.base_type, other_base_type): + for decision_id in decision_ids: + yield decision_id, "BASE" + else: + old_data_type = other_socket.data_type + if type_infos.is_link_allowed(decl.base_type, old_data_type): + for decision_id in decision_ids: + yield decision_id, "BASE" + + +# Inference pack list decisions +######################################## + +def make_base_list_variadic_decisions(tree_data, list_decisions, vector_decisions): + decisions = dict() + + for decision_id, decl, socket in iter_base_list_variadic_sockets(tree_data): + assert not socket.is_output + + origin_node, origin_socket = tree_data.try_get_origin_with_node(socket) + if origin_socket is None: + decisions[decision_id] = "BASE" + continue + + origin_decl = origin_socket.get_decl(origin_node) + if data_sockets_are_static(origin_decl): + data_type = origin_socket.data_type + if type_infos.is_link_allowed(data_type, decl.base_type): + decisions[decision_id] = "BASE" + elif type_infos.is_link_allowed(data_type, decl.list_type): + decisions[decision_id] = "LIST" + else: + decisions[decision_id] = "BASE" + elif isinstance(origin_decl, ListSocketDecl): + list_decision_id = DecisionID(origin_node, origin_decl.prop_name) + if list_decision_id in list_decisions: + other_base_type = list_decisions[list_decision_id] + if type_infos.is_link_allowed(other_base_type, decl.base_type): + decisions[decision_id] = origin_decl.list_or_base + else: + decisions[decision_id] = "BASE" + else: + old_origin_type = origin_socket.data_type + if type_infos.is_link_allowed(old_origin_type, decl.base_type): + decisions[decision_id] = "BASE" + elif type_infos.is_link_allowed(old_origin_type, decl.list_type): + decisions[decision_id] = "LIST" + else: + decisions[decision_id] = "BASE" + elif isinstance(origin_decl, VectorizedOutputDecl): + other_base_type = origin_decl.base_type + if type_infos.is_link_allowed(other_base_type, decl.base_type): + for input_prop_name in origin_decl.input_prop_names: + input_decision_id = DecisionID(origin_node, input_prop_name) + if input_decision_id in vector_decisions: + if vector_decisions[input_decision_id] == "LIST": + decisions[decision_id] = "LIST" + break + else: + decisions[decision_id] = "BASE" + else: + decisions[decision_id] = "BASE" + else: + decisions[decision_id] = "BASE" + + return decisions + +def data_sockets_are_static(decl): + return isinstance(decl, FixedSocketDecl) + +def iter_base_list_variadic_sockets(tree_data): + for node in tree_data.iter_nodes(): + for decl, sockets in node.decl_map.iter_decl_with_sockets(): + if isinstance(decl, BaseListVariadic): + collection = decl.get_collection() + for i, socket in enumerate(sockets[:-1]): + decision_id = DecisionID(node, f"{decl.prop_name}[{i}].state") + yield decision_id, decl, socket diff --git a/release/scripts/startup/nodes/keymap.py b/release/scripts/startup/nodes/keymap.py new file mode 100644 index 00000000000..909217f980e --- /dev/null +++ b/release/scripts/startup/nodes/keymap.py @@ -0,0 +1,16 @@ +import bpy + +def register(): + wm = bpy.context.window_manager + if wm.keyconfigs.addon is None: + return + + km = wm.keyconfigs.addon.keymaps.new( + name="Node Editor", + space_type='NODE_EDITOR') + + km.keymap_items.new( + "fn.node_search", + type='A', + value='PRESS', + ctrl=True) diff --git a/release/scripts/startup/nodes/menu.py b/release/scripts/startup/nodes/menu.py new file mode 100644 index 00000000000..0aa16af7f27 --- /dev/null +++ b/release/scripts/startup/nodes/menu.py @@ -0,0 +1,95 @@ +import bpy +from . function_tree import FunctionTree + +def draw_menu(self, context): + tree = context.space_data.node_tree + if not isinstance(tree, FunctionTree): + return + + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + + layout.operator("fn.node_search", text="Search", icon='VIEWZOOM') + layout.separator() + layout.menu("FN_MT_function_nodes_menu", text="Function Nodes") + layout.separator() + insert_node(layout, "fn_ParticleSystemNode", "Particle System") + layout.menu("BP_MT_influences_nodes_menu", text="Influences") + layout.menu("BP_MT_action_nodes_menu", text="Actions") + +class FunctionNodesMenu(bpy.types.Menu): + bl_idname = "FN_MT_function_nodes_menu" + bl_label = "Function Nodes Menu" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + + insert_node(layout, "fn_SwitchNode", "Switch") + layout.separator() + insert_node(layout, "fn_FloatRangeNode", "Float Range") + layout.separator() + insert_node(layout, "fn_CombineVectorNode", "Combine Vector") + insert_node(layout, "fn_SeparateVectorNode", "Separate Vector") + insert_node(layout, "fn_VectorDistanceNode", "Vector Distance") + layout.separator() + insert_node(layout, "fn_SeparateColorNode", "Separate Color") + insert_node(layout, "fn_CombineColorNode", "Combine Color") + layout.separator() + insert_node(layout, "fn_GetListElementNode", "Get List Element") + insert_node(layout, "fn_ListLengthNode", "List Length") + insert_node(layout, "fn_PackListNode", "Pack List") + layout.separator() + insert_node(layout, "fn_ObjectMeshNode", "Object Mesh") + insert_node(layout, "fn_ObjectTransformsNode", "Object Transforms") + insert_node(layout, "fn_TextLengthNode", "Text Length") + +class InfluencesNodesMenu(bpy.types.Menu): + bl_idname = "BP_MT_influences_nodes_menu" + bl_label = "Influences Nodes Menu" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + + insert_node(layout, "fn_CombineInfluencesNode", "Combine Influences") + layout.separator() + insert_node(layout, "fn_InitialGridEmitterNode", "Initial Grid Emitter") + insert_node(layout, "fn_MeshEmitterNode", "Mesh Emitter") + insert_node(layout, "fn_PointEmitterNode", "Point Emitter") + layout.separator() + insert_node(layout, "fn_AgeReachedEventNode", "Age Reached Event") + insert_node(layout, "fn_MeshCollisionEventNode", "Mesh Collision Event") + insert_node(layout, "fn_CustomEventNode", "Custom Event") + layout.separator() + insert_node(layout, "fn_ForceNode", "Force") + layout.separator() + insert_node(layout, "fn_SizeOverTimeNode", "Size Over Time") + insert_node(layout, "fn_ParticleTrailsNode", "Trails") + insert_node(layout, "fn_AlwaysExecuteNode", "Always Execute") + + +class ActionNodesMenu(bpy.types.Menu): + bl_idname = "BP_MT_action_nodes_menu" + bl_label = "Action Nodes Menu" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + + insert_node(layout, "fn_ParticleConditionNode", "Condition") + + +def insert_node(layout, type, text, settings = {}, icon = "NONE"): + operator = layout.operator("node.add_node", text = text, icon = icon) + operator.type = type + operator.use_transform = True + for name, value in settings.items(): + item = operator.settings.add() + item.name = name + item.value = value + return operator + + +def register(): + bpy.types.NODE_MT_add.append(draw_menu) diff --git a/release/scripts/startup/nodes/node_builder.py b/release/scripts/startup/nodes/node_builder.py new file mode 100644 index 00000000000..67ce6e70ed6 --- /dev/null +++ b/release/scripts/startup/nodes/node_builder.py @@ -0,0 +1,240 @@ +from . declaration import ( + FixedSocketDecl, + ListSocketDecl, + BaseListVariadic, + VectorizedInputDecl, + VectorizedOutputDecl, + InfluencesSocketDecl, + ExecuteOutputDecl, + ExecuteInputListDecl, + ExecuteInputDecl, + + NoDefaultValue, +) + +class NodeBuilder: + def __init__(self, node): + self.node = node + self.input_declarations = [] + self.output_declarations = [] + self._background_color = None + + def _add_input(self, decl): + self.input_declarations.append(decl) + + def _add_output(self, decl): + self.output_declarations.append(decl) + + def initialize_decls(self): + for decl in self.input_declarations: + decl.init() + + for decl in self.output_declarations: + decl.init() + + def build(self): + from . sync import skip_syncing + with skip_syncing(): + self.node.inputs.clear() + self.node.outputs.clear() + + for decl in self.input_declarations: + sockets = decl.build(self.node.inputs) + assert len(sockets) == decl.amount() + decl.init_default(sockets) + + for decl in self.output_declarations: + sockets = decl.build(self.node.outputs) + assert len(sockets) == decl.amount() + decl.init_default(sockets) + + if self._background_color is not None: + self.node.use_custom_color = True + self.node.color = self._background_color + + def get_sockets_decl_map(self): + return SocketDeclMap( + self.node, + self.input_declarations, + self.output_declarations) + + def matches_sockets(self): + if not self._declarations_matches_sockets(self.input_declarations, self.node.inputs): + return False + if not self._declarations_matches_sockets(self.output_declarations, self.node.outputs): + return False + return True + + def _declarations_matches_sockets(self, declarations, all_sockets): + sockets_iter = iter(all_sockets) + for decl in declarations: + amount = decl.amount() + try: sockets = [next(sockets_iter) for _ in range(amount)] + except StopIteration: return False + if not decl.validate(sockets): + return False + if len(tuple(sockets_iter)) > 0: + return False + return True + + # General Node Properties + ################################### + + def background_color(self, color): + assert len(color) == 3 + self._background_color = color + + + # Fixed Data Types + ################################### + + def fixed_input(self, identifier, name, data_type, + *, default=NoDefaultValue, **kwargs): + decl = FixedSocketDecl(self.node, identifier, name, data_type, default, kwargs) + self._add_input(decl) + + def fixed_output(self, identifier, name, data_type, + *, default=NoDefaultValue, **kwargs): + decl = FixedSocketDecl(self.node, identifier, name, data_type, default, kwargs) + self._add_output(decl) + + def fixed_pass_through(self, identifier, name, data_type, *, default=NoDefaultValue): + self.fixed_input(identifier, name, data_type, default=default) + self.fixed_output(identifier, name, data_type, default=default) + + + # Packed List + ################################### + + @staticmethod + def BaseListVariadicProperty(): + return BaseListVariadic.Property() + + @staticmethod + def BaseListVariadicPropertyHasList(prop): + return any(v.state == "LIST" for v in prop) + + + def base_list_variadic_input(self, identifier, prop_name, base_type, default_amount=2): + decl = BaseListVariadic(self.node, identifier, prop_name, base_type, default_amount) + self._add_input(decl) + + + # Dynamic List + ################################### + + @staticmethod + def DynamicListProperty(): + return ListSocketDecl.Property() + + def dynamic_list_input(self, identifier, name, prop_name): + decl = ListSocketDecl(self.node, identifier, name, prop_name, "LIST") + self._add_input(decl) + + def dynamic_list_output(self, identifier, name, prop_name): + decl = ListSocketDecl(self.node, identifier, name, prop_name, "LIST") + self._add_output(decl) + + def dynamic_base_input(self, identifier, name, prop_name): + decl = ListSocketDecl(self.node, identifier, name, prop_name, "BASE") + self._add_input(decl) + + def dynamic_base_output(self, identifier, name, prop_name): + decl = ListSocketDecl(self.node, identifier, name, prop_name, "BASE") + self._add_output(decl) + + + # Vectorized + ################################## + + @staticmethod + def VectorizedProperty(): + return VectorizedInputDecl.Property() + + def vectorized_input(self, identifier, prop_name, base_name, list_name, base_type, + *, default=NoDefaultValue, **kwargs): + decl = VectorizedInputDecl( + self.node, identifier, prop_name, + base_name, list_name, base_type, + default, kwargs) + self._add_input(decl) + + def vectorized_output(self, identifier, input_prop_names, base_name, list_name, base_type, + **kwargs): + decl = VectorizedOutputDecl( + self.node, identifier, input_prop_names, + base_name, list_name, base_type, kwargs) + self._add_output(decl) + + + # BParticles + ################################### + + def influences_input(self, identifier, name): + decl = InfluencesSocketDecl(self.node, identifier, name) + self._add_input(decl) + + def influences_output(self, identifier, name): + decl = InfluencesSocketDecl(self.node, identifier, name) + self._add_output(decl) + + @staticmethod + def ExecuteInputProperty(): + return ExecuteInputListDecl.Property() + + def execute_input(self, identifier, display_name, prop_name): + decl = ExecuteInputListDecl(self.node, identifier, prop_name, display_name) + self._add_input(decl) + + def single_execute_input(self, identifier, name): + decl = ExecuteInputDecl(self.node, identifier, name) + self._add_input(decl) + + def execute_output(self, identifier, name): + decl = ExecuteOutputDecl(self.node, identifier, name) + self._add_output(decl) + + + +class SocketDeclMap: + def __init__(self, node, input_declarations, output_declarations): + self.node = node + self._sockets_by_decl = dict() + self._decl_by_socket = dict() + self._socket_index_in_decl = dict() + + for decl, sockets in iter_sockets_by_decl(node.inputs, input_declarations): + self._sockets_by_decl[decl] = sockets + for i, socket in enumerate(sockets): + self._decl_by_socket[socket] = decl + self._socket_index_in_decl[socket] = i + + for decl, sockets in iter_sockets_by_decl(node.outputs, output_declarations): + self._sockets_by_decl[decl] = sockets + for i, socket in enumerate(sockets): + self._decl_by_socket[socket] = decl + self._socket_index_in_decl[socket] = i + + def get_decl_by_socket(self, socket): + return self._decl_by_socket[socket] + + def get_socket_index_in_decl(self, socket): + return self._socket_index_in_decl[socket] + + def get_sockets_by_decl(self, decl): + return self._sockets_by_decl[decl] + + def iter_decl_with_sockets(self): + yield from self._sockets_by_decl.items() + + def iter_decls(self): + yield from self._sockets_by_decl.keys() + + +def iter_sockets_by_decl(node_sockets, declarations): + node_sockets_iter = iter(node_sockets) + for decl in declarations: + amount = decl.amount() + sockets_of_decl = tuple(next(node_sockets_iter) for _ in range(amount)) + assert decl.validate(sockets_of_decl) + yield decl, sockets_of_decl diff --git a/release/scripts/startup/nodes/node_operators.py b/release/scripts/startup/nodes/node_operators.py new file mode 100644 index 00000000000..05a8b362fee --- /dev/null +++ b/release/scripts/startup/nodes/node_operators.py @@ -0,0 +1,151 @@ +import bpy +from bpy.props import * +from . types import type_infos +from . function_tree import FunctionTree + +def try_find_node(tree_name, node_name): + tree = bpy.data.node_groups.get(tree_name) + if tree is not None: + return tree.nodes.get(node_name) + return None + +class NodeOperatorBase: + tree_name: StringProperty() + node_name: StringProperty() + function_name: StringProperty() + settings_repr: StringProperty() + + def call(self, *args): + node = try_find_node(self.tree_name, self.node_name) + if node is None: + return {'CANCELLED'} + + function = getattr(node, self.function_name) + settings = eval(self.settings_repr) + function(*args, *settings) + return {'FINISHED'} + +class NodeOperator(bpy.types.Operator, NodeOperatorBase): + bl_idname = "fn.node_operator" + bl_label = "Generic Node Operator" + bl_options = {'INTERNAL'} + + def execute(self, context): + return self.call() + +class NodeDataTypeSelector(bpy.types.Operator, NodeOperatorBase): + bl_idname = "fn.node_data_type_selector" + bl_label = "Generic Node Data Type Selector" + bl_options = {'INTERNAL'} + bl_property = "item" + + mode: EnumProperty( + items=[ + ("ALL", "All", ""), + ("BASE", "Base", ""), + ]) + + def get_items(self, context): + if self.mode == "ALL": + return type_infos.get_data_type_items() + elif self.mode == "BASE": + return type_infos.get_base_type_items() + else: + assert False + + item: EnumProperty(items=get_items) + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {'CANCELLED'} + + def execute(self, context): + return self.call(self.item) + +class NodeGroupSelector(bpy.types.Operator, NodeOperatorBase): + bl_idname = "fn.node_group_selector" + bl_label = "Node Group Selector" + bl_options = {'INTERNAL'} + bl_property = "item" + + def get_items(self, context): + tree = bpy.data.node_groups.get(self.tree_name) + possible_trees = tree.find_callable_trees() + + items = [] + for tree in possible_trees: + items.append((tree.name, tree.name, "")) + items.append(("NONE", "None", "")) + return items + + item: EnumProperty(items=get_items) + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {'CANCELLED'} + + def execute(self, context): + if self.item == "NONE": + return self.call(None) + else: + return self.call(bpy.data.node_groups.get(self.item)) + +class MoveViewToNode(bpy.types.Operator): + bl_idname = "fn.move_view_to_node" + bl_label = "Move View to Node" + bl_options = {'INTERNAL'} + + tree_name: StringProperty() + node_name: StringProperty() + + def execute(self, context): + target_node = try_find_node(self.tree_name, self.node_name) + if target_node is None: + return {'CANCELLED'} + + tree = target_node.tree + context.space_data.node_tree = tree + for node in tree.nodes: + node.select = False + + target_node.select = True + tree.nodes.active = target_node + + bpy.ops.node.view_selected('INVOKE_DEFAULT') + return {'FINISHED'} + +def new_function_tree(name, inputs, outputs): + tree = bpy.data.node_groups.new(name, "FunctionTree") + + for i, (data_type, input_name) in enumerate(inputs): + input_node = tree.nodes.new("fn_GroupInputNode") + input_node.sort_index = i + input_node.interface_type = "DATA" + input_node.input_name = input_name + input_node.data_type = data_type + input_node.location = (-200, -i * 130) + + for i, (data_type, output_name) in enumerate(outputs): + output_node = tree.nodes.new("fn_GroupOutputNode") + output_node.sort_index = i + output_node.output_name = output_name + output_node.data_type = data_type + output_node.location = (200, -i * 130) + + tree.sync() + return tree + +class NewParticleSystem(bpy.types.Operator): + bl_idname = "fn.new_particle_system" + bl_label = "New Particle System" + + def execute(self, context): + mesh = bpy.data.meshes.new("Particle Simulation") + ob = bpy.data.objects.new("Particle Simulation", mesh) + modifier = ob.modifiers.new("BParticles", 'BPARTICLES') + + bpy.ops.fn.new_particle_simulation_tree(object_name=ob.name, modifier_name=modifier.name) + + context.collection.objects.link(ob) + + return {'FINISHED'} diff --git a/release/scripts/startup/nodes/problems.py b/release/scripts/startup/nodes/problems.py new file mode 100644 index 00000000000..445649b352d --- /dev/null +++ b/release/scripts/startup/nodes/problems.py @@ -0,0 +1,34 @@ +import bpy +from bpy.props import * +from . ui import NodeSidebarPanel + +warnings = [] + +def report_warning(node, msg): + warnings.append((node, msg)) + +class ProblemsPanel(bpy.types.Panel, NodeSidebarPanel): + bl_idname = "FN_PT_problems" + bl_label = "Problems" + + def draw(self, context): + layout = self.layout + for i, (node, msg) in enumerate(warnings): + row = layout.row(align=True) + row.label(text=msg) + props = row.operator("fn.move_view_to_node", text="Find") + props.tree_name = node.tree.name + props.node_name = node.name + props = row.operator("fn.remove_warning", text="", icon='X') + props.index = i + +class RemoveWarning(bpy.types.Operator): + bl_idname = "fn.remove_warning" + bl_label = "Remove Warning" + bl_options = {'INTERNAL'} + + index: IntProperty() + + def execute(self, context): + del warnings[self.index] + return {'FINISHED'} diff --git a/release/scripts/startup/nodes/search.py b/release/scripts/startup/nodes/search.py new file mode 100644 index 00000000000..d977aea820e --- /dev/null +++ b/release/scripts/startup/nodes/search.py @@ -0,0 +1,94 @@ +import os +import bpy +from bpy.props import * +from pathlib import Path +from . base import BaseNode +from . utils.enum_items_cache import cache_enum_items +from functools import lru_cache + +@lru_cache() +def get_node_group_names_in_file(path: str): + with bpy.data.libraries.load(path) as (data_from, data_to): + return list(data_from.node_groups) + +class NodeSearch(bpy.types.Operator): + bl_idname = "fn.node_search" + bl_label = "Node Search" + bl_options = {'REGISTER', 'UNDO'} + bl_property = "item" + + def get_search_items(self, context): + items = [] + tree = context.space_data.edit_tree + for node_cls in BaseNode.iter_final_subclasses(): + for search_term, settings in node_cls.get_search_terms(): + item = encode_search_item(("BUILTIN", node_cls.bl_idname, settings), search_term) + items.append(item) + + current_tree = context.space_data.node_tree + for tree in current_tree.find_callable_trees(): + item = encode_search_item(("EXISTING_GROUP", tree.name), tree.name + " (G)") + items.append(item) + + nodelibdir = context.preferences.filepaths.nodelib_directory + if len(nodelibdir) > 0 and os.path.exists(nodelibdir): + local_group_names = set(tree.name for tree in bpy.data.node_groups) + for path in Path(nodelibdir).glob("**/*.blend"): + if not path.is_file(): + continue + for group_name in get_node_group_names_in_file(str(path)): + if group_name not in local_group_names: + item = encode_search_item(("LIB_GROUP", str(path), group_name), group_name + " (G)") + items.append(item) + + sorted_items = list(sorted(items, key=lambda item: item[1])) + return sorted_items + + item: EnumProperty(items=cache_enum_items(get_search_items)) + + @classmethod + def poll(cls, context): + try: return context.space_data.node_tree.bl_idname == "FunctionTree" + except: return False + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {'CANCELLED'} + + def execute(self, context): + tree = context.space_data.node_tree + for node in tree.nodes: + node.select = False + + item_data = decode_search_item(self.item) + item_type = item_data[0] + + if item_type == "BUILTIN": + idname, settings = item_data[1:] + bpy.ops.node.add_node('INVOKE_DEFAULT', type=idname) + new_node = context.active_node + for key, value in settings.items(): + setattr(new_node, key, value) + elif item_type == "EXISTING_GROUP": + group_name = item_data[1] + bpy.ops.node.add_node('INVOKE_DEFAULT', type="fn_GroupNode") + new_node = context.active_node + new_node.node_group = bpy.data.node_groups[group_name] + elif item_type == "LIB_GROUP": + path, group_name = item_data[1:] + bpy.ops.node.add_node('INVOKE_DEFAULT', type="fn_GroupNode") + new_node = context.active_node + with bpy.data.libraries.load(path, link=True) as (data_from, data_to): + data_to.node_groups = [group_name] + new_node.node_group = bpy.data.node_groups[group_name] + + bpy.ops.node.translate_attach("INVOKE_DEFAULT") + return {'FINISHED'} + + +def encode_search_item(data, search_term): + identifier = repr(data) + return (identifier, search_term, "") + +def decode_search_item(identifier): + return eval(identifier) diff --git a/release/scripts/startup/nodes/sockets.py b/release/scripts/startup/nodes/sockets.py new file mode 100644 index 00000000000..5072259dd23 --- /dev/null +++ b/release/scripts/startup/nodes/sockets.py @@ -0,0 +1,228 @@ +import bpy +from . base import DataSocket, BaseSocket +from bpy.props import * + +class OperatorSocket(bpy.types.NodeSocket, BaseSocket): + bl_idname = "fn_OperatorSocket" + bl_label = "Operator Socket" + + def draw_color(self, context, node): + return (0, 0, 0, 0) + +class FloatSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_FloatSocket" + bl_label = "Float Socket" + data_type = "Float" + color = (0, 0.3, 0.5, 1) + + value: FloatProperty( + name="Value", + default=0.0, + ) + + def draw_property(self, layout, node, text): + layout.prop(self, "value", text=text) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +class IntegerSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_IntegerSocket" + bl_label = "Integer Socket" + data_type = "Integer" + color = (0.3, 0.7, 0.5, 1) + + value: IntProperty( + name="Value", + default=0, + ) + + def draw_property(self, layout, node, text): + layout.prop(self, "value", text=text) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +class VectorSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_VectorSocket" + bl_label = "Vector Socket" + data_type = "Vector" + color = (0, 0, 0.5, 1) + + value: FloatVectorProperty( + name="Value", + size=3, + default=(0.0, 0.0, 0.0), + ) + + def draw_property(self, layout, node, text): + layout.column().prop(self, "value", text=text) + + def get_state(self): + return tuple(self.value) + + def restore_state(self, state): + self.value = state + +class BooleanSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_BooleanSocket" + bl_label = "Boolean Socket" + data_type = "Boolean" + color = (0.3, 0.3, 0.3, 1) + + value: BoolProperty( + name="Value", + default=False, + ) + + def draw_property(self, layout, node, text): + layout.prop(self, "value", text=text) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +class ObjectSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_ObjectSocket" + bl_label = "Object Socket" + data_type = "Object" + color = (0, 0, 0, 1) + + value: PointerProperty( + name="Value", + type=bpy.types.Object, + ) + + display_name: BoolProperty(default=True) + + def draw_property(self, layout, node, text): + if not self.display_name: + text = "" + layout.prop(self, "value", text=text) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +class ImageSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_ImageSocket" + bl_label = "Image Socket" + data_type = "Image" + color = (0.6, 0.6, 0.6, 1) + + value: PointerProperty( + name="Value", + type=bpy.types.Image, + ) + + display_name: BoolProperty() + + def draw_property(self, layout, node, text): + if not self.display_name: + text = "" + layout.prop(self, "value", text=text) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +class ColorSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_ColorSocket" + bl_label = "Color Socket" + data_type = "Color" + color = (0.8, 0.8, 0.2, 1) + + value: FloatVectorProperty( + name="Value", + size=4, + default=(0.8, 0.8, 0.8, 1.0), + subtype='COLOR', + soft_min=0.0, + soft_max=0.0, + ) + + def draw_property(self, layout, node, text): + layout.prop(self, "value", text=text) + + def get_state(self): + return tuple(self.value) + + def restore_state(self, state): + self.value = state + +class TextSocket(bpy.types.NodeSocket, DataSocket): + bl_idname = "fn_TextSocket" + bl_label = "Text Socket" + data_type = "Text" + color = (0.8, 0.8, 0.8, 1) + + value: StringProperty( + name="Value", + default="", + ) + + display_name: BoolProperty(default=True) + display_icon: StringProperty(default="NONE") + + def draw_property(self, layout, node, text): + if not self.display_name: + text = "" + layout.prop(self, "value", text=text, icon=self.display_icon) + + def get_state(self): + return self.value + + def restore_state(self, state): + self.value = state + +def create_simple_data_socket(idname, data_type, color): + return type(idname, (bpy.types.NodeSocket, DataSocket), + { + "bl_idname" : idname, + "bl_label" : idname, + "data_type" : data_type, + "color" : color, + }) + +FloatListSocket = create_simple_data_socket( + "fn_FloatListSocket", "Float List", (0, 0.3, 0.5, 0.5)) +VectorListSocket = create_simple_data_socket( + "fn_VectorListSocket", "Vector List", (0, 0, 0.5, 0.5)) +IntegerListSocket = create_simple_data_socket( + "fn_IntegerListSocket", "Integer List", (0.3, 0.7, 0.5, 0.5)) +BooleanListSocket = create_simple_data_socket( + "fn_BooleanListSocket", "Boolean List", (0.3, 0.3, 0.3, 0.5)) +ObjectListSocket = create_simple_data_socket( + "fn_ObjectListSocket", "Object List", (0, 0, 0, 0.5)) +ImageListSocket = create_simple_data_socket( + "fn_ImageListSocket", "Image List", (0.6, 0.6, 0.6, 0.5)) +ColorListSocket = create_simple_data_socket( + "fn_ColorListSocket", "Color List", (0.8, 0.8, 0.2, 0.5)) +TextListSocket = create_simple_data_socket( + "fn_TextListSocket", "Text List", (0.8, 0.8, 0.8, 0.5)) +SurfaceHookSocket = create_simple_data_socket( + "fn_SurfaceHookSocket", "Surface Hook", (0.2, 0.8, 0.2, 1.0)) +SurfaceHookListSocket = create_simple_data_socket( + "fn_SurfaceHookListSocket", "Surface Hook List", (0.2, 0.8, 0.2, 0.5)) + +class ExecuteSocket(bpy.types.NodeSocket, BaseSocket): + bl_idname = "fn_ExecuteSocket" + bl_label = "Control Flow Socket" + color = (0.8, 0.2, 0.2, 1) + +class InfluencesSocket(bpy.types.NodeSocket, BaseSocket): + bl_idname = "fn_InfluencesSocket" + bl_label = "Influences Socket" + color = (0.8, 0.8, 0.2, 1) diff --git a/release/scripts/startup/nodes/sync.py b/release/scripts/startup/nodes/sync.py new file mode 100644 index 00000000000..84b10566a5d --- /dev/null +++ b/release/scripts/startup/nodes/sync.py @@ -0,0 +1,183 @@ +import bpy +from pprint import pprint +from contextlib import contextmanager + +from . base import BaseNode +from . tree_data import TreeData +from . graph import DirectedGraphBuilder +from . function_tree import FunctionTree +from . utils.generic import getattr_recursive, setattr_recursive + +_is_syncing = False + +def sync_trees_and_dependent_trees(trees): + global _is_syncing + if _is_syncing: + return + if _skip_syncing: + return + + _is_syncing = True + + try: + for tree in iter_trees_to_sync_in_order(trees): + sync_tree(tree) + finally: + _is_syncing = False + +def sync_tree(tree): + rebuild_currently_outdated_nodes(tree) + + tree_data = TreeData(tree) + + tree_changed = run_socket_operators(tree_data) + if tree_changed: tree_data = TreeData(tree) + + tree_changed = do_inferencing_and_update_nodes(tree_data) + if tree_changed: tree_data = TreeData(tree) + + tree_changed = remove_invalid_links(tree_data) + +# Sync skipping +###################################### + +_skip_syncing = False + +@contextmanager +def skip_syncing(): + global _skip_syncing + last_state = _skip_syncing + _skip_syncing = True + + try: + yield + finally: + _skip_syncing = last_state + + +# Tree sync ordering +############################################ + +def iter_trees_to_sync_in_order(trees): + stored_tree_ids = {id(tree) for tree in bpy.data.node_groups} + if any(id(tree) not in stored_tree_ids for tree in trees): + # can happen after undo or on load + return + + dependency_graph = FunctionTree.BuildInvertedCallGraph() + all_trees_to_sync = dependency_graph.reachable(trees) + trees_in_sync_order = dependency_graph.toposort_partial(all_trees_to_sync) + yield from trees_in_sync_order + + +# Rebuild already outdated nodes +############################################ + +def rebuild_currently_outdated_nodes(tree): + outdated_nodes = list(iter_nodes_with_outdated_sockets(tree)) + rebuild_nodes_and_try_keep_state(outdated_nodes) + +def iter_nodes_with_outdated_sockets(tree): + for node in tree.nodes: + if isinstance(node, BaseNode): + if not node_matches_current_declaration(node): + yield node + +def node_matches_current_declaration(node): + from . node_builder import NodeBuilder + builder = node.get_node_builder() + return builder.matches_sockets() + + +# Socket Operators +############################################ + +def run_socket_operators(tree_data): + from . sockets import OperatorSocket + + tree_changed = False + while True: + for link in tree_data.iter_blinks(): + if isinstance(link.to_socket, OperatorSocket): + own_node = link.to_node + own_socket = link.to_socket + linked_socket = link.from_socket + connected_sockets = list(tree_data.iter_connected_origins(own_socket)) + elif isinstance(link.from_socket, OperatorSocket): + own_node = link.from_node + own_socket = link.from_socket + linked_socket = link.to_socket + connected_sockets = list(tree_data.iter_connected_targets(own_socket)) + else: + continue + + tree_data.tree.links.remove(link) + decl = own_socket.get_decl(own_node) + decl.operator_socket_call(own_socket, linked_socket, connected_sockets) + tree_changed = True + else: + return tree_changed + + +# Inferencing +#################################### + +def do_inferencing_and_update_nodes(tree_data): + from . inferencing import get_inferencing_decisions + + decisions = get_inferencing_decisions(tree_data) + + nodes_to_rebuild = set() + + for decision_id, value in decisions.items(): + if getattr_recursive(decision_id.node, decision_id.prop_name) != value: + setattr_recursive(decision_id.node, decision_id.prop_name, value) + nodes_to_rebuild.add(decision_id.node) + + rebuild_nodes_and_try_keep_state(nodes_to_rebuild) + + tree_changed = len(nodes_to_rebuild) > 0 + return tree_changed + + +# Remove Invalid Links +#################################### + +def remove_invalid_links(tree_data): + links_to_remove = set() + for from_socket, to_socket in tree_data.iter_connections(): + if not is_link_valid(tree_data, from_socket, to_socket): + links_to_remove.update(tree_data.iter_incident_links(to_socket)) + + tree_changed = len(links_to_remove) > 0 + + tree = tree_data.tree + for link in links_to_remove: + tree.links.remove(link) + + return tree_changed + +def is_link_valid(tree_data, from_socket, to_socket): + from . types import type_infos + from . base import DataSocket + + is_data_src = isinstance(from_socket, DataSocket) + is_data_dst = isinstance(to_socket, DataSocket) + + if is_data_src != is_data_dst: + return False + + if is_data_src and is_data_dst: + from_type = from_socket.data_type + to_type = to_socket.data_type + return type_infos.is_link_allowed(from_type, to_type) + + return True + + +# Utils +###################################### + +def rebuild_nodes_and_try_keep_state(nodes): + for node in nodes: + node.rebuild() diff --git a/release/scripts/startup/nodes/tree_data.py b/release/scripts/startup/nodes/tree_data.py new file mode 100644 index 00000000000..079cbd3d3a3 --- /dev/null +++ b/release/scripts/startup/nodes/tree_data.py @@ -0,0 +1,133 @@ +from collections import defaultdict +from . base import BaseNode + +class TreeData: + def __init__(self, tree): + self.tree = tree + self.links_mapping = find_direct_links_mapping(tree) + self.node_by_socket = get_node_by_socket_mapping(tree) + self.connections_mapping = find_links_following_reroutes(self.links_mapping, self.node_by_socket) + self.link_by_sockets = get_link_by_sockets_mapping(tree) + + def iter_nodes(self): + for node in self.tree.nodes: + if isinstance(node, BaseNode): + yield node + + def iter_blinks(self): + yield from self.tree.links + + def iter_connections(self): + for socket, others in self.connections_mapping.items(): + if socket.is_output: + continue + for other in others: + yield other, socket + + def get_node(self, socket): + return self.node_by_socket[socket] + + def iter_connected_origins(self, socket): + node = self.get_node(socket) + if is_reroute(node): + socket = node.inputs[0] + for other_socket in self.links_mapping[socket]: + yield from self.iter_connected_origins(other_socket) + else: + if socket.is_output: + yield socket + else: + yield from self.iter_connected_sockets(socket) + + def iter_connected_targets(self, socket): + node = self.get_node(socket) + if is_reroute(node): + socket = node.outputs[0] + for other_socket in self.links_mapping[socket]: + yield from self.iter_connected_targets(other_socket) + else: + if socket.is_output: + yield from self.iter_connected_sockets(socket) + else: + yield socket + + def iter_connected_sockets(self, socket): + yield from self.connections_mapping[socket] + + def iter_connected_sockets_with_nodes(self, socket): + for other_socket in self.iter_connected_sockets(socket): + other_node = self.get_node(other_socket) + yield other_node, other_socket + + def try_get_origin_with_node(self, socket): + linked_sockets = self.connections_mapping[socket] + amount = len(linked_sockets) + if amount == 0: + return None, None + elif amount == 1: + origin_socket = next(iter(linked_sockets)) + origin_node = self.get_node(origin_socket) + return origin_node, origin_socket + else: + assert False + + def iter_incident_links(self, socket): + if socket.is_output: + for other_socket in self.links_mapping[socket]: + yield self.link_by_sockets[(socket, other_socket)] + else: + for other_socket in self.links_mapping[socket]: + yield self.link_by_sockets[(other_socket, socket)] + +def find_direct_links_mapping(tree): + direct_links = defaultdict(set) + for link in tree.links: + direct_links[link.from_socket].add(link.to_socket) + direct_links[link.to_socket].add(link.from_socket) + return dict(direct_links) + +def get_node_by_socket_mapping(tree): + node_by_socket = dict() + for node in tree.nodes: + for socket in node.inputs: + node_by_socket[socket] = node + for socket in node.outputs: + node_by_socket[socket] = node + return node_by_socket + +def get_link_by_sockets_mapping(tree): + link_by_sockets = dict() + for link in tree.links: + link_by_sockets[(link.from_socket, link.to_socket)] = link + return link_by_sockets + +def find_links_following_reroutes(direct_links, node_by_socket): + links = defaultdict(set) + for socket, direct_linked_sockets in direct_links.items(): + node = node_by_socket[socket] + if socket.is_output: + # handle every link only once + continue + if is_reroute(node): + continue + + for other_socket in direct_linked_sockets: + for origin_socket in iter_non_reroute_outputs(direct_links, node_by_socket, other_socket): + links[socket].add(origin_socket) + links[origin_socket].add(socket) + return links + +def iter_non_reroute_outputs(direct_links, node_by_socket, socket): + assert socket.is_output + + node = node_by_socket[socket] + if is_reroute(node): + input_socket = node.inputs[0] + if input_socket in direct_links: + for origin_socket in direct_links[input_socket]: + yield from iter_non_reroute_outputs(direct_links, node_by_socket, origin_socket) + else: + yield socket + +def is_reroute(node): + return node.bl_idname == "NodeReroute" diff --git a/release/scripts/startup/nodes/tree_panel.py b/release/scripts/startup/nodes/tree_panel.py new file mode 100644 index 00000000000..09f7b26b907 --- /dev/null +++ b/release/scripts/startup/nodes/tree_panel.py @@ -0,0 +1,17 @@ +import bpy +from . ui import NodeSidebarPanel +from . base import BaseTree + +class TreePanel(bpy.types.Panel, NodeSidebarPanel): + bl_idname = "FN_PT_tree_panel" + bl_label = "Tree" + + @classmethod + def poll(self, context): + try: return isinstance(context.space_data.edit_tree, BaseTree) + except: return False + + def draw(self, context): + layout = self.layout + + tree = context.space_data.edit_tree diff --git a/release/scripts/startup/nodes/types.py b/release/scripts/startup/nodes/types.py new file mode 100644 index 00000000000..82cc2562793 --- /dev/null +++ b/release/scripts/startup/nodes/types.py @@ -0,0 +1,16 @@ +from . import sockets as s +from . types_base import DataTypesInfo + +type_infos = DataTypesInfo() + +type_infos.insert_data_type(s.FloatSocket, s.FloatListSocket) +type_infos.insert_data_type(s.VectorSocket, s.VectorListSocket) +type_infos.insert_data_type(s.IntegerSocket, s.IntegerListSocket) +type_infos.insert_data_type(s.BooleanSocket, s.BooleanListSocket) +type_infos.insert_data_type(s.ObjectSocket, s.ObjectListSocket) +type_infos.insert_data_type(s.ImageSocket, s.ImageListSocket) +type_infos.insert_data_type(s.ColorSocket, s.ColorListSocket) +type_infos.insert_data_type(s.TextSocket, s.TextListSocket) +type_infos.insert_data_type(s.SurfaceHookSocket, s.SurfaceHookListSocket) + +type_infos.insert_conversion_group(["Boolean", "Integer", "Float"]) diff --git a/release/scripts/startup/nodes/types_base.py b/release/scripts/startup/nodes/types_base.py new file mode 100644 index 00000000000..648c320a300 --- /dev/null +++ b/release/scripts/startup/nodes/types_base.py @@ -0,0 +1,164 @@ +import itertools +from collections import namedtuple + +''' +Type Rules +========== + +A -> B means, Type A can be converted to type B implicitely. +A -!> B means, Type A cannot be converted to type B implicitely. +A_List is the type that contains a list of elements of type A + +Iff T1 -> T2, then T1_List -> T2_List. +T -> T_List. +T_List -!> T. + +Types always come in pairs: T and T_List. +There are no lists of lists. + +A type can be in zero or one conversion group. +Every type in this group can be converted to any other implicitely. +The types within a group are ordered by their "rank". +When two types with different rank are used in one expression, +the type with lower rank is converted to the other. +''' + +# Type Info Container +##################################### + +ImplicitConversion = namedtuple("ImplicitConversion", ("from_type", "to_type")) + +class DataTypesInfo: + def __init__(self): + self.data_types = set() + self.cls_by_data_type = dict() + self.list_by_base = dict() + self.base_by_list = dict() + self.unidirectional_conversions = set() + self.conversion_groups = dict() + self.all_implicit_conversions = set() + + + # Insert New Information + ############################# + + def insert_data_type(self, base_socket_cls, list_socket_cls): + base_type = base_socket_cls.data_type + list_type = list_socket_cls.data_type + + assert base_type not in self.data_types + assert list_type not in self.data_types + + self.data_types.add(base_type) + self.data_types.add(list_type) + self.list_by_base[base_type] = list_type + self.base_by_list[list_type] = base_type + self.cls_by_data_type[base_type] = base_socket_cls + self.cls_by_data_type[list_type] = list_socket_cls + + self.all_implicit_conversions.add(ImplicitConversion(base_type, list_type)) + + def insert_conversion_group(self, types_by_rank): + '''lowest rank comes first''' + + for data_type in types_by_rank: + assert self.is_data_type(data_type) + assert self.is_base(data_type) + assert data_type not in self.conversion_groups + + group = tuple(types_by_rank) + for data_type in types_by_rank: + self.conversion_groups[data_type] = group + + for from_base_type, to_base_type in itertools.combinations(group, 2): + from_list_type = self.to_list(from_base_type) + to_list_type = self.to_list(to_base_type) + self.all_implicit_conversions.add(ImplicitConversion(from_base_type, to_base_type)) + self.all_implicit_conversions.add(ImplicitConversion(to_base_type, from_base_type)) + self.all_implicit_conversions.add(ImplicitConversion(from_list_type, to_list_type)) + self.all_implicit_conversions.add(ImplicitConversion(to_list_type, from_list_type)) + + def insert_unidirectional_conversion(self, from_type, to_type): + assert self.is_data_type(from_type) + assert self.is_data_type(to_type) + assert self.is_base(from_type) + assert self.is_base(to_type) + + base_conversion = ImplicitConversion(from_type, to_type) + assert base_conversion not in self.implicit_conversions + self.implicit_conversions.add(base_conversion) + self.all_implicit_conversions.add(base_conversion) + + list_conversion = ImplicitConversion( + self.to_list(from_type), self.to_list(to_type)) + assert list_conversion not in self.implicit_conversions + self.implicit_conversions.add(list_conversion) + self.all_implicit_conversions.add(list_conversion) + + + # Query Information + ########################## + + def is_data_type(self, data_type): + return data_type in self.data_types + + def is_base(self, data_type): + return data_type in self.list_by_base + + def is_list(self, data_type): + return data_type in self.base_by_list + + def to_list(self, data_type): + assert self.is_base(data_type) + return self.list_by_base[data_type] + + def to_base(self, data_type): + assert self.is_list(data_type) + return self.base_by_list[data_type] + + def get_data_type_items(self): + items = [] + for data_type in self.data_types: + items.append((data_type, data_type, "")) + return items + + def get_base_type_items(self): + items = [] + for data_type in self.iter_base_types(): + items.append((data_type, data_type, "")) + return items + + def get_data_type_items_cb(self): + def callback(_1, _2): + return self.get_data_type_items() + return callback + + def get_socket_color(self, data_type): + builder = self.to_builder(data_type) + return builder.get_color() + + def is_link_allowed(self, from_type, to_type): + assert self.is_data_type(from_type) + assert self.is_data_type(to_type) + + if from_type == to_type: + return True + else: + return self.is_implicitly_convertable(from_type, to_type) + + def is_implicitly_convertable(self, from_type, to_type): + return ImplicitConversion(from_type, to_type) in self.all_implicit_conversions + + def iter_list_types(self): + yield from self.base_by_list.keys() + + def iter_base_types(self): + yield from self.list_by_base.keys() + + # Build + ########################## + + def build(self, data_type, node_sockets, name, identifier): + idname = self.cls_by_data_type[data_type].bl_idname + socket = node_sockets.new(idname, name, identifier=identifier) + return socket diff --git a/release/scripts/startup/nodes/ui.py b/release/scripts/startup/nodes/ui.py new file mode 100644 index 00000000000..6b59cc7fff7 --- /dev/null +++ b/release/scripts/startup/nodes/ui.py @@ -0,0 +1,6 @@ +import bpy + +class NodeSidebarPanel: + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_category = "Node" diff --git a/release/scripts/startup/nodes/utils/enum_items_cache.py b/release/scripts/startup/nodes/utils/enum_items_cache.py new file mode 100644 index 00000000000..1b1ca0c8937 --- /dev/null +++ b/release/scripts/startup/nodes/utils/enum_items_cache.py @@ -0,0 +1,20 @@ +import functools +from collections import defaultdict + +cached_item_tuples_by_hash = defaultdict(list) + +def cache_enum_items(items_cb): + + @functools.wraps(items_cb) + def wrapper(self, context): + item_tuples = tuple(items_cb(self, context)) + item_tuples_hash = hash(item_tuples) + + for cached_item_tuple in cached_item_tuples_by_hash[item_tuples_hash]: + if cached_item_tuple == item_tuples: + return cached_item_tuple + else: + cached_item_tuples_by_hash[item_tuples_hash].append(item_tuples) + return item_tuples + + return wrapper diff --git a/release/scripts/startup/nodes/utils/generic.py b/release/scripts/startup/nodes/utils/generic.py new file mode 100644 index 00000000000..166fc0eaaf1 --- /dev/null +++ b/release/scripts/startup/nodes/utils/generic.py @@ -0,0 +1,21 @@ +import string +import random + +def iter_subclasses_recursive(cls): + for sub in cls.__subclasses__(): + yield sub + yield from iter_subclasses_recursive(sub) + +def getattr_recursive(obj, name: str): + if "." not in name and "[" not in name: + return getattr(obj, name) + else: + # TODO: implement without eval + return eval("obj." + name, globals(), locals()) + +def setattr_recursive(obj, name: str, value): + if "." not in name and "[" not in name: + setattr(obj, name, value) + else: + # TODO: implement without exec + exec("obj." + name + " = value", globals(), locals()) diff --git a/release/scripts/startup/nodes/utils/graph.py b/release/scripts/startup/nodes/utils/graph.py new file mode 100644 index 00000000000..0a7ebcb6e1b --- /dev/null +++ b/release/scripts/startup/nodes/utils/graph.py @@ -0,0 +1,19 @@ +def iter_connected_components(nodes: set, links: dict): + nodes = set(nodes) + while len(nodes) > 0: + start_node = next(iter(nodes)) + component = depth_first_search(start_node, links) + yield component + nodes -= component + +def depth_first_search(start_node, links): + result = set() + found = set() + found.add(start_node) + while len(found) > 0: + node = found.pop() + result.add(node) + for linked_node in links[node]: + if linked_node not in result: + found.add(linked_node) + return result diff --git a/release/scripts/startup/nodes/utils/pie_menu_helper.py b/release/scripts/startup/nodes/utils/pie_menu_helper.py new file mode 100644 index 00000000000..b0ac542b189 --- /dev/null +++ b/release/scripts/startup/nodes/utils/pie_menu_helper.py @@ -0,0 +1,38 @@ +class PieMenuHelper: + def draw(self, context): + pie = self.layout.menu_pie() + self.draw_left(pie) + self.draw_right(pie) + self.draw_bottom(pie) + self.draw_top(pie) + self.draw_top_left(pie) + self.draw_top_right(pie) + self.draw_bottom_left(pie) + self.draw_bottom_right(pie) + + def draw_left(self, layout): + self.empty(layout) + + def draw_right(self, layout): + self.empty(layout) + + def draw_bottom(self, layout): + self.empty(layout) + + def draw_top(self, layout): + self.empty(layout) + + def draw_top_left(self, layout): + self.empty(layout) + + def draw_top_right(self, layout): + self.empty(layout) + + def draw_bottom_left(self, layout): + self.empty(layout) + + def draw_bottom_right(self, layout): + self.empty(layout) + + def empty(self, layout, text=""): + layout.row().label(text=text)
\ No newline at end of file diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 203b6da272f..cee48b4edce 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -113,6 +113,8 @@ add_subdirectory(nodes) add_subdirectory(modifiers) add_subdirectory(gpencil_modifiers) add_subdirectory(shader_fx) +add_subdirectory(functions) +add_subdirectory(simulations) add_subdirectory(io) add_subdirectory(makesdna) add_subdirectory(makesrna) diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index d389b557503..d8a1989c7c2 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -257,6 +257,8 @@ struct FCurve *iter_step_fcurve(struct FCurve *fcu_iter, const char rna_path[]); struct FCurve *id_data_find_fcurve( ID *id, void *data, struct StructRNA *type, const char *prop_name, int index, bool *r_driven); +void *get_driver_variable_function(struct DriverVar *dvar); + /* Get list of LinkData's containing pointers to the F-Curves which control the types of data * indicated * e.g. numMatches = list_find_data_fcurves(matches, &act->curves, "pose.bones[", "MyFancyBone"); diff --git a/source/blender/blenkernel/BKE_id_data_cache.h b/source/blender/blenkernel/BKE_id_data_cache.h new file mode 100644 index 00000000000..12966f8ada3 --- /dev/null +++ b/source/blender/blenkernel/BKE_id_data_cache.h @@ -0,0 +1,34 @@ +#ifndef __BKE_ID_DATA_CACHE_H__ +#define __BKE_ID_DATA_CACHE_H__ + +#include <mutex> + +#include "BLI_kdopbvh.h" +#include "BLI_kdtree.h" +#include "BLI_map.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BKE_bvhutils.h" + +namespace BKE { + +using BLI::Map; + +class IDDataCache { + private: + mutable Map<Object *, BVHTreeFromMesh *> m_bvh_trees; + mutable std::mutex m_bvt_trees_mutex; + + public: + IDDataCache() = default; + ~IDDataCache(); + + BVHTreeFromMesh *get_bvh_tree(Object *object) const; +}; + +} // namespace BKE + +#endif /* __BKE_ID_DATA_CACHE_H__ */ diff --git a/source/blender/blenkernel/BKE_id_handle.h b/source/blender/blenkernel/BKE_id_handle.h new file mode 100644 index 00000000000..ce45ccb1eb3 --- /dev/null +++ b/source/blender/blenkernel/BKE_id_handle.h @@ -0,0 +1,108 @@ +#ifndef __BKE_ID_HANDLE_H__ +#define __BKE_ID_HANDLE_H__ + +#include "BLI_utildefines.h" + +#include "BLI_map.h" + +extern "C" { +struct ID; +struct Object; +struct Image; +} + +namespace BKE { + +using BLI::Map; + +/** + * This is a weak reference to an ID data-block. It does not contain a pointer to the actual data. + * It can happen that the IDHandle references data, that does not exist anymore. The handle does + * not know that. + */ +class IDHandle { + private: + uint32_t m_identifier; + + public: + IDHandle() : m_identifier((uint32_t)-1) + { + } + + IDHandle(struct ID *id); + + friend bool operator==(IDHandle a, IDHandle b) + { + return a.m_identifier == b.m_identifier; + } + + friend bool operator!=(IDHandle a, IDHandle b) + { + return !(a == b); + } + + uint32_t internal_identifier() const + { + return m_identifier; + } +}; + +class ObjectIDHandle : public IDHandle { + public: + ObjectIDHandle() : IDHandle() + { + } + + ObjectIDHandle(struct Object *object); +}; + +class ImageIDHandle : public IDHandle { + public: + ImageIDHandle() : IDHandle() + { + } + + ImageIDHandle(struct Image *image); +}; + +class IDHandleLookup { + private: + Map<IDHandle, ID *> m_handle_to_id_map; + + public: + void add(ID &id) + { + IDHandle handle(&id); + m_handle_to_id_map.add(handle, &id); + } + + ID *lookup(IDHandle handle) const + { + return m_handle_to_id_map.lookup_default(handle, nullptr); + } + + struct Object *lookup(ObjectIDHandle handle) const + { + return reinterpret_cast<struct Object *>(this->lookup((IDHandle)handle)); + } + + struct Image *lookup(ImageIDHandle handle) const + { + return reinterpret_cast<struct Image *>(this->lookup((IDHandle)handle)); + } + + static const IDHandleLookup &Empty(); +}; + +} // namespace BKE + +namespace BLI { +template<> struct DefaultHash<BKE::IDHandle> { + uint32_t operator()(const BKE::IDHandle &value) const + { + return value.internal_identifier(); + } +}; +} // namespace BLI + +#endif /* __BKE_ID_HANDLE_H__ */
\ No newline at end of file diff --git a/source/blender/blenkernel/BKE_surface_hook.h b/source/blender/blenkernel/BKE_surface_hook.h new file mode 100644 index 00000000000..d1f7e19c674 --- /dev/null +++ b/source/blender/blenkernel/BKE_surface_hook.h @@ -0,0 +1,97 @@ +#ifndef __BKE_SURFACE_HOOK_H__ +#define __BKE_SURFACE_HOOK_H__ + +#include "BLI_float3.h" +#include "BLI_utildefines.h" + +#include "BKE_id_handle.h" + +namespace BKE { + +using BLI::float3; + +namespace SurfaceHookType { +enum Enum { + None, + MeshObject, +}; +} + +/** + * References a point on a surface. If the surface moves, the point moves with it. + */ +class SurfaceHook { + private: + SurfaceHookType::Enum m_type; + + /** + * Used to identify the object if m_type is MeshObject. + */ + ObjectIDHandle m_object_handle; + + /* Index of the triangle that contains the referenced location. */ + uint32_t m_triangle_index; + + /* Barycentric coordinates of the referenced location inside the triangle. */ + float3 m_bary_coords; + + public: + SurfaceHook() : m_type(SurfaceHookType::None) + { + } + + SurfaceHook(ObjectIDHandle object_handle, uint32_t triangle_index, float3 bary_coords) + : m_type(SurfaceHookType::MeshObject), + m_object_handle(object_handle), + m_triangle_index(triangle_index), + m_bary_coords(bary_coords) + { + } + + SurfaceHookType::Enum type() const + { + return m_type; + } + + bool is_valid() const + { + return m_type != SurfaceHookType::None; + } + + ObjectIDHandle object_handle() const + { + BLI_assert(m_type == SurfaceHookType::MeshObject); + return m_object_handle; + } + + uint32_t triangle_index() const + { + BLI_assert(m_type == SurfaceHookType::MeshObject); + return m_triangle_index; + } + + float3 bary_coords() const + { + BLI_assert(m_type == SurfaceHookType::MeshObject); + return m_bary_coords; + } + + static bool on_same_surface(const SurfaceHook &a, const SurfaceHook &b) + { + if (a.type() != b.type()) { + return false; + } + switch (a.type()) { + case BKE::SurfaceHookType::None: + return true; + case BKE::SurfaceHookType::MeshObject: + return a.object_handle() == b.object_handle(); + } + BLI_assert(false); + return false; + } +}; + +} // namespace BKE + +#endif /* __BKE_SURFACE_HOOK_H__ */ diff --git a/source/blender/blenkernel/BKE_virtual_node_tree.h b/source/blender/blenkernel/BKE_virtual_node_tree.h new file mode 100644 index 00000000000..98df55de980 --- /dev/null +++ b/source/blender/blenkernel/BKE_virtual_node_tree.h @@ -0,0 +1,367 @@ +#ifndef __BKE_VIRTUAL_NODE_TREE_H__ +#define __BKE_VIRTUAL_NODE_TREE_H__ + +#include "BLI_array_cxx.h" +#include "BLI_linear_allocated_vector.h" +#include "BLI_resource_collector.h" +#include "BLI_string_map.h" +#include "BLI_string_multi_map.h" +#include "BLI_string_ref.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" + +#include "DNA_node_types.h" + +#include "RNA_access.h" + +namespace BKE { + +using BLI::Array; +using BLI::ArrayRef; +using BLI::LinearAllocatedVector; +using BLI::ResourceCollector; +using BLI::StringMap; +using BLI::StringMultiMap; +using BLI::StringRef; +using BLI::StringRefNull; +using BLI::Vector; + +class VSocket; +class VInputSocket; +class VOutputSocket; +class VNode; +class VirtualNodeTree; + +/* Virtual Node Tree declarations + ******************************************/ + +class VSocket : BLI::NonCopyable, BLI::NonMovable { + protected: + LinearAllocatedVector<VSocket *> m_linked_sockets; + LinearAllocatedVector<VSocket *> m_directly_linked_sockets; + VNode *m_node; + bool m_is_input; + bNodeSocket *m_bsocket; + uint m_id; + PointerRNA m_rna; + uint m_index; + + friend VirtualNodeTree; + + public: + ArrayRef<const VSocket *> linked_sockets() const; + ArrayRef<const VSocket *> directly_linked_sockets() const; + + const VNode &node() const; + const VirtualNodeTree &tree() const; + uint id() const; + + uint index() const; + + bool is_input() const; + bool is_output() const; + + const VSocket &as_base() const; + const VInputSocket &as_input() const; + const VOutputSocket &as_output() const; + + PointerRNA *rna() const; + + StringRefNull idname() const; + StringRefNull name() const; + + bool is_linked() const; + + bNodeSocket *bsocket() const; + bNodeTree *btree() const; +}; + +class VInputSocket final : public VSocket { + public: + ArrayRef<const VOutputSocket *> linked_sockets() const; + ArrayRef<const VOutputSocket *> directly_linked_sockets() const; +}; + +class VOutputSocket final : public VSocket { + public: + ArrayRef<const VInputSocket *> linked_sockets() const; + ArrayRef<const VInputSocket *> directly_linked_sockets() const; +}; + +class VNode : BLI::NonCopyable, BLI::NonMovable { + private: + VirtualNodeTree *m_vtree; + LinearAllocatedVector<VInputSocket *> m_inputs; + LinearAllocatedVector<VOutputSocket *> m_outputs; + bNode *m_bnode; + uint m_id; + PointerRNA m_rna; + + friend VirtualNodeTree; + + public: + const VirtualNodeTree &tree() const; + + ArrayRef<const VInputSocket *> inputs() const; + ArrayRef<const VOutputSocket *> outputs() const; + + PointerRNA *rna() const; + StringRefNull idname() const; + StringRefNull name() const; + + const VInputSocket &input(uint index) const; + const VOutputSocket &output(uint index) const; + + const VInputSocket &input(uint index, StringRef expected_name) const; + const VOutputSocket &output(uint index, StringRef expected_name) const; + + bNode *bnode() const; + bNodeTree *btree() const; + + uint id() const; +}; + +class VirtualNodeTree : BLI::NonCopyable, BLI::NonMovable { + private: + BLI::LinearAllocator<> m_allocator; + bNodeTree *m_btree; + Vector<VNode *> m_nodes_by_id; + Vector<VSocket *> m_sockets_by_id; + Vector<VInputSocket *> m_input_sockets; + Vector<VOutputSocket *> m_output_sockets; + StringMultiMap<VNode *> m_nodes_by_idname; + + public: + VirtualNodeTree(bNodeTree *btree); + ~VirtualNodeTree(); + + ArrayRef<const VNode *> nodes() const; + ArrayRef<const VNode *> nodes_with_idname(StringRef idname) const; + uint socket_count() const; + + ArrayRef<const VSocket *> all_sockets() const; + ArrayRef<const VInputSocket *> all_input_sockets() const; + ArrayRef<const VOutputSocket *> all_output_sockets() const; + + const VSocket &socket_by_id(uint id) const; + + bNodeTree *btree() const; + + private: + void find_targets_skipping_reroutes(VOutputSocket &vsocket, + LinearAllocatedVector<VSocket *> &r_targets); +}; + +/* Virtual Node Tree inline functions + ****************************************************/ + +inline ArrayRef<const VSocket *> VSocket::linked_sockets() const +{ + return m_linked_sockets.as_ref(); +} + +inline ArrayRef<const VSocket *> VSocket::directly_linked_sockets() const +{ + return m_directly_linked_sockets.as_ref(); +} + +inline const VirtualNodeTree &VSocket::tree() const +{ + return m_node->tree(); +} + +inline const VNode &VSocket::node() const +{ + return *m_node; +} + +inline uint VSocket::id() const +{ + return m_id; +} + +inline uint VSocket::index() const +{ + return m_index; +} + +inline bool VSocket::is_input() const +{ + return m_is_input; +} + +inline bool VSocket::is_output() const +{ + return !m_is_input; +} + +inline bool VSocket::is_linked() const +{ + return m_linked_sockets.size() > 0; +} + +inline const VSocket &VSocket::as_base() const +{ + return *this; +} + +inline const VInputSocket &VSocket::as_input() const +{ + BLI_assert(this->is_input()); + return *(const VInputSocket *)this; +} + +inline const VOutputSocket &VSocket::as_output() const +{ + BLI_assert(this->is_output()); + return *(const VOutputSocket *)this; +} + +inline PointerRNA *VSocket::rna() const +{ + return const_cast<PointerRNA *>(&m_rna); +} + +inline StringRefNull VSocket::idname() const +{ + return m_bsocket->idname; +} + +inline StringRefNull VSocket::name() const +{ + return m_bsocket->name; +} + +inline bNodeSocket *VSocket::bsocket() const +{ + return m_bsocket; +} + +inline ArrayRef<const VOutputSocket *> VInputSocket::linked_sockets() const +{ + return m_linked_sockets.as_ref().cast<const VOutputSocket *>(); + ; +} + +inline ArrayRef<const VOutputSocket *> VInputSocket::directly_linked_sockets() const +{ + return m_directly_linked_sockets.as_ref().cast<const VOutputSocket *>(); +} + +inline ArrayRef<const VInputSocket *> VOutputSocket::linked_sockets() const +{ + return m_linked_sockets.as_ref().cast<const VInputSocket *>(); +} + +inline ArrayRef<const VInputSocket *> VOutputSocket::directly_linked_sockets() const +{ + return m_directly_linked_sockets.as_ref().cast<const VInputSocket *>(); +} + +inline ArrayRef<const VInputSocket *> VNode::inputs() const +{ + return m_inputs.as_ref(); +} + +inline ArrayRef<const VOutputSocket *> VNode::outputs() const +{ + return m_outputs.as_ref(); +} + +inline const VirtualNodeTree &VNode::tree() const +{ + return *m_vtree; +} + +inline PointerRNA *VNode::rna() const +{ + return const_cast<PointerRNA *>(&m_rna); +} + +inline StringRefNull VNode::idname() const +{ + return m_bnode->idname; +} + +inline StringRefNull VNode::name() const +{ + return m_bnode->name; +} + +inline const VInputSocket &VNode::input(uint index) const +{ + return *m_inputs[index]; +} + +inline const VOutputSocket &VNode::output(uint index) const +{ + return *m_outputs[index]; +} + +inline const VInputSocket &VNode::input(uint index, StringRef expected_name) const +{ + BLI_assert(m_inputs[index]->name() == expected_name); + UNUSED_VARS_NDEBUG(expected_name); + return *m_inputs[index]; +} + +inline const VOutputSocket &VNode::output(uint index, StringRef expected_name) const +{ + BLI_assert(m_outputs[index]->name() == expected_name); + UNUSED_VARS_NDEBUG(expected_name); + return *m_outputs[index]; +} + +inline bNode *VNode::bnode() const +{ + return m_bnode; +} + +inline uint VNode::id() const +{ + return m_id; +} + +inline bNodeTree *VirtualNodeTree::btree() const +{ + return m_btree; +} + +inline ArrayRef<const VNode *> VirtualNodeTree::nodes() const +{ + return m_nodes_by_id.as_ref(); +} + +inline ArrayRef<const VNode *> VirtualNodeTree::nodes_with_idname(StringRef idname) const +{ + return m_nodes_by_idname.lookup_default(idname); +} + +inline uint VirtualNodeTree::socket_count() const +{ + return m_sockets_by_id.size(); +} + +inline ArrayRef<const VSocket *> VirtualNodeTree::all_sockets() const +{ + return m_sockets_by_id.as_ref(); +} + +inline ArrayRef<const VInputSocket *> VirtualNodeTree::all_input_sockets() const +{ + return m_input_sockets.as_ref(); +} + +inline ArrayRef<const VOutputSocket *> VirtualNodeTree::all_output_sockets() const +{ + return m_output_sockets.as_ref(); +} + +inline const VSocket &VirtualNodeTree::socket_by_id(uint id) const +{ + return *m_sockets_by_id[id]; +} + +} // namespace BKE + +#endif /* __BKE_VIRTUAL_NODE_TREE_H__ */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 6e612df33d5..f718f25ee3d 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -37,6 +37,7 @@ set(INC ../nodes ../physics ../shader_fx + ../simulations ../render/extern/include ../../../intern/ghost ../../../intern/glew-mx @@ -128,6 +129,8 @@ set(SRC intern/icons_rasterize.c intern/idprop.c intern/idprop_utils.c + intern/id_data_cache.cc + intern/id_handle.cc intern/idtype.c intern/image.c intern/image_gen.c @@ -230,6 +233,7 @@ set(SRC intern/subdiv_stats.c intern/subdiv_topology.c intern/subsurf_ccg.c + intern/surface_hook.cc intern/text.c intern/text_suggestions.c intern/texture.c @@ -243,6 +247,7 @@ set(SRC intern/tracking_util.c intern/undo_system.c intern/unit.c + intern/virtual_node_tree.cc intern/volume.cc intern/volume_render.cc intern/workspace.c @@ -309,6 +314,8 @@ set(SRC BKE_hair.h BKE_icons.h BKE_idprop.h + BKE_id_data_cache.h + BKE_id_handle.h BKE_idtype.h BKE_image.h BKE_ipo.h @@ -374,12 +381,14 @@ set(SRC BKE_subdiv_mesh.h BKE_subdiv_topology.h BKE_subsurf.h + BKE_surface_hook.h BKE_text.h BKE_text_suggestions.h BKE_texture.h BKE_tracking.h BKE_undo_system.h BKE_unit.h + BKE_virtual_node_tree.h BKE_volume.h BKE_volume_render.h BKE_workspace.h diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index 439992a4113..cd933553d8d 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -1871,15 +1871,15 @@ static DriverVarTypeInfo dvar_types[MAX_DVAR_TYPES] = { BEGIN_DVAR_TYPEDEF(DVAR_TYPE_ROT_DIFF) dvar_eval_rotDiff, /* eval callback */ 2, /* number of targets used */ {"Object/Bone 1", "Object/Bone 2"}, /* UI names for targets */ - {DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY, - DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY} /* flags */ + {DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY, DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY} + /* flags */ END_DVAR_TYPEDEF, BEGIN_DVAR_TYPEDEF(DVAR_TYPE_LOC_DIFF) dvar_eval_locDiff, /* eval callback */ 2, /* number of targets used */ {"Object/Bone 1", "Object/Bone 2"}, /* UI names for targets */ - {DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY, - DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY} /* flags */ + {DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY, DTAR_FLAG_STRUCT_REF | DTAR_FLAG_ID_OB_ONLY} + /* flags */ END_DVAR_TYPEDEF, BEGIN_DVAR_TYPEDEF(DVAR_TYPE_TRANSFORM_CHAN) dvar_eval_transChan, /* eval callback */ @@ -1983,6 +1983,10 @@ void driver_change_variable_type(DriverVar *dvar, int type) if ((flags & DTAR_FLAG_ID_OB_ONLY) || (dtar->idtype == 0)) { dtar->idtype = ID_OB; } + + if (type == DVAR_TYPE_FUNCTION) { + dtar->idtype = ID_NT; + } } DRIVER_TARGETS_LOOPER_END; } diff --git a/source/blender/blenkernel/intern/id_data_cache.cc b/source/blender/blenkernel/intern/id_data_cache.cc new file mode 100644 index 00000000000..187808fbeef --- /dev/null +++ b/source/blender/blenkernel/intern/id_data_cache.cc @@ -0,0 +1,31 @@ +#include "BKE_id_data_cache.h" + +namespace BKE { + +IDDataCache::~IDDataCache() +{ + for (auto bvhtree : m_bvh_trees.values()) { + if (bvhtree != nullptr) { + free_bvhtree_from_mesh(bvhtree); + delete bvhtree; + } + } +} + +BVHTreeFromMesh *IDDataCache::get_bvh_tree(Object *object) const +{ + BLI_assert(object != nullptr); + + std::lock_guard<std::mutex> lock(m_bvt_trees_mutex); + + return m_bvh_trees.lookup_or_add(object, [&]() -> BVHTreeFromMesh * { + if (object->type != OB_MESH) { + return nullptr; + } + BVHTreeFromMesh *bvhtree_data = new BVHTreeFromMesh(); + BKE_bvhtree_from_mesh_get(bvhtree_data, (Mesh *)object->data, BVHTREE_FROM_LOOPTRI, 2); + return bvhtree_data; + }); +} + +} // namespace BKE diff --git a/source/blender/blenkernel/intern/id_handle.cc b/source/blender/blenkernel/intern/id_handle.cc new file mode 100644 index 00000000000..4e1c06a022e --- /dev/null +++ b/source/blender/blenkernel/intern/id_handle.cc @@ -0,0 +1,33 @@ +#include "BLI_hash.h" +#include "BLI_utildefines.h" + +#include "BKE_id_handle.h" + +#include "DNA_ID.h" +#include "DNA_image_types.h" +#include "DNA_object_types.h" + +namespace BKE { + +IDHandle::IDHandle(ID *id) +{ + BLI_assert(id != nullptr); + m_identifier = BLI_hash_string(id->name); +} + +ObjectIDHandle::ObjectIDHandle(Object *object) : IDHandle(&object->id) +{ +} + +ImageIDHandle::ImageIDHandle(Image *image) : IDHandle(&image->id) +{ +} + +static IDHandleLookup empty_id_handle_lookup; + +const IDHandleLookup &IDHandleLookup::Empty() +{ + return empty_id_handle_lookup; +} + +} // namespace BKE diff --git a/source/blender/blenkernel/intern/shrinkwrap.c b/source/blender/blenkernel/intern/shrinkwrap.c index 06086cdf56a..ecdb92ffa8a 100644 --- a/source/blender/blenkernel/intern/shrinkwrap.c +++ b/source/blender/blenkernel/intern/shrinkwrap.c @@ -721,7 +721,7 @@ static void shrinkwrap_calc_normal_projection(ShrinkwrapCalcData *calc) /* * Shrinkwrap Target Surface Project mode * - * It uses Newton's method to find a surface location with its + * It uses Newton's method to find a Surface Hook with its * smooth normal pointing at the original point. * * The equation system on barycentric weights and normal multiplier: diff --git a/source/blender/blenkernel/intern/surface_hook.cc b/source/blender/blenkernel/intern/surface_hook.cc new file mode 100644 index 00000000000..b8ff99cf505 --- /dev/null +++ b/source/blender/blenkernel/intern/surface_hook.cc @@ -0,0 +1,7 @@ +#include "BKE_surface_hook.h" + +#include "BLI_hash.h" + +namespace BKE { + +} // namespace BKE diff --git a/source/blender/blenkernel/intern/virtual_node_tree.cc b/source/blender/blenkernel/intern/virtual_node_tree.cc new file mode 100644 index 00000000000..81698d0e524 --- /dev/null +++ b/source/blender/blenkernel/intern/virtual_node_tree.cc @@ -0,0 +1,115 @@ +#include "BKE_virtual_node_tree.h" + +#include "BLI_listbase_wrapper.h" +#include "BLI_map.h" + +namespace BKE { + +using BLI::Map; +using BSocketList = BLI::IntrusiveListBaseWrapper<bNodeSocket>; +using BNodeList = BLI::IntrusiveListBaseWrapper<bNode>; +using BLinkList = BLI::IntrusiveListBaseWrapper<bNodeLink>; + +static bool is_reroute_node(const VNode &vnode) +{ + return vnode.idname() == "NodeReroute"; +} + +VirtualNodeTree::VirtualNodeTree(bNodeTree *btree) : m_btree(btree) +{ + BLI_assert(btree != nullptr); + + VirtualNodeTree &vtree = *this; + + Map<bNode *, VNode *> node_mapping; + + for (bNode *bnode : BNodeList(btree->nodes)) { + VNode &vnode = *vtree.m_allocator.construct<VNode>(); + + vnode.m_vtree = &vtree; + vnode.m_bnode = bnode; + vnode.m_id = vtree.m_nodes_by_id.append_and_get_index(&vnode); + RNA_pointer_create(&btree->id, &RNA_Node, bnode, &vnode.m_rna); + + for (bNodeSocket *bsocket : BSocketList(bnode->inputs)) { + VInputSocket &vsocket = *vtree.m_allocator.construct<VInputSocket>(); + + vsocket.m_node = &vnode; + vsocket.m_index = vnode.m_inputs.append_and_get_index(&vsocket, m_allocator); + vsocket.m_is_input = true; + vsocket.m_bsocket = bsocket; + vsocket.m_id = vtree.m_sockets_by_id.append_and_get_index(&vsocket); + RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &vsocket.m_rna); + + vtree.m_input_sockets.append(&vsocket); + } + + for (bNodeSocket *bsocket : BSocketList(bnode->outputs)) { + VOutputSocket &vsocket = *vtree.m_allocator.construct<VOutputSocket>(); + + vsocket.m_node = &vnode; + vsocket.m_index = vnode.m_outputs.append_and_get_index(&vsocket, m_allocator); + vsocket.m_is_input = false; + vsocket.m_bsocket = bsocket; + vsocket.m_id = vtree.m_sockets_by_id.append_and_get_index(&vsocket); + RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &vsocket.m_rna); + + vtree.m_output_sockets.append(&vsocket); + } + + node_mapping.add_new(bnode, &vnode); + } + + for (bNodeLink *blink : BLinkList(btree->links)) { + VOutputSocket &from_vsocket = + *node_mapping.lookup(blink->fromnode) + ->m_outputs[BSocketList(blink->fromnode->outputs).index_of(blink->fromsock)]; + VInputSocket &to_vsocket = + *node_mapping.lookup(blink->tonode) + ->m_inputs[BSocketList(blink->tonode->inputs).index_of(blink->tosock)]; + + from_vsocket.m_directly_linked_sockets.append(&to_vsocket, m_allocator); + to_vsocket.m_directly_linked_sockets.append(&from_vsocket, m_allocator); + } + + for (VOutputSocket *socket : vtree.m_output_sockets) { + if (!is_reroute_node(socket->node())) { + vtree.find_targets_skipping_reroutes(*socket, socket->m_linked_sockets); + for (VSocket *target : socket->m_linked_sockets) { + target->m_linked_sockets.append(socket, m_allocator); + } + } + } + + for (VNode *vnode : vtree.m_nodes_by_id) { + vtree.m_nodes_by_idname.add(vnode->idname(), vnode); + } +} + +void VirtualNodeTree::find_targets_skipping_reroutes(VOutputSocket &vsocket, + LinearAllocatedVector<VSocket *> &r_targets) +{ + for (VSocket *direct_target : vsocket.m_directly_linked_sockets) { + if (is_reroute_node(*direct_target->m_node)) { + this->find_targets_skipping_reroutes(*direct_target->m_node->m_outputs[0], r_targets); + } + else if (!r_targets.contains(direct_target)) { + r_targets.append(direct_target, m_allocator); + } + } +} + +VirtualNodeTree::~VirtualNodeTree() +{ + for (VNode *node : m_nodes_by_id) { + node->~VNode(); + } + for (VInputSocket *socket : m_input_sockets) { + socket->~VInputSocket(); + } + for (VOutputSocket *socket : m_output_sockets) { + socket->~VOutputSocket(); + } +} + +}; // namespace BKE diff --git a/source/blender/blenlib/BLI_buffer_cache.h b/source/blender/blenlib/BLI_buffer_cache.h new file mode 100644 index 00000000000..9d70fe87633 --- /dev/null +++ b/source/blender/blenlib/BLI_buffer_cache.h @@ -0,0 +1,83 @@ +#ifndef __BLI_BUFFER_ALLOCATOR_H__ +#define __BLI_BUFFER_ALLOCATOR_H__ + +#include "BLI_vector.h" + +namespace BLI { + +class BufferCache { + private: + static const int Alignment = 64; + + struct BufferHead { + uint buffer_size_in_bytes; + + void *user_ptr() + { + BLI_STATIC_ASSERT(sizeof(BufferHead) <= Alignment, ""); + return POINTER_OFFSET(this, Alignment); + } + + static BufferHead *FromUserPtr(void *ptr) + { + return (BufferHead *)POINTER_OFFSET(ptr, -Alignment); + } + }; + + Vector<BufferHead *, 16> m_all_buffers; + Vector<BufferHead *, 16> m_cached_buffers; + + public: + BufferCache() = default; + + ~BufferCache() + { + assert_same_size(m_cached_buffers, m_all_buffers); + + for (BufferHead *head : m_all_buffers) { + MEM_freeN((void *)head); + } + } + + void *allocate(uint size, uint alignment) + { + UNUSED_VARS_NDEBUG(alignment); + BLI_assert(alignment <= Alignment); + + /* Only use buffer sizes that are a power of two, to make them easier to reuse. */ + uint padded_size = power_of_2_max_u(size); + + /* Try to use a cached memory buffer. Start searching from the back to prefer buffers that have + * been used "just before". */ + for (int i = m_cached_buffers.size() - 1; i >= 0; i--) { + BufferHead *head = m_cached_buffers[i]; + if (head->buffer_size_in_bytes == padded_size) { + void *user_ptr = head->user_ptr(); + m_cached_buffers.remove_and_reorder(i); + return user_ptr; + } + } + + BufferHead *new_head = (BufferHead *)MEM_mallocN_aligned( + padded_size + Alignment, Alignment, "allocate in BufferCache"); + new_head->buffer_size_in_bytes = padded_size; + m_all_buffers.append(new_head); + return new_head->user_ptr(); + } + + void deallocate(void *buffer) + { + BufferHead *head = BufferHead::FromUserPtr(buffer); + BLI_assert(m_all_buffers.contains(head)); + m_cached_buffers.append(head); + } + + void *allocate(uint element_amount, uint element_size, uint alignment) + { + return this->allocate(element_amount * element_size, alignment); + } +}; + +} // namespace BLI + +#endif /* __BLI_BUFFER_ALLOCATOR_H__ */ diff --git a/source/blender/blenlib/BLI_color.h b/source/blender/blenlib/BLI_color.h new file mode 100644 index 00000000000..354312efca8 --- /dev/null +++ b/source/blender/blenlib/BLI_color.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BLI_COLOR_H__ +#define __BLI_COLOR_H__ + +#include <array> +#include <iostream> + +#include "BLI_math_color.h" + +namespace BLI { + +struct rgba_f { + float r, g, b, a; + + rgba_f() = default; + + rgba_f(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) + { + } + + operator float *() + { + return &r; + } + + operator std::array<float, 4>() + { + return {r, g, b, a}; + } + + friend std::ostream &operator<<(std::ostream &stream, rgba_f c) + { + stream << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; + return stream; + } +}; + +struct rgba_b { + uint8_t r, g, b, a; + + rgba_b() = default; + + rgba_b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) + { + } + + rgba_b(rgba_f other) + { + rgba_float_to_uchar(*this, other); + } + + operator rgba_f() const + { + rgba_f result; + rgba_uchar_to_float(result, *this); + return result; + } + + operator uint8_t *() + { + return &r; + } + + operator const uint8_t *() const + { + return &r; + } +}; + +} // namespace BLI + +#endif /* __BLI_COLOR_H__ */ diff --git a/source/blender/blenlib/BLI_dot_export.h b/source/blender/blenlib/BLI_dot_export.h new file mode 100644 index 00000000000..4ad430f88ff --- /dev/null +++ b/source/blender/blenlib/BLI_dot_export.h @@ -0,0 +1,274 @@ +#ifndef __BLI_DOT_EXPORT_H__ +#define __BLI_DOT_EXPORT_H__ + +/** + * Language grammar: https://www.graphviz.org/doc/info/lang.html + * Attributes: https://www.graphviz.org/doc/info/attrs.html + * Node Shapes: https://www.graphviz.org/doc/info/shapes.html + * Preview: https://dreampuf.github.io/GraphvizOnline + */ + +#include "BLI_map.h" +#include "BLI_optional.h" +#include "BLI_set.h" +#include "BLI_string_map.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" + +#include "BLI_dot_export_attribute_enums.h" + +#include <sstream> + +namespace BLI { +namespace DotExport { + +class Graph; +class DirectedGraph; +class UndirectedGraph; +class Node; +class NodePort; +class DirectedEdge; +class UndirectedEdge; +class Cluster; +class AttributeList; + +class AttributeList { + private: + Map<std::string, std::string> m_attributes; + + public: + void export__as_bracket_list(std::stringstream &ss) const; + + void set(StringRef key, StringRef value) + { + m_attributes.add_override(key, value); + } +}; + +class Graph { + private: + AttributeList m_attributes; + Vector<std::unique_ptr<Node>> m_nodes; + Vector<std::unique_ptr<Cluster>> m_clusters; + + Set<Node *> m_top_level_nodes; + Set<Cluster *> m_top_level_clusters; + + friend Cluster; + friend Node; + + public: + Node &new_node(StringRef label); + Cluster &new_cluster(StringRef label = ""); + + void export__declare_nodes_and_clusters(std::stringstream &ss) const; + + void set_attribute(StringRef key, StringRef value) + { + m_attributes.set(key, value); + } + + void set_rankdir(Attr_rankdir::Enum rankdir) + { + this->set_attribute("rankdir", Attr_rankdir::to_string(rankdir)); + } + + void set_random_cluster_bgcolors(); +}; + +class Cluster { + private: + AttributeList m_attributes; + Graph &m_graph; + Cluster *m_parent = nullptr; + Set<Cluster *> m_children; + Set<Node *> m_nodes; + + friend Graph; + friend Node; + + Cluster(Graph &graph) : m_graph(graph) + { + } + + public: + void export__declare_nodes_and_clusters(std::stringstream &ss) const; + + void set_attribute(StringRef key, StringRef value) + { + m_attributes.set(key, value); + } + + void set_parent_cluster(Cluster *cluster); + void set_parent_cluster(Cluster &cluster) + { + this->set_parent_cluster(&cluster); + } + + void set_random_cluster_bgcolors(); +}; + +class Node { + private: + AttributeList m_attributes; + Graph &m_graph; + Cluster *m_cluster = nullptr; + + friend Graph; + + Node(Graph &graph) : m_graph(graph) + { + } + + public: + const AttributeList &attributes() const + { + return m_attributes; + } + + AttributeList &attributes() + { + return m_attributes; + } + + void set_parent_cluster(Cluster *cluster); + void set_parent_cluster(Cluster &cluster) + { + this->set_parent_cluster(&cluster); + } + + void set_attribute(StringRef key, StringRef value) + { + m_attributes.set(key, value); + } + + void set_shape(Attr_shape::Enum shape) + { + this->set_attribute("shape", Attr_shape::to_string(shape)); + } + + /* See https://www.graphviz.org/doc/info/attrs.html#k:color. */ + void set_background_color(StringRef name) + { + this->set_attribute("fillcolor", name); + this->set_attribute("style", "filled"); + } + + void export__as_id(std::stringstream &ss) const; + + void export__as_declaration(std::stringstream &ss) const; +}; + +class UndirectedGraph final : public Graph { + private: + Vector<std::unique_ptr<UndirectedEdge>> m_edges; + + public: + std::string to_dot_string() const; + + UndirectedEdge &new_edge(NodePort a, NodePort b); +}; + +class DirectedGraph final : public Graph { + private: + Vector<std::unique_ptr<DirectedEdge>> m_edges; + + public: + std::string to_dot_string() const; + + DirectedEdge &new_edge(NodePort from, NodePort to); +}; + +class NodePort { + private: + Node *m_node; + Optional<std::string> m_port_name; + + public: + NodePort(Node &node, Optional<std::string> port_name = {}) + : m_node(&node), m_port_name(std::move(port_name)) + { + } + + void to_dot_string(std::stringstream &ss) const; +}; + +class Edge : BLI::NonCopyable, BLI::NonMovable { + protected: + AttributeList m_attributes; + NodePort m_a; + NodePort m_b; + + public: + Edge(NodePort a, NodePort b) : m_a(std::move(a)), m_b(std::move(b)) + { + } + + void set_attribute(StringRef key, StringRef value) + { + m_attributes.set(key, value); + } + + void set_arrowhead(Attr_arrowType::Enum type) + { + this->set_attribute("arrowhead", Attr_arrowType::to_string(type)); + } + + void set_arrowtail(Attr_arrowType::Enum type) + { + this->set_attribute("arrowtail", Attr_arrowType::to_string(type)); + } + + void set_dir(Attr_dirType::Enum type) + { + this->set_attribute("dir", Attr_dirType::to_string(type)); + } +}; + +class DirectedEdge : public Edge { + public: + DirectedEdge(NodePort from, NodePort to) : Edge(std::move(from), std::move(to)) + { + } + + void export__as_edge_statement(std::stringstream &ss) const; +}; + +class UndirectedEdge : public Edge { + public: + UndirectedEdge(NodePort a, NodePort b) : Edge(std::move(a), std::move(b)) + { + } + + void export__as_edge_statement(std::stringstream &ss) const; +}; + +std::string color_attr_from_hsv(float h, float s, float v); + +class NodeWithSocketsRef { + private: + Node *m_node; + + public: + NodeWithSocketsRef(Node &node, + StringRef name, + ArrayRef<std::string> input_names, + ArrayRef<std::string> output_names); + + NodePort input(uint index) const + { + std::string port = "\"in" + std::to_string(index) + "\""; + return NodePort(*m_node, port); + } + + NodePort output(uint index) const + { + std::string port = "\"out" + std::to_string(index) + "\""; + return NodePort(*m_node, port); + } +}; + +} // namespace DotExport +} // namespace BLI + +#endif /* __BLI_DOT_EXPORT_H__ */ diff --git a/source/blender/blenlib/BLI_dot_export_attribute_enums.h b/source/blender/blenlib/BLI_dot_export_attribute_enums.h new file mode 100644 index 00000000000..3e7f1d7623d --- /dev/null +++ b/source/blender/blenlib/BLI_dot_export_attribute_enums.h @@ -0,0 +1,112 @@ +#ifndef __BLI_DOT_EXPORT_ATTRIBUTE_ENUMS_H__ +#define __BLI_DOT_EXPORT_ATTRIBUTE_ENUMS_H__ + +#include "BLI_string_ref.h" + +namespace BLI { +namespace DotExport { + +namespace Attr_rankdir { +enum Enum { + LeftToRight, + TopToBottom, +}; + +static StringRef to_string(Enum value) +{ + switch (value) { + case LeftToRight: + return "LR"; + case TopToBottom: + return "TB"; + } + return ""; +} +} // namespace Attr_rankdir + +namespace Attr_shape { +enum Enum { + Rectangle, + Ellipse, + Circle, + Point, + Diamond, + Square, +}; + +static StringRef to_string(Enum value) +{ + switch (value) { + case Rectangle: + return "rectangle"; + case Ellipse: + return "ellipse"; + case Circle: + return "circle"; + case Point: + return "point"; + case Diamond: + return "diamond"; + case Square: + return "square"; + } + return ""; +} +} // namespace Attr_shape + +namespace Attr_arrowType { +enum Enum { + Normal, + Inv, + Dot, + None, + Empty, + Box, + Vee, +}; + +static StringRef to_string(Enum value) +{ + switch (value) { + case Normal: + return "normal"; + case Inv: + return "inv"; + case Dot: + return "dot"; + case None: + return "none"; + case Empty: + return "empty"; + case Box: + return "box"; + case Vee: + return "vee"; + } + return ""; +} +} // namespace Attr_arrowType + +namespace Attr_dirType { +enum Enum { Forward, Back, Both, None }; + +static StringRef to_string(Enum value) +{ + switch (value) { + case Forward: + return "forward"; + case Back: + return "back"; + case Both: + return "both"; + case None: + return "none"; + } + return ""; +} +} // namespace Attr_dirType + +} // namespace DotExport +} // namespace BLI + +#endif /* __BLI_DOT_EXPORT_ATTRIBUTE_ENUMS_H__ */ diff --git a/source/blender/blenlib/BLI_float2.h b/source/blender/blenlib/BLI_float2.h new file mode 100644 index 00000000000..c95302e7431 --- /dev/null +++ b/source/blender/blenlib/BLI_float2.h @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BLI_FLOAT2_H__ +#define __BLI_FLOAT2_H__ + +#include "BLI_float3.h" + +namespace BLI { + +struct float2 { + float x, y; + + float2() = default; + + float2(const float *ptr) : x{ptr[0]}, y{ptr[1]} + { + } + + float2(float x, float y) : x(x), y(y) + { + } + + float2(float3 other) : x(other.x), y(other.y) + { + } + + operator float *() + { + return &x; + } + + float2 clamped(float min, float max) + { + return {std::min(std::max(x, min), max), std::min(std::max(y, min), max)}; + } + + float2 clamped_01() + { + return this->clamped(0, 1); + } + + friend float2 operator+(float2 a, float2 b) + { + return {a.x + b.x, a.y + b.y}; + } + + friend float2 operator-(float2 a, float2 b) + { + return {a.x - b.x, a.y - b.y}; + } + + friend float2 operator*(float2 a, float b) + { + return {a.x * b, a.y * b}; + } + + friend float2 operator/(float2 a, float b) + { + return {a.x / b, a.y / b}; + } + + friend float2 operator*(float a, float2 b) + { + return b * a; + } + + friend std::ostream &operator<<(std::ostream &stream, float2 v) + { + stream << "(" << v.x << ", " << v.y << ")"; + return stream; + } +}; + +} // namespace BLI + +#endif /* __BLI_FLOAT2_H__ */ diff --git a/source/blender/blenlib/BLI_float3.h b/source/blender/blenlib/BLI_float3.h new file mode 100644 index 00000000000..bff673b872f --- /dev/null +++ b/source/blender/blenlib/BLI_float3.h @@ -0,0 +1,229 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BLI_FLOAT3_H__ +#define __BLI_FLOAT3_H__ + +#include <array> +#include <iostream> + +#include "BLI_math_vector.h" + +namespace BLI { +struct float3 { + float x, y, z; + + float3() = default; + + float3(const float *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]} + { + } + + explicit float3(float value) : x(value), y(value), z(value) + { + } + + explicit float3(int value) : x(value), y(value), z(value) + { + } + + float3(float x, float y, float z) : x{x}, y{y}, z{z} + { + } + + operator const float *() const + { + return (const float *)this; + } + + operator float *() + { + return (float *)this; + } + + operator std::array<float, 3>() + { + return {x, y, z}; + } + + float normalize_and_get_length() + { + return normalize_v3(*this); + } + + float3 normalized() const + { + float3 result; + normalize_v3_v3(result, *this); + return result; + } + + float length() const + { + return len_v3(*this); + } + + float length_squared() const + { + return len_squared_v3(*this); + } + + void reflect(float3 normal) + { + *this = this->reflected(normal); + } + + float3 reflected(float3 normal) const + { + float3 result; + reflect_v3_v3v3(result, *this, normal); + return result; + } + + static float3 safe_divide(const float3 a, const float3 b) + { + float3 result; + result.x = (b.x == 0.0f) ? 0.0f : a.x / b.x; + result.y = (b.y == 0.0f) ? 0.0f : a.y / b.y; + result.z = (b.z == 0.0f) ? 0.0f : a.z / b.z; + return result; + } + + void invert() + { + x = -x; + y = -y; + z = -z; + } + + bool is_zero() const + { + return x != 0.0f && y != 0.0f && z != 0.0f; + } + + friend float3 operator+(float3 a, float3 b) + { + return {a.x + b.x, a.y + b.y, a.z + b.z}; + } + + void operator+=(float3 b) + { + this->x += b.x; + this->y += b.y; + this->z += b.z; + } + + friend float3 operator-(float3 a, float3 b) + { + return {a.x - b.x, a.y - b.y, a.z - b.z}; + } + + friend float3 operator-(float3 a) + { + return {-a.x, -a.y, -a.z}; + } + + void operator-=(float3 b) + { + this->x -= b.x; + this->y -= b.y; + this->z -= b.z; + } + + void operator*=(float scalar) + { + this->x *= scalar; + this->y *= scalar; + this->z *= scalar; + } + + void operator*=(float3 other) + { + this->x *= other.x; + this->y *= other.y; + this->z *= other.z; + } + + friend float3 operator*(float3 a, float3 b) + { + return {a.x * b.x, a.y * b.y, a.z * b.z}; + } + + friend float3 operator*(float3 a, float b) + { + return {a.x * b, a.y * b, a.z * b}; + } + + friend float3 operator*(float a, float3 b) + { + return b * a; + } + + friend float3 operator/(float3 a, float3 b) + { + BLI_assert(!b.is_zero()); + return {a.x / b.x, a.y / b.y, a.z / b.z}; + } + + friend float3 operator/(float3 a, float b) + { + BLI_assert(b != 0); + return {a.x / b, a.y / b, a.z / b}; + } + + friend std::ostream &operator<<(std::ostream &stream, float3 v) + { + stream << "(" << v.x << ", " << v.y << ", " << v.z << ")"; + return stream; + } + + static float dot(float3 a, float3 b) + { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + static float3 cross_high_precision(float3 a, float3 b) + { + float3 result; + cross_v3_v3v3_hi_prec(result, a, b); + return result; + } + + static float3 project(float3 a, float3 b) + { + float3 result; + project_v3_v3v3(result, a, b); + return result; + } + + static float distance(float3 a, float3 b) + { + return (a - b).length(); + } + + static float distance_squared(float3 a, float3 b) + { + return float3::dot(a, b); + } + + static float3 interpolate(float3 a, float3 b, float t) + { + return a * (1 - t) + b * t; + } +}; +} // namespace BLI + +#endif /* __BLI_FLOAT3_H__ */ diff --git a/source/blender/blenlib/BLI_float4x4.h b/source/blender/blenlib/BLI_float4x4.h new file mode 100644 index 00000000000..61e458cc85e --- /dev/null +++ b/source/blender/blenlib/BLI_float4x4.h @@ -0,0 +1,103 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BLI_FLOAT4X4_H__ +#define __BLI_FLOAT4X4_H__ + +#include "BLI_array_ref.h" +#include "BLI_float3.h" +#include "BLI_math_matrix.h" + +namespace BLI { + +struct float4x4 { + float values[4][4]; + + float4x4() = default; + + float4x4(float *matrix) + { + memcpy(values, matrix, sizeof(float) * 16); + } + + float4x4(float matrix[4][4]) : float4x4((float *)matrix) + { + } + + operator float *() + { + return (float *)this; + } + + float4x4 inverted() const + { + float result[4][4]; + invert_m4_m4(result, values); + return result; + } + + float4x4 inverted__LocRotScale() const + { + return this->inverted(); + } + + float3 transform_position(float3 position) const + { + mul_m4_v3(values, position); + return position; + } + + float3 transform_direction(float3 direction) const + { + mul_mat3_m4_v3(values, direction); + return direction; + } + + static void transform_positions(ArrayRef<float4x4> matrices, + ArrayRef<float3> positions, + MutableArrayRef<float3> r_results) + { + uint amount = matrices.size(); + BLI_assert(amount == positions.size()); + BLI_assert(amount == r_results.size()); + for (uint i = 0; i < amount; i++) { + r_results[i] = matrices[i].transform_position(positions[i]); + } + } + + static void transform_directions(ArrayRef<float4x4> matrices, + ArrayRef<float3> directions, + MutableArrayRef<float3> r_results) + { + uint amount = matrices.size(); + BLI_assert(amount == directions.size()); + BLI_assert(amount == r_results.size()); + for (uint i = 0; i < amount; i++) { + r_results[i] = matrices[i].transform_direction(directions[i]); + } + } + + static float4x4 interpolate(float4x4 a, float4x4 b, float t) + { + float result[4][4]; + interp_m4_m4m4(result, a.values, b.values, t); + return result; + } +}; + +} // namespace BLI + +#endif /* __BLI_FLOAT4X4_H__ */ diff --git a/source/blender/blenlib/BLI_float_interval.h b/source/blender/blenlib/BLI_float_interval.h new file mode 100644 index 00000000000..ce37fa61607 --- /dev/null +++ b/source/blender/blenlib/BLI_float_interval.h @@ -0,0 +1,94 @@ +#ifndef __BLI_TIME_SPAN_H__ +#define __BLI_TIME_SPAN_H__ + +#include "BLI_array_ref.h" + +namespace BLI { + +class FloatInterval { + private: + float m_start; + float m_size; + + public: + FloatInterval(float start, float size) : m_start(start), m_size(size) + { + BLI_assert(size >= 0.0f); + } + + float start() const + { + return m_start; + } + + float size() const + { + return m_size; + } + + float end() const + { + return m_start + m_size; + } + + float value_at(float factor) const + { + return m_start + factor * m_size; + } + + void value_at(ArrayRef<float> factors, MutableArrayRef<float> r_values) + { + assert_same_size(factors, r_values); + for (uint i : factors.index_range()) { + r_values[i] = this->value_at(factors[i]); + } + } + + void sample_linear(MutableArrayRef<float> r_values) + { + if (r_values.size() == 0) { + return; + } + if (r_values.size() == 1) { + r_values[0] = this->value_at(0.5f); + } + for (uint i : r_values.index_range()) { + float factor = (i - 1) / (float)r_values.size(); + r_values[i] = this->value_at(factor); + } + } + + float factor_of(float value) const + { + BLI_assert(m_size > 0.0f); + return (value - m_start) / m_size; + } + + float safe_factor_of(float value) const + { + if (m_size > 0.0f) { + return this->factor_of(value); + } + else { + return 0.0f; + } + } + + void uniform_sample_range(float samples_per_time, + float &r_factor_start, + float &r_factor_step) const + { + if (m_size == 0.0f) { + /* Just needs to be greater than one. */ + r_factor_start = 2.0f; + return; + } + r_factor_step = 1 / (m_size * samples_per_time); + float time_start = std::ceil(m_start * samples_per_time) / samples_per_time; + r_factor_start = this->safe_factor_of(time_start); + } +}; + +} // namespace BLI + +#endif /* __BLI_TIME_SPAN_H__ */ diff --git a/source/blender/blenlib/BLI_index_mask.h b/source/blender/blenlib/BLI_index_mask.h new file mode 100644 index 00000000000..85cb3025fae --- /dev/null +++ b/source/blender/blenlib/BLI_index_mask.h @@ -0,0 +1,118 @@ +#ifndef __BLI_INDEX_MASK_H__ +#define __BLI_INDEX_MASK_H__ + +#include "BLI_array_ref.h" +#include "BLI_index_range.h" +#include "BLI_vector.h" +#include "BLI_vector_adaptor.h" + +namespace BLI { + +class IndexMask { + private: + ArrayRef<uint> m_indices; + + public: + IndexMask() = default; + + IndexMask(ArrayRef<uint> indices) : m_indices(indices) + { +#ifdef DEBUG + for (uint i = 1; i < indices.size(); i++) { + BLI_assert(indices[i - 1] < indices[i]); + } +#endif + } + + IndexMask(IndexRange range) : m_indices(range.as_array_ref()) + { + } + + template<uint N, typename Allocator> + IndexMask(const Vector<uint, N, Allocator> &vector) : IndexMask(vector.as_ref()) + { + } + + IndexMask(const VectorAdaptor<uint> &vector) : IndexMask(ArrayRef<uint>(vector)) + { + } + + explicit IndexMask(uint n) : IndexMask(IndexRange(n)) + { + } + + operator ArrayRef<uint>() const + { + return m_indices; + } + + const uint *begin() const + { + return m_indices.begin(); + } + + const uint *end() const + { + return m_indices.end(); + } + + uint operator[](uint index) const + { + return m_indices[index]; + } + + uint size() const + { + return m_indices.size(); + } + + uint min_array_size() const + { + return (m_indices.size() == 0) ? 0 : m_indices.last() + 1; + } + + ArrayRef<uint> indices() const + { + return m_indices; + } + + bool is_range() const + { + return m_indices.size() > 0 && m_indices.last() - m_indices.first() == m_indices.size() - 1; + } + + IndexRange as_range() const + { + BLI_assert(this->is_range()); + return IndexRange{m_indices.first(), m_indices.size()}; + } + + template<typename FuncT> void foreach_index(const FuncT &func) const + { + if (this->is_range()) { + IndexRange range = this->as_range(); + for (uint i : range) { + func(i); + } + } + else { + for (uint i : m_indices) { + func(i); + } + } + } + + IndexRange index_range() const + { + return m_indices.index_range(); + } + + uint last() const + { + return m_indices.last(); + } +}; + +} // namespace BLI + +#endif /* __BLI_INDEX_MASK_H__ */ diff --git a/source/blender/blenlib/BLI_index_to_ref_map.h b/source/blender/blenlib/BLI_index_to_ref_map.h new file mode 100644 index 00000000000..8e4c11f830c --- /dev/null +++ b/source/blender/blenlib/BLI_index_to_ref_map.h @@ -0,0 +1,121 @@ +#ifndef __BLI_INDEX_TO_REF_MAP_H__ +#define __BLI_INDEX_TO_REF_MAP_H__ + +#include "BLI_array_cxx.h" +#include "BLI_multi_map.h" + +namespace BLI { + +template<typename T, uint N = 4, typename Allocator = GuardedAllocator> class IndexToRefMap { + private: + Array<T *, N, Allocator> m_array; + + public: + IndexToRefMap(uint size) : m_array(size, nullptr) + { + } + + uint size() const + { + return m_array.size(); + } + + void add(uint key, T &value) + { + m_array[key] = &value; + } + + void add_new(uint key, T &value) + { + BLI_assert(m_array[key] == nullptr); + m_array[key] = &value; + } + + bool contains(uint key) const + { + return m_array[key] != nullptr; + } + + const T &lookup(uint key) const + { + BLI_assert(this->contains(key)); + return *m_array[key]; + } + + T &lookup(uint key) + { + BLI_assert(this->contains(key)); + return *m_array[key]; + } +}; + +#define IndexToRefMultiMap_UNMAPPED nullptr +#define IndexToRefMultiMap_MULTIMAPPED ((T *)1) + +template<typename T, uint N = 4, typename Allocator = GuardedAllocator> class IndexToRefMultiMap { + private: + Array<T *> m_array; + MultiMap<uint, T *> m_fallback_multimap; + + public: + IndexToRefMultiMap(uint max_index) : m_array(max_index, IndexToRefMultiMap_UNMAPPED) + { + } + + uint max_index() const + { + return m_array.size(); + } + + bool contains(uint key) const + { + return m_array[key] != IndexToRefMultiMap_UNMAPPED; + } + + ArrayRef<T *> lookup(uint key) const + { + T *const *stored_value_addr = &m_array[key]; + const T *stored_value = *stored_value_addr; + if (stored_value == IndexToRefMultiMap_UNMAPPED) { + return {}; + } + else if (stored_value == IndexToRefMultiMap_MULTIMAPPED) { + return m_fallback_multimap.lookup(key); + } + else { + return ArrayRef<T *>(stored_value_addr, 1); + } + } + + T &lookup_single(uint key) + { + T *stored_value = m_array[key]; + BLI_assert(stored_value != IndexToRefMultiMap_UNMAPPED && + stored_value != IndexToRefMultiMap_MULTIMAPPED); + return *stored_value; + } + + void add(uint key, T &value) + { + T **stored_value_addr = &m_array[key]; + T *stored_value = *stored_value_addr; + if (stored_value == IndexToRefMultiMap_UNMAPPED) { + *stored_value_addr = &value; + } + else if (stored_value == IndexToRefMultiMap_MULTIMAPPED) { + m_fallback_multimap.add(key, &value); + } + else { + T *other_value = stored_value; + *stored_value_addr = IndexToRefMultiMap_MULTIMAPPED; + m_fallback_multimap.add_multiple_new(key, {other_value, &value}); + } + } +}; + +#undef IndexToRefMultiMap_UNMAPPED +#undef IndexToRefMultiMap_MULTIMAPPED + +} // namespace BLI + +#endif /* __BLI_INDEX_TO_REF_MAP_H__ */ diff --git a/source/blender/blenlib/BLI_linear_allocated_vector.h b/source/blender/blenlib/BLI_linear_allocated_vector.h new file mode 100644 index 00000000000..d5d19b9975f --- /dev/null +++ b/source/blender/blenlib/BLI_linear_allocated_vector.h @@ -0,0 +1,230 @@ +#pragma once + +#include "BLI_index_range.h" +#include "BLI_linear_allocator.h" +#include "BLI_memory_utils_cxx.h" + +namespace BLI { + +template<typename T> class LinearAllocatedVector : BLI::NonCopyable { + private: + T *m_begin; + T *m_end; + T *m_capacity_end; + +#ifdef DEBUG + uint m_debug_size; +# define UPDATE_VECTOR_SIZE(ptr) (ptr)->m_debug_size = (ptr)->m_end - (ptr)->m_begin +#else +# define UPDATE_VECTOR_SIZE(ptr) ((void)0) +#endif + + public: + LinearAllocatedVector() : m_begin(nullptr), m_end(nullptr), m_capacity_end(nullptr) + { + UPDATE_VECTOR_SIZE(this); + } + + ~LinearAllocatedVector() + { + destruct_n(m_begin, this->size()); + } + + LinearAllocatedVector(LinearAllocatedVector &&other) + { + m_begin = other.m_begin; + m_end = other.m_end; + m_capacity_end = other.m_capacity_end; + + other.m_begin = nullptr; + other.m_end = nullptr; + other.m_capacity_end = nullptr; + + UPDATE_VECTOR_SIZE(this); + UPDATE_VECTOR_SIZE(&other); + } + + LinearAllocatedVector &operator=(LinearAllocatedVector &&other) + { + if (this == &other) { + return *this; + } + + m_begin = other.m_begin; + m_end = other.m_end; + m_capacity_end = other.m_capacity_end; + + other.m_begin = nullptr; + other.m_end = nullptr; + other.m_capacity_end = nullptr; + + UPDATE_VECTOR_SIZE(this); + UPDATE_VECTOR_SIZE(&other); + + return *this; + } + + operator ArrayRef<T>() const + { + return ArrayRef<T>(m_begin, this->size()); + } + + operator MutableArrayRef<T>() + { + return MutableArrayRef<T>(m_begin, this->size()); + } + + ArrayRef<T> as_ref() const + { + return *this; + } + + MutableArrayRef<T> as_mutable_ref() const + { + return *this; + } + + IndexRange index_range() const + { + return IndexRange(this->size()); + } + + uint size() const + { + return m_end - m_begin; + } + + uint capacity() const + { + return m_capacity_end - m_begin; + } + + void clear() + { + destruct_n(m_begin, this->size()); + m_end = m_begin; + UPDATE_VECTOR_SIZE(this); + } + + void append_unchecked(const T &value) + { + BLI_assert(m_end < m_capacity_end); + new (m_end) T(value); + m_end++; + UPDATE_VECTOR_SIZE(this); + } + + template<typename AllocT> void append(const T &value, LinearAllocator<AllocT> &allocator) + { + if (m_end == m_capacity_end) { + this->grow(this->size() + 1, allocator); + } + this->append_unchecked(value); + } + + template<typename AllocT> + uint append_and_get_index(const T &value, LinearAllocator<AllocT> &allocator) + { + uint index = this->size(); + this->append(value, allocator); + return index; + } + + bool contains(const T &value) const + { + for (const T ¤t : *this) { + if (current == value) { + return true; + } + } + return false; + } + + const T &operator[](uint index) const + { + BLI_assert(index < this->size()); + return m_begin[index]; + } + + T &operator[](uint index) + { + BLI_assert(index < this->size()); + return m_begin[index]; + } + + const T *begin() const + { + return m_begin; + } + + const T *end() const + { + return m_end; + } + + T *begin() + { + return m_begin; + } + + T *end() + { + return m_end; + } + + void remove_and_reorder(uint index) + { + BLI_assert(index < this->size()); + T *element_to_remove = m_begin + index; + m_end--; + if (element_to_remove < m_end) { + *element_to_remove = std::move(*m_end); + } + destruct(m_end); + UPDATE_VECTOR_SIZE(this); + } + + int index_try(const T &value) const + { + for (T *current = m_begin; current != m_end; current++) { + if (*current == value) { + return current - m_begin; + } + } + return -1; + } + + uint index(const T &value) const + { + int index = this->index_try(value); + BLI_assert(index >= 0); + return (uint)index; + } + + void remove_first_occurrence_and_reorder(const T &value) + { + uint index = this->index(value); + this->remove_and_reorder((uint)index); + } + + private: + template<typename AllocT> + BLI_NOINLINE void grow(uint min_capacity, LinearAllocator<AllocT> &allocator) + { + if (min_capacity <= this->capacity()) { + return; + } + + uint size = this->size(); + min_capacity = power_of_2_max_u(min_capacity); + + T *new_begin = (T *)allocator.allocate(sizeof(T) * min_capacity, alignof(T)); + uninitialized_relocate_n(m_begin, size, new_begin); + + m_begin = new_begin; + m_end = new_begin + size; + m_capacity_end = new_begin + min_capacity; + } +}; + +} // namespace BLI diff --git a/source/blender/blenlib/BLI_linear_allocator.h b/source/blender/blenlib/BLI_linear_allocator.h new file mode 100644 index 00000000000..f723ff470b2 --- /dev/null +++ b/source/blender/blenlib/BLI_linear_allocator.h @@ -0,0 +1,172 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bli + * + * A linear allocator is the simplest form of an allocator. It never reuses any memory, and + * therefore does not need a deallocation method. It simply hands out consecutive buffers of + * memory. When the current buffer is full, it reallocates a new larger buffer and continues. + */ + +#pragma once + +#include "BLI_stack_cxx.h" +#include "BLI_string_ref.h" +#include "BLI_timeit.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" + +namespace BLI { + +template<typename Allocator = GuardedAllocator> class LinearAllocator : NonCopyable, NonMovable { + private: + Allocator m_allocator; + Vector<void *> m_owned_buffers; + Vector<ArrayRef<char>> m_unused_borrowed_buffers; + + uintptr_t m_current_begin; + uintptr_t m_current_end; + uint m_next_min_alloc_size; + +#ifdef DEBUG + uint m_debug_allocated_amount = 0; +#endif + + public: + LinearAllocator() + { + m_current_begin = 0; + m_current_end = 0; + m_next_min_alloc_size = 64; + } + + ~LinearAllocator() + { + for (void *ptr : m_owned_buffers) { + m_allocator.deallocate(ptr); + } + } + + void provide_buffer(void *buffer, uint size) + { + m_unused_borrowed_buffers.append(ArrayRef<char>((char *)buffer, size)); + } + + template<uint Size, uint Alignment> + void provide_buffer(AlignedBuffer<Size, Alignment> &aligned_buffer) + { + this->provide_buffer(aligned_buffer.ptr(), Size); + } + + template<typename T> T *allocate() + { + return (T *)this->allocate(sizeof(T), alignof(T)); + } + + template<typename T> MutableArrayRef<T> allocate_array(uint length) + { + return MutableArrayRef<T>((T *)this->allocate(sizeof(T) * length), length); + } + + void *allocate(uint size, uint alignment = 4) + { + BLI_assert(alignment >= 1); + BLI_assert(is_power_of_2_i(alignment)); + +#ifdef DEBUG + m_debug_allocated_amount += size; +#endif + + uintptr_t alignment_mask = alignment - 1; + uintptr_t potential_allocation_begin = (m_current_begin + alignment_mask) & ~alignment_mask; + uintptr_t potential_allocation_end = potential_allocation_begin + size; + + if (potential_allocation_end <= m_current_end) { + m_current_begin = potential_allocation_end; + return (void *)potential_allocation_begin; + } + else { + this->allocate_new_buffer(size + alignment); + return this->allocate(size, alignment); + } + }; + + StringRefNull copy_string(StringRef str) + { + uint alloc_size = str.size() + 1; + char *buffer = (char *)this->allocate(alloc_size, 1); + str.copy(buffer, alloc_size); + return StringRefNull((const char *)buffer); + } + + template<typename T, typename... Args> T *construct(Args &&... args) + { + void *buffer = this->allocate(sizeof(T), alignof(T)); + T *value = new (buffer) T(std::forward<Args>(args)...); + return value; + } + + template<typename T, typename... Args> + ArrayRef<T *> construct_elements_and_pointer_array(uint n, Args &&... args) + { + void *pointer_buffer = this->allocate(n * sizeof(T *), alignof(T *)); + void *element_buffer = this->allocate(n * sizeof(T), alignof(T)); + + MutableArrayRef<T *> pointers((T **)pointer_buffer, n); + T *elements = (T *)element_buffer; + + for (uint i : IndexRange(n)) { + pointers[i] = elements + i; + } + for (uint i : IndexRange(n)) { + new (elements + i) T(std::forward<Args>(args)...); + } + + return pointers; + } + + template<typename T> MutableArrayRef<T> construct_array_copy(ArrayRef<T> source) + { + T *buffer = (T *)this->allocate(source.byte_size(), alignof(T)); + source.copy_to(buffer); + return MutableArrayRef<T>(buffer, source.size()); + } + + private: + void allocate_new_buffer(uint min_allocation_size) + { + for (uint i : m_unused_borrowed_buffers.index_range()) { + ArrayRef<char> buffer = m_unused_borrowed_buffers[i]; + if (buffer.size() >= min_allocation_size) { + m_unused_borrowed_buffers.remove_and_reorder(i); + m_current_begin = (uintptr_t)buffer.begin(); + m_current_end = (uintptr_t)buffer.end(); + return; + } + } + + uint size_in_bytes = power_of_2_min_u(std::max(min_allocation_size, m_next_min_alloc_size)); + m_next_min_alloc_size = size_in_bytes * 2; + + void *buffer = m_allocator.allocate(size_in_bytes, __func__); + m_owned_buffers.append(buffer); + m_current_begin = (uintptr_t)buffer; + m_current_end = m_current_begin + size_in_bytes; + } +}; + +} // namespace BLI diff --git a/source/blender/blenlib/BLI_multi_map.h b/source/blender/blenlib/BLI_multi_map.h new file mode 100644 index 00000000000..71867e0f5da --- /dev/null +++ b/source/blender/blenlib/BLI_multi_map.h @@ -0,0 +1,225 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bli + * + * A multimap is a map that allows storing multiple values per key. + */ + +#pragma once + +#include "BLI_array_ref.h" +#include "BLI_linear_allocator.h" +#include "BLI_map.h" +#include "BLI_vector.h" + +namespace BLI { + +template<typename KeyT, typename ValueT, uint N = 4> class MultiMap { + private: + struct Entry { + ValueT *ptr = nullptr; + uint length = 0; + uint capacity = 0; + }; + + LinearAllocator<> m_allocator; + Map<KeyT, Entry> m_map; + + public: + MultiMap() = default; + + ~MultiMap() + { + this->foreach_value([](ValueT &value) { value.~ValueT(); }); + } + + MultiMap(const MultiMap &other) + { + this->add_multiple(other); + } + + MultiMap &operator=(const MultiMap &other) + { + if (this == &other) { + return *this; + } + this->~MultiMap(); + new (this) MultiMap(other); + return *this; + } + + uint key_amount() const + { + return m_map.size(); + } + + uint value_amount(const KeyT &key) const + { + return m_map.lookup_default(key, {}).length; + } + + void add_new(const KeyT &key, const ValueT &value) + { + BLI_assert(!this->contains(key)); + this->add(key, value); + } + + void add_multiple_new(const KeyT &key, ArrayRef<ValueT> values) + { + BLI_assert(!this->contains(key)); + this->add_multiple(key, values); + } + + bool add(const KeyT &key, const ValueT &value) + { + return this->add__impl(key, value); + } + bool add(const KeyT &key, ValueT &&value) + { + return this->add__impl(key, std::move(value)); + } + bool add(KeyT &&key, const ValueT &value) + { + return this->add__impl(std::move(key), value); + } + bool add(KeyT &&key, ValueT &&value) + { + return this->add__impl(std::move(key), std::move(value)); + } + + void add_multiple(const KeyT &key, ArrayRef<ValueT> values) + { + this->add_multiple__impl(key, values); + } + void add_multiple(const KeyT &&key, ArrayRef<ValueT> values) + { + this->add_multiple__impl(std::move(key), values); + } + + template<uint OtherN> void add_multiple(const MultiMap<KeyT, ValueT, OtherN> &other) + { + BLI_assert(this != &other); + other.foreach_item( + [&](const KeyT &key, ArrayRef<ValueT> values) { this->add_multiple(key, values); }); + } + + ArrayRef<ValueT> lookup(const KeyT &key) const + { + const Entry &entry = m_map.lookup(key); + return ArrayRef<ValueT>(entry.ptr, entry.length); + } + + ArrayRef<ValueT> lookup_default(const KeyT &key, + ArrayRef<ValueT> default_array = ArrayRef<ValueT>()) const + { + const Entry *entry = m_map.lookup_ptr(key); + if (entry == nullptr) { + return default_array; + } + else { + return ArrayRef<ValueT>(entry->ptr, entry->length); + } + } + + bool contains(const KeyT &key) const + { + return m_map.contains(key); + } + + typename Map<KeyT, Entry>::KeyIterator keys() const + { + return m_map.keys(); + } + + template<typename FuncT> void foreach_value(const FuncT &func) const + { + for (const Entry &entry : m_map.values()) { + for (const ValueT &value : ArrayRef<ValueT>(entry.ptr, entry.length)) { + func(value); + } + } + } + + template<typename FuncT> void foreach_value(const FuncT &func) + { + for (Entry &entry : m_map.values()) { + for (ValueT &value : MutableArrayRef<ValueT>(entry.ptr, entry.length)) { + func(value); + } + } + } + + template<typename FuncT> void foreach_item(const FuncT &func) const + { + for (auto item : m_map.items()) { + const KeyT &key = item.key; + ArrayRef<ValueT> values{item.value.ptr, item.value.length}; + func(key, values); + } + } + + private: + template<typename ForwardKeyT> + void add_multiple__impl(ForwardKeyT &&key, ArrayRef<ValueT> values) + { + for (const ValueT &value : values) { + this->add(std::forward<ForwardKeyT>(key), value); + } + } + + template<typename ForwardKeyT, typename ForwardValueT> + bool add__impl(ForwardKeyT &&key, ForwardValueT &&value) + { + bool newly_inserted = m_map.add_or_modify( + std::forward<ForwardKeyT>(key), + /* Insert new key with value. */ + [&](Entry *r_entry) -> bool { + uint initial_capacity = 1; + ValueT *array = (ValueT *)m_allocator.allocate(sizeof(ValueT) * initial_capacity, + alignof(ValueT)); + new (array) ValueT(std::forward<ForwardValueT>(value)); + r_entry->ptr = array; + r_entry->length = 1; + r_entry->capacity = initial_capacity; + return true; + }, + /* Append new value for existing key. */ + [&](Entry *entry) -> bool { + if (entry->length < entry->capacity) { + new (entry->ptr + entry->length) ValueT(std::forward<ForwardValueT>(value)); + entry->length++; + } + else { + uint old_capacity = entry->capacity; + BLI_assert(old_capacity >= 1); + uint new_capacity = old_capacity * 2; + ValueT *new_array = (ValueT *)m_allocator.allocate(sizeof(ValueT) * new_capacity, + alignof(ValueT)); + uninitialized_relocate_n(entry->ptr, old_capacity, new_array); + new (new_array + entry->length) ValueT(std::forward<ForwardValueT>(value)); + entry->ptr = new_array; + entry->length++; + entry->capacity = new_capacity; + } + return false; + }); + return newly_inserted; + } +}; + +} /* namespace BLI */ diff --git a/source/blender/blenlib/BLI_parallel.h b/source/blender/blenlib/BLI_parallel.h new file mode 100644 index 00000000000..82c1e87a693 --- /dev/null +++ b/source/blender/blenlib/BLI_parallel.h @@ -0,0 +1,153 @@ +#ifndef __BLI_PARALLEL_H__ +#define __BLI_PARALLEL_H__ + +#ifdef WITH_TBB +# define TBB_SUPPRESS_DEPRECATED_MESSAGES 1 +# include "tbb/parallel_for.h" +# include "tbb/parallel_invoke.h" +#endif + +#include "BLI_index_range.h" +#include "BLI_multi_map.h" +#include "BLI_string_map.h" +#include "BLI_string_multi_map.h" + +namespace BLI { + +/** + * Call func for every index in the IndexRange. func has to receive a single uint parameter. + */ +template<typename FuncT> void parallel_for(IndexRange range, const FuncT &func) +{ + if (range.size() == 0) { + return; + } +#ifdef WITH_TBB + tbb::parallel_for(range.first(), range.one_after_last(), func); +#else + for (uint i : range) { + func(i); + } +#endif +} + +/** + * Call func for subranges of range. The size of the individual subranges is controlled by a + * grain_size. func has to receive an IndexRange as parameter. + */ +template<typename FuncT> +void blocked_parallel_for(IndexRange range, uint grain_size, const FuncT &func) +{ + if (range.size() == 0) { + return; + } +#ifdef WITH_TBB + tbb::parallel_for( + tbb::blocked_range<uint>(range.first(), range.one_after_last(), grain_size), + [&](const tbb::blocked_range<uint> &sub_range) { func(IndexRange(sub_range)); }); +#else + UNUSED_VARS(grain_size); + func(range); +#endif +} + +/** + * Invoke multiple functions in parallel. + */ +template<typename FuncT1, typename FuncT2> +void parallel_invoke(const FuncT1 &func1, const FuncT2 &func2) +{ +#ifdef WITH_TBB + tbb::parallel_invoke(func1, func2); +#else + func1(); + func2(); +#endif +} + +template<typename FuncT1, typename FuncT2, typename FuncT3> +void parallel_invoke(const FuncT1 &func1, const FuncT2 &func2, const FuncT3 &func3) +{ +#ifdef WITH_TBB + tbb::parallel_invoke(func1, func2, func3); +#else + func1(); + func2(); + func3(); +#endif +} + +template<typename KeyT, typename ValueT, uint N, typename FuncT> +void parallel_map_items(const MultiMap<KeyT, ValueT, N> &multi_map, const FuncT &func) +{ + ScopedVector<const KeyT *> key_vector; + ScopedVector<ArrayRef<ValueT>> values_vector; + + multi_map.foreach_item([&](const KeyT &key, ArrayRef<ValueT> values) { + key_vector.append(&key); + values_vector.append(values); + }); + + parallel_for(key_vector.index_range(), [&](uint index) { + const KeyT &key = *key_vector[index]; + ArrayRef<ValueT> values = values_vector[index]; + + func(key, values); + }); +} + +template<typename ValueT, typename FuncT> +void parallel_map_items(const StringMultiMap<ValueT> &string_multi_map, const FuncT &func) +{ + ScopedVector<StringRefNull> key_vector; + ScopedVector<ArrayRef<ValueT>> values_vector; + + string_multi_map.foreach_item([&](StringRefNull key, ArrayRef<ValueT> values) { + key_vector.append(key); + values_vector.append(values); + }); + + parallel_for(key_vector.index_range(), [&](uint index) { + StringRefNull &key = key_vector[index]; + ArrayRef<ValueT> values = values_vector[index]; + + func(key, values); + }); +} + +template<typename ValueT, typename FuncT> +void parallel_map_items(const StringMap<ValueT> &string_map, const FuncT &func) +{ + ScopedVector<StringRefNull> key_vector; + ScopedVector<const ValueT *> value_vector; + + string_map.foreach_item([&](StringRefNull key, const ValueT &value) { + key_vector.append(key); + value_vector.append(&value); + }); + + parallel_for(key_vector.index_range(), [&](uint index) { + StringRefNull key = key_vector[index]; + const ValueT &value = *value_vector[index]; + + func(key, value); + }); +} + +template<typename ValueT, typename FuncT> +void parallel_map_keys(const StringMap<ValueT> &string_map, const FuncT &func) +{ + ScopedVector<StringRefNull> key_vector; + + string_map.foreach_item( + [&](StringRefNull key, const ValueT &UNUSED(value)) { key_vector.append(key); }); + + parallel_for(key_vector.index_range(), [&](uint index) { + StringRefNull key = key_vector[index]; + func(key); + }); +} + +} // namespace BLI + +#endif /* __BLI_PARALLEL_H__ */ diff --git a/source/blender/blenlib/BLI_rand_cxx.h b/source/blender/blenlib/BLI_rand_cxx.h new file mode 100644 index 00000000000..bb21dfd8e71 --- /dev/null +++ b/source/blender/blenlib/BLI_rand_cxx.h @@ -0,0 +1,26 @@ +#ifndef __BLI_RAND_CXX_H__ +#define __BLI_RAND_CXX_H__ + +#include "BLI_utildefines.h" + +#include <iostream> + +namespace BLI { + +inline uint32_t hash_from_path_and_line(const char *path, uint32_t line) +{ + uint32_t hash = 5381; + const char *str = path; + char c = 0; + while ((c = *str++)) { + hash = hash * 37 + c; + } + hash = hash ^ ((line + 573259433) * 654188383); + return hash; +} + +} // namespace BLI + +#define BLI_RAND_PER_LINE_UINT32 BLI::hash_from_path_and_line(__FILE__, __LINE__) + +#endif /* __BLI_RAND_CXX_H__ */ diff --git a/source/blender/blenlib/BLI_resource_collector.h b/source/blender/blenlib/BLI_resource_collector.h new file mode 100644 index 00000000000..ccfa25499e2 --- /dev/null +++ b/source/blender/blenlib/BLI_resource_collector.h @@ -0,0 +1,105 @@ +#ifndef __BLI_OWNED_RESOURCES_H__ +#define __BLI_OWNED_RESOURCES_H__ + +#include "BLI_linear_allocator.h" +#include "BLI_string_ref.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" + +namespace BLI { + +class ResourceCollector : NonCopyable { + private: + struct ResourceData { + void *data; + void (*free)(void *data); + const char *name; + }; + + LinearAllocator<> m_allocator; + Vector<ResourceData> m_resources; + + public: + ResourceCollector() = default; + + ~ResourceCollector() + { + for (int i = m_resources.size() - 1; i >= 0; i--) { + ResourceData &data = m_resources[i]; + // std::cout << "FREE: " << data.name << std::endl; + data.free(data.data); + } + } + + /** + * Add another object that will be freed when this container is freed. Objects are freed in + * reverse order. + */ + template<typename T> void add(std::unique_ptr<T> resource, const char *name) + { + BLI_assert(resource.get() != nullptr); + this->add( + resource.release(), + [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + delete typed_data; + }, + name); + } + + template<typename T> void add(destruct_ptr<T> resource, const char *name) + { + BLI_assert(resource.get() != nullptr); + this->add( + resource.release(), + [](void *data) { + T *typed_data = reinterpret_cast<T *>(data); + typed_data->~T(); + }, + name); + } + + void *allocate(uint size, uint alignment) + { + return m_allocator.allocate(size, alignment); + } + + LinearAllocator<> &allocator() + { + return m_allocator; + } + + template<typename T, typename... Args> T &construct(const char *name, Args &&... args) + { + T *value = m_allocator.construct<T>(std::forward<Args>(args)...); + this->add(destruct_ptr<T>(value), name); + return *value; + } + + void add(void *userdata, void (*free)(void *), const char *name) + { + ResourceData data; + data.name = name; + data.data = userdata; + data.free = free; + m_resources.append(data); + } + + void print(StringRef name) const + { + if (m_resources.size() == 0) { + std::cout << "\"" << name << "\" has no resources.\n"; + return; + } + else { + std::cout << "Resources for \"" << name << "\":\n"; + for (const ResourceData &data : m_resources) { + std::cout << " " << data.data << ": " << data.name << '\n'; + } + } + } +}; + +} // namespace BLI + +#endif /* __BLI_OWNED_RESOURCES_H__ */ diff --git a/source/blender/blenlib/BLI_static_class_ids.h b/source/blender/blenlib/BLI_static_class_ids.h new file mode 100644 index 00000000000..d048fef64ae --- /dev/null +++ b/source/blender/blenlib/BLI_static_class_ids.h @@ -0,0 +1,28 @@ +#ifndef __BLI_STATIC_CLASS_IDS_H__ +#define __BLI_STATIC_CLASS_IDS_H__ + +#include "BLI_utildefines.h" + +namespace BLI { + +using class_id_t = uintptr_t; + +template<typename T> class_id_t get_class_id(); + +} // namespace BLI + +#define BLI_CREATE_CLASS_ID_UTIL1(class_name, id) \ + namespace BLI { \ + static char class_id_char##id = 0; \ + static class_id_t class_id##id = (class_id_t)&class_id_char##id; \ + template<> class_id_t get_class_id<class_name>() \ + { \ + return class_id##id; \ + } \ + } + +#define BLI_CREATE_CLASS_ID_UTIL2(class_name, id) BLI_CREATE_CLASS_ID_UTIL1(class_name, id) + +#define BLI_CREATE_CLASS_ID(class_name) BLI_CREATE_CLASS_ID_UTIL2(class_name, __LINE__) + +#endif /* __BLI_STATIC_CLASS_IDS_H__ */ diff --git a/source/blender/blenlib/BLI_string_map.h b/source/blender/blenlib/BLI_string_map.h index f304b140bcc..1402fdf8a03 100644 --- a/source/blender/blenlib/BLI_string_map.h +++ b/source/blender/blenlib/BLI_string_map.h @@ -140,12 +140,12 @@ template<typename T, typename Allocator = GuardedAllocator> class StringMap { return m_hashes[offset] == hash; } - bool has_exact_key(uint offset, StringRef key, const Vector<char> &chars) const + bool has_exact_key(uint offset, StringRef key, const Vector<char, 4, Allocator> &chars) const { return key == this->get_key(offset, chars); } - StringRefNull get_key(uint offset, const Vector<char> &chars) const + StringRefNull get_key(uint offset, const Vector<char, 4, Allocator> &chars) const { const char *ptr = chars.begin() + m_indices[offset]; uint length = *(uint *)ptr; @@ -165,7 +165,7 @@ template<typename T, typename Allocator = GuardedAllocator> class StringMap { using ArrayType = OpenAddressingArray<Item, 1, Allocator>; ArrayType m_array; - Vector<char> m_chars; + Vector<char, 4, Allocator> m_chars; public: StringMap() = default; diff --git a/source/blender/blenlib/BLI_string_multi_map.h b/source/blender/blenlib/BLI_string_multi_map.h new file mode 100644 index 00000000000..6ccfdcdb0ad --- /dev/null +++ b/source/blender/blenlib/BLI_string_multi_map.h @@ -0,0 +1,81 @@ +#ifndef __BLI_STRING_MULTI_MAP_H__ +#define __BLI_STRING_MULTI_MAP_H__ + +#include "BLI_string_map.h" +#include "BLI_string_ref.h" +#include "BLI_vector.h" + +namespace BLI { + +template<typename ValueT> class StringMultiMap { + private: + StringMap<Vector<ValueT>> m_map; + + public: + StringMultiMap() = default; + ~StringMultiMap() = default; + + uint key_amount() const + { + return m_map.size(); + } + + uint value_amount(StringRef key) const + { + return m_map.lookup(key).size(); + } + + bool add(StringRef key, const ValueT &value) + { + if (m_map.contains(key)) { + m_map.lookup(key).append(value); + return false; + } + else { + m_map.add_new(key, Vector<ValueT>({value})); + return true; + } + } + + void add_multiple(StringRef key, ArrayRef<ValueT> values) + { + if (m_map.contains(key)) { + m_map.lookup(key).extend(values); + } + else { + m_map.add_new(key, values); + } + } + + void add_multiple(const StringMultiMap<ValueT> &other) + { + other.foreach_item( + [&](StringRefNull key, ArrayRef<ValueT> values) { this->add_multiple(key, values); }); + } + + ArrayRef<ValueT> lookup(StringRef key) const + { + return m_map.lookup(key); + } + + ArrayRef<ValueT> lookup_default(StringRef key, + ArrayRef<ValueT> default_array = ArrayRef<ValueT>()) const + { + const Vector<ValueT> *values = m_map.lookup_ptr(key); + if (values == nullptr) { + return default_array; + } + else { + return *values; + } + } + + template<typename FuncT> void foreach_item(const FuncT &func) const + { + m_map.foreach_item([&](StringRefNull key, ArrayRef<ValueT> vector) { func(key, vector); }); + } +}; + +} // namespace BLI + +#endif /* __BLI_STRING_MULTI_MAP_H__ */ diff --git a/source/blender/blenlib/BLI_string_ref.h b/source/blender/blenlib/BLI_string_ref.h index 2389542bcea..8681d15afdb 100644 --- a/source/blender/blenlib/BLI_string_ref.h +++ b/source/blender/blenlib/BLI_string_ref.h @@ -94,12 +94,28 @@ class StringRefBase { return m_data + m_size; } - void copy_to__with_null(char *dst) const + void unsafe_copy(char *dst) const { memcpy(dst, m_data, m_size); dst[m_size] = '\0'; } + void copy(char *dst, uint dst_size) const + { + if (m_size < dst_size) { + this->unsafe_copy(dst); + } + else { + BLI_assert(false); + dst[0] = '\0'; + } + } + + template<uint N> void copy(char (&dst)[N]) + { + this->copy(dst, N); + } + /** * Returns true when the string begins with the given prefix. Otherwise false. */ diff --git a/source/blender/blenlib/BLI_timeit.h b/source/blender/blenlib/BLI_timeit.h new file mode 100644 index 00000000000..121e2036b72 --- /dev/null +++ b/source/blender/blenlib/BLI_timeit.h @@ -0,0 +1,53 @@ +#pragma once + +/* This file contains utilities to make timing of + * code segments easy. + */ + +#include "BLI_sys_types.h" +#include <chrono> +#include <iostream> + +namespace BLI { + +namespace Timers { + +using Clock = std::chrono::high_resolution_clock; +using TimePoint = Clock::time_point; +using Nanoseconds = std::chrono::nanoseconds; + +inline void print_duration(Nanoseconds duration) +{ + if (duration.count() < 100000) { + std::cout << duration.count() << " ns"; + } + else { + std::cout << duration.count() / 1000000.0 << " ms"; + } +} + +class ScopedTimer { + private: + const char *m_name; + TimePoint m_start; + + public: + ScopedTimer(const char *name = "") : m_name(name) + { + m_start = Clock::now(); + } + + ~ScopedTimer() + { + TimePoint end = Clock::now(); + Nanoseconds duration = end - m_start; + std::cout << "Timer '" << m_name << "' took "; + print_duration(duration); + std::cout << "\n"; + } +}; + +} // namespace Timers +} // namespace BLI + +#define SCOPED_TIMER(name) BLI::Timers::ScopedTimer t(name); diff --git a/source/blender/blenlib/BLI_vector_adaptor.h b/source/blender/blenlib/BLI_vector_adaptor.h new file mode 100644 index 00000000000..fe552ed6c0b --- /dev/null +++ b/source/blender/blenlib/BLI_vector_adaptor.h @@ -0,0 +1,176 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bli + * + * This vector wraps an externally provided memory buffer. At allows using any buffer as if it were + * a vector. It does not grow the array dynamically. It asserts that the amount of added elements + * does not exceed the capacity. + * + * This constraint allows a very efficient append operation, because no boundary checks have to be + * performed in release builds. + */ + +#pragma once + +#include "BLI_array_ref.h" +#include "BLI_vector_adaptor.h" + +namespace BLI { + +template<typename T> class VectorAdaptor { + private: + T *m_begin; + T *m_end; + T *m_capacity_end; + + public: + /** + * Construct an empty vector adaptor. + */ + VectorAdaptor() : m_begin(nullptr), m_end(nullptr), m_capacity_end(nullptr) + { + } + + /** + * Construct using any pointer and a capacity. + * The initial size is set to zero. + */ + VectorAdaptor(T *ptr, uint capacity, uint size = 0) + : m_begin(ptr), m_end(ptr + size), m_capacity_end(ptr + capacity) + { + } + + /** + * Construct from an array. The capacity is automatically determined + * from the length of the array. + * The initial size is set to zero. + */ + template<uint N> + VectorAdaptor(T (&array)[N]) : m_begin(array), m_end(array), m_capacity_end(array + N) + { + } + + /** + * Elements should continue to live after the adapter is destructed. + */ + ~VectorAdaptor() = default; + + void clear() + { + for (T &value : *this) { + value.~T(); + } + m_end = m_begin; + } + + /** + * Insert one element at the end of the vector. + * Asserts, when the capacity is exceeded. + */ + void append(const T &value) + { + BLI_assert(this->size() < this->capacity()); + new (m_end) T(value); + m_end += 1; + } + + void append(T &&value) + { + BLI_assert(this->size() < this->capacity()); + new (m_end) T(std::move(value)); + m_end += 1; + } + + void append_n_times(const T &value, uint n) + { + BLI_assert(this->size() < this->capacity()); + uninitialized_fill_n(m_end, n, value); + m_end += n; + } + + /** + * Insert multiple elements at the end of the vector. + * Asserts, when the capacity is exceeded. + */ + void extend(ArrayRef<T> values) + { + BLI_assert(this->size() + values.size() < this->capacity()); + std::uninitialized_copy_n(values.begin(), values.size(), m_end); + m_end += values.size(); + } + + /** + * Return the maximum size of the vector. + */ + uint capacity() const + { + return m_capacity_end - m_begin; + } + + /** + * Return the current size of the vector. + */ + uint size() const + { + return m_end - m_begin; + } + + bool is_full() const + { + return m_end == m_capacity_end; + } + + operator ArrayRef<T>() const + { + return ArrayRef<T>(m_begin, this->size()); + } + + T &operator[](uint index) + { + BLI_assert(index < this->size()); + return m_begin[index]; + } + + const T &operator[](uint index) const + { + BLI_assert(index < this->size()); + return m_begin[index]; + } + + T *begin() + { + return m_begin; + } + + T *end() + { + return m_end; + } + + const T *begin() const + { + return m_begin; + } + + const T *end() const + { + return m_end; + } +}; + +} // namespace BLI diff --git a/source/blender/blenlib/BLI_virtual_list_list_ref.h b/source/blender/blenlib/BLI_virtual_list_list_ref.h new file mode 100644 index 00000000000..ebcda7eb438 --- /dev/null +++ b/source/blender/blenlib/BLI_virtual_list_list_ref.h @@ -0,0 +1,85 @@ +#ifndef __BLI_VIRTUAL_ARRAY_LIST_REF_H__ +#define __BLI_VIRTUAL_ARRAY_LIST_REF_H__ + +#include "BLI_virtual_list_ref.h" + +namespace BLI { + +template<typename T> class VirtualListListRef { + private: + enum Category { + SingleArray, + ListOfStartPointers, + }; + + uint m_virtual_size; + Category m_category; + + union { + struct { + const T *start; + uint size; + } single_array; + struct { + const T *const *starts; + const uint *sizes; + } list_of_start_pointers; + } m_data; + + public: + VirtualListListRef() + { + m_virtual_size = 0; + m_category = ListOfStartPointers; + m_data.list_of_start_pointers.starts = nullptr; + m_data.list_of_start_pointers.sizes = nullptr; + } + + static VirtualListListRef FromSingleArray(ArrayRef<T> array, uint virtual_list_size) + { + VirtualListListRef list; + list.m_virtual_size = virtual_list_size; + list.m_category = Category::SingleArray; + list.m_data.single_array.start = array.begin(); + list.m_data.single_array.size = array.size(); + return list; + } + + static VirtualListListRef FromListOfStartPointers(ArrayRef<const T *> starts, + ArrayRef<uint> sizes) + { + assert_same_size(starts, sizes); + VirtualListListRef list; + list.m_virtual_size = starts.size(); + list.m_category = Category::ListOfStartPointers; + list.m_data.list_of_start_pointers.starts = starts.begin(); + list.m_data.list_of_start_pointers.sizes = sizes.begin(); + return list; + } + + uint size() const + { + return m_virtual_size; + } + + VirtualListRef<T> operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + + switch (m_category) { + case Category::SingleArray: + return VirtualListRef<T>::FromFullArray( + ArrayRef<T>(m_data.single_array.start, m_data.single_array.size)); + case Category::ListOfStartPointers: + return VirtualListRef<T>::FromFullArray(m_data.list_of_start_pointers.starts[index], + m_data.list_of_start_pointers.sizes[index]); + } + + BLI_assert(false); + return {}; + } +}; + +} // namespace BLI + +#endif /* __BLI_VIRTUAL_ARRAY_LIST_REF_H__ */ diff --git a/source/blender/blenlib/BLI_virtual_list_ref.h b/source/blender/blenlib/BLI_virtual_list_ref.h new file mode 100644 index 00000000000..3f9cfab1f4d --- /dev/null +++ b/source/blender/blenlib/BLI_virtual_list_ref.h @@ -0,0 +1,187 @@ +#ifndef __BLI_VIRTUAL_LIST_REF_H__ +#define __BLI_VIRTUAL_LIST_REF_H__ + +#include "BLI_array_ref.h" + +#include <climits> + +namespace BLI { + +template<typename T> class VirtualListRef { + private: + enum Category { + Single, + FullArray, + FullPointerArray, + RepeatedArray, + }; + + uint m_virtual_size; + Category m_category; + + union { + struct { + const T *data; + } single; + struct { + const T *data; + } full_array; + struct { + const T *const *data; + } full_pointer_array; + struct { + const T *data; + uint real_size; + } repeated_array; + } m_data; + + public: + VirtualListRef() + { + m_virtual_size = 0; + m_category = Category::FullArray; + m_data.single.data = nullptr; + } + + static VirtualListRef FromSingle(const T *data, uint virtual_size) + { + VirtualListRef list; + list.m_virtual_size = virtual_size; + list.m_category = Category::Single; + list.m_data.single.data = data; + return list; + } + + static VirtualListRef FromSingle_MaxSize(const T *data) + { + return VirtualListRef::FromSingle(data, UINT_MAX); + } + + static VirtualListRef FromFullArray(const T *data, uint size) + { + VirtualListRef list; + list.m_virtual_size = size; + list.m_category = Category::FullArray; + list.m_data.full_array.data = data; + return list; + } + + static VirtualListRef FromFullArray(ArrayRef<T> array) + { + return VirtualListRef::FromFullArray(array.begin(), array.size()); + } + + static VirtualListRef FromFullPointerArray(const T *const *data, uint size) + { + VirtualListRef list; + list.m_virtual_size = size; + list.m_category = Category::FullPointerArray; + list.m_data.full_pointer_array.data = data; + return list; + } + + static VirtualListRef FromFullPointerArray(ArrayRef<const T *> data) + { + return VirtualListRef::FromFullPointerArray(data.begin(), data.size()); + } + + static VirtualListRef FromRepeatedArray(const T *data, uint real_size, uint virtual_size) + { + BLI_assert(virtual_size == 0 || real_size > 0); + + VirtualListRef list; + list.m_virtual_size = virtual_size; + list.m_category = Category::RepeatedArray; + list.m_data.repeated_array.data = data; + list.m_data.repeated_array.real_size = real_size; + return list; + } + + static VirtualListRef FromRepeatedArray(ArrayRef<T> array, uint virtual_size) + { + return VirtualListRef::FromRepeatedArray(array.begin(), array.size(), virtual_size); + } + + bool all_equal(ArrayRef<uint> indices) const + { + if (indices.size() == 0) { + return true; + } + if (this->is_single_element()) { + return true; + } + + const T &first_value = (*this)[indices.first()]; + for (uint i : indices.drop_front(1)) { + if (first_value != (*this)[i]) { + return false; + } + } + return true; + } + + const T &operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case Category::Single: + return *m_data.single.data; + case Category::FullArray: + return m_data.full_array.data[index]; + case Category::FullPointerArray: + return *m_data.full_pointer_array.data[index]; + case Category::RepeatedArray: + uint real_index = index % m_data.repeated_array.real_size; + return m_data.repeated_array.data[real_index]; + } + BLI_assert(false); + return *m_data.single.data; + } + + uint size() const + { + return m_virtual_size; + } + + bool is_non_single_full_array() const + { + return m_category == Category::FullArray && m_virtual_size > 1; + } + + ArrayRef<T> as_full_array() const + { + BLI_assert(m_category == Category::FullArray); + return ArrayRef<T>(m_data.full_array.data, m_virtual_size); + } + + const T &as_single_element() const + { + BLI_assert(this->is_single_element()); + return (*this)[0]; + } + + bool is_single_element() const + { + switch (m_category) { + case Category::Single: + return true; + case Category::FullArray: + return m_virtual_size == 1; + case Category::FullPointerArray: + return m_virtual_size == 1; + case Category::RepeatedArray: + return m_data.repeated_array.real_size == 1; + } + BLI_assert(false); + return false; + } + + IndexRange index_range() const + { + return IndexRange(m_virtual_size); + } +}; + +} // namespace BLI + +#endif /* __BLI_VIRTUAL_LIST_REF_H__ */ diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index bdfe3dd4747..c0a8a80ca56 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -254,6 +254,26 @@ set(SRC BLI_winstuff.h PIL_time.h PIL_time_utildefines.h + + BLI_dot_export.h + BLI_dot_export_attribute_enums.h + intern/dot_export.cc + BLI_linear_allocator.h + BLI_multi_map.h + BLI_timeit.h + BLI_vector_adaptor.h + BLI_static_class_ids.h + BLI_index_mask.h + BLI_parallel.h + BLI_string_multi_map.h + BLI_index_to_ref_map.h + BLI_rand_cxx.h + BLI_buffer_cache.h + BLI_float3.h + BLI_float2.h + BLI_float4x4.h + BLI_color.h + BLI_linear_allocated_vector.h ) set(LIB diff --git a/source/blender/blenlib/intern/BLI_lazy_init.cc b/source/blender/blenlib/intern/BLI_lazy_init.cc new file mode 100644 index 00000000000..a47c72203af --- /dev/null +++ b/source/blender/blenlib/intern/BLI_lazy_init.cc @@ -0,0 +1,53 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <mutex> + +#include "BLI_stack_cxx.h" + +struct FreeFunc { + std::function<void()> func; + const char *name; +}; + +BLI::Stack<FreeFunc> free_functions; +std::mutex store_free_func_mutex; + +void BLI_lazy_init_free_all() +{ + while (!free_functions.is_empty()) { + FreeFunc free_object = free_functions.pop(); + free_object.func(); + } + free_functions.clear_and_make_small(); +} + +void BLI_lazy_init_list_all() +{ + for (FreeFunc &func : free_functions) { + std::cout << func.name << "\n"; + } +} + +namespace BLI { + +void lazy_init_register(std::function<void()> free_func, const char *name) +{ + std::lock_guard<std::mutex> lock(store_free_func_mutex); + free_functions.push({free_func, name}); +} + +} // namespace BLI diff --git a/source/blender/blenlib/intern/dot_export.cc b/source/blender/blenlib/intern/dot_export.cc new file mode 100644 index 00000000000..67bd564af29 --- /dev/null +++ b/source/blender/blenlib/intern/dot_export.cc @@ -0,0 +1,289 @@ +#include <iomanip> + +#include "BLI_dot_export.h" + +namespace BLI { +namespace DotExport { + +/* Graph Building + ************************************************/ + +Node &Graph::new_node(StringRef label) +{ + Node *node = new Node(*this); + m_nodes.append(std::unique_ptr<Node>(node)); + m_top_level_nodes.add_new(node); + node->set_attribute("label", label); + return *node; +} + +Cluster &Graph::new_cluster(StringRef label) +{ + Cluster *cluster = new Cluster(*this); + m_clusters.append(std::unique_ptr<Cluster>(cluster)); + m_top_level_clusters.add_new(cluster); + cluster->set_attribute("label", label); + return *cluster; +} + +UndirectedEdge &UndirectedGraph::new_edge(NodePort a, NodePort b) +{ + UndirectedEdge *edge = new UndirectedEdge(a, b); + m_edges.append(std::unique_ptr<UndirectedEdge>(edge)); + return *edge; +} + +DirectedEdge &DirectedGraph::new_edge(NodePort from, NodePort to) +{ + DirectedEdge *edge = new DirectedEdge(from, to); + m_edges.append(std::unique_ptr<DirectedEdge>(edge)); + return *edge; +} + +void Cluster::set_parent_cluster(Cluster *new_parent) +{ + if (m_parent == new_parent) { + return; + } + else if (m_parent == nullptr) { + m_graph.m_top_level_clusters.remove(this); + new_parent->m_children.add_new(this); + } + else if (new_parent == nullptr) { + m_parent->m_children.remove(this); + m_graph.m_top_level_clusters.add_new(this); + } + else { + m_parent->m_children.remove(this); + new_parent->m_children.add_new(this); + } + m_parent = new_parent; +} + +void Node::set_parent_cluster(Cluster *cluster) +{ + if (m_cluster == cluster) { + return; + } + else if (m_cluster == nullptr) { + m_graph.m_top_level_nodes.remove(this); + cluster->m_nodes.add_new(this); + } + else if (cluster == nullptr) { + m_cluster->m_nodes.remove(this); + m_graph.m_top_level_nodes.add_new(this); + } + else { + m_cluster->m_nodes.remove(this); + cluster->m_nodes.add_new(this); + } + m_cluster = cluster; +} + +/* Utility methods + **********************************************/ + +void Graph::set_random_cluster_bgcolors() +{ + for (Cluster *cluster : m_top_level_clusters) { + cluster->set_random_cluster_bgcolors(); + } +} + +void Cluster::set_random_cluster_bgcolors() +{ + float hue = rand() / (float)RAND_MAX; + float staturation = 0.3f; + float value = 0.8f; + this->set_attribute("bgcolor", color_attr_from_hsv(hue, staturation, value)); + + for (Cluster *cluster : m_children) { + cluster->set_random_cluster_bgcolors(); + } +} + +/* Dot Generation + **********************************************/ + +std::string DirectedGraph::to_dot_string() const +{ + std::stringstream ss; + ss << "digraph {\n"; + this->export__declare_nodes_and_clusters(ss); + ss << "\n"; + + for (auto &edge : m_edges) { + edge->export__as_edge_statement(ss); + ss << "\n"; + } + + ss << "}\n"; + return ss.str(); +} + +std::string UndirectedGraph::to_dot_string() const +{ + std::stringstream ss; + ss << "graph {\n"; + this->export__declare_nodes_and_clusters(ss); + ss << "\n"; + + for (auto &edge : m_edges) { + edge->export__as_edge_statement(ss); + ss << "\n"; + } + + ss << "}\n"; + return ss.str(); +} + +void Graph::export__declare_nodes_and_clusters(std::stringstream &ss) const +{ + ss << "graph "; + m_attributes.export__as_bracket_list(ss); + ss << "\n\n"; + + for (Node *node : m_top_level_nodes) { + node->export__as_declaration(ss); + } + + for (Cluster *cluster : m_top_level_clusters) { + cluster->export__declare_nodes_and_clusters(ss); + } +} + +void Cluster::export__declare_nodes_and_clusters(std::stringstream &ss) const +{ + ss << "subgraph cluster_" << (void *)this << " {\n"; + + ss << "graph "; + m_attributes.export__as_bracket_list(ss); + ss << "\n\n"; + + for (Node *node : m_nodes) { + node->export__as_declaration(ss); + } + + for (Cluster *cluster : m_children) { + cluster->export__declare_nodes_and_clusters(ss); + } + + ss << "}\n"; +} + +void DirectedEdge::export__as_edge_statement(std::stringstream &ss) const +{ + m_a.to_dot_string(ss); + ss << " -> "; + m_b.to_dot_string(ss); + ss << " "; + m_attributes.export__as_bracket_list(ss); +} + +void UndirectedEdge::export__as_edge_statement(std::stringstream &ss) const +{ + m_a.to_dot_string(ss); + ss << " -- "; + m_b.to_dot_string(ss); + ss << " "; + m_attributes.export__as_bracket_list(ss); +} + +void AttributeList::export__as_bracket_list(std::stringstream &ss) const +{ + ss << "["; + for (auto item : m_attributes.items()) { + if (StringRef(item.value).startswith("<")) { + /* Don't draw the quotes, this is an html-like value. */ + ss << item.key << "=" << item.value << ", "; + } + else { + ss << item.key << "=\"" << item.value << "\", "; + } + } + ss << "]"; +} + +void Node::export__as_id(std::stringstream &ss) const +{ + ss << '"' << (const void *)this << '"'; +} + +void Node::export__as_declaration(std::stringstream &ss) const +{ + this->export__as_id(ss); + ss << " "; + m_attributes.export__as_bracket_list(ss); + ss << "\n"; +} + +void NodePort::to_dot_string(std::stringstream &ss) const +{ + m_node->export__as_id(ss); + if (m_port_name.has_value()) { + ss << ":" << m_port_name.value(); + } +} + +std::string color_attr_from_hsv(float h, float s, float v) +{ + std::stringstream ss; + ss << std::setprecision(4) << h << ' ' << s << ' ' << v; + return ss.str(); +} + +NodeWithSocketsRef::NodeWithSocketsRef(Node &node, + StringRef name, + ArrayRef<std::string> input_names, + ArrayRef<std::string> output_names) + : m_node(&node) +{ + std::stringstream ss; + + ss << "<<table border=\"0\" cellspacing=\"3\">"; + + /* Header */ + ss << "<tr><td colspan=\"3\" align=\"center\"><b>"; + ss << ((name.size() == 0) ? "No Name" : name); + ss << "</b></td></tr>"; + + /* Sockets */ + uint socket_max_amount = std::max(input_names.size(), output_names.size()); + for (uint i = 0; i < socket_max_amount; i++) { + ss << "<tr>"; + if (i < input_names.size()) { + StringRef name = input_names[i]; + if (name.size() == 0) { + name = "No Name"; + } + ss << "<td align=\"left\" port=\"in" << i << "\">"; + ss << name; + ss << "</td>"; + } + else { + ss << "<td></td>"; + } + ss << "<td></td>"; + if (i < output_names.size()) { + StringRef name = output_names[i]; + if (name.size() == 0) { + name = "No Name"; + } + ss << "<td align=\"right\" port=\"out" << i << "\">"; + ss << name; + ss << "</td>"; + } + else { + ss << "<td></td>"; + } + ss << "</tr>"; + } + + ss << "</table>>"; + + m_node->set_attribute("label", ss.str()); + m_node->set_shape(Attr_shape::Rectangle); +} + +} // namespace DotExport +} // namespace BLI diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 9ff5ab3a648..53c22b7ce9b 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -5898,6 +5898,30 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb, Object *ob) } } } + else if (md->type == eModifierType_BParticles) { + BParticlesModifierData *bpmd = (BParticlesModifierData *)md; + bpmd->cached_frames = newdataadr(fd, bpmd->cached_frames); + + for (uint frame_index = 0; frame_index < bpmd->num_cached_frames; frame_index++) { + BParticlesFrameCache *cached_frame = &bpmd->cached_frames[frame_index]; + cached_frame->particle_types = newdataadr(fd, cached_frame->particle_types); + + for (uint type = 0; type < cached_frame->num_particle_types; type++) { + BParticlesTypeCache *cached_type = &cached_frame->particle_types[type]; + cached_type->attributes_float = newdataadr(fd, cached_type->attributes_float); + + for (uint i = 0; i < cached_type->num_attributes_float; i++) { + BParticlesAttributeCacheFloat *cached_attribute = &cached_type->attributes_float[i]; + cached_attribute->values = newdataadr(fd, cached_attribute->values); + + if (fd->flags & FD_FLAGS_SWITCH_ENDIAN) { + BLI_endian_switch_float_array(cached_attribute->values, + cached_type->particle_amount); + } + } + } + } + } else if (md->type == eModifierType_Bevel) { BevelModifierData *bmd = (BevelModifierData *)md; bmd->custom_profile = newdataadr(fd, bmd->custom_profile); diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index ee5e6f4610a..8bc6a90befb 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -1799,6 +1799,37 @@ static void write_modifiers(WriteData *wd, ListBase *modbase) } } } + else if (md->type == eModifierType_BParticles) { + BParticlesModifierData *bpmd = (BParticlesModifierData *)md; + writestruct(wd, DATA, BParticlesFrameCache, bpmd->num_cached_frames, bpmd->cached_frames); + + for (uint frame_index = 0; frame_index < bpmd->num_cached_frames; frame_index++) { + BParticlesFrameCache *cached_frame = &bpmd->cached_frames[frame_index]; + writestruct(wd, + DATA, + BParticlesTypeCache, + cached_frame->num_particle_types, + cached_frame->particle_types); + + for (uint type = 0; type < cached_frame->num_particle_types; type++) { + BParticlesTypeCache *cached_type = &cached_frame->particle_types[type]; + writestruct(wd, + DATA, + BParticlesAttributeCacheFloat, + cached_type->num_attributes_float, + cached_type->attributes_float); + + for (uint i = 0; i < cached_type->num_attributes_float; i++) { + BParticlesAttributeCacheFloat *attribute_cache = &cached_type->attributes_float[i]; + writedata(wd, + DATA, + sizeof(float) * attribute_cache->floats_per_particle * + cached_type->particle_amount, + attribute_cache->values); + } + } + } + } else if (md->type == eModifierType_Bevel) { BevelModifierData *bmd = (BevelModifierData *)md; if (bmd->custom_profile) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 89def9e0bdc..ccd8a7545ae 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -1521,6 +1521,7 @@ void DepsgraphRelationBuilder::build_driver_variables(ID *id, FCurve *fcu) /* Special handling for directly-named bones. */ if ((dtar->flag & DTAR_FLAG_STRUCT_REF) && (object && object->type == OB_ARMATURE) && (dtar->pchan_name[0])) { + bPoseChannel *target_pchan = BKE_pose_channel_find_name(object->pose, dtar->pchan_name); if (target_pchan == nullptr) { continue; diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index c2c25e47908..6cd67418571 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -32,6 +32,7 @@ set(INC ../../modifiers ../../python ../../shader_fx + ../../simulations ../../render/extern/include ../../windowmanager ../../../../intern/glew-mx diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index d8ba270073e..f18b223a713 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -181,6 +181,7 @@ void OBJECT_OT_skin_radii_equalize(struct wmOperatorType *ot); void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot); void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot); void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot); +void OBJECT_OT_bparticles_clear_cache(struct wmOperatorType *ot); /* object_gpencil_modifiers.c */ void OBJECT_OT_gpencil_modifier_add(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index a24f3ba2269..04dbc60b7b7 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -91,6 +91,8 @@ #include "WM_api.h" #include "WM_types.h" +#include "BParticles.h" + #include "object_intern.h" static void modifier_skin_customdata_delete(struct Object *ob); @@ -2679,3 +2681,54 @@ void OBJECT_OT_surfacedeform_bind(wmOperatorType *ot) } /** \} */ + +/************************ BParticles ***********************/ + +static bool bparticles_clear_cache_poll(bContext *C) +{ + return edit_modifier_poll_generic(C, &RNA_BParticlesModifier, 0, true); +} + +static int bparticles_clear_cache_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_active_context(C); + BParticlesModifierData *bpmd = (BParticlesModifierData *)edit_modifier_property_get( + op, ob, eModifierType_BParticles); + + if (bpmd == NULL) { + return OPERATOR_CANCELLED; + } + + BParticles_modifier_free_cache(bpmd); + + DEG_id_tag_update(&ob->id, ID_RECALC_ALL); + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); + return OPERATOR_FINISHED; +} + +static int bparticles_clear_cache_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + if (edit_modifier_invoke_properties(C, op)) { + return bparticles_clear_cache_exec(C, op); + } + else { + return OPERATOR_CANCELLED; + } +} + +void OBJECT_OT_bparticles_clear_cache(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clear BParticles Cache"; + ot->description = "Clear the cache for the modifier"; + ot->idname = "OBJECT_OT_bparticles_clear_cache"; + + /* api callbacks */ + ot->poll = bparticles_clear_cache_poll; + ot->invoke = bparticles_clear_cache_invoke; + ot->exec = bparticles_clear_cache_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + edit_modifier_properties(ot); +} diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index fef046169a7..c68aa1bc7d2 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -268,6 +268,8 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_voxel_size_edit); WM_operatortype_append(OBJECT_OT_quadriflow_remesh); + + WM_operatortype_append(OBJECT_OT_bparticles_clear_cache); } void ED_operatormacros_object(void) diff --git a/source/blender/editors/space_graph/graph_buttons.c b/source/blender/editors/space_graph/graph_buttons.c index 8c931a0c4a3..9a3a942a9eb 100644 --- a/source/blender/editors/space_graph/graph_buttons.c +++ b/source/blender/editors/space_graph/graph_buttons.c @@ -897,6 +897,16 @@ static void graph_panel_driverVar__transChan(uiLayout *layout, ID *id, DriverVar uiItemR(sub, &dtar_ptr, "transform_space", 0, IFACE_("Space"), ICON_NONE); } +/* settings for 'function' driver variable type */ +static void graph_panel_driverVar_function(uiLayout *layout, ID *id, DriverVar *dvar) +{ + DriverTarget *dtar = &dvar->targets[0]; + PointerRNA dtar_ptr; + RNA_pointer_create(id, &RNA_DriverTarget, dtar, &dtar_ptr); + + uiItemR(layout, &dtar_ptr, "id", 0, IFACE_("Function"), ICON_NONE); +} + /* ----------------------------------------------------------------- */ /* property driven by the driver - duplicates Active FCurve, but useful for clarity */ @@ -1168,6 +1178,9 @@ static void graph_draw_driver_settings_panel(uiLayout *layout, case DVAR_TYPE_TRANSFORM_CHAN: /* transform channel */ graph_panel_driverVar__transChan(box, id, dvar); break; + case DVAR_TYPE_FUNCTION: /* function */ + graph_panel_driverVar_function(box, id, dvar); + break; } /* 3) value of variable */ diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index ce83cfc3c97..9030bdb1d58 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -2194,6 +2194,10 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) /* Default */ case eModifierType_None: case eModifierType_ShapeKey: + case eModifierType_FunctionDeform: + case eModifierType_FunctionPoints: + case eModifierType_BParticles: + case eModifierType_BParticlesOutput: case NUM_MODIFIER_TYPES: data.icon = ICON_DOT; diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt new file mode 100644 index 00000000000..4e73a5c0238 --- /dev/null +++ b/source/blender/functions/CMakeLists.txt @@ -0,0 +1,94 @@ +set(INC + . + ../blenlib + ../makesdna + ../makesrna + ../blenkernel + ../depsgraph + ../windowmanager + ../imbuf + ../../../intern/guardedalloc +) + +set(INC_SYS + ${LLVM_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) + +if(WITH_PYTHON) + add_definitions(-DWITH_PYTHON) + list(APPEND INC + ../python + ) +endif() + +set(SRC + intern/multi_functions/constants.cc + intern/multi_functions/global_functions.cc + intern/multi_functions/lists.cc + intern/multi_functions/mixed.cc + intern/multi_functions/network.cc + intern/multi_functions/particles.cc + intern/multi_functions/sampling_util.cc + intern/multi_functions/surface_hook.cc + intern/multi_functions/vectorize.cc + intern/node_tree_multi_function_network/builder.cc + intern/node_tree_multi_function_network/generate.cc + intern/node_tree_multi_function_network/mappings_nodes.cc + intern/node_tree_multi_function_network/mappings_sockets.cc + intern/node_tree_multi_function_network/mappings.cc + intern/attributes_ref.cc + intern/cpp_type.cc + intern/cpp_types.cc + intern/generic_array_ref.cc + intern/generic_tuple.cc + intern/initialize.cc + intern/multi_function_common_contexts.cc + intern/multi_function_context.cc + intern/multi_function_network_optimization.cc + intern/multi_function_network.cc + intern/multi_function.cc + intern/node_tree.cc + + FN_attributes_ref.h + FN_cpp_type.h + FN_generic_array_ref.h + FN_generic_tuple.h + FN_generic_vector_array.h + FN_generic_virtual_list_list_ref.h + FN_generic_virtual_list_ref.h + FN_initialize.h + FN_multi_function_common_contexts.h + FN_multi_function_context.h + FN_multi_function_data_type.h + FN_multi_function_dependencies.h + FN_multi_function_network_optimization.h + FN_multi_function_network.h + FN_multi_function_param_type.h + FN_multi_function.h + FN_multi_functions.h + FN_node_tree_multi_function_network_generation.h + FN_node_tree_multi_function_network.h + FN_node_tree.h + + intern/multi_functions/constants.h + intern/multi_functions/customizable.h + intern/multi_functions/global_functions.h + intern/multi_functions/lists.h + intern/multi_functions/mixed.h + intern/multi_functions/network.h + intern/multi_functions/particles.h + intern/multi_functions/sampling_util.h + intern/multi_functions/surface_hook.h + intern/multi_functions/util.h + intern/multi_functions/vectorize.h + intern/node_tree_multi_function_network/builder.h + intern/node_tree_multi_function_network/mappings.h + intern/cpp_types.h +) + +set(LIB + bf_blenlib +) + +blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/functions/FN_attributes_ref.h b/source/blender/functions/FN_attributes_ref.h new file mode 100644 index 00000000000..812b0e26a6a --- /dev/null +++ b/source/blender/functions/FN_attributes_ref.h @@ -0,0 +1,574 @@ +#ifndef __FN_ATTRIBUTES_REF_H__ +#define __FN_ATTRIBUTES_REF_H__ + +#include "FN_cpp_type.h" +#include "FN_generic_array_ref.h" + +#include "BLI_array_cxx.h" +#include "BLI_linear_allocator.h" +#include "BLI_optional.h" +#include "BLI_string_map.h" +#include "BLI_vector.h" +#include "BLI_vector_set.h" + +namespace FN { + +using BLI::Array; +using BLI::ArrayRef; +using BLI::IndexRange; +using BLI::LinearAllocator; +using BLI::MutableArrayRef; +using BLI::Optional; +using BLI::StringMap; +using BLI::Vector; +using BLI::VectorSet; + +class AttributesInfo; + +class AttributesInfoBuilder : BLI::NonCopyable, BLI::NonMovable { + private: + LinearAllocator<> m_allocator; + VectorSet<std::string> m_names; + Vector<const CPPType *> m_types; + Vector<void *> m_defaults; + + public: + AttributesInfoBuilder() = default; + ~AttributesInfoBuilder(); + + template<typename T> void add(StringRef name, const T &default_value) + { + this->add(name, CPP_TYPE<T>(), (const void *)&default_value); + } + + void add(StringRef name, const CPPType &type, const void *default_value = nullptr) + { + if (m_names.add(name)) { + m_types.append(&type); + void *dst = m_allocator.allocate(type.size(), type.alignment()); + if (default_value == nullptr) { + type.copy_to_uninitialized(type.default_value(), dst); + } + else { + type.copy_to_uninitialized(default_value, dst); + } + m_defaults.append(dst); + } + else { + BLI_assert(m_types[m_names.index(name)] == &type); + } + } + + bool name_and_type_collide_with_existing(StringRef name, const CPPType &type) const + { + int index = m_names.index_try(name); + if (index == -1) { + return false; + } + + const CPPType *existing_type = m_types[index]; + if (*existing_type == type) { + return false; + } + + return true; + } + + uint size() const + { + return m_names.size(); + } + + ArrayRef<std::string> names() const + { + return m_names; + } + + ArrayRef<const CPPType *> types() const + { + return m_types; + } + + ArrayRef<const void *> defaults() const + { + return ArrayRef<const void *>(m_defaults.begin(), m_defaults.size()); + } + + void add(const AttributesInfoBuilder &other); + void add(const AttributesInfo &other); +}; + +class AttributesInfo : BLI::NonCopyable, BLI::NonMovable { + private: + LinearAllocator<> m_allocator; + StringMap<int> m_index_by_name; + Vector<std::string> m_name_by_index; + Vector<const CPPType *> m_type_by_index; + Vector<void *> m_defaults; + + public: + AttributesInfo() = default; + AttributesInfo(const AttributesInfoBuilder &builder); + ~AttributesInfo(); + + uint size() const + { + return m_name_by_index.size(); + } + + StringRefNull name_of(uint index) const + { + return m_name_by_index[index]; + } + + uint index_of(StringRef name) const + { + return m_index_by_name.lookup(name); + } + + const void *default_of(uint index) const + { + return m_defaults[index]; + } + + const void *default_of(StringRef name) const + { + return this->default_of(this->index_of(name)); + } + + bool has_attribute(StringRef name, const CPPType &type) const + { + return this->try_index_of(name, type) >= 0; + } + + int try_index_of(StringRef name, const CPPType &type) const + { + int index = this->try_index_of(name); + if (index == -1) { + return -1; + } + else if (this->type_of((uint)index) == type) { + return index; + } + else { + return -1; + } + } + + template<typename T> int try_index_of(StringRef name) const + { + return this->try_index_of(name, CPP_TYPE<T>()); + } + + int try_index_of(StringRef name) const + { + return m_index_by_name.lookup_default(name, -1); + } + + const CPPType &type_of(uint index) const + { + return *m_type_by_index[index]; + } + + const CPPType &type_of(StringRef name) const + { + return this->type_of(this->index_of(name)); + } + + ArrayRef<const CPPType *> types() const + { + return m_type_by_index; + } + + IndexRange indices() const + { + return IndexRange(this->size()); + } +}; + +class MutableAttributesRef { + private: + const AttributesInfo *m_info; + ArrayRef<void *> m_buffers; + IndexRange m_range; + + friend class AttributesRef; + + public: + MutableAttributesRef(const AttributesInfo &info, ArrayRef<void *> buffers, uint size) + : MutableAttributesRef(info, buffers, IndexRange(size)) + { + } + + MutableAttributesRef(const AttributesInfo &info, ArrayRef<void *> buffers, IndexRange range) + : m_info(&info), m_buffers(buffers), m_range(range) + { + } + + uint size() const + { + return m_range.size(); + } + + const AttributesInfo &info() const + { + return *m_info; + } + + GenericMutableArrayRef get(uint index) const + { + const CPPType &type = m_info->type_of(index); + void *ptr = POINTER_OFFSET(m_buffers[index], type.size() * m_range.start()); + return GenericMutableArrayRef(m_info->type_of(index), ptr, m_range.size()); + } + + GenericMutableArrayRef get(StringRef name) const + { + return this->get(m_info->index_of(name)); + } + + template<typename T> MutableArrayRef<T> get(uint index) const + { + BLI_assert(m_info->type_of(index) == CPP_TYPE<T>()); + return MutableArrayRef<T>((T *)m_buffers[index] + m_range.start(), m_range.size()); + } + + template<typename T> MutableArrayRef<T> get(StringRef name) const + { + return this->get<T>(m_info->index_of(name)); + } + + Optional<GenericMutableArrayRef> try_get(StringRef name, const CPPType &type) const + { + int index = m_info->try_index_of(name, type); + if (index == -1) { + return {}; + } + else { + return this->get((uint)index); + } + } + + template<typename T> Optional<MutableArrayRef<T>> try_get(StringRef name) + { + int index = m_info->try_index_of<T>(name); + if (index == -1) { + return {}; + } + else { + return this->get<T>((uint)index); + } + } + + MutableAttributesRef slice(IndexRange range) const + { + return this->slice(range.start(), range.size()); + } + + MutableAttributesRef slice(uint start, uint size) const + { + return MutableAttributesRef(*m_info, m_buffers, m_range.slice(start, size)); + } + + MutableAttributesRef take_front(uint n) const + { + return this->slice(0, n); + } + + ArrayRef<void *> internal_buffers() + { + return m_buffers; + } + + IndexRange internal_range() + { + return m_range; + } + + void destruct_and_reorder(IndexMask indices_to_destruct); + + static void RelocateUninitialized(MutableAttributesRef from, MutableAttributesRef to); +}; + +class AttributesRef { + private: + mutable MutableAttributesRef m_ref; + + public: + AttributesRef(const AttributesInfo &info, ArrayRef<void *> buffers, uint size) + : m_ref(info, buffers, IndexRange(size)) + { + } + + AttributesRef(const AttributesInfo &info, ArrayRef<void *> buffers, IndexRange range) + : m_ref(info, buffers, range) + { + } + + AttributesRef(MutableAttributesRef ref) : m_ref(ref) + { + } + + uint size() const + { + return m_ref.size(); + } + + const AttributesInfo &info() const + { + return m_ref.info(); + } + + GenericArrayRef get(uint index) const + { + return m_ref.get(index); + } + + GenericArrayRef get(StringRef name) const + { + return m_ref.get(name); + } + + template<typename T> ArrayRef<T> get(uint index) const + { + return m_ref.get<T>(index); + } + + template<typename T> ArrayRef<T> get(StringRef name) const + { + return m_ref.get<T>(name); + } + + Optional<GenericArrayRef> try_get(StringRef name, const CPPType &type) const + { + Optional<GenericMutableArrayRef> array = m_ref.try_get(name, type); + if (array.has_value()) { + return GenericArrayRef(array.value()); + } + else { + return {}; + } + } + + template<typename T> Optional<ArrayRef<T>> try_get(StringRef name) + { + return m_ref.try_get<T>(name); + } + + AttributesRef slice(IndexRange range) const + { + return m_ref.slice(range); + } + + AttributesRef slice(uint start, uint size) const + { + return m_ref.slice(start, size); + } + + AttributesRef take_front(uint n) const + { + return this->slice(0, n); + } +}; + +class AttributesRefGroup { + private: + const AttributesInfo *m_info; + Vector<ArrayRef<void *>> m_buffers; + Vector<IndexRange> m_ranges; + uint m_total_size; + + public: + AttributesRefGroup(const AttributesInfo &info, + Vector<ArrayRef<void *>> buffers, + Vector<IndexRange> ranges); + + const AttributesInfo &info() const + { + return *m_info; + } + + template<typename T> void set(uint index, ArrayRef<T> data) + { + BLI_assert(data.size() == m_total_size); + BLI_assert(m_info->type_of(index) == CPP_TYPE<T>()); + + uint offset = 0; + for (MutableAttributesRef attributes : *this) { + MutableArrayRef<T> array = attributes.get<T>(index); + array.copy_from(data.slice(offset, array.size())); + offset += array.size(); + } + } + + template<typename T> void set(StringRef name, ArrayRef<T> data) + { + this->set(m_info->index_of(name), data); + } + + void set(uint index, GenericArrayRef data) + { + BLI_assert(data.size() == m_total_size); + BLI_assert(m_info->type_of(index) == data.type()); + + uint offset = 0; + for (MutableAttributesRef attributes : *this) { + GenericMutableArrayRef array = attributes.get(index); + array.type().copy_to_initialized_n(data[offset], array[0], attributes.size()); + offset += attributes.size(); + } + } + + void set(StringRef name, GenericArrayRef data) + { + this->set(m_info->index_of(name), data); + } + + template<typename T> void set_repeated(uint index, ArrayRef<T> data) + { + BLI_assert(m_total_size == 0 || data.size() > 0); + BLI_assert(m_info->type_of(index) == CPP_TYPE<T>()); + + uint src_index = 0; + for (AttributesRef attributes : *this) { + MutableArrayRef<T> array = attributes.get<T>(index); + + for (uint i = 0; i < attributes.size(); i++) { + array[i] = data[src_index]; + src_index++; + if (src_index == data.size()) { + src_index = 0; + } + } + } + } + + template<typename T> void set_repeated(StringRef name, ArrayRef<T> data) + { + this->set_repeated(m_info->index_of(name), data); + } + + void set_repeated(uint index, GenericArrayRef data) + { + BLI_assert(m_total_size == 0 || data.size() > 0); + BLI_assert(m_info->type_of(index) == data.type()); + + uint src_index = 0; + for (MutableAttributesRef attributes : *this) { + GenericMutableArrayRef array = attributes.get(index); + + for (uint i = 0; i < attributes.size(); i++) { + array.copy_in__initialized(i, data[src_index]); + src_index++; + if (src_index == data.size()) { + src_index = 0; + } + } + } + } + + void set_repeated(StringRef name, GenericArrayRef data) + { + this->set_repeated(m_info->index_of(name), data); + } + + template<typename T> void fill(uint index, const T &value) + { + BLI_assert(m_info->type_of(index) == CPP_TYPE<T>()); + + for (MutableAttributesRef attributes : *this) { + MutableArrayRef<T> array = attributes.get<T>(index); + array.fill(value); + } + } + + template<typename T> void fill(StringRef name, const T &value) + { + this->fill(m_info->index_of(name), value); + } + + void fill(uint index, const CPPType &type, const void *value) + { + BLI_assert(m_info->type_of(index) == type); + UNUSED_VARS_NDEBUG(type); + + for (MutableAttributesRef attributes : *this) { + GenericMutableArrayRef array = attributes.get(index); + array.fill__initialized(value); + } + } + + void fill(StringRef name, const CPPType &type, const void *value) + { + this->fill(m_info->index_of(name), type, value); + } + + uint total_size() const + { + return m_total_size; + } + + class Iterator { + private: + AttributesRefGroup *m_group; + uint m_current; + + public: + Iterator(AttributesRefGroup &group, uint current) : m_group(&group), m_current(current) + { + } + + Iterator &operator++() + { + m_current++; + return *this; + } + + MutableAttributesRef operator*() + { + return MutableAttributesRef( + *m_group->m_info, m_group->m_buffers[m_current], m_group->m_ranges[m_current]); + } + + friend bool operator!=(const Iterator &a, const Iterator &b) + { + BLI_assert(a.m_group == b.m_group); + return a.m_current != b.m_current; + } + }; + + Iterator begin() + { + return Iterator(*this, 0); + } + + Iterator end() + { + return Iterator(*this, m_buffers.size()); + } +}; + +class AttributesInfoDiff { + private: + const AttributesInfo *m_old_info; + const AttributesInfo *m_new_info; + Array<int> m_old_to_new_mapping; + Array<int> m_new_to_old_mapping; + + public: + AttributesInfoDiff(const AttributesInfo &old_info, const AttributesInfo &new_info); + + void update(uint capacity, + uint used_size, + ArrayRef<void *> old_buffers, + MutableArrayRef<void *> new_buffers) const; + + uint new_buffer_amount() const + { + return m_new_info->size(); + } +}; + +} // namespace FN + +#endif /* __FN_ATTRIBUTES_REF_H__ */ diff --git a/source/blender/functions/FN_cpp_type.h b/source/blender/functions/FN_cpp_type.h new file mode 100644 index 00000000000..35dafa8f632 --- /dev/null +++ b/source/blender/functions/FN_cpp_type.h @@ -0,0 +1,379 @@ +#ifndef __FN_CPP_TYPE_H__ +#define __FN_CPP_TYPE_H__ + +#include "BLI_index_mask.h" +#include "BLI_string_ref.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::IndexMask; +using BLI::StringRef; +using BLI::StringRefNull; + +class CPPType { + public: + using ConstructDefaultF = void (*)(void *ptr); + using ConstructDefaultNF = void (*)(void *ptr, uint n); + using ConstructDefaultIndicesF = void (*)(void *ptr, IndexMask index_mask); + + using DestructF = void (*)(void *ptr); + using DestructNF = void (*)(void *ptr, uint n); + using DestructIndicesF = void (*)(void *ptr, IndexMask index_mask); + + using CopyToInitializedF = void (*)(const void *src, void *dst); + using CopyToInitializedNF = void (*)(const void *src, void *dst, uint n); + using CopyToInitializedIndicesF = void (*)(const void *src, void *dst, IndexMask index_mask); + + using CopyToUninitializedF = void (*)(const void *src, void *dst); + using CopyToUninitializedNF = void (*)(const void *src, void *dst, uint n); + using CopyToUninitializedIndicesF = void (*)(const void *src, void *dst, IndexMask index_mask); + + using RelocateToInitializedF = void (*)(void *src, void *dst); + using RelocateToInitializedNF = void (*)(void *src, void *dst, uint n); + using RelocateToInitializedIndicesF = void (*)(void *src, void *dst, IndexMask index_mask); + + using RelocateToUninitializedF = void (*)(void *src, void *dst); + using RelocateToUninitializedNF = void (*)(void *src, void *dst, uint n); + using RelocateToUninitializedIndicesF = void (*)(void *src, void *dst, IndexMask index_mask); + + using FillInitializedF = void (*)(const void *value, void *dst, uint n); + using FillInitializedIndicesF = void (*)(const void *value, void *dst, IndexMask index_mask); + + using FillUninitializedF = void (*)(const void *value, void *dst, uint n); + using FillUninitializedIndicesF = void (*)(const void *value, void *dst, IndexMask index_mask); + + CPPType(std::string name, + uint size, + uint alignment, + bool trivially_destructible, + ConstructDefaultF construct_default, + ConstructDefaultNF construct_default_n, + ConstructDefaultIndicesF construct_default_indices, + DestructF destruct, + DestructNF destruct_n, + DestructIndicesF destruct_indices, + CopyToInitializedF copy_to_initialized, + CopyToInitializedNF copy_to_initialized_n, + CopyToInitializedIndicesF copy_to_initialized_indices, + CopyToUninitializedF copy_to_uninitialized, + CopyToUninitializedNF copy_to_uninitialized_n, + CopyToUninitializedIndicesF copy_to_uninitialized_indices, + RelocateToInitializedF relocate_to_initialized, + RelocateToInitializedNF relocate_to_initialized_n, + RelocateToInitializedIndicesF relocate_to_initialized_indices, + RelocateToUninitializedF relocate_to_uninitialized, + RelocateToUninitializedNF relocate_to_uninitialized_n, + RelocateToUninitializedIndicesF relocate_to_uninitialized_indices, + FillInitializedF fill_initialized, + FillInitializedIndicesF fill_initialized_indices, + FillUninitializedF fill_uninitialized, + FillUninitializedIndicesF fill_uninitialized_indices, + uint32_t type_hash, + const void *default_value) + : m_size(size), + m_alignment(alignment), + m_trivially_destructible(trivially_destructible), + m_construct_default(construct_default), + m_construct_default_n(construct_default_n), + m_construct_default_indices(construct_default_indices), + m_destruct(destruct), + m_destruct_n(destruct_n), + m_destruct_indices(destruct_indices), + m_copy_to_initialized(copy_to_initialized), + m_copy_to_initialized_n(copy_to_initialized_n), + m_copy_to_initialized_indices(copy_to_initialized_indices), + m_copy_to_uninitialized(copy_to_uninitialized), + m_copy_to_uninitialized_n(copy_to_uninitialized_n), + m_copy_to_uninitialized_indices(copy_to_uninitialized_indices), + m_relocate_to_initialized(relocate_to_initialized), + m_relocate_to_initialized_n(relocate_to_initialized_n), + m_relocate_to_initialized_indices(relocate_to_initialized_indices), + m_relocate_to_uninitialized(relocate_to_uninitialized), + m_relocate_to_uninitialized_n(relocate_to_uninitialized_n), + m_relocate_to_uninitialized_indices(relocate_to_uninitialized_indices), + m_fill_initialized(fill_initialized), + m_fill_initialized_indices(fill_initialized_indices), + m_fill_uninitialized(fill_uninitialized), + m_fill_uninitialized_indices(fill_uninitialized_indices), + m_type_hash(type_hash), + m_default_value(default_value), + m_name(name) + { + BLI_assert(is_power_of_2_i(m_alignment)); + m_alignment_mask = m_alignment - 1; + } + + virtual ~CPPType(); + + StringRefNull name() const + { + return m_name; + } + + uint size() const + { + return m_size; + } + + uint alignment() const + { + return m_alignment; + } + bool trivially_destructible() const + { + return m_trivially_destructible; + } + + bool pointer_has_valid_alignment(const void *ptr) const + { + return (POINTER_AS_UINT(ptr) & m_alignment_mask) == 0; + } + + void construct_default(void *ptr) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_construct_default(ptr); + } + + void construct_default_n(void *ptr, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_construct_default_n(ptr, n); + } + + void construct_default_indices(void *ptr, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_construct_default_indices(ptr, index_mask); + } + + void destruct(void *ptr) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_destruct(ptr); + } + + void destruct_n(void *ptr, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_destruct_n(ptr, n); + } + + void destruct_indices(void *ptr, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(ptr)); + + m_destruct_indices(ptr, index_mask); + } + + DestructF destruct_cb() const + { + return m_destruct; + } + + void copy_to_initialized(const void *src, void *dst) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_initialized(src, dst); + } + + void copy_to_initialized_n(const void *src, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_initialized_n(src, dst, n); + } + + void copy_to_initialized_indices(const void *src, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_initialized_indices(src, dst, index_mask); + } + + void copy_to_uninitialized(const void *src, void *dst) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_uninitialized(src, dst); + } + + void copy_to_uninitialized_n(const void *src, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_uninitialized_n(src, dst, n); + } + + void copy_to_uninitialized_indices(const void *src, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_copy_to_uninitialized_indices(src, dst, index_mask); + } + + void relocate_to_initialized(void *src, void *dst) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_initialized(src, dst); + } + + void relocate_to_initialized_n(void *src, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_initialized_n(src, dst, n); + } + + void relocate_to_initialized_indices(void *src, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_initialized_indices(src, dst, index_mask); + } + + void relocate_to_uninitialized(void *src, void *dst) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_uninitialized(src, dst); + } + + void relocate_to_uninitialized_n(void *src, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_uninitialized_n(src, dst, n); + } + + void relocate_to_uninitialized_indices(void *src, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_relocate_to_uninitialized_indices(src, dst, index_mask); + } + + void fill_initialized(const void *value, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_fill_initialized(value, dst, n); + } + + void fill_initialized_indices(const void *value, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_fill_initialized_indices(value, dst, index_mask); + } + + void fill_uninitialized(const void *value, void *dst, uint n) const + { + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_fill_uninitialized(value, dst, n); + } + + void fill_uninitialized_indices(const void *value, void *dst, IndexMask index_mask) const + { + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); + + m_fill_uninitialized_indices(value, dst, index_mask); + } + + const void *default_value() const + { + return m_default_value; + } + + uint32_t type_hash() const + { + return m_type_hash; + } + + friend bool operator==(const CPPType &a, const CPPType &b) + { + return &a == &b; + } + + friend bool operator!=(const CPPType &a, const CPPType &b) + { + return !(&a == &b); + } + + private: + uint m_size; + uint m_alignment; + uint m_alignment_mask; + bool m_trivially_destructible; + + ConstructDefaultF m_construct_default; + ConstructDefaultNF m_construct_default_n; + ConstructDefaultIndicesF m_construct_default_indices; + + DestructF m_destruct; + DestructNF m_destruct_n; + DestructIndicesF m_destruct_indices; + + CopyToInitializedF m_copy_to_initialized; + CopyToInitializedNF m_copy_to_initialized_n; + CopyToInitializedIndicesF m_copy_to_initialized_indices; + + CopyToUninitializedF m_copy_to_uninitialized; + CopyToUninitializedNF m_copy_to_uninitialized_n; + CopyToUninitializedIndicesF m_copy_to_uninitialized_indices; + + RelocateToInitializedF m_relocate_to_initialized; + RelocateToInitializedNF m_relocate_to_initialized_n; + RelocateToInitializedIndicesF m_relocate_to_initialized_indices; + + RelocateToUninitializedF m_relocate_to_uninitialized; + RelocateToUninitializedNF m_relocate_to_uninitialized_n; + RelocateToUninitializedIndicesF m_relocate_to_uninitialized_indices; + + FillInitializedF m_fill_initialized; + FillInitializedIndicesF m_fill_initialized_indices; + + FillUninitializedF m_fill_uninitialized; + FillUninitializedIndicesF m_fill_uninitialized_indices; + + uint32_t m_type_hash; + const void *m_default_value; + std::string m_name; +}; + +template<typename T> const CPPType &CPP_TYPE(); +extern const CPPType &CPPType_float; +extern const CPPType &CPPType_float3; +extern const CPPType &CPPType_int32; +extern const CPPType &CPPType_string; + +} // namespace FN + +#endif /* __FN_CPP_TYPE_H__ */ diff --git a/source/blender/functions/FN_generic_array_ref.h b/source/blender/functions/FN_generic_array_ref.h new file mode 100644 index 00000000000..4e46fa4eafb --- /dev/null +++ b/source/blender/functions/FN_generic_array_ref.h @@ -0,0 +1,171 @@ +#ifndef __FN_GENERIC_ARRAY_REF_H__ +#define __FN_GENERIC_ARRAY_REF_H__ + +#include "FN_cpp_type.h" + +#include "BLI_array_ref.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::MutableArrayRef; + +class GenericArrayRef { + private: + const CPPType *m_type; + const void *m_buffer; + uint m_size; + + public: + GenericArrayRef(const CPPType &type) : GenericArrayRef(type, nullptr, 0) + { + } + + GenericArrayRef(const CPPType &type, const void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_size(size) + { + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + template<typename T> + GenericArrayRef(ArrayRef<T> array) + : GenericArrayRef(CPP_TYPE<T>(), (const void *)array.begin(), array.size()) + { + } + + const CPPType &type() const + { + return *m_type; + } + + uint size() const + { + return m_size; + } + + const void *buffer() const + { + return m_buffer; + } + + const void *operator[](uint index) const + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> ArrayRef<T> as_typed_ref() const + { + BLI_assert(CPP_TYPE<T>() == *m_type); + return ArrayRef<T>((const T *)m_buffer, m_size); + } +}; + +class GenericMutableArrayRef { + private: + const CPPType *m_type; + void *m_buffer; + uint m_size; + + public: + GenericMutableArrayRef(const CPPType &type) : GenericMutableArrayRef(type, nullptr, 0) + { + } + + GenericMutableArrayRef(const CPPType &type, void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_size(size) + { + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + template<typename T> + GenericMutableArrayRef(MutableArrayRef<T> array) + : GenericMutableArrayRef(CPP_TYPE<T>(), (void *)array.begin(), array.size()) + { + } + + operator GenericArrayRef() const + { + return GenericArrayRef(*m_type, m_buffer, m_size); + } + + void destruct_all() + { + m_type->destruct_n(m_buffer, m_size); + } + + void destruct_indices(IndexMask indices) + { + m_type->destruct_indices(m_buffer, indices); + } + + GenericMutableArrayRef slice(uint start, uint size) + { + BLI_assert(start + size <= m_size); + return GenericMutableArrayRef(*m_type, POINTER_OFFSET(m_buffer, start * m_type->size()), size); + } + + const CPPType &type() const + { + return *m_type; + } + + void *buffer() + { + return m_buffer; + } + + uint size() const + { + return m_size; + } + + void default_initialize(IndexMask indices) + { + m_type->construct_default_indices(m_buffer, indices); + } + + void fill__uninitialized(const void *value) + { + m_type->fill_uninitialized(value, m_buffer, m_size); + } + + void fill__initialized(const void *value) + { + m_type->fill_initialized(value, m_buffer, m_size); + } + + void copy_in__uninitialized(uint index, const void *src) + { + BLI_assert(index < m_size); + void *dst = POINTER_OFFSET(m_buffer, m_type->size() * index); + m_type->copy_to_uninitialized(src, dst); + } + + void copy_in__initialized(uint index, const void *src) + { + BLI_assert(index < m_size); + void *dst = POINTER_OFFSET(m_buffer, m_type->size() * index); + m_type->copy_to_initialized(src, dst); + } + + static void RelocateUninitialized(GenericMutableArrayRef from, GenericMutableArrayRef to); + + void *operator[](uint index) + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> MutableArrayRef<T> as_typed_ref() + { + BLI_assert(CPP_TYPE<T>() == *m_type); + return MutableArrayRef<T>((T *)m_buffer, m_size); + } +}; + +} // namespace FN + +#endif /* __FN_GENERIC_ARRAY_REF_H__ */ diff --git a/source/blender/functions/FN_generic_tuple.h b/source/blender/functions/FN_generic_tuple.h new file mode 100644 index 00000000000..eba36b3dfb7 --- /dev/null +++ b/source/blender/functions/FN_generic_tuple.h @@ -0,0 +1,477 @@ +#ifndef __FN_GENERIC_TUPLE_H__ +#define __FN_GENERIC_TUPLE_H__ + +#include "BLI_vector.h" + +#include "FN_cpp_type.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::Vector; + +class GenericTupleInfo : BLI::NonCopyable, BLI::NonMovable { + private: + Vector<uint> m_offsets; + Vector<const CPPType *> m_types; + uint m_alignment; + uintptr_t m_do_align_mask; + uint m_size__data; + uint m_size__data_and_init; + uint m_size__alignable_data_and_init; + bool m_all_trivially_destructible; + + public: + GenericTupleInfo(Vector<const CPPType *> types); + + ArrayRef<const CPPType *> types() const + { + return m_types; + } + + const CPPType &type_at_index(uint index) const + { + return *m_types[index]; + } + + uint offset_of_index(uint index) const + { + return m_offsets[index]; + } + + uint size_of_data() const + { + return m_size__data; + } + + uint size_of_init() const + { + return m_size__data_and_init - m_size__data; + } + + uint size_of_data_and_init() const + { + return m_size__data_and_init; + } + + uint size_of_alignable_data_and_init() const + { + return m_size__alignable_data_and_init; + } + + void *align_data_buffer(void *ptr) const + { + uintptr_t ptr_i = (uintptr_t)ptr; + uintptr_t aligned_ptr_i = ptr_i & m_do_align_mask; + void *aligned_ptr = (void *)aligned_ptr_i; + return aligned_ptr; + } + + uint size() const + { + return m_types.size(); + } + + uint alignment() const + { + return m_alignment; + } + + bool all_trivially_destructible() const + { + return m_all_trivially_destructible; + } + + template<typename T> bool element_has_type(uint index) const + { + return CPP_TYPE<T>() == *m_types[index]; + } +}; + +class GenericTupleRef { + private: + GenericTupleInfo *m_info; + void *m_data; + bool *m_init; + + GenericTupleRef(GenericTupleInfo &info, void *data, bool *init) + : m_info(&info), m_data(data), m_init(init) + { + BLI_assert(m_info != nullptr); + BLI_assert(m_data != nullptr); + BLI_assert(m_init != nullptr); + BLI_assert(POINTER_AS_UINT(data) % m_info->alignment() == 0); + } + + public: + static GenericTupleRef FromPreparedBuffers(GenericTupleInfo &info, void *data, bool *init) + { + return GenericTupleRef(info, data, init); + } + + static GenericTupleRef FromAlignableBuffer(GenericTupleInfo &info, void *alignable_buffer) + { + void *data = info.align_data_buffer(alignable_buffer); + bool *init = (bool *)POINTER_OFFSET(data, info.size_of_data()); + return GenericTupleRef(info, data, init); + } + + static GenericTupleRef FromAlignedBuffer(GenericTupleInfo &info, void *aligned_buffer) + { + BLI_assert(info.align_data_buffer(aligned_buffer) == aligned_buffer); + void *data = aligned_buffer; + bool *init = (bool *)POINTER_OFFSET(data, info.size_of_data()); + return GenericTupleRef(info, data, init); + } + + ~GenericTupleRef() = default; + + template<typename T> void copy_in(uint index, const T &value) + { + BLI_assert(index < m_info->size()); + BLI_assert(m_info->element_has_type<T>(index)); + + T *dst = (T *)this->element_ptr(index); + if (std::is_trivially_copyable<T>::value) { + std::memcpy(dst, &value, sizeof(T)); + } + else { + if (m_init[index]) { + *dst = value; + } + else { + new (dst) T(value); + } + } + } + + void copy_in__dynamic(uint index, void *src) + { + BLI_assert(index < m_info->size()); + BLI_assert(src != nullptr); + + void *dst = this->element_ptr(index); + const CPPType &type = m_info->type_at_index(index); + + if (m_init[index]) { + type.copy_to_initialized(src, dst); + } + else { + type.copy_to_uninitialized(src, dst); + m_init[index] = true; + } + } + + template<typename T> void move_in(uint index, T &value) + { + BLI_assert(index < m_info->size()); + BLI_assert(m_info->element_has_type<T>(index)); + + T *dst = (T *)this->element_ptr(index); + + if (m_init[index]) { + *dst = std::move(value); + } + else { + new (dst) T(std::move(value)); + m_init[index] = true; + } + } + + void relocate_in__dynamic(uint index, void *src) + { + BLI_assert(index < m_info->size()); + BLI_assert(src != nullptr); + + void *dst = this->element_ptr(index); + const CPPType &type = m_info->type_at_index(index); + + if (m_init[index]) { + type.relocate_to_initialized(src, dst); + } + else { + type.relocate_to_uninitialized(src, dst); + m_init[index] = true; + } + } + + template<typename T> void set(uint index, const T &value) + { + BLI_STATIC_ASSERT(std::is_trivially_copyable<T>::value, + "can only be used with trivially copyable types"); + this->copy_in<T>(index, value); + } + + template<typename T> T copy_out(uint index) const + { + BLI_assert(index < m_info->size()); + BLI_assert(m_info->element_has_type<T>(index)); + BLI_assert(m_init[index]); + + const T *src = (const T *)this->element_ptr(index); + return *src; + } + + template<typename T> T relocate_out(uint index) + { + BLI_assert(index < m_info->size()); + BLI_assert(m_info->element_has_type<T>(index)); + BLI_assert(m_init[index]); + + T *stored_value_ptr = (T *)this->element_ptr(index); + T tmp = std::move(*stored_value_ptr); + stored_value_ptr->~T(); + m_init[index] = false; + + return tmp; + } + + void relocate_to_initialized__dynamic(uint index, void *dst) + { + BLI_assert(index < m_info->size()); + BLI_assert(m_init[index]); + BLI_assert(dst != nullptr); + + void *src = this->element_ptr(index); + const CPPType &type = m_info->type_at_index(index); + + type.relocate_to_initialized(src, dst); + m_init[index] = false; + } + + void relocate_to_uninitialized__dynamic(uint index, void *dst) + { + BLI_assert(index < m_info->size()); + BLI_assert(m_init[index]); + BLI_assert(dst != nullptr); + + void *src = this->element_ptr(index); + const CPPType &type = m_info->type_at_index(index); + + type.relocate_to_uninitialized(src, dst); + m_init[index] = false; + } + + template<typename T> T get(uint index) const + { + BLI_STATIC_ASSERT(std::is_trivially_copyable<T>::value, + "can only be used with trivially copyable types"); + return this->copy_out<T>(index); + } + + template<typename T> T CPP_TYPE(uint index) const + { + BLI_STATIC_ASSERT(std::is_trivial<T>::value, "can only be used with trivial types"); + return this->copy_out<T>(index); + } + + static void CopyElement(const GenericTupleRef &from, + uint from_index, + GenericTupleRef &to, + uint to_index) + { + BLI_assert(from.m_init[from_index]); + BLI_assert(&from.m_info->type_at_index(from_index) == &to.m_info->type_at_index(to_index)); + + void *src = from.element_ptr(from_index); + void *dst = to.element_ptr(to_index); + const CPPType &type = from.m_info->type_at_index(from_index); + + if (to.m_init[to_index]) { + type.copy_to_initialized(src, dst); + } + else { + type.copy_to_uninitialized(src, dst); + to.m_init[to_index] = true; + } + } + + static void RelocateElement(GenericTupleRef &from, + uint from_index, + GenericTupleRef &to, + uint to_index) + { + BLI_assert(from.m_init[from_index]); + BLI_assert(&from.m_info->type_at_index(from_index) == &to.m_info->type_at_index(to_index)); + + void *src = from.element_ptr(from_index); + void *dst = to.element_ptr(to_index); + const CPPType &type = from.m_info->type_at_index(from_index); + + if (to.m_init[to_index]) { + type.relocate_to_initialized(src, dst); + } + else { + type.relocate_to_uninitialized(src, dst); + to.m_init[to_index] = true; + } + from.m_init[from_index] = false; + } + + bool all_initialized() const + { + for (uint i = 0; i < m_info->size(); i++) { + if (!m_init[i]) { + return false; + } + } + return true; + } + + void set_all_initialized() + { + for (uint i = 0; i < m_info->size(); i++) { + m_init[i] = true; + } + } + + bool all_uninitialized() const + { + for (uint i = 0; i < m_info->size(); i++) { + if (m_init[i]) { + return false; + } + } + return true; + } + + void set_all_uninitialized() + { + for (uint i = 0; i < m_info->size(); i++) { + m_init[i] = false; + } + } + + void destruct_all() + { + if (!m_info->all_trivially_destructible()) { + uint size = m_info->size(); + for (uint i = 0; i < size; i++) { + if (m_init[i]) { + m_info->type_at_index(i).destruct(this->element_ptr(i)); + } + } + } + this->set_all_uninitialized(); + } + + uint size() const + { + return m_info->size(); + } + + GenericTupleInfo &info() + { + return *m_info; + } + + void *element_ptr(uint index) const + { + uint offset = m_info->offset_of_index(index); + void *ptr = POINTER_OFFSET(m_data, offset); + + BLI_assert(m_info->type_at_index(index).pointer_has_valid_alignment(ptr)); + return ptr; + } +}; + +class GenericDestructingTuple : BLI::NonCopyable, BLI::NonMovable { + private: + GenericTupleRef m_tuple; + + public: + GenericDestructingTuple(GenericTupleInfo &info, void *alignable_buffer) + : m_tuple(GenericTupleRef::FromAlignableBuffer(info, alignable_buffer)) + { + } + + ~GenericDestructingTuple() + { + m_tuple.destruct_all(); + } + + operator GenericTupleRef &() + { + return m_tuple; + } + + GenericTupleRef *operator->() + { + return &m_tuple; + } +}; + +class GenericTupleNameProvider { + public: + virtual StringRefNull get_element_name(uint index) const = 0; +}; + +class NamedGenericTupleRef { + private: + GenericTupleRef m_tuple; + const GenericTupleNameProvider *m_name_provider; + + public: + NamedGenericTupleRef(GenericTupleRef tuple, const GenericTupleNameProvider &name_provider) + : m_tuple(tuple), m_name_provider(&name_provider) + { + } + + void assert_name_is_correct(uint index, StringRef expected_name) const + { +#ifdef DEBUG + StringRef real_name = m_name_provider->get_element_name(index); + BLI_assert(expected_name == real_name); +#endif + UNUSED_VARS_NDEBUG(expected_name); + UNUSED_VARS_NDEBUG(index); + } + + template<typename T> T relocate_out(uint index, StringRef expected_name) + { + this->assert_name_is_correct(index, expected_name); + return m_tuple.relocate_out<T>(index); + } + + template<typename T> T get(uint index, StringRef expected_name) + { + this->assert_name_is_correct(index, expected_name); + return m_tuple.get<T>(index); + } + + template<typename T> void move_in(uint index, StringRef expected_name, T &value) + { + this->assert_name_is_correct(index, expected_name); + m_tuple.move_in(index, value); + } + + template<typename T> void set(uint index, StringRef expected_name, T &value) + { + this->assert_name_is_correct(index, expected_name); + m_tuple.set<T>(index, value); + } +}; + +class CustomGenericTupleNameProvider final : public GenericTupleNameProvider { + private: + Vector<std::string> m_names; + + public: + CustomGenericTupleNameProvider(Vector<std::string> names) : m_names(std::move(names)) + { + } + + StringRefNull get_element_name(uint index) const override + { + return m_names[index]; + } +}; + +} // namespace FN + +#define FN_TUPLE_STACK_ALLOC(NAME, INFO_EXPR) \ + FN::GenericTupleInfo &NAME##_info = (INFO_EXPR); \ + void *NAME##_buffer = alloca(NAME##_info.size_of_alignable_data_and_init()); \ + FN::GenericDestructingTuple NAME(NAME##_info, NAME##_buffer) + +#endif /* __FN_GENERIC_TUPLE_H__ */ diff --git a/source/blender/functions/FN_generic_vector_array.h b/source/blender/functions/FN_generic_vector_array.h new file mode 100644 index 00000000000..8661321b4e6 --- /dev/null +++ b/source/blender/functions/FN_generic_vector_array.h @@ -0,0 +1,245 @@ +#ifndef __FN_GENERIC_MULTI_VECTOR_H__ +#define __FN_GENERIC_MULTI_VECTOR_H__ + +#include "FN_cpp_type.h" +#include "FN_generic_array_ref.h" +#include "FN_generic_virtual_list_list_ref.h" + +#include "BLI_array_ref.h" +#include "BLI_index_range.h" +#include "BLI_linear_allocator.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::IndexRange; +using BLI::LinearAllocator; +using BLI::MutableArrayRef; + +class GenericVectorArray : BLI::NonCopyable, BLI::NonMovable { + private: + BLI::GuardedAllocator m_slices_allocator; + LinearAllocator<> m_elements_allocator; + const CPPType &m_type; + void **m_starts; + uint *m_lengths; + uint *m_capacities; + uint m_array_size; + uint m_element_size; + + public: + GenericVectorArray() = delete; + + GenericVectorArray(const CPPType &type, uint array_size) + : m_type(type), m_array_size(array_size), m_element_size(type.size()) + { + uint byte_size__starts = sizeof(void *) * array_size; + m_starts = (void **)m_slices_allocator.allocate(byte_size__starts, __func__); + memset((void *)m_starts, 0, byte_size__starts); + + uint byte_size__lengths = sizeof(uint) * array_size; + m_lengths = (uint *)m_slices_allocator.allocate(byte_size__lengths, __func__); + memset((void *)m_lengths, 0, byte_size__lengths); + + uint byte_size__capacities = sizeof(uint) * array_size; + m_capacities = (uint *)m_slices_allocator.allocate(byte_size__capacities, __func__); + memset((void *)m_capacities, 0, byte_size__capacities); + } + + ~GenericVectorArray() + { + this->destruct_all_elements(); + m_slices_allocator.deallocate((void *)m_starts); + m_slices_allocator.deallocate((void *)m_lengths); + m_slices_allocator.deallocate((void *)m_capacities); + } + + operator GenericVirtualListListRef() const + { + return GenericVirtualListListRef::FromFullArrayList(m_type, m_starts, m_lengths, m_array_size); + } + + uint size() const + { + return m_array_size; + } + + const CPPType &type() const + { + return m_type; + } + + const void *const *starts() const + { + return m_starts; + } + + const uint *lengths() const + { + return m_lengths; + } + + void append_single__copy(uint index, const void *src) + { + uint old_length = m_lengths[index]; + if (old_length == m_capacities[index]) { + this->grow_single(index, old_length + 1); + } + + void *dst = POINTER_OFFSET(m_starts[index], m_element_size * old_length); + m_type.copy_to_uninitialized(src, dst); + m_lengths[index]++; + } + + void extend_single__copy(uint index, const GenericVirtualListRef &values) + { + uint extend_length = values.size(); + uint old_length = m_lengths[index]; + uint new_length = old_length + extend_length; + + if (new_length > m_capacities[index]) { + this->grow_single(index, new_length); + } + + void *start = POINTER_OFFSET(m_starts[index], old_length * m_element_size); + + if (values.is_single_element()) { + const void *value = values.as_single_element(); + m_type.fill_uninitialized(value, start, extend_length); + } + else if (values.is_non_single_full_array()) { + GenericArrayRef array = values.as_full_array(); + m_type.copy_to_uninitialized_n(array.buffer(), start, extend_length); + } + else { + for (uint i = 0; i < extend_length; i++) { + void *dst = POINTER_OFFSET(start, m_element_size * i); + m_type.copy_to_uninitialized(values[i], dst); + } + } + + m_lengths[index] = new_length; + } + + void extend_multiple__copy(IndexMask indices, const GenericVirtualListListRef &values) + { + for (uint i : indices) { + this->extend_single__copy(i, values[i]); + } + } + + GenericMutableArrayRef allocate_single(uint index, uint size) + { + if (m_lengths[index] + size > m_capacities[index]) { + this->grow_single(index, m_lengths[index] + size); + } + void *allocation_start = POINTER_OFFSET(m_starts[index], m_element_size * m_lengths[index]); + m_lengths[index] += size; + return GenericMutableArrayRef(m_type, allocation_start, size); + } + + GenericArrayRef operator[](uint index) const + { + BLI_assert(index < m_array_size); + return GenericArrayRef(m_type, m_starts[index], m_lengths[index]); + } + + template<typename T> class TypedRef { + private: + const GenericVectorArray *m_data; + + public: + TypedRef(const GenericVectorArray &data) : m_data(&data) + { + } + + ArrayRef<T> operator[](uint index) const + { + return ArrayRef<T>((const T *)m_data->m_starts[index], m_data->m_lengths[index]); + } + }; + + template<typename T> class MutableTypedRef { + private: + GenericVectorArray *m_data; + + public: + MutableTypedRef(GenericVectorArray &data) : m_data(&data) + { + } + + operator TypedRef<T>() const + { + return TypedRef<T>(*m_data); + } + + MutableArrayRef<T> operator[](uint index) const + { + return MutableArrayRef<T>((T *)m_data->m_starts[index], m_data->m_lengths[index]); + } + + void append_single(uint index, const T &value) + { + m_data->append_single__copy(index, (const void *)&value); + } + + void extend_single(uint index, ArrayRef<T> values) + { + m_data->extend_single__copy(index, GenericVirtualListRef::FromFullArray(values)); + } + + MutableArrayRef<T> allocate_and_default_construct(uint index, uint amount) + { + GenericMutableArrayRef array = m_data->allocate_single(index, amount); + m_data->type().construct_default_n(array.buffer(), amount); + return array.as_typed_ref<T>(); + } + + MutableArrayRef<T> allocate(uint index, uint amount) + { + GenericMutableArrayRef array = m_data->allocate_single(index, amount); + return array.as_typed_ref<T>(); + } + }; + + template<typename T> const TypedRef<T> as_typed_ref() const + { + BLI_assert(CPP_TYPE<T>() == m_type); + return TypedRef<T>(*this); + } + + template<typename T> MutableTypedRef<T> as_mutable_typed_ref() + { + BLI_assert(CPP_TYPE<T>() == m_type); + return MutableTypedRef<T>(*this); + } + + private: + void grow_single(uint index, uint min_capacity) + { + BLI_assert(m_capacities[index] < min_capacity); + min_capacity = power_of_2_max_u(min_capacity); + void *new_buffer = m_elements_allocator.allocate(m_element_size * min_capacity, + m_type.alignment()); + + m_type.relocate_to_uninitialized_n(m_starts[index], new_buffer, m_lengths[index]); + + m_starts[index] = new_buffer; + m_capacities[index] = min_capacity; + } + + void destruct_all_elements() + { + if (m_type.trivially_destructible()) { + return; + } + + for (uint index = 0; index < m_array_size; index++) { + m_type.destruct_n(m_starts[index], m_lengths[index]); + } + } +}; + +}; // namespace FN + +#endif /* __FN_GENERIC_MULTI_VECTOR_H__ */ diff --git a/source/blender/functions/FN_generic_virtual_list_list_ref.h b/source/blender/functions/FN_generic_virtual_list_list_ref.h new file mode 100644 index 00000000000..e09df56eba1 --- /dev/null +++ b/source/blender/functions/FN_generic_virtual_list_list_ref.h @@ -0,0 +1,194 @@ +#ifndef __FN_GENERIC_VIRTUAL_LIST_LIST_REF_H__ +#define __FN_GENERIC_VIRTUAL_LIST_LIST_REF_H__ + +#include "BLI_virtual_list_list_ref.h" + +#include "FN_generic_virtual_list_ref.h" + +namespace FN { + +using BLI::VirtualListListRef; + +class GenericVirtualListListRef { + private: + enum Category { + SingleArray, + FullArrayList, + }; + + const CPPType *m_type; + uint m_virtual_list_size; + Category m_category; + + union { + struct { + const void *data; + uint real_array_size; + } single_array; + struct { + const void *const *starts; + const uint *real_array_sizes; + } full_array_list; + } m_data; + + GenericVirtualListListRef() = default; + + public: + static GenericVirtualListListRef FromSingleArray(const CPPType &type, + const void *buffer, + uint real_array_size, + uint virtual_list_size) + { + GenericVirtualListListRef list; + list.m_type = &type; + list.m_virtual_list_size = virtual_list_size; + list.m_category = Category::SingleArray; + list.m_data.single_array.data = buffer; + list.m_data.single_array.real_array_size = real_array_size; + return list; + } + + static GenericVirtualListListRef FromFullArrayList(const CPPType &type, + const void *const *starts, + const uint *real_array_sizes, + uint list_size) + { + GenericVirtualListListRef list; + list.m_type = &type; + list.m_virtual_list_size = list_size; + list.m_category = Category::FullArrayList; + list.m_data.full_array_list.starts = starts; + list.m_data.full_array_list.real_array_sizes = real_array_sizes; + return list; + } + + static GenericVirtualListListRef FromFullArrayList(const CPPType &type, + ArrayRef<const void *> starts, + ArrayRef<uint> array_sizes) + { + BLI::assert_same_size(starts, array_sizes); + return GenericVirtualListListRef::FromFullArrayList( + type, starts.begin(), array_sizes.begin(), starts.size()); + } + + uint size() const + { + return m_virtual_list_size; + } + + uint sublist_size(uint index) const + { + BLI_assert(index < m_virtual_list_size); + switch (m_category) { + case Category::SingleArray: + return m_data.single_array.real_array_size; + case Category::FullArrayList: + return m_data.full_array_list.real_array_sizes[index]; + } + BLI_assert(false); + return 0; + } + + const CPPType &type() const + { + return *m_type; + } + + bool is_single_list() const + { + switch (m_category) { + case Category::SingleArray: + return true; + case Category::FullArrayList: + return m_virtual_list_size == 1; + } + BLI_assert(false); + return false; + } + + GenericVirtualListRef operator[](uint index) const + { + BLI_assert(index < m_virtual_list_size); + + switch (m_category) { + case Category::SingleArray: + return GenericVirtualListRef::FromFullArray( + *m_type, m_data.single_array.data, m_data.single_array.real_array_size); + case Category::FullArrayList: + return GenericVirtualListRef::FromFullArray( + *m_type, + m_data.full_array_list.starts[index], + m_data.full_array_list.real_array_sizes[index]); + } + + BLI_assert(false); + return GenericVirtualListRef{*m_type}; + } + + template<typename T> VirtualListListRef<T> as_typed_ref() const + { + BLI_assert(CPP_TYPE<T>() == *m_type); + + switch (m_category) { + case Category::SingleArray: + return VirtualListListRef<T>::FromSingleArray( + ArrayRef<T>((const T *)m_data.single_array.data, m_data.single_array.real_array_size), + m_virtual_list_size); + case Category::FullArrayList: + return VirtualListListRef<T>::FromListOfStartPointers( + ArrayRef<const T *>((const T **)m_data.full_array_list.starts, m_virtual_list_size), + ArrayRef<uint>(m_data.full_array_list.real_array_sizes, m_virtual_list_size)); + } + + BLI_assert(false); + return {}; + } + + GenericVirtualListRef repeated_sublist(uint index, uint new_virtual_size) const + { + BLI_assert(index < m_virtual_list_size); + + switch (m_category) { + case Category::SingleArray: + return GenericVirtualListRef::FromRepeatedArray(*m_type, + m_data.single_array.data, + m_data.single_array.real_array_size, + new_virtual_size); + case Category::FullArrayList: + return GenericVirtualListRef::FromRepeatedArray( + *m_type, + m_data.full_array_list.starts[index], + m_data.full_array_list.real_array_sizes[index], + new_virtual_size); + } + + BLI_assert(false); + return {*m_type}; + } + + GenericVirtualListListRef extended_single_list(uint new_virtual_size) const + { + BLI_assert(this->is_single_list()); + + switch (m_category) { + case Category::SingleArray: + return GenericVirtualListListRef::FromSingleArray(*m_type, + m_data.single_array.data, + m_data.single_array.real_array_size, + new_virtual_size); + case Category::FullArrayList: + return GenericVirtualListListRef::FromSingleArray( + *m_type, + m_data.full_array_list.starts[0], + m_data.full_array_list.real_array_sizes[0], + new_virtual_size); + } + + BLI_assert(false); + return {}; + } +}; + +} // namespace FN + +#endif /* __FN_GENERIC_VIRTUAL_LIST_LIST_REF_H__ */ diff --git a/source/blender/functions/FN_generic_virtual_list_ref.h b/source/blender/functions/FN_generic_virtual_list_ref.h new file mode 100644 index 00000000000..6544e221a2d --- /dev/null +++ b/source/blender/functions/FN_generic_virtual_list_ref.h @@ -0,0 +1,238 @@ +#ifndef __FN_GENERIC_VIRTUAL_LIST_REF_H__ +#define __FN_GENERIC_VIRTUAL_LIST_REF_H__ + +#include "FN_cpp_type.h" +#include "FN_generic_array_ref.h" + +#include "BLI_virtual_list_ref.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::VirtualListRef; + +class GenericVirtualListRef { + private: + enum Category { + Single, + FullArray, + FullPointerArray, + RepeatedArray, + }; + + const CPPType *m_type; + uint m_virtual_size; + Category m_category; + + union { + struct { + const void *data; + } single; + struct { + const void *data; + } full_array; + struct { + const void *const *data; + } full_pointer_array; + struct { + const void *data; + uint real_size; + } repeated_array; + } m_data; + + GenericVirtualListRef() = default; + + public: + GenericVirtualListRef(const CPPType &type) + { + m_virtual_size = 0; + m_type = &type; + m_category = Category::FullArray; + m_data.full_array.data = nullptr; + } + + GenericVirtualListRef(GenericArrayRef array) + { + m_virtual_size = array.size(); + m_type = &array.type(); + m_category = Category::FullArray; + m_data.full_array.data = array.buffer(); + } + + GenericVirtualListRef(GenericMutableArrayRef array) + : GenericVirtualListRef(GenericArrayRef(array)) + { + } + + static GenericVirtualListRef FromSingle(const CPPType &type, + const void *buffer, + uint virtual_size) + { + GenericVirtualListRef list; + list.m_virtual_size = virtual_size; + list.m_type = &type; + list.m_category = Category::Single; + list.m_data.single.data = buffer; + return list; + } + + static GenericVirtualListRef FromFullArray(const CPPType &type, const void *buffer, uint size) + { + GenericVirtualListRef list; + list.m_virtual_size = size; + list.m_type = &type; + list.m_category = Category::FullArray; + list.m_data.full_array.data = buffer; + return list; + } + + template<typename T> static GenericVirtualListRef FromFullArray(ArrayRef<T> array) + { + return GenericVirtualListRef::FromFullArray( + CPP_TYPE<T>(), (const void *)array.begin(), array.size()); + } + + static GenericVirtualListRef FromFullPointerArray(const CPPType &type, + const void *const *buffer, + uint size) + { + GenericVirtualListRef list; + list.m_virtual_size = size; + list.m_type = &type; + list.m_category = Category::FullPointerArray; + list.m_data.full_pointer_array.data = buffer; + return list; + } + + static GenericVirtualListRef FromRepeatedArray(const CPPType &type, + const void *buffer, + uint real_size, + uint virtual_size) + { + if (real_size < virtual_size) { + GenericVirtualListRef list; + list.m_virtual_size = virtual_size; + list.m_type = &type; + list.m_category = Category::RepeatedArray; + list.m_data.repeated_array.data = buffer; + list.m_data.repeated_array.real_size = real_size; + return list; + } + else { + return GenericVirtualListRef::FromFullArray(type, buffer, virtual_size); + } + } + + bool is_single_element() const + { + switch (m_category) { + case Category::Single: + return true; + case Category::FullArray: + return m_virtual_size == 1; + case Category::FullPointerArray: + return m_virtual_size == 1; + case Category::RepeatedArray: + return m_data.repeated_array.real_size == 1; + } + BLI_assert(false); + return false; + } + + const void *as_single_element() const + { + BLI_assert(this->is_single_element()); + return (*this)[0]; + } + + bool is_non_single_full_array() const + { + return m_category == Category::FullArray && m_virtual_size > 1; + } + + GenericArrayRef as_full_array() const + { + BLI_assert(m_category == Category::FullArray); + return GenericArrayRef(*m_type, m_data.full_array.data, m_virtual_size); + } + + uint size() const + { + return m_virtual_size; + } + + const CPPType &type() const + { + return *m_type; + } + + const void *operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + + switch (m_category) { + case Category::Single: + return m_data.single.data; + case Category::FullArray: + return POINTER_OFFSET(m_data.full_array.data, index * m_type->size()); + case Category::FullPointerArray: + return m_data.full_pointer_array.data[index]; + case Category::RepeatedArray: + uint real_index = index % m_data.repeated_array.real_size; + return POINTER_OFFSET(m_data.repeated_array.data, real_index * m_type->size()); + } + + BLI_assert(false); + return m_data.single.data; + } + + template<typename T> VirtualListRef<T> as_typed_ref() const + { + BLI_assert(CPP_TYPE<T>() == *m_type); + + switch (m_category) { + case Category::Single: + return VirtualListRef<T>::FromSingle((const T *)m_data.single.data, m_virtual_size); + case Category::FullArray: + return VirtualListRef<T>::FromFullArray((const T *)m_data.full_array.data, m_virtual_size); + case Category::FullPointerArray: + return VirtualListRef<T>::FromFullPointerArray( + (const T *const *)m_data.full_pointer_array.data, m_virtual_size); + case Category::RepeatedArray: + return VirtualListRef<T>::FromRepeatedArray((const T *)m_data.repeated_array.data, + m_data.repeated_array.real_size, + m_virtual_size); + } + + BLI_assert(false); + return {}; + } + + GenericVirtualListRef repeated_element(uint index, uint new_virtual_size) const + { + return GenericVirtualListRef::FromSingle(*m_type, (*this)[index], new_virtual_size); + } + + void materialize_to_uninitialized(IndexMask index_mask, GenericMutableArrayRef r_array) + { + BLI_assert(this->size() >= index_mask.min_array_size()); + BLI_assert(r_array.size() >= index_mask.min_array_size()); + + if (this->is_single_element()) { + m_type->fill_uninitialized_indices(this->as_single_element(), r_array.buffer(), index_mask); + } + else if (this->is_non_single_full_array()) { + m_type->copy_to_uninitialized_indices( + this->as_full_array().buffer(), r_array.buffer(), index_mask); + } + else { + for (uint i : index_mask) { + m_type->copy_to_uninitialized((*this)[i], r_array[i]); + } + } + } +}; + +} // namespace FN + +#endif /* __FN_GENERIC_VIRTUAL_LIST_REF_H__ */ diff --git a/source/blender/functions/FN_initialize.h b/source/blender/functions/FN_initialize.h new file mode 100644 index 00000000000..10e8a68f02d --- /dev/null +++ b/source/blender/functions/FN_initialize.h @@ -0,0 +1,10 @@ +#ifdef __cplusplus +extern "C" { +#endif + +void FN_initialize(void); +void FN_exit(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/functions/FN_multi_function.h b/source/blender/functions/FN_multi_function.h new file mode 100644 index 00000000000..6a3c36ee70b --- /dev/null +++ b/source/blender/functions/FN_multi_function.h @@ -0,0 +1,456 @@ +#ifndef __FN_MULTI_FUNCTION_H__ +#define __FN_MULTI_FUNCTION_H__ + +#include <memory> +#include <typeinfo> + +#include "FN_generic_array_ref.h" +#include "FN_generic_vector_array.h" +#include "FN_generic_virtual_list_list_ref.h" +#include "FN_generic_virtual_list_ref.h" +#include "FN_multi_function_context.h" +#include "FN_multi_function_data_type.h" +#include "FN_multi_function_param_type.h" + +#include "BLI_vector.h" + +namespace FN { + +class MultiFunction; + +struct MFSignatureData { + std::string function_name; + Vector<std::string> param_names; + Vector<MFParamType> param_types; + Vector<BLI::class_id_t> used_element_contexts; + Vector<BLI::class_id_t> used_global_contexts; + Vector<uint> param_data_indices; + + uint data_index(uint param_index) const + { + return this->param_data_indices[param_index]; + } +}; + +class MFSignatureBuilder { + private: + MFSignatureData &m_data; + uint m_array_ref_count = 0; + uint m_virtual_list_count = 0; + uint m_virtual_list_list_count = 0; + uint m_vector_array_count = 0; + + public: + MFSignatureBuilder(MFSignatureData &data) : m_data(data) + { + } + + /* Used Contexts */ + + template<typename T> void use_element_context() + { + BLI::class_id_t id = BLI::get_class_id<T>(); + m_data.used_element_contexts.append_non_duplicates(id); + } + + template<typename T> void use_global_context() + { + BLI::class_id_t id = BLI::get_class_id<T>(); + m_data.used_global_contexts.append_non_duplicates(id); + } + + void copy_used_contexts(const MultiFunction &fn); + + /* Input Param Types */ + + template<typename T> void single_input(StringRef name) + { + this->single_input(name, CPP_TYPE<T>()); + } + void single_input(StringRef name, const CPPType &type) + { + this->input(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_input(StringRef name) + { + this->vector_input(name, CPP_TYPE<T>()); + } + void vector_input(StringRef name, const CPPType &base_type) + { + this->input(name, MFDataType::ForVector(base_type)); + } + void input(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Input, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_virtual_list_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_virtual_list_list_count++); + break; + } + } + + /* Output Param Types */ + + template<typename T> void single_output(StringRef name) + { + this->single_output(name, CPP_TYPE<T>()); + } + void single_output(StringRef name, const CPPType &type) + { + this->output(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_output(StringRef name) + { + this->vector_output(name, CPP_TYPE<T>()); + } + void vector_output(StringRef name, const CPPType &base_type) + { + this->output(name, MFDataType::ForVector(base_type)); + } + void output(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Output, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_array_ref_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } + + /* Mutable Param Types */ + + void mutable_single(StringRef name, const CPPType &type) + { + this->mutable_param(name, MFDataType::ForSingle(type)); + } + void mutable_vector(StringRef name, const CPPType &base_type) + { + this->mutable_param(name, MFDataType::ForVector(base_type)); + } + void mutable_param(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Mutable, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_array_ref_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } +}; + +class MFParams; + +class MultiFunction { + public: + virtual ~MultiFunction() + { + } + virtual void call(IndexMask mask, MFParams params, MFContext context) const = 0; + + IndexRange param_indices() const + { + return IndexRange(m_signature_data.param_types.size()); + } + + MFParamType param_type(uint index) const + { + return m_signature_data.param_types[index]; + } + + StringRefNull param_name(uint index) const + { + return m_signature_data.param_names[index]; + } + + StringRefNull name() const + { + return m_signature_data.function_name; + } + + bool depends_on_per_element_context() const + { + return m_signature_data.used_element_contexts.size() > 0; + } + + bool depends_on_context() const + { + return m_signature_data.used_element_contexts.size() > 0 || + m_signature_data.used_global_contexts.size() > 0; + } + + template<typename T> bool uses_element_context() const + { + BLI::class_id_t id = BLI::get_class_id<T>(); + return m_signature_data.used_element_contexts.contains(id); + } + + template<typename T> bool uses_global_context() const + { + BLI::class_id_t id = BLI::get_class_id<T>(); + return m_signature_data.used_global_contexts.contains(id); + } + + protected: + MFSignatureBuilder get_builder(StringRef function_name) + { + m_signature_data.function_name = function_name; + return MFSignatureBuilder(m_signature_data); + } + + private: + MFSignatureData m_signature_data; + + friend class MFParamsBuilder; + friend class MFSignatureBuilder; +}; + +class MFParamsBuilder { + private: + Vector<GenericVirtualListRef> m_virtual_list_refs; + Vector<GenericMutableArrayRef> m_mutable_array_refs; + Vector<GenericVirtualListListRef> m_virtual_list_list_refs; + Vector<GenericVectorArray *> m_vector_arrays; + const MFSignatureData *m_signature; + uint m_min_array_size; + + friend MFParams; + + public: + MFParamsBuilder(const MultiFunction &function, uint min_array_size) + : m_signature(&function.m_signature_data), m_min_array_size(min_array_size) + { + } + + template<typename T> void add_readonly_single_input(ArrayRef<T> array) + { + this->add_readonly_single_input(GenericVirtualListRef::FromFullArray<T>(array)); + } + + template<typename T> void add_readonly_single_input(const T *value) + { + this->add_readonly_single_input( + GenericVirtualListRef::FromSingle(CPP_TYPE<T>(), (void *)value, m_min_array_size)); + } + + void add_readonly_single_input(GenericVirtualListRef list) + { + this->assert_current_param_type(MFParamType::ForSingleInput(list.type())); + BLI_assert(list.size() >= m_min_array_size); + m_virtual_list_refs.append(list); + } + + void add_readonly_vector_input(GenericVirtualListListRef list) + { + this->assert_current_param_type(MFParamType::ForVectorInput(list.type())); + BLI_assert(list.size() >= m_min_array_size); + m_virtual_list_list_refs.append(list); + } + + template<typename T> void add_single_output(MutableArrayRef<T> array) + { + BLI_assert(array.size() >= m_min_array_size); + this->add_single_output(GenericMutableArrayRef(array)); + } + template<typename T> void add_single_output(T *value) + { + BLI_assert(m_min_array_size == 1); + BLI_assert(value != nullptr); + this->add_single_output(GenericMutableArrayRef(CPP_TYPE<T>(), (void *)value, 1)); + } + void add_single_output(GenericMutableArrayRef array) + { + this->assert_current_param_type(MFParamType::ForSingleOutput(array.type())); + BLI_assert(array.size() >= m_min_array_size); + m_mutable_array_refs.append(array); + } + + void add_vector_output(GenericVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForVectorOutput(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + void add_mutable_vector(GenericVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForVectorMutable(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + void add_mutable_single(GenericMutableArrayRef array) + { + this->assert_current_param_type(MFParamType::ForSingleMutable(array.type())); + BLI_assert(array.size() >= m_min_array_size); + m_mutable_array_refs.append(array); + } + + /* Utilities to get the data after the function has been called. */ + + GenericMutableArrayRef computed_array(uint index) + { + BLI_assert(ELEM(m_signature->param_types[index].type(), + MFParamType::MutableSingle, + MFParamType::SingleOutput)); + uint data_index = m_signature->data_index(index); + return m_mutable_array_refs[data_index]; + } + + GenericVectorArray &computed_vector_array(uint index) + { + BLI_assert(ELEM(m_signature->param_types[index].type(), + MFParamType::MutableVector, + MFParamType::VectorOutput)); + uint data_index = m_signature->data_index(index); + return *m_vector_arrays[data_index]; + } + + private: + void assert_current_param_type(MFParamType param_type) const + { + UNUSED_VARS_NDEBUG(param_type); +#ifdef DEBUG + uint param_index = this->current_param_index(); + MFParamType expected_type = m_signature->param_types[param_index]; + BLI_assert(expected_type == param_type); +#endif + } + + void assert_current_param_type(MFParamType::Type type) const + { + UNUSED_VARS_NDEBUG(type); +#ifdef DEBUG + uint param_index = this->current_param_index(); + MFParamType::Type expected_type = m_signature->param_types[param_index].type(); + BLI_assert(expected_type == type); +#endif + } + + uint current_param_index() const + { + return m_mutable_array_refs.size() + m_virtual_list_refs.size() + + m_virtual_list_list_refs.size() + m_vector_arrays.size(); + } +}; + +class MFParams { + public: + MFParams(MFParamsBuilder &builder) : m_builder(&builder) + { + } + + template<typename T> VirtualListRef<T> readonly_single_input(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::ForSingleInput(CPP_TYPE<T>())); + return this->readonly_single_input(index, name).as_typed_ref<T>(); + } + + GenericVirtualListRef readonly_single_input(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::SingleInput); + uint data_index = m_builder->m_signature->data_index(index); + return m_builder->m_virtual_list_refs[data_index]; + } + + template<typename T> + MutableArrayRef<T> uninitialized_single_output(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::ForSingleOutput(CPP_TYPE<T>())); + return this->uninitialized_single_output(index, name).as_typed_ref<T>(); + } + GenericMutableArrayRef uninitialized_single_output(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::SingleOutput); + uint data_index = m_builder->m_signature->data_index(index); + return m_builder->m_mutable_array_refs[data_index]; + } + + template<typename T> + const VirtualListListRef<T> readonly_vector_input(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::ForVectorInput(CPP_TYPE<T>())); + return this->readonly_vector_input(index, name).as_typed_ref<T>(); + } + GenericVirtualListListRef readonly_vector_input(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::VectorInput); + uint data_index = m_builder->m_signature->data_index(index); + return m_builder->m_virtual_list_list_refs[data_index]; + } + + template<typename T> + GenericVectorArray::MutableTypedRef<T> vector_output(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::ForVectorOutput(CPP_TYPE<T>())); + return this->vector_output(index, name).as_mutable_typed_ref<T>(); + } + GenericVectorArray &vector_output(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::VectorOutput); + uint data_index = m_builder->m_signature->data_index(index); + return *m_builder->m_vector_arrays[data_index]; + } + + GenericMutableArrayRef mutable_single(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::MutableSingle); + uint data_index = m_builder->m_signature->data_index(index); + return m_builder->m_mutable_array_refs[data_index]; + } + GenericVectorArray &mutable_vector(uint index, StringRef name = "") + { + this->assert_correct_param(index, name, MFParamType::Type::MutableVector); + uint data_index = m_builder->m_signature->data_index(index); + return *m_builder->m_vector_arrays[data_index]; + } + + private: + void assert_correct_param(uint index, StringRef name, MFParamType type) const + { + UNUSED_VARS_NDEBUG(index, name, type); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[index] == type); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[index] == name); + } +#endif + } + + void assert_correct_param(uint index, StringRef name, MFParamType::Type type) const + { + UNUSED_VARS_NDEBUG(index, name, type); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[index].type() == type); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[index] == name); + } +#endif + } + + MFParamsBuilder *m_builder; +}; + +inline void MFSignatureBuilder::copy_used_contexts(const MultiFunction &fn) +{ + m_data.used_element_contexts.extend_non_duplicates(fn.m_signature_data.used_element_contexts); + m_data.used_global_contexts.extend_non_duplicates(fn.m_signature_data.used_global_contexts); +} + +}; // namespace FN + +#endif /* __FN_MULTI_FUNCTION_H__ */ diff --git a/source/blender/functions/FN_multi_function_common_contexts.h b/source/blender/functions/FN_multi_function_common_contexts.h new file mode 100644 index 00000000000..0b689ab08d5 --- /dev/null +++ b/source/blender/functions/FN_multi_function_common_contexts.h @@ -0,0 +1,45 @@ +#ifndef __FN_MULTI_FUNCTION_COMMON_CONTEXTS_H__ +#define __FN_MULTI_FUNCTION_COMMON_CONTEXTS_H__ + +#include <mutex> + +#include "FN_attributes_ref.h" +#include "FN_multi_function_context.h" + +#include "BLI_float3.h" +#include "BLI_map.h" + +namespace FN { + +using BLI::Map; + +struct VertexPositionArray { + ArrayRef<BLI::float3> positions; +}; + +struct SceneTimeContext { + float time; +}; + +struct ParticleAttributesContext { + AttributesRef attributes; +}; + +struct EmitterTimeInfoContext { + float duration; + float begin; + float end; + int step; +}; + +struct EventFilterEndTimeContext { + float end_time; +}; + +struct EventFilterDurationsContext { + ArrayRef<float> durations; +}; + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_COMMON_CONTEXTS_H__ */ diff --git a/source/blender/functions/FN_multi_function_context.h b/source/blender/functions/FN_multi_function_context.h new file mode 100644 index 00000000000..7d6e4fa06b1 --- /dev/null +++ b/source/blender/functions/FN_multi_function_context.h @@ -0,0 +1,181 @@ +#ifndef __FN_MULTI_FUNCTION_CONTEXT_H__ +#define __FN_MULTI_FUNCTION_CONTEXT_H__ + +#include "BLI_buffer_cache.h" +#include "BLI_index_range.h" +#include "BLI_optional.h" +#include "BLI_static_class_ids.h" +#include "BLI_utility_mixins.h" +#include "BLI_vector.h" +#include "BLI_virtual_list_ref.h" + +#include "BKE_id_handle.h" + +namespace FN { + +using BKE::IDHandleLookup; +using BLI::ArrayRef; +using BLI::BufferCache; +using BLI::IndexRange; +using BLI::Optional; +using BLI::Vector; +using BLI::VirtualListRef; + +class MFElementContextIndices { + private: + MFElementContextIndices() = default; + + public: + static MFElementContextIndices FromDirectMapping() + { + return MFElementContextIndices(); + } + + uint operator[](uint index) const + { + return index; + } + + bool is_direct_mapping() const + { + return true; + } +}; + +class MFElementContexts { + private: + Vector<BLI::class_id_t> m_ids; + Vector<const void *> m_contexts; + Vector<MFElementContextIndices> m_indices; + + friend class MFContextBuilder; + + public: + MFElementContexts() = default; + + template<typename T> struct TypedContext { + const T *data; + MFElementContextIndices indices; + }; + + template<typename T> Optional<TypedContext<T>> try_find() const + { + BLI::class_id_t context_id = BLI::get_class_id<T>(); + for (uint i : m_contexts.index_range()) { + if (m_ids[i] == context_id) { + const T *context = (const T *)m_contexts[i]; + return TypedContext<T>{context, m_indices[i]}; + } + } + return {}; + } +}; + +class MFGlobalContexts { + private: + Vector<BLI::class_id_t> m_ids; + Vector<const void *> m_contexts; + + friend class MFContextBuilder; + + public: + MFGlobalContexts() = default; + + template<typename T> const T *try_find() const + { + BLI::class_id_t context_id = BLI::get_class_id<T>(); + for (uint i : m_contexts.index_range()) { + if (m_ids[i] == context_id) { + const T *context = (const T *)m_contexts[i]; + return context; + } + } + return nullptr; + } +}; + +class MFContext; + +class MFContextBuilder : BLI::NonCopyable, BLI::NonMovable { + private: + MFElementContexts m_element_contexts; + MFGlobalContexts m_global_contexts; + BufferCache m_buffer_cache_fallback; + BufferCache *m_buffer_cache = nullptr; + + friend class MFContext; + + public: + MFContextBuilder() : m_buffer_cache(&m_buffer_cache_fallback) + { + } + + void set_buffer_cache(BufferCache &buffer_cache) + { + m_buffer_cache = &buffer_cache; + } + + void add_global_contexts(const MFContext &other); + + template<typename T> void add_element_context(const T &context, MFElementContextIndices indices) + { + m_element_contexts.m_ids.append(BLI::get_class_id<T>()); + m_element_contexts.m_contexts.append((const void *)&context); + m_element_contexts.m_indices.append(indices); + } + + template<typename T> void add_global_context(const T &context) + { + this->add_global_context(BLI::get_class_id<T>(), (const void *)&context); + } + + void add_global_context(BLI::class_id_t id, const void *context) + { + m_global_contexts.m_ids.append(id); + m_global_contexts.m_contexts.append(context); + } +}; + +class MFContext { + private: + MFContextBuilder *m_builder; + + friend MFContextBuilder; + + public: + MFContext(MFContextBuilder &builder) : m_builder(&builder) + { + } + + template<typename T> Optional<MFElementContexts::TypedContext<T>> try_find_per_element() const + { + return m_builder->m_element_contexts.try_find<T>(); + } + + template<typename T> const T *try_find_global() const + { + return m_builder->m_global_contexts.try_find<T>(); + } + + BufferCache &buffer_cache() + { + return *m_builder->m_buffer_cache; + } +}; + +inline void MFContextBuilder::add_global_contexts(const MFContext &other) +{ + const MFGlobalContexts &global_contexts = other.m_builder->m_global_contexts; + + for (uint i : global_contexts.m_ids.index_range()) { + BLI::class_id_t id = other.m_builder->m_global_contexts.m_ids[i]; + const void *context = other.m_builder->m_global_contexts.m_contexts[i]; + + m_global_contexts.m_ids.append(id); + m_global_contexts.m_contexts.append(context); + } +} + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_CONTEXT_H__ */ diff --git a/source/blender/functions/FN_multi_function_data_type.h b/source/blender/functions/FN_multi_function_data_type.h new file mode 100644 index 00000000000..f1eadc5c0a4 --- /dev/null +++ b/source/blender/functions/FN_multi_function_data_type.h @@ -0,0 +1,119 @@ +#ifndef __FN_MULTI_FUNCTION_DATA_TYPE_H__ +#define __FN_MULTI_FUNCTION_DATA_TYPE_H__ + +#include "FN_cpp_type.h" + +#include "BLI_hash_cxx.h" + +namespace FN { + +struct MFDataType { + public: + enum Category { + Single, + Vector, + }; + + private: + MFDataType(Category category, const CPPType &type) : m_category(category), m_base_type(&type) + { + } + + public: + MFDataType() = default; + + template<typename T> static MFDataType ForSingle() + { + return MFDataType::ForSingle(CPP_TYPE<T>()); + } + + template<typename T> static MFDataType ForVector() + { + return MFDataType::ForVector(CPP_TYPE<T>()); + } + + static MFDataType ForSingle(const CPPType &type) + { + return MFDataType(Category::Single, type); + } + + static MFDataType ForVector(const CPPType &type) + { + return MFDataType(Category::Vector, type); + } + + bool is_single() const + { + return m_category == Category::Single; + } + + bool is_vector() const + { + return m_category == Category::Vector; + } + + Category category() const + { + return m_category; + } + + const CPPType &single__cpp_type() const + { + BLI_assert(m_category == Category::Single); + return *m_base_type; + } + + const CPPType &vector__cpp_base_type() const + { + BLI_assert(m_category == Category::Vector); + return *m_base_type; + } + + friend bool operator==(MFDataType a, MFDataType b) + { + return a.m_category == b.m_category && a.m_base_type == b.m_base_type; + } + + friend bool operator!=(MFDataType a, MFDataType b) + { + return !(a == b); + } + + std::string to_string() const + { + switch (m_category) { + case Single: + return m_base_type->name(); + case Vector: + return m_base_type->name() + " Vector"; + } + BLI_assert(false); + return ""; + } + + friend std::ostream &operator<<(std::ostream &stream, MFDataType type) + { + stream << type.to_string(); + return stream; + } + + private: + Category m_category; + const CPPType *m_base_type; + + friend BLI::DefaultHash<MFDataType>; +}; + +} // namespace FN + +namespace BLI { +template<> struct DefaultHash<FN::MFDataType> { + uint32_t operator()(const FN::MFDataType &value) const + { + return DefaultHash<FN::CPPType *>{}(value.m_base_type) + 243523 * (uint)value.m_category; + } +}; + +} // namespace BLI + +#endif /* __FN_MULTI_FUNCTION_DATA_TYPE_H__ */ diff --git a/source/blender/functions/FN_multi_function_dependencies.h b/source/blender/functions/FN_multi_function_dependencies.h new file mode 100644 index 00000000000..b056dbafad6 --- /dev/null +++ b/source/blender/functions/FN_multi_function_dependencies.h @@ -0,0 +1,72 @@ +#ifndef __FN_MULTI_FUNCTION_DEPENDENCIES_H__ +#define __FN_MULTI_FUNCTION_DEPENDENCIES_H__ + +#include "BLI_set.h" + +#include "DNA_image_types.h" +#include "DNA_object_types.h" + +#include "FN_node_tree.h" + +namespace FN { + +using BLI::Set; + +inline Set<Object *> get_objects_used_by_sockets(const FunctionTree &function_tree) +{ + Set<Object *> objects; + for (const FSocket *fsocket : function_tree.all_sockets()) { + if (fsocket->idname() == "fn_ObjectSocket") { + Object *object = (Object *)RNA_pointer_get(fsocket->rna(), "value").data; + if (object != nullptr) { + objects.add(object); + } + } + } + for (const FGroupInput *group_input : function_tree.all_group_inputs()) { + if (group_input->vsocket().idname() == "fn_ObjectSocket") { + Object *object = (Object *)RNA_pointer_get(group_input->vsocket().rna(), "value").data; + if (object != nullptr) { + objects.add(object); + } + } + } + return objects; +} + +inline Set<Image *> get_images_used_by_sockets(const FunctionTree &function_tree) +{ + Set<Image *> images; + for (const FSocket *fsocket : function_tree.all_sockets()) { + if (fsocket->idname() == "fn_ImageSocket") { + Image *image = (Image *)RNA_pointer_get(fsocket->rna(), "value").data; + if (image != nullptr) { + images.add(image); + } + } + } + for (const FGroupInput *group_input : function_tree.all_group_inputs()) { + if (group_input->vsocket().idname() == "fn_ImageSocket") { + Image *image = (Image *)RNA_pointer_get(group_input->vsocket().rna(), "value").data; + if (image != nullptr) { + images.add(image); + } + } + } + return images; +} + +inline void add_ids_used_by_nodes(IDHandleLookup &id_handle_lookup, + const FunctionTree &function_tree) +{ + for (Object *object : get_objects_used_by_sockets(function_tree)) { + id_handle_lookup.add(object->id); + } + for (Image *image : get_images_used_by_sockets(function_tree)) { + id_handle_lookup.add(image->id); + } +} + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_DEPENDENCIES_H__ */ diff --git a/source/blender/functions/FN_multi_function_network.h b/source/blender/functions/FN_multi_function_network.h new file mode 100644 index 00000000000..18cc4a603e4 --- /dev/null +++ b/source/blender/functions/FN_multi_function_network.h @@ -0,0 +1,868 @@ +#ifndef __FN_MULTI_FUNCTION_NETWORK_H__ +#define __FN_MULTI_FUNCTION_NETWORK_H__ + +#include "FN_multi_function.h" + +#include "BLI_array_cxx.h" +#include "BLI_linear_allocated_vector.h" +#include "BLI_map.h" +#include "BLI_optional.h" +#include "BLI_set.h" +#include "BLI_vector_set.h" + +namespace FN { + +using BLI::Array; +using BLI::LinearAllocatedVector; +using BLI::Map; +using BLI::Optional; +using BLI::Set; +using BLI::VectorSet; + +/* MFNetwork Builder + ****************************************/ + +class MFBuilderNode; +class MFBuilderFunctionNode; +class MFBuilderDummyNode; + +class MFBuilderSocket; +class MFBuilderInputSocket; +class MFBuilderOutputSocket; + +class MFNetworkBuilder; + +class MFBuilderNode : BLI::NonCopyable, BLI::NonMovable { + protected: + MFNetworkBuilder *m_network; + ArrayRef<MFBuilderInputSocket *> m_inputs; + ArrayRef<MFBuilderOutputSocket *> m_outputs; + bool m_is_dummy; + uint m_id; + + friend MFNetworkBuilder; + + public: + MFNetworkBuilder &network(); + + ArrayRef<MFBuilderInputSocket *> inputs(); + ArrayRef<MFBuilderOutputSocket *> outputs(); + + MFBuilderInputSocket &input(uint index); + MFBuilderOutputSocket &output(uint index); + + StringRefNull name(); + uint id(); + + bool is_function(); + bool is_dummy(); + + MFBuilderFunctionNode &as_function(); + MFBuilderDummyNode &as_dummy(); + + template<typename FuncT> void foreach_target_socket(const FuncT &func); + template<typename FuncT> void foreach_target_node(const FuncT &func); + template<typename FuncT> void foreach_origin_node(const FuncT &func); + template<typename FuncT> void foreach_linked_node(const FuncT &func); +}; + +class MFBuilderFunctionNode : public MFBuilderNode { + private: + const MultiFunction *m_function; + ArrayRef<uint> m_input_param_indices; + ArrayRef<uint> m_output_param_indices; + + friend MFNetworkBuilder; + + public: + const MultiFunction &function(); + + ArrayRef<uint> input_param_indices(); + ArrayRef<uint> output_param_indices(); +}; + +class MFBuilderDummyNode : public MFBuilderNode { + private: + StringRefNull m_name; + MutableArrayRef<StringRefNull> m_input_names; + MutableArrayRef<StringRefNull> m_output_names; + + friend MFNetworkBuilder; + friend MFBuilderSocket; + friend MFBuilderNode; +}; + +class MFBuilderSocket : BLI::NonCopyable, BLI::NonMovable { + private: + MFBuilderNode *m_node; + bool m_is_output; + uint m_index; + MFDataType m_data_type; + uint m_id; + + friend MFNetworkBuilder; + + public: + MFBuilderNode &node(); + MFDataType data_type(); + + uint index(); + StringRefNull name(); + uint id(); + + bool is_input(); + bool is_output(); + + MFBuilderInputSocket &as_input(); + MFBuilderOutputSocket &as_output(); +}; + +class MFBuilderInputSocket : public MFBuilderSocket { + private: + MFBuilderOutputSocket *m_origin; + + friend MFNetworkBuilder; + + public: + MFBuilderOutputSocket *origin(); +}; + +class MFBuilderOutputSocket : public MFBuilderSocket { + private: + LinearAllocatedVector<MFBuilderInputSocket *> m_targets; + + friend MFNetworkBuilder; + + public: + ArrayRef<MFBuilderInputSocket *> targets(); +}; + +class MFNetworkBuilder : BLI::NonCopyable, BLI::NonMovable { + private: + LinearAllocator<> m_allocator; + + VectorSet<MFBuilderFunctionNode *> m_function_nodes; + VectorSet<MFBuilderDummyNode *> m_dummy_nodes; + + Vector<MFBuilderNode *> m_node_or_null_by_id; + Vector<MFBuilderSocket *> m_socket_or_null_by_id; + + public: + ~MFNetworkBuilder(); + + std::string to_dot(const Set<MFBuilderNode *> &marked_nodes = {}); + void to_dot__clipboard(const Set<MFBuilderNode *> &marked_nodes = {}); + + MFBuilderFunctionNode &add_function(const MultiFunction &function); + MFBuilderDummyNode &add_dummy(StringRef name, + ArrayRef<MFDataType> input_types, + ArrayRef<MFDataType> output_types, + ArrayRef<StringRef> input_names, + ArrayRef<StringRef> output_names); + MFBuilderDummyNode &add_input_dummy(StringRef name, MFBuilderInputSocket &socket); + MFBuilderDummyNode &add_output_dummy(StringRef name, MFBuilderOutputSocket &socket); + + void add_link(MFBuilderOutputSocket &from, MFBuilderInputSocket &to); + void remove_link(MFBuilderOutputSocket &from, MFBuilderInputSocket &to); + void remove_node(MFBuilderNode &node); + void remove_nodes(ArrayRef<MFBuilderNode *> nodes); + void replace_origin(MFBuilderOutputSocket &old_origin, MFBuilderOutputSocket &new_origin); + + Array<bool> find_nodes_to_the_right_of__inclusive__mask(ArrayRef<MFBuilderNode *> nodes); + Array<bool> find_nodes_to_the_left_of__inclusive__mask(ArrayRef<MFBuilderNode *> nodes); + Vector<MFBuilderNode *> find_nodes_not_to_the_left_of__exclusive__vector( + ArrayRef<MFBuilderNode *> nodes); + + Vector<MFBuilderNode *> nodes_by_id_inverted_id_mask(ArrayRef<bool> id_mask); + + uint current_index_of(MFBuilderFunctionNode &node) const + { + return m_function_nodes.index(&node); + } + + uint current_index_of(MFBuilderDummyNode &node) const + { + return m_dummy_nodes.index(&node); + } + + uint node_id_amount() const + { + return m_node_or_null_by_id.size(); + } + + bool node_id_is_valid(uint id) const + { + return m_node_or_null_by_id[id] != nullptr; + } + + MFBuilderNode &node_by_id(uint id) + { + BLI_assert(this->node_id_is_valid(id)); + return *m_node_or_null_by_id[id]; + } + + MFBuilderFunctionNode &function_by_id(uint id) + { + return this->node_by_id(id).as_function(); + } + + MFBuilderDummyNode &dummy_by_id(uint id) + { + return this->node_by_id(id).as_dummy(); + } + + uint socket_id_amount() + { + return m_socket_or_null_by_id.size(); + } + + bool socket_id_is_valid(uint id) const + { + return m_socket_or_null_by_id[id] != nullptr; + } + + MFBuilderSocket &socket_by_id(uint id) + { + BLI_assert(m_socket_or_null_by_id[id] != nullptr); + return *m_socket_or_null_by_id[id]; + } + + MFBuilderInputSocket &input_by_id(uint id) + { + return this->socket_by_id(id).as_input(); + } + + MFBuilderOutputSocket &output_by_id(uint id) + { + return this->socket_by_id(id).as_output(); + } + + ArrayRef<MFBuilderSocket *> sockets_or_null_by_id() + { + return m_socket_or_null_by_id; + } + + ArrayRef<MFBuilderFunctionNode *> function_nodes() const + { + return m_function_nodes; + } + + ArrayRef<MFBuilderDummyNode *> dummy_nodes() const + { + return m_dummy_nodes; + } +}; + +void optimize_multi_function_network(MFNetworkBuilder &network); + +/* Network + ******************************************/ + +class MFNode; +class MFFunctionNode; +class MFDummyNode; + +class MFSocket; +class MFInputSocket; +class MFOutputSocket; + +class MFNetwork; + +class MFNode : BLI::NonCopyable, BLI::NonMovable { + private: + MFNetwork *m_network; + ArrayRef<MFInputSocket *> m_inputs; + ArrayRef<MFOutputSocket *> m_outputs; + bool m_is_dummy; + uint m_id; + + friend MFNetwork; + + public: + const MFNetwork &network() const; + + StringRefNull name() const; + + const MFInputSocket &input(uint index) const; + const MFOutputSocket &output(uint index) const; + + ArrayRef<const MFInputSocket *> inputs() const; + ArrayRef<const MFOutputSocket *> outputs() const; + + uint id() const; + + bool is_function() const; + bool is_dummy() const; + + const MFFunctionNode &as_function() const; + const MFDummyNode &as_dummy() const; + + template<typename FuncT> void foreach_origin_node(const FuncT &func) const; + template<typename FuncT> void foreach_origin_socket(const FuncT &func) const; +}; + +class MFFunctionNode final : public MFNode { + private: + const MultiFunction *m_function; + ArrayRef<uint> m_input_param_indices; + ArrayRef<uint> m_output_param_indices; + + friend MFNetwork; + + public: + const MultiFunction &function() const; + + ArrayRef<uint> input_param_indices() const; + ArrayRef<uint> output_param_indices() const; + + const MFInputSocket &input_for_param(uint param_index) const; + const MFOutputSocket &output_for_param(uint param_index) const; +}; + +class MFDummyNode final : public MFNode { + private: + StringRefNull m_name; + MutableArrayRef<StringRefNull> m_input_names; + MutableArrayRef<StringRefNull> m_output_names; + + friend MFNetwork; +}; + +class MFSocket : BLI::NonCopyable, BLI::NonMovable { + private: + MFNode *m_node; + bool m_is_output; + uint m_index; + MFDataType m_data_type; + uint m_id; + + friend MFNetwork; + + public: + const MFNode &node() const; + MFDataType data_type() const; + uint param_index() const; + MFParamType param_type() const; + + uint index() const; + uint id() const; + + bool is_input() const; + bool is_output() const; + + MFInputSocket &as_input(); + MFOutputSocket &as_output(); + + const MFInputSocket &as_input() const; + const MFOutputSocket &as_output() const; +}; + +class MFInputSocket final : public MFSocket { + private: + MFOutputSocket *m_origin; + + friend MFNetwork; + + public: + const MFOutputSocket &origin() const; +}; + +class MFOutputSocket final : public MFSocket { + private: + Vector<const MFInputSocket *> m_targets; + + friend MFNetwork; + + public: + ArrayRef<const MFInputSocket *> targets() const; + uint target_amount() const; +}; + +class MFNetwork : BLI::NonCopyable, BLI::NonMovable { + private: + LinearAllocator<> m_allocator; + + Vector<MFNode *> m_node_by_id; + Vector<MFSocket *> m_socket_by_id; + + Vector<MFFunctionNode *> m_function_nodes; + Vector<MFDummyNode *> m_dummy_nodes; + Vector<MFInputSocket *> m_input_sockets; + Vector<MFOutputSocket *> m_output_sockets; + + Array<uint> m_max_dependency_depth_per_node; + + public: + MFNetwork(MFNetworkBuilder &builder); + ~MFNetwork(); + + const MFNode &node_by_id(uint id) const; + const MFSocket &socket_by_id(uint id) const; + IndexRange socket_ids() const; + IndexRange node_ids() const; + + ArrayRef<const MFDummyNode *> dummy_nodes() const; + ArrayRef<const MFFunctionNode *> function_nodes() const; + + Vector<const MFOutputSocket *> find_dummy_dependencies( + ArrayRef<const MFInputSocket *> sockets) const; + + Vector<const MFFunctionNode *> find_function_dependencies( + ArrayRef<const MFInputSocket *> sockets) const; + + ArrayRef<uint> max_dependency_depth_per_node() const; + + const MFDummyNode &find_dummy_node(MFBuilderDummyNode &builder_node) const; + const MFInputSocket &find_dummy_socket(MFBuilderInputSocket &builder_socket) const; + const MFOutputSocket &find_dummy_socket(MFBuilderOutputSocket &builder_socket) const; + + private: + void create_links_to_node(MFNetworkBuilder &builder, + MFNode *to_node, + MFBuilderNode *to_builder_node); + + void create_link_to_socket(MFNetworkBuilder &builder, + MFInputSocket *to_socket, + MFBuilderInputSocket *to_builder_socket); + + void compute_max_dependency_depths(); +}; + +/* Builder Implementations + *******************************************/ + +inline MFNetworkBuilder &MFBuilderNode::network() +{ + return *m_network; +} + +inline ArrayRef<MFBuilderInputSocket *> MFBuilderNode::inputs() +{ + return m_inputs; +} +inline ArrayRef<MFBuilderOutputSocket *> MFBuilderNode::outputs() +{ + return m_outputs; +} + +inline MFBuilderInputSocket &MFBuilderNode::input(uint index) +{ + return *m_inputs[index]; +} + +inline MFBuilderOutputSocket &MFBuilderNode::output(uint index) +{ + return *m_outputs[index]; +} + +inline StringRefNull MFBuilderNode::name() +{ + if (this->is_function()) { + return this->as_function().function().name(); + } + else { + return this->as_dummy().m_name; + } +} + +inline uint MFBuilderNode::id() +{ + return m_id; +} + +inline bool MFBuilderNode::is_function() +{ + return !m_is_dummy; +} +inline bool MFBuilderNode::is_dummy() +{ + return m_is_dummy; +} + +inline MFBuilderFunctionNode &MFBuilderNode::as_function() +{ + BLI_assert(this->is_function()); + return *(MFBuilderFunctionNode *)this; +} + +inline MFBuilderDummyNode &MFBuilderNode::as_dummy() +{ + BLI_assert(this->is_dummy()); + return *(MFBuilderDummyNode *)this; +} + +template<typename FuncT> inline void MFBuilderNode::foreach_target_socket(const FuncT &func) +{ + for (MFBuilderOutputSocket *socket : m_outputs) { + for (MFBuilderInputSocket *target : socket->targets()) { + func(*target); + } + } +} + +template<typename FuncT> inline void MFBuilderNode::foreach_target_node(const FuncT &func) +{ + for (MFBuilderOutputSocket *socket : m_outputs) { + for (MFBuilderInputSocket *target : socket->targets()) { + func(target->node()); + } + } +} + +template<typename FuncT> inline void MFBuilderNode::foreach_origin_node(const FuncT &func) +{ + for (MFBuilderInputSocket *socket : m_inputs) { + MFBuilderOutputSocket *origin = socket->origin(); + if (origin != nullptr) { + func(origin->node()); + } + } +} + +template<typename FuncT> inline void MFBuilderNode::foreach_linked_node(const FuncT &func) +{ + this->foreach_origin_node(func); + this->foreach_target_node(func); +} + +inline const MultiFunction &MFBuilderFunctionNode::function() +{ + return *m_function; +} + +inline ArrayRef<uint> MFBuilderFunctionNode::input_param_indices() +{ + return m_input_param_indices; +} + +inline ArrayRef<uint> MFBuilderFunctionNode::output_param_indices() +{ + return m_output_param_indices; +} + +inline MFBuilderNode &MFBuilderSocket::node() +{ + return *m_node; +} + +inline MFDataType MFBuilderSocket::data_type() +{ + return m_data_type; +} + +inline uint MFBuilderSocket::index() +{ + return m_index; +} + +inline StringRefNull MFBuilderSocket::name() +{ + if (m_node->is_function()) { + MFBuilderFunctionNode &node = m_node->as_function(); + if (m_is_output) { + return node.function().param_name(node.output_param_indices()[m_index]); + } + else { + return node.function().param_name(node.input_param_indices()[m_index]); + } + } + else { + MFBuilderDummyNode &node = m_node->as_dummy(); + if (m_is_output) { + return node.m_output_names[m_index]; + } + else { + return node.m_input_names[m_index]; + } + } +} + +inline uint MFBuilderSocket::id() +{ + return m_id; +} + +inline bool MFBuilderSocket::is_input() +{ + return !m_is_output; +} +inline bool MFBuilderSocket::is_output() +{ + return m_is_output; +} + +inline MFBuilderInputSocket &MFBuilderSocket::as_input() +{ + BLI_assert(this->is_input()); + return *(MFBuilderInputSocket *)this; +} +inline MFBuilderOutputSocket &MFBuilderSocket::as_output() +{ + BLI_assert(this->is_output()); + return *(MFBuilderOutputSocket *)this; +} + +inline MFBuilderOutputSocket *MFBuilderInputSocket::origin() +{ + return m_origin; +} + +inline ArrayRef<MFBuilderInputSocket *> MFBuilderOutputSocket::targets() +{ + return m_targets; +} + +/* MFNetwork Implementations + **************************************/ + +inline const MFNetwork &MFNode::network() const +{ + return *m_network; +} + +inline ArrayRef<const MFInputSocket *> MFNode::inputs() const +{ + return m_inputs; +} + +inline ArrayRef<const MFOutputSocket *> MFNode::outputs() const +{ + return m_outputs; +} + +inline const MFInputSocket &MFNode::input(uint index) const +{ + return *m_inputs[index]; +} + +inline const MFOutputSocket &MFNode::output(uint index) const +{ + return *m_outputs[index]; +} + +inline uint MFNode::id() const +{ + return m_id; +} + +inline StringRefNull MFNode::name() const +{ + if (this->is_function()) { + return this->as_function().function().name(); + } + else { + return "Dummy"; + } +} + +inline bool MFNode::is_function() const +{ + return !m_is_dummy; +} + +inline bool MFNode::is_dummy() const +{ + return m_is_dummy; +} + +inline const MFFunctionNode &MFNode::as_function() const +{ + BLI_assert(this->is_function()); + return *(MFFunctionNode *)this; +} + +inline const MFDummyNode &MFNode::as_dummy() const +{ + BLI_assert(this->is_dummy()); + return *(const MFDummyNode *)this; +} + +template<typename FuncT> inline void MFNode::foreach_origin_node(const FuncT &func) const +{ + for (const MFInputSocket *socket : m_inputs) { + const MFOutputSocket &origin_socket = socket->origin(); + const MFNode &origin_node = origin_socket.node(); + func(origin_node); + } +} + +template<typename FuncT> inline void MFNode::foreach_origin_socket(const FuncT &func) const +{ + for (const MFInputSocket *socket : m_inputs) { + const MFOutputSocket &origin_socket = socket->origin(); + func(origin_socket); + } +} + +inline const MultiFunction &MFFunctionNode::function() const +{ + return *m_function; +} + +inline ArrayRef<uint> MFFunctionNode::input_param_indices() const +{ + return m_input_param_indices; +} + +inline ArrayRef<uint> MFFunctionNode::output_param_indices() const +{ + return m_output_param_indices; +} + +inline const MFInputSocket &MFFunctionNode::input_for_param(uint param_index) const +{ + return this->input(m_input_param_indices.first_index(param_index)); +} + +inline const MFOutputSocket &MFFunctionNode::output_for_param(uint param_index) const +{ + return this->output(m_output_param_indices.first_index(param_index)); +} + +inline const MFNode &MFSocket::node() const +{ + return *m_node; +} + +inline MFDataType MFSocket::data_type() const +{ + return m_data_type; +} + +inline uint MFSocket::param_index() const +{ + const MFFunctionNode &node = m_node->as_function(); + if (m_is_output) { + return node.output_param_indices()[m_index]; + } + else { + return node.input_param_indices()[m_index]; + } +} + +inline MFParamType MFSocket::param_type() const +{ + uint param_index = this->param_index(); + return m_node->as_function().function().param_type(param_index); +} + +inline uint MFSocket::index() const +{ + return m_index; +} + +inline uint MFSocket::id() const +{ + return m_id; +} + +inline bool MFSocket::is_input() const +{ + return !m_is_output; +} + +inline bool MFSocket::is_output() const +{ + return m_is_output; +} + +inline MFInputSocket &MFSocket::as_input() +{ + BLI_assert(this->is_input()); + return *(MFInputSocket *)this; +} + +inline MFOutputSocket &MFSocket::as_output() +{ + BLI_assert(this->is_output()); + return *(MFOutputSocket *)this; +} + +inline const MFInputSocket &MFSocket::as_input() const +{ + BLI_assert(this->is_input()); + return *(const MFInputSocket *)this; +} + +inline const MFOutputSocket &MFSocket::as_output() const +{ + BLI_assert(this->is_output()); + return *(const MFOutputSocket *)this; +} + +inline const MFOutputSocket &MFInputSocket::origin() const +{ + return *m_origin; +} + +inline ArrayRef<const MFInputSocket *> MFOutputSocket::targets() const +{ + return m_targets; +} + +inline uint MFOutputSocket::target_amount() const +{ + return m_targets.size(); +} + +inline const MFNode &MFNetwork::node_by_id(uint index) const +{ + return *m_node_by_id[index]; +} + +inline const MFSocket &MFNetwork::socket_by_id(uint index) const +{ + return *m_socket_by_id[index]; +} + +inline IndexRange MFNetwork::socket_ids() const +{ + return IndexRange(m_socket_by_id.size()); +} + +inline IndexRange MFNetwork::node_ids() const +{ + return IndexRange(m_node_by_id.size()); +} + +inline ArrayRef<const MFDummyNode *> MFNetwork::dummy_nodes() const +{ + return m_dummy_nodes.as_ref(); +} + +inline ArrayRef<const MFFunctionNode *> MFNetwork::function_nodes() const +{ + return m_function_nodes.as_ref(); +} + +inline ArrayRef<uint> MFNetwork::max_dependency_depth_per_node() const +{ + return m_max_dependency_depth_per_node; +} + +inline const MFDummyNode &MFNetwork::find_dummy_node(MFBuilderDummyNode &builder_node) const +{ + uint node_index = builder_node.network().current_index_of(builder_node); + const MFDummyNode &node = *this->m_dummy_nodes[node_index]; + return node; +} + +inline const MFInputSocket &MFNetwork::find_dummy_socket( + MFBuilderInputSocket &builder_socket) const +{ + const MFDummyNode &node = this->find_dummy_node(builder_socket.node().as_dummy()); + const MFInputSocket &socket = node.input(builder_socket.index()); + return socket; +} + +inline const MFOutputSocket &MFNetwork::find_dummy_socket( + MFBuilderOutputSocket &builder_socket) const +{ + const MFDummyNode &node = this->find_dummy_node(builder_socket.node().as_dummy()); + const MFOutputSocket &socket = node.output(builder_socket.index()); + return socket; +} + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_NETWORK_H__ */ diff --git a/source/blender/functions/FN_multi_function_network_optimization.h b/source/blender/functions/FN_multi_function_network_optimization.h new file mode 100644 index 00000000000..6c6ef713771 --- /dev/null +++ b/source/blender/functions/FN_multi_function_network_optimization.h @@ -0,0 +1,18 @@ +#ifndef __FN_MULTI_FUNCTION_NETWORK_OPTIMIZATION_H__ +#define __FN_MULTI_FUNCTION_NETWORK_OPTIMIZATION_H__ + +#include "FN_multi_function_network.h" + +#include "BLI_resource_collector.h" + +namespace FN { + +using BLI::ResourceCollector; + +void optimize_network__constant_folding(MFNetworkBuilder &network, ResourceCollector &resources); +void optimize_network__remove_unused_nodes(MFNetworkBuilder &network_builder); +void optimize_network__remove_duplicates(MFNetworkBuilder &network_builder); + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_NETWORK_OPTIMIZATION_H__ */ diff --git a/source/blender/functions/FN_multi_function_param_type.h b/source/blender/functions/FN_multi_function_param_type.h new file mode 100644 index 00000000000..863430e3bcc --- /dev/null +++ b/source/blender/functions/FN_multi_function_param_type.h @@ -0,0 +1,163 @@ +#ifndef __FN_MULTI_FUNCTION_PARAM_TYPE_H__ +#define __FN_MULTI_FUNCTION_PARAM_TYPE_H__ + +#include "FN_multi_function_data_type.h" + +namespace FN { + +struct MFParamType { + public: + enum InterfaceType { + Input, + Output, + Mutable, + }; + + enum Type { + SingleInput, + VectorInput, + SingleOutput, + VectorOutput, + MutableSingle, + MutableVector, + }; + + MFParamType(InterfaceType interface_type, MFDataType data_type) + : m_interface_type(interface_type), m_data_type(data_type) + { + } + + static MFParamType ForSingleInput(const CPPType &type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorInput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForVector(base_type)); + } + + static MFParamType ForSingleOutput(const CPPType &type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorOutput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForVector(base_type)); + } + + static MFParamType ForSingleMutable(const CPPType &type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorMutable(const CPPType &base_type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForVector(base_type)); + } + + bool is_input() const + { + return m_interface_type == Input; + } + + bool is_output() const + { + return m_interface_type == Output; + } + + bool is_mutable() const + { + return m_interface_type == Mutable; + } + + bool is_single_input() const + { + return m_interface_type == Input && m_data_type.is_single(); + } + + bool is_vector_input() const + { + return m_interface_type == Input && m_data_type.is_vector(); + } + + bool is_mutable_single() const + { + return m_interface_type == Mutable && m_data_type.is_single(); + } + + bool is_mutable_vector() const + { + return m_interface_type == Mutable && m_data_type.is_vector(); + } + + bool is_single_output() const + { + return m_interface_type == Output && m_data_type.is_single(); + } + + bool is_input_or_mutable() const + { + return ELEM(m_interface_type, Input, Mutable); + } + + bool is_output_or_mutable() const + { + return ELEM(m_interface_type, Output, Mutable); + } + + bool is_vector_output() const + { + return m_interface_type == Output && m_data_type.is_vector(); + } + + Type type() const + { + if (m_data_type.is_single()) { + switch (m_interface_type) { + case InterfaceType::Input: + return SingleInput; + case InterfaceType::Output: + return SingleOutput; + case InterfaceType::Mutable: + return MutableSingle; + } + } + else if (m_data_type.is_vector()) { + switch (m_interface_type) { + case InterfaceType::Input: + return VectorInput; + case InterfaceType::Output: + return VectorOutput; + case InterfaceType::Mutable: + return MutableVector; + } + } + BLI_assert(false); + return Type::MutableSingle; + } + + MFDataType data_type() const + { + return m_data_type; + } + + InterfaceType interface_type() const + { + return m_interface_type; + } + + friend bool operator==(MFParamType a, MFParamType b) + { + return a.m_interface_type == b.m_interface_type && a.m_data_type == b.m_data_type; + } + + private: + InterfaceType m_interface_type; + MFDataType m_data_type; +}; + +} // namespace FN + +#endif /* __FN_MULTI_FUNCTION_PARAM_TYPE_H__ */ diff --git a/source/blender/functions/FN_multi_functions.h b/source/blender/functions/FN_multi_functions.h new file mode 100644 index 00000000000..7312be67d41 --- /dev/null +++ b/source/blender/functions/FN_multi_functions.h @@ -0,0 +1,14 @@ +#ifndef __FN_MULTI_FUNCTIONS_H__ +#define __FN_MULTI_FUNCTIONS_H__ + +#include "intern/multi_functions/constants.h" +#include "intern/multi_functions/customizable.h" +#include "intern/multi_functions/global_functions.h" +#include "intern/multi_functions/lists.h" +#include "intern/multi_functions/mixed.h" +#include "intern/multi_functions/network.h" +#include "intern/multi_functions/particles.h" +#include "intern/multi_functions/surface_hook.h" +#include "intern/multi_functions/vectorize.h" + +#endif /* __FN_MULTI_FUNCTIONS_H__ */ diff --git a/source/blender/functions/FN_node_tree.h b/source/blender/functions/FN_node_tree.h new file mode 100644 index 00000000000..fd7f21bba3b --- /dev/null +++ b/source/blender/functions/FN_node_tree.h @@ -0,0 +1,456 @@ +#ifndef __BKE_INLINED_NODE_TREE_H__ +#define __BKE_INLINED_NODE_TREE_H__ + +#include "BKE_virtual_node_tree.h" + +#include "BLI_linear_allocated_vector.h" +#include "BLI_map.h" +#include "BLI_multi_map.h" + +namespace FN { + +using BKE::VInputSocket; +using BKE::VirtualNodeTree; +using BKE::VNode; +using BKE::VOutputSocket; +using BKE::VSocket; +using BLI::ArrayRef; +using BLI::LinearAllocatedVector; +using BLI::Map; +using BLI::MultiMap; +using BLI::MutableArrayRef; +using BLI::StringMap; +using BLI::StringMultiMap; +using BLI::StringRef; +using BLI::StringRefNull; +using BLI::Vector; + +class FNode; +class FParentNode; +class FSocket; +class FInputSocket; +class FOutputSocket; +class FGroupInput; +class FunctionTree; + +class FSocket : BLI::NonCopyable, BLI::NonMovable { + protected: + FNode *m_node; + const VSocket *m_vsocket; + bool m_is_input; + + /* Input and output sockets share the same id-space. */ + uint m_id; + + friend FunctionTree; + + public: + const FNode &node() const; + uint id() const; + + bool is_input() const; + bool is_output() const; + const FSocket &as_base() const; + const FInputSocket &as_input() const; + const FOutputSocket &as_output() const; + + PointerRNA *rna() const; + StringRefNull idname() const; + StringRefNull name() const; + + uint index() const; +}; + +class FInputSocket : public FSocket { + private: + LinearAllocatedVector<FOutputSocket *> m_linked_sockets; + LinearAllocatedVector<FGroupInput *> m_linked_group_inputs; + + friend FunctionTree; + + public: + const VInputSocket &vsocket() const; + ArrayRef<const FOutputSocket *> linked_sockets() const; + ArrayRef<const FGroupInput *> linked_group_inputs() const; + + bool is_linked() const; +}; + +class FOutputSocket : public FSocket { + private: + LinearAllocatedVector<FInputSocket *> m_linked_sockets; + + friend FunctionTree; + + public: + const VOutputSocket &vsocket() const; + ArrayRef<const FInputSocket *> linked_sockets() const; +}; + +class FGroupInput : BLI::NonCopyable, BLI::NonMovable { + private: + const VInputSocket *m_vsocket; + FParentNode *m_parent; + LinearAllocatedVector<FInputSocket *> m_linked_sockets; + uint m_id; + + friend FunctionTree; + + public: + const VInputSocket &vsocket() const; + const FParentNode *parent() const; + ArrayRef<const FInputSocket *> linked_sockets() const; + uint id() const; +}; + +class FNode : BLI::NonCopyable, BLI::NonMovable { + private: + const VNode *m_vnode; + FParentNode *m_parent; + + LinearAllocatedVector<FInputSocket *> m_inputs; + LinearAllocatedVector<FOutputSocket *> m_outputs; + + /* Uniquely identifies this node in the inlined node tree. */ + uint m_id; + + friend FunctionTree; + + void destruct_with_sockets(); + + public: + const VNode &vnode() const; + const FParentNode *parent() const; + + ArrayRef<const FInputSocket *> inputs() const; + ArrayRef<const FOutputSocket *> outputs() const; + + const FInputSocket &input(uint index) const; + const FOutputSocket &output(uint index) const; + const FInputSocket &input(uint index, StringRef expected_name) const; + const FOutputSocket &output(uint index, StringRef expected_name) const; + + uint id() const; + + PointerRNA *rna() const; + StringRefNull idname() const; + StringRefNull name() const; + + const FInputSocket *input_with_name_prefix(StringRef name_prefix) const; +}; + +class FParentNode : BLI::NonCopyable, BLI::NonMovable { + private: + const VNode *m_vnode; + FParentNode *m_parent; + uint m_id; + + friend FunctionTree; + + public: + const FParentNode *parent() const; + const VNode &vnode() const; + uint id() const; +}; + +using BTreeVTreeMap = Map<bNodeTree *, std::unique_ptr<const VirtualNodeTree>>; + +class FunctionTree : BLI::NonCopyable, BLI::NonMovable { + private: + BLI::LinearAllocator<> m_allocator; + bNodeTree *m_btree; + Vector<FNode *> m_node_by_id; + Vector<FGroupInput *> m_group_inputs; + Vector<FParentNode *> m_parent_nodes; + + Vector<FSocket *> m_sockets_by_id; + Vector<FInputSocket *> m_input_sockets; + Vector<FOutputSocket *> m_output_sockets; + + StringMultiMap<FNode *> m_nodes_by_idname; + + public: + FunctionTree(bNodeTree *btree, BTreeVTreeMap &vtrees); + ~FunctionTree(); + + std::string to_dot() const; + void to_dot__clipboard() const; + + const FSocket &socket_by_id(uint id) const; + uint socket_count() const; + uint node_count() const; + + ArrayRef<const FSocket *> all_sockets() const; + ArrayRef<const FNode *> all_nodes() const; + ArrayRef<const FInputSocket *> all_input_sockets() const; + ArrayRef<const FOutputSocket *> all_output_sockets() const; + ArrayRef<const FGroupInput *> all_group_inputs() const; + ArrayRef<const FNode *> nodes_with_idname(StringRef idname) const; + + private: + void expand_groups(Vector<FNode *> &all_nodes, + Vector<FGroupInput *> &all_group_inputs, + Vector<FParentNode *> &all_parent_nodes, + BTreeVTreeMap &vtrees); + void expand_group_node(FNode &group_node, + Vector<FNode *> &all_nodes, + Vector<FGroupInput *> &all_group_inputs, + Vector<FParentNode *> &all_parent_nodes, + BTreeVTreeMap &vtrees); + void expand_group__group_inputs_for_unlinked_inputs(FNode &group_node, + Vector<FGroupInput *> &all_group_inputs); + void expand_group__relink_inputs(const VirtualNodeTree &vtree, + ArrayRef<FNode *> new_fnodes_by_id, + FNode &group_node); + void expand_group__relink_outputs(const VirtualNodeTree &vtree, + ArrayRef<FNode *> new_fnodes_by_id, + FNode &group_node); + void insert_linked_nodes_for_vtree_in_id_order(const VirtualNodeTree &vtree, + Vector<FNode *> &all_nodes, + FParentNode *parent); + FNode &create_node(const VNode &vnode, + FParentNode *parent, + MutableArrayRef<FSocket *> sockets_map); + void remove_expanded_groups_and_interfaces(Vector<FNode *> &all_nodes); + void store_tree_in_this_and_init_ids(Vector<FNode *> &&all_nodes, + Vector<FGroupInput *> &&all_group_inputs, + Vector<FParentNode *> &&all_parent_nodes); +}; + +/* Inline functions + ********************************************/ + +inline const VNode &FNode::vnode() const +{ + return *m_vnode; +} + +inline const FParentNode *FNode::parent() const +{ + return m_parent; +} + +inline ArrayRef<const FInputSocket *> FNode::inputs() const +{ + return m_inputs.as_ref(); +} + +inline ArrayRef<const FOutputSocket *> FNode::outputs() const +{ + return m_outputs.as_ref(); +} + +inline const FInputSocket &FNode::input(uint index) const +{ + return *m_inputs[index]; +} + +inline const FOutputSocket &FNode::output(uint index) const +{ + return *m_outputs[index]; +} + +inline const FInputSocket &FNode::input(uint index, StringRef expected_name) const +{ + BLI_assert(m_inputs[index]->name() == expected_name); + UNUSED_VARS_NDEBUG(expected_name); + return *m_inputs[index]; +} + +inline const FOutputSocket &FNode::output(uint index, StringRef expected_name) const +{ + BLI_assert(m_outputs[index]->name() == expected_name); + UNUSED_VARS_NDEBUG(expected_name); + return *m_outputs[index]; +} + +inline uint FNode::id() const +{ + return m_id; +} + +inline PointerRNA *FNode::rna() const +{ + return m_vnode->rna(); +} + +inline StringRefNull FNode::idname() const +{ + return m_vnode->idname(); +} + +inline StringRefNull FNode::name() const +{ + return m_vnode->name(); +} + +inline const FParentNode *FParentNode::parent() const +{ + return m_parent; +} + +inline const VNode &FParentNode::vnode() const +{ + return *m_vnode; +} + +inline uint FParentNode::id() const +{ + return m_id; +} + +inline const FNode &FSocket::node() const +{ + return *m_node; +} + +inline uint FSocket::id() const +{ + return m_id; +} + +inline bool FSocket::is_input() const +{ + return m_is_input; +} + +inline bool FSocket::is_output() const +{ + return !m_is_input; +} + +inline const FSocket &FSocket::as_base() const +{ + return *this; +} + +inline const FInputSocket &FSocket::as_input() const +{ + BLI_assert(this->is_input()); + return *(const FInputSocket *)this; +} + +inline const FOutputSocket &FSocket::as_output() const +{ + BLI_assert(this->is_output()); + return *(const FOutputSocket *)this; +} + +inline PointerRNA *FSocket::rna() const +{ + return m_vsocket->rna(); +} + +inline StringRefNull FSocket::idname() const +{ + return m_vsocket->idname(); +} + +inline StringRefNull FSocket::name() const +{ + return m_vsocket->name(); +} + +inline uint FSocket::index() const +{ + return m_vsocket->index(); +} + +inline const VInputSocket &FInputSocket::vsocket() const +{ + return m_vsocket->as_input(); +} + +inline ArrayRef<const FOutputSocket *> FInputSocket::linked_sockets() const +{ + return m_linked_sockets.as_ref(); +} + +inline ArrayRef<const FGroupInput *> FInputSocket::linked_group_inputs() const +{ + return m_linked_group_inputs.as_ref(); +} + +inline bool FInputSocket::is_linked() const +{ + return m_linked_sockets.size() > 0 || m_linked_group_inputs.size() > 0; +} + +inline const VOutputSocket &FOutputSocket::vsocket() const +{ + return m_vsocket->as_output(); +} + +inline ArrayRef<const FInputSocket *> FOutputSocket::linked_sockets() const +{ + return m_linked_sockets.as_ref(); +} + +inline const VInputSocket &FGroupInput::vsocket() const +{ + return *m_vsocket; +} + +inline const FParentNode *FGroupInput::parent() const +{ + return m_parent; +} + +inline ArrayRef<const FInputSocket *> FGroupInput::linked_sockets() const +{ + return m_linked_sockets.as_ref(); +} + +inline uint FGroupInput::id() const +{ + return m_id; +} + +inline const FSocket &FunctionTree::socket_by_id(uint id) const +{ + return *m_sockets_by_id[id]; +} + +inline uint FunctionTree::socket_count() const +{ + return m_sockets_by_id.size(); +} + +inline uint FunctionTree::node_count() const +{ + return m_node_by_id.size(); +} + +inline ArrayRef<const FSocket *> FunctionTree::all_sockets() const +{ + return m_sockets_by_id.as_ref(); +} + +inline ArrayRef<const FNode *> FunctionTree::all_nodes() const +{ + return m_node_by_id.as_ref(); +} + +inline ArrayRef<const FInputSocket *> FunctionTree::all_input_sockets() const +{ + return m_input_sockets.as_ref(); +} + +inline ArrayRef<const FOutputSocket *> FunctionTree::all_output_sockets() const +{ + return m_output_sockets.as_ref(); +} + +inline ArrayRef<const FGroupInput *> FunctionTree::all_group_inputs() const +{ + return m_group_inputs.as_ref(); +} + +inline ArrayRef<const FNode *> FunctionTree::nodes_with_idname(StringRef idname) const +{ + return m_nodes_by_idname.lookup_default(idname); +} + +} // namespace FN + +#endif /* __BKE_INLINED_NODE_TREE_H__ */ diff --git a/source/blender/functions/FN_node_tree_multi_function_network.h b/source/blender/functions/FN_node_tree_multi_function_network.h new file mode 100644 index 00000000000..54c4bb69b38 --- /dev/null +++ b/source/blender/functions/FN_node_tree_multi_function_network.h @@ -0,0 +1,155 @@ +#ifndef __FN_VTREE_MULTI_FUNCTION_NETWORK_H__ +#define __FN_VTREE_MULTI_FUNCTION_NETWORK_H__ + +#include "FN_node_tree.h" + +#include "BLI_index_to_ref_map.h" +#include "BLI_multi_map.h" + +#include "FN_multi_function_network.h" + +namespace FN { + +using BLI::IndexToRefMap; +using BLI::MultiMap; + +class DummySocketMap { + private: + const FunctionTree *m_function_tree; + const MFNetwork *m_network; + + IndexToRefMap<const MFSocket> m_dummy_socket_by_fsocket_id; + IndexToRefMap<const FSocket> m_fsocket_by_dummy_socket_id; + + public: + DummySocketMap(const FunctionTree &function_tree, + const MFNetwork &network, + IndexToRefMap<const MFSocket> dummy_socket_by_fsocket_id, + IndexToRefMap<const FSocket> fsocket_by_dummy_socket_id) + : m_function_tree(&function_tree), + m_network(&network), + m_dummy_socket_by_fsocket_id(std::move(dummy_socket_by_fsocket_id)), + m_fsocket_by_dummy_socket_id(std::move(fsocket_by_dummy_socket_id)) + { + } + + bool is_mapped(const FSocket &fsocket) const + { + return m_dummy_socket_by_fsocket_id.contains(fsocket.id()); + } + + bool is_mapped(const MFSocket &socket) const + { + return m_fsocket_by_dummy_socket_id.contains(socket.id()); + } + + const MFInputSocket &lookup_singly_mapped_input_socket(const FInputSocket &fsocket) const + { + return m_dummy_socket_by_fsocket_id.lookup(fsocket.id()).as_input(); + } + + const MFOutputSocket &lookup_socket(const FOutputSocket &fsocket) const + { + return m_dummy_socket_by_fsocket_id.lookup(fsocket.id()).as_output(); + } + + const FInputSocket &lookup_fsocket(const MFInputSocket &socket) const + { + BLI_assert(socket.node().is_dummy()); + return m_fsocket_by_dummy_socket_id.lookup(socket.id()).as_input(); + } + + const FOutputSocket &lookup_fsocket(const MFOutputSocket &socket) const + { + BLI_assert(socket.node().is_dummy()); + return m_fsocket_by_dummy_socket_id.lookup(socket.id()).as_output(); + } +}; + +class FunctionTreeMFNetwork { + private: + const FunctionTree &m_function_tree; + std::unique_ptr<MFNetwork> m_network; + DummySocketMap m_socket_map; + + public: + FunctionTreeMFNetwork(const FunctionTree &function_tree, + std::unique_ptr<MFNetwork> network, + DummySocketMap socket_map) + : m_function_tree(function_tree), + m_network(std::move(network)), + m_socket_map(std::move(socket_map)) + { + } + + const FunctionTree &function_tree() const + { + return m_function_tree; + } + + const MFNetwork &network() const + { + return *m_network; + } + + bool is_mapped(const FSocket &fsocket) const + { + return m_socket_map.is_mapped(fsocket); + } + + bool is_mapped(const MFSocket &socket) const + { + return m_socket_map.is_mapped(socket); + } + + const MFInputSocket &lookup_dummy_socket(const FInputSocket &fsocket) const + { + const MFInputSocket &socket = m_socket_map.lookup_singly_mapped_input_socket(fsocket); + BLI_assert(socket.node().is_dummy()); + return socket; + } + + const MFOutputSocket &lookup_dummy_socket(const FOutputSocket &fsocket) const + { + const MFOutputSocket &socket = this->lookup_socket(fsocket); + BLI_assert(socket.node().is_dummy()); + return socket; + } + + const MFOutputSocket &lookup_socket(const FOutputSocket &fsocket) const + { + return m_socket_map.lookup_socket(fsocket); + } + + const FInputSocket &lookup_fsocket(const MFInputSocket &socket) const + { + return m_socket_map.lookup_fsocket(socket); + } + + const FOutputSocket &lookup_fsocket(const MFOutputSocket &socket) const + { + return m_socket_map.lookup_fsocket(socket); + } + + void lookup_dummy_sockets(ArrayRef<const FOutputSocket *> fsockets, + MutableArrayRef<const MFOutputSocket *> r_result) const + { + BLI::assert_same_size(fsockets, r_result); + for (uint i : fsockets.index_range()) { + r_result[i] = &this->lookup_socket(*fsockets[i]); + } + } + + void lookup_dummy_sockets(ArrayRef<const FInputSocket *> fsockets, + MutableArrayRef<const MFInputSocket *> r_result) const + { + BLI::assert_same_size(fsockets, r_result); + for (uint i : fsockets.index_range()) { + r_result[i] = &this->lookup_dummy_socket(*fsockets[i]); + } + } +}; + +} // namespace FN + +#endif /* __FN_VTREE_MULTI_FUNCTION_NETWORK_H__ */ diff --git a/source/blender/functions/FN_node_tree_multi_function_network_generation.h b/source/blender/functions/FN_node_tree_multi_function_network_generation.h new file mode 100644 index 00000000000..b45e7f6f84e --- /dev/null +++ b/source/blender/functions/FN_node_tree_multi_function_network_generation.h @@ -0,0 +1,22 @@ +#ifndef __FN_VTREE_MULTI_FUNCTION_NETWORK_GENERATION_H__ +#define __FN_VTREE_MULTI_FUNCTION_NETWORK_GENERATION_H__ + +#include "BLI_resource_collector.h" +#include "FN_node_tree_multi_function_network.h" +#include "intern/multi_functions/network.h" + +namespace FN { +namespace MFGeneration { + +using BLI::ResourceCollector; + +std::unique_ptr<FunctionTreeMFNetwork> generate_node_tree_multi_function_network( + const FunctionTree &function_tree, ResourceCollector &resources); + +std::unique_ptr<MF_EvaluateNetwork> generate_node_tree_multi_function( + const FunctionTree &function_tree, ResourceCollector &resources); + +} // namespace MFGeneration +} // namespace FN + +#endif /* __FN_VTREE_MULTI_FUNCTION_NETWORK_GENERATION_H__ */ diff --git a/source/blender/functions/intern/attributes_ref.cc b/source/blender/functions/intern/attributes_ref.cc new file mode 100644 index 00000000000..9fbf492f934 --- /dev/null +++ b/source/blender/functions/intern/attributes_ref.cc @@ -0,0 +1,164 @@ +#include "FN_attributes_ref.h" + +namespace FN { + +AttributesInfoBuilder::~AttributesInfoBuilder() +{ + for (uint i = 0; i < m_defaults.size(); i++) { + m_types[i]->destruct(m_defaults[i]); + } +} + +void AttributesInfoBuilder::add(const AttributesInfoBuilder &other) +{ + for (uint i = 0; i < other.size(); i++) { + this->add(other.m_names[i], *other.m_types[i], other.m_defaults[i]); + } +} + +void AttributesInfoBuilder::add(const AttributesInfo &other) +{ + for (uint i = 0; i < other.size(); i++) { + this->add(other.name_of(i), other.type_of(i), other.default_of(i)); + } +} + +AttributesInfo::AttributesInfo(const AttributesInfoBuilder &builder) +{ + for (uint i = 0; i < builder.size(); i++) { + StringRef name = builder.names()[i]; + const CPPType &type = *builder.types()[i]; + const void *default_value = builder.defaults()[i]; + + m_index_by_name.add_new(name, i); + m_name_by_index.append(name); + m_type_by_index.append(&type); + + void *dst = m_allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(default_value, dst); + m_defaults.append(dst); + } +} + +AttributesInfo::~AttributesInfo() +{ + for (uint i = 0; i < m_defaults.size(); i++) { + m_type_by_index[i]->destruct(m_defaults[i]); + } +} + +void MutableAttributesRef::destruct_and_reorder(IndexMask index_mask) +{ +#ifdef DEBUG + BLI_assert(index_mask.size() <= m_range.size()); + BLI_assert(index_mask.size() == 0 || index_mask.last() < m_range.size()); + for (uint i = 1; i < index_mask.size(); i++) { + BLI_assert(index_mask[i - 1] < index_mask[i]); + } +#endif + + for (uint attribute_index : m_info->indices()) { + GenericMutableArrayRef array = this->get(attribute_index); + const CPPType &type = m_info->type_of(attribute_index); + + array.destruct_indices(index_mask); + + for (uint i : index_mask.index_range()) { + uint last_index = m_range.size() - 1 - i; + uint index_to_remove = index_mask[index_mask.size() - 1 - i]; + if (index_to_remove == last_index) { + /* Do nothing. It has been destructed before. */ + } + else { + /* Relocate last undestructed value. */ + type.relocate_to_uninitialized(array[last_index], array[index_to_remove]); + } + } + } +} + +void MutableAttributesRef::RelocateUninitialized(MutableAttributesRef from, + MutableAttributesRef to) +{ + BLI::assert_same_size(from, to); + BLI_assert(&from.info() == &to.info()); + + for (uint attribute_index : from.info().indices()) { + GenericMutableArrayRef from_array = from.get(attribute_index); + GenericMutableArrayRef to_array = to.get(attribute_index); + + GenericMutableArrayRef::RelocateUninitialized(from_array, to_array); + } +} + +AttributesRefGroup::AttributesRefGroup(const AttributesInfo &info, + Vector<ArrayRef<void *>> buffers, + Vector<IndexRange> ranges) + : m_info(&info), m_buffers(std::move(buffers)), m_ranges(std::move(ranges)) +{ + m_total_size = 0; + for (IndexRange range : m_ranges) { + m_total_size += range.size(); + } +} + +static Array<int> map_attribute_indices(const AttributesInfo &from_info, + const AttributesInfo &to_info) +{ + Array<int> mapping = Array<int>(from_info.size()); + + for (uint from_index : from_info.indices()) { + StringRef name = from_info.name_of(from_index); + const CPPType &type = from_info.type_of(from_index); + + int to_index = to_info.try_index_of(name, type); + mapping[from_index] = to_index; + } + + return mapping; +} + +AttributesInfoDiff::AttributesInfoDiff(const AttributesInfo &old_info, + const AttributesInfo &new_info) + : m_old_info(&old_info), m_new_info(&new_info) +{ + m_old_to_new_mapping = map_attribute_indices(old_info, new_info); + m_new_to_old_mapping = map_attribute_indices(new_info, old_info); +} + +void AttributesInfoDiff::update(uint capacity, + uint used_size, + ArrayRef<void *> old_buffers, + MutableArrayRef<void *> new_buffers) const +{ + BLI::assert_same_size(old_buffers, *m_old_info); + BLI::assert_same_size(new_buffers, *m_new_info); + + for (uint new_index : m_new_info->indices()) { + int old_index = m_new_to_old_mapping[new_index]; + const CPPType &type = m_new_info->type_of(new_index); + + if (old_index == -1) { + void *new_buffer = MEM_mallocN_aligned(capacity * type.size(), type.alignment(), __func__); + + GenericMutableArrayRef{type, new_buffer, used_size}.fill__uninitialized( + m_new_info->default_of(new_index)); + + new_buffers[new_index] = new_buffer; + } + else { + new_buffers[new_index] = old_buffers[old_index]; + } + }; + + for (uint old_index : m_old_info->indices()) { + int new_index = m_old_to_new_mapping[old_index]; + void *old_buffer = old_buffers[old_index]; + + if (new_index == -1 && old_buffer != nullptr) { + MEM_freeN(old_buffer); + } + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/cpp_type.cc b/source/blender/functions/intern/cpp_type.cc new file mode 100644 index 00000000000..2e04b405a22 --- /dev/null +++ b/source/blender/functions/intern/cpp_type.cc @@ -0,0 +1,9 @@ +#include "FN_cpp_type.h" + +namespace FN { + +CPPType::~CPPType() +{ +} + +} // namespace FN diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc new file mode 100644 index 00000000000..c6d63994f27 --- /dev/null +++ b/source/blender/functions/intern/cpp_types.cc @@ -0,0 +1,222 @@ +#include "cpp_types.h" +#include "FN_cpp_type.h" + +#include "BLI_color.h" +#include "BLI_float3.h" +#include "BLI_rand_cxx.h" + +#include "BKE_surface_hook.h" + +namespace FN { + +void init_cpp_types() +{ +} + +void free_cpp_types() +{ +} + +template<typename T> void ConstructDefault_CB(void *ptr) +{ + BLI::construct_default((T *)ptr); +} +template<typename T> void ConstructDefaultN_CB(void *ptr, uint n) +{ + for (uint i = 0; i < n; i++) { + BLI::construct_default((T *)ptr + i); + } +} +template<typename T> void ConstructDefaultIndices_CB(void *ptr, IndexMask index_mask) +{ + index_mask.foreach_index([=](uint i) { BLI::construct_default((T *)ptr + i); }); +} + +template<typename T> void Destruct_CB(void *ptr) +{ + BLI::destruct((T *)ptr); +} +template<typename T> void DestructN_CB(void *ptr, uint n) +{ + BLI::destruct_n((T *)ptr, n); +} +template<typename T> void DestructIndices_CB(void *ptr, IndexMask index_mask) +{ + index_mask.foreach_index([=](uint i) { BLI::destruct((T *)ptr + i); }); +} + +template<typename T> void CopyToInitialized_CB(const void *src, void *dst) +{ + *(T *)dst = *(T *)src; +} +template<typename T> void CopyToInitializedN_CB(const void *src, void *dst, uint n) +{ + const T *src_ = (const T *)src; + T *dst_ = (T *)dst; + + for (uint i = 0; i < n; i++) { + dst_[i] = src_[i]; + } +} +template<typename T> +void CopyToInitializedIndices_CB(const void *src, void *dst, IndexMask index_mask) +{ + const T *src_ = (const T *)src; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { dst_[i] = src_[i]; }); +} + +template<typename T> void CopyToUninitialized_CB(const void *src, void *dst) +{ + BLI::uninitialized_copy_n((T *)src, 1, (T *)dst); +} +template<typename T> void CopyToUninitializedN_CB(const void *src, void *dst, uint n) +{ + BLI::uninitialized_copy_n((T *)src, n, (T *)dst); +} +template<typename T> +void CopyToUninitializedIndices_CB(const void *src, void *dst, IndexMask index_mask) +{ + const T *src_ = (const T *)src; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { new (dst_ + i) T(src_[i]); }); +} + +template<typename T> void RelocateToInitialized_CB(void *src, void *dst) +{ + BLI::relocate((T *)src, (T *)dst); +} +template<typename T> void RelocateToInitializedN_CB(void *src, void *dst, uint n) +{ + BLI::relocate_n((T *)src, n, (T *)dst); +} +template<typename T> +void RelocateToInitializedIndices_CB(void *src, void *dst, IndexMask index_mask) +{ + T *src_ = (T *)src; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { + dst_[i] = std::move(src_[i]); + src_[i].~T(); + }); +} + +template<typename T> void RelocateToUninitialized_CB(void *src, void *dst) +{ + BLI::uninitialized_relocate((T *)src, (T *)dst); +} +template<typename T> void RelocateToUninitializedN_CB(void *src, void *dst, uint n) +{ + BLI::uninitialized_relocate_n((T *)src, n, (T *)dst); +} +template<typename T> +void RelocateToUninitializedIndices_CB(void *src, void *dst, IndexMask index_mask) +{ + T *src_ = (T *)src; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { + new (dst_ + i) T(std::move(src_[i])); + src_[i].~T(); + }); +} + +template<typename T> void FillInitialized_CB(const void *value, void *dst, uint n) +{ + const T &value_ = *(const T *)value; + T *dst_ = (T *)dst; + + for (uint i = 0; i < n; i++) { + dst_[i] = value_; + } +} +template<typename T> +void FillInitializedIndices_CB(const void *value, void *dst, IndexMask index_mask) +{ + const T &value_ = *(const T *)value; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { dst_[i] = value_; }); +} + +template<typename T> void FillUninitialized_CB(const void *value, void *dst, uint n) +{ + const T &value_ = *(const T *)value; + T *dst_ = (T *)dst; + + for (uint i = 0; i < n; i++) { + new (dst_ + i) T(value_); + } +} +template<typename T> +void FillUninitializedIndices_CB(const void *value, void *dst, IndexMask index_mask) +{ + const T &value_ = *(const T *)value; + T *dst_ = (T *)dst; + + index_mask.foreach_index([=](uint i) { new (dst_ + i) T(value_); }); +} + +template<typename T> +static std::unique_ptr<const CPPType> create_cpp_type(StringRef name, + uint32_t type_hash, + const T &default_value) +{ + const CPPType *type = new CPPType(name, + sizeof(T), + alignof(T), + std::is_trivially_destructible<T>::value, + ConstructDefault_CB<T>, + ConstructDefaultN_CB<T>, + ConstructDefaultIndices_CB<T>, + Destruct_CB<T>, + DestructN_CB<T>, + DestructIndices_CB<T>, + CopyToInitialized_CB<T>, + CopyToInitializedN_CB<T>, + CopyToInitializedIndices_CB<T>, + CopyToUninitialized_CB<T>, + CopyToUninitializedN_CB<T>, + CopyToUninitializedIndices_CB<T>, + RelocateToInitialized_CB<T>, + RelocateToInitializedN_CB<T>, + RelocateToInitializedIndices_CB<T>, + RelocateToUninitialized_CB<T>, + RelocateToUninitializedN_CB<T>, + RelocateToUninitializedIndices_CB<T>, + FillInitialized_CB<T>, + FillInitializedIndices_CB<T>, + FillUninitialized_CB<T>, + FillUninitializedIndices_CB<T>, + type_hash, + (const void *)&default_value); + return std::unique_ptr<const CPPType>(type); +} + +#define MAKE_CPP_TYPE(IDENTIFIER, TYPE_NAME) \ + static TYPE_NAME default_value_##IDENTIFIER; \ + static std::unique_ptr<const CPPType> CPPTYPE_##IDENTIFIER##_owner = \ + create_cpp_type<TYPE_NAME>( \ + STRINGIFY(IDENTIFIER), BLI_RAND_PER_LINE_UINT32, default_value_##IDENTIFIER); \ + const CPPType &CPPType_##IDENTIFIER = *CPPTYPE_##IDENTIFIER##_owner; \ + template<> const CPPType &CPP_TYPE<TYPE_NAME>() \ + { \ + return CPPType_##IDENTIFIER; \ + } + +MAKE_CPP_TYPE(float, float) +MAKE_CPP_TYPE(uint32, uint32_t) +MAKE_CPP_TYPE(uint8, uint8_t) +MAKE_CPP_TYPE(bool, bool) +MAKE_CPP_TYPE(ObjectIDHandle, BKE::ObjectIDHandle) +MAKE_CPP_TYPE(ImageIDHandle, BKE::ImageIDHandle) +MAKE_CPP_TYPE(int32, int32_t) +MAKE_CPP_TYPE(rgba_f, BLI::rgba_f) +MAKE_CPP_TYPE(float3, BLI::float3) +MAKE_CPP_TYPE(string, std::string) +MAKE_CPP_TYPE(SurfaceHook, BKE::SurfaceHook) + +} // namespace FN diff --git a/source/blender/functions/intern/cpp_types.h b/source/blender/functions/intern/cpp_types.h new file mode 100644 index 00000000000..34299f20beb --- /dev/null +++ b/source/blender/functions/intern/cpp_types.h @@ -0,0 +1,11 @@ +#ifndef __FN_CPP_TYPES_H__ +#define __FN_CPP_TYPES_H__ + +namespace FN { + +void init_cpp_types(); +void free_cpp_types(); + +} // namespace FN + +#endif /* __FN_CPP_TYPES_H__ */ diff --git a/source/blender/functions/intern/generic_array_ref.cc b/source/blender/functions/intern/generic_array_ref.cc new file mode 100644 index 00000000000..7004ee864da --- /dev/null +++ b/source/blender/functions/intern/generic_array_ref.cc @@ -0,0 +1,14 @@ +#include "FN_generic_array_ref.h" + +namespace FN { + +void GenericMutableArrayRef::RelocateUninitialized(GenericMutableArrayRef from, + GenericMutableArrayRef to) +{ + BLI::assert_same_size(from, to); + BLI_assert(from.type() == to.type()); + + from.m_type->relocate_to_uninitialized_n(from.buffer(), to.buffer(), from.size()); +} + +} // namespace FN diff --git a/source/blender/functions/intern/generic_tuple.cc b/source/blender/functions/intern/generic_tuple.cc new file mode 100644 index 00000000000..b58a05be9d4 --- /dev/null +++ b/source/blender/functions/intern/generic_tuple.cc @@ -0,0 +1,31 @@ +#include "FN_generic_tuple.h" + +namespace FN { + +GenericTupleInfo::GenericTupleInfo(Vector<const CPPType *> types) : m_types(std::move(types)) +{ + m_all_trivially_destructible = true; + m_size__data = 0; + m_alignment = 1; + for (const CPPType *type : m_types) { + uint size = type->size(); + uint alignment = type->alignment(); + uint alignment_mask = alignment - 1; + + m_alignment = std::max(m_alignment, alignment); + + m_size__data = (m_size__data + alignment_mask) & ~alignment_mask; + m_offsets.append(m_size__data); + m_size__data += size; + + if (!type->trivially_destructible()) { + m_all_trivially_destructible = false; + } + } + + m_do_align_mask = ~(uintptr_t)(m_alignment - 1); + m_size__data_and_init = m_size__data + m_types.size(); + m_size__alignable_data_and_init = m_size__data_and_init + m_alignment - 1; +} + +} // namespace FN diff --git a/source/blender/functions/intern/initialize.cc b/source/blender/functions/intern/initialize.cc new file mode 100644 index 00000000000..7361f4f55d0 --- /dev/null +++ b/source/blender/functions/intern/initialize.cc @@ -0,0 +1,18 @@ +#include "FN_initialize.h" +#include "cpp_types.h" +#include "multi_functions/global_functions.h" +#include "node_tree_multi_function_network/mappings.h" + +void FN_initialize(void) +{ + FN::init_cpp_types(); + FN::init_global_functions(); + FN::MFGeneration::init_function_tree_mf_mappings(); +} + +void FN_exit(void) +{ + FN::MFGeneration::free_function_tree_mf_mappings(); + FN::free_global_functions(); + FN::free_cpp_types(); +} diff --git a/source/blender/functions/intern/multi_function.cc b/source/blender/functions/intern/multi_function.cc new file mode 100644 index 00000000000..5eac4689dc5 --- /dev/null +++ b/source/blender/functions/intern/multi_function.cc @@ -0,0 +1,5 @@ +#include "FN_multi_function.h" + +namespace FN { + +} // namespace FN diff --git a/source/blender/functions/intern/multi_function_common_contexts.cc b/source/blender/functions/intern/multi_function_common_contexts.cc new file mode 100644 index 00000000000..6e752ddfdf8 --- /dev/null +++ b/source/blender/functions/intern/multi_function_common_contexts.cc @@ -0,0 +1,13 @@ +#include "FN_multi_function_common_contexts.h" + +#include "BKE_id_data_cache.h" +#include "BKE_id_handle.h" + +BLI_CREATE_CLASS_ID(FN::VertexPositionArray) +BLI_CREATE_CLASS_ID(FN::SceneTimeContext) +BLI_CREATE_CLASS_ID(FN::ParticleAttributesContext) +BLI_CREATE_CLASS_ID(BKE::IDHandleLookup) +BLI_CREATE_CLASS_ID(BKE::IDDataCache) +BLI_CREATE_CLASS_ID(FN::EmitterTimeInfoContext) +BLI_CREATE_CLASS_ID(FN::EventFilterEndTimeContext) +BLI_CREATE_CLASS_ID(FN::EventFilterDurationsContext) diff --git a/source/blender/functions/intern/multi_function_context.cc b/source/blender/functions/intern/multi_function_context.cc new file mode 100644 index 00000000000..c58331b4895 --- /dev/null +++ b/source/blender/functions/intern/multi_function_context.cc @@ -0,0 +1,5 @@ +#include "FN_multi_function_context.h" + +namespace FN { + +} // namespace FN diff --git a/source/blender/functions/intern/multi_function_network.cc b/source/blender/functions/intern/multi_function_network.cc new file mode 100644 index 00000000000..71620ec3e1f --- /dev/null +++ b/source/blender/functions/intern/multi_function_network.cc @@ -0,0 +1,638 @@ +#include <sstream> + +#include "FN_multi_function_network.h" + +#include "BLI_dot_export.h" +#include "BLI_set.h" +#include "BLI_stack_cxx.h" + +extern "C" { +void WM_clipboard_text_set(const char *buf, bool selection); +} + +namespace FN { + +using BLI::Map; +using BLI::ScopedVector; +using BLI::Set; +using BLI::Stack; + +/* MFNetwork Builder + **************************************/ + +MFNetworkBuilder::~MFNetworkBuilder() +{ + for (MFBuilderFunctionNode *node : m_function_nodes) { + for (MFBuilderInputSocket *input_socket : node->m_inputs) { + input_socket->~MFBuilderInputSocket(); + } + for (MFBuilderOutputSocket *output_socket : node->m_outputs) { + output_socket->~MFBuilderOutputSocket(); + } + node->~MFBuilderFunctionNode(); + } + + for (MFBuilderDummyNode *node : m_dummy_nodes) { + for (MFBuilderInputSocket *input_socket : node->m_inputs) { + input_socket->~MFBuilderInputSocket(); + } + for (MFBuilderOutputSocket *output_socket : node->m_outputs) { + output_socket->~MFBuilderOutputSocket(); + } + node->~MFBuilderDummyNode(); + } +} + +MFBuilderFunctionNode &MFNetworkBuilder::add_function(const MultiFunction &function) +{ + ScopedVector<uint> input_param_indices; + ScopedVector<uint> output_param_indices; + for (uint param_index : function.param_indices()) { + switch (function.param_type(param_index).interface_type()) { + case MFParamType::InterfaceType::Input: { + input_param_indices.append(param_index); + break; + } + case MFParamType::InterfaceType::Output: { + output_param_indices.append(param_index); + break; + } + case MFParamType::InterfaceType::Mutable: { + input_param_indices.append(param_index); + output_param_indices.append(param_index); + break; + } + } + } + + auto &node = *m_allocator.construct<MFBuilderFunctionNode>(); + m_function_nodes.add_new(&node); + + node.m_network = this; + node.m_is_dummy = false; + node.m_function = &function; + node.m_id = m_node_or_null_by_id.append_and_get_index(&node); + node.m_input_param_indices = m_allocator.construct_array_copy<uint>(input_param_indices); + node.m_output_param_indices = m_allocator.construct_array_copy<uint>(output_param_indices); + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFBuilderInputSocket>( + input_param_indices.size()); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFBuilderOutputSocket>( + output_param_indices.size()); + + for (uint i : input_param_indices.index_range()) { + MFParamType param = function.param_type(input_param_indices[i]); + BLI_assert(param.is_input_or_mutable()); + + MFBuilderInputSocket &input_socket = *node.m_inputs[i]; + input_socket.m_data_type = param.data_type(); + input_socket.m_node = &node; + input_socket.m_index = i; + input_socket.m_is_output = false; + input_socket.m_id = m_socket_or_null_by_id.append_and_get_index(&input_socket); + } + + for (uint i : output_param_indices.index_range()) { + MFParamType param = function.param_type(output_param_indices[i]); + BLI_assert(param.is_output_or_mutable()); + + MFBuilderOutputSocket &output_socket = *node.m_outputs[i]; + output_socket.m_data_type = param.data_type(); + output_socket.m_node = &node; + output_socket.m_index = i; + output_socket.m_is_output = true; + output_socket.m_id = m_socket_or_null_by_id.append_and_get_index(&output_socket); + } + + return node; +} + +MFBuilderDummyNode &MFNetworkBuilder::add_dummy(StringRef name, + ArrayRef<MFDataType> input_types, + ArrayRef<MFDataType> output_types, + ArrayRef<StringRef> input_names, + ArrayRef<StringRef> output_names) +{ + BLI::assert_same_size(input_types, input_names); + BLI::assert_same_size(output_types, output_names); + + auto &node = *m_allocator.construct<MFBuilderDummyNode>(); + m_dummy_nodes.add_new(&node); + + node.m_network = this; + node.m_is_dummy = true; + node.m_name = m_allocator.copy_string(name); + node.m_id = m_node_or_null_by_id.append_and_get_index(&node); + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFBuilderInputSocket>( + input_types.size()); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFBuilderOutputSocket>( + output_types.size()); + + node.m_input_names = m_allocator.allocate_array<StringRefNull>(input_types.size()); + node.m_output_names = m_allocator.allocate_array<StringRefNull>(output_types.size()); + + for (uint i : input_types.index_range()) { + MFBuilderInputSocket &input_socket = *node.m_inputs[i]; + input_socket.m_data_type = input_types[i]; + input_socket.m_node = &node; + input_socket.m_index = i; + input_socket.m_is_output = false; + input_socket.m_id = m_socket_or_null_by_id.append_and_get_index(&input_socket); + node.m_input_names[i] = m_allocator.copy_string(input_names[i]); + } + for (uint i : output_types.index_range()) { + MFBuilderOutputSocket &output_socket = *node.m_outputs[i]; + output_socket.m_data_type = output_types[i]; + output_socket.m_node = &node; + output_socket.m_index = i; + output_socket.m_is_output = true; + output_socket.m_id = m_socket_or_null_by_id.append_and_get_index(&output_socket); + node.m_output_names[i] = m_allocator.copy_string(output_names[i]); + } + return node; +} + +MFBuilderDummyNode &MFNetworkBuilder::add_input_dummy(StringRef name, MFBuilderInputSocket &socket) +{ + MFBuilderDummyNode &node = this->add_dummy(name, {}, {socket.data_type()}, {}, {"Value"}); + this->add_link(node.output(0), socket); + return node; +} + +MFBuilderDummyNode &MFNetworkBuilder::add_output_dummy(StringRef name, + MFBuilderOutputSocket &socket) +{ + MFBuilderDummyNode &node = this->add_dummy(name, {socket.data_type()}, {}, {"Value"}, {}); + this->add_link(socket, node.input(0)); + return node; +} + +void MFNetworkBuilder::add_link(MFBuilderOutputSocket &from, MFBuilderInputSocket &to) +{ + BLI_assert(to.origin() == nullptr); + BLI_assert(from.m_node->m_network == to.m_node->m_network); + BLI_assert(from.data_type() == to.data_type()); + from.m_targets.append(&to, m_allocator); + to.m_origin = &from; +} + +void MFNetworkBuilder::remove_link(MFBuilderOutputSocket &from, MFBuilderInputSocket &to) +{ + BLI_assert(from.m_targets.contains(&to)); + BLI_assert(to.m_origin == &from); + from.m_targets.remove_first_occurrence_and_reorder(&to); + to.m_origin = nullptr; +} + +void MFNetworkBuilder::replace_origin(MFBuilderOutputSocket &old_origin, + MFBuilderOutputSocket &new_origin) +{ + BLI_assert(&old_origin != &new_origin); + BLI_assert(old_origin.data_type() == new_origin.data_type()); + for (MFBuilderInputSocket *target : old_origin.targets()) { + BLI_assert(target->m_origin != nullptr); + target->m_origin = &new_origin; + new_origin.m_targets.append(target, m_allocator); + } + old_origin.m_targets.clear(); +} + +void MFNetworkBuilder::remove_node(MFBuilderNode &node) +{ + for (MFBuilderInputSocket *input_socket : node.inputs()) { + m_socket_or_null_by_id[input_socket->m_id] = nullptr; + MFBuilderOutputSocket *origin = input_socket->origin(); + if (origin != nullptr) { + origin->m_targets.remove_first_occurrence_and_reorder(input_socket); + } + input_socket->~MFBuilderInputSocket(); + } + for (MFBuilderOutputSocket *output_socket : node.outputs()) { + m_socket_or_null_by_id[output_socket->m_id] = nullptr; + for (MFBuilderInputSocket *target : output_socket->targets()) { + target->m_origin = nullptr; + } + output_socket->~MFBuilderOutputSocket(); + } + + m_node_or_null_by_id[node.m_id] = nullptr; + if (node.is_dummy()) { + MFBuilderDummyNode &dummy_node = node.as_dummy(); + m_dummy_nodes.remove(&dummy_node); + dummy_node.~MFBuilderDummyNode(); + } + else { + MFBuilderFunctionNode &function_node = node.as_function(); + m_function_nodes.remove(&function_node); + function_node.~MFBuilderFunctionNode(); + } +} + +void MFNetworkBuilder::remove_nodes(ArrayRef<MFBuilderNode *> nodes) +{ + for (MFBuilderNode *node : nodes) { + this->remove_node(*node); + } +} + +static bool set_tag_and_check_if_modified(bool &tag, bool new_value) +{ + if (tag != new_value) { + tag = new_value; + return true; + } + else { + return false; + } +} + +Array<bool> MFNetworkBuilder::find_nodes_to_the_right_of__inclusive__mask( + ArrayRef<MFBuilderNode *> nodes) +{ + Array<bool> is_to_the_right(this->node_id_amount(), false); + + for (MFBuilderNode *node : nodes) { + is_to_the_right[node->id()] = true; + } + + Stack<MFBuilderNode *> nodes_to_check = nodes; + while (!nodes_to_check.is_empty()) { + MFBuilderNode &node = *nodes_to_check.pop(); + + if (is_to_the_right[node.id()]) { + node.foreach_target_node([&](MFBuilderNode &other_node) { + if (set_tag_and_check_if_modified(is_to_the_right[other_node.id()], true)) { + nodes_to_check.push(&other_node); + } + }); + } + } + + return is_to_the_right; +} + +Array<bool> MFNetworkBuilder::find_nodes_to_the_left_of__inclusive__mask( + ArrayRef<MFBuilderNode *> nodes) +{ + Array<bool> is_to_the_left(this->node_id_amount(), false); + + for (MFBuilderNode *node : nodes) { + is_to_the_left[node->id()] = true; + } + + Stack<MFBuilderNode *> nodes_to_check = nodes; + while (!nodes_to_check.is_empty()) { + MFBuilderNode &node = *nodes_to_check.pop(); + + if (is_to_the_left[node.id()]) { + node.foreach_origin_node([&](MFBuilderNode &other_node) { + if (set_tag_and_check_if_modified(is_to_the_left[other_node.id()], true)) { + nodes_to_check.push(&other_node); + } + }); + } + } + + return is_to_the_left; +} + +Vector<MFBuilderNode *> MFNetworkBuilder::nodes_by_id_inverted_id_mask(ArrayRef<bool> id_mask) +{ + Vector<MFBuilderNode *> nodes; + for (uint id : id_mask.index_range()) { + if (this->node_id_is_valid(id)) { + if (!id_mask[id]) { + MFBuilderNode &node = this->node_by_id(id); + nodes.append(&node); + } + } + } + return nodes; +} + +Vector<MFBuilderNode *> MFNetworkBuilder::find_nodes_not_to_the_left_of__exclusive__vector( + ArrayRef<MFBuilderNode *> nodes) +{ + Array<bool> is_to_the_left = this->find_nodes_to_the_left_of__inclusive__mask(nodes); + Vector<MFBuilderNode *> result = this->nodes_by_id_inverted_id_mask(is_to_the_left); + return result; +} + +std::string MFNetworkBuilder::to_dot(const Set<MFBuilderNode *> &marked_nodes) +{ + using BLI::DotExport::NodeWithSocketsRef; + + BLI::DotExport::DirectedGraph digraph; + digraph.set_rankdir(BLI::DotExport::Attr_rankdir::LeftToRight); + Map<MFBuilderNode *, NodeWithSocketsRef> dot_nodes; + + Vector<MFBuilderNode *> all_nodes; + all_nodes.extend(m_function_nodes.as_ref()); + all_nodes.extend(m_dummy_nodes.as_ref()); + + for (MFBuilderNode *node : all_nodes) { + auto &dot_node = digraph.new_node(""); + + Vector<std::string> input_names; + for (MFBuilderInputSocket *socket : node->inputs()) { + input_names.append(socket->name()); + } + Vector<std::string> output_names; + for (MFBuilderOutputSocket *socket : node->outputs()) { + output_names.append(socket->name()); + } + + if (node->is_dummy()) { + dot_node.set_background_color("#DDDDFF"); + } + if (marked_nodes.contains(node)) { + dot_node.set_background_color("#99EE99"); + } + + dot_nodes.add_new(node, NodeWithSocketsRef(dot_node, node->name(), input_names, output_names)); + } + + for (MFBuilderNode *to_node : all_nodes) { + auto to_dot_node = dot_nodes.lookup(to_node); + + for (MFBuilderInputSocket *to_socket : to_node->inputs()) { + MFBuilderOutputSocket *from_socket = to_socket->origin(); + if (from_socket != nullptr) { + MFBuilderNode &from_node = from_socket->node(); + + auto from_dot_node = dot_nodes.lookup(&from_node); + + digraph.new_edge(from_dot_node.output(from_socket->index()), + to_dot_node.input(to_socket->index())); + } + } + } + + return digraph.to_dot_string(); +} + +void MFNetworkBuilder::to_dot__clipboard(const Set<MFBuilderNode *> &marked_nodes) +{ + std::string dot = this->to_dot(marked_nodes); + WM_clipboard_text_set(dot.c_str(), false); +} + +/* Network + ********************************************/ + +MFNetwork::MFNetwork(MFNetworkBuilder &builder) +{ + ArrayRef<MFBuilderFunctionNode *> builder_function_nodes = builder.function_nodes(); + ArrayRef<MFBuilderDummyNode *> builder_dummy_nodes = builder.dummy_nodes(); + + for (MFBuilderFunctionNode *builder_node : builder_function_nodes) { + uint input_amount = builder_node->inputs().size(); + uint output_amount = builder_node->outputs().size(); + + MFFunctionNode &node = *m_allocator.construct<MFFunctionNode>(); + + node.m_function = &builder_node->function(); + node.m_id = m_node_by_id.append_and_get_index(&node); + node.m_network = this; + node.m_is_dummy = false; + + node.m_input_param_indices = m_allocator.construct_array_copy( + builder_node->input_param_indices()); + node.m_output_param_indices = m_allocator.construct_array_copy( + builder_node->output_param_indices()); + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>(input_amount); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>( + output_amount); + + for (uint i : IndexRange(input_amount)) { + MFBuilderInputSocket &builder_socket = builder_node->input(i); + MFInputSocket &socket = *node.m_inputs[i]; + socket.m_id = m_socket_by_id.append_and_get_index(&socket); + socket.m_index = i; + socket.m_is_output = false; + socket.m_node = &node; + socket.m_data_type = builder_socket.data_type(); + + m_input_sockets.append(&socket); + } + for (uint i : IndexRange(output_amount)) { + MFBuilderOutputSocket &builder_socket = builder_node->output(i); + MFOutputSocket &socket = *node.m_outputs[i]; + socket.m_id = m_socket_by_id.append_and_get_index(&socket); + socket.m_index = i; + socket.m_is_output = true; + socket.m_node = &node; + socket.m_data_type = builder_socket.data_type(); + + m_output_sockets.append(&socket); + } + + m_function_nodes.append(&node); + } + + for (MFBuilderDummyNode *builder_node : builder_dummy_nodes) { + uint input_amount = builder_node->inputs().size(); + uint output_amount = builder_node->outputs().size(); + + MFDummyNode &node = *m_allocator.construct<MFDummyNode>(); + + node.m_id = m_node_by_id.append_and_get_index(&node); + node.m_network = this; + node.m_is_dummy = true; + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>(input_amount); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>( + output_amount); + + node.m_input_names = m_allocator.allocate_array<StringRefNull>(input_amount); + node.m_output_names = m_allocator.allocate_array<StringRefNull>(output_amount); + + for (uint i : IndexRange(input_amount)) { + MFBuilderInputSocket &builder_socket = builder_node->input(i); + MFInputSocket &socket = *node.m_inputs[i]; + socket.m_id = m_socket_by_id.append_and_get_index(&socket); + socket.m_index = i; + socket.m_is_output = false; + socket.m_node = &node; + socket.m_data_type = builder_socket.data_type(); + + m_input_sockets.append(&socket); + node.m_input_names[i] = m_allocator.copy_string(builder_socket.name()); + } + for (uint i : IndexRange(output_amount)) { + MFBuilderOutputSocket &builder_socket = builder_node->output(i); + MFOutputSocket &socket = *node.m_outputs[i]; + socket.m_id = m_socket_by_id.append_and_get_index(&socket); + socket.m_index = i; + socket.m_is_output = true; + socket.m_node = &node; + socket.m_data_type = builder_socket.data_type(); + + m_output_sockets.append(&socket); + node.m_output_names[i] = m_allocator.copy_string(builder_socket.name()); + } + + m_dummy_nodes.append(&node); + } + + for (uint node_index : builder_function_nodes.index_range()) { + MFFunctionNode *to_node = m_function_nodes[node_index]; + MFBuilderFunctionNode *to_builder_node = builder_function_nodes[node_index]; + this->create_links_to_node(builder, to_node, to_builder_node); + } + for (uint node_index : builder.dummy_nodes().index_range()) { + MFDummyNode *to_node = m_dummy_nodes[node_index]; + MFBuilderDummyNode *to_builder_node = builder_dummy_nodes[node_index]; + this->create_links_to_node(builder, to_node, to_builder_node); + } + + this->compute_max_dependency_depths(); +} + +void MFNetwork::create_links_to_node(MFNetworkBuilder &builder, + MFNode *to_node, + MFBuilderNode *to_builder_node) +{ + for (uint socket_index : to_builder_node->inputs().index_range()) { + MFInputSocket *to_socket = to_node->m_inputs[socket_index]; + MFBuilderInputSocket *to_builder_socket = &to_builder_node->input(socket_index); + this->create_link_to_socket(builder, to_socket, to_builder_socket); + } +} + +void MFNetwork::create_link_to_socket(MFNetworkBuilder &builder, + MFInputSocket *to_socket, + MFBuilderInputSocket *to_builder_socket) +{ + BLI_assert(to_socket->m_origin == nullptr); + + MFBuilderOutputSocket *from_builder_socket = to_builder_socket->origin(); + BLI_assert(from_builder_socket != nullptr); + + MFBuilderNode *from_builder_node = &from_builder_socket->node(); + + MFNode *from_node = nullptr; + if (from_builder_node->is_dummy()) { + uint dummy_node_index = builder.current_index_of(from_builder_node->as_dummy()); + from_node = m_dummy_nodes[dummy_node_index]; + } + else { + uint function_node_index = builder.current_index_of(from_builder_node->as_function()); + from_node = m_function_nodes[function_node_index]; + } + + uint from_index = from_builder_socket->index(); + MFOutputSocket *from_socket = from_node->m_outputs[from_index]; + + from_socket->m_targets.append(to_socket); + to_socket->m_origin = from_socket; +} + +BLI_NOINLINE void MFNetwork::compute_max_dependency_depths() +{ + m_max_dependency_depth_per_node = Array<uint>(m_node_by_id.size(), UINT32_MAX); + Array<uint> &max_depths = m_max_dependency_depth_per_node; + + for (const MFDummyNode *node : this->dummy_nodes()) { + max_depths[node->id()] = 0; + } + + Stack<const MFNode *> nodes_to_check; + nodes_to_check.push_multiple(this->function_nodes()); + + while (!nodes_to_check.is_empty()) { + const MFNode ¤t = *nodes_to_check.peek(); + if (max_depths[current.id()] != UINT32_MAX) { + nodes_to_check.pop(); + continue; + } + + bool all_inputs_computed = true; + uint max_incoming_depth = 0; + current.foreach_origin_node([&](const MFNode &origin_node) { + uint origin_depth = max_depths[origin_node.id()]; + if (origin_depth == UINT32_MAX) { + nodes_to_check.push(&origin_node); + all_inputs_computed = false; + } + else { + max_incoming_depth = std::max(max_incoming_depth, origin_depth); + } + }); + + if (!all_inputs_computed) { + continue; + } + + nodes_to_check.pop(); + max_depths[current.id()] = max_incoming_depth + 1; + } +} + +MFNetwork::~MFNetwork() +{ + for (auto node : m_function_nodes) { + node->~MFFunctionNode(); + } + for (auto node : m_dummy_nodes) { + node->~MFDummyNode(); + } + for (auto socket : m_input_sockets) { + socket->~MFInputSocket(); + } + for (auto socket : m_output_sockets) { + socket->~MFOutputSocket(); + } +} + +Vector<const MFOutputSocket *> MFNetwork::find_dummy_dependencies( + ArrayRef<const MFInputSocket *> sockets) const +{ + Vector<const MFOutputSocket *> dummy_dependencies; + Set<const MFOutputSocket *> found_outputs; + Stack<const MFInputSocket *> inputs_to_check = sockets; + + while (!inputs_to_check.is_empty()) { + const MFInputSocket &input_socket = *inputs_to_check.pop(); + const MFOutputSocket &origin_socket = input_socket.origin(); + + if (found_outputs.add(&origin_socket)) { + const MFNode &origin_node = origin_socket.node(); + if (origin_node.is_dummy()) { + dummy_dependencies.append(&origin_socket); + } + else { + inputs_to_check.push_multiple(origin_node.inputs()); + } + } + } + + return dummy_dependencies; +} + +Vector<const MFFunctionNode *> MFNetwork::find_function_dependencies( + ArrayRef<const MFInputSocket *> sockets) const +{ + Vector<const MFFunctionNode *> function_dependencies; + Set<const MFNode *> found_nodes; + Stack<const MFInputSocket *> inputs_to_check = sockets; + + while (!inputs_to_check.is_empty()) { + const MFInputSocket &input_socket = *inputs_to_check.pop(); + const MFOutputSocket &origin_socket = input_socket.origin(); + const MFNode &origin_node = origin_socket.node(); + + if (found_nodes.add(&origin_node)) { + if (origin_node.is_function()) { + function_dependencies.append(&origin_node.as_function()); + inputs_to_check.push_multiple(origin_node.inputs()); + } + } + } + + return function_dependencies; +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_function_network_optimization.cc b/source/blender/functions/intern/multi_function_network_optimization.cc new file mode 100644 index 00000000000..380a65e7ecf --- /dev/null +++ b/source/blender/functions/intern/multi_function_network_optimization.cc @@ -0,0 +1,280 @@ +#include "FN_multi_function_network.h" +#include "FN_multi_function_network_optimization.h" +#include "FN_multi_functions.h" + +#include "BLI_multi_map.h" +#include "BLI_rand.h" +#include "BLI_rand_cxx.h" +#include "BLI_stack_cxx.h" + +namespace FN { + +using BLI::MultiMap; +using BLI::Stack; + +static uint32_t get_function_hash(const MultiFunction &fn) +{ + return POINTER_AS_UINT(&fn); +} + +static bool functions_are_equal(const MultiFunction &a, const MultiFunction &b) +{ + return &a == &b; +} + +/* TODO: Use approriate caching to avoid doing the same checks many times. */ +static bool outputs_have_same_value(MFBuilderOutputSocket &a, MFBuilderOutputSocket &b) +{ + if (a.index() != b.index()) { + return false; + } + if (&a.node() == &b.node()) { + return true; + } + if (a.node().is_dummy() || b.node().is_dummy()) { + return false; + } + if (!functions_are_equal(a.node().as_function().function(), b.node().as_function().function())) { + return false; + } + for (uint i : a.node().inputs().index_range()) { + MFBuilderOutputSocket *origin_a = a.node().input(i).origin(); + MFBuilderOutputSocket *origin_b = b.node().input(i).origin(); + if (origin_a == nullptr || origin_b == nullptr) { + return false; + } + if (!outputs_have_same_value(*origin_a, *origin_b)) { + return false; + } + } + return true; +} + +void optimize_network__remove_duplicates(MFNetworkBuilder &network_builder) +{ + Array<uint32_t> hash_by_output_socket(network_builder.socket_id_amount()); + Array<bool> node_outputs_are_hashed(network_builder.node_id_amount(), false); + + RNG *rng = BLI_rng_new(0); + + for (MFBuilderDummyNode *node : network_builder.dummy_nodes()) { + for (MFBuilderOutputSocket *output_socket : node->outputs()) { + uint32_t output_hash = BLI_rng_get_uint(rng); + hash_by_output_socket[output_socket->id()] = output_hash; + } + node_outputs_are_hashed[node->id()] = true; + } + + Stack<MFBuilderFunctionNode *> nodes_to_check = network_builder.function_nodes(); + while (!nodes_to_check.is_empty()) { + MFBuilderFunctionNode &node = *nodes_to_check.peek(); + if (node_outputs_are_hashed[node.id()]) { + nodes_to_check.pop(); + continue; + } + + bool all_dependencies_ready = true; + node.foreach_origin_node([&](MFBuilderNode &origin_node) { + if (!node_outputs_are_hashed[origin_node.id()]) { + all_dependencies_ready = false; + nodes_to_check.push(&origin_node.as_function()); + } + }); + + if (!all_dependencies_ready) { + continue; + } + + uint32_t combined_inputs_hash = BLI_RAND_PER_LINE_UINT32; + for (MFBuilderInputSocket *input_socket : node.inputs()) { + MFBuilderOutputSocket *origin = input_socket->origin(); + uint32_t input_hash; + if (origin == nullptr) { + input_hash = BLI_rng_get_uint(rng); + } + else { + input_hash = hash_by_output_socket[origin->id()]; + } + combined_inputs_hash = combined_inputs_hash * BLI_RAND_PER_LINE_UINT32 + input_hash; + } + + uint32_t function_hash = get_function_hash(node.function()); + uint32_t node_hash = combined_inputs_hash * BLI_RAND_PER_LINE_UINT32 + function_hash; + + for (MFBuilderOutputSocket *output_socket : node.outputs()) { + uint32_t output_hash = node_hash * + (345741 + BLI_RAND_PER_LINE_UINT32 * output_socket->index()); + hash_by_output_socket[output_socket->id()] = output_hash; + } + + nodes_to_check.pop(); + node_outputs_are_hashed[node.id()] = true; + } + + MultiMap<uint32_t, MFBuilderOutputSocket *> outputs_by_hash; + for (uint id : hash_by_output_socket.index_range()) { + MFBuilderSocket *socket = network_builder.sockets_or_null_by_id()[id]; + if (socket != nullptr && socket->is_output()) { + uint32_t output_hash = hash_by_output_socket[id]; + outputs_by_hash.add(output_hash, &socket->as_output()); + } + } + + Vector<MFBuilderOutputSocket *> remaining_sockets; + + outputs_by_hash.foreach_item( + [&](uint32_t UNUSED(hash), ArrayRef<MFBuilderOutputSocket *> outputs_with_hash) { + if (outputs_with_hash.size() <= 1) { + return; + } + + remaining_sockets.clear(); + ArrayRef<MFBuilderOutputSocket *> outputs_to_check = outputs_with_hash; + while (outputs_to_check.size() >= 2) { + MFBuilderOutputSocket &deduplicated_output = *outputs_to_check[0]; + for (MFBuilderOutputSocket *socket : outputs_to_check.drop_front(1)) { + if (outputs_have_same_value(deduplicated_output, *socket)) { + network_builder.replace_origin(*socket, deduplicated_output); + } + else { + remaining_sockets.append(socket); + } + } + + outputs_to_check = remaining_sockets; + } + }); + + BLI_rng_free(rng); +} + +void optimize_network__remove_unused_nodes(MFNetworkBuilder &network_builder) +{ + ArrayRef<MFBuilderNode *> dummy_nodes = network_builder.dummy_nodes(); + Vector<MFBuilderNode *> nodes = network_builder.find_nodes_not_to_the_left_of__exclusive__vector( + dummy_nodes); + network_builder.remove_nodes(nodes); +} + +void optimize_network__constant_folding(MFNetworkBuilder &network_builder, + ResourceCollector &resources) +{ + Vector<MFBuilderNode *> non_constant_nodes; + non_constant_nodes.extend(network_builder.dummy_nodes()); + for (MFBuilderFunctionNode *node : network_builder.function_nodes()) { + if (node->function().depends_on_context()) { + non_constant_nodes.append(node); + } + } + + Array<bool> node_is_not_constant = network_builder.find_nodes_to_the_right_of__inclusive__mask( + non_constant_nodes); + Vector<MFBuilderNode *> constant_builder_nodes = network_builder.nodes_by_id_inverted_id_mask( + node_is_not_constant); + // network_builder.to_dot__clipboard(constant_builder_nodes.as_ref()); + + Vector<MFBuilderDummyNode *> dummy_nodes_to_compute; + for (MFBuilderNode *node : constant_builder_nodes) { + if (node->inputs().size() == 0) { + continue; + } + + for (MFBuilderOutputSocket *output_socket : node->outputs()) { + MFDataType data_type = output_socket->data_type(); + + for (MFBuilderInputSocket *target_socket : output_socket->targets()) { + MFBuilderNode &target_node = target_socket->node(); + if (!node_is_not_constant[target_node.id()]) { + continue; + } + + MFBuilderDummyNode &dummy_node = network_builder.add_dummy( + "Dummy", {data_type}, {}, {"Value"}, {}); + network_builder.add_link(*output_socket, dummy_node.input(0)); + dummy_nodes_to_compute.append(&dummy_node); + break; + } + } + } + + if (dummy_nodes_to_compute.size() == 0) { + return; + } + + MFNetwork network{network_builder}; + + Vector<const MFInputSocket *> sockets_to_compute; + for (MFBuilderDummyNode *dummy_node : dummy_nodes_to_compute) { + uint node_index = network_builder.current_index_of(*dummy_node); + sockets_to_compute.append(&network.dummy_nodes()[node_index]->input(0)); + } + + MF_EvaluateNetwork network_function{{}, sockets_to_compute}; + + MFContextBuilder context_builder; + MFParamsBuilder params_builder{network_function, 1}; + + for (uint param_index : network_function.param_indices()) { + MFParamType param_type = network_function.param_type(param_index); + BLI_assert(param_type.is_output()); + MFDataType data_type = param_type.data_type(); + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &cpp_type = data_type.single__cpp_type(); + void *buffer = resources.allocate(cpp_type.size(), cpp_type.alignment()); + GenericMutableArrayRef array{cpp_type, buffer, 1}; + params_builder.add_single_output(array); + break; + } + case MFDataType::Vector: { + const CPPType &cpp_base_type = data_type.vector__cpp_base_type(); + GenericVectorArray &vector_array = resources.construct<GenericVectorArray>( + "constant vector", cpp_base_type, 1); + params_builder.add_vector_output(vector_array); + break; + } + } + } + + network_function.call(IndexRange(1), params_builder, context_builder); + + for (uint param_index : network_function.param_indices()) { + MFParamType param_type = network_function.param_type(param_index); + MFDataType data_type = param_type.data_type(); + + const MultiFunction *constant_fn = nullptr; + + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &cpp_type = data_type.single__cpp_type(); + + GenericMutableArrayRef array = params_builder.computed_array(param_index); + void *buffer = array.buffer(); + resources.add(buffer, array.type().destruct_cb(), "Constant folded value"); + + constant_fn = &resources.construct<MF_GenericConstantValue>( + "Constant folded function", cpp_type, buffer); + break; + } + case MFDataType::Vector: { + GenericVectorArray &vector_array = params_builder.computed_vector_array(param_index); + GenericArrayRef array = vector_array[0]; + constant_fn = &resources.construct<MF_GenericConstantVector>("Constant folded function", + array); + break; + } + } + + MFBuilderFunctionNode &folded_node = network_builder.add_function(*constant_fn); + MFBuilderOutputSocket &original_socket = + *dummy_nodes_to_compute[param_index]->input(0).origin(); + network_builder.replace_origin(original_socket, folded_node.output(0)); + } + + for (MFBuilderDummyNode *dummy_node : dummy_nodes_to_compute) { + network_builder.remove_node(*dummy_node); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/constants.cc b/source/blender/functions/intern/multi_functions/constants.cc new file mode 100644 index 00000000000..0cf59b48da5 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/constants.cc @@ -0,0 +1,74 @@ +#include "constants.h" + +namespace FN { + +void MF_GenericConstantValue::value_to_string(std::stringstream &ss, + const CPPType &type, + const void *value) +{ + if (type == CPPType_float) { + ss << (*(float *)value); + } + else if (type == CPPType_int32) { + ss << *(int *)value; + } + else if (type == CPPType_float3) { + ss << *(BLI::float3 *)value; + } + else if (type == CPP_TYPE<bool>()) { + ss << ((*(bool *)value) ? "true" : "false"); + } + else if (type == CPPType_string) { + ss << "\"" << *(std::string *)value << "\""; + } + else { + ss << "Value"; + } +} + +MF_GenericConstantValue::MF_GenericConstantValue(const CPPType &type, const void *value) + : m_value(value) +{ + MFSignatureBuilder signature = this->get_builder("Constant " + type.name()); + std::stringstream ss; + MF_GenericConstantValue::value_to_string(ss, type, value); + signature.single_output(ss.str(), type); +} + +void MF_GenericConstantValue::call(IndexMask mask, + MFParams params, + MFContext UNUSED(context)) const +{ + GenericMutableArrayRef r_value = params.uninitialized_single_output(0); + r_value.type().fill_uninitialized_indices(m_value, r_value.buffer(), mask); +} + +MF_GenericConstantVector::MF_GenericConstantVector(GenericArrayRef array) : m_array(array) +{ + const CPPType &type = array.type(); + MFSignatureBuilder signature = this->get_builder("Constant " + type.name() + " List"); + std::stringstream ss; + ss << "["; + uint max_amount = 5; + for (uint i : IndexRange(std::min(max_amount, array.size()))) { + MF_GenericConstantValue::value_to_string(ss, type, array[i]); + ss << ", "; + } + if (max_amount < array.size()) { + ss << "..."; + } + ss << "]"; + signature.vector_output(ss.str(), type); +} + +void MF_GenericConstantVector::call(IndexMask mask, + MFParams params, + MFContext UNUSED(context)) const +{ + GenericVectorArray &r_vectors = params.vector_output(0); + for (uint i : mask) { + r_vectors.extend_single__copy(i, m_array); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/constants.h b/source/blender/functions/intern/multi_functions/constants.h new file mode 100644 index 00000000000..257e0012b3c --- /dev/null +++ b/source/blender/functions/intern/multi_functions/constants.h @@ -0,0 +1,61 @@ +#pragma once + +#include "FN_multi_function.h" + +#include <sstream> + +#include "BLI_float3.h" +#include "BLI_hash.h" +#include "BLI_rand_cxx.h" + +namespace FN { + +/** + * The value buffer passed into the constructor should have a longer lifetime than the + * function itself. + */ +class MF_GenericConstantValue : public MultiFunction { + private: + const void *m_value; + + public: + MF_GenericConstantValue(const CPPType &type, const void *value); + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override; + + static void value_to_string(std::stringstream &ss, const CPPType &type, const void *value); +}; + +/** + * The passed in buffer has to have a longer lifetime than the function itself. + */ +class MF_GenericConstantVector : public MultiFunction { + private: + GenericArrayRef m_array; + + public: + MF_GenericConstantVector(GenericArrayRef array); + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override; +}; + +template<typename T> class MF_ConstantValue : public MultiFunction { + private: + T m_value; + + public: + MF_ConstantValue(T value) : m_value(std::move(value)) + { + MFSignatureBuilder signature = this->get_builder("Constant " + CPP_TYPE<T>().name()); + std::stringstream ss; + MF_GenericConstantValue::value_to_string(ss, CPP_TYPE<T>(), (const void *)&m_value); + signature.single_output<T>(ss.str()); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + MutableArrayRef<T> output = params.uninitialized_single_output<T>(0); + + mask.foreach_index([&](uint i) { new (output.begin() + i) T(m_value); }); + } +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/customizable.h b/source/blender/functions/intern/multi_functions/customizable.h new file mode 100644 index 00000000000..bbc2839d767 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/customizable.h @@ -0,0 +1,197 @@ +#pragma once + +#include <sstream> + +#include "FN_multi_function.h" + +namespace FN { + +template<typename FromT, typename ToT> class MF_Convert : public MultiFunction { + public: + MF_Convert() + { + MFSignatureBuilder signature = this->get_builder(CPP_TYPE<FromT>().name() + " to " + + CPP_TYPE<ToT>().name()); + signature.single_input<FromT>("Input"); + signature.single_output<ToT>("Output"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VirtualListRef<FromT> inputs = params.readonly_single_input<FromT>(0, "Input"); + MutableArrayRef<ToT> outputs = params.uninitialized_single_output<ToT>(1, "Output"); + + for (uint i : mask.indices()) { + const FromT &from_value = inputs[i]; + new (outputs.begin() + i) ToT(from_value); + } + } +}; + +template<typename InT, typename OutT> class MF_Custom_In1_Out1 final : public MultiFunction { + private: + using FunctionT = + std::function<void(IndexMask mask, VirtualListRef<InT>, MutableArrayRef<OutT>)>; + FunctionT m_fn; + + public: + MF_Custom_In1_Out1(StringRef name, FunctionT fn) : m_fn(std::move(fn)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_input<InT>("Input"); + signature.single_output<OutT>("Output"); + } + + template<typename ElementFuncT> + MF_Custom_In1_Out1(StringRef name, ElementFuncT element_fn) + : MF_Custom_In1_Out1(name, MF_Custom_In1_Out1::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, VirtualListRef<InT> inputs, MutableArrayRef<OutT> outputs) { + if (inputs.is_non_single_full_array()) { + ArrayRef<InT> in_array = inputs.as_full_array(); + mask.foreach_index([=](uint i) { outputs[i] = element_fn(in_array[i]); }); + } + else if (inputs.is_single_element()) { + InT in_single = inputs.as_single_element(); + outputs.fill_indices(mask.indices(), element_fn(in_single)); + } + else { + mask.foreach_index([=](uint i) { outputs[i] = element_fn(inputs[i]); }); + } + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VirtualListRef<InT> inputs = params.readonly_single_input<InT>(0); + MutableArrayRef<OutT> outputs = params.uninitialized_single_output<OutT>(1); + m_fn(mask, inputs, outputs); + } +}; + +template<typename InT1, typename InT2, typename OutT> +class MF_Custom_In2_Out1 final : public MultiFunction { + private: + using FunctionT = std::function<void( + IndexMask mask, VirtualListRef<InT1>, VirtualListRef<InT2>, MutableArrayRef<OutT>)>; + + FunctionT m_fn; + + public: + MF_Custom_In2_Out1(StringRef name, FunctionT fn) : m_fn(std::move(fn)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_input<InT1>("Input 1"); + signature.single_input<InT2>("Input 2"); + signature.single_output<OutT>("Output"); + } + + template<typename ElementFuncT> + MF_Custom_In2_Out1(StringRef name, ElementFuncT element_fn) + : MF_Custom_In2_Out1(name, MF_Custom_In2_Out1::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, + VirtualListRef<InT1> inputs1, + VirtualListRef<InT2> inputs2, + MutableArrayRef<OutT> outputs) -> void { + if (inputs1.is_non_single_full_array() && inputs2.is_non_single_full_array()) { + ArrayRef<InT1> in1_array = inputs1.as_full_array(); + ArrayRef<InT2> in2_array = inputs2.as_full_array(); + mask.foreach_index( + [=](uint i) { new (&outputs[i]) OutT(element_fn(in1_array[i], in2_array[i])); }); + } + else if (inputs1.is_non_single_full_array() && inputs2.is_single_element()) { + ArrayRef<InT1> in1_array = inputs1.as_full_array(); + InT2 in2_single = inputs2.as_single_element(); + mask.foreach_index( + [=](uint i) { new (&outputs[i]) OutT(element_fn(in1_array[i], in2_single)); }); + } + else if (inputs1.is_single_element() && inputs2.is_non_single_full_array()) { + InT1 in1_single = inputs1.as_single_element(); + ArrayRef<InT2> in2_array = inputs2.as_full_array(); + mask.foreach_index( + [=](uint i) { new (&outputs[i]) OutT(element_fn(in1_single, in2_array[i])); }); + } + else if (inputs1.is_single_element() && inputs2.is_single_element()) { + InT1 in1_single = inputs1.as_single_element(); + InT2 in2_single = inputs2.as_single_element(); + OutT out_single = element_fn(in1_single, in2_single); + outputs.fill_indices(mask.indices(), out_single); + } + else { + mask.foreach_index( + [=](uint i) { new (&outputs[i]) OutT(element_fn(inputs1[i], inputs2[i])); }); + } + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VirtualListRef<InT1> inputs1 = params.readonly_single_input<InT1>(0); + VirtualListRef<InT2> inputs2 = params.readonly_single_input<InT2>(1); + MutableArrayRef<OutT> outputs = params.uninitialized_single_output<OutT>(2); + m_fn(mask, inputs1, inputs2, outputs); + } +}; + +template<typename T> class MF_VariadicMath final : public MultiFunction { + private: + using FunctionT = std::function<void( + IndexMask mask, VirtualListRef<T>, VirtualListRef<T>, MutableArrayRef<T>)>; + + uint m_input_amount; + FunctionT m_fn; + + public: + MF_VariadicMath(StringRef name, uint input_amount, FunctionT fn) + : m_input_amount(input_amount), m_fn(fn) + { + BLI_STATIC_ASSERT(std::is_trivial<T>::value, ""); + BLI_assert(input_amount >= 1); + MFSignatureBuilder signature = this->get_builder(name); + for (uint i = 0; i < m_input_amount; i++) { + signature.single_input<T>("Input"); + } + signature.single_output<T>("Output"); + } + + template<typename ElementFuncT> + MF_VariadicMath(StringRef name, uint input_amount, ElementFuncT element_func) + : MF_VariadicMath( + name, input_amount, MF_Custom_In2_Out1<T, T, T>::create_function(element_func)) + { + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + MutableArrayRef<T> outputs = params.uninitialized_single_output<T>(m_input_amount, "Output"); + + if (m_input_amount == 1) { + VirtualListRef<T> inputs = params.readonly_single_input<T>(0, "Input"); + for (uint i : mask.indices()) { + outputs[i] = inputs[i]; + } + } + else { + BLI_assert(m_input_amount >= 2); + VirtualListRef<T> inputs0 = params.readonly_single_input<T>(0, "Input"); + VirtualListRef<T> inputs1 = params.readonly_single_input<T>(1, "Input"); + m_fn(mask, inputs0, inputs1, outputs); + + for (uint param_index = 2; param_index < m_input_amount; param_index++) { + VirtualListRef<T> inputs = params.readonly_single_input<T>(param_index, "Input"); + m_fn(mask, VirtualListRef<T>::FromFullArray(outputs), inputs, outputs); + } + } + } +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/global_functions.cc b/source/blender/functions/intern/multi_functions/global_functions.cc new file mode 100644 index 00000000000..68c87357c2d --- /dev/null +++ b/source/blender/functions/intern/multi_functions/global_functions.cc @@ -0,0 +1,40 @@ +#include "global_functions.h" +#include "FN_multi_functions.h" + +namespace FN { + +const MultiFunction *MF_GLOBAL_add_floats_2 = nullptr; +const MultiFunction *MF_GLOBAL_multiply_floats_2 = nullptr; +const MultiFunction *MF_GLOBAL_subtract_floats = nullptr; +const MultiFunction *MF_GLOBAL_safe_division_floats = nullptr; +const MultiFunction *MF_GLOBAL_sin_float = nullptr; +const MultiFunction *MF_GLOBAL_cos_float = nullptr; + +void init_global_functions() +{ + MF_GLOBAL_add_floats_2 = new MF_Custom_In2_Out1<float, float, float>( + "add 2 floats", [](float a, float b) { return a + b; }); + MF_GLOBAL_multiply_floats_2 = new MF_Custom_In2_Out1<float, float, float>( + "multiply 2 floats", [](float a, float b) { return a * b; }); + MF_GLOBAL_subtract_floats = new MF_Custom_In2_Out1<float, float, float>( + "subtract 2 floats", [](float a, float b) { return a - b; }); + MF_GLOBAL_safe_division_floats = new MF_Custom_In2_Out1<float, float, float>( + "safe divide 2 floats", [](float a, float b) { return (b != 0.0f) ? a / b : 0.0f; }); + + MF_GLOBAL_sin_float = new MF_Custom_In1_Out1<float, float>("sin float", + [](float a) { return std::sin(a); }); + MF_GLOBAL_cos_float = new MF_Custom_In1_Out1<float, float>("cos float", + [](float a) { return std::cos(a); }); +} + +void free_global_functions() +{ + delete MF_GLOBAL_add_floats_2; + delete MF_GLOBAL_multiply_floats_2; + delete MF_GLOBAL_subtract_floats; + delete MF_GLOBAL_safe_division_floats; + delete MF_GLOBAL_sin_float; + delete MF_GLOBAL_cos_float; +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/global_functions.h b/source/blender/functions/intern/multi_functions/global_functions.h new file mode 100644 index 00000000000..a36894085d1 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/global_functions.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +void init_global_functions(); +void free_global_functions(); + +extern const MultiFunction *MF_GLOBAL_add_floats_2; +extern const MultiFunction *MF_GLOBAL_multiply_floats_2; +extern const MultiFunction *MF_GLOBAL_subtract_floats; +extern const MultiFunction *MF_GLOBAL_safe_division_floats; +extern const MultiFunction *MF_GLOBAL_sin_float; +extern const MultiFunction *MF_GLOBAL_cos_float; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/lists.cc b/source/blender/functions/intern/multi_functions/lists.cc new file mode 100644 index 00000000000..11354b776cc --- /dev/null +++ b/source/blender/functions/intern/multi_functions/lists.cc @@ -0,0 +1,158 @@ +#include "lists.h" + +namespace FN { + +MF_PackList::MF_PackList(const CPPType &base_type, ArrayRef<bool> input_list_status) + : m_base_type(base_type), m_input_list_status(input_list_status) +{ + MFSignatureBuilder signature = this->get_builder("Pack List"); + if (m_input_list_status.size() == 0) { + /* Output just an empty list. */ + signature.vector_output("List", m_base_type); + } + else if (this->input_is_list(0)) { + /* Extend the first incoming list. */ + signature.mutable_vector("List", m_base_type); + for (uint i = 1; i < m_input_list_status.size(); i++) { + if (this->input_is_list(i)) { + signature.vector_input("List", m_base_type); + } + else { + signature.single_input("Value", m_base_type); + } + } + } + else { + /* Create a new list and append everything. */ + for (uint i = 0; i < m_input_list_status.size(); i++) { + if (this->input_is_list(i)) { + signature.vector_input("List", m_base_type); + } + else { + signature.single_input("Value", m_base_type); + } + } + signature.vector_output("List", m_base_type); + } +} + +void MF_PackList::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + GenericVectorArray *vector_array; + bool is_mutating_first_list; + if (m_input_list_status.size() == 0) { + vector_array = ¶ms.vector_output(0, "List"); + is_mutating_first_list = false; + } + else if (this->input_is_list(0)) { + vector_array = ¶ms.mutable_vector(0, "List"); + is_mutating_first_list = true; + } + else { + vector_array = ¶ms.vector_output(m_input_list_status.size(), "List"); + is_mutating_first_list = false; + } + + uint first_index = is_mutating_first_list ? 1 : 0; + for (uint input_index = first_index; input_index < m_input_list_status.size(); input_index++) { + if (this->input_is_list(input_index)) { + GenericVirtualListListRef list = params.readonly_vector_input(input_index, "List"); + for (uint i : mask.indices()) { + vector_array->extend_single__copy(i, list[i]); + } + } + else { + GenericVirtualListRef list = params.readonly_single_input(input_index, "Value"); + for (uint i : mask.indices()) { + vector_array->append_single__copy(i, list[i]); + } + } + } +} + +bool MF_PackList::input_is_list(uint index) const +{ + return m_input_list_status[index]; +} + +MF_GetListElement::MF_GetListElement(const CPPType &base_type) : m_base_type(base_type) +{ + MFSignatureBuilder signature = this->get_builder("Get List Element"); + signature.vector_input("List", m_base_type); + signature.single_input<int>("Index"); + signature.single_input("Fallback", m_base_type); + signature.single_output("Value", m_base_type); +} + +void MF_GetListElement::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + GenericVirtualListListRef lists = params.readonly_vector_input(0, "List"); + VirtualListRef<int> indices = params.readonly_single_input<int>(1, "Index"); + GenericVirtualListRef fallbacks = params.readonly_single_input(2, "Fallback"); + + GenericMutableArrayRef r_output_values = params.uninitialized_single_output(3, "Value"); + + for (uint i : mask.indices()) { + int index = indices[i]; + if (index >= 0) { + GenericVirtualListRef list = lists[i]; + if (index < list.size()) { + m_base_type.copy_to_uninitialized(list[index], r_output_values[i]); + continue; + } + } + m_base_type.copy_to_uninitialized(fallbacks[i], r_output_values[i]); + } +} + +MF_GetListElements::MF_GetListElements(const CPPType &base_type) : m_base_type(base_type) +{ + MFSignatureBuilder signature = this->get_builder("Get List Elements"); + signature.vector_input("List", m_base_type); + signature.vector_input<int>("Indices"); + signature.single_input("Fallback", m_base_type); + signature.vector_output("Values", m_base_type); +} + +void MF_GetListElements::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + GenericVirtualListListRef lists = params.readonly_vector_input(0, "List"); + VirtualListListRef<int> indices = params.readonly_vector_input<int>(1, "Indices"); + GenericVirtualListRef fallbacks = params.readonly_single_input(2, "Fallback"); + + GenericVectorArray &r_output_values = params.vector_output(3, "Values"); + + for (uint i : mask.indices()) { + GenericVirtualListRef list = lists[i]; + VirtualListRef<int> sub_indices = indices[i]; + GenericMutableArrayRef values = r_output_values.allocate_single(i, sub_indices.size()); + for (uint j = 0; j < sub_indices.size(); j++) { + int index = sub_indices[j]; + if (index >= 0 && index < list.size()) { + values.copy_in__uninitialized(j, list[index]); + } + else { + values.copy_in__uninitialized(j, fallbacks[i]); + } + } + } +} + +MF_ListLength::MF_ListLength(const CPPType &base_type) : m_base_type(base_type) +{ + MFSignatureBuilder signature = this->get_builder("List Length"); + signature.vector_input("List", m_base_type); + signature.single_output<int>("Length"); +} + +void MF_ListLength::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + GenericVirtualListListRef lists = params.readonly_vector_input(0, "List"); + MutableArrayRef<int> lengths = params.uninitialized_single_output<int>(1, "Length"); + + for (uint i : mask.indices()) { + lengths[i] = lists[i].size(); + } +} + +}; // namespace FN diff --git a/source/blender/functions/intern/multi_functions/lists.h b/source/blender/functions/intern/multi_functions/lists.h new file mode 100644 index 00000000000..8f89a3d7e69 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/lists.h @@ -0,0 +1,110 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +class MF_GetListElement final : public MultiFunction { + private: + const CPPType &m_base_type; + + public: + MF_GetListElement(const CPPType &base_type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_GetListElements final : public MultiFunction { + private: + const CPPType &m_base_type; + + public: + MF_GetListElements(const CPPType &base_type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_ListLength final : public MultiFunction { + private: + const CPPType &m_base_type; + + public: + MF_ListLength(const CPPType &base_type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_PackList final : public MultiFunction { + private: + const CPPType &m_base_type; + Vector<bool> m_input_list_status; + + public: + MF_PackList(const CPPType &base_type, ArrayRef<bool> input_list_status); + void call(IndexMask mask, MFParams params, MFContext context) const override; + + private: + bool input_is_list(uint index) const; +}; + +template<typename T> class MF_EmptyList : public MultiFunction { + public: + MF_EmptyList() + { + MFSignatureBuilder signature = this->get_builder("Empty List - " + CPP_TYPE<T>().name()); + signature.vector_output<T>("Output"); + } + + void call(IndexMask UNUSED(mask), + MFParams UNUSED(params), + MFContext UNUSED(context)) const override + { + } +}; + +template<typename FromT, typename ToT> class MF_ConvertList : public MultiFunction { + public: + MF_ConvertList() + { + MFSignatureBuilder signature = this->get_builder(CPP_TYPE<FromT>().name() + " List to " + + CPP_TYPE<ToT>().name() + " List"); + signature.vector_input<FromT>("Inputs"); + signature.vector_output<ToT>("Outputs"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VirtualListListRef<FromT> inputs = params.readonly_vector_input<FromT>(0, "Inputs"); + GenericVectorArray::MutableTypedRef<ToT> outputs = params.vector_output<ToT>(1, "Outputs"); + + for (uint index : mask.indices()) { + VirtualListRef<FromT> input_list = inputs[index]; + + for (uint i = 0; i < input_list.size(); i++) { + const FromT &from_value = input_list[i]; + ToT to_value = static_cast<ToT>(from_value); + outputs.append_single(index, to_value); + } + } + } +}; + +template<typename T> class MF_SingleElementList : public MultiFunction { + public: + MF_SingleElementList() + { + MFSignatureBuilder signature = this->get_builder("Single Element List - " + + CPP_TYPE<T>().name()); + signature.single_input<T>("Input"); + signature.vector_output<T>("Outputs"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VirtualListRef<T> inputs = params.readonly_single_input<T>(0, "Input"); + GenericVectorArray::MutableTypedRef<T> outputs = params.vector_output<T>(1, "Outputs"); + + for (uint i : mask.indices()) { + outputs.append_single(i, inputs[i]); + } + } +}; + +}; // namespace FN diff --git a/source/blender/functions/intern/multi_functions/mixed.cc b/source/blender/functions/intern/multi_functions/mixed.cc new file mode 100644 index 00000000000..590193b5aab --- /dev/null +++ b/source/blender/functions/intern/multi_functions/mixed.cc @@ -0,0 +1,896 @@ +#include "mixed.h" + +#include "FN_generic_array_ref.h" +#include "FN_generic_vector_array.h" +#include "FN_multi_function_common_contexts.h" + +#include "BLI_array_cxx.h" +#include "BLI_color.h" +#include "BLI_float3.h" +#include "BLI_float4x4.h" +#include "BLI_hash.h" +#include "BLI_kdtree.h" +#include "BLI_noise.h" +#include "BLI_rand.h" +#include "BLI_rand_cxx.h" +#include "BLI_string_map.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "IMB_imbuf_types.h" + +#include "BKE_customdata.h" + +#include "BKE_deform.h" +#include "BKE_id_data_cache.h" +#include "BKE_mesh_runtime.h" + +namespace FN { + +using BKE::ImageIDHandle; +using BKE::ObjectIDHandle; +using BLI::float3; +using BLI::float4x4; +using BLI::rgba_f; + +MF_CombineColor::MF_CombineColor() +{ + MFSignatureBuilder signature = this->get_builder("Combine Color"); + signature.single_input<float>("R"); + signature.single_input<float>("G"); + signature.single_input<float>("B"); + signature.single_input<float>("A"); + signature.single_output<rgba_f>("Color"); +} + +void MF_CombineColor::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> r = params.readonly_single_input<float>(0, "R"); + VirtualListRef<float> g = params.readonly_single_input<float>(1, "G"); + VirtualListRef<float> b = params.readonly_single_input<float>(2, "B"); + VirtualListRef<float> a = params.readonly_single_input<float>(3, "A"); + MutableArrayRef<rgba_f> color = params.uninitialized_single_output<rgba_f>(4, "Color"); + + for (uint i : mask.indices()) { + color[i] = {r[i], g[i], b[i], a[i]}; + } +} + +MF_SeparateColor::MF_SeparateColor() +{ + MFSignatureBuilder signature = this->get_builder("Separate Color"); + signature.single_input<rgba_f>("Color"); + signature.single_output<float>("R"); + signature.single_output<float>("G"); + signature.single_output<float>("B"); + signature.single_output<float>("A"); +} + +void MF_SeparateColor::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + auto color = params.readonly_single_input<rgba_f>(0, "Color"); + auto r = params.uninitialized_single_output<float>(1, "R"); + auto g = params.uninitialized_single_output<float>(2, "G"); + auto b = params.uninitialized_single_output<float>(3, "B"); + auto a = params.uninitialized_single_output<float>(4, "A"); + + for (uint i : mask.indices()) { + rgba_f v = color[i]; + r[i] = v.r; + g[i] = v.g; + b[i] = v.b; + a[i] = v.a; + } +} + +MF_CombineVector::MF_CombineVector() +{ + MFSignatureBuilder signature = this->get_builder("Combine Vector"); + signature.single_input<float>("X"); + signature.single_input<float>("Y"); + signature.single_input<float>("Z"); + signature.single_output<float3>("Vector"); +} + +void MF_CombineVector::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> x = params.readonly_single_input<float>(0, "X"); + VirtualListRef<float> y = params.readonly_single_input<float>(1, "Y"); + VirtualListRef<float> z = params.readonly_single_input<float>(2, "Z"); + MutableArrayRef<float3> vector = params.uninitialized_single_output<float3>(3, "Vector"); + + for (uint i : mask.indices()) { + vector[i] = {x[i], y[i], z[i]}; + } +} + +MF_SeparateVector::MF_SeparateVector() +{ + MFSignatureBuilder signature = this->get_builder("Separate Vector"); + signature.single_input<float3>("Vector"); + signature.single_output<float>("X"); + signature.single_output<float>("Y"); + signature.single_output<float>("Z"); +} + +void MF_SeparateVector::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + auto vector = params.readonly_single_input<float3>(0, "Vector"); + auto x = params.uninitialized_single_output<float>(1, "X"); + auto y = params.uninitialized_single_output<float>(2, "Y"); + auto z = params.uninitialized_single_output<float>(3, "Z"); + + for (uint i : mask.indices()) { + float3 v = vector[i]; + x[i] = v.x; + y[i] = v.y; + z[i] = v.z; + } +} + +MF_VectorFromValue::MF_VectorFromValue() +{ + MFSignatureBuilder signature = this->get_builder("Vector from Value"); + signature.single_input<float>("Value"); + signature.single_output<float3>("Vector"); +} + +void MF_VectorFromValue::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> values = params.readonly_single_input<float>(0, "Value"); + MutableArrayRef<float3> r_vectors = params.uninitialized_single_output<float3>(1, "Vector"); + + for (uint i : mask.indices()) { + float value = values[i]; + r_vectors[i] = {value, value, value}; + } +} + +MF_FloatArraySum::MF_FloatArraySum() +{ + MFSignatureBuilder signature = this->get_builder("Float Array Sum"); + signature.vector_input<float>("Array"); + signature.single_output<float>("Sum"); +} + +void MF_FloatArraySum::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + auto arrays = params.readonly_vector_input<float>(0, "Array"); + MutableArrayRef<float> sums = params.uninitialized_single_output<float>(1, "Sum"); + + for (uint i : mask.indices()) { + float sum = 0.0f; + VirtualListRef<float> array = arrays[i]; + for (uint j = 0; j < array.size(); j++) { + sum += array[j]; + } + sums[i] = sum; + } +} + +MF_FloatRange_Amount_Start_Step::MF_FloatRange_Amount_Start_Step() +{ + MFSignatureBuilder signature = this->get_builder("Float Range"); + + signature.single_input<int>("Amount"); + signature.single_input<float>("Start"); + signature.single_input<float>("Step"); + signature.vector_output<float>("Range"); +} + +void MF_FloatRange_Amount_Start_Step::call(IndexMask mask, + MFParams params, + MFContext UNUSED(context)) const +{ + VirtualListRef<int> amounts = params.readonly_single_input<int>(0, "Amount"); + VirtualListRef<float> starts = params.readonly_single_input<float>(1, "Start"); + VirtualListRef<float> steps = params.readonly_single_input<float>(2, "Step"); + auto r_ranges = params.vector_output<float>(3, "Range"); + + for (uint index : mask.indices()) { + uint amount = std::max<int>(0, amounts[index]); + float start = starts[index]; + float step = steps[index]; + + MutableArrayRef<float> range = r_ranges.allocate(index, amount); + + for (int i = 0; i < amount; i++) { + float value = start + i * step; + range[i] = value; + } + } +} + +MF_FloatRange_Amount_Start_Stop::MF_FloatRange_Amount_Start_Stop() +{ + MFSignatureBuilder signature = this->get_builder("Float Range"); + + signature.single_input<int>("Amount"); + signature.single_input<float>("Start"); + signature.single_input<float>("Stop"); + signature.vector_output<float>("Range"); +} + +void MF_FloatRange_Amount_Start_Stop::call(IndexMask mask, + MFParams params, + MFContext UNUSED(context)) const +{ + VirtualListRef<int> amounts = params.readonly_single_input<int>(0, "Amount"); + VirtualListRef<float> starts = params.readonly_single_input<float>(1, "Start"); + VirtualListRef<float> stops = params.readonly_single_input<float>(2, "Stop"); + auto r_ranges = params.vector_output<float>(3, "Range"); + + for (uint index : mask.indices()) { + uint amount = std::max<int>(0, amounts[index]); + float start = starts[index]; + float stop = stops[index]; + + if (amount == 0) { + continue; + } + else if (amount == 1) { + r_ranges.append_single(index, (start + stop) / 2.0f); + } + else { + MutableArrayRef<float> range = r_ranges.allocate(index, amount); + + float step = (stop - start) / (amount - 1); + for (int i = 0; i < amount; i++) { + float value = start + i * step; + range[i] = value; + } + } + } +} + +MF_ObjectVertexPositions::MF_ObjectVertexPositions() +{ + MFSignatureBuilder signature = this->get_builder("Object Vertex Positions"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<ObjectIDHandle>("Object"); + signature.vector_output<float3>("Positions"); +} + +void MF_ObjectVertexPositions::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<ObjectIDHandle> objects = params.readonly_single_input<ObjectIDHandle>(0, + "Object"); + auto positions = params.vector_output<float3>(1, "Positions"); + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + return; + } + + for (uint i : mask.indices()) { + Object *object = id_handle_lookup->lookup(objects[i]); + if (object == nullptr || object->type != OB_MESH) { + continue; + } + + float4x4 transform = object->obmat; + + Mesh *mesh = (Mesh *)object->data; + Array<float3> coords(mesh->totvert); + for (uint j = 0; j < mesh->totvert; j++) { + coords[j] = transform.transform_position(mesh->mvert[j].co); + } + positions.extend_single(i, coords); + } +} + +MF_ObjectWorldLocation::MF_ObjectWorldLocation() +{ + MFSignatureBuilder signature = this->get_builder("Object Location"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<ObjectIDHandle>("Object"); + signature.single_output<float3>("Location"); +} + +void MF_ObjectWorldLocation::call(IndexMask mask, MFParams params, MFContext context) const +{ + auto objects = params.readonly_single_input<ObjectIDHandle>(0, "Object"); + auto r_locations = params.uninitialized_single_output<float3>(1, "Location"); + + float3 fallback = {0, 0, 0}; + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + r_locations.fill_indices(mask.indices(), fallback); + return; + } + + for (uint i : mask.indices()) { + Object *object = id_handle_lookup->lookup(objects[i]); + if (object != nullptr) { + r_locations[i] = object->obmat[3]; + } + else { + r_locations[i] = fallback; + } + } +} + +MF_SwitchSingle::MF_SwitchSingle(const CPPType &type) : m_type(type) +{ + MFSignatureBuilder signature = this->get_builder("Switch"); + signature.single_input<bool>("Condition"); + signature.single_input("True", m_type); + signature.single_input("False", m_type); + signature.single_output("Result", m_type); +} + +void MF_SwitchSingle::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<bool> conditions = params.readonly_single_input<bool>(0, "Condition"); + GenericVirtualListRef if_true = params.readonly_single_input(1, "True"); + GenericVirtualListRef if_false = params.readonly_single_input(2, "False"); + GenericMutableArrayRef results = params.uninitialized_single_output(3, "Result"); + + for (uint i : mask.indices()) { + if (conditions[i]) { + results.copy_in__uninitialized(i, if_true[i]); + } + else { + results.copy_in__uninitialized(i, if_false[i]); + } + } +} + +MF_SwitchVector::MF_SwitchVector(const CPPType &type) : m_type(type) +{ + MFSignatureBuilder signature = this->get_builder("Switch"); + signature.single_input<bool>("Condition"); + signature.vector_input("True", m_type); + signature.vector_input("False", m_type); + signature.vector_output("Result", m_type); +} + +void MF_SwitchVector::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<bool> conditions = params.readonly_single_input<bool>(0, "Condition"); + GenericVirtualListListRef if_true = params.readonly_vector_input(1, "True"); + GenericVirtualListListRef if_false = params.readonly_vector_input(2, "False"); + GenericVectorArray &results = params.vector_output(3, "Result"); + + for (uint i : mask.indices()) { + if (conditions[i]) { + results.extend_single__copy(i, if_true[i]); + } + else { + results.extend_single__copy(i, if_false[i]); + } + } +} + +MF_SelectSingle::MF_SelectSingle(const CPPType &type, uint inputs) : m_inputs(inputs) +{ + MFSignatureBuilder signature = this->get_builder("Select Single: " + type.name()); + signature.single_input<int>("Select"); + for (uint i : IndexRange(inputs)) { + signature.single_input(std::to_string(i), type); + } + signature.single_input("Fallback", type); + signature.single_output("Result", type); +} + +void MF_SelectSingle::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<int> selects = params.readonly_single_input<int>(0, "Select"); + GenericVirtualListRef fallbacks = params.readonly_single_input(m_inputs + 1, "Fallback"); + GenericMutableArrayRef r_results = params.uninitialized_single_output(m_inputs + 2, "Result"); + + for (uint i : mask.indices()) { + int select = selects[i]; + if (0 <= select && select < m_inputs) { + GenericVirtualListRef selected = params.readonly_single_input(select + 1); + r_results.copy_in__uninitialized(i, selected[i]); + } + else { + r_results.copy_in__uninitialized(i, fallbacks[i]); + } + } +} + +MF_SelectVector::MF_SelectVector(const CPPType &base_type, uint inputs) : m_inputs(inputs) +{ + MFSignatureBuilder signature = this->get_builder("Select Vector: " + base_type.name() + " List"); + signature.single_input<int>("Select"); + for (uint i : IndexRange(inputs)) { + signature.vector_input(std::to_string(i), base_type); + } + signature.vector_input("Fallback", base_type); + signature.vector_output("Result", base_type); +} + +void MF_SelectVector::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<int> selects = params.readonly_single_input<int>(0, "Select"); + GenericVirtualListListRef fallback = params.readonly_vector_input(m_inputs + 1, "Fallback"); + GenericVectorArray &r_results = params.vector_output(m_inputs + 2, "Result"); + + for (uint i : mask.indices()) { + int select = selects[i]; + if (0 <= select && select < m_inputs) { + GenericVirtualListListRef selected = params.readonly_vector_input(select + 1); + r_results.extend_single__copy(i, selected[i]); + } + else { + r_results.extend_single__copy(i, fallback[i]); + } + } +} + +MF_TextLength::MF_TextLength() +{ + MFSignatureBuilder signature = this->get_builder("Text Length"); + signature.single_input<std::string>("Text"); + signature.single_output<int>("Length"); +} + +void MF_TextLength::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + auto texts = params.readonly_single_input<std::string>(0, "Text"); + auto lengths = params.uninitialized_single_output<int>(1, "Length"); + + for (uint i : mask.indices()) { + lengths[i] = texts[i].size(); + } +} + +MF_ContextVertexPosition::MF_ContextVertexPosition() +{ + MFSignatureBuilder signature = this->get_builder("Vertex Position"); + signature.use_element_context<VertexPositionArray>(); + signature.single_output<float3>("Position"); +} + +void MF_ContextVertexPosition::call(IndexMask mask, MFParams params, MFContext context) const +{ + MutableArrayRef<float3> positions = params.uninitialized_single_output<float3>(0, "Position"); + auto vertices_context = context.try_find_per_element<VertexPositionArray>(); + + if (vertices_context.has_value()) { + for (uint i : mask.indices()) { + uint context_index = vertices_context.value().indices[i]; + positions[i] = vertices_context.value().data->positions[context_index]; + } + } + else { + positions.fill_indices(mask.indices(), {0, 0, 0}); + } +} + +MF_ContextCurrentFrame::MF_ContextCurrentFrame() +{ + MFSignatureBuilder signature = this->get_builder("Current Frame"); + signature.use_global_context<SceneTimeContext>(); + signature.single_output<float>("Frame"); +} + +void MF_ContextCurrentFrame::call(IndexMask mask, MFParams params, MFContext context) const +{ + MutableArrayRef<float> frames = params.uninitialized_single_output<float>(0, "Frame"); + + auto *time_context = context.try_find_global<SceneTimeContext>(); + + if (time_context != nullptr) { + float current_frame = time_context->time; + frames.fill_indices(mask.indices(), current_frame); + } + else { + frames.fill_indices(mask.indices(), 0.0f); + } +} + +MF_PerlinNoise::MF_PerlinNoise() +{ + MFSignatureBuilder signature = this->get_builder("Perlin Noise"); + signature.single_input<float3>("Position"); + signature.single_input<float>("Amplitude"); + signature.single_input<float>("Scale"); + signature.single_output<float>("Noise 1D"); + signature.single_output<float3>("Noise 3D"); +} + +void MF_PerlinNoise::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float3> positions = params.readonly_single_input<float3>(0, "Position"); + VirtualListRef<float> amplitudes = params.readonly_single_input<float>(1, "Amplitude"); + VirtualListRef<float> scales = params.readonly_single_input<float>(2, "Scale"); + + MutableArrayRef<float> r_noise1 = params.uninitialized_single_output<float>(3, "Noise 1D"); + MutableArrayRef<float3> r_noise3 = params.uninitialized_single_output<float3>(4, "Noise 3D"); + + for (uint i : mask.indices()) { + float3 pos = positions[i]; + float noise = BLI_gNoise(scales[i], pos.x, pos.y, pos.z, false, 1); + r_noise1[i] = noise * amplitudes[i]; + } + + for (uint i : mask.indices()) { + float3 pos = positions[i]; + float x = BLI_gNoise(scales[i], pos.x, pos.y, pos.z + 1000.0f, false, 1); + float y = BLI_gNoise(scales[i], pos.x, pos.y + 1000.0f, pos.z, false, 1); + float z = BLI_gNoise(scales[i], pos.x + 1000.0f, pos.y, pos.z, false, 1); + r_noise3[i] = float3(x, y, z) * amplitudes[i]; + } +} + +MF_MapRange::MF_MapRange(bool clamp) : m_clamp(clamp) +{ + MFSignatureBuilder signature = this->get_builder("Map Range"); + signature.single_input<float>("Value"); + signature.single_input<float>("From Min"); + signature.single_input<float>("From Max"); + signature.single_input<float>("To Min"); + signature.single_input<float>("To Max"); + signature.single_output<float>("Value"); +} + +void MF_MapRange::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> values = params.readonly_single_input<float>(0, "Value"); + VirtualListRef<float> from_min = params.readonly_single_input<float>(1, "From Min"); + VirtualListRef<float> from_max = params.readonly_single_input<float>(2, "From Max"); + VirtualListRef<float> to_min = params.readonly_single_input<float>(3, "To Min"); + VirtualListRef<float> to_max = params.readonly_single_input<float>(4, "To Max"); + MutableArrayRef<float> r_values = params.uninitialized_single_output<float>(5, "Value"); + + for (uint i : mask.indices()) { + float diff = from_max[i] - from_min[i]; + if (diff != 0.0f) { + r_values[i] = (values[i] - from_min[i]) / diff * (to_max[i] - to_min[i]) + to_min[i]; + } + else { + r_values[i] = to_min[i]; + } + } + + if (m_clamp) { + for (uint i : mask.indices()) { + float min_v = to_min[i]; + float max_v = to_max[i]; + float value = r_values[i]; + if (min_v < max_v) { + r_values[i] = std::min(std::max(value, min_v), max_v); + } + else { + r_values[i] = std::min(std::max(value, max_v), min_v); + } + } + } +} + +MF_Clamp::MF_Clamp(bool sort_minmax) : m_sort_minmax(sort_minmax) +{ + MFSignatureBuilder signature = this->get_builder("Clamp"); + signature.single_input<float>("Value"); + signature.single_input<float>("Min"); + signature.single_input<float>("Max"); + signature.single_output<float>("Value"); +} + +void MF_Clamp::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> values = params.readonly_single_input<float>(0, "Value"); + VirtualListRef<float> min_values = params.readonly_single_input<float>(1, "Min"); + VirtualListRef<float> max_values = params.readonly_single_input<float>(2, "Max"); + MutableArrayRef<float> r_values = params.uninitialized_single_output<float>(3, "Value"); + + if (m_sort_minmax) { + for (uint i : mask.indices()) { + float min_v = min_values[i]; + float max_v = max_values[i]; + float value = values[i]; + if (min_v < max_v) { + r_values[i] = std::min(std::max(value, min_v), max_v); + } + else { + r_values[i] = std::min(std::max(value, max_v), min_v); + } + } + } + else { + for (uint i : mask.indices()) { + float min_v = min_values[i]; + float max_v = max_values[i]; + float value = values[i]; + r_values[i] = std::min(std::max(value, min_v), max_v); + } + } +} + +MF_RandomFloat::MF_RandomFloat(uint seed) : m_seed(seed * BLI_RAND_PER_LINE_UINT32) +{ + MFSignatureBuilder signature = this->get_builder("Random Float"); + signature.single_input<float>("Min"); + signature.single_input<float>("Max"); + signature.single_input<int>("Seed"); + signature.single_output<float>("Value"); +} + +void MF_RandomFloat::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float> min_values = params.readonly_single_input<float>(0, "Min"); + VirtualListRef<float> max_values = params.readonly_single_input<float>(1, "Max"); + VirtualListRef<int> seeds = params.readonly_single_input<int>(2, "Seed"); + MutableArrayRef<float> r_values = params.uninitialized_single_output<float>(3, "Value"); + + for (uint i : mask.indices()) { + float value = BLI_hash_int_01(seeds[i] ^ m_seed); + r_values[i] = value * (max_values[i] - min_values[i]) + min_values[i]; + } +} + +MF_RandomFloats::MF_RandomFloats(uint seed) : m_seed(seed * BLI_RAND_PER_LINE_UINT32) +{ + MFSignatureBuilder signature = this->get_builder("Random Floats"); + signature.single_input<int>("Amount"); + signature.single_input<float>("Min"); + signature.single_input<float>("Max"); + signature.single_input<int>("Seed"); + signature.vector_output<float>("Values"); +} + +void MF_RandomFloats::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<int> amounts = params.readonly_single_input<int>(0, "Amount"); + VirtualListRef<float> min_values = params.readonly_single_input<float>(1, "Min"); + VirtualListRef<float> max_values = params.readonly_single_input<float>(2, "Max"); + VirtualListRef<int> seeds = params.readonly_single_input<int>(3, "Seed"); + GenericVectorArray::MutableTypedRef<float> r_values = params.vector_output<float>(4, "Values"); + + RNG *rng = BLI_rng_new(0); + + for (uint i : mask.indices()) { + uint amount = std::max<int>(0, amounts[i]); + MutableArrayRef<float> r_array = r_values.allocate(i, amount); + BLI_rng_srandom(rng, seeds[i] + m_seed); + + float range = max_values[i] - min_values[i]; + float offset = min_values[i]; + + for (float &r_value : r_array) { + r_value = BLI_rng_get_float(rng) * range + offset; + } + } + + BLI_rng_free(rng); +} + +MF_RandomVector::MF_RandomVector(uint seed, RandomVectorMode::Enum mode) + : m_seed(seed * BLI_RAND_PER_LINE_UINT32), m_mode(mode) +{ + MFSignatureBuilder signature = this->get_builder("Random Vector"); + signature.single_input<float3>("Factor"); + signature.single_input<int>("Seed"); + signature.single_output<float3>("Vector"); +} + +static float3 rng_get_float3_01(RNG *rng) +{ + float x = BLI_rng_get_float(rng); + float y = BLI_rng_get_float(rng); + float z = BLI_rng_get_float(rng); + return {x, y, z}; +} + +static float3 rng_get_float3_neg1_1(RNG *rng) +{ + return rng_get_float3_01(rng) * 2 - float3(1.0f, 1.0f, 1.0f); +} + +void MF_RandomVector::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<float3> factors = params.readonly_single_input<float3>(0, "Factor"); + VirtualListRef<int> seeds = params.readonly_single_input<int>(1, "Seed"); + MutableArrayRef<float3> r_vectors = params.uninitialized_single_output<float3>(2, "Vector"); + + RNG *rng = BLI_rng_new(0); + + switch (m_mode) { + case RandomVectorMode::UniformInCube: { + for (uint i : mask.indices()) { + uint seed = seeds[i] ^ m_seed; + BLI_rng_srandom(rng, seed); + float3 vector = rng_get_float3_neg1_1(rng); + r_vectors[i] = vector * factors[i]; + } + break; + } + case RandomVectorMode::UniformOnSphere: { + for (uint i : mask.indices()) { + uint seed = seeds[i] ^ m_seed; + BLI_rng_srandom(rng, seed); + float3 vector; + BLI_rng_get_float_unit_v3(rng, vector); + r_vectors[i] = vector * factors[i]; + } + break; + } + case RandomVectorMode::UniformInSphere: { + for (uint i : mask.indices()) { + uint seed = seeds[i] ^ m_seed; + BLI_rng_srandom(rng, seed); + float3 vector; + do { + vector = rng_get_float3_neg1_1(rng); + } while (vector.length_squared() >= 1.0f); + r_vectors[i] = vector * factors[i]; + } + break; + } + } + + BLI_rng_free(rng); +} + +MF_RandomVectors::MF_RandomVectors(uint seed, RandomVectorMode::Enum mode) + : m_seed(seed * BLI_RAND_PER_LINE_UINT32), m_mode(mode) +{ + MFSignatureBuilder signature = this->get_builder("Random Vectors"); + signature.single_input<int>("Amount"); + signature.single_input<float3>("Factor"); + signature.single_input<int>("Seed"); + signature.vector_output<float3>("Vectors"); +} + +void MF_RandomVectors::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListRef<int> amounts = params.readonly_single_input<int>(0, "Amount"); + VirtualListRef<float3> factors = params.readonly_single_input<float3>(1, "Factor"); + VirtualListRef<int> seeds = params.readonly_single_input<int>(2, "Seed"); + GenericVectorArray::MutableTypedRef<float3> r_vectors_array = params.vector_output<float3>( + 3, "Vectors"); + + RNG *rng = BLI_rng_new(0); + + for (uint index : mask.indices()) { + uint amount = std::max<int>(0, amounts[index]); + float3 factor = factors[index]; + uint seed = seeds[index] ^ m_seed; + + MutableArrayRef<float3> r_vectors = r_vectors_array.allocate(index, amount); + + BLI_rng_srandom(rng, seed); + + switch (m_mode) { + case RandomVectorMode::UniformInCube: { + for (uint i : IndexRange(amount)) { + float3 vector = rng_get_float3_neg1_1(rng); + r_vectors[i] = vector; + } + break; + } + case RandomVectorMode::UniformOnSphere: { + for (uint i : IndexRange(amount)) { + float3 vector; + BLI_rng_get_float_unit_v3(rng, vector); + r_vectors[i] = vector; + } + break; + } + case RandomVectorMode::UniformInSphere: { + for (uint i : IndexRange(amount)) { + float3 vector; + do { + vector = rng_get_float3_neg1_1(rng); + } while (vector.length_squared() >= 1.0f); + r_vectors[i] = vector; + } + break; + } + } + + for (float3 &vector : r_vectors) { + vector *= factor; + } + } + + BLI_rng_free(rng); +} + +MF_FindNonClosePoints::MF_FindNonClosePoints() +{ + MFSignatureBuilder signature = this->get_builder("Remove Close Points"); + signature.vector_input<float3>("Points"); + signature.single_input<float>("Min Distance"); + signature.vector_output<int>("Indices"); +} + +static BLI_NOINLINE Vector<int> find_non_close_indices(VirtualListRef<float3> points, + float min_distance) +{ + if (min_distance <= 0.0f) { + return IndexRange(points.size()).as_array_ref().cast<int>(); + } + + KDTree_3d *kdtree = BLI_kdtree_3d_new(points.size()); + for (uint i : IndexRange(points.size())) { + BLI_kdtree_3d_insert(kdtree, i, points[i]); + } + + BLI_kdtree_3d_balance(kdtree); + + Array<bool> keep_index(points.size()); + keep_index.fill(true); + + for (uint i : IndexRange(points.size())) { + if (!keep_index[i]) { + continue; + } + + float3 current_point = points[i]; + + struct CBData { + MutableArrayRef<bool> keep_index_ref; + uint current_index; + } cb_data = {keep_index, i}; + + BLI_kdtree_3d_range_search_cb( + kdtree, + current_point, + min_distance, + [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) -> bool { + CBData &cb_data = *(CBData *)user_data; + if (index != cb_data.current_index) { + cb_data.keep_index_ref[index] = false; + } + return true; + }, + (void *)&cb_data); + } + + BLI_kdtree_3d_free(kdtree); + + Vector<int> indices; + for (uint i : keep_index.index_range()) { + if (keep_index[i]) { + indices.append(i); + } + } + + return indices; +} + +void MF_FindNonClosePoints::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListListRef<float3> points_list = params.readonly_vector_input<float3>(0, "Points"); + VirtualListRef<float> min_distances = params.readonly_single_input<float>(1, "Min Distance"); + GenericVectorArray::MutableTypedRef<int> indices_list = params.vector_output<int>(2, "Indices"); + + for (uint i : mask.indices()) { + Vector<int> filtered_indices = find_non_close_indices(points_list[i], min_distances[i]); + indices_list.extend_single(i, filtered_indices); + } +} + +MF_JoinTextList::MF_JoinTextList() +{ + MFSignatureBuilder signature = this->get_builder("Join Text List"); + signature.vector_input<std::string>("Texts"); + signature.single_output<std::string>("Text"); +} + +void MF_JoinTextList::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const +{ + VirtualListListRef<std::string> text_lists = params.readonly_vector_input<std::string>(0, + "Texts"); + MutableArrayRef<std::string> r_texts = params.uninitialized_single_output<std::string>(1, + "Text"); + + for (uint index : mask.indices()) { + VirtualListRef<std::string> texts = text_lists[index]; + std::string r_text = ""; + for (uint i : texts.index_range()) { + r_text += texts[i]; + } + new (&r_texts[index]) std::string(std::move(r_text)); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/mixed.h b/source/blender/functions/intern/multi_functions/mixed.h new file mode 100644 index 00000000000..68c7beb033d --- /dev/null +++ b/source/blender/functions/intern/multi_functions/mixed.h @@ -0,0 +1,205 @@ +#pragma once + +#include <functional> + +#include "FN_multi_function.h" + +namespace FN { + +class MF_CombineColor final : public MultiFunction { + public: + MF_CombineColor(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SeparateColor final : public MultiFunction { + public: + MF_SeparateColor(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_CombineVector final : public MultiFunction { + public: + MF_CombineVector(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SeparateVector final : public MultiFunction { + public: + MF_SeparateVector(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_VectorFromValue final : public MultiFunction { + public: + MF_VectorFromValue(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_FloatArraySum final : public MultiFunction { + public: + MF_FloatArraySum(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_FloatRange_Amount_Start_Step final : public MultiFunction { + public: + MF_FloatRange_Amount_Start_Step(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_FloatRange_Amount_Start_Stop final : public MultiFunction { + public: + MF_FloatRange_Amount_Start_Stop(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_ObjectWorldLocation final : public MultiFunction { + public: + MF_ObjectWorldLocation(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_ObjectVertexPositions final : public MultiFunction { + public: + MF_ObjectVertexPositions(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_TextLength final : public MultiFunction { + public: + MF_TextLength(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_RandomFloat final : public MultiFunction { + private: + uint m_seed; + + public: + MF_RandomFloat(uint seed); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_RandomFloats final : public MultiFunction { + private: + uint m_seed; + + public: + MF_RandomFloats(uint seed); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +namespace RandomVectorMode { +enum Enum { + UniformInCube, + UniformOnSphere, + UniformInSphere, +}; +} + +class MF_RandomVector final : public MultiFunction { + private: + uint m_seed; + RandomVectorMode::Enum m_mode; + + public: + MF_RandomVector(uint seed, RandomVectorMode::Enum mode); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_RandomVectors final : public MultiFunction { + private: + uint m_seed; + RandomVectorMode::Enum m_mode; + + public: + MF_RandomVectors(uint seed, RandomVectorMode::Enum mode); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_ContextVertexPosition final : public MultiFunction { + public: + MF_ContextVertexPosition(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_ContextCurrentFrame final : public MultiFunction { + public: + MF_ContextCurrentFrame(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SwitchSingle final : public MultiFunction { + private: + const CPPType &m_type; + + public: + MF_SwitchSingle(const CPPType &type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SwitchVector final : public MultiFunction { + private: + const CPPType &m_type; + + public: + MF_SwitchVector(const CPPType &type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SelectSingle final : public MultiFunction { + private: + uint m_inputs; + + public: + MF_SelectSingle(const CPPType &type, uint inputs); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SelectVector final : public MultiFunction { + private: + uint m_inputs; + + public: + MF_SelectVector(const CPPType &type, uint inputs); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_PerlinNoise final : public MultiFunction { + public: + MF_PerlinNoise(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_MapRange final : public MultiFunction { + private: + bool m_clamp; + + public: + MF_MapRange(bool clamp); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_Clamp final : public MultiFunction { + private: + bool m_sort_minmax; + + public: + MF_Clamp(bool sort_minmax); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_FindNonClosePoints final : public MultiFunction { + public: + MF_FindNonClosePoints(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_JoinTextList final : public MultiFunction { + public: + MF_JoinTextList(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/network.cc b/source/blender/functions/intern/multi_functions/network.cc new file mode 100644 index 00000000000..2cc96428164 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/network.cc @@ -0,0 +1,999 @@ +#include "network.h" + +#include "BLI_buffer_cache.h" + +namespace FN { + +using BLI::BufferCache; +using BLI::ScopedVector; + +namespace { + +namespace ValueType { +enum Enum { + InputSingle, + InputVector, + OutputSingle, + OutputVector, + OwnSingle, + OwnVector, +}; +} + +struct Value { + ValueType::Enum type; + + Value(ValueType::Enum type) : type(type) + { + } +}; + +struct InputSingleValue : public Value { + GenericVirtualListRef list_ref; + + InputSingleValue(GenericVirtualListRef list_ref) + : Value(ValueType::InputSingle), list_ref(list_ref) + { + } +}; + +struct InputVectorValue : public Value { + GenericVirtualListListRef list_list_ref; + + InputVectorValue(GenericVirtualListListRef list_list_ref) + : Value(ValueType::InputVector), list_list_ref(list_list_ref) + { + } +}; + +struct OutputValue : public Value { + bool is_computed = false; + + OutputValue(ValueType::Enum type) : Value(type) + { + } +}; + +struct OutputSingleValue : public OutputValue { + GenericMutableArrayRef array_ref; + + OutputSingleValue(GenericMutableArrayRef array_ref) + : OutputValue(ValueType::OutputSingle), array_ref(array_ref) + { + } +}; + +struct OutputVectorValue : public OutputValue { + GenericVectorArray *vector_array; + + OutputVectorValue(GenericVectorArray &vector_array) + : OutputValue(ValueType::OutputVector), vector_array(&vector_array) + { + } +}; + +struct OwnSingleValue : public Value { + GenericMutableArrayRef array_ref; + int max_remaining_users; + bool is_single_allocated; + + OwnSingleValue(GenericMutableArrayRef array_ref, + int max_remaining_users, + bool is_single_allocated) + : Value(ValueType::OwnSingle), + array_ref(array_ref), + max_remaining_users(max_remaining_users), + is_single_allocated(is_single_allocated) + { + } +}; + +struct OwnVectorValue : public Value { + GenericVectorArray *vector_array; + int max_remaining_users; + + OwnVectorValue(GenericVectorArray &vector_array, int max_remaining_users) + : Value(ValueType::OwnVector), + vector_array(&vector_array), + max_remaining_users(max_remaining_users) + { + } +}; + +} // namespace + +class NetworkEvaluationStorage { + private: + LinearAllocator<> m_allocator; + BufferCache &m_buffer_cache; + IndexMask m_mask; + Array<Value *> m_value_per_output_id; + uint m_min_array_size; + + public: + NetworkEvaluationStorage(BufferCache &buffer_cache, IndexMask mask, uint socket_id_amount) + : m_buffer_cache(buffer_cache), + m_mask(mask), + m_value_per_output_id(socket_id_amount, nullptr), + m_min_array_size(mask.min_array_size()) + { + } + + ~NetworkEvaluationStorage() + { + for (Value *any_value : m_value_per_output_id) { + if (any_value == nullptr) { + continue; + } + else if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + GenericMutableArrayRef array_ref = value->array_ref; + const CPPType &type = array_ref.type(); + if (value->is_single_allocated) { + type.destruct(array_ref.buffer()); + } + else { + type.destruct_indices(array_ref.buffer(), m_mask); + m_buffer_cache.deallocate(array_ref.buffer()); + } + } + else if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + delete value->vector_array; + } + } + } + + IndexMask mask() const + { + return m_mask; + } + + bool socket_is_computed(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return false; + } + if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) { + return ((OutputValue *)any_value)->is_computed; + } + return true; + } + + bool is_same_value_for_every_index(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + switch (any_value->type) { + case ValueType::OwnSingle: + return ((OwnSingleValue *)any_value)->array_ref.size() == 1; + case ValueType::OwnVector: + return ((OwnVectorValue *)any_value)->vector_array->size() == 1; + case ValueType::InputSingle: + return ((InputSingleValue *)any_value)->list_ref.is_single_element(); + case ValueType::InputVector: + return ((InputVectorValue *)any_value)->list_list_ref.is_single_list(); + case ValueType::OutputSingle: + return ((OutputSingleValue *)any_value)->array_ref.size() == 1; + case ValueType::OutputVector: + return ((OutputVectorValue *)any_value)->vector_array->size() == 1; + } + BLI_assert(false); + return false; + } + + bool socket_has_buffer_for_output(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return false; + } + + BLI_assert(ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)); + return true; + } + + void finish_output_socket(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return; + } + + if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) { + ((OutputValue *)any_value)->is_computed = true; + } + } + + void finish_input_socket(const MFInputSocket &socket) + { + const MFOutputSocket &origin = socket.origin(); + + Value *any_value = m_value_per_output_id[origin.id()]; + if (any_value == nullptr) { + /* Can happen when a value has been forward to the next node. */ + return; + } + + switch (any_value->type) { + case ValueType::InputSingle: + case ValueType::OutputSingle: + case ValueType::InputVector: + case ValueType::OutputVector: { + break; + } + case ValueType::OwnSingle: { + OwnSingleValue *value = (OwnSingleValue *)any_value; + BLI_assert(value->max_remaining_users >= 1); + value->max_remaining_users--; + if (value->max_remaining_users == 0) { + GenericMutableArrayRef array_ref = value->array_ref; + const CPPType &type = array_ref.type(); + if (value->is_single_allocated) { + type.destruct(array_ref.buffer()); + } + else { + type.destruct_indices(array_ref.buffer(), m_mask); + m_buffer_cache.deallocate(array_ref.buffer()); + } + m_value_per_output_id[origin.id()] = nullptr; + } + break; + } + case ValueType::OwnVector: { + OwnVectorValue *value = (OwnVectorValue *)any_value; + BLI_assert(value->max_remaining_users >= 1); + value->max_remaining_users--; + if (value->max_remaining_users == 0) { + delete value->vector_array; + m_value_per_output_id[origin.id()] = nullptr; + } + break; + } + } + } + + /* Add function inputs from caller to the storage. + ********************************************************/ + + void add_single_input_from_caller(const MFOutputSocket &socket, GenericVirtualListRef list_ref) + { + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(list_ref.size() >= m_min_array_size); + + auto *value = m_allocator.construct<InputSingleValue>(list_ref); + m_value_per_output_id[socket.id()] = value; + } + + void add_vector_input_from_caller(const MFOutputSocket &socket, + GenericVirtualListListRef list_list_ref) + { + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(list_list_ref.size() >= m_min_array_size); + + auto *value = m_allocator.construct<InputVectorValue>(list_list_ref); + m_value_per_output_id[socket.id()] = value; + } + + /* Add function outputs from caller to the storage. + *******************************************************/ + + void add_single_output_from_caller(const MFOutputSocket &socket, + GenericMutableArrayRef array_ref) + { + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(array_ref.size() >= m_min_array_size); + + auto *value = m_allocator.construct<OutputSingleValue>(array_ref); + m_value_per_output_id[socket.id()] = value; + } + + void add_vector_output_from_caller(const MFOutputSocket &socket, + GenericVectorArray &vector_array) + { + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(vector_array.size() >= m_min_array_size); + + auto *value = m_allocator.construct<OutputVectorValue>(vector_array); + m_value_per_output_id[socket.id()] = value; + } + + /* Get memory for the output of individual function calls. + ********************************************************************/ + + GenericMutableArrayRef get_single_output__full(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().single__cpp_type(); + void *buffer = m_buffer_cache.allocate(m_min_array_size, type.size(), type.alignment()); + GenericMutableArrayRef array_ref(type, buffer, m_min_array_size); + + auto *value = m_allocator.construct<OwnSingleValue>( + array_ref, socket.target_amount(), false); + m_value_per_output_id[socket.id()] = value; + + return array_ref; + } + else { + BLI_assert(any_value->type == ValueType::OutputSingle); + return ((OutputSingleValue *)any_value)->array_ref; + } + } + + GenericMutableArrayRef get_single_output__single(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().single__cpp_type(); + void *buffer = m_allocator.allocate(type.size(), type.alignment()); + GenericMutableArrayRef array_ref(type, buffer, 1); + + auto *value = m_allocator.construct<OwnSingleValue>(array_ref, socket.target_amount(), true); + m_value_per_output_id[socket.id()] = value; + + return value->array_ref; + } + else { + BLI_assert(any_value->type == ValueType::OutputSingle); + GenericMutableArrayRef array_ref = ((OutputSingleValue *)any_value)->array_ref; + BLI_assert(array_ref.size() == 1); + return array_ref; + } + } + + GenericVectorArray &get_vector_output__full(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().vector__cpp_base_type(); + GenericVectorArray *vector_array = new GenericVectorArray(type, m_min_array_size); + + auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.target_amount()); + m_value_per_output_id[socket.id()] = value; + + return *value->vector_array; + } + else { + BLI_assert(any_value->type == ValueType::OutputVector); + return *((OutputVectorValue *)any_value)->vector_array; + } + } + + GenericVectorArray &get_vector_output__single(const MFOutputSocket &socket) + { + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().vector__cpp_base_type(); + GenericVectorArray *vector_array = new GenericVectorArray(type, 1); + + auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.target_amount()); + m_value_per_output_id[socket.id()] = value; + + return *value->vector_array; + } + else { + BLI_assert(any_value->type == ValueType::OutputVector); + GenericVectorArray &vector_array = *((OutputVectorValue *)any_value)->vector_array; + BLI_assert(vector_array.size() == 1); + return vector_array; + } + } + + /* Get a mutable memory for a function that wants to mutate date. + **********************************************************************/ + + GenericMutableArrayRef get_mutable_single__full(const MFInputSocket &input, + const MFOutputSocket &output) + { + const MFOutputSocket &from = input.origin(); + const MFOutputSocket &to = output; + const CPPType &type = from.data_type().single__cpp_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(type == to.data_type().single__cpp_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputSingle); + GenericMutableArrayRef array_ref = ((OutputSingleValue *)to_any_value)->array_ref; + GenericVirtualListRef list_ref = this->get_single_input__full(input); + list_ref.materialize_to_uninitialized(m_mask, array_ref); + return array_ref; + } + + if (from_any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)from_any_value; + if (value->max_remaining_users == 1 && !value->is_single_allocated) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.target_amount(); + return value->array_ref; + } + } + + GenericVirtualListRef list_ref = this->get_single_input__full(input); + void *new_buffer = m_buffer_cache.allocate(m_min_array_size, type.size(), type.alignment()); + GenericMutableArrayRef new_array_ref(type, new_buffer, m_min_array_size); + list_ref.materialize_to_uninitialized(m_mask, new_array_ref); + + OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>( + new_array_ref, to.target_amount(), false); + m_value_per_output_id[to.id()] = new_value; + return new_array_ref; + } + + GenericMutableArrayRef get_mutable_single__single(const MFInputSocket &input, + const MFOutputSocket &output) + { + const MFOutputSocket &from = input.origin(); + const MFOutputSocket &to = output; + const CPPType &type = from.data_type().single__cpp_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(type == to.data_type().single__cpp_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputSingle); + GenericMutableArrayRef array_ref = ((OutputSingleValue *)to_any_value)->array_ref; + BLI_assert(array_ref.size() == 1); + GenericVirtualListRef list_ref = this->get_single_input__single(input); + type.copy_to_uninitialized(list_ref.as_single_element(), array_ref[0]); + return array_ref; + } + + if (from_any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.target_amount(); + BLI_assert(value->array_ref.size() == 1); + return value->array_ref; + } + } + + GenericVirtualListRef list_ref = this->get_single_input__single(input); + + void *new_buffer = m_allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(list_ref.as_single_element(), new_buffer); + GenericMutableArrayRef new_array_ref(type, new_buffer, 1); + + OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>( + new_array_ref, to.target_amount(), true); + m_value_per_output_id[to.id()] = new_value; + return new_array_ref; + } + + GenericVectorArray &get_mutable_vector__full(const MFInputSocket &input, + const MFOutputSocket &output) + { + const MFOutputSocket &from = input.origin(); + const MFOutputSocket &to = output; + const CPPType &base_type = from.data_type().vector__cpp_base_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(base_type == to.data_type().vector__cpp_base_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputVector); + GenericVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array; + GenericVirtualListListRef list_list_ref = this->get_vector_input__full(input); + vector_array.extend_multiple__copy(m_mask, list_list_ref); + return vector_array; + } + + if (from_any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.target_amount(); + return *value->vector_array; + } + } + + GenericVirtualListListRef list_list_ref = this->get_vector_input__full(input); + + GenericVectorArray *new_vector_array = new GenericVectorArray(base_type, m_min_array_size); + new_vector_array->extend_multiple__copy(m_mask, list_list_ref); + + OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array, + to.target_amount()); + m_value_per_output_id[to.id()] = new_value; + + return *new_vector_array; + } + + GenericVectorArray &get_mutable_vector__single(const MFInputSocket &input, + const MFOutputSocket &output) + { + const MFOutputSocket &from = input.origin(); + const MFOutputSocket &to = output; + const CPPType &base_type = from.data_type().vector__cpp_base_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(base_type == to.data_type().vector__cpp_base_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputVector); + GenericVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array; + BLI_assert(vector_array.size() == 1); + GenericVirtualListListRef list_list_ref = this->get_vector_input__single(input); + vector_array.extend_single__copy(0, list_list_ref[0]); + return vector_array; + } + + if (from_any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.target_amount(); + return *value->vector_array; + } + } + + GenericVirtualListListRef list_list_ref = this->get_vector_input__single(input); + + GenericVectorArray *new_vector_array = new GenericVectorArray(base_type, 1); + new_vector_array->extend_single__copy(0, list_list_ref[0]); + + OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array, + to.target_amount()); + m_value_per_output_id[to.id()] = new_value; + return *new_vector_array; + } + + /* Get readonly inputs for a function call. + **************************************************/ + + GenericVirtualListRef get_single_input__full(const MFInputSocket &socket) + { + const MFOutputSocket &origin = socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + if (value->is_single_allocated) { + return GenericVirtualListRef::FromSingle( + value->array_ref.type(), value->array_ref.buffer(), m_min_array_size); + } + else { + return value->array_ref; + } + } + else if (any_value->type == ValueType::InputSingle) { + InputSingleValue *value = (InputSingleValue *)any_value; + return value->list_ref; + } + else if (any_value->type == ValueType::OutputSingle) { + OutputSingleValue *value = (OutputSingleValue *)any_value; + BLI_assert(value->is_computed); + return value->array_ref; + } + + BLI_assert(false); + return GenericVirtualListRef(CPPType_float); + } + + GenericVirtualListRef get_single_input__single(const MFInputSocket &socket) + { + const MFOutputSocket &origin = socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + BLI_assert(value->array_ref.size() == 1); + return value->array_ref; + } + else if (any_value->type == ValueType::InputSingle) { + InputSingleValue *value = (InputSingleValue *)any_value; + BLI_assert(value->list_ref.is_single_element()); + return value->list_ref; + } + else if (any_value->type == ValueType::OutputSingle) { + OutputSingleValue *value = (OutputSingleValue *)any_value; + BLI_assert(value->is_computed); + BLI_assert(value->array_ref.size() == 1); + return value->array_ref; + } + + BLI_assert(false); + return GenericVirtualListRef(CPPType_float); + } + + GenericVirtualListListRef get_vector_input__full(const MFInputSocket &socket) + { + const MFOutputSocket &origin = socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + if (value->vector_array->size() == 1) { + GenericArrayRef array_ref = (*value->vector_array)[0]; + return GenericVirtualListListRef::FromSingleArray( + array_ref.type(), array_ref.buffer(), array_ref.size(), m_min_array_size); + } + else { + return *value->vector_array; + } + } + else if (any_value->type == ValueType::InputVector) { + InputVectorValue *value = (InputVectorValue *)any_value; + return value->list_list_ref; + } + else if (any_value->type == ValueType::OutputVector) { + OutputVectorValue *value = (OutputVectorValue *)any_value; + return *value->vector_array; + } + + BLI_assert(false); + return GenericVirtualListListRef::FromSingleArray(CPPType_float, nullptr, 0, 0); + } + + GenericVirtualListListRef get_vector_input__single(const MFInputSocket &socket) + { + const MFOutputSocket &origin = socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + BLI_assert(value->vector_array->size() == 1); + return *value->vector_array; + } + else if (any_value->type == ValueType::InputVector) { + InputVectorValue *value = (InputVectorValue *)any_value; + BLI_assert(value->list_list_ref.is_single_list()); + return value->list_list_ref; + } + else if (any_value->type == ValueType::OutputVector) { + OutputVectorValue *value = (OutputVectorValue *)any_value; + BLI_assert(value->vector_array->size() == 1); + return *value->vector_array; + } + + BLI_assert(false); + return GenericVirtualListListRef::FromSingleArray(CPPType_float, nullptr, 0, 0); + } +}; + +MF_EvaluateNetwork::MF_EvaluateNetwork(Vector<const MFOutputSocket *> inputs, + Vector<const MFInputSocket *> outputs) + : m_inputs(std::move(inputs)), m_outputs(std::move(outputs)) +{ + BLI_assert(m_outputs.size() > 0); + const MFNetwork &network = m_outputs[0]->node().network(); + + MFSignatureBuilder signature = this->get_builder("Function Tree"); + + Vector<const MFFunctionNode *> used_function_nodes = network.find_function_dependencies( + m_outputs); + for (const MFFunctionNode *node : used_function_nodes) { + signature.copy_used_contexts(node->function()); + } + + for (auto socket : m_inputs) { + BLI_assert(socket->node().is_dummy()); + + MFDataType type = socket->data_type(); + switch (type.category()) { + case MFDataType::Single: + signature.single_input("Input", type.single__cpp_type()); + break; + case MFDataType::Vector: + signature.vector_input("Input", type.vector__cpp_base_type()); + break; + } + } + + for (auto socket : m_outputs) { + BLI_assert(socket->node().is_dummy()); + + MFDataType type = socket->data_type(); + switch (type.category()) { + case MFDataType::Single: + signature.single_output("Output", type.single__cpp_type()); + break; + case MFDataType::Vector: + signature.vector_output("Output", type.vector__cpp_base_type()); + break; + } + } +} + +void MF_EvaluateNetwork::call(IndexMask mask, MFParams params, MFContext context) const +{ + if (mask.size() == 0) { + return; + } + + const MFNetwork &network = m_outputs[0]->node().network(); + Storage storage(context.buffer_cache(), mask, network.socket_ids().size()); + + Vector<const MFInputSocket *> outputs_to_initialize_in_the_end; + + this->copy_inputs_to_storage(params, storage); + this->copy_outputs_to_storage(params, storage, outputs_to_initialize_in_the_end); + this->evaluate_network_to_compute_outputs(context, storage); + this->initialize_remaining_outputs(params, storage, outputs_to_initialize_in_the_end); +} + +BLI_NOINLINE void MF_EvaluateNetwork::copy_inputs_to_storage(MFParams params, + Storage &storage) const +{ + for (uint input_index : m_inputs.index_range()) { + uint param_index = input_index + 0; + const MFOutputSocket &socket = *m_inputs[input_index]; + switch (socket.data_type().category()) { + case MFDataType::Single: { + GenericVirtualListRef input_list = params.readonly_single_input(param_index); + storage.add_single_input_from_caller(socket, input_list); + break; + } + case MFDataType::Vector: { + GenericVirtualListListRef input_list_list = params.readonly_vector_input(param_index); + storage.add_vector_input_from_caller(socket, input_list_list); + break; + } + } + } +} + +BLI_NOINLINE void MF_EvaluateNetwork::copy_outputs_to_storage( + MFParams params, + Storage &storage, + Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const +{ + for (uint output_index : m_outputs.index_range()) { + uint param_index = output_index + m_inputs.size(); + const MFInputSocket &socket = *m_outputs[output_index]; + const MFOutputSocket &origin = socket.origin(); + + if (origin.node().is_dummy()) { + BLI_assert(m_inputs.contains(&origin)); + /* Don't overwrite input buffers. */ + outputs_to_initialize_in_the_end.append(&socket); + continue; + } + + if (storage.socket_has_buffer_for_output(origin)) { + /* When two outputs will be initialized to the same values. */ + outputs_to_initialize_in_the_end.append(&socket); + continue; + } + + switch (socket.data_type().category()) { + case MFDataType::Single: { + GenericMutableArrayRef array_ref = params.uninitialized_single_output(param_index); + storage.add_single_output_from_caller(origin, array_ref); + break; + } + case MFDataType::Vector: { + GenericVectorArray &vector_array = params.vector_output(param_index); + storage.add_vector_output_from_caller(origin, vector_array); + break; + } + } + } +} + +BLI_NOINLINE void MF_EvaluateNetwork::evaluate_network_to_compute_outputs( + MFContext &global_context, Storage &storage) const +{ + const MFNetwork &network = m_outputs[0]->node().network(); + ArrayRef<uint> max_dependency_depths = network.max_dependency_depth_per_node(); + + Stack<const MFOutputSocket *> sockets_to_compute; + for (const MFInputSocket *socket : m_outputs) { + sockets_to_compute.push(&socket->origin()); + } + + ScopedVector<const MFOutputSocket *> missing_sockets; + + while (!sockets_to_compute.is_empty()) { + const MFOutputSocket &socket = *sockets_to_compute.peek(); + const MFNode &node = socket.node(); + + if (storage.socket_is_computed(socket)) { + sockets_to_compute.pop(); + continue; + } + + BLI_assert(node.is_function()); + const MFFunctionNode &function_node = node.as_function(); + + missing_sockets.clear(); + function_node.foreach_origin_socket([&](const MFOutputSocket &origin) { + if (!storage.socket_is_computed(origin)) { + missing_sockets.append(&origin); + } + }); + + std::sort(missing_sockets.begin(), + missing_sockets.end(), + [&](const MFOutputSocket *a, const MFOutputSocket *b) { + return max_dependency_depths[a->node().id()] < + max_dependency_depths[b->node().id()]; + }); + + sockets_to_compute.push_multiple(missing_sockets.as_ref()); + + bool all_inputs_are_computed = missing_sockets.size() == 0; + if (all_inputs_are_computed) { + this->evaluate_function(global_context, function_node, storage); + sockets_to_compute.pop(); + } + } +} + +BLI_NOINLINE void MF_EvaluateNetwork::evaluate_function(MFContext &global_context, + const MFFunctionNode &function_node, + Storage &storage) const +{ + const MultiFunction &function = function_node.function(); + // std::cout << "Function: " << function.name() << "\n"; + + if (this->can_do_single_value_evaluation(function_node, storage)) { + MFParamsBuilder params_builder{function, 1}; + + for (uint param_index : function.param_indices()) { + MFParamType param_type = function.param_type(param_index); + switch (param_type.type()) { + case MFParamType::SingleInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GenericVirtualListRef values = storage.get_single_input__single(socket); + params_builder.add_readonly_single_input(values); + break; + } + case MFParamType::VectorInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GenericVirtualListListRef values = storage.get_vector_input__single(socket); + params_builder.add_readonly_vector_input(values); + break; + } + case MFParamType::SingleOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GenericMutableArrayRef values = storage.get_single_output__single(socket); + params_builder.add_single_output(values); + break; + } + case MFParamType::VectorOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GenericVectorArray &values = storage.get_vector_output__single(socket); + params_builder.add_vector_output(values); + break; + } + case MFParamType::MutableSingle: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GenericMutableArrayRef values = storage.get_mutable_single__single(input, output); + params_builder.add_mutable_single(values); + break; + } + case MFParamType::MutableVector: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GenericVectorArray &values = storage.get_mutable_vector__single(input, output); + params_builder.add_mutable_vector(values); + break; + } + } + } + + function.call(IndexRange(1), params_builder, global_context); + } + else { + MFParamsBuilder params_builder{function, storage.mask().min_array_size()}; + + for (uint param_index : function.param_indices()) { + MFParamType param_type = function.param_type(param_index); + switch (param_type.type()) { + case MFParamType::SingleInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GenericVirtualListRef values = storage.get_single_input__full(socket); + params_builder.add_readonly_single_input(values); + break; + } + case MFParamType::VectorInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GenericVirtualListListRef values = storage.get_vector_input__full(socket); + params_builder.add_readonly_vector_input(values); + break; + } + case MFParamType::SingleOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GenericMutableArrayRef values = storage.get_single_output__full(socket); + params_builder.add_single_output(values); + break; + } + case MFParamType::VectorOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GenericVectorArray &values = storage.get_vector_output__full(socket); + params_builder.add_vector_output(values); + break; + } + case MFParamType::MutableSingle: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GenericMutableArrayRef values = storage.get_mutable_single__full(input, output); + params_builder.add_mutable_single(values); + break; + } + case MFParamType::MutableVector: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GenericVectorArray &values = storage.get_mutable_vector__full(input, output); + params_builder.add_mutable_vector(values); + break; + } + } + } + + function.call(storage.mask(), params_builder, global_context); + } + + for (const MFInputSocket *socket : function_node.inputs()) { + storage.finish_input_socket(*socket); + } + for (const MFOutputSocket *socket : function_node.outputs()) { + storage.finish_output_socket(*socket); + } +} + +bool MF_EvaluateNetwork::can_do_single_value_evaluation(const MFFunctionNode &function_node, + Storage &storage) const +{ + if (function_node.function().depends_on_per_element_context()) { + return false; + } + for (const MFInputSocket *socket : function_node.inputs()) { + if (!storage.is_same_value_for_every_index(socket->origin())) { + return false; + } + } + if (storage.mask().min_array_size() >= 1) { + for (const MFOutputSocket *socket : function_node.outputs()) { + if (storage.socket_has_buffer_for_output(*socket)) { + return false; + } + } + } + return true; +} + +BLI_NOINLINE void MF_EvaluateNetwork::initialize_remaining_outputs( + MFParams params, Storage &storage, ArrayRef<const MFInputSocket *> remaining_outputs) const +{ + for (const MFInputSocket *socket : remaining_outputs) { + uint param_index = m_inputs.size() + m_outputs.index(socket); + + switch (socket->data_type().category()) { + case MFDataType::Single: { + GenericVirtualListRef values = storage.get_single_input__full(*socket); + GenericMutableArrayRef output_values = params.uninitialized_single_output(param_index); + values.materialize_to_uninitialized(storage.mask(), output_values); + break; + } + case MFDataType::Vector: { + GenericVirtualListListRef values = storage.get_vector_input__full(*socket); + GenericVectorArray &output_values = params.vector_output(param_index); + output_values.extend_multiple__copy(storage.mask(), values); + break; + } + } + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/network.h b/source/blender/functions/intern/multi_functions/network.h new file mode 100644 index 00000000000..ecd530fb14d --- /dev/null +++ b/source/blender/functions/intern/multi_functions/network.h @@ -0,0 +1,47 @@ +#pragma once + +#include "FN_multi_function_network.h" + +#include "BLI_map.h" +#include "BLI_stack_cxx.h" + +namespace FN { + +using BLI::Map; +using BLI::Stack; + +class NetworkEvaluationStorage; + +class MF_EvaluateNetwork final : public MultiFunction { + private: + Vector<const MFOutputSocket *> m_inputs; + Vector<const MFInputSocket *> m_outputs; + + public: + MF_EvaluateNetwork(Vector<const MFOutputSocket *> inputs, Vector<const MFInputSocket *> outputs); + + void call(IndexMask mask, MFParams params, MFContext context) const override; + + private: + using Storage = NetworkEvaluationStorage; + + void copy_inputs_to_storage(MFParams params, Storage &storage) const; + void copy_outputs_to_storage( + MFParams params, + Storage &storage, + Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const; + + void evaluate_network_to_compute_outputs(MFContext &global_context, Storage &storage) const; + + void evaluate_function(MFContext &global_context, + const MFFunctionNode &function_node, + Storage &storage) const; + + bool can_do_single_value_evaluation(const MFFunctionNode &function_node, Storage &storage) const; + + void initialize_remaining_outputs(MFParams params, + Storage &storage, + ArrayRef<const MFInputSocket *> remaining_outputs) const; +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/particles.cc b/source/blender/functions/intern/multi_functions/particles.cc new file mode 100644 index 00000000000..820e49d9274 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/particles.cc @@ -0,0 +1,129 @@ +#include "particles.h" +#include "util.h" + +#include "BLI_rand_cxx.h" +#include "FN_multi_function_common_contexts.h" + +namespace FN { + +MF_ParticleAttribute::MF_ParticleAttribute(const CPPType &type) : m_type(type) +{ + MFSignatureBuilder signature = this->get_builder("Particle Attribute"); + signature.use_element_context<ParticleAttributesContext>(); + signature.single_input<std::string>("Attribute Name"); + signature.single_output("Value", type); +} + +void MF_ParticleAttribute::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<std::string> attribute_names = params.readonly_single_input<std::string>( + 0, "Attribute Name"); + GenericMutableArrayRef r_values = params.uninitialized_single_output(1, "Value"); + + auto context_data = context.try_find_per_element<ParticleAttributesContext>(); + if (!context_data.has_value()) { + r_values.default_initialize(mask.indices()); + return; + } + + AttributesRef attributes = context_data->data->attributes; + MFElementContextIndices element_indices = context_data->indices; + + group_indices_by_same_value( + mask, attribute_names, [&](StringRef attribute_name, IndexMask indices_with_same_name) { + Optional<GenericArrayRef> opt_array = attributes.try_get(attribute_name, m_type); + if (!opt_array.has_value()) { + r_values.default_initialize(indices_with_same_name); + return; + } + GenericArrayRef array = opt_array.value(); + if (element_indices.is_direct_mapping()) { + m_type.copy_to_uninitialized_indices( + array.buffer(), r_values.buffer(), indices_with_same_name); + } + else { + for (uint i : indices_with_same_name) { + uint index = element_indices[i]; + r_values.copy_in__initialized(i, array[index]); + } + } + }); +} + +MF_EmitterTimeInfo::MF_EmitterTimeInfo() +{ + MFSignatureBuilder signature = this->get_builder("Emitter Time Info"); + signature.use_global_context<EmitterTimeInfoContext>(); + signature.single_output<float>("Duration"); + signature.single_output<float>("Begin"); + signature.single_output<float>("End"); + signature.single_output<int>("Step"); +} + +void MF_EmitterTimeInfo::call(IndexMask mask, MFParams params, MFContext context) const +{ + MutableArrayRef<float> r_durations = params.uninitialized_single_output<float>(0, "Duration"); + MutableArrayRef<float> r_begins = params.uninitialized_single_output<float>(1, "Begin"); + MutableArrayRef<float> r_ends = params.uninitialized_single_output<float>(2, "End"); + MutableArrayRef<int> r_steps = params.uninitialized_single_output<int>(3, "Step"); + + auto *time_context = context.try_find_global<EmitterTimeInfoContext>(); + + if (time_context == nullptr) { + r_durations.fill_indices(mask, 0.0f); + r_begins.fill_indices(mask, 0.0f); + r_ends.fill_indices(mask, 0.0f); + r_steps.fill_indices(mask, 0); + } + else { + r_durations.fill_indices(mask, time_context->duration); + r_begins.fill_indices(mask, time_context->begin); + r_ends.fill_indices(mask, time_context->end); + r_steps.fill_indices(mask, time_context->step); + } +} + +MF_EventFilterEndTime::MF_EventFilterEndTime() +{ + MFSignatureBuilder signature = this->get_builder("Event Filter End Time"); + signature.use_global_context<EventFilterEndTimeContext>(); + signature.single_output<float>("End Time"); +} + +void MF_EventFilterEndTime::call(IndexMask mask, MFParams params, MFContext context) const +{ + MutableArrayRef<float> r_end_times = params.uninitialized_single_output<float>(0, "End Time"); + + auto *time_context = context.try_find_global<EventFilterEndTimeContext>(); + if (time_context == nullptr) { + r_end_times.fill_indices(mask, 0.0f); + } + else { + r_end_times.fill_indices(mask, time_context->end_time); + } +} + +MF_EventFilterDuration::MF_EventFilterDuration() +{ + MFSignatureBuilder signature = this->get_builder("Event Filter Duration"); + signature.use_element_context<EventFilterDurationsContext>(); + signature.single_output<float>("Duration"); +} + +void MF_EventFilterDuration::call(IndexMask mask, MFParams params, MFContext context) const +{ + MutableArrayRef<float> r_durations = params.uninitialized_single_output<float>(0, "Duration"); + + auto duration_context = context.try_find_per_element<EventFilterDurationsContext>(); + if (duration_context.has_value()) { + for (uint i : mask) { + uint index = duration_context->indices[i]; + r_durations[i] = duration_context->data->durations[index]; + } + } + else { + r_durations.fill_indices(mask, 0.0f); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/particles.h b/source/blender/functions/intern/multi_functions/particles.h new file mode 100644 index 00000000000..2c2c6aae67a --- /dev/null +++ b/source/blender/functions/intern/multi_functions/particles.h @@ -0,0 +1,34 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +class MF_ParticleAttribute final : public MultiFunction { + private: + const CPPType &m_type; + + public: + MF_ParticleAttribute(const CPPType &type); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_EmitterTimeInfo final : public MultiFunction { + public: + MF_EmitterTimeInfo(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_EventFilterEndTime final : public MultiFunction { + public: + MF_EventFilterEndTime(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_EventFilterDuration final : public MultiFunction { + public: + MF_EventFilterDuration(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/sampling_util.cc b/source/blender/functions/intern/multi_functions/sampling_util.cc new file mode 100644 index 00000000000..29c0d52c00c --- /dev/null +++ b/source/blender/functions/intern/multi_functions/sampling_util.cc @@ -0,0 +1,89 @@ +#include "sampling_util.h" + +#include "BLI_vector_adaptor.h" + +namespace FN { + +using BLI::VectorAdaptor; + +float compute_cumulative_distribution(ArrayRef<float> weights, + MutableArrayRef<float> r_cumulative_weights) +{ + BLI_assert(weights.size() + 1 == r_cumulative_weights.size()); + + r_cumulative_weights[0] = 0; + for (uint i : weights.index_range()) { + r_cumulative_weights[i + 1] = r_cumulative_weights[i] + weights[i]; + } + + float weight_sum = r_cumulative_weights.last(); + return weight_sum; +} + +static void sample_cumulative_distribution__recursive(RNG *rng, + uint amount, + uint start, + uint one_after_end, + ArrayRef<float> cumulative_weights, + VectorAdaptor<uint> &sampled_indices) +{ + BLI_assert(start <= one_after_end); + uint size = one_after_end - start; + if (size == 0) { + BLI_assert(amount == 0); + } + else if (amount == 0) { + return; + } + else if (size == 1) { + sampled_indices.append_n_times(start, amount); + } + else { + uint middle = start + size / 2; + float left_weight = cumulative_weights[middle] - cumulative_weights[start]; + float right_weight = cumulative_weights[one_after_end] - cumulative_weights[middle]; + BLI_assert(left_weight >= 0.0f && right_weight >= 0.0f); + float weight_sum = left_weight + right_weight; + BLI_assert(weight_sum > 0.0f); + + float left_factor = left_weight / weight_sum; + float right_factor = right_weight / weight_sum; + + uint left_amount = amount * left_factor; + uint right_amount = amount * right_factor; + + if (left_amount + right_amount < amount) { + BLI_assert(left_amount + right_amount + 1 == amount); + float weight_per_item = weight_sum / amount; + float total_remaining_weight = weight_sum - (left_amount + right_amount) * weight_per_item; + float left_remaining_weight = left_weight - left_amount * weight_per_item; + float left_remaining_factor = left_remaining_weight / total_remaining_weight; + if (BLI_rng_get_float(rng) < left_remaining_factor) { + left_amount++; + } + else { + right_amount++; + } + } + + sample_cumulative_distribution__recursive( + rng, left_amount, start, middle, cumulative_weights, sampled_indices); + sample_cumulative_distribution__recursive( + rng, right_amount, middle, one_after_end, cumulative_weights, sampled_indices); + } +} + +void sample_cumulative_distribution(RNG *rng, + ArrayRef<float> cumulative_weights, + MutableArrayRef<uint> r_sampled_indices) +{ + BLI_assert(r_sampled_indices.size() == 0 || cumulative_weights.last() > 0.0f); + + uint amount = r_sampled_indices.size(); + VectorAdaptor<uint> sampled_indices(r_sampled_indices.begin(), amount); + sample_cumulative_distribution__recursive( + rng, amount, 0, cumulative_weights.size() - 1, cumulative_weights, sampled_indices); + BLI_assert(sampled_indices.is_full()); +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/sampling_util.h b/source/blender/functions/intern/multi_functions/sampling_util.h new file mode 100644 index 00000000000..4e78e36d079 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/sampling_util.h @@ -0,0 +1,19 @@ +#pragma once + +#include "BLI_array_ref.h" + +#include "BLI_rand.h" + +namespace FN { + +using BLI::ArrayRef; +using BLI::MutableArrayRef; + +float compute_cumulative_distribution(ArrayRef<float> weights, + MutableArrayRef<float> r_cumulative_weights); + +void sample_cumulative_distribution(RNG *rng, + ArrayRef<float> cumulative_weights, + MutableArrayRef<uint> r_sampled_indices); + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/surface_hook.cc b/source/blender/functions/intern/multi_functions/surface_hook.cc new file mode 100644 index 00000000000..f53e522ba84 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/surface_hook.cc @@ -0,0 +1,625 @@ +#include "surface_hook.h" + +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_id_data_cache.h" +#include "BKE_id_handle.h" +#include "BKE_image.h" +#include "BKE_mesh_runtime.h" +#include "BKE_surface_hook.h" + +#include "IMB_imbuf_types.h" + +#include "DNA_customdata_types.h" + +#include "BLI_array_cxx.h" +#include "BLI_color.h" +#include "BLI_float2.h" +#include "BLI_float3.h" +#include "BLI_float4x4.h" +#include "BLI_vector_adaptor.h" + +#include "sampling_util.h" +#include "util.h" + +namespace FN { + +using BKE::IDDataCache; +using BKE::IDHandleLookup; +using BKE::ImageIDHandle; +using BKE::ObjectIDHandle; +using BKE::SurfaceHook; +using BLI::Array; +using BLI::float2; +using BLI::float3; +using BLI::float4x4; +using BLI::rgba_b; +using BLI::rgba_f; +using BLI::VectorAdaptor; + +MF_ClosestSurfaceHookOnObject::MF_ClosestSurfaceHookOnObject() +{ + MFSignatureBuilder signature = this->get_builder("Closest Point on Object"); + signature.use_global_context<IDDataCache>(); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<ObjectIDHandle>("Object"); + signature.single_input<float3>("Position"); + signature.single_output<SurfaceHook>("Closest Location"); +} + +static BVHTreeNearest get_nearest_point(BVHTreeFromMesh *bvhtree_data, float3 point) +{ + BVHTreeNearest nearest = {0}; + nearest.dist_sq = 10000000.0f; + nearest.index = -1; + BLI_bvhtree_find_nearest( + bvhtree_data->tree, point, &nearest, bvhtree_data->nearest_callback, (void *)bvhtree_data); + return nearest; +} + +static float3 get_barycentric_coords(Mesh *mesh, + const MLoopTri *triangles, + float3 position, + uint triangle_index) +{ + const MLoopTri &triangle = triangles[triangle_index]; + + float3 v1 = mesh->mvert[mesh->mloop[triangle.tri[0]].v].co; + float3 v2 = mesh->mvert[mesh->mloop[triangle.tri[1]].v].co; + float3 v3 = mesh->mvert[mesh->mloop[triangle.tri[2]].v].co; + + float3 weights; + interp_weights_tri_v3(weights, v1, v2, v3, position); + return weights; +} + +void MF_ClosestSurfaceHookOnObject::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<ObjectIDHandle> object_handles = params.readonly_single_input<ObjectIDHandle>( + 0, "Object"); + VirtualListRef<float3> positions = params.readonly_single_input<float3>(1, "Position"); + MutableArrayRef<SurfaceHook> r_surface_hooks = params.uninitialized_single_output<SurfaceHook>( + 2, "Closest Location"); + + auto *id_data_cache = context.try_find_global<IDDataCache>(); + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + + if (id_data_cache == nullptr || id_handle_lookup == nullptr) { + r_surface_hooks.fill_indices(mask.indices(), {}); + return; + } + + group_indices_by_same_value( + mask.indices(), + object_handles, + [&](ObjectIDHandle object_handle, IndexMask indices_with_same_object) { + Object *object = id_handle_lookup->lookup(object_handle); + if (object == nullptr) { + r_surface_hooks.fill_indices(indices_with_same_object, {}); + return; + } + + BVHTreeFromMesh *bvhtree = id_data_cache->get_bvh_tree(object); + if (bvhtree == nullptr) { + r_surface_hooks.fill_indices(indices_with_same_object, {}); + return; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(mesh); + + float4x4 global_to_local = float4x4(object->obmat).inverted__LocRotScale(); + + for (uint i : indices_with_same_object) { + float3 local_position = global_to_local.transform_position(positions[i]); + BVHTreeNearest nearest = get_nearest_point(bvhtree, local_position); + if (nearest.index == -1) { + r_surface_hooks[i] = {}; + continue; + } + + float3 bary_coords = get_barycentric_coords(mesh, triangles, nearest.co, nearest.index); + r_surface_hooks[i] = SurfaceHook(object_handle, nearest.index, bary_coords); + } + }); +} + +MF_GetPositionOnSurface::MF_GetPositionOnSurface() +{ + MFSignatureBuilder signature = this->get_builder("Get Position on Surface"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<SurfaceHook>("Surface Hook"); + signature.single_output<float3>("Position"); +} + +void MF_GetPositionOnSurface::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<SurfaceHook> surface_hooks = params.readonly_single_input<SurfaceHook>( + 0, "Surface Hook"); + MutableArrayRef<float3> r_positions = params.uninitialized_single_output<float3>(1, "Position"); + + float3 fallback = {0, 0, 0}; + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + r_positions.fill_indices(mask.indices(), fallback); + return; + } + + group_indices_by_same_value( + mask.indices(), + surface_hooks, + [&](SurfaceHook base_hook, IndexMask indices_on_same_surface) { + if (base_hook.type() != BKE::SurfaceHookType::MeshObject) { + r_positions.fill_indices(indices_on_same_surface, fallback); + return; + } + + Object *object = id_handle_lookup->lookup(base_hook.object_handle()); + if (object == nullptr) { + r_positions.fill_indices(indices_on_same_surface, fallback); + return; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(mesh); + int triangle_amount = BKE_mesh_runtime_looptri_len(mesh); + + for (uint i : indices_on_same_surface) { + SurfaceHook hook = surface_hooks[i]; + + if (hook.triangle_index() >= triangle_amount) { + r_positions[i] = fallback; + continue; + } + + const MLoopTri &triangle = triangles[hook.triangle_index()]; + float3 v1 = mesh->mvert[mesh->mloop[triangle.tri[0]].v].co; + float3 v2 = mesh->mvert[mesh->mloop[triangle.tri[1]].v].co; + float3 v3 = mesh->mvert[mesh->mloop[triangle.tri[2]].v].co; + + float3 position; + interp_v3_v3v3v3(position, v1, v2, v3, hook.bary_coords()); + float4x4 local_to_world = object->obmat; + position = local_to_world.transform_position(position); + + r_positions[i] = position; + } + }, + SurfaceHook::on_same_surface); +} + +MF_GetNormalOnSurface::MF_GetNormalOnSurface() +{ + MFSignatureBuilder signature = this->get_builder("Get Normal on Surface"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<SurfaceHook>("Surface Hook"); + signature.single_output<float3>("Normal"); +} + +static float3 short_normal_to_float3(const short normal[3]) +{ + return float3( + (float)normal[0] / 32767.0f, (float)normal[1] / 32767.0f, (float)normal[2] / 32767.0f); +} + +void MF_GetNormalOnSurface::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<SurfaceHook> surface_hooks = params.readonly_single_input<SurfaceHook>( + 0, "Surface Hook"); + MutableArrayRef<float3> r_normals = params.uninitialized_single_output<float3>(1, "Normal"); + + float3 fallback = {0, 0, 1}; + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + r_normals.fill_indices(mask.indices(), fallback); + return; + } + + group_indices_by_same_value( + mask.indices(), + surface_hooks, + [&](SurfaceHook base_hook, IndexMask indices_on_same_surface) { + if (base_hook.type() != BKE::SurfaceHookType::MeshObject) { + r_normals.fill_indices(indices_on_same_surface, fallback); + return; + } + + Object *object = id_handle_lookup->lookup(base_hook.object_handle()); + if (object == nullptr) { + r_normals.fill_indices(indices_on_same_surface, fallback); + return; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(mesh); + int triangle_amount = BKE_mesh_runtime_looptri_len(mesh); + + for (uint i : indices_on_same_surface) { + SurfaceHook hook = surface_hooks[i]; + + if (hook.triangle_index() >= triangle_amount) { + r_normals[i] = fallback; + continue; + } + + const MLoopTri &triangle = triangles[hook.triangle_index()]; + float3 v1 = short_normal_to_float3(mesh->mvert[mesh->mloop[triangle.tri[0]].v].no); + float3 v2 = short_normal_to_float3(mesh->mvert[mesh->mloop[triangle.tri[1]].v].no); + float3 v3 = short_normal_to_float3(mesh->mvert[mesh->mloop[triangle.tri[2]].v].no); + + float3 position; + interp_v3_v3v3v3(position, v1, v2, v3, hook.bary_coords()); + float4x4 local_to_world = object->obmat; + position = local_to_world.transform_direction(position); + + r_normals[i] = position; + } + }, + SurfaceHook::on_same_surface); +} + +MF_GetWeightOnSurface::MF_GetWeightOnSurface() +{ + MFSignatureBuilder signature = this->get_builder("Get Weight on Surface"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<SurfaceHook>("Surface Hook"); + signature.single_input<std::string>("Group Name"); + signature.single_output<float>("Weight"); +} + +void MF_GetWeightOnSurface::call(IndexMask mask, MFParams params, MFContext context) const +{ + VirtualListRef<SurfaceHook> surface_hooks = params.readonly_single_input<SurfaceHook>( + 0, "Surface Hook"); + VirtualListRef<std::string> group_names = params.readonly_single_input<std::string>( + 1, "Group Name"); + MutableArrayRef<float> r_weights = params.uninitialized_single_output<float>(2, "Weight"); + + float fallback = 0.0f; + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + r_weights.fill_indices(mask.indices(), fallback); + return; + } + + group_indices_by_same_value( + mask.indices(), + surface_hooks, + [&](SurfaceHook base_hook, IndexMask indices_on_same_surface) { + if (base_hook.type() != BKE::SurfaceHookType::MeshObject) { + r_weights.fill_indices(indices_on_same_surface, fallback); + return; + } + + Object *object = id_handle_lookup->lookup(base_hook.object_handle()); + if (object == nullptr) { + r_weights.fill_indices(indices_on_same_surface, fallback); + return; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(mesh); + int triangle_amount = BKE_mesh_runtime_looptri_len(mesh); + + group_indices_by_same_value( + indices_on_same_surface, + group_names, + [&](const std::string &group, IndexMask indices_with_same_group) { + MDeformVert *vertex_weights = mesh->dvert; + int group_index = BKE_object_defgroup_name_index(object, group.c_str()); + if (group_index == -1 || vertex_weights == nullptr) { + r_weights.fill_indices(indices_on_same_surface, fallback); + return; + } + for (uint i : indices_with_same_group) { + SurfaceHook hook = surface_hooks[i]; + + if (hook.triangle_index() >= triangle_amount) { + r_weights[i] = fallback; + continue; + } + + const MLoopTri &triangle = triangles[hook.triangle_index()]; + uint v1 = mesh->mloop[triangle.tri[0]].v; + uint v2 = mesh->mloop[triangle.tri[1]].v; + uint v3 = mesh->mloop[triangle.tri[2]].v; + + float3 corner_weights{BKE_defvert_find_weight(vertex_weights + v1, group_index), + BKE_defvert_find_weight(vertex_weights + v2, group_index), + BKE_defvert_find_weight(vertex_weights + v3, group_index)}; + + float weight = float3::dot(hook.bary_coords(), corner_weights); + r_weights[i] = weight; + } + }); + }, + SurfaceHook::on_same_surface); +} + +MF_GetImageColorOnSurface::MF_GetImageColorOnSurface() +{ + MFSignatureBuilder signature = this->get_builder("Get Image Color on Surface"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<SurfaceHook>("Surface Hook"); + signature.single_input<ImageIDHandle>("Image"); + signature.single_output<rgba_f>("Color"); +} + +static void get_colors_on_surface(IndexMask indices, + VirtualListRef<SurfaceHook> surface_hooks, + MutableArrayRef<rgba_f> r_colors, + rgba_f fallback, + const IDHandleLookup &id_handle_lookup, + const ImBuf &ibuf) +{ + group_indices_by_same_value( + indices, + surface_hooks, + [&](SurfaceHook base_hook, IndexMask indices_on_same_surface) { + if (base_hook.type() != BKE::SurfaceHookType::MeshObject) { + r_colors.fill_indices(indices_on_same_surface, fallback); + return; + } + + Object *object = id_handle_lookup.lookup(base_hook.object_handle()); + if (object == nullptr) { + r_colors.fill_indices(indices_on_same_surface, fallback); + return; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles = BKE_mesh_runtime_looptri_ensure(mesh); + int triangle_amount = BKE_mesh_runtime_looptri_len(mesh); + + int uv_layer_index = 0; + ArrayRef<MLoopUV> uv_layer = BLI::ref_c_array( + (MLoopUV *)CustomData_get_layer_n(&mesh->ldata, CD_MLOOPUV, uv_layer_index), + mesh->totloop); + + ArrayRef<rgba_b> pixel_buffer = BLI::ref_c_array((rgba_b *)ibuf.rect, ibuf.x * ibuf.y); + + for (uint i : indices_on_same_surface) { + SurfaceHook hook = surface_hooks[i]; + if (hook.triangle_index() >= triangle_amount) { + r_colors[i] = fallback; + continue; + } + + const MLoopTri &triangle = triangles[hook.triangle_index()]; + + float2 uv1 = uv_layer[triangle.tri[0]].uv; + float2 uv2 = uv_layer[triangle.tri[1]].uv; + float2 uv3 = uv_layer[triangle.tri[2]].uv; + + float2 uv; + interp_v2_v2v2v2(uv, uv1, uv2, uv3, hook.bary_coords()); + + uv = uv.clamped_01(); + uint x = uv.x * (ibuf.x - 1); + uint y = uv.y * (ibuf.y - 1); + rgba_b color = pixel_buffer[y * ibuf.x + x]; + r_colors[i] = color; + } + }, + SurfaceHook::on_same_surface); +} + +void MF_GetImageColorOnSurface::call(IndexMask mask, MFParams params, MFContext context) const +{ + if (mask.size() == 0) { + return; + } + + VirtualListRef<SurfaceHook> surface_hooks = params.readonly_single_input<SurfaceHook>( + 0, "Surface Hook"); + VirtualListRef<ImageIDHandle> image_handles = params.readonly_single_input<ImageIDHandle>( + 1, "Image"); + MutableArrayRef<rgba_f> r_colors = params.uninitialized_single_output<rgba_f>(2, "Color"); + + rgba_f fallback = {0.0f, 0.0f, 0.0f, 1.0f}; + + auto *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + r_colors.fill_indices(mask.indices(), fallback); + return; + } + + group_indices_by_same_value<ImageIDHandle>( + mask.indices(), + image_handles, + [&](ImageIDHandle image_handle, IndexMask indices_with_image) { + Image *image = id_handle_lookup->lookup(image_handle); + if (image == nullptr) { + r_colors.fill_indices(indices_with_image, fallback); + return; + } + + ImageUser image_user = {0}; + image_user.ok = true; + ImBuf *ibuf = BKE_image_acquire_ibuf(image, &image_user, NULL); + + get_colors_on_surface( + indices_with_image, surface_hooks, r_colors, fallback, *id_handle_lookup, *ibuf); + + BKE_image_release_ibuf(image, ibuf, NULL); + }); +} + +MF_SampleObjectSurface::MF_SampleObjectSurface(bool use_vertex_weights) + : m_use_vertex_weights(use_vertex_weights) +{ + MFSignatureBuilder signature = this->get_builder("Sample Object Surface"); + signature.use_global_context<IDHandleLookup>(); + signature.single_input<ObjectIDHandle>("Object"); + signature.single_input<int>("Amount"); + signature.single_input<int>("Seed"); + if (use_vertex_weights) { + signature.single_input<std::string>("Vertex Group Name"); + } + signature.vector_output<SurfaceHook>("Surface Hooks"); +} + +static BLI_NOINLINE void compute_triangle_areas(Mesh *mesh, + ArrayRef<MLoopTri> triangles, + MutableArrayRef<float> r_areas) +{ + BLI::assert_same_size(triangles, r_areas); + + for (uint i : triangles.index_range()) { + const MLoopTri &triangle = triangles[i]; + + float3 v1 = mesh->mvert[mesh->mloop[triangle.tri[0]].v].co; + float3 v2 = mesh->mvert[mesh->mloop[triangle.tri[1]].v].co; + float3 v3 = mesh->mvert[mesh->mloop[triangle.tri[2]].v].co; + + float area = area_tri_v3(v1, v2, v3); + r_areas[i] = area; + } +} + +static float3 random_uniform_bary_coords(RNG *rng) +{ + float rand1 = BLI_rng_get_float(rng); + float rand2 = BLI_rng_get_float(rng); + + if (rand1 + rand2 > 1.0f) { + rand1 = 1.0f - rand1; + rand2 = 1.0f - rand2; + } + + return float3(rand1, rand2, 1.0f - rand1 - rand2); +} + +static BLI_NOINLINE void compute_random_uniform_bary_coords( + RNG *rng, MutableArrayRef<float3> r_sampled_bary_coords) +{ + for (float3 &bary_coords : r_sampled_bary_coords) { + bary_coords = random_uniform_bary_coords(rng); + } +} + +static BLI_NOINLINE bool get_vertex_weights(Object *object, + StringRefNull group_name, + MutableArrayRef<float> r_vertex_weights) +{ + Mesh *mesh = (Mesh *)object->data; + BLI_assert(r_vertex_weights.size() == mesh->totvert); + + MDeformVert *vertices = mesh->dvert; + int group_index = BKE_object_defgroup_name_index(object, group_name.data()); + if (group_index == -1 || vertices == nullptr) { + return false; + } + + for (uint i : r_vertex_weights.index_range()) { + r_vertex_weights[i] = BKE_defvert_find_weight(vertices + i, group_index); + } + return true; +} + +static BLI_NOINLINE void vertex_weights_to_triangle_weights( + Mesh *mesh, + ArrayRef<MLoopTri> triangles, + ArrayRef<float> vertex_weights, + MutableArrayRef<float> r_triangle_weights) +{ + BLI::assert_same_size(r_triangle_weights, triangles); + BLI_assert(mesh->totvert == vertex_weights.size()); + + for (uint triangle_index : triangles.index_range()) { + const MLoopTri &looptri = triangles[triangle_index]; + float triangle_weight = 0.0f; + for (uint i = 0; i < 3; i++) { + uint vertex_index = mesh->mloop[looptri.tri[i]].v; + float weight = vertex_weights[vertex_index]; + triangle_weight += weight; + } + + r_triangle_weights[triangle_index] = triangle_weight / 3.0f; + } +} + +void MF_SampleObjectSurface::call(IndexMask mask, MFParams params, MFContext context) const +{ + uint param_index = 0; + VirtualListRef<ObjectIDHandle> object_handles = params.readonly_single_input<ObjectIDHandle>( + param_index++, "Object"); + VirtualListRef<int> amounts = params.readonly_single_input<int>(param_index++, "Amount"); + VirtualListRef<int> seeds = params.readonly_single_input<int>(param_index++, "Seed"); + VirtualListRef<std::string> vertex_group_names; + if (m_use_vertex_weights) { + vertex_group_names = params.readonly_single_input<std::string>(param_index++, + "Vertex Group Name"); + } + GenericVectorArray::MutableTypedRef<SurfaceHook> r_hooks_per_index = + params.vector_output<SurfaceHook>(param_index++, "Surface Hooks"); + + const IDHandleLookup *id_handle_lookup = context.try_find_global<IDHandleLookup>(); + if (id_handle_lookup == nullptr) { + return; + } + + RNG *rng = BLI_rng_new(0); + + for (uint i : mask.indices()) { + uint amount = (uint)std::max<int>(amounts[i], 0); + if (amount == 0) { + continue; + } + + ObjectIDHandle object_handle = object_handles[i]; + Object *object = id_handle_lookup->lookup(object_handle); + if (object == nullptr && object->type != OB_MESH) { + continue; + } + + Mesh *mesh = (Mesh *)object->data; + const MLoopTri *triangles_buffer = BKE_mesh_runtime_looptri_ensure(mesh); + ArrayRef<MLoopTri> triangles(triangles_buffer, BKE_mesh_runtime_looptri_len(mesh)); + if (triangles.size() == 0) { + continue; + } + + Array<float> triangle_weights(triangles.size()); + compute_triangle_areas(mesh, triangles, triangle_weights); + + if (m_use_vertex_weights) { + Array<float> vertex_weights(mesh->totvert); + if (get_vertex_weights(object, vertex_group_names[i], vertex_weights)) { + Array<float> vertex_weights_for_triangles(triangles.size()); + vertex_weights_to_triangle_weights( + mesh, triangles, vertex_weights, vertex_weights_for_triangles); + + for (uint i : triangle_weights.index_range()) { + triangle_weights[i] *= vertex_weights_for_triangles[i]; + } + } + } + + Array<float> cumulative_weights(triangle_weights.size() + 1); + float total_weight = compute_cumulative_distribution(triangle_weights, cumulative_weights); + if (total_weight <= 0.0f) { + continue; + } + + BLI_rng_srandom(rng, seeds[i] + amount * 1000); + Array<uint> triangle_indices(amount); + sample_cumulative_distribution(rng, cumulative_weights, triangle_indices); + + Array<float3> bary_coords(amount); + compute_random_uniform_bary_coords(rng, bary_coords); + + MutableArrayRef<SurfaceHook> r_hooks = r_hooks_per_index.allocate_and_default_construct( + i, amount); + for (uint i : IndexRange(amount)) { + r_hooks[i] = SurfaceHook(object_handle, triangle_indices[i], bary_coords[i]); + } + } + + BLI_rng_free(rng); +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/surface_hook.h b/source/blender/functions/intern/multi_functions/surface_hook.h new file mode 100644 index 00000000000..e7fe6a1f5e8 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/surface_hook.h @@ -0,0 +1,46 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +class MF_ClosestSurfaceHookOnObject final : public MultiFunction { + public: + MF_ClosestSurfaceHookOnObject(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_GetPositionOnSurface final : public MultiFunction { + public: + MF_GetPositionOnSurface(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_GetNormalOnSurface final : public MultiFunction { + public: + MF_GetNormalOnSurface(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_GetWeightOnSurface final : public MultiFunction { + public: + MF_GetWeightOnSurface(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_GetImageColorOnSurface final : public MultiFunction { + public: + MF_GetImageColorOnSurface(); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +class MF_SampleObjectSurface final : public MultiFunction { + private: + bool m_use_vertex_weights; + + public: + MF_SampleObjectSurface(bool use_vertex_weights); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/util.h b/source/blender/functions/intern/multi_functions/util.h new file mode 100644 index 00000000000..4e2ba6ee751 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/util.h @@ -0,0 +1,48 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +using BLI::ScopedVector; + +template<typename T, typename FuncT, typename EqualFuncT = std::equal_to<T>> +void group_indices_by_same_value(IndexMask indices, + VirtualListRef<T> values, + const FuncT &func, + EqualFuncT equal = std::equal_to<T>()) +{ + if (indices.size() == 0) { + return; + } + if (values.is_single_element()) { + const T &value = values[indices[0]]; + func(value, indices); + return; + } + + ScopedVector<T> seen_values; + ScopedVector<uint> indices_with_value; + + for (uint i : indices.index_range()) { + uint index = indices[i]; + + const T &value = values[index]; + if (seen_values.as_ref().any([&](const T &seen_value) { return equal(value, seen_value); })) { + continue; + } + seen_values.append(value); + + indices_with_value.clear(); + for (uint j : indices.indices().drop_front(i)) { + if (equal(values[j], value)) { + indices_with_value.append(j); + } + } + + IndexMask mask_with_same_value = indices_with_value.as_ref(); + func(value, mask_with_same_value); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/vectorize.cc b/source/blender/functions/intern/multi_functions/vectorize.cc new file mode 100644 index 00000000000..773301597a6 --- /dev/null +++ b/source/blender/functions/intern/multi_functions/vectorize.cc @@ -0,0 +1,128 @@ +#include "vectorize.h" + +#include "BLI_array_cxx.h" +#include "BLI_rand_cxx.h" + +namespace FN { + +using BLI::Array; + +MF_SimpleVectorize::MF_SimpleVectorize(const MultiFunction &function, + ArrayRef<bool> input_is_vectorized) + : m_function(function), m_input_is_vectorized(input_is_vectorized) +{ + BLI_assert(input_is_vectorized.contains(true)); + + MFSignatureBuilder signature = this->get_builder(function.name() + " (Vectorized)"); + signature.copy_used_contexts(function); + + bool found_output_param = false; + UNUSED_VARS_NDEBUG(found_output_param); + + for (uint param_index : function.param_indices()) { + MFParamType param_type = function.param_type(param_index); + MFDataType data_type = param_type.data_type(); + StringRef param_name = function.param_name(param_index); + + switch (param_type.type()) { + case MFParamType::VectorInput: + case MFParamType::VectorOutput: + case MFParamType::MutableVector: + case MFParamType::MutableSingle: { + BLI_assert(false); + break; + } + case MFParamType::SingleInput: { + BLI_assert(!found_output_param); + if (input_is_vectorized[param_index]) { + signature.vector_input(param_name + " (List)", data_type.single__cpp_type()); + m_vectorized_inputs.append(param_index); + } + else { + signature.single_input(param_name, data_type.single__cpp_type()); + } + break; + } + case MFParamType::SingleOutput: { + signature.vector_output(param_name + " (List)", data_type.single__cpp_type()); + m_output_indices.append(param_index); + found_output_param = true; + break; + } + } + } +} + +static void get_vectorization_lengths(IndexMask mask, + MFParams params, + ArrayRef<uint> vectorized_param_indices, + MutableArrayRef<int> r_lengths) +{ + r_lengths.fill_indices(mask.indices(), -1); + + for (uint param_index : vectorized_param_indices) { + GenericVirtualListListRef values = params.readonly_vector_input(param_index); + for (uint i : mask.indices()) { + if (r_lengths[i] != 0) { + uint sublist_size = values.sublist_size(i); + r_lengths[i] = std::max<int>(r_lengths[i], sublist_size); + } + } + } +} + +void MF_SimpleVectorize::call(IndexMask mask, MFParams params, MFContext context) const +{ + if (mask.size() == 0) { + return; + } + + Array<int> vectorization_lengths(mask.min_array_size()); + get_vectorization_lengths(mask, params, m_vectorized_inputs, vectorization_lengths); + + MFContextBuilder sub_context_builder; + sub_context_builder.add_global_contexts(context); + + for (uint index : mask.indices()) { + uint length = vectorization_lengths[index]; + MFParamsBuilder params_builder(m_function, length); + + for (uint param_index : m_function.param_indices()) { + MFParamType param_type = m_function.param_type(param_index); + switch (param_type.type()) { + case MFParamType::VectorInput: + case MFParamType::VectorOutput: + case MFParamType::MutableVector: + case MFParamType::MutableSingle: { + BLI_assert(false); + break; + } + case MFParamType::SingleInput: { + if (m_input_is_vectorized[param_index]) { + GenericVirtualListListRef input_list_list = params.readonly_vector_input(param_index); + GenericVirtualListRef repeated_input = input_list_list.repeated_sublist(index, length); + params_builder.add_readonly_single_input(repeated_input); + } + else { + GenericVirtualListRef input_list = params.readonly_single_input(param_index); + GenericVirtualListRef repeated_input = input_list.repeated_element(index, length); + params_builder.add_readonly_single_input(repeated_input); + } + break; + } + case MFParamType::SingleOutput: { + GenericVectorArray &output_array_list = params.vector_output(param_index); + GenericMutableArrayRef output_array = output_array_list.allocate_single(index, length); + params_builder.add_single_output(output_array); + break; + } + } + } + + /* TODO: Pass modified per element contexts. */ + ArrayRef<uint> sub_mask_indices = IndexRange(length).as_array_ref(); + m_function.call(sub_mask_indices, params_builder, sub_context_builder); + } +} + +} // namespace FN diff --git a/source/blender/functions/intern/multi_functions/vectorize.h b/source/blender/functions/intern/multi_functions/vectorize.h new file mode 100644 index 00000000000..999915fb63a --- /dev/null +++ b/source/blender/functions/intern/multi_functions/vectorize.h @@ -0,0 +1,19 @@ +#pragma once + +#include "FN_multi_function.h" + +namespace FN { + +class MF_SimpleVectorize final : public MultiFunction { + private: + const MultiFunction &m_function; + Vector<bool> m_input_is_vectorized; + Vector<uint> m_vectorized_inputs; + Vector<uint> m_output_indices; + + public: + MF_SimpleVectorize(const MultiFunction &function, ArrayRef<bool> input_is_vectorized); + void call(IndexMask mask, MFParams params, MFContext context) const override; +}; + +} // namespace FN diff --git a/source/blender/functions/intern/node_tree.cc b/source/blender/functions/intern/node_tree.cc new file mode 100644 index 00000000000..f4f2528f2b7 --- /dev/null +++ b/source/blender/functions/intern/node_tree.cc @@ -0,0 +1,480 @@ +#include "FN_node_tree.h" + +#include "BLI_dot_export.h" +#include "BLI_string.h" + +extern "C" { +void WM_clipboard_text_set(const char *buf, bool selection); +} + +namespace FN { + +static const VirtualNodeTree &get_vtree(BTreeVTreeMap &vtrees, bNodeTree *btree) +{ + return *vtrees.lookup_or_add(btree, + [btree]() { return BLI::make_unique<VirtualNodeTree>(btree); }); +} + +static bool cmp_group_interface_nodes(const VNode *a, const VNode *b) +{ + int a_index = RNA_int_get(a->rna(), "sort_index"); + int b_index = RNA_int_get(b->rna(), "sort_index"); + if (a_index < b_index) { + return true; + } + if (a_index > b_index) { + return false; + } + + /* TODO: Match sorting with Python. */ + return BLI_strcasecmp(a->name().data(), b->name().data()) == -1; +} + +static Vector<const VOutputSocket *> get_group_inputs(const VirtualNodeTree &vtree) +{ + Vector<const VNode *> input_vnodes = vtree.nodes_with_idname("fn_GroupInputNode"); + std::sort(input_vnodes.begin(), input_vnodes.end(), cmp_group_interface_nodes); + + Vector<const VOutputSocket *> input_vsockets; + for (const VNode *vnode : input_vnodes) { + input_vsockets.append(&vnode->output(0)); + } + + return input_vsockets; +} + +static Vector<const VInputSocket *> get_group_outputs(const VirtualNodeTree &vtree) +{ + Vector<const VNode *> output_vnodes = vtree.nodes_with_idname("fn_GroupOutputNode"); + std::sort(output_vnodes.begin(), output_vnodes.end(), cmp_group_interface_nodes); + + Vector<const VInputSocket *> output_vsockets; + for (const VNode *vnode : output_vnodes) { + output_vsockets.append(&vnode->input(0)); + } + + return output_vsockets; +} + +static bool is_input_interface_vnode(const VNode &vnode) +{ + return vnode.idname() == "fn_GroupInputNode"; +} + +static bool is_output_interface_vnode(const VNode &vnode) +{ + return vnode.idname() == "fn_GroupOutputNode"; +} + +static bool is_interface_node(const VNode &vnode) +{ + return is_input_interface_vnode(vnode) || is_output_interface_vnode(vnode); +} + +static bool is_group_node(const VNode &vnode) +{ + return vnode.idname() == "fn_GroupNode"; +} + +FunctionTree::~FunctionTree() +{ + for (FNode *fnode : m_node_by_id) { + fnode->~FNode(); + } + for (FGroupInput *xgroup_input : m_group_inputs) { + xgroup_input->~FGroupInput(); + } + for (FParentNode *xparent_node : m_parent_nodes) { + xparent_node->~FParentNode(); + } + for (FInputSocket *fsocket : m_input_sockets) { + fsocket->~FInputSocket(); + } + for (FOutputSocket *fsocket : m_output_sockets) { + fsocket->~FOutputSocket(); + } +} + +void FNode::destruct_with_sockets() +{ + for (FInputSocket *socket : m_inputs) { + socket->~FInputSocket(); + } + for (FOutputSocket *socket : m_outputs) { + socket->~FOutputSocket(); + } + this->~FNode(); +} + +BLI_NOINLINE FunctionTree::FunctionTree(bNodeTree *btree, BTreeVTreeMap &vtrees) : m_btree(btree) +{ + const VirtualNodeTree &main_vtree = get_vtree(vtrees, btree); + + Vector<FNode *> all_nodes; + Vector<FGroupInput *> all_group_inputs; + Vector<FParentNode *> all_parent_nodes; + + this->insert_linked_nodes_for_vtree_in_id_order(main_vtree, all_nodes, nullptr); + this->expand_groups(all_nodes, all_group_inputs, all_parent_nodes, vtrees); + this->remove_expanded_groups_and_interfaces(all_nodes); + this->store_tree_in_this_and_init_ids( + std::move(all_nodes), std::move(all_group_inputs), std::move(all_parent_nodes)); +} + +BLI_NOINLINE void FunctionTree::expand_groups(Vector<FNode *> &all_nodes, + Vector<FGroupInput *> &all_group_inputs, + Vector<FParentNode *> &all_parent_nodes, + BTreeVTreeMap &vtrees) +{ + for (uint i = 0; i < all_nodes.size(); i++) { + FNode ¤t_node = *all_nodes[i]; + if (is_group_node(*current_node.m_vnode)) { + this->expand_group_node(current_node, all_nodes, all_group_inputs, all_parent_nodes, vtrees); + } + } +} + +BLI_NOINLINE void FunctionTree::expand_group_node(FNode &group_node, + Vector<FNode *> &all_nodes, + Vector<FGroupInput *> &all_group_inputs, + Vector<FParentNode *> &all_parent_nodes, + BTreeVTreeMap &vtrees) +{ + BLI_assert(is_group_node(*group_node.m_vnode)); + const VNode &group_vnode = *group_node.m_vnode; + bNodeTree *btree = (bNodeTree *)RNA_pointer_get(group_vnode.rna(), "node_group").data; + if (btree == nullptr) { + return; + } + + const VirtualNodeTree &vtree = get_vtree(vtrees, btree); + + FParentNode &sub_parent = *m_allocator.construct<FParentNode>(); + sub_parent.m_id = all_parent_nodes.append_and_get_index(&sub_parent); + sub_parent.m_parent = group_node.m_parent; + sub_parent.m_vnode = &group_vnode; + + this->insert_linked_nodes_for_vtree_in_id_order(vtree, all_nodes, &sub_parent); + ArrayRef<FNode *> new_fnodes_by_id = all_nodes.as_ref().take_back(vtree.nodes().size()); + + this->expand_group__group_inputs_for_unlinked_inputs(group_node, all_group_inputs); + this->expand_group__relink_inputs(vtree, new_fnodes_by_id, group_node); + this->expand_group__relink_outputs(vtree, new_fnodes_by_id, group_node); +} + +BLI_NOINLINE void FunctionTree::expand_group__group_inputs_for_unlinked_inputs( + FNode &group_node, Vector<FGroupInput *> &all_group_inputs) +{ + for (FInputSocket *input_socket : group_node.m_inputs) { + if (!input_socket->is_linked()) { + FGroupInput &group_input = *m_allocator.construct<FGroupInput>(); + group_input.m_id = all_group_inputs.append_and_get_index(&group_input); + group_input.m_vsocket = &input_socket->m_vsocket->as_input(); + group_input.m_parent = group_node.m_parent; + + group_input.m_linked_sockets.append(input_socket, m_allocator); + input_socket->m_linked_group_inputs.append(&group_input, m_allocator); + } + } +} + +BLI_NOINLINE void FunctionTree::expand_group__relink_inputs(const VirtualNodeTree &vtree, + ArrayRef<FNode *> new_fnodes_by_id, + FNode &group_node) +{ + Vector<const VOutputSocket *> group_inputs = get_group_inputs(vtree); + + for (uint input_index : group_inputs.index_range()) { + const VOutputSocket *inside_interface_vsocket = group_inputs[input_index]; + const VNode &inside_interface_vnode = inside_interface_vsocket->node(); + FNode *inside_interface_fnode = new_fnodes_by_id[inside_interface_vnode.id()]; + + FOutputSocket &inside_interface = + *inside_interface_fnode->m_outputs[inside_interface_vsocket->index()]; + FInputSocket &outside_interface = *group_node.m_inputs[input_index]; + + for (FOutputSocket *outside_connected : outside_interface.m_linked_sockets) { + outside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&outside_interface); + } + + for (FGroupInput *outside_connected : outside_interface.m_linked_group_inputs) { + outside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&outside_interface); + } + + for (FInputSocket *inside_connected : inside_interface.m_linked_sockets) { + inside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&inside_interface); + + for (FOutputSocket *outside_connected : outside_interface.m_linked_sockets) { + inside_connected->m_linked_sockets.append(outside_connected, m_allocator); + outside_connected->m_linked_sockets.append(inside_connected, m_allocator); + } + + for (FGroupInput *outside_connected : outside_interface.m_linked_group_inputs) { + inside_connected->m_linked_group_inputs.append(outside_connected, m_allocator); + outside_connected->m_linked_sockets.append(inside_connected, m_allocator); + } + } + + inside_interface.m_linked_sockets.clear(); + outside_interface.m_linked_sockets.clear(); + outside_interface.m_linked_group_inputs.clear(); + } +} + +BLI_NOINLINE void FunctionTree::expand_group__relink_outputs(const VirtualNodeTree &vtree, + ArrayRef<FNode *> new_fnodes_by_id, + FNode &group_node) +{ + Vector<const VInputSocket *> group_outputs = get_group_outputs(vtree); + + for (uint output_index : group_outputs.index_range()) { + const VInputSocket *inside_interface_vsocket = group_outputs[output_index]; + const VNode &inside_interface_vnode = inside_interface_vsocket->node(); + FNode *inside_interface_fnode = new_fnodes_by_id[inside_interface_vnode.id()]; + + FInputSocket &inside_interface = + *inside_interface_fnode->m_inputs[inside_interface_vsocket->index()]; + FOutputSocket &outside_interface = *group_node.m_outputs[output_index]; + + for (FOutputSocket *inside_connected : inside_interface.m_linked_sockets) { + inside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&inside_interface); + + for (FInputSocket *outside_connected : outside_interface.m_linked_sockets) { + inside_connected->m_linked_sockets.append(outside_connected, m_allocator); + outside_connected->m_linked_sockets.append(inside_connected, m_allocator); + } + } + + for (FGroupInput *inside_connected : inside_interface.m_linked_group_inputs) { + inside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&inside_interface); + + for (FInputSocket *outside_connected : outside_interface.m_linked_sockets) { + inside_connected->m_linked_sockets.append(outside_connected, m_allocator); + outside_connected->m_linked_group_inputs.append(inside_connected, m_allocator); + } + } + + for (FInputSocket *outside_connected : outside_interface.m_linked_sockets) { + outside_connected->m_linked_sockets.remove_first_occurrence_and_reorder(&outside_interface); + } + + outside_interface.m_linked_sockets.clear(); + inside_interface.m_linked_group_inputs.clear(); + } +} + +BLI_NOINLINE void FunctionTree::insert_linked_nodes_for_vtree_in_id_order( + const VirtualNodeTree &vtree, Vector<FNode *> &all_nodes, FParentNode *parent) +{ + BLI::Array<FSocket *> sockets_map(vtree.socket_count()); + + /* Insert nodes of group. */ + for (const VNode *vnode : vtree.nodes()) { + FNode &node = this->create_node(*vnode, parent, sockets_map); + all_nodes.append(&node); + } + + /* Insert links of group. */ + for (const VNode *vnode : vtree.nodes()) { + for (const VInputSocket *to_vsocket : vnode->inputs()) { + FInputSocket *to_socket = (FInputSocket *)sockets_map[to_vsocket->id()]; + for (const VOutputSocket *from_vsocket : to_vsocket->linked_sockets()) { + FOutputSocket *from_socket = (FOutputSocket *)sockets_map[from_vsocket->id()]; + to_socket->m_linked_sockets.append(from_socket, m_allocator); + from_socket->m_linked_sockets.append(to_socket, m_allocator); + } + } + } +} + +BLI_NOINLINE FNode &FunctionTree::create_node(const VNode &vnode, + FParentNode *parent, + MutableArrayRef<FSocket *> sockets_map) +{ + FNode &new_node = *m_allocator.construct<FNode>(); + new_node.m_vnode = &vnode; + new_node.m_parent = parent; + new_node.m_id = UINT32_MAX; + + for (const VInputSocket *vsocket : vnode.inputs()) { + FInputSocket &new_socket = *m_allocator.construct<FInputSocket>(); + new_socket.m_vsocket = vsocket; + new_socket.m_node = &new_node; + new_socket.m_id = UINT32_MAX; + new_socket.m_is_input = true; + + new_node.m_inputs.append_and_get_index(&new_socket, m_allocator); + sockets_map[vsocket->id()] = &new_socket; + } + + for (const VOutputSocket *vsocket : vnode.outputs()) { + FOutputSocket &new_socket = *m_allocator.construct<FOutputSocket>(); + new_socket.m_vsocket = vsocket; + new_socket.m_node = &new_node; + new_socket.m_id = UINT32_MAX; + new_socket.m_is_input = false; + + new_node.m_outputs.append_and_get_index(&new_socket, m_allocator); + sockets_map[vsocket->id()] = &new_socket; + } + + return new_node; +} + +BLI_NOINLINE void FunctionTree::remove_expanded_groups_and_interfaces(Vector<FNode *> &all_nodes) +{ + for (int i = 0; i < all_nodes.size(); i++) { + FNode *current_node = all_nodes[i]; + if (is_group_node(current_node->vnode()) || + (is_interface_node(current_node->vnode()) && current_node->parent() != nullptr)) { + all_nodes.remove_and_reorder(i); + current_node->destruct_with_sockets(); + i--; + } + } +} + +BLI_NOINLINE void FunctionTree::store_tree_in_this_and_init_ids( + Vector<FNode *> &&all_nodes, + Vector<FGroupInput *> &&all_group_inputs, + Vector<FParentNode *> &&all_parent_nodes) +{ + m_node_by_id = std::move(all_nodes); + m_group_inputs = std::move(all_group_inputs); + m_parent_nodes = std::move(all_parent_nodes); + + for (uint node_index : m_node_by_id.index_range()) { + FNode *fnode = m_node_by_id[node_index]; + fnode->m_id = node_index; + + m_nodes_by_idname.add(fnode->idname(), fnode); + + for (FInputSocket *fsocket : fnode->m_inputs) { + fsocket->m_id = m_sockets_by_id.append_and_get_index(fsocket); + m_input_sockets.append(fsocket); + } + for (FOutputSocket *fsocket : fnode->m_outputs) { + fsocket->m_id = m_sockets_by_id.append_and_get_index(fsocket); + m_output_sockets.append(fsocket); + } + } +} + +static BLI::DotExport::Cluster *get_cluster_for_parent( + BLI::DotExport::DirectedGraph &graph, + Map<const FParentNode *, BLI::DotExport::Cluster *> &clusters, + const FParentNode *parent) +{ + if (parent == nullptr) { + return nullptr; + } + else if (!clusters.contains(parent)) { + auto *parent_cluster = get_cluster_for_parent(graph, clusters, parent->parent()); + const VNode &parent_node = parent->vnode(); + bNodeTree *btree = (bNodeTree *)RNA_pointer_get(parent_node.rna(), "node_group").data; + auto &new_cluster = graph.new_cluster(parent->vnode().name() + " / " + + StringRef(btree->id.name + 2)); + new_cluster.set_parent_cluster(parent_cluster); + clusters.add_new(parent, &new_cluster); + return &new_cluster; + } + else { + return clusters.lookup(parent); + } +} + +std::string FunctionTree::to_dot() const +{ + BLI::DotExport::DirectedGraph digraph; + digraph.set_rankdir(BLI::DotExport::Attr_rankdir::LeftToRight); + + Map<const FNode *, BLI::DotExport::NodeWithSocketsRef> dot_nodes; + Map<const FGroupInput *, BLI::DotExport::NodeWithSocketsRef> dot_group_inputs; + Map<const FParentNode *, BLI::DotExport::Cluster *> dot_clusters; + + for (const FNode *fnode : m_node_by_id) { + auto &dot_node = digraph.new_node(""); + dot_node.set_attribute("bgcolor", "white"); + dot_node.set_attribute("style", "filled"); + + Vector<std::string> input_names; + for (const FInputSocket *input : fnode->inputs()) { + input_names.append(input->m_vsocket->name()); + } + Vector<std::string> output_names; + for (const FOutputSocket *output : fnode->outputs()) { + output_names.append(output->m_vsocket->name()); + } + + dot_nodes.add_new(fnode, + BLI::DotExport::NodeWithSocketsRef( + dot_node, fnode->vnode().name(), input_names, output_names)); + + BLI::DotExport::Cluster *cluster = get_cluster_for_parent( + digraph, dot_clusters, fnode->parent()); + dot_node.set_parent_cluster(cluster); + + for (const FInputSocket *input : fnode->inputs()) { + for (const FGroupInput *group_input : input->linked_group_inputs()) { + if (!dot_group_inputs.contains(group_input)) { + auto &dot_group_input_node = digraph.new_node(""); + dot_group_input_node.set_attribute("bgcolor", "white"); + dot_group_input_node.set_attribute("style", "filled"); + + std::string group_input_name = group_input->vsocket().name(); + + dot_group_inputs.add_new( + group_input, + BLI::DotExport::NodeWithSocketsRef( + dot_group_input_node, "Group Input", {}, {group_input_name})); + + BLI::DotExport::Cluster *cluster = get_cluster_for_parent( + digraph, dot_clusters, group_input->parent()); + dot_group_input_node.set_parent_cluster(cluster); + } + } + } + } + + for (const FNode *to_fnode : m_node_by_id) { + auto to_dot_node = dot_nodes.lookup(to_fnode); + + for (const FInputSocket *to_fsocket : to_fnode->inputs()) { + for (const FOutputSocket *from_fsocket : to_fsocket->linked_sockets()) { + const FNode *from_fnode = &from_fsocket->node(); + + auto from_dot_node = dot_nodes.lookup(from_fnode); + + digraph.new_edge(from_dot_node.output(from_fsocket->vsocket().index()), + to_dot_node.input(to_fsocket->vsocket().index())); + } + for (const FGroupInput *group_input : to_fsocket->linked_group_inputs()) { + auto from_dot_node = dot_group_inputs.lookup(group_input); + + digraph.new_edge(from_dot_node.output(0), + to_dot_node.input(to_fsocket->vsocket().index())); + } + } + } + + digraph.set_random_cluster_bgcolors(); + return digraph.to_dot_string(); +} + +void FunctionTree::to_dot__clipboard() const +{ + std::string dot = this->to_dot(); + WM_clipboard_text_set(dot.c_str(), false); +} + +const FInputSocket *FNode::input_with_name_prefix(StringRef name_prefix) const +{ + for (const FInputSocket *fsocket : m_inputs) { + if (fsocket->name().startswith(name_prefix)) { + return fsocket; + } + } + return nullptr; +} + +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/builder.cc b/source/blender/functions/intern/node_tree_multi_function_network/builder.cc new file mode 100644 index 00000000000..5b4815092a8 --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/builder.cc @@ -0,0 +1,117 @@ +#include "builder.h" + +namespace FN { +namespace MFGeneration { + +using BLI::ScopedVector; + +MFBuilderFunctionNode &CommonBuilderBase::add_function(const MultiFunction &function) +{ + return m_common.network_builder.add_function(function); +} + +MFBuilderFunctionNode &CommonBuilderBase::add_function(const MultiFunction &function, + const FNode &fnode) +{ + MFBuilderFunctionNode &node = m_common.network_builder.add_function(function); + m_common.socket_map.add(fnode, node, m_common.fsocket_data_types); + return node; +} + +MFBuilderDummyNode &CommonBuilderBase::add_dummy(const FNode &fnode) +{ + ScopedVector<const FInputSocket *> data_inputs; + ScopedVector<MFDataType> input_types; + ScopedVector<StringRef> input_names; + + for (const FInputSocket *fsocket : fnode.inputs()) { + Optional<MFDataType> data_type = m_common.fsocket_data_types.try_lookup_data_type(*fsocket); + if (data_type.has_value()) { + data_inputs.append(fsocket); + input_types.append(data_type.value()); + input_names.append(fsocket->name()); + } + } + + ScopedVector<const FOutputSocket *> data_outputs; + ScopedVector<MFDataType> output_types; + ScopedVector<StringRef> output_names; + for (const FOutputSocket *fsocket : fnode.outputs()) { + Optional<MFDataType> data_type = m_common.fsocket_data_types.try_lookup_data_type(*fsocket); + if (data_type.has_value()) { + data_outputs.append(fsocket); + output_types.append(data_type.value()); + output_names.append(fsocket->name()); + } + } + + MFBuilderDummyNode &node = m_common.network_builder.add_dummy( + fnode.name(), input_types, output_types, input_names, output_names); + + m_common.socket_map.add(data_inputs, node.inputs()); + m_common.socket_map.add(data_outputs, node.outputs()); + + for (uint i : data_inputs.index_range()) { + const FInputSocket *fsocket = data_inputs[i]; + MFBuilderInputSocket *socket = &node.input(i); + m_common.dummy_socket_mapping.add_new(fsocket, socket); + } + for (uint i : data_outputs.index_range()) { + const FOutputSocket *fsocket = data_outputs[i]; + MFBuilderOutputSocket *socket = &node.output(i); + m_common.dummy_socket_mapping.add_new(fsocket, socket); + } + + return node; +} + +Vector<bool> FNodeMFBuilder::get_list_base_variadic_states(StringRefNull prop_name) +{ + Vector<bool> states; + RNA_BEGIN (m_fnode.rna(), itemptr, prop_name.data()) { + int state = RNA_enum_get(&itemptr, "state"); + if (state == 0) { + /* single value case */ + states.append(false); + } + else if (state == 1) { + /* list case */ + states.append(true); + } + else { + BLI_assert(false); + } + } + RNA_END; + return states; +} + +void FNodeMFBuilder::set_matching_fn(const MultiFunction &fn) +{ + MFBuilderFunctionNode &node = this->add_function(fn); + m_common.socket_map.add(m_fnode, node, m_common.fsocket_data_types); +} + +const MultiFunction &FNodeMFBuilder::get_vectorized_function( + const MultiFunction &base_function, ArrayRef<const char *> is_vectorized_prop_names) +{ + ScopedVector<bool> input_is_vectorized; + for (const char *prop_name : is_vectorized_prop_names) { + char state[5]; + RNA_string_get(m_fnode.rna(), prop_name, state); + BLI_assert(STREQ(state, "BASE") || STREQ(state, "LIST")); + + bool is_vectorized = STREQ(state, "LIST"); + input_is_vectorized.append(is_vectorized); + } + + if (input_is_vectorized.contains(true)) { + return this->construct_fn<MF_SimpleVectorize>(base_function, input_is_vectorized); + } + else { + return base_function; + } +} + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/builder.h b/source/blender/functions/intern/node_tree_multi_function_network/builder.h new file mode 100644 index 00000000000..5472e1e84a5 --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/builder.h @@ -0,0 +1,405 @@ +#pragma once + +#include "FN_multi_functions.h" +#include "FN_node_tree_multi_function_network.h" + +#include "BLI_multi_map.h" + +#include "mappings.h" + +namespace FN { +namespace MFGeneration { + +using BKE::VSocket; +using BLI::IndexToRefMultiMap; +using BLI::MultiMap; + +class FSocketDataTypes { + private: + Array<Optional<MFDataType>> m_data_type_by_fsocket_id; + Array<Optional<MFDataType>> m_data_type_by_group_input_id; + + public: + FSocketDataTypes(const FunctionTree &function_tree) + { + auto &mappings = get_function_tree_multi_function_mappings(); + + m_data_type_by_fsocket_id = Array<Optional<MFDataType>>(function_tree.socket_count()); + for (const FSocket *fsocket : function_tree.all_sockets()) { + m_data_type_by_fsocket_id[fsocket->id()] = mappings.data_type_by_idname.try_lookup( + fsocket->idname()); + } + + m_data_type_by_group_input_id = Array<Optional<MFDataType>>( + function_tree.all_group_inputs().size()); + for (const FGroupInput *group_input : function_tree.all_group_inputs()) { + m_data_type_by_group_input_id[group_input->id()] = mappings.data_type_by_idname.try_lookup( + group_input->vsocket().idname()); + } + } + + Optional<MFDataType> try_lookup_data_type(const FSocket &fsocket) const + { + return m_data_type_by_fsocket_id[fsocket.id()]; + } + + MFDataType lookup_data_type(const FSocket &fsocket) const + { + return m_data_type_by_fsocket_id[fsocket.id()].value(); + } + + bool is_data_socket(const FSocket &fsocket) const + { + return m_data_type_by_fsocket_id[fsocket.id()].has_value(); + } + + bool is_data_group_input(const FGroupInput &group_input) const + { + return m_data_type_by_group_input_id[group_input.id()].has_value(); + } + + bool has_data_sockets(const FNode &fnode) const + { + for (const FInputSocket *fsocket : fnode.inputs()) { + if (this->is_data_socket(*fsocket)) { + return true; + } + } + for (const FOutputSocket *fsocket : fnode.outputs()) { + if (this->is_data_socket(*fsocket)) { + return true; + } + } + return false; + } +}; + +class MFSocketByFSocketMapping { + private: + IndexToRefMultiMap<MFBuilderSocket> m_sockets_by_fsocket_id; + IndexToRefMap<MFBuilderOutputSocket> m_socket_by_group_input_id; + + public: + MFSocketByFSocketMapping(const FunctionTree &function_tree) + : m_sockets_by_fsocket_id(function_tree.all_sockets().size()), + m_socket_by_group_input_id(function_tree.all_group_inputs().size()) + { + } + + const IndexToRefMultiMap<MFBuilderSocket> &sockets_by_fsocket_id() const + { + return m_sockets_by_fsocket_id; + } + + void add(const FInputSocket &fsocket, MFBuilderInputSocket &socket) + { + m_sockets_by_fsocket_id.add(fsocket.id(), socket); + } + + void add(const FOutputSocket &fsocket, MFBuilderOutputSocket &socket) + { + m_sockets_by_fsocket_id.add(fsocket.id(), socket); + } + + void add(ArrayRef<const FInputSocket *> fsockets, ArrayRef<MFBuilderInputSocket *> sockets) + { + BLI::assert_same_size(fsockets, sockets); + for (uint i : fsockets.index_range()) { + this->add(*fsockets[i], *sockets[i]); + } + } + + void add(ArrayRef<const FOutputSocket *> fsockets, ArrayRef<MFBuilderOutputSocket *> sockets) + { + BLI::assert_same_size(fsockets, sockets); + for (uint i : fsockets.index_range()) { + this->add(*fsockets[i], *sockets[i]); + } + } + + void add(const FGroupInput &group_input, MFBuilderOutputSocket &socket) + { + m_socket_by_group_input_id.add_new(group_input.id(), socket); + } + + void add(const FNode &fnode, MFBuilderNode &node, const FSocketDataTypes &fsocket_data_types) + { + uint data_inputs = 0; + for (const FInputSocket *fsocket : fnode.inputs()) { + if (fsocket_data_types.is_data_socket(*fsocket)) { + this->add(*fsocket, *node.inputs()[data_inputs]); + data_inputs++; + } + } + + uint data_outputs = 0; + for (const FOutputSocket *fsocket : fnode.outputs()) { + if (fsocket_data_types.is_data_socket(*fsocket)) { + this->add(*fsocket, *node.outputs()[data_outputs]); + data_outputs++; + } + } + } + + MFBuilderOutputSocket &lookup(const FGroupInput &group_input) + { + return m_socket_by_group_input_id.lookup(group_input.id()); + } + + MFBuilderOutputSocket &lookup(const FOutputSocket &fsocket) + { + return m_sockets_by_fsocket_id.lookup_single(fsocket.id()).as_output(); + } + + ArrayRef<MFBuilderInputSocket *> lookup(const FInputSocket &fsocket) + { + return m_sockets_by_fsocket_id.lookup(fsocket.id()).cast<MFBuilderInputSocket *>(); + } + + bool is_mapped(const FSocket &fsocket) const + { + return m_sockets_by_fsocket_id.contains(fsocket.id()); + } +}; + +struct CommonBuilderData { + ResourceCollector &resources; + const FunctionTreeMFMappings &mappings; + const FSocketDataTypes &fsocket_data_types; + MFSocketByFSocketMapping &socket_map; + MFNetworkBuilder &network_builder; + const FunctionTree &function_tree; + Map<const FSocket *, MFBuilderSocket *> &dummy_socket_mapping; +}; + +class CommonBuilderBase { + protected: + CommonBuilderData &m_common; + + public: + CommonBuilderBase(CommonBuilderData &common) : m_common(common) + { + } + + CommonBuilderData &common() + { + return m_common; + } + + const FunctionTree &function_tree() const + { + return m_common.function_tree; + } + + ResourceCollector &resources() + { + return m_common.resources; + } + + const FunctionTreeMFMappings &mappings() const + { + return m_common.mappings; + } + + MFSocketByFSocketMapping &socket_map() const + { + return m_common.socket_map; + } + + const FSocketDataTypes &fsocket_data_types() const + { + return m_common.fsocket_data_types; + } + + template<typename T, typename... Args> T &construct_fn(Args &&... args) + { + BLI_STATIC_ASSERT((std::is_base_of<MultiFunction, T>::value), ""); + void *buffer = m_common.resources.allocate(sizeof(T), alignof(T)); + T *fn = new (buffer) T(std::forward<Args>(args)...); + m_common.resources.add(BLI::destruct_ptr<T>(fn), fn->name().data()); + return *fn; + } + + const CPPType &cpp_type_by_name(StringRef name) const + { + return *m_common.mappings.cpp_type_by_type_name.lookup(name); + } + + const CPPType &cpp_type_from_property(const FNode &fnode, StringRefNull prop_name) const + { + char *type_name = RNA_string_get_alloc(fnode.rna(), prop_name.data(), nullptr, 0); + const CPPType &type = this->cpp_type_by_name(type_name); + MEM_freeN(type_name); + return type; + } + + MFDataType data_type_from_property(const FNode &fnode, StringRefNull prop_name) const + { + char *type_name = RNA_string_get_alloc(fnode.rna(), prop_name.data(), nullptr, 0); + MFDataType type = m_common.mappings.data_type_by_type_name.lookup(type_name); + MEM_freeN(type_name); + return type; + } + + void add_link(MFBuilderOutputSocket &from, MFBuilderInputSocket &to) + { + m_common.network_builder.add_link(from, to); + } + + MFBuilderFunctionNode &add_function(const MultiFunction &function); + + MFBuilderFunctionNode &add_function(const MultiFunction &function, const FNode &fnode); + + MFBuilderDummyNode &add_dummy(const FNode &fnode); +}; + +class VSocketMFBuilder : public CommonBuilderBase { + private: + const VSocket &m_vsocket; + MFBuilderOutputSocket *m_socket_to_build = nullptr; + + public: + VSocketMFBuilder(CommonBuilderData &common, const VSocket &vsocket) + : CommonBuilderBase(common), m_vsocket(vsocket) + { + } + + MFBuilderOutputSocket &built_socket() + { + BLI_assert(m_socket_to_build != nullptr); + return *m_socket_to_build; + } + + const VSocket &vsocket() const + { + return m_vsocket; + } + + PointerRNA *rna() + { + return m_vsocket.rna(); + } + + template<typename T> void set_constant_value(T value) + { + const MultiFunction &fn = this->construct_fn<MF_ConstantValue<T>>(std::move(value)); + this->set_generator_fn(fn); + } + + void set_generator_fn(const MultiFunction &fn) + { + MFBuilderFunctionNode &node = m_common.network_builder.add_function(fn); + this->set_socket(node.output(0)); + } + + void set_socket(MFBuilderOutputSocket &socket) + { + m_socket_to_build = &socket; + } +}; + +class FNodeMFBuilder : public CommonBuilderBase { + private: + const FNode &m_fnode; + + using CommonBuilderBase::cpp_type_from_property; + using CommonBuilderBase::data_type_from_property; + + public: + FNodeMFBuilder(CommonBuilderData &common, const FNode &fnode) + : CommonBuilderBase(common), m_fnode(fnode) + { + } + + const FNode &fnode() const + { + return m_fnode; + } + + PointerRNA *rna() + { + return m_fnode.rna(); + } + + const CPPType &cpp_type_from_property(StringRefNull prop_name) + { + return this->cpp_type_from_property(m_fnode, prop_name); + } + + MFDataType data_type_from_property(StringRefNull prop_name) + { + return this->data_type_from_property(m_fnode, prop_name); + } + + std::string string_from_property(StringRefNull prop_name) + { + char *str_ptr = RNA_string_get_alloc(m_fnode.rna(), prop_name.data(), nullptr, 0); + std::string str = str_ptr; + MEM_freeN(str_ptr); + return str; + } + + Vector<bool> get_list_base_variadic_states(StringRefNull prop_name); + + template<typename T, typename... Args> + void set_vectorized_constructed_matching_fn(ArrayRef<const char *> is_vectorized_prop_names, + Args &&... args) + { + const MultiFunction &base_fn = this->construct_fn<T>(std::forward<Args>(args)...); + this->set_vectorized_matching_fn(is_vectorized_prop_names, base_fn); + } + + void set_vectorized_matching_fn(ArrayRef<const char *> is_vectorized_prop_names, + const MultiFunction &base_fn) + { + const MultiFunction &fn = this->get_vectorized_function(base_fn, is_vectorized_prop_names); + this->set_matching_fn(fn); + } + + template<typename T, typename... Args> void set_constructed_matching_fn(Args &&... args) + { + const MultiFunction &fn = this->construct_fn<T>(std::forward<Args>(args)...); + this->set_matching_fn(fn); + } + + void set_matching_fn(const MultiFunction &fn); + + const MultiFunction &get_vectorized_function(const MultiFunction &base_function, + ArrayRef<const char *> is_vectorized_prop_names); +}; + +class ConversionMFBuilder : public CommonBuilderBase { + private: + MFBuilderInputSocket *m_built_input = nullptr; + MFBuilderOutputSocket *m_built_output = nullptr; + + public: + ConversionMFBuilder(CommonBuilderData &common) : CommonBuilderBase(common) + { + } + + template<typename T, typename... Args> void set_constructed_conversion_fn(Args &&... args) + { + const MultiFunction &fn = this->construct_fn<T>(std::forward<Args>(args)...); + MFBuilderFunctionNode &node = this->add_function(fn); + BLI_assert(node.inputs().size() == 1); + BLI_assert(node.outputs().size() == 1); + m_built_input = &node.input(0); + m_built_output = &node.output(0); + } + + MFBuilderInputSocket &built_input() + { + BLI_assert(m_built_input != nullptr); + return *m_built_input; + } + + MFBuilderOutputSocket &built_output() + { + BLI_assert(m_built_output != nullptr); + return *m_built_output; + } +}; + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/generate.cc b/source/blender/functions/intern/node_tree_multi_function_network/generate.cc new file mode 100644 index 00000000000..1c7258c9daa --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/generate.cc @@ -0,0 +1,321 @@ +#include "FN_multi_function_network_optimization.h" +#include "FN_multi_functions.h" +#include "FN_node_tree_multi_function_network_generation.h" + +#include "BLI_string.h" +#include "BLI_string_map.h" + +#include "builder.h" +#include "mappings.h" + +namespace FN { +namespace MFGeneration { + +BLI_NOINLINE static bool check_if_data_links_are_valid(const FunctionTree &function_tree, + const FunctionTreeMFMappings &mappings, + const FSocketDataTypes &fsocket_data_types) +{ + for (const FInputSocket *to_fsocket : function_tree.all_input_sockets()) { + ArrayRef<const FOutputSocket *> origin_sockets = to_fsocket->linked_sockets(); + ArrayRef<const FGroupInput *> origin_group_inputs = to_fsocket->linked_group_inputs(); + + if (fsocket_data_types.is_data_socket(*to_fsocket)) { + uint total_linked_amount = origin_sockets.size() + origin_group_inputs.size(); + if (total_linked_amount > 1) { + /* A data input can have at most one linked input. */ + return false; + } + else if (total_linked_amount == 0) { + continue; + } + + StringRef origin_idname = (origin_sockets.size() == 1) ? + origin_sockets[0]->idname() : + origin_group_inputs[0]->vsocket().idname(); + + MFDataType to_type = mappings.data_type_by_idname.lookup(to_fsocket->idname()); + Optional<MFDataType> from_type = mappings.data_type_by_idname.try_lookup(origin_idname); + + if (!from_type.has_value()) { + /* A data input can only be connected to data outputs. */ + return false; + } + if (to_type != *from_type) { + if (!mappings.conversion_inserters.contains({*from_type, to_type})) { + /* A data input can only be connected to data outputs of the same or implicitly + * convertible types. */ + return false; + } + } + } + else { + for (const FOutputSocket *from_fsocket : origin_sockets) { + if (fsocket_data_types.is_data_socket(*from_fsocket)) { + /* A non-data input cannot be connected to a data socket. */ + return false; + } + } + for (const FGroupInput *from_group_input : origin_group_inputs) { + if (fsocket_data_types.is_data_group_input(*from_group_input)) { + /* A non-data input cannot be connected to a data socket. */ + return false; + } + } + } + } + return true; +} + +static const FNodeInserter *try_find_node_inserter(CommonBuilderData &common, const FNode &fnode) +{ + StringRef idname = fnode.idname(); + return common.mappings.fnode_inserters.lookup_ptr(idname); +} + +static const VSocketInserter *try_find_socket_inserter(CommonBuilderData &common, + const VSocket &vsocket) +{ + StringRef idname = vsocket.idname(); + return common.mappings.fsocket_inserters.lookup_ptr(idname); +} + +static bool insert_nodes(CommonBuilderData &common) +{ + for (const FNode *fnode : common.function_tree.all_nodes()) { + const FNodeInserter *inserter = try_find_node_inserter(common, *fnode); + + if (inserter != nullptr) { + FNodeMFBuilder fnode_builder{common, *fnode}; + (*inserter)(fnode_builder); + } + else if (common.fsocket_data_types.has_data_sockets(*fnode)) { + CommonBuilderBase builder{common}; + builder.add_dummy(*fnode); + } + } + return true; +} + +static bool insert_group_inputs(CommonBuilderData &common) +{ + for (const FGroupInput *group_input : common.function_tree.all_group_inputs()) { + const VSocketInserter *inserter = try_find_socket_inserter(common, group_input->vsocket()); + + if (inserter != nullptr) { + VSocketMFBuilder socket_builder{common, group_input->vsocket()}; + (*inserter)(socket_builder); + common.socket_map.add(*group_input, socket_builder.built_socket()); + } + } + return true; +} + +static MFBuilderOutputSocket *try_find_origin_of_data_socket(CommonBuilderData &common, + const FInputSocket &to_fsocket) +{ + ArrayRef<const FOutputSocket *> origin_sockets = to_fsocket.linked_sockets(); + ArrayRef<const FGroupInput *> origin_group_inputs = to_fsocket.linked_group_inputs(); + uint total_linked_amount = origin_sockets.size() + origin_group_inputs.size(); + BLI_assert(total_linked_amount <= 1); + + if (total_linked_amount == 0) { + return nullptr; + } + + if (origin_sockets.size() == 1) { + return &common.socket_map.lookup(*origin_sockets[0]); + } + else { + return &common.socket_map.lookup(*origin_group_inputs[0]); + } +} + +static const ConversionInserter *try_find_conversion_inserter(CommonBuilderData &common, + MFDataType from_type, + MFDataType to_type) +{ + return common.mappings.conversion_inserters.lookup_ptr({from_type, to_type}); +} + +static bool insert_links(CommonBuilderData &common) +{ + for (const FInputSocket *to_fsocket : common.function_tree.all_input_sockets()) { + if (!common.fsocket_data_types.is_data_socket(*to_fsocket)) { + continue; + } + + MFBuilderOutputSocket *from_socket = try_find_origin_of_data_socket(common, *to_fsocket); + if (from_socket == nullptr) { + continue; + } + + Vector<MFBuilderInputSocket *> to_sockets = common.socket_map.lookup(*to_fsocket); + BLI_assert(to_sockets.size() >= 1); + + MFDataType from_type = from_socket->data_type(); + MFDataType to_type = to_sockets[0]->data_type(); + + if (from_type != to_type) { + const ConversionInserter *inserter = try_find_conversion_inserter( + common, from_type, to_type); + if (inserter == nullptr) { + return false; + } + + ConversionMFBuilder builder{common}; + (*inserter)(builder); + builder.add_link(*from_socket, builder.built_input()); + from_socket = &builder.built_output(); + } + + for (MFBuilderInputSocket *to_socket : to_sockets) { + common.network_builder.add_link(*from_socket, *to_socket); + } + } + + return true; +} + +static bool insert_unlinked_inputs(CommonBuilderData &common) +{ + Vector<const FInputSocket *> unlinked_data_inputs; + for (const FInputSocket *fsocket : common.function_tree.all_input_sockets()) { + if (common.fsocket_data_types.is_data_socket(*fsocket)) { + if (!fsocket->is_linked()) { + unlinked_data_inputs.append(fsocket); + } + } + } + + for (const FInputSocket *fsocket : unlinked_data_inputs) { + const VSocketInserter *inserter = common.mappings.fsocket_inserters.lookup_ptr( + fsocket->idname()); + + if (inserter == nullptr) { + return false; + } + + VSocketMFBuilder fsocket_builder{common, fsocket->vsocket()}; + (*inserter)(fsocket_builder); + for (MFBuilderInputSocket *to_socket : common.socket_map.lookup(*fsocket)) { + common.network_builder.add_link(fsocket_builder.built_socket(), *to_socket); + } + } + + return true; +} + +static std::unique_ptr<FunctionTreeMFNetwork> build( + const FunctionTree &function_tree, + MFNetworkBuilder &network_builder, + Map<const FSocket *, MFBuilderSocket *> &dummy_socket_mapping) +{ + auto network = BLI::make_unique<MFNetwork>(network_builder); + + IndexToRefMap<const MFSocket> dummy_socket_by_fsocket_id(function_tree.socket_count()); + IndexToRefMap<const FSocket> fsocket_by_dummy_socket_id(network->socket_ids().size()); + + dummy_socket_mapping.foreach_item([&](const FSocket *fsocket, MFBuilderSocket *builder_socket) { + const MFSocket *socket = nullptr; + if (builder_socket->is_input()) { + socket = &network->find_dummy_socket(builder_socket->as_input()); + } + else { + socket = &network->find_dummy_socket(builder_socket->as_output()); + } + dummy_socket_by_fsocket_id.add_new(fsocket->id(), *socket); + fsocket_by_dummy_socket_id.add_new(socket->id(), *fsocket); + }); + + DummySocketMap socket_map(function_tree, + *network, + std::move(dummy_socket_by_fsocket_id), + std::move(fsocket_by_dummy_socket_id)); + + return BLI::make_unique<FunctionTreeMFNetwork>( + function_tree, std::move(network), std::move(socket_map)); +} + +std::unique_ptr<FunctionTreeMFNetwork> generate_node_tree_multi_function_network( + const FunctionTree &function_tree, ResourceCollector &resources) +{ + const FunctionTreeMFMappings &mappings = get_function_tree_multi_function_mappings(); + FSocketDataTypes fsocket_data_types{function_tree}; + MFSocketByFSocketMapping socket_map{function_tree}; + Map<const FSocket *, MFBuilderSocket *> dummy_socket_mapping; + MFNetworkBuilder network_builder; + + BLI_assert(check_if_data_links_are_valid(function_tree, mappings, fsocket_data_types)); + + CommonBuilderData common{resources, + mappings, + fsocket_data_types, + socket_map, + network_builder, + function_tree, + dummy_socket_mapping}; + if (!insert_nodes(common)) { + BLI_assert(false); + } + if (!insert_group_inputs(common)) { + BLI_assert(false); + } + if (!insert_links(common)) { + BLI_assert(false); + } + if (!insert_unlinked_inputs(common)) { + BLI_assert(false); + } + + optimize_network__constant_folding(network_builder, resources); + // optimize_network__remove_duplicates(network_builder); + optimize_network__remove_unused_nodes(network_builder); + // network_builder.to_dot__clipboard(); + // function_tree.to_dot__clipboard(); + auto function_tree_network = build(function_tree, network_builder, dummy_socket_mapping); + return function_tree_network; +} + +static bool cmp_group_interface_nodes(const FNode *a, const FNode *b) +{ + int a_index = RNA_int_get(a->rna(), "sort_index"); + int b_index = RNA_int_get(b->rna(), "sort_index"); + if (a_index < b_index) { + return true; + } + + /* TODO: Match sorting with Python. */ + return BLI_strcasecmp(a->name().data(), b->name().data()) == -1; +} + +std::unique_ptr<MF_EvaluateNetwork> generate_node_tree_multi_function( + const FunctionTree &function_tree, ResourceCollector &resources) +{ + std::unique_ptr<FunctionTreeMFNetwork> network = generate_node_tree_multi_function_network( + function_tree, resources); + + Vector<const FNode *> input_fnodes = function_tree.nodes_with_idname("fn_GroupInputNode"); + Vector<const FNode *> output_fnodes = function_tree.nodes_with_idname("fn_GroupOutputNode"); + + std::sort(input_fnodes.begin(), input_fnodes.end(), cmp_group_interface_nodes); + std::sort(output_fnodes.begin(), output_fnodes.end(), cmp_group_interface_nodes); + + Vector<const MFOutputSocket *> function_inputs; + Vector<const MFInputSocket *> function_outputs; + + for (const FNode *fnode : input_fnodes) { + function_inputs.append(&network->lookup_dummy_socket(fnode->output(0))); + } + + for (const FNode *fnode : output_fnodes) { + function_outputs.append(&network->lookup_dummy_socket(fnode->input(0))); + } + + auto function = BLI::make_unique<MF_EvaluateNetwork>(std::move(function_inputs), + std::move(function_outputs)); + resources.add(std::move(network), "VTree Multi Function Network"); + return function; +} + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/mappings.cc b/source/blender/functions/intern/node_tree_multi_function_network/mappings.cc new file mode 100644 index 00000000000..1bce677b7f7 --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/mappings.cc @@ -0,0 +1,28 @@ +#include "mappings.h" + +#include "FN_multi_functions.h" + +namespace FN { +namespace MFGeneration { + +static FunctionTreeMFMappings *mappings; + +void init_function_tree_mf_mappings() +{ + mappings = new FunctionTreeMFMappings(); + add_function_tree_socket_mapping_info(*mappings); + add_function_tree_node_mapping_info(*mappings); +} + +void free_function_tree_mf_mappings() +{ + delete mappings; +} + +const FunctionTreeMFMappings &get_function_tree_multi_function_mappings() +{ + return *mappings; +} + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/mappings.h b/source/blender/functions/intern/node_tree_multi_function_network/mappings.h new file mode 100644 index 00000000000..7941f706def --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/mappings.h @@ -0,0 +1,42 @@ +#pragma once + +#include "BLI_map.h" +#include "BLI_resource_collector.h" +#include "BLI_string_map.h" + +#include "FN_node_tree_multi_function_network.h" + +namespace FN { +namespace MFGeneration { + +using BLI::Map; +using BLI::ResourceCollector; +using BLI::StringMap; + +class FNodeMFBuilder; +class VSocketMFBuilder; +class ConversionMFBuilder; + +using FNodeInserter = std::function<void(FNodeMFBuilder &builder)>; +using VSocketInserter = std::function<void(VSocketMFBuilder &builder)>; +using ConversionInserter = std::function<void(ConversionMFBuilder &builder)>; + +struct FunctionTreeMFMappings { + StringMap<MFDataType> data_type_by_idname; + StringMap<const CPPType *> cpp_type_by_type_name; + StringMap<MFDataType> data_type_by_type_name; + Map<const CPPType *, std::string> type_name_from_cpp_type; + StringMap<FNodeInserter> fnode_inserters; + StringMap<VSocketInserter> fsocket_inserters; + Map<std::pair<MFDataType, MFDataType>, ConversionInserter> conversion_inserters; +}; + +void add_function_tree_socket_mapping_info(FunctionTreeMFMappings &mappings); +void add_function_tree_node_mapping_info(FunctionTreeMFMappings &mappings); + +void init_function_tree_mf_mappings(); +void free_function_tree_mf_mappings(); +const FunctionTreeMFMappings &get_function_tree_multi_function_mappings(); + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/mappings_nodes.cc b/source/blender/functions/intern/node_tree_multi_function_network/mappings_nodes.cc new file mode 100644 index 00000000000..468d513e853 --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/mappings_nodes.cc @@ -0,0 +1,606 @@ +#include "builder.h" +#include "mappings.h" + +#include "FN_multi_functions.h" +#include "FN_node_tree_multi_function_network_generation.h" + +#include "BLI_rand_cxx.h" + +#include "BKE_surface_hook.h" + +namespace FN { +namespace MFGeneration { + +using BLI::float3; +static void INSERT_combine_color(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_CombineColor>( + {"use_list__red", "use_list__green", "use_list__blue", "use_list__alpha"}); +} + +static void INSERT_separate_color(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_SeparateColor>({"use_list__color"}); +} + +static void INSERT_combine_vector(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_CombineVector>( + {"use_list__x", "use_list__y", "use_list__z"}); +} + +static void INSERT_separate_vector(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_SeparateVector>({"use_list__vector"}); +} + +static void INSERT_vector_from_value(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_VectorFromValue>({"use_list__value"}); +} + +static void INSERT_list_length(FNodeMFBuilder &builder) +{ + const CPPType &type = builder.cpp_type_from_property("active_type"); + builder.set_constructed_matching_fn<MF_ListLength>(type); +} + +static void INSERT_get_list_element(FNodeMFBuilder &builder) +{ + const CPPType &type = builder.cpp_type_from_property("active_type"); + builder.set_constructed_matching_fn<MF_GetListElement>(type); +} + +static void INSERT_get_list_elements(FNodeMFBuilder &builder) +{ + const CPPType &type = builder.cpp_type_from_property("active_type"); + builder.set_constructed_matching_fn<MF_GetListElements>(type); +} + +static void INSERT_pack_list(FNodeMFBuilder &builder) +{ + const CPPType &type = builder.cpp_type_from_property("active_type"); + Vector<bool> list_states = builder.get_list_base_variadic_states("variadic"); + builder.set_constructed_matching_fn<MF_PackList>(type, list_states); +} + +static void INSERT_object_location(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_ObjectWorldLocation>(); +} + +static void INSERT_object_mesh_info(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_ObjectVertexPositions>(); +} + +static void INSERT_get_position_on_surface(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_GetPositionOnSurface>( + {"use_list__surface_hook"}); +} + +static void INSERT_get_normal_on_surface(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_GetNormalOnSurface>( + {"use_list__surface_hook"}); +} + +static void INSERT_get_weight_on_surface(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_GetWeightOnSurface>( + {"use_list__surface_hook", "use_list__vertex_group_name"}); +} + +static void INSERT_get_image_color_on_surface(FNodeMFBuilder &builder) +{ + builder.set_vectorized_constructed_matching_fn<MF_GetImageColorOnSurface>( + {"use_list__surface_hook", "use_list__image"}); +} + +static void INSERT_switch(FNodeMFBuilder &builder) +{ + MFDataType type = builder.data_type_from_property("data_type"); + switch (type.category()) { + case MFDataType::Single: { + builder.set_constructed_matching_fn<MF_SwitchSingle>(type.single__cpp_type()); + break; + } + case MFDataType::Vector: { + builder.set_constructed_matching_fn<MF_SwitchVector>(type.vector__cpp_base_type()); + break; + } + } +} + +static void INSERT_select(FNodeMFBuilder &builder) +{ + MFDataType type = builder.data_type_from_property("data_type"); + uint inputs = RNA_collection_length(builder.rna(), "input_items"); + switch (type.category()) { + case MFDataType::Single: { + builder.set_constructed_matching_fn<MF_SelectSingle>(type.single__cpp_type(), inputs); + break; + } + case MFDataType::Vector: { + builder.set_constructed_matching_fn<MF_SelectVector>(type.vector__cpp_base_type(), inputs); + break; + } + } +} + +static void INSERT_text_length(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_TextLength>(); +} + +static void INSERT_vertex_info(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_ContextVertexPosition>(); +} + +static void INSERT_float_range(FNodeMFBuilder &builder) +{ + int mode = RNA_enum_get(builder.rna(), "mode"); + switch (mode) { + case 0: { + builder.set_constructed_matching_fn<MF_FloatRange_Amount_Start_Step>(); + break; + } + case 1: { + builder.set_constructed_matching_fn<MF_FloatRange_Amount_Start_Stop>(); + break; + } + default: + BLI_assert(false); + } +} + +static void INSERT_time_info(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_ContextCurrentFrame>(); +} + +template<typename InT, typename OutT, typename FuncT> +static void build_math_fn_in1_out1(FNodeMFBuilder &builder, FuncT func) +{ + builder.set_vectorized_constructed_matching_fn<MF_Custom_In1_Out1<InT, OutT>>( + {"use_list"}, builder.fnode().name(), func); +} + +static void build_math_fn_in1_out1(FNodeMFBuilder &builder, const MultiFunction &base_fn) +{ + builder.set_vectorized_matching_fn({"use_list"}, base_fn); +} + +template<typename InT1, typename InT2, typename OutT, typename FuncT> +static void build_math_fn_in2_out1(FNodeMFBuilder &builder, FuncT element_func) +{ + builder.set_vectorized_constructed_matching_fn<MF_Custom_In2_Out1<InT1, InT2, OutT>>( + {"use_list__a", "use_list__b"}, builder.fnode().name(), element_func); +} + +static void build_math_fn_in2_out1(FNodeMFBuilder &builder, const MultiFunction &base_fn) +{ + builder.set_vectorized_matching_fn({"use_list__a", "use_list__b"}, base_fn); +} + +template<typename T, typename FuncT> +static void build_variadic_math_fn(FNodeMFBuilder &builder, FuncT element_func, T default_value) +{ + Vector<bool> list_states = builder.get_list_base_variadic_states("variadic"); + if (list_states.size() == 0) { + builder.set_constructed_matching_fn<MF_ConstantValue<T>>(default_value); + } + else { + const MultiFunction &base_fn = builder.construct_fn<MF_VariadicMath<T>>( + builder.fnode().name(), list_states.size(), element_func); + if (list_states.contains(true)) { + builder.set_constructed_matching_fn<MF_SimpleVectorize>(base_fn, list_states); + } + else { + builder.set_matching_fn(base_fn); + } + } +} + +static void INSERT_add_floats(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float a, float b) -> float { return a + b; }, 0.0f); +} + +static void INSERT_multiply_floats(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float a, float b) -> float { return a * b; }, 1.0f); +} + +static void INSERT_minimum_floats(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float a, float b) -> float { return std::min(a, b); }, 0.0f); +} + +static void INSERT_maximum_floats(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float a, float b) -> float { return std::max(a, b); }, 0.0f); +} + +static void INSERT_subtract_floats(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1(builder, *MF_GLOBAL_subtract_floats); +} + +static void INSERT_divide_floats(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1(builder, *MF_GLOBAL_safe_division_floats); +} + +static void INSERT_power_floats(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float, float, float>(builder, [](float a, float b) -> float { + return (a >= 0.0f) ? (float)std::pow(a, b) : 0.0f; + }); +} + +static void INSERT_sqrt_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float, float>( + builder, [](float a) -> float { return (a >= 0.0f) ? (float)std::sqrt(a) : 0.0f; }); +} + +static void INSERT_abs_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float, float>(builder, [](float a) -> float { return std::abs(a); }); +} + +static void INSERT_sine_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1(builder, *MF_GLOBAL_sin_float); +} + +static void INSERT_cosine_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1(builder, *MF_GLOBAL_cos_float); +} + +static void INSERT_ceil_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float, float>(builder, [](float a) -> float { return std::ceil(a); }); +} + +static void INSERT_floor_float(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float, float>(builder, [](float a) -> float { return std::floor(a); }); +} + +static void INSERT_add_vectors(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float3 a, float3 b) -> float3 { return a + b; }, float3(0, 0, 0)); +} + +static void INSERT_multiply_vectors(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](float3 a, float3 b) -> float3 { return a * b; }, float3(1, 1, 1)); +} + +static void INSERT_subtract_vectors(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float3>( + builder, [](float3 a, float3 b) -> float3 { return a - b; }); +} + +static void INSERT_divide_vectors(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float3>(builder, float3::safe_divide); +} + +static void INSERT_vector_cross_product(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float3>(builder, float3::cross_high_precision); +} + +static void INSERT_reflect_vector(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float3>( + builder, [](float3 a, float3 b) { return a.reflected(b.normalized()); }); +} + +static void INSERT_project_vector(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float3>(builder, float3::project); +} + +static void INSERT_vector_dot_product(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float>(builder, float3::dot); +} + +static void INSERT_vector_distance(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float3, float>(builder, float3::distance); +} + +static void INSERT_multiply_vector_with_float(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float3, float, float3>(builder, [](float3 a, float b) { return a * b; }); +} + +static void INSERT_normalize_vector(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float3, float3>(builder, + [](float3 a) -> float3 { return a.normalized(); }); +} + +static void INSERT_vector_length(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<float3, float>(builder, [](float3 a) -> float { return a.length(); }); +} + +static void INSERT_boolean_and(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](bool a, bool b) { return a && b; }, true); +} + +static void INSERT_boolean_or(FNodeMFBuilder &builder) +{ + build_variadic_math_fn( + builder, [](bool a, bool b) { return a || b; }, false); +} + +static void INSERT_boolean_not(FNodeMFBuilder &builder) +{ + build_math_fn_in1_out1<bool, bool>(builder, [](bool a) -> bool { return !a; }); +} + +static void INSERT_less_than_float(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float, float, bool>(builder, + [](float a, float b) -> bool { return a < b; }); +} + +static void INSERT_greater_than_float(FNodeMFBuilder &builder) +{ + build_math_fn_in2_out1<float, float, bool>(builder, + [](float a, float b) -> bool { return a > b; }); +} + +static void INSERT_perlin_noise(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_PerlinNoise>(); +} + +static void INSERT_get_particle_attribute(FNodeMFBuilder &builder) +{ + const CPPType &type = builder.cpp_type_from_property("attribute_type"); + builder.set_constructed_matching_fn<MF_ParticleAttribute>(type); +} + +static void INSERT_closest_surface_hook_on_object(FNodeMFBuilder &builder) +{ + const MultiFunction &main_fn = builder.construct_fn<MF_ClosestSurfaceHookOnObject>(); + const MultiFunction &position_fn = builder.construct_fn<MF_GetPositionOnSurface>(); + const MultiFunction &normal_fn = builder.construct_fn<MF_GetNormalOnSurface>(); + + const MultiFunction &vectorized_main_fn = builder.get_vectorized_function( + main_fn, {"use_list__object", "use_list__position"}); + + MFBuilderFunctionNode *main_node, *position_node, *normal_node; + + if (&main_fn == &vectorized_main_fn) { + main_node = &builder.add_function(main_fn); + position_node = &builder.add_function(position_fn); + normal_node = &builder.add_function(normal_fn); + } + else { + std::array<bool, 1> input_is_vectorized = {true}; + const MultiFunction &vectorized_position_fn = builder.construct_fn<MF_SimpleVectorize>( + position_fn, input_is_vectorized); + const MultiFunction &vectorized_normal_fn = builder.construct_fn<MF_SimpleVectorize>( + normal_fn, input_is_vectorized); + + main_node = &builder.add_function(vectorized_main_fn); + position_node = &builder.add_function(vectorized_position_fn); + normal_node = &builder.add_function(vectorized_normal_fn); + } + + builder.add_link(main_node->output(0), position_node->input(0)); + builder.add_link(main_node->output(0), normal_node->input(0)); + + const FNode &fnode = builder.fnode(); + builder.socket_map().add(fnode.inputs(), main_node->inputs()); + builder.socket_map().add(fnode.output(0), main_node->output(0)); + builder.socket_map().add(fnode.output(1), position_node->output(0)); + builder.socket_map().add(fnode.output(2), normal_node->output(0)); +} + +static void INSERT_clamp_float(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_Clamp>(false); +} + +static void INSERT_map_range(FNodeMFBuilder &builder) +{ + bool clamp = RNA_boolean_get(builder.rna(), "clamp"); + builder.set_vectorized_constructed_matching_fn<MF_MapRange>({"use_list__value", + "use_list__from_min", + "use_list__from_max", + "use_list__to_min", + "use_list__to_max"}, + clamp); +} + +static void INSERT_random_float(FNodeMFBuilder &builder) +{ + uint node_seed = (uint)RNA_int_get(builder.rna(), "node_seed"); + builder.set_constructed_matching_fn<MF_RandomFloat>(node_seed); +} + +static void INSERT_random_floats(FNodeMFBuilder &builder) +{ + uint node_seed = (uint)RNA_int_get(builder.rna(), "node_seed"); + builder.set_constructed_matching_fn<MF_RandomFloats>(node_seed); +} + +static void INSERT_random_vector(FNodeMFBuilder &builder) +{ + uint node_seed = (uint)RNA_int_get(builder.rna(), "node_seed"); + RandomVectorMode::Enum mode = (RandomVectorMode::Enum)RNA_enum_get(builder.rna(), "mode"); + builder.set_vectorized_constructed_matching_fn<MF_RandomVector>( + {"use_list__factor", "use_list__seed"}, node_seed, mode); +} + +static void INSERT_random_vectors(FNodeMFBuilder &builder) +{ + uint node_seed = (uint)RNA_int_get(builder.rna(), "node_seed"); + RandomVectorMode::Enum mode = (RandomVectorMode::Enum)RNA_enum_get(builder.rna(), "mode"); + builder.set_constructed_matching_fn<MF_RandomVectors>(node_seed, mode); +} + +static void INSERT_value(FNodeMFBuilder &builder) +{ + const FOutputSocket &fsocket = builder.fnode().output(0); + const VSocket &vsocket = fsocket.vsocket(); + + VSocketMFBuilder socket_builder{builder.common(), vsocket}; + auto &inserter = builder.mappings().fsocket_inserters.lookup(vsocket.idname()); + inserter(socket_builder); + MFBuilderOutputSocket &built_socket = socket_builder.built_socket(); + + builder.socket_map().add(fsocket, built_socket); +} + +static void INSERT_emitter_time_info(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_EmitterTimeInfo>(); +} + +static void INSERT_sample_object_surface(FNodeMFBuilder &builder) +{ + int value = RNA_enum_get(builder.rna(), "weight_mode"); + builder.set_constructed_matching_fn<MF_SampleObjectSurface>(value == 1); +} + +static void INSERT_find_non_close_points(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_FindNonClosePoints>(); +} + +static void INSERT_join_text_list(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_JoinTextList>(); +} + +static void INSERT_node_instance_identifier(FNodeMFBuilder &builder) +{ + const FNode &fnode = builder.fnode(); + std::string identifier = ""; + for (const FN::FParentNode *parent = fnode.parent(); parent; parent = parent->parent()) { + identifier = parent->vnode().name() + "/" + identifier; + } + identifier = "/nodeid/" + identifier + fnode.name(); + std::cout << identifier << "\n"; + builder.set_constructed_matching_fn<MF_ConstantValue<std::string>>(std::move(identifier)); +} + +static void INSERT_event_filter_end_time(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_EventFilterEndTime>(); +} + +static void INSERT_event_filter_duration(FNodeMFBuilder &builder) +{ + builder.set_constructed_matching_fn<MF_EventFilterDuration>(); +} + +void add_function_tree_node_mapping_info(FunctionTreeMFMappings &mappings) +{ + mappings.fnode_inserters.add_new("fn_CombineColorNode", INSERT_combine_color); + mappings.fnode_inserters.add_new("fn_SeparateColorNode", INSERT_separate_color); + mappings.fnode_inserters.add_new("fn_CombineVectorNode", INSERT_combine_vector); + mappings.fnode_inserters.add_new("fn_SeparateVectorNode", INSERT_separate_vector); + mappings.fnode_inserters.add_new("fn_VectorFromValueNode", INSERT_vector_from_value); + mappings.fnode_inserters.add_new("fn_SwitchNode", INSERT_switch); + mappings.fnode_inserters.add_new("fn_SelectNode", INSERT_select); + mappings.fnode_inserters.add_new("fn_ListLengthNode", INSERT_list_length); + mappings.fnode_inserters.add_new("fn_PackListNode", INSERT_pack_list); + mappings.fnode_inserters.add_new("fn_GetListElementNode", INSERT_get_list_element); + mappings.fnode_inserters.add_new("fn_GetListElementsNode", INSERT_get_list_elements); + mappings.fnode_inserters.add_new("fn_ObjectTransformsNode", INSERT_object_location); + mappings.fnode_inserters.add_new("fn_ObjectMeshNode", INSERT_object_mesh_info); + mappings.fnode_inserters.add_new("fn_GetPositionOnSurfaceNode", INSERT_get_position_on_surface); + mappings.fnode_inserters.add_new("fn_GetNormalOnSurfaceNode", INSERT_get_normal_on_surface); + mappings.fnode_inserters.add_new("fn_GetWeightOnSurfaceNode", INSERT_get_weight_on_surface); + mappings.fnode_inserters.add_new("fn_GetImageColorOnSurfaceNode", + INSERT_get_image_color_on_surface); + mappings.fnode_inserters.add_new("fn_TextLengthNode", INSERT_text_length); + mappings.fnode_inserters.add_new("fn_VertexInfoNode", INSERT_vertex_info); + mappings.fnode_inserters.add_new("fn_FloatRangeNode", INSERT_float_range); + mappings.fnode_inserters.add_new("fn_TimeInfoNode", INSERT_time_info); + mappings.fnode_inserters.add_new("fn_LessThanFloatNode", INSERT_less_than_float); + mappings.fnode_inserters.add_new("fn_GreaterThanFloatNode", INSERT_greater_than_float); + mappings.fnode_inserters.add_new("fn_PerlinNoiseNode", INSERT_perlin_noise); + mappings.fnode_inserters.add_new("fn_GetParticleAttributeNode", INSERT_get_particle_attribute); + mappings.fnode_inserters.add_new("fn_ClosestLocationOnObjectNode", + INSERT_closest_surface_hook_on_object); + mappings.fnode_inserters.add_new("fn_MapRangeNode", INSERT_map_range); + mappings.fnode_inserters.add_new("fn_FloatClampNode", INSERT_clamp_float); + mappings.fnode_inserters.add_new("fn_RandomFloatNode", INSERT_random_float); + mappings.fnode_inserters.add_new("fn_RandomFloatsNode", INSERT_random_floats); + mappings.fnode_inserters.add_new("fn_RandomVectorNode", INSERT_random_vector); + mappings.fnode_inserters.add_new("fn_RandomVectorsNode", INSERT_random_vectors); + mappings.fnode_inserters.add_new("fn_ValueNode", INSERT_value); + mappings.fnode_inserters.add_new("fn_EmitterTimeInfoNode", INSERT_emitter_time_info); + mappings.fnode_inserters.add_new("fn_SampleObjectSurfaceNode", INSERT_sample_object_surface); + mappings.fnode_inserters.add_new("fn_FindNonClosePointsNode", INSERT_find_non_close_points); + + mappings.fnode_inserters.add_new("fn_AddFloatsNode", INSERT_add_floats); + mappings.fnode_inserters.add_new("fn_MultiplyFloatsNode", INSERT_multiply_floats); + mappings.fnode_inserters.add_new("fn_MinimumFloatsNode", INSERT_minimum_floats); + mappings.fnode_inserters.add_new("fn_MaximumFloatsNode", INSERT_maximum_floats); + + mappings.fnode_inserters.add_new("fn_SubtractFloatsNode", INSERT_subtract_floats); + mappings.fnode_inserters.add_new("fn_DivideFloatsNode", INSERT_divide_floats); + mappings.fnode_inserters.add_new("fn_PowerFloatsNode", INSERT_power_floats); + + mappings.fnode_inserters.add_new("fn_SqrtFloatNode", INSERT_sqrt_float); + mappings.fnode_inserters.add_new("fn_AbsoluteFloatNode", INSERT_abs_float); + mappings.fnode_inserters.add_new("fn_SineFloatNode", INSERT_sine_float); + mappings.fnode_inserters.add_new("fn_CosineFloatNode", INSERT_cosine_float); + + mappings.fnode_inserters.add_new("fn_CeilFloatNode", INSERT_ceil_float); + mappings.fnode_inserters.add_new("fn_FloorFloatNode", INSERT_floor_float); + + mappings.fnode_inserters.add_new("fn_AddVectorsNode", INSERT_add_vectors); + mappings.fnode_inserters.add_new("fn_SubtractVectorsNode", INSERT_subtract_vectors); + mappings.fnode_inserters.add_new("fn_MultiplyVectorsNode", INSERT_multiply_vectors); + mappings.fnode_inserters.add_new("fn_DivideVectorsNode", INSERT_divide_vectors); + + mappings.fnode_inserters.add_new("fn_VectorCrossProductNode", INSERT_vector_cross_product); + mappings.fnode_inserters.add_new("fn_ReflectVectorNode", INSERT_reflect_vector); + mappings.fnode_inserters.add_new("fn_ProjectVectorNode", INSERT_project_vector); + mappings.fnode_inserters.add_new("fn_VectorDotProductNode", INSERT_vector_dot_product); + mappings.fnode_inserters.add_new("fn_VectorDistanceNode", INSERT_vector_distance); + mappings.fnode_inserters.add_new("fn_MultiplyVectorWithFloatNode", + INSERT_multiply_vector_with_float); + mappings.fnode_inserters.add_new("fn_NormalizeVectorNode", INSERT_normalize_vector); + mappings.fnode_inserters.add_new("fn_VectorLengthNode", INSERT_vector_length); + + mappings.fnode_inserters.add_new("fn_BooleanAndNode", INSERT_boolean_and); + mappings.fnode_inserters.add_new("fn_BooleanOrNode", INSERT_boolean_or); + mappings.fnode_inserters.add_new("fn_BooleanNotNode", INSERT_boolean_not); + + mappings.fnode_inserters.add_new("fn_JoinTextListNode", INSERT_join_text_list); + mappings.fnode_inserters.add_new("fn_NodeInstanceIdentifierNode", + INSERT_node_instance_identifier); + mappings.fnode_inserters.add_new("fn_EventFilterEndTimeNode", INSERT_event_filter_end_time); + mappings.fnode_inserters.add_new("fn_EventFilterDurationNode", INSERT_event_filter_duration); +} + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/functions/intern/node_tree_multi_function_network/mappings_sockets.cc b/source/blender/functions/intern/node_tree_multi_function_network/mappings_sockets.cc new file mode 100644 index 00000000000..23d4f6d305a --- /dev/null +++ b/source/blender/functions/intern/node_tree_multi_function_network/mappings_sockets.cc @@ -0,0 +1,188 @@ +#include "builder.h" +#include "mappings.h" + +#include "BLI_color.h" +#include "BLI_float3.h" + +#include "BKE_surface_hook.h" + +#include "FN_multi_functions.h" + +namespace FN { +namespace MFGeneration { + +/* Socket Inserters + **********************************************************/ + +static void INSERT_vector_socket(VSocketMFBuilder &builder) +{ + BLI::float3 value; + RNA_float_get_array(builder.rna(), "value", value); + builder.set_constant_value(value); +} + +static void INSERT_color_socket(VSocketMFBuilder &builder) +{ + BLI::rgba_f value; + RNA_float_get_array(builder.rna(), "value", value); + builder.set_constant_value(value); +} + +static void INSERT_float_socket(VSocketMFBuilder &builder) +{ + float value = RNA_float_get(builder.rna(), "value"); + builder.set_constant_value(value); +} + +static void INSERT_bool_socket(VSocketMFBuilder &builder) +{ + bool value = RNA_boolean_get(builder.rna(), "value"); + builder.set_constant_value(value); +} + +static void INSERT_int_socket(VSocketMFBuilder &builder) +{ + int value = RNA_int_get(builder.rna(), "value"); + builder.set_constant_value(value); +} + +static void INSERT_object_socket(VSocketMFBuilder &builder) +{ + Object *value = (Object *)RNA_pointer_get(builder.rna(), "value").data; + if (value == nullptr) { + builder.set_constant_value(BKE::ObjectIDHandle()); + } + else { + builder.set_constant_value(BKE::ObjectIDHandle(value)); + } +} + +static void INSERT_image_socket(VSocketMFBuilder &builder) +{ + Image *value = (Image *)RNA_pointer_get(builder.rna(), "value").data; + if (value == nullptr) { + builder.set_constant_value(BKE::ImageIDHandle()); + } + else { + builder.set_constant_value(BKE::ImageIDHandle(value)); + } +} + +static void INSERT_text_socket(VSocketMFBuilder &builder) +{ + char *value = RNA_string_get_alloc(builder.rna(), "value", nullptr, 0); + std::string text = value; + MEM_freeN(value); + builder.set_constant_value(std::move(text)); +} + +static void INSERT_surface_hook_socket(VSocketMFBuilder &builder) +{ + builder.set_constant_value(BKE::SurfaceHook()); +} + +template<typename T> static void INSERT_empty_list_socket(VSocketMFBuilder &builder) +{ + const MultiFunction &fn = builder.construct_fn<FN::MF_EmptyList<T>>(); + builder.set_generator_fn(fn); +} + +/* Implicit Conversion Inserters + *******************************************/ + +template<typename FromT, typename ToT> static void INSERT_convert(ConversionMFBuilder &builder) +{ + builder.set_constructed_conversion_fn<MF_Convert<FromT, ToT>>(); +} + +template<typename FromT, typename ToT> +static void INSERT_convert_list(ConversionMFBuilder &builder) +{ + builder.set_constructed_conversion_fn<MF_ConvertList<FromT, ToT>>(); +} + +template<typename T> static void INSERT_element_to_list(ConversionMFBuilder &builder) +{ + builder.set_constructed_conversion_fn<MF_SingleElementList<T>>(); +} + +template<typename T> +static void add_basic_type(FunctionTreeMFMappings &mappings, + StringRef base_name, + StringRef base_name_without_spaces, + VSocketInserter base_inserter) +{ + std::string base_idname = "fn_" + base_name_without_spaces + "Socket"; + std::string list_idname = "fn_" + base_name_without_spaces + "ListSocket"; + std::string list_name = base_name + " List"; + + const CPPType &cpp_type = CPP_TYPE<T>(); + MFDataType base_data_type = MFDataType::ForSingle(cpp_type); + MFDataType list_data_type = MFDataType::ForVector(cpp_type); + + mappings.cpp_type_by_type_name.add_new(base_name, &cpp_type); + mappings.data_type_by_idname.add_new(base_idname, base_data_type); + mappings.data_type_by_idname.add_new(list_idname, list_data_type); + mappings.data_type_by_type_name.add_new(base_name, base_data_type); + mappings.data_type_by_type_name.add_new(list_name, list_data_type); + mappings.fsocket_inserters.add_new(base_idname, base_inserter); + mappings.fsocket_inserters.add_new(list_idname, INSERT_empty_list_socket<T>); + mappings.conversion_inserters.add_new({base_data_type, list_data_type}, + INSERT_element_to_list<T>); + mappings.type_name_from_cpp_type.add_new(&cpp_type, base_name); +} + +template<typename T> +static void add_basic_type(FunctionTreeMFMappings &mappings, + StringRef base_name, + VSocketInserter base_inserter) +{ + add_basic_type<T>(mappings, base_name, base_name, base_inserter); +} + +template<typename FromT, typename ToT> +static void add_implicit_conversion(FunctionTreeMFMappings &mappings) +{ + StringRef from_name = mappings.type_name_from_cpp_type.lookup(&CPP_TYPE<FromT>()); + StringRef to_name = mappings.type_name_from_cpp_type.lookup(&CPP_TYPE<ToT>()); + + std::string from_base_idname = "fn_" + from_name + "Socket"; + std::string from_list_idname = "fn_" + from_name + "ListSocket"; + + std::string to_base_idname = "fn_" + to_name + "Socket"; + std::string to_list_idname = "fn_" + to_name + "ListSocket"; + + mappings.conversion_inserters.add_new( + {MFDataType::ForSingle<FromT>(), MFDataType::ForSingle<ToT>()}, INSERT_convert<FromT, ToT>); + mappings.conversion_inserters.add_new( + {MFDataType::ForVector<FromT>(), MFDataType::ForVector<ToT>()}, + INSERT_convert_list<FromT, ToT>); +} + +template<typename T1, typename T2> +static void add_bidirectional_implicit_conversion(FunctionTreeMFMappings &mappings) +{ + add_implicit_conversion<T1, T2>(mappings); + add_implicit_conversion<T2, T1>(mappings); +} + +void add_function_tree_socket_mapping_info(FunctionTreeMFMappings &mappings) +{ + add_basic_type<float>(mappings, "Float", INSERT_float_socket); + add_basic_type<BLI::float3>(mappings, "Vector", INSERT_vector_socket); + add_basic_type<int32_t>(mappings, "Integer", INSERT_int_socket); + add_basic_type<BKE::ObjectIDHandle>(mappings, "Object", INSERT_object_socket); + add_basic_type<BKE::ImageIDHandle>(mappings, "Image", INSERT_image_socket); + add_basic_type<std::string>(mappings, "Text", INSERT_text_socket); + add_basic_type<bool>(mappings, "Boolean", INSERT_bool_socket); + add_basic_type<BLI::rgba_f>(mappings, "Color", INSERT_color_socket); + add_basic_type<BKE::SurfaceHook>( + mappings, "Surface Hook", "SurfaceHook", INSERT_surface_hook_socket); + + add_bidirectional_implicit_conversion<float, int32_t>(mappings); + add_bidirectional_implicit_conversion<float, bool>(mappings); + add_bidirectional_implicit_conversion<int32_t, bool>(mappings); +} + +} // namespace MFGeneration +} // namespace FN diff --git a/source/blender/makesdna/DNA_anim_types.h b/source/blender/makesdna/DNA_anim_types.h index fbffa039ee9..82b2b96ce90 100644 --- a/source/blender/makesdna/DNA_anim_types.h +++ b/source/blender/makesdna/DNA_anim_types.h @@ -438,6 +438,8 @@ typedef enum eDriverVar_Types { DVAR_TYPE_LOC_DIFF, /** 'final' transform for object/bones */ DVAR_TYPE_TRANSFORM_CHAN, + /* evaluate function */ + DVAR_TYPE_FUNCTION, /** Maximum number of variable types. * diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 431fcb7a243..8bbc2493632 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -94,6 +94,10 @@ typedef enum ModifierType { eModifierType_WeightedNormal = 54, eModifierType_Weld = 55, eModifierType_Fluid = 56, + eModifierType_BParticles = 57, + eModifierType_BParticlesOutput = 58, + eModifierType_FunctionDeform = 59, + eModifierType_FunctionPoints = 60, NUM_MODIFIER_TYPES, } ModifierType; @@ -2116,6 +2120,68 @@ enum { #define MOD_MESHSEQ_READ_ALL \ (MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR) +typedef struct FunctionDeformModifierData { + ModifierData modifier; + float control1; + int control2; + struct bNodeTree *function_tree; +} FunctionDeformModifierData; + +typedef struct FunctionPointsModifierData { + ModifierData modifier; + float control1; + int control2; + struct bNodeTree *function_tree; +} FunctionPointsModifierData; + +typedef struct BParticlesAttributeCacheFloat { + char name[64]; + unsigned int floats_per_particle; + char _pad[4]; + float *values; +} BParticlesAttributeCacheFloat; + +typedef struct BParticlesTypeCache { + char name[64]; + unsigned int particle_amount; + + unsigned int num_attributes_float; + BParticlesAttributeCacheFloat *attributes_float; +} BParticlesTypeCache; + +typedef struct BParticlesFrameCache { + unsigned int num_particle_types; + float frame; + BParticlesTypeCache *particle_types; +} BParticlesFrameCache; + +typedef enum eBParticlesOutputType { + MOD_BPARTICLES_OUTPUT_POINTS, + MOD_BPARTICLES_OUTPUT_TETRAHEDONS, + MOD_BPARTICLES_OUTPUT_NONE, +} eBParticlesOutputType; + +typedef struct BParticlesModifierData { + ModifierData modifier; + + /* eBParticlesOutputType */ + unsigned int output_type; + + unsigned int num_cached_frames; + struct bNodeTree *node_tree; + BParticlesFrameCache *cached_frames; +} BParticlesModifierData; + +typedef struct BParticlesOutputModifierData { + ModifierData modifier; + struct Object *source_object; + char source_particle_system[64]; + + /* eBParticlesOutputType */ + unsigned int output_type; + char _pad[4]; +} BParticlesOutputModifierData; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index e8f4cae573a..5c620a9a346 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -643,6 +643,7 @@ typedef struct UserDef { /** FILE_MAXDIR length. */ char tempdir[768]; char fontdir[768]; + char nodelibdir[768]; /** FILE_MAX length. */ char renderdir[1024]; /* EXR cache path */ diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 31d1ed54fa1..bab61d9e00e 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -86,6 +86,7 @@ extern StructRNA RNA_Bone; extern StructRNA RNA_BoneGroup; extern StructRNA RNA_BoolProperty; extern StructRNA RNA_BooleanModifier; +extern StructRNA RNA_BParticlesModifier; extern StructRNA RNA_Brush; extern StructRNA RNA_BrushCapabilitiesImagePaint; extern StructRNA RNA_BrushCapabilitiesVertexPaint; diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index 46854bc6307..47057d8a19b 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -2244,7 +2244,7 @@ static void rna_def_property_funcs_header_cpp(FILE *f, StructRNA *srna, Property fprintf(f, "\n"); } -static const char *rna_parameter_type_cpp_name(PropertyRNA *prop) +static const char *rna_parameter_cpp_type_name(PropertyRNA *prop) { if (prop->type == PROP_POINTER) { /* for cpp api we need to use RNA structures names for pointers */ @@ -2257,7 +2257,7 @@ static const char *rna_parameter_type_cpp_name(PropertyRNA *prop) } } -static void rna_def_struct_function_prototype_cpp(FILE *f, +static void rna_def_struct_function_protocpp_type(FILE *f, StructRNA *UNUSED(srna), FunctionDefRNA *dfunc, const char *namespace, @@ -2271,7 +2271,7 @@ static void rna_def_struct_function_prototype_cpp(FILE *f, if (func->c_ret) { dp = rna_find_parameter_def(func->c_ret); - retval_type = rna_parameter_type_cpp_name(dp->prop); + retval_type = rna_parameter_cpp_type_name(dp->prop); } if (namespace && namespace[0]) { @@ -2328,14 +2328,14 @@ static void rna_def_struct_function_prototype_cpp(FILE *f, if (!(flag & PROP_DYNAMIC) && dp->prop->arraydimension) { fprintf(f, "%s %s[%u]", - rna_parameter_type_cpp_name(dp->prop), + rna_parameter_cpp_type_name(dp->prop), rna_safe_id(dp->prop->identifier), dp->prop->totarraylength); } else { fprintf(f, "%s%s%s%s", - rna_parameter_type_cpp_name(dp->prop), + rna_parameter_cpp_type_name(dp->prop), (dp->prop->type == PROP_POINTER && ptrstr[0] == '\0') ? "& " : " ", ptrstr, rna_safe_id(dp->prop->identifier)); @@ -2357,7 +2357,7 @@ static void rna_def_struct_function_header_cpp(FILE *f, StructRNA *srna, Functio fprintf(f, "\n\t/* %s */\n", func->description); #endif - rna_def_struct_function_prototype_cpp(f, srna, dfunc, NULL, 1); + rna_def_struct_function_protocpp_type(f, srna, dfunc, NULL, 1); } } @@ -2604,7 +2604,7 @@ static void rna_def_struct_function_impl_cpp(FILE *f, StructRNA *srna, FunctionD return; } - rna_def_struct_function_prototype_cpp(f, srna, dfunc, srna->identifier, 0); + rna_def_struct_function_protocpp_type(f, srna, dfunc, srna->identifier, 0); fprintf(f, " {\n"); diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index 33f19153e3a..340d10007bd 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -1834,6 +1834,7 @@ static void rna_def_drivervar(BlenderRNA *brna) ICON_DRIVER_DISTANCE, "Distance", "Distance between two bones or objects"}, + {DVAR_TYPE_FUNCTION, "FUNCTION", ICON_NONE, "Function", "Evaluate Function"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 5fc91622eba..1130b822f75 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -187,6 +187,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_AUTOMERGE_OFF, "Weld", "Find groups of vertices closer then dist and merges them together"}, + {eModifierType_FunctionPoints, "FUNCTION_POINTS", ICON_NONE, "Function Points", ""}, {eModifierType_Wireframe, "WIREFRAME", ICON_MOD_WIREFRAME, @@ -266,6 +267,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_MOD_WAVE, "Wave", "Adds a ripple-like motion to an object’s geometry"}, + {eModifierType_FunctionDeform, "FUNCTION_DEFORM", ICON_NONE, "Function Deform", ""}, {0, "", 0, N_("Simulate"), ""}, {eModifierType_Cloth, "CLOTH", ICON_MOD_CLOTH, "Cloth", ""}, {eModifierType_Collision, "COLLISION", ICON_MOD_PHYSICS, "Collision", ""}, @@ -289,6 +291,8 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { "Spawn particles from the shape"}, {eModifierType_Softbody, "SOFT_BODY", ICON_MOD_SOFT, "Soft Body", ""}, {eModifierType_Surface, "SURFACE", ICON_MODIFIER, "Surface", ""}, + {eModifierType_BParticles, "BPARTICLES", ICON_NONE, "BParticles", ""}, + {eModifierType_BParticlesOutput, "BPARTICLES_OUTPUT", ICON_NONE, "BParticles Output", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -716,6 +720,14 @@ static StructRNA *rna_Modifier_refine(struct PointerRNA *ptr) return &RNA_SurfaceDeformModifier; case eModifierType_WeightedNormal: return &RNA_WeightedNormalModifier; + case eModifierType_FunctionDeform: + return &RNA_FunctionDeformModifier; + case eModifierType_FunctionPoints: + return &RNA_FunctionPointsModifier; + case eModifierType_BParticles: + return &RNA_BParticlesModifier; + case eModifierType_BParticlesOutput: + return &RNA_BParticlesOutputModifier; /* Default */ case eModifierType_Fluidsim: /* deprecated */ case eModifierType_None: @@ -6530,6 +6542,114 @@ static void rna_def_modifier_weightednormal(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Modifier_update"); } +static void rna_def_modifier_function_deform(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "FunctionDeformModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "Function Deform Modifier", ""); + RNA_def_struct_sdna(srna, "FunctionDeformModifierData"); + RNA_def_struct_ui_icon(srna, ICON_NONE); + + prop = RNA_def_float(srna, "control1", 0.0, -FLT_MAX, FLT_MAX, "Control 1", "", -10, 10); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_int(srna, "control2", 0, INT_MIN, INT_MAX, "Control 2", "", -10, 10); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "function_tree", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Function Tree", "Function node tree"); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); +} + +static void rna_def_modifier_function_points(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "FunctionPointsModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "Function Points Modifier", ""); + RNA_def_struct_sdna(srna, "FunctionPointsModifierData"); + RNA_def_struct_ui_icon(srna, ICON_NONE); + + prop = RNA_def_float(srna, "control1", 0.0, -FLT_MAX, FLT_MAX, "Control 1", "", -10, 10); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_int(srna, "control2", 0, INT_MIN, INT_MAX, "Control 2", "", -10, 10); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "function_tree", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Function Tree", "Function node tree"); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); +} + +const static EnumPropertyItem bparticles_output_type_items[] = { + {MOD_BPARTICLES_OUTPUT_POINTS, + "POINTS", + 0, + "Points", + "Create a mesh containing only vertices"}, + {MOD_BPARTICLES_OUTPUT_TETRAHEDONS, + "TETRAHEDONS", + 0, + "Tetrahedons", + "Create a mesh that has a tetrahedon at every vertex position"}, + {MOD_BPARTICLES_OUTPUT_NONE, "NONE", 0, "None", "Create no output mesh"}, + {0, NULL, 0, NULL, NULL}, +}; + +static void rna_def_modifier_bparticles(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "BParticlesModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "BParticles Modifier", ""); + RNA_def_struct_sdna(srna, "BParticlesModifierData"); + RNA_def_struct_ui_icon(srna, ICON_NONE); + + prop = RNA_def_property(srna, "node_tree", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "BParticles Tree", "BParticles node tree"); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "output_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, bparticles_output_type_items); + RNA_def_property_ui_text( + prop, "Output Type", "Method for creating the output mesh from the particle data"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); +} + +static void rna_def_modifier_bparticles_output(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "BParticlesOutputModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "BParticles Output Modifier", ""); + RNA_def_struct_sdna(srna, "BParticlesOutputModifierData"); + RNA_def_struct_ui_icon(srna, ICON_NONE); + + prop = RNA_def_property(srna, "source_object", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Source Object", "Object to copy a particle system from"); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "source_particle_system", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text( + prop, "Particle System", "Name of the particle system that should be copied"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "output_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, bparticles_output_type_items); + RNA_def_property_ui_text( + prop, "Output Type", "Method for creating the output mesh from the particle data"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); +} + void RNA_def_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -6655,6 +6775,10 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_meshseqcache(brna); rna_def_modifier_surfacedeform(brna); rna_def_modifier_weightednormal(brna); + rna_def_modifier_function_deform(brna); + rna_def_modifier_function_points(brna); + rna_def_modifier_bparticles(brna); + rna_def_modifier_bparticles_output(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 31d6ff80f34..024ed879d34 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9474,6 +9474,17 @@ static void rna_def_texture_nodetree(BlenderRNA *brna) RNA_def_struct_ui_icon(srna, ICON_TEXTURE); } +static void rna_def_simulation_nodetree(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "SimulationNodeTree", "NodeTree"); + RNA_def_struct_ui_text( + srna, "Simulation Node Tree", "Node tree consisting of linked nodes used for simulations"); + RNA_def_struct_sdna(srna, "bNodeTree"); + RNA_def_struct_ui_icon(srna, 0 /* TODO */); +} + static StructRNA *define_specific_node(BlenderRNA *brna, const char *struct_name, const char *base_name, @@ -9568,6 +9579,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_composite_nodetree(brna); rna_def_shader_nodetree(brna); rna_def_texture_nodetree(brna); + rna_def_simulation_nodetree(brna); # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ { \ diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 514d7428ff8..ee3434905b0 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -5990,6 +5990,13 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) prop, "Temporary Directory", "The directory for storing temporary save files"); RNA_def_property_update(prop, 0, "rna_userdef_temp_update"); + prop = RNA_def_property(srna, "nodelib_directory", PROP_STRING, PROP_DIRPATH); + RNA_def_property_string_sdna(prop, NULL, "nodelibdir"); + RNA_def_property_ui_text( + prop, + "Nodelib Directory", + "All node groups in .blend files in this directory can be loaded easily"); + prop = RNA_def_property(srna, "render_cache_directory", PROP_STRING, PROP_DIRPATH); RNA_def_property_string_sdna(prop, NULL, "render_cachedir"); RNA_def_property_ui_text(prop, "Render Cache Path", "Where to cache raw render results"); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 48acbdc17f3..30961e4e029 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -26,6 +26,8 @@ set(INC ../blenlib ../bmesh ../depsgraph + ../functions + ../simulations ../makesdna ../makesrna ../render/extern/include @@ -42,6 +44,8 @@ set(SRC intern/MOD_array.c intern/MOD_bevel.c intern/MOD_boolean.c + intern/MOD_bparticles.c + intern/MOD_bparticles_output.c intern/MOD_build.c intern/MOD_cast.c intern/MOD_cloth.c @@ -55,6 +59,10 @@ set(SRC intern/MOD_edgesplit.c intern/MOD_explode.c intern/MOD_fluid.c + intern/MOD_functiondeform_cxx.cc + intern/MOD_functiondeform.c + intern/MOD_functionpoints_cxx.cc + intern/MOD_functionpoints.c intern/MOD_hook.c intern/MOD_laplaciandeform.c intern/MOD_laplaciansmooth.c @@ -111,6 +119,8 @@ set(SRC set(LIB bf_blenkernel bf_blenlib + bf_functions + bf_simulations ) if(WITH_ALEMBIC) diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h index 5dc4adf4393..e881b0e12bf 100644 --- a/source/blender/modifiers/MOD_modifiertypes.h +++ b/source/blender/modifiers/MOD_modifiertypes.h @@ -86,6 +86,10 @@ extern ModifierTypeInfo modifierType_CorrectiveSmooth; extern ModifierTypeInfo modifierType_MeshSequenceCache; extern ModifierTypeInfo modifierType_SurfaceDeform; extern ModifierTypeInfo modifierType_WeightedNormal; +extern ModifierTypeInfo modifierType_FunctionDeform; +extern ModifierTypeInfo modifierType_FunctionPoints; +extern ModifierTypeInfo modifierType_BParticles; +extern ModifierTypeInfo modifierType_BParticlesOutput; /* MOD_util.c */ void modifier_type_init(ModifierTypeInfo *types[]); diff --git a/source/blender/modifiers/intern/MOD_bparticles.c b/source/blender/modifiers/intern/MOD_bparticles.c new file mode 100644 index 00000000000..6ef04684593 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_bparticles.c @@ -0,0 +1,227 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2019 by the Blender Foundation. + * All rights reserved. + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file blender/modifiers/intern/MOD_bparticles.c + * \ingroup modifiers + * + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_lib_query.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "BLI_math.h" + +#include "MOD_bparticles.h" +#include "MOD_util.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +typedef struct RuntimeData { + BParticlesSimulationState simulation_state; + float last_simulated_frame; +} RuntimeData; + +static RuntimeData *get_or_create_runtime_struct(BParticlesModifierData *bpmd) +{ + if (bpmd->modifier.runtime == NULL) { + RuntimeData *runtime = MEM_callocN(sizeof(RuntimeData), __func__); + runtime->simulation_state = NULL; + runtime->last_simulated_frame = 0.0f; + bpmd->modifier.runtime = runtime; + } + + return bpmd->modifier.runtime; +} + +static RuntimeData *get_runtime_struct(BParticlesModifierData *bpmd) +{ + return bpmd->modifier.runtime; +} + +static void free_runtime_data(RuntimeData *runtime) +{ + BParticles_simulation_free(runtime->simulation_state); + MEM_freeN(runtime); +} + +static void free_modifier_runtime_data(BParticlesModifierData *bpmd) +{ + RuntimeData *runtime = (RuntimeData *)bpmd->modifier.runtime; + if (runtime != NULL) { + free_runtime_data(runtime); + bpmd->modifier.runtime = NULL; + } +} + +BParticlesSimulationState MOD_bparticles_find_simulation_state(Object *object) +{ + BLI_assert(object != NULL); + BParticlesModifierData *bpmd = (BParticlesModifierData *)modifiers_findByType( + object, eModifierType_BParticles); + if (bpmd == NULL) { + return NULL; + } + RuntimeData *runtime = get_runtime_struct(bpmd); + if (runtime == NULL) { + return NULL; + } + return runtime->simulation_state; +} + +static Mesh *applyModifier(ModifierData *md, const struct ModifierEvalContext *ctx, Mesh *mesh) +{ + BParticlesModifierData *bpmd = (BParticlesModifierData *)md; + BParticlesModifierData *bpmd_orig = (BParticlesModifierData *)modifier_get_original( + &bpmd->modifier); + + Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); + float current_frame = BKE_scene_frame_get(scene); + + RuntimeData *runtime = get_or_create_runtime_struct(bpmd); + + if (runtime->simulation_state == NULL) { + runtime->simulation_state = BParticles_new_simulation(); + } + + if (current_frame == runtime->last_simulated_frame) { + /* do nothing */ + } + else if (current_frame == runtime->last_simulated_frame + 1.0f) { + BParticles_simulate_modifier(bpmd, ctx->depsgraph, runtime->simulation_state, 1.0f / FPS); + runtime->last_simulated_frame = current_frame; + } + else { + free_modifier_runtime_data(bpmd); + runtime = get_or_create_runtime_struct(bpmd); + runtime->simulation_state = BParticles_new_simulation(); + runtime->last_simulated_frame = current_frame; + BParticles_modifier_free_cache(bpmd_orig); + + BParticles_simulate_modifier(bpmd, ctx->depsgraph, runtime->simulation_state, 0.0f); + runtime->last_simulated_frame = current_frame; + } + + if (bpmd->output_type == MOD_BPARTICLES_OUTPUT_POINTS) { + return BParticles_modifier_point_mesh_from_state(runtime->simulation_state); + } + else if (bpmd->output_type == MOD_BPARTICLES_OUTPUT_TETRAHEDONS) { + Mesh *new_mesh = BParticles_modifier_mesh_from_state(runtime->simulation_state); + BKE_mesh_copy_settings(new_mesh, mesh); + return new_mesh; + } + else { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } +} + +static void initData(ModifierData *UNUSED(md)) +{ +} + +static void freeData(ModifierData *md) +{ + BParticlesModifierData *bpmd = (BParticlesModifierData *)md; + free_modifier_runtime_data(bpmd); + BParticles_modifier_free_cache(bpmd); +} + +static void copyData(const ModifierData *md, ModifierData *target, const int flag) +{ + BParticlesModifierData *tbpmd = (BParticlesModifierData *)target; + + modifier_copyData_generic(md, target, flag); + tbpmd->num_cached_frames = 0; + tbpmd->cached_frames = NULL; +} + +static void freeRuntimeData(void *runtime_data_v) +{ + if (runtime_data_v == NULL) { + return; + } + RuntimeData *runtime = (RuntimeData *)runtime_data_v; + free_runtime_data(runtime); +} + +static bool dependsOnTime(ModifierData *UNUSED(md)) +{ + return true; +} + +static void updateDepsgraph(ModifierData *UNUSED(md), + const ModifierUpdateDepsgraphContext *UNUSED(ctx)) +{ +} + +static void foreachObjectLink(ModifierData *UNUSED(md), + Object *UNUSED(ob), + ObjectWalkFunc UNUSED(walk), + void *UNUSED(userData)) +{ +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + BParticlesModifierData *bpmd = (BParticlesModifierData *)md; + walk(userData, ob, (ID **)&bpmd->node_tree, IDWALK_CB_NOP); + + foreachObjectLink(md, ob, (ObjectWalkFunc)walk, userData); +} + +ModifierTypeInfo modifierType_BParticles = { + /* name */ "BParticles", + /* structName */ "BParticlesModifierData", + /* structSize */ sizeof(BParticlesModifierData), + /* type */ eModifierTypeType_Constructive, + /* flags */ eModifierTypeFlag_AcceptsMesh, + /* copyData */ copyData, + + /* deformVerts */ NULL, + /* deformMatrices */ NULL, + /* deformVertsEM */ NULL, + /* deformMatricesEM */ NULL, + /* applyModifier */ applyModifier, + + /* initData */ initData, + /* requiredDataMask */ NULL, + /* freeData */ freeData, + /* isDisabled */ NULL, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ dependsOnTime, + /* dependsOnNormals */ NULL, + /* foreachObjectLink */ foreachObjectLink, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* freeRuntimeData */ freeRuntimeData, +}; diff --git a/source/blender/modifiers/intern/MOD_bparticles.h b/source/blender/modifiers/intern/MOD_bparticles.h new file mode 100644 index 00000000000..c0d31b85f6b --- /dev/null +++ b/source/blender/modifiers/intern/MOD_bparticles.h @@ -0,0 +1,10 @@ +#include "BParticles.h" + +#ifndef __MOD_BPARTICLES_H__ +# define __MOD_BPARTICLES_H__ + +struct Object; + +BParticlesSimulationState MOD_bparticles_find_simulation_state(struct Object *object); + +#endif /* __MOD_BPARTICLES_H__ */ diff --git a/source/blender/modifiers/intern/MOD_bparticles_output.c b/source/blender/modifiers/intern/MOD_bparticles_output.c new file mode 100644 index 00000000000..0482a17feac --- /dev/null +++ b/source/blender/modifiers/intern/MOD_bparticles_output.c @@ -0,0 +1,134 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2019 by the Blender Foundation. + * All rights reserved. + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file blender/modifiers/intern/MOD_bparticles_output.c + * \ingroup modifiers + * + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_lib_query.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "BLI_math.h" + +#include "MOD_bparticles.h" +#include "MOD_util.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "BParticles.h" + +static Mesh *applyModifier(ModifierData *md, + const struct ModifierEvalContext *UNUSED(ctx), + Mesh *mesh) +{ + BParticlesOutputModifierData *bpmd = (BParticlesOutputModifierData *)md; + if (bpmd->source_object == NULL) { + return mesh; + } + + BParticlesSimulationState simulation_state = MOD_bparticles_find_simulation_state( + bpmd->source_object); + if (simulation_state == NULL) { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } + + if (bpmd->output_type == MOD_BPARTICLES_OUTPUT_TETRAHEDONS) { + Mesh *new_mesh = BParticles_state_extract_type__tetrahedons(simulation_state, + bpmd->source_particle_system); + BKE_mesh_copy_settings(new_mesh, mesh); + return new_mesh; + } + else if (bpmd->output_type == MOD_BPARTICLES_OUTPUT_POINTS) { + return BParticles_state_extract_type__points(simulation_state, bpmd->source_particle_system); + } + else { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } +} + +static void initData(ModifierData *UNUSED(md)) +{ +} + +static void freeData(ModifierData *UNUSED(md)) +{ +} + +static void copyData(const ModifierData *md, ModifierData *target, const int flag) +{ + modifier_copyData_generic(md, target, flag); +} + +static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + BParticlesOutputModifierData *bpmd = (BParticlesOutputModifierData *)md; + if (bpmd->source_object) { + DEG_add_object_relation( + ctx->node, bpmd->source_object, DEG_OB_COMP_GEOMETRY, "BParticles Output Modifier"); + } +} + +static void foreachObjectLink(ModifierData *md, Object *ob, ObjectWalkFunc walk, void *userData) +{ + BParticlesOutputModifierData *bpmd = (BParticlesOutputModifierData *)md; + walk(userData, ob, &bpmd->source_object, IDWALK_CB_NOP); +} + +ModifierTypeInfo modifierType_BParticlesOutput = { + /* name */ "BParticles Output", + /* structName */ "BParticlesOutputModifierData", + /* structSize */ sizeof(BParticlesOutputModifierData), + /* type */ eModifierTypeType_Constructive, + /* flags */ eModifierTypeFlag_AcceptsMesh, + /* copyData */ copyData, + + /* deformVerts */ NULL, + /* deformMatrices */ NULL, + /* deformVertsEM */ NULL, + /* deformMatricesEM */ NULL, + /* applyModifier */ applyModifier, + + /* initData */ initData, + /* requiredDataMask */ NULL, + /* freeData */ freeData, + /* isDisabled */ NULL, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ NULL, + /* dependsOnNormals */ NULL, + /* foreachObjectLink */ foreachObjectLink, + /* foreachIDLink */ NULL, + /* foreachTexLink */ NULL, + /* freeRuntimeData */ NULL, +}; diff --git a/source/blender/modifiers/intern/MOD_functiondeform.c b/source/blender/modifiers/intern/MOD_functiondeform.c new file mode 100644 index 00000000000..ec746a4f73d --- /dev/null +++ b/source/blender/modifiers/intern/MOD_functiondeform.c @@ -0,0 +1,128 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2005 by the Blender Foundation. + * All rights reserved. + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file blender/modifiers/intern/MOD_functiondeform.c + * \ingroup modifiers + * + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "BKE_lib_query.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "BKE_global.h" +#include "BKE_main.h" + +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "MOD_util.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" +#include "time.h" + +void MOD_functiondeform_do(FunctionDeformModifierData *fdmd, + float (*vertexCos)[3], + int numVerts, + const ModifierEvalContext *ctx, + Mesh *mesh); + +static void deformVerts(ModifierData *md, + const ModifierEvalContext *ctx, + Mesh *mesh, + float (*vertexCos)[3], + int numVerts) +{ + MOD_functiondeform_do((FunctionDeformModifierData *)md, vertexCos, numVerts, ctx, mesh); +} + +static void deformVertsEM(ModifierData *md, + const ModifierEvalContext *ctx, + struct BMEditMesh *UNUSED(em), + Mesh *mesh, + float (*vertexCos)[3], + int numVerts) +{ + MOD_functiondeform_do((FunctionDeformModifierData *)md, vertexCos, numVerts, ctx, mesh); +} + +static void initData(ModifierData *md) +{ + FunctionDeformModifierData *fdmd = (FunctionDeformModifierData *)md; + fdmd->control1 = 1.0f; + fdmd->control2 = 0; +} + +static bool dependsOnTime(ModifierData *UNUSED(md)) +{ + return true; +} + +static void updateDepsgraph(ModifierData *UNUSED(md), + const ModifierUpdateDepsgraphContext *UNUSED(ctx)) +{ +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + FunctionDeformModifierData *fdmd = (FunctionDeformModifierData *)md; + + walk(userData, ob, (ID **)&fdmd->function_tree, IDWALK_CB_USER); +} + +ModifierTypeInfo modifierType_FunctionDeform = { + /* name */ "Function Deform", + /* structName */ "FunctionDeformModifierData", + /* structSize */ sizeof(FunctionDeformModifierData), + /* type */ eModifierTypeType_OnlyDeform, + /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode, + /* copyData */ modifier_copyData_generic, + + /* deformVerts */ deformVerts, + /* deformMatrices */ NULL, + /* deformVertsEM */ deformVertsEM, + /* deformMatricesEM */ NULL, + /* applyModifier */ NULL, + + /* initData */ initData, + /* requiredDataMask */ NULL, + /* freeData */ NULL, + /* isDisabled */ NULL, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ dependsOnTime, + /* dependsOnNormals */ NULL, + /* foreachObjectLink */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* freeRuntimeData */ NULL, +}; diff --git a/source/blender/modifiers/intern/MOD_functiondeform_cxx.cc b/source/blender/modifiers/intern/MOD_functiondeform_cxx.cc new file mode 100644 index 00000000000..579cc5eec35 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_functiondeform_cxx.cc @@ -0,0 +1,82 @@ +#include "DNA_modifier_types.h" + +#include "FN_multi_function_common_contexts.h" +#include "FN_multi_function_dependencies.h" +#include "FN_multi_functions.h" +#include "FN_node_tree_multi_function_network_generation.h" + +#include "BKE_id_data_cache.h" +#include "BKE_modifier.h" + +#include "DEG_depsgraph_query.h" + +using BKE::VNode; +using BLI::ArrayRef; +using BLI::float3; +using BLI::IndexRange; +using BLI::Vector; +using FN::FunctionTree; +using FN::MFContext; +using FN::MFContextBuilder; +using FN::MFInputSocket; +using FN::MFOutputSocket; +using FN::MFParamsBuilder; + +extern "C" { +void MOD_functiondeform_do(FunctionDeformModifierData *fdmd, + float (*vertexCos)[3], + int numVerts, + const ModifierEvalContext *ctx, + Mesh *mesh); +} + +void MOD_functiondeform_do(FunctionDeformModifierData *fdmd, + float (*vertexCos)[3], + int numVerts, + const ModifierEvalContext *ctx, + Mesh *UNUSED(mesh)) +{ + if (fdmd->function_tree == nullptr) { + return; + } + + bNodeTree *btree = (bNodeTree *)DEG_get_original_id((ID *)fdmd->function_tree); + + FN::BTreeVTreeMap vtrees; + FunctionTree function_tree(btree, vtrees); + + BLI::ResourceCollector resources; + auto function = FN::MFGeneration::generate_node_tree_multi_function(function_tree, resources); + + MFParamsBuilder params_builder(*function, numVerts); + params_builder.add_readonly_single_input(ArrayRef<float3>((float3 *)vertexCos, numVerts)); + params_builder.add_readonly_single_input(&fdmd->control1); + params_builder.add_readonly_single_input(&fdmd->control2); + + Vector<float3> output_vectors(numVerts); + params_builder.add_single_output<float3>(output_vectors); + + float current_time = DEG_get_ctime(ctx->depsgraph); + + FN::SceneTimeContext time_context; + time_context.time = current_time; + + FN::VertexPositionArray vertex_positions_context; + vertex_positions_context.positions = ArrayRef<float3>((float3 *)vertexCos, numVerts); + + BKE::IDHandleLookup id_handle_lookup; + FN::add_ids_used_by_nodes(id_handle_lookup, function_tree); + + BKE::IDDataCache id_data_cache; + + MFContextBuilder context_builder; + context_builder.add_global_context(id_handle_lookup); + context_builder.add_global_context(time_context); + context_builder.add_global_context(id_data_cache); + context_builder.add_element_context(vertex_positions_context, + FN::MFElementContextIndices::FromDirectMapping()); + + function->call(IndexRange(numVerts), params_builder, context_builder); + + memcpy(vertexCos, output_vectors.begin(), output_vectors.size() * sizeof(float3)); +} diff --git a/source/blender/modifiers/intern/MOD_functionpoints.c b/source/blender/modifiers/intern/MOD_functionpoints.c new file mode 100644 index 00000000000..f61e8be373d --- /dev/null +++ b/source/blender/modifiers/intern/MOD_functionpoints.c @@ -0,0 +1,113 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2005 by the Blender Foundation. + * All rights reserved. + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file blender/modifiers/intern/MOD_functiondeform.c + * \ingroup modifiers + * + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "BKE_lib_query.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "BKE_global.h" +#include "BKE_main.h" + +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "MOD_util.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" +#include "time.h" + +Mesh *MOD_functionpoints_do(FunctionPointsModifierData *fpmd, + const struct ModifierEvalContext *ctx); + +static Mesh *applyModifier(ModifierData *md, + const struct ModifierEvalContext *ctx, + struct Mesh *UNUSED(mesh)) +{ + return MOD_functionpoints_do((FunctionPointsModifierData *)md, ctx); +} + +static void initData(ModifierData *md) +{ + FunctionPointsModifierData *fpmd = (FunctionPointsModifierData *)md; + fpmd->control1 = 1.0f; + fpmd->control2 = 0; +} + +static bool dependsOnTime(ModifierData *UNUSED(md)) +{ + return true; +} + +static void updateDepsgraph(ModifierData *UNUSED(md), + const ModifierUpdateDepsgraphContext *UNUSED(ctx)) +{ +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + FunctionPointsModifierData *fpmd = (FunctionPointsModifierData *)md; + + walk(userData, ob, (ID **)&fpmd->function_tree, IDWALK_CB_USER); +} + +ModifierTypeInfo modifierType_FunctionPoints = { + /* name */ "Function Points", + /* structName */ "FunctionPointsModifierData", + /* structSize */ sizeof(FunctionPointsModifierData), + /* type */ eModifierTypeType_Constructive, + /* flags */ eModifierTypeFlag_AcceptsMesh, + /* copyData */ modifier_copyData_generic, + + /* deformVerts */ NULL, + /* deformMatrices */ NULL, + /* deformVertsEM */ NULL, + /* deformMatricesEM */ NULL, + /* applyModifier */ applyModifier, + + /* initData */ initData, + /* requiredDataMask */ NULL, + /* freeData */ NULL, + /* isDisabled */ NULL, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ dependsOnTime, + /* dependsOnNormals */ NULL, + /* foreachObjectLink */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* freeRuntimeData */ NULL, +}; diff --git a/source/blender/modifiers/intern/MOD_functionpoints_cxx.cc b/source/blender/modifiers/intern/MOD_functionpoints_cxx.cc new file mode 100644 index 00000000000..087a2f46b5d --- /dev/null +++ b/source/blender/modifiers/intern/MOD_functionpoints_cxx.cc @@ -0,0 +1,80 @@ +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" + +#include "BKE_id_data_cache.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" + +#include "BLI_math.h" + +#include "FN_multi_function_common_contexts.h" +#include "FN_multi_function_dependencies.h" +#include "FN_multi_functions.h" +#include "FN_node_tree_multi_function_network_generation.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +using BKE::VNode; +using BLI::ArrayRef; +using BLI::float3; +using BLI::IndexRange; +using BLI::Vector; +using FN::FunctionTree; +using FN::MFContext; +using FN::MFInputSocket; +using FN::MFOutputSocket; +using FN::MFParamsBuilder; + +extern "C" { +Mesh *MOD_functionpoints_do(FunctionPointsModifierData *fpmd, + const struct ModifierEvalContext *ctx); +} + +Mesh *MOD_functionpoints_do(FunctionPointsModifierData *fpmd, + const struct ModifierEvalContext *ctx) +{ + if (fpmd->function_tree == nullptr) { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } + + bNodeTree *btree = (bNodeTree *)DEG_get_original_id((ID *)fpmd->function_tree); + + FN::BTreeVTreeMap vtrees; + FunctionTree function_tree(btree, vtrees); + + BLI::ResourceCollector resources; + auto function = FN::MFGeneration::generate_node_tree_multi_function(function_tree, resources); + + MFParamsBuilder params_builder(*function, 1); + params_builder.add_readonly_single_input(&fpmd->control1); + params_builder.add_readonly_single_input(&fpmd->control2); + + FN::GenericVectorArray vector_array{FN::CPPType_float3, 1}; + params_builder.add_vector_output(vector_array); + + FN::SceneTimeContext time_context; + time_context.time = DEG_get_ctime(ctx->depsgraph); + + BKE::IDHandleLookup id_handle_lookup; + FN::add_ids_used_by_nodes(id_handle_lookup, function_tree); + + BKE::IDDataCache id_data_cache; + + FN::MFContextBuilder context_builder; + context_builder.add_global_context(id_handle_lookup); + context_builder.add_global_context(time_context); + context_builder.add_global_context(id_data_cache); + + function->call(BLI::IndexMask(1), params_builder, context_builder); + + ArrayRef<float3> output_points = vector_array[0].as_typed_ref<float3>(); + + Mesh *mesh = BKE_mesh_new_nomain(output_points.size(), 0, 0, 0, 0); + for (uint i = 0; i < output_points.size(); i++) { + copy_v3_v3(mesh->mvert[i].co, output_points[i]); + } + + return mesh; +} diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index f6b7c829c13..21680e77654 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -337,5 +337,9 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(MeshSequenceCache); INIT_TYPE(SurfaceDeform); INIT_TYPE(WeightedNormal); + INIT_TYPE(FunctionDeform); + INIT_TYPE(FunctionPoints); + INIT_TYPE(BParticles); + INIT_TYPE(BParticlesOutput); #undef INIT_TYPE } diff --git a/source/blender/simulations/BParticles.h b/source/blender/simulations/BParticles.h new file mode 100644 index 00000000000..2bfc888b80f --- /dev/null +++ b/source/blender/simulations/BParticles.h @@ -0,0 +1,45 @@ + +#ifndef __SIM_PARTICLES_C_H__ +#define __SIM_PARTICLES_C_H__ + +#include "BLI_utildefines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Mesh; +struct Depsgraph; +struct BParticlesModifierData; +struct BParticlesFrameCache; +struct Depsgraph; + +typedef struct OpaqueBParticlesSimulationState *BParticlesSimulationState; + +BParticlesSimulationState BParticles_new_simulation(void); +void BParticles_simulation_free(BParticlesSimulationState simulation_state); + +void BParticles_simulate_modifier(struct BParticlesModifierData *bpmd, + struct Depsgraph *depsgraph, + BParticlesSimulationState simulation_state, + float time_step); + +Mesh *BParticles_modifier_point_mesh_from_state(BParticlesSimulationState simulation_state); +Mesh *BParticles_modifier_mesh_from_state(BParticlesSimulationState simulation_state); + +Mesh *BParticles_state_extract_type__tetrahedons(BParticlesSimulationState simulation_state, + const char *particle_type); +Mesh *BParticles_state_extract_type__points(BParticlesSimulationState simulation_state, + const char *particle_type); + +void BParticles_modifier_free_cache(struct BParticlesModifierData *bpmd); +struct Mesh *BParticles_modifier_mesh_from_cache(struct BParticlesFrameCache *cached_frame); +void BParticles_modifier_cache_state(struct BParticlesModifierData *bpmd, + BParticlesSimulationState simulation_state, + float frame); + +#ifdef __cplusplus +} +#endif + +#endif /* __SIM_PARTICLES_C_H__ */ diff --git a/source/blender/simulations/CMakeLists.txt b/source/blender/simulations/CMakeLists.txt new file mode 100644 index 00000000000..331b0c6f85b --- /dev/null +++ b/source/blender/simulations/CMakeLists.txt @@ -0,0 +1,73 @@ +set(INC + . + ../blenlib + ../makesdna + ../makesrna + ../blenkernel + ../depsgraph + ../functions + ../imbuf + ../../../intern/guardedalloc +) + +set(INC_SYS + ${LLVM_INCLUDE_DIRS} +) + + +set(SRC + BParticles.h + + bparticles/simulate.hpp + bparticles/simulate.cpp + bparticles/emitters.hpp + bparticles/emitters.cpp + bparticles/forces.hpp + bparticles/forces.cpp + bparticles/actions.hpp + bparticles/actions.cpp + bparticles/particle_action.hpp + bparticles/particle_action.cpp + bparticles/events.hpp + bparticles/events.cpp + bparticles/emitter_interface.hpp + bparticles/block_step_data.hpp + bparticles/event_interface.hpp + bparticles/integrator_interface.hpp + bparticles/offset_handler_interface.hpp + bparticles/c_wrapper.cpp + bparticles/step_simulator.hpp + bparticles/simulation_state.hpp + bparticles/world_state.hpp + bparticles/integrator.hpp + bparticles/integrator.cpp + bparticles/node_frontend.hpp + bparticles/node_frontend.cpp + bparticles/particles_state.hpp + bparticles/particles_state.cpp + bparticles/particle_allocator.hpp + bparticles/particle_allocator.cpp + bparticles/offset_handlers.hpp + bparticles/offset_handlers.cpp + bparticles/particle_function.hpp + bparticles/particle_function.cpp + bparticles/particle_set.hpp + bparticles/particle_set.cpp + bparticles/force_interface.hpp + bparticles/force_interface.cpp +) + +set(LIB + bf_blenlib + bf_blenkernel +) + +if(WITH_TBB) + add_definitions(-DWITH_TBB) + + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) +endif() + +blender_add_lib(bf_simulations "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/simulations/bparticles/actions.cpp b/source/blender/simulations/bparticles/actions.cpp new file mode 100644 index 00000000000..9e9bb8f3f79 --- /dev/null +++ b/source/blender/simulations/bparticles/actions.cpp @@ -0,0 +1,220 @@ +#include "actions.hpp" + +#include "BLI_hash.h" + +namespace BParticles { + +void ActionSequence::execute(ParticleActionContext &context) +{ + for (auto &action : m_actions) { + action->execute(context); + } +} + +static void update_position_and_velocity_offsets(ParticleActionContext &context) +{ + auto *offsets_context = context.try_find<ParticleIntegratedOffsets>(); + auto *remaining_times_context = context.try_find<ParticleRemainingTimeInStep>(); + if (offsets_context == nullptr || remaining_times_context == nullptr) { + return; + } + + AttributesRef attributes = context.attributes(); + MutableAttributesRef attribute_offsets = offsets_context->offsets; + ArrayRef<float> remaining_times = remaining_times_context->remaining_times; + + auto velocities = attributes.get<float3>("Velocity"); + auto position_offsets = attribute_offsets.try_get<float3>("Position"); + auto velocity_offsets = attribute_offsets.try_get<float3>("Velocity"); + + for (uint pindex : context.mask()) { + float3 velocity = velocities[pindex]; + + if (position_offsets.has_value()) { + position_offsets.value()[pindex] = velocity * remaining_times[pindex]; + } + if (velocity_offsets.has_value()) { + velocity_offsets.value()[pindex] = float3(0); + } + } +} + +void ConditionAction::execute(ParticleActionContext &context) +{ + ParticleFunctionEvaluator inputs{m_inputs_fn, context.mask(), context.attributes()}; + inputs.context_builder().set_buffer_cache(context.buffer_cache()); + inputs.compute(); + + Vector<uint> true_pindices, false_pindices; + for (uint pindex : context.mask()) { + if (inputs.get_single<bool>("Condition", 0, pindex)) { + true_pindices.append(pindex); + } + else { + false_pindices.append(pindex); + } + } + + m_true_action.execute_for_subset(true_pindices.as_ref(), context); + m_false_action.execute_for_subset(false_pindices.as_ref(), context); +} + +void SetAttributeAction::execute(ParticleActionContext &context) +{ + Optional<GenericMutableArrayRef> attribute_opt = context.attributes().try_get(m_attribute_name, + m_attribute_type); + + if (!attribute_opt.has_value()) { + return; + } + + GenericMutableArrayRef attribute = *attribute_opt; + + ParticleFunctionEvaluator inputs{m_inputs_fn, context.mask(), context.attributes()}; + inputs.context_builder().set_buffer_cache(context.buffer_cache()); + inputs.compute(); + + for (uint pindex : context.mask()) { + const void *value = inputs.get_single("Value", 0, pindex); + void *dst = attribute[pindex]; + m_attribute_type.copy_to_initialized(value, dst); + } + + if (m_attribute_name == "Velocity") { + update_position_and_velocity_offsets(context); + } +} + +using FN::MFDataType; +using FN::MFParamType; + +void SpawnParticlesAction::execute(ParticleActionContext &context) +{ + if (context.mask().size() == 0) { + return; + } + + auto *current_time_context = context.try_find<ParticleCurrentTimesContext>(); + if (current_time_context == nullptr) { + return; + } + ArrayRef<float> current_times = current_time_context->current_times; + + uint array_size = context.mask().min_array_size(); + + ParticleFunctionEvaluator inputs{m_spawn_function, context.mask(), context.attributes()}; + inputs.context_builder().set_buffer_cache(context.buffer_cache()); + inputs.compute(); + + Array<int> particle_counts(array_size, -1); + + const MultiFunction &fn = m_spawn_function.fn(); + for (uint param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + if (param_type.is_vector_output()) { + FN::GenericVectorArray &vector_array = inputs.computed_vector_array(param_index); + for (uint i : context.mask()) { + FN::GenericArrayRef array = vector_array[i]; + particle_counts[i] = std::max<int>(particle_counts[i], array.size()); + } + } + } + + for (uint i : context.mask()) { + if (particle_counts[i] == -1) { + particle_counts[i] = 1; + } + } + + uint total_spawn_amount = 0; + for (uint i : context.mask()) { + total_spawn_amount += particle_counts[i]; + } + + StringMap<GenericMutableArrayRef> attribute_arrays; + + Vector<float> new_birth_times; + for (uint i : context.mask()) { + new_birth_times.append_n_times(current_times[i], particle_counts[i]); + } + attribute_arrays.add_new("Birth Time", new_birth_times.as_mutable_ref()); + + for (uint param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + MFDataType data_type = param_type.data_type(); + StringRef attribute_name = m_attribute_names[param_index]; + + switch (data_type.category()) { + case MFDataType::Single: { + const FN::CPPType &type = data_type.single__cpp_type(); + void *buffer = MEM_malloc_arrayN(total_spawn_amount, type.size(), __func__); + GenericMutableArrayRef array(type, buffer, total_spawn_amount); + GenericArrayRef computed_array = inputs.computed_array(param_index); + + uint current = 0; + for (uint i : context.mask()) { + uint amount = particle_counts[i]; + array.slice(current, amount).fill__uninitialized(computed_array[i]); + current += amount; + } + + attribute_arrays.add(attribute_name, array); + break; + } + case MFDataType::Vector: { + const FN::CPPType &base_type = data_type.vector__cpp_base_type(); + void *buffer = MEM_malloc_arrayN(total_spawn_amount, base_type.size(), __func__); + GenericMutableArrayRef array(base_type, buffer, total_spawn_amount); + FN::GenericVectorArray &computed_vector_array = inputs.computed_vector_array(param_index); + + uint current = 0; + for (uint pindex : context.mask()) { + uint amount = particle_counts[pindex]; + GenericMutableArrayRef array_slice = array.slice(current, amount); + GenericArrayRef computed_array = computed_vector_array[pindex]; + + if (computed_array.size() == 0) { + const void *default_buffer = context.attributes().info().default_of(attribute_name); + array_slice.fill__uninitialized(default_buffer); + } + else if (computed_array.size() == amount) { + base_type.copy_to_uninitialized_n( + computed_array.buffer(), array_slice.buffer(), amount); + } + else { + for (uint i : IndexRange(amount)) { + base_type.copy_to_uninitialized(computed_array[i % computed_array.size()], + array_slice[i]); + } + } + + current += amount; + } + + attribute_arrays.add(attribute_name, array); + break; + } + } + } + + for (StringRef system_name : m_systems_to_emit) { + auto new_particles = context.particle_allocator().request(system_name, total_spawn_amount); + + attribute_arrays.foreach_item([&](StringRef attribute_name, GenericMutableArrayRef array) { + if (new_particles.info().has_attribute(attribute_name, array.type())) { + new_particles.set(attribute_name, array); + } + }); + + m_action.execute_for_new_particles(new_particles, context); + } + + attribute_arrays.foreach_item([&](StringRef attribute_name, GenericMutableArrayRef array) { + if (attribute_name != "Birth Time") { + array.destruct_indices(context.mask()); + MEM_freeN(array.buffer()); + } + }); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/actions.hpp b/source/blender/simulations/bparticles/actions.hpp new file mode 100644 index 00000000000..6a4304db659 --- /dev/null +++ b/source/blender/simulations/bparticles/actions.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "particle_action.hpp" +#include "particle_function.hpp" + +namespace BParticles { + +using FN::CPPType; + +class ActionSequence : public ParticleAction { + private: + Vector<ParticleAction *> m_actions; + + public: + ActionSequence(Vector<ParticleAction *> actions) : m_actions(std::move(actions)) + { + } + + void execute(ParticleActionContext &context) override; +}; + +class ConditionAction : public ParticleAction { + private: + const ParticleFunction &m_inputs_fn; + ParticleAction &m_true_action; + ParticleAction &m_false_action; + + public: + ConditionAction(const ParticleFunction &inputs_fn, + ParticleAction &true_action, + ParticleAction &false_action) + : m_inputs_fn(inputs_fn), m_true_action(true_action), m_false_action(false_action) + { + } + + void execute(ParticleActionContext &context) override; +}; + +class SetAttributeAction : public ParticleAction { + private: + std::string m_attribute_name; + const CPPType &m_attribute_type; + ParticleFunction &m_inputs_fn; + + public: + SetAttributeAction(std::string attribute_name, + const CPPType &attribute_type, + ParticleFunction &inputs_fn) + : m_attribute_name(std::move(attribute_name)), + m_attribute_type(attribute_type), + m_inputs_fn(inputs_fn) + { + } + + void execute(ParticleActionContext &context) override; +}; + +class SpawnParticlesAction : public ParticleAction { + private: + ArrayRef<std::string> m_systems_to_emit; + const ParticleFunction &m_spawn_function; + Vector<std::string> m_attribute_names; + ParticleAction &m_action; + + public: + SpawnParticlesAction(ArrayRef<std::string> systems_to_emit, + const ParticleFunction &spawn_function, + Vector<std::string> attribute_names, + ParticleAction &action) + : m_systems_to_emit(systems_to_emit), + m_spawn_function(spawn_function), + m_attribute_names(std::move(attribute_names)), + m_action(action) + { + } + + void execute(ParticleActionContext &context) override; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/block_step_data.hpp b/source/blender/simulations/bparticles/block_step_data.hpp new file mode 100644 index 00000000000..bcf196c5d09 --- /dev/null +++ b/source/blender/simulations/bparticles/block_step_data.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "FN_attributes_ref.h" + +#include "BLI_buffer_cache.h" +#include "BLI_float_interval.h" + +#include "simulation_state.hpp" + +namespace BParticles { + +using BLI::BufferCache; +using BLI::FloatInterval; +using FN::AttributesRef; +using FN::MutableAttributesRef; + +struct BlockStepData { + SimulationState &simulation_state; + BufferCache &buffer_cache; + MutableAttributesRef attributes; + MutableAttributesRef attribute_offsets; + MutableArrayRef<float> remaining_durations; + float step_end_time; + + uint array_size() + { + return this->remaining_durations.size(); + } +}; + +class BlockStepDataAccess { + protected: + BlockStepData &m_step_data; + + public: + BlockStepDataAccess(BlockStepData &step_data) : m_step_data(step_data) + { + } + + SimulationState &simulation_state() + { + return m_step_data.simulation_state; + } + + BufferCache &buffer_cache() + { + return m_step_data.buffer_cache; + } + + uint array_size() const + { + return m_step_data.array_size(); + } + + BlockStepData &step_data() + { + return m_step_data; + } + + MutableAttributesRef attributes() + { + return m_step_data.attributes; + } + + MutableAttributesRef attribute_offsets() + { + return m_step_data.attribute_offsets; + } + + MutableArrayRef<float> remaining_durations() + { + return m_step_data.remaining_durations; + } + + float step_end_time() + { + return m_step_data.step_end_time; + } + + FloatInterval time_span(uint pindex) + { + float duration = m_step_data.remaining_durations[pindex]; + return FloatInterval(m_step_data.step_end_time - duration, duration); + } +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/c_wrapper.cpp b/source/blender/simulations/bparticles/c_wrapper.cpp new file mode 100644 index 00000000000..84d8255d535 --- /dev/null +++ b/source/blender/simulations/bparticles/c_wrapper.cpp @@ -0,0 +1,348 @@ +#include "BParticles.h" +#include "node_frontend.hpp" +#include "simulate.hpp" +#include "simulation_state.hpp" +#include "world_state.hpp" + +#include "BLI_color.h" +#include "BLI_parallel.h" +#include "BLI_string.h" +#include "BLI_timeit.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "FN_node_tree.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" + +#define WRAPPERS(T1, T2) \ + inline T1 unwrap(T2 value) \ + { \ + return (T1)value; \ + } \ + inline T2 wrap(T1 value) \ + { \ + return (T2)value; \ + } + +using namespace BParticles; + +using BLI::ArrayRef; +using BLI::float3; +using BLI::rgba_b; +using BLI::rgba_f; +using BLI::StringRef; +using BLI::Vector; + +WRAPPERS(SimulationState *, BParticlesSimulationState) + +BParticlesSimulationState BParticles_new_simulation() +{ + SimulationState *state = new SimulationState(); + return wrap(state); +} + +void BParticles_simulation_free(BParticlesSimulationState state_c) +{ + delete unwrap(state_c); +} + +void BParticles_simulate_modifier(BParticlesModifierData *bpmd, + Depsgraph *UNUSED(depsgraph), + BParticlesSimulationState state_c, + float time_step) +{ + if (bpmd->node_tree == NULL) { + return; + } + + SimulationState &simulation_state = *unwrap(state_c); + simulation_state.time().start_update(time_step); + + bNodeTree *btree = (bNodeTree *)DEG_get_original_id((ID *)bpmd->node_tree); + auto simulator = simulator_from_node_tree(btree); + + simulator->simulate(simulation_state); + + simulation_state.time().end_update(); + + auto &containers = simulation_state.particles().particle_containers(); + containers.foreach_item([](StringRefNull system_name, ParticleSet *particles) { + std::cout << "Particle System: " << system_name << ": " << particles->size() << "\n"; + }); +} + +static float3 tetrahedon_vertices[4] = { + {1, -1, -1}, + {1, 1, 1}, + {-1, -1, 1}, + {-1, 1, -1}, +}; + +static uint tetrahedon_loop_starts[4] = {0, 3, 6, 9}; +static uint tetrahedon_loop_lengths[4] = {3, 3, 3, 3}; +static uint tetrahedon_loop_vertices[12] = {0, 1, 2, 0, 3, 1, 0, 2, 3, 1, 2, 3}; +static uint tetrahedon_loop_edges[12] = {0, 3, 1, 2, 4, 0, 1, 5, 2, 3, 5, 4}; +static uint tetrahedon_edges[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}; + +static void distribute_tetrahedons_range(Mesh *mesh, + MutableArrayRef<MLoopCol> loop_colors, + IndexRange range, + ArrayRef<float3> centers, + ArrayRef<float> scales, + ArrayRef<rgba_f> colors) +{ + for (uint instance : range) { + uint vertex_offset = instance * ARRAY_SIZE(tetrahedon_vertices); + uint face_offset = instance * ARRAY_SIZE(tetrahedon_loop_starts); + uint loop_offset = instance * ARRAY_SIZE(tetrahedon_loop_vertices); + uint edge_offset = instance * ARRAY_SIZE(tetrahedon_edges); + + float3 center = centers[instance]; + for (uint i = 0; i < ARRAY_SIZE(tetrahedon_vertices); i++) { + copy_v3_v3(mesh->mvert[vertex_offset + i].co, + center + tetrahedon_vertices[i] * scales[instance]); + } + + for (uint i = 0; i < ARRAY_SIZE(tetrahedon_loop_starts); i++) { + mesh->mpoly[face_offset + i].loopstart = loop_offset + tetrahedon_loop_starts[i]; + mesh->mpoly[face_offset + i].totloop = tetrahedon_loop_lengths[i]; + } + + rgba_f color_f = colors[instance]; + rgba_b color_b = color_f; + MLoopCol loop_col = {color_b.r, color_b.g, color_b.b, color_b.a}; + for (uint i = 0; i < ARRAY_SIZE(tetrahedon_loop_vertices); i++) { + mesh->mloop[loop_offset + i].v = vertex_offset + tetrahedon_loop_vertices[i]; + mesh->mloop[loop_offset + i].e = edge_offset + tetrahedon_loop_edges[i]; + loop_colors[loop_offset + i] = loop_col; + } + + for (uint i = 0; i < ARRAY_SIZE(tetrahedon_edges); i++) { + mesh->medge[edge_offset + i].v1 = vertex_offset + tetrahedon_edges[i][0]; + mesh->medge[edge_offset + i].v2 = vertex_offset + tetrahedon_edges[i][1]; + } + } +} + +static Mesh *distribute_tetrahedons(ArrayRef<float3> centers, + ArrayRef<float> scales, + ArrayRef<rgba_f> colors) +{ + uint amount = centers.size(); + Mesh *mesh = BKE_mesh_new_nomain(amount * ARRAY_SIZE(tetrahedon_vertices), + amount * ARRAY_SIZE(tetrahedon_edges), + 0, + amount * ARRAY_SIZE(tetrahedon_loop_vertices), + amount * ARRAY_SIZE(tetrahedon_loop_starts)); + + auto loop_colors = MutableArrayRef<MLoopCol>( + (MLoopCol *)CustomData_add_layer_named( + &mesh->ldata, CD_MLOOPCOL, CD_DEFAULT, nullptr, mesh->totloop, "Color"), + mesh->totloop); + + BLI::blocked_parallel_for(IndexRange(amount), 1000, [&](IndexRange range) { + distribute_tetrahedons_range(mesh, loop_colors, range, centers, scales, colors); + }); + + return mesh; +} + +static Mesh *distribute_points(ArrayRef<float3> points) +{ + Mesh *mesh = BKE_mesh_new_nomain(points.size(), 0, 0, 0, 0); + + for (uint i = 0; i < mesh->totvert; i++) { + copy_v3_v3(mesh->mvert[i].co, points[i]); + mesh->mvert[i].no[2] = 32767; + } + + return mesh; +} + +void BParticles_modifier_free_cache(BParticlesModifierData *bpmd) +{ + if (bpmd->cached_frames == nullptr) { + BLI_assert(bpmd->num_cached_frames == 0); + return; + } + + for (auto &cached_frame : BLI::ref_c_array(bpmd->cached_frames, bpmd->num_cached_frames)) { + for (auto &cached_type : + BLI::ref_c_array(cached_frame.particle_types, cached_frame.num_particle_types)) { + for (auto &cached_attribute : + BLI::ref_c_array(cached_type.attributes_float, cached_type.num_attributes_float)) { + if (cached_attribute.values != nullptr) { + MEM_freeN(cached_attribute.values); + } + } + if (cached_type.attributes_float != nullptr) { + MEM_freeN(cached_type.attributes_float); + } + } + if (cached_frame.particle_types != nullptr) { + MEM_freeN(cached_frame.particle_types); + } + } + MEM_freeN(bpmd->cached_frames); + bpmd->cached_frames = nullptr; + bpmd->num_cached_frames = 0; +} + +Mesh *BParticles_modifier_point_mesh_from_state(BParticlesSimulationState state_c) +{ + SimulationState &state = *unwrap(state_c); + + Vector<float3> all_positions; + state.particles().particle_containers().foreach_value([&](ParticleSet *particles) { + ArrayRef<float3> positions = particles->attributes().get<float3>("Position"); + all_positions.extend(positions); + }); + + return distribute_points(all_positions); +} + +Mesh *BParticles_modifier_mesh_from_state(BParticlesSimulationState state_c) +{ + SimulationState &state = *unwrap(state_c); + + Vector<float3> positions; + Vector<float> sizes; + Vector<rgba_f> colors; + + state.particles().particle_containers().foreach_value( + [&positions, &colors, &sizes](ParticleSet *particles) { + AttributesRef attributes = particles->attributes(); + positions.extend(attributes.get<float3>("Position")); + colors.extend(attributes.get<rgba_f>("Color")); + sizes.extend(attributes.get<float>("Size")); + }); + + Mesh *mesh = distribute_tetrahedons(positions, sizes, colors); + return mesh; +} + +Mesh *BParticles_modifier_mesh_from_cache(BParticlesFrameCache *cached_frame) +{ + Vector<float3> positions; + Vector<float> sizes; + Vector<rgba_f> colors; + + for (uint i = 0; i < cached_frame->num_particle_types; i++) { + BParticlesTypeCache &type = cached_frame->particle_types[i]; + positions.extend( + ArrayRef<float3>((float3 *)type.attributes_float[0].values, type.particle_amount)); + sizes.extend(ArrayRef<float>(type.attributes_float[1].values, type.particle_amount)); + colors.extend( + ArrayRef<rgba_f>((rgba_f *)type.attributes_float[2].values, type.particle_amount)); + } + + Mesh *mesh = distribute_tetrahedons(positions, sizes, colors); + return mesh; +} + +Mesh *BParticles_state_extract_type__tetrahedons(BParticlesSimulationState simulation_state_c, + const char *particle_type) +{ + SimulationState &state = *unwrap(simulation_state_c); + ParticlesState &particles_state = state.particles(); + ParticleSet **particles_ptr = particles_state.particle_containers().lookup_ptr(particle_type); + if (particles_ptr == nullptr) { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } + ParticleSet &particles = **particles_ptr; + + AttributesRef attributes = particles.attributes(); + auto positions = attributes.get<float3>("Position"); + auto sizes = attributes.get<float>("Size"); + auto colors = attributes.get<rgba_f>("Color"); + + return distribute_tetrahedons(positions, sizes, colors); +} + +Mesh *BParticles_state_extract_type__points(BParticlesSimulationState simulation_state_c, + const char *particle_type) +{ + SimulationState &state = *unwrap(simulation_state_c); + ParticlesState &particles_state = state.particles(); + ParticleSet *particles_ptr = particles_state.particle_containers().lookup_default(particle_type, + nullptr); + if (particles_ptr == nullptr) { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } + ParticleSet &particles = *particles_ptr; + + auto positions = particles.attributes().get<float3>("Position"); + return distribute_points(positions); +} + +void BParticles_modifier_cache_state(BParticlesModifierData *bpmd, + BParticlesSimulationState state_c, + float frame) +{ + SimulationState &state = *unwrap(state_c); + + Vector<std::string> system_names; + Vector<ParticleSet *> particle_sets; + + state.particles().particle_containers().foreach_item( + [&system_names, &particle_sets](StringRefNull name, ParticleSet *particles) { + system_names.append(name); + particle_sets.append(particles); + }); + + BParticlesFrameCache cached_frame; + memset(&cached_frame, 0, sizeof(BParticlesFrameCache)); + cached_frame.frame = frame; + cached_frame.num_particle_types = particle_sets.size(); + cached_frame.particle_types = (BParticlesTypeCache *)MEM_calloc_arrayN( + particle_sets.size(), sizeof(BParticlesTypeCache), __func__); + + for (uint i : particle_sets.index_range()) { + ParticleSet &particles = *particle_sets[i]; + BParticlesTypeCache &cached_type = cached_frame.particle_types[i]; + + strncpy(cached_type.name, system_names[i].data(), sizeof(cached_type.name) - 1); + cached_type.particle_amount = particles.size(); + + cached_type.num_attributes_float = 3; + cached_type.attributes_float = (BParticlesAttributeCacheFloat *)MEM_calloc_arrayN( + cached_type.num_attributes_float, sizeof(BParticlesAttributeCacheFloat), __func__); + + BParticlesAttributeCacheFloat &position_attribute = cached_type.attributes_float[0]; + position_attribute.floats_per_particle = 3; + strncpy(position_attribute.name, "Position", sizeof(position_attribute.name)); + position_attribute.values = (float *)MEM_malloc_arrayN( + cached_type.particle_amount, sizeof(float3), __func__); + FN::CPPType_float3.copy_to_uninitialized_n(particles.attributes().get("Position").buffer(), + position_attribute.values, + cached_type.particle_amount); + + BParticlesAttributeCacheFloat &size_attribute = cached_type.attributes_float[1]; + size_attribute.floats_per_particle = 1; + strncpy(size_attribute.name, "Size", sizeof(size_attribute.name)); + size_attribute.values = (float *)MEM_malloc_arrayN( + cached_type.particle_amount, sizeof(float), __func__); + FN::CPPType_float.copy_to_uninitialized_n(particles.attributes().get("Size").buffer(), + size_attribute.values, + cached_type.particle_amount); + + BParticlesAttributeCacheFloat &color_attribute = cached_type.attributes_float[2]; + color_attribute.floats_per_particle = 4; + strncpy(color_attribute.name, "Color", sizeof(color_attribute.name)); + color_attribute.values = (float *)MEM_malloc_arrayN( + cached_type.particle_amount, sizeof(rgba_f), __func__); + FN::CPP_TYPE<rgba_f>().copy_to_uninitialized_n(particles.attributes().get("Color").buffer(), + color_attribute.values, + cached_type.particle_amount); + } + + bpmd->cached_frames = (BParticlesFrameCache *)MEM_reallocN( + bpmd->cached_frames, sizeof(BParticlesFrameCache) * (bpmd->num_cached_frames + 1)); + bpmd->cached_frames[bpmd->num_cached_frames] = cached_frame; + bpmd->num_cached_frames++; +} diff --git a/source/blender/simulations/bparticles/emitter_interface.hpp b/source/blender/simulations/bparticles/emitter_interface.hpp new file mode 100644 index 00000000000..651eb7e1cd6 --- /dev/null +++ b/source/blender/simulations/bparticles/emitter_interface.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "particle_allocator.hpp" +#include "simulation_state.hpp" + +namespace BParticles { + +class EmitterInterface { + private: + SimulationState &m_simulation_state; + ParticleAllocator &m_particle_allocator; + FloatInterval m_time_span; + + public: + EmitterInterface(SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + FloatInterval time_span) + : m_simulation_state(simulation_state), + m_particle_allocator(particle_allocator), + m_time_span(time_span) + { + } + + ~EmitterInterface() = default; + + ParticleAllocator &particle_allocator() + { + return m_particle_allocator; + } + + /** + * Time span that new particles should be emitted in. + */ + FloatInterval time_span() + { + return m_time_span; + } + + uint time_step() + { + return m_simulation_state.time().current_update_index(); + } + + /** + * True when this is the first time step in a simulation, otherwise false. + */ + bool is_first_step() + { + return m_simulation_state.time().current_update_index() == 1; + } +}; + +/** + * An emitter creates new particles of possibly different types within a certain time span. + */ +class Emitter { + public: + virtual ~Emitter() + { + } + + /** + * Create new particles within a time span. + * + * In general it works like so: + * 1. Prepare vectors with attribute values for e.g. position and velocity of the new + * particles. + * 2. Request an emit target that can contain a given amount of particles of a specific type. + * 3. Copy the prepared attribute arrays into the target. Other attributes are initialized with + * some default value. + * 4. Specify the exact birth times of every particle within the time span. This will allow the + * framework to simulate the new particles for partial time steps to avoid stepping. + * + * To create particles of different types, multiple emit targets have to be requested. + */ + virtual void emit(EmitterInterface &interface) = 0; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/emitters.cpp b/source/blender/simulations/bparticles/emitters.cpp new file mode 100644 index 00000000000..09409d80e38 --- /dev/null +++ b/source/blender/simulations/bparticles/emitters.cpp @@ -0,0 +1,487 @@ +#include "DNA_curve_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BKE_curve.h" +#include "BKE_deform.h" +#include "BKE_mesh_runtime.h" +#include "BKE_surface_hook.h" + +#include "BLI_math_geom.h" +#include "BLI_vector_adaptor.h" + +#include "FN_multi_function_common_contexts.h" + +#include "emitters.hpp" + +namespace BParticles { + +using BKE::SurfaceHook; +using BLI::VectorAdaptor; + +static float random_float() +{ + return (rand() % 4096) / 4096.0f; +} + +void PointEmitter::emit(EmitterInterface &interface) +{ + uint amount = 10; + Vector<float3> new_positions(amount); + Vector<float3> new_velocities(amount); + Vector<float> new_sizes(amount); + Vector<float> birth_times(amount); + + for (uint i = 0; i < amount; i++) { + float t = i / (float)amount; + new_positions[i] = m_position.interpolate(t); + new_velocities[i] = m_velocity.interpolate(t); + new_sizes[i] = m_size.interpolate(t); + birth_times[i] = interface.time_span().value_at(t); + } + + for (StringRef type : m_systems_to_emit) { + auto new_particles = interface.particle_allocator().request(type, new_positions.size()); + new_particles.set<float3>("Position", new_positions); + new_particles.set<float3>("Velocity", new_velocities); + new_particles.set<float>("Size", new_sizes); + new_particles.set<float>("Birth Time", birth_times); + + m_action.execute_from_emitter(new_particles, interface); + } +} + +static float3 random_uniform_bary_coords() +{ + float rand1 = random_float(); + float rand2 = random_float(); + + if (rand1 + rand2 > 1.0f) { + rand1 = 1.0f - rand1; + rand2 = 1.0f - rand2; + } + + return float3(rand1, rand2, 1.0f - rand1 - rand2); +} + +static BLI_NOINLINE void get_average_triangle_weights(const Mesh *mesh, + ArrayRef<MLoopTri> looptris, + ArrayRef<float> vertex_weights, + MutableArrayRef<float> r_looptri_weights) +{ + for (uint triangle_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[triangle_index]; + float triangle_weight = 0.0f; + for (uint i = 0; i < 3; i++) { + uint vertex_index = mesh->mloop[looptri.tri[i]].v; + float weight = vertex_weights[vertex_index]; + triangle_weight += weight; + } + + if (triangle_weight > 0) { + triangle_weight /= 3.0f; + } + r_looptri_weights[triangle_index] = triangle_weight; + } +} + +static BLI_NOINLINE void compute_cumulative_distribution( + ArrayRef<float> weights, MutableArrayRef<float> r_cumulative_weights) +{ + BLI_assert(weights.size() + 1 == r_cumulative_weights.size()); + + r_cumulative_weights[0] = 0; + for (uint i : weights.index_range()) { + r_cumulative_weights[i + 1] = r_cumulative_weights[i] + weights[i]; + } +} + +static void sample_cumulative_distribution__recursive(uint amount, + uint start, + uint one_after_end, + ArrayRef<float> cumulative_weights, + VectorAdaptor<uint> &sampled_indices) +{ + BLI_assert(start <= one_after_end); + uint size = one_after_end - start; + if (size == 0) { + BLI_assert(amount == 0); + } + else if (amount == 0) { + return; + } + else if (size == 1) { + sampled_indices.append_n_times(start, amount); + } + else { + uint middle = start + size / 2; + float left_weight = cumulative_weights[middle] - cumulative_weights[start]; + float right_weight = cumulative_weights[one_after_end] - cumulative_weights[middle]; + BLI_assert(left_weight >= 0.0f && right_weight >= 0.0f); + float weight_sum = left_weight + right_weight; + BLI_assert(weight_sum > 0.0f); + + float left_factor = left_weight / weight_sum; + float right_factor = right_weight / weight_sum; + + uint left_amount = amount * left_factor; + uint right_amount = amount * right_factor; + + if (left_amount + right_amount < amount) { + BLI_assert(left_amount + right_amount + 1 == amount); + float weight_per_item = weight_sum / amount; + float total_remaining_weight = weight_sum - (left_amount + right_amount) * weight_per_item; + float left_remaining_weight = left_weight - left_amount * weight_per_item; + float left_remaining_factor = left_remaining_weight / total_remaining_weight; + if (random_float() < left_remaining_factor) { + left_amount++; + } + else { + right_amount++; + } + } + + sample_cumulative_distribution__recursive( + left_amount, start, middle, cumulative_weights, sampled_indices); + sample_cumulative_distribution__recursive( + right_amount, middle, one_after_end, cumulative_weights, sampled_indices); + } +} + +static BLI_NOINLINE void sample_cumulative_distribution(uint amount, + ArrayRef<float> cumulative_weights, + MutableArrayRef<uint> r_sampled_indices) +{ + BLI_assert(amount == r_sampled_indices.size()); + + VectorAdaptor<uint> sampled_indices(r_sampled_indices.begin(), amount); + sample_cumulative_distribution__recursive( + amount, 0, cumulative_weights.size() - 1, cumulative_weights, sampled_indices); + BLI_assert(sampled_indices.is_full()); +} + +static BLI_NOINLINE void compute_triangle_areas(Mesh *mesh, + ArrayRef<MLoopTri> triangles, + MutableArrayRef<float> r_areas) +{ + BLI::assert_same_size(triangles, r_areas); + + for (uint i : triangles.index_range()) { + const MLoopTri &triangle = triangles[i]; + + float3 v1 = mesh->mvert[mesh->mloop[triangle.tri[0]].v].co; + float3 v2 = mesh->mvert[mesh->mloop[triangle.tri[1]].v].co; + float3 v3 = mesh->mvert[mesh->mloop[triangle.tri[2]].v].co; + + float area = area_tri_v3(v1, v2, v3); + r_areas[i] = area; + } +} + +static BLI_NOINLINE bool sample_weighted_buckets(uint sample_amount, + ArrayRef<float> weights, + MutableArrayRef<uint> r_samples) +{ + BLI_assert(sample_amount == r_samples.size()); + + Array<float> cumulative_weights(weights.size() + 1); + compute_cumulative_distribution(weights, cumulative_weights); + + if (sample_amount > 0 && cumulative_weights.as_ref().last() == 0.0f) { + /* All weights are zero. */ + return false; + } + + sample_cumulative_distribution(sample_amount, cumulative_weights, r_samples); + return true; +} + +static BLI_NOINLINE void sample_looptris(Mesh *mesh, + ArrayRef<MLoopTri> triangles, + ArrayRef<uint> triangles_to_sample, + MutableArrayRef<float3> r_sampled_positions, + MutableArrayRef<float3> r_sampled_normals, + MutableArrayRef<float3> r_sampled_bary_coords) +{ + BLI::assert_same_size(triangles_to_sample, r_sampled_positions); + + MLoop *loops = mesh->mloop; + MVert *verts = mesh->mvert; + + for (uint i : triangles_to_sample.index_range()) { + uint triangle_index = triangles_to_sample[i]; + const MLoopTri &triangle = triangles[triangle_index]; + + float3 v1 = verts[loops[triangle.tri[0]].v].co; + float3 v2 = verts[loops[triangle.tri[1]].v].co; + float3 v3 = verts[loops[triangle.tri[2]].v].co; + + float3 bary_coords = random_uniform_bary_coords(); + + float3 position; + interp_v3_v3v3v3(position, v1, v2, v3, bary_coords); + + float3 normal; + normal_tri_v3(normal, v1, v2, v3); + + r_sampled_positions[i] = position; + r_sampled_normals[i] = normal; + r_sampled_bary_coords[i] = bary_coords; + } +} + +void SurfaceEmitter::emit(EmitterInterface &interface) +{ + if (m_object == nullptr) { + return; + } + if (m_object->type != OB_MESH) { + return; + } + if (m_rate <= 0.0f) { + return; + } + + Vector<float> birth_moments; + float factor_start, factor_step; + interface.time_span().uniform_sample_range(m_rate, factor_start, factor_step); + for (float factor = factor_start; factor < 1.0f; factor += factor_step) { + birth_moments.append(factor); + } + std::random_shuffle(birth_moments.begin(), birth_moments.end()); + + uint particles_to_emit = birth_moments.size(); + + Mesh *mesh = (Mesh *)m_object->data; + + const MLoopTri *triangles_buffer = BKE_mesh_runtime_looptri_ensure(mesh); + ArrayRef<MLoopTri> triangles(triangles_buffer, BKE_mesh_runtime_looptri_len(mesh)); + if (triangles.size() == 0) { + return; + } + + Array<float> triangle_weights(triangles.size()); + get_average_triangle_weights(mesh, triangles, m_vertex_weights, triangle_weights); + + Array<float> triangle_areas(triangles.size()); + compute_triangle_areas(mesh, triangles, triangle_areas); + + for (uint i : triangles.index_range()) { + triangle_weights[i] *= triangle_areas[i]; + } + + Array<uint> triangles_to_sample(particles_to_emit); + if (!sample_weighted_buckets(particles_to_emit, triangle_weights, triangles_to_sample)) { + return; + } + + Array<float3> local_positions(particles_to_emit); + Array<float3> local_normals(particles_to_emit); + Array<float3> bary_coords(particles_to_emit); + sample_looptris( + mesh, triangles, triangles_to_sample, local_positions, local_normals, bary_coords); + + float epsilon = 0.01f; + Array<float4x4> transforms_at_birth(particles_to_emit); + Array<float4x4> transforms_before_birth(particles_to_emit); + m_transform.interpolate(birth_moments, 0.0f, transforms_at_birth); + m_transform.interpolate(birth_moments, -epsilon, transforms_before_birth); + + Array<float3> positions_at_birth(particles_to_emit); + float4x4::transform_positions(transforms_at_birth, local_positions, positions_at_birth); + + Array<float3> surface_velocities(particles_to_emit); + for (uint i = 0; i < particles_to_emit; i++) { + float3 position_before_birth = transforms_before_birth[i].transform_position( + local_positions[i]); + surface_velocities[i] = (positions_at_birth[i] - position_before_birth) / epsilon / + interface.time_span().size(); + } + + Array<float3> world_normals(particles_to_emit); + float4x4::transform_directions(transforms_at_birth, local_normals, world_normals); + + Array<float> birth_times(particles_to_emit); + interface.time_span().value_at(birth_moments, birth_times); + + Array<SurfaceHook> emit_hooks(particles_to_emit); + BKE::ObjectIDHandle object_handle(m_object); + for (uint i = 0; i < particles_to_emit; i++) { + emit_hooks[i] = SurfaceHook(object_handle, triangles_to_sample[i], bary_coords[i]); + } + + for (StringRef system_name : m_systems_to_emit) { + auto new_particles = interface.particle_allocator().request(system_name, + positions_at_birth.size()); + new_particles.set<float3>("Position", positions_at_birth); + new_particles.set<float>("Birth Time", birth_times); + new_particles.set<SurfaceHook>("Emit Hook", emit_hooks); + + m_on_birth_action.execute_from_emitter(new_particles, interface); + } +} + +void InitialGridEmitter::emit(EmitterInterface &interface) +{ + if (!interface.is_first_step()) { + return; + } + + Vector<float3> new_positions; + + float offset_x = -(m_amount_x * m_step_x / 2.0f); + float offset_y = -(m_amount_y * m_step_y / 2.0f); + + for (uint x = 0; x < m_amount_x; x++) { + for (uint y = 0; y < m_amount_y; y++) { + new_positions.append(float3(x * m_step_x + offset_x, y * m_step_y + offset_y, 0.0f)); + } + } + + for (StringRef system_name : m_systems_to_emit) { + auto new_particles = interface.particle_allocator().request(system_name, new_positions.size()); + new_particles.set<float3>("Position", new_positions); + new_particles.fill<float>("Birth Time", interface.time_span().start()); + new_particles.fill<float>("Size", m_size); + + m_action.execute_from_emitter(new_particles, interface); + } +} + +using FN::MFDataType; +using FN::MFParamType; + +void CustomEmitter::emit(EmitterInterface &interface) +{ + FN::MFParamsBuilder params_builder{m_emitter_function, 1}; + + for (uint param_index : m_emitter_function.param_indices()) { + MFParamType param_type = m_emitter_function.param_type(param_index); + MFDataType data_type = param_type.data_type(); + BLI_assert(param_type.is_output()); + switch (data_type.category()) { + case MFDataType::Single: { + const FN::CPPType &type = data_type.single__cpp_type(); + void *buffer = MEM_mallocN(type.size(), __func__); + FN::GenericMutableArrayRef array{type, buffer, 1}; + params_builder.add_single_output(array); + break; + } + case MFDataType::Vector: { + const FN::CPPType &base_type = data_type.vector__cpp_base_type(); + FN::GenericVectorArray *vector_array = new FN::GenericVectorArray(base_type, 1); + params_builder.add_vector_output(*vector_array); + break; + } + } + } + + FloatInterval time_span = interface.time_span(); + FN::EmitterTimeInfoContext time_context; + time_context.begin = time_span.start(); + time_context.end = time_span.end(); + time_context.duration = time_span.size(); + time_context.step = interface.time_step(); + + FN::MFContextBuilder context_builder; + context_builder.add_global_context(m_id_data_cache); + context_builder.add_global_context(m_id_handle_lookup); + context_builder.add_global_context(time_context); + + m_emitter_function.call(BLI::IndexMask(1), params_builder, context_builder); + + int particle_count = -1; + + for (uint param_index : m_emitter_function.param_indices()) { + MFParamType param_type = m_emitter_function.param_type(param_index); + if (param_type.is_vector_output()) { + FN::GenericVectorArray &vector_array = params_builder.computed_vector_array(param_index); + FN::GenericArrayRef array = vector_array[0]; + particle_count = std::max<int>(particle_count, array.size()); + } + } + + if (particle_count == -1) { + particle_count = 1; + } + + for (StringRef system_name : m_systems_to_emit) { + auto new_particles = interface.particle_allocator().request(system_name, particle_count); + + switch (m_birth_time_mode) { + case BirthTimeModes::None: + case BirthTimeModes::End: + new_particles.fill<float>("Birth Time", time_span.end()); + break; + case BirthTimeModes::Begin: + new_particles.fill<float>("Birth Time", time_span.start()); + break; + case BirthTimeModes::Linear: { + Array<float> birth_times(new_particles.total_size()); + time_span.sample_linear(birth_times); + new_particles.set<float>("Birth Time", birth_times); + break; + } + case BirthTimeModes::Random: { + Array<float> birth_times(new_particles.total_size()); + for (uint i = 0; i < particle_count; i++) { + birth_times[i] = time_span.value_at(random_float()); + } + new_particles.set<float>("Birth Time", birth_times); + break; + } + } + + const AttributesInfo &info = new_particles.info(); + + for (uint param_index : m_emitter_function.param_indices()) { + MFParamType param_type = m_emitter_function.param_type(param_index); + StringRef attribute_name = m_attribute_names[param_index]; + if (param_type.is_vector_output()) { + FN::GenericVectorArray &vector_array = params_builder.computed_vector_array(param_index); + FN::GenericArrayRef array = vector_array[0]; + const FN::CPPType &base_type = array.type(); + if (info.has_attribute(attribute_name, base_type)) { + if (array.size() == 0) { + const void *default_buffer = new_particles.info().default_of(attribute_name); + new_particles.fill(attribute_name, base_type, default_buffer); + } + else { + new_particles.set_repeated(attribute_name, array); + } + } + } + else if (param_type.is_single_output()) { + FN::GenericMutableArrayRef array = params_builder.computed_array(param_index); + const FN::CPPType &type = array.type(); + if (info.has_attribute(attribute_name, type)) { + new_particles.fill(attribute_name, type, array[0]); + } + } + else { + BLI_assert(false); + } + } + + m_action.execute_from_emitter(new_particles, interface); + } + + for (uint param_index : m_emitter_function.param_indices()) { + MFParamType param_type = m_emitter_function.param_type(param_index); + if (param_type.is_vector_output()) { + delete ¶ms_builder.computed_vector_array(param_index); + } + else if (param_type.is_single_output()) { + FN::GenericMutableArrayRef array = params_builder.computed_array(param_index); + BLI_assert(array.size() == 1); + array.destruct_all(); + MEM_freeN(array.buffer()); + } + else { + BLI_assert(false); + } + } +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/emitters.hpp b/source/blender/simulations/bparticles/emitters.hpp new file mode 100644 index 00000000000..9ba4ba43a1a --- /dev/null +++ b/source/blender/simulations/bparticles/emitters.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include "emitter_interface.hpp" +#include "particle_action.hpp" +#include "world_state.hpp" + +#include "FN_multi_function.h" + +#include "BKE_id_data_cache.h" +#include "BKE_id_handle.h" + +namespace BParticles { + +using FN::MultiFunction; + +class SurfaceEmitter : public Emitter { + private: + ArrayRef<std::string> m_systems_to_emit; + ParticleAction &m_on_birth_action; + + Object *m_object; + VaryingFloat4x4 m_transform; + float m_rate; + + Vector<float> m_vertex_weights; + + public: + SurfaceEmitter(ArrayRef<std::string> systems_to_emit, + ParticleAction &on_birth_action, + Object *object, + VaryingFloat4x4 transform, + float rate, + Vector<float> vertex_weights) + : m_systems_to_emit(systems_to_emit), + m_on_birth_action(on_birth_action), + m_object(object), + m_transform(transform), + m_rate(rate), + m_vertex_weights(std::move(vertex_weights)) + { + } + + void emit(EmitterInterface &interface) override; +}; + +class PointEmitter : public Emitter { + private: + ArrayRef<std::string> m_systems_to_emit; + VaryingFloat3 m_position; + VaryingFloat3 m_velocity; + VaryingFloat m_size; + ParticleAction &m_action; + + public: + PointEmitter(ArrayRef<std::string> systems_to_emit, + VaryingFloat3 position, + VaryingFloat3 velocity, + VaryingFloat size, + ParticleAction &action) + : m_systems_to_emit(systems_to_emit), + m_position(position), + m_velocity(velocity), + m_size(size), + m_action(action) + { + } + + void emit(EmitterInterface &interface) override; +}; + +class InitialGridEmitter : public Emitter { + private: + ArrayRef<std::string> m_systems_to_emit; + uint m_amount_x; + uint m_amount_y; + float m_step_x; + float m_step_y; + float m_size; + ParticleAction &m_action; + + public: + InitialGridEmitter(ArrayRef<std::string> systems_to_emit, + uint amount_x, + uint amount_y, + float step_x, + float step_y, + float size, + ParticleAction &action) + : m_systems_to_emit(systems_to_emit), + m_amount_x(amount_x), + m_amount_y(amount_y), + m_step_x(step_x), + m_step_y(step_y), + m_size(size), + m_action(action) + { + } + + void emit(EmitterInterface &interface) override; +}; + +namespace BirthTimeModes { +enum Enum { + None = 0, + Begin = 1, + End = 2, + Random = 3, + Linear = 4, +}; +} + +class CustomEmitter : public Emitter { + private: + ArrayRef<std::string> m_systems_to_emit; + const MultiFunction &m_emitter_function; + Vector<std::string> m_attribute_names; + ParticleAction &m_action; + BirthTimeModes::Enum m_birth_time_mode; + const BKE::IDHandleLookup &m_id_handle_lookup; + const BKE::IDDataCache &m_id_data_cache; + + public: + CustomEmitter(ArrayRef<std::string> systems_to_emit, + const MultiFunction &emitter_function, + Vector<std::string> attribute_names, + ParticleAction &action, + BirthTimeModes::Enum birth_time_mode, + const BKE::IDHandleLookup &id_handle_lookup, + const BKE::IDDataCache &id_data_cache) + : m_systems_to_emit(systems_to_emit), + m_emitter_function(emitter_function), + m_attribute_names(std::move(attribute_names)), + m_action(action), + m_birth_time_mode(birth_time_mode), + m_id_handle_lookup(id_handle_lookup), + m_id_data_cache(id_data_cache) + { + } + + void emit(EmitterInterface &interface) override; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/event_interface.hpp b/source/blender/simulations/bparticles/event_interface.hpp new file mode 100644 index 00000000000..de46f85bf94 --- /dev/null +++ b/source/blender/simulations/bparticles/event_interface.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include "BLI_index_mask.h" + +#include "block_step_data.hpp" +#include "particle_allocator.hpp" + +namespace BParticles { + +using BLI::IndexMask; + +/** + * Interface between the Event->filter() function and the core simulation code. + */ +class EventFilterInterface : public BlockStepDataAccess { + private: + IndexMask m_mask; + ArrayRef<float> m_known_min_time_factors; + + Vector<uint> &m_filtered_pindices; + Vector<float> &m_filtered_time_factors; + + public: + EventFilterInterface(BlockStepData &step_data, + IndexMask mask, + ArrayRef<float> known_min_time_factors, + Vector<uint> &r_filtered_pindices, + Vector<float> &r_filtered_time_factors) + : BlockStepDataAccess(step_data), + m_mask(mask), + m_known_min_time_factors(known_min_time_factors), + m_filtered_pindices(r_filtered_pindices), + m_filtered_time_factors(r_filtered_time_factors) + { + } + + /** + * Return the indices that should be checked. + */ + IndexMask mask() + { + return m_mask; + } + + /** + * Mark a particle as triggered by the event at a specific point in time. + * Note: The index must increase between consecutive calls to this function. + */ + void trigger_particle(uint pindex, float time_factor) + { + BLI_assert(0.0f <= time_factor && time_factor <= 1.0f); + + if (time_factor <= m_known_min_time_factors[pindex]) { + m_filtered_pindices.append(pindex); + m_filtered_time_factors.append(time_factor); + } + } +}; + +/** + * Interface between the Event->execute() function and the core simulation code. + */ +class EventExecuteInterface : public BlockStepDataAccess { + private: + ArrayRef<uint> m_pindices; + ArrayRef<float> m_current_times; + ParticleAllocator &m_particle_allocator; + + public: + EventExecuteInterface(BlockStepData &step_data, + ArrayRef<uint> pindices, + ArrayRef<float> current_times, + ParticleAllocator &particle_allocator) + : BlockStepDataAccess(step_data), + m_pindices(pindices), + m_current_times(current_times), + m_particle_allocator(particle_allocator) + { + } + + ~EventExecuteInterface() = default; + + /** + * Access the indices that should be modified by this event. + */ + ArrayRef<uint> pindices() + { + return m_pindices; + } + + /** + * Get the time at which every particle is modified by this event. + */ + ArrayRef<float> current_times() + { + return m_current_times; + } + + ParticleAllocator &particle_allocator() + { + return m_particle_allocator; + } +}; + +/** + * An event consists of two parts. + * 1. Filter the particles that trigger the event within a specific time span. + * 2. Modify the particles that were triggered. + * + * In some cases it is necessary to pass data from the filter to the execute function (e.g. the + * normal of the surface at a collision point). So that is supported as well. Currently, only + * POD (plain-old-data / simple C structs) can be used. + */ +class Event { + public: + virtual ~Event() + { + } + + /** + * Gets a set of particles and checks which of those trigger the event. + */ + virtual void filter(EventFilterInterface &interface) = 0; + + /** + * Gets a set of particles that trigger this event and can do the following operations: + * - Change any attribute of the particles. + * - Change the remaining integrated attribute offsets of the particles. + * - Kill the particles. + * - Spawn new particles of any type. + * + * Currently, it is not supported to change the attributes of other particles, that exist + * already. However, the attributes of new particles can be changed. + */ + virtual void execute(EventExecuteInterface &interface) = 0; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/events.cpp b/source/blender/simulations/bparticles/events.cpp new file mode 100644 index 00000000000..c66179cc6f1 --- /dev/null +++ b/source/blender/simulations/bparticles/events.cpp @@ -0,0 +1,155 @@ +#include "BLI_hash.h" +#include "BLI_utildefines.h" + +#include "events.hpp" + +namespace BParticles { + +/* Age Reached Event + ******************************************/ + +void AgeReachedEvent::filter(EventFilterInterface &interface) +{ + AttributesRef attributes = interface.attributes(); + + ParticleFunctionEvaluator inputs{m_inputs_fn, interface.mask(), interface.attributes()}; + inputs.context_builder().set_buffer_cache(interface.buffer_cache()); + inputs.compute(); + + float end_time = interface.step_end_time(); + auto birth_times = attributes.get<float>("Birth Time"); + auto was_activated_before = attributes.get<bool>(m_is_triggered_attribute); + + for (uint pindex : interface.mask()) { + if (was_activated_before[pindex]) { + continue; + } + + float trigger_age = inputs.get_single<float>("Age", 0, pindex); + float birth_time = birth_times[pindex]; + float age_at_end = end_time - birth_time; + + if (age_at_end >= trigger_age) { + FloatInterval time_span = interface.time_span(pindex); + + float age_at_start = age_at_end - time_span.size(); + if (trigger_age < age_at_start) { + interface.trigger_particle(pindex, 0.0f); + } + else { + float time_factor = time_span.safe_factor_of(birth_time + trigger_age); + CLAMP(time_factor, 0.0f, 1.0f); + interface.trigger_particle(pindex, time_factor); + } + } + } +} + +void AgeReachedEvent::execute(EventExecuteInterface &interface) +{ + auto was_activated_before = interface.attributes().get<bool>(m_is_triggered_attribute); + for (uint pindex : interface.pindices()) { + was_activated_before[pindex] = true; + } + + m_action.execute_from_event(interface); +} + +/* Custom Event + ***********************************************/ + +void CustomEvent::filter(EventFilterInterface &interface) +{ + FN::EventFilterEndTimeContext end_time_context = {interface.step_end_time()}; + FN::EventFilterDurationsContext durations_context = {interface.remaining_durations()}; + + ParticleFunctionEvaluator inputs{m_inputs_fn, interface.mask(), interface.attributes()}; + FN::MFContextBuilder &context_builder = inputs.context_builder(); + context_builder.set_buffer_cache(interface.buffer_cache()); + context_builder.add_global_context(end_time_context); + context_builder.add_element_context(durations_context, + FN::MFElementContextIndices::FromDirectMapping()); + inputs.compute(); + + for (uint pindex : interface.mask()) { + bool condition = inputs.get_single<bool>("Condition", 0, pindex); + if (condition) { + float time_factor = inputs.get_single<float>("Time Factor", 1, pindex); + time_factor = std::min(std::max(time_factor, 0.0f), 1.0f); + interface.trigger_particle(pindex, time_factor); + } + } +} + +void CustomEvent::execute(EventExecuteInterface &interface) +{ + m_action.execute_from_event(interface); +} + +/* Collision Event + ***********************************************/ + +void MeshCollisionEvent::filter(EventFilterInterface &interface) +{ + AttributesRef attributes = interface.attributes(); + auto positions = attributes.get<float3>("Position"); + auto last_collision_step = attributes.get<int32_t>(m_last_collision_attribute); + auto position_offsets = interface.attribute_offsets().get<float3>("Position"); + + uint current_update_index = interface.simulation_state().time().current_update_index(); + + for (uint pindex : interface.mask()) { + if (last_collision_step[pindex] == current_update_index) { + continue; + } + + float3 world_ray_start = positions[pindex]; + float3 world_ray_direction = position_offsets[pindex]; + float3 world_ray_end = world_ray_start + world_ray_direction; + + float3 local_ray_start = m_world_to_local_begin.transform_position(world_ray_start); + float3 local_ray_end = m_world_to_local_end.transform_position(world_ray_end); + float3 local_ray_direction = local_ray_end - local_ray_start; + float local_ray_length = local_ray_direction.normalize_and_get_length(); + + auto result = this->ray_cast(local_ray_start, local_ray_direction, local_ray_length); + if (result.success) { + float time_factor = result.distance / local_ray_length; + interface.trigger_particle(pindex, time_factor); + if (float3::dot(result.normal, local_ray_direction) > 0) { + result.normal = -result.normal; + } + } + } +} + +MeshCollisionEvent::RayCastResult MeshCollisionEvent::ray_cast(float3 start, + float3 normalized_direction, + float max_distance) +{ + BVHTreeRayHit hit; + hit.dist = max_distance; + hit.index = -1; + BLI_bvhtree_ray_cast(m_bvhtree_data.tree, + start, + normalized_direction, + 0.0f, + &hit, + m_bvhtree_data.raycast_callback, + (void *)&m_bvhtree_data); + + return {hit.index >= 0, hit.index, float3(hit.no), hit.dist}; +} + +void MeshCollisionEvent::execute(EventExecuteInterface &interface) +{ + auto last_collision_step = interface.attributes().get<int32_t>(m_last_collision_attribute); + uint current_update_index = interface.simulation_state().time().current_update_index(); + + for (uint pindex : interface.pindices()) { + last_collision_step[pindex] = current_update_index; + } + m_action.execute_from_event(interface); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/events.hpp b/source/blender/simulations/bparticles/events.hpp new file mode 100644 index 00000000000..e678430a9e8 --- /dev/null +++ b/source/blender/simulations/bparticles/events.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include "actions.hpp" + +#include "BKE_bvhutils.h" + +#include "BLI_kdopbvh.h" +#include "BLI_kdtree.h" + +#include "DNA_object_types.h" + +struct Object; + +namespace BParticles { + +using BLI::float3; +using BLI::float4x4; + +class AgeReachedEvent : public Event { + private: + std::string m_is_triggered_attribute; + const ParticleFunction &m_inputs_fn; + ParticleAction &m_action; + + public: + AgeReachedEvent(StringRef is_triggered_attribute, + const ParticleFunction &inputs_fn, + ParticleAction &action) + : m_is_triggered_attribute(is_triggered_attribute), m_inputs_fn(inputs_fn), m_action(action) + { + } + + void filter(EventFilterInterface &interface) override; + void execute(EventExecuteInterface &interface) override; +}; + +class CustomEvent : public Event { + private: + const ParticleFunction &m_inputs_fn; + ParticleAction &m_action; + + public: + CustomEvent(const ParticleFunction &inputs_fn, ParticleAction &action) + : m_inputs_fn(inputs_fn), m_action(action) + { + } + + void filter(EventFilterInterface &interface) override; + void execute(EventExecuteInterface &interface) override; +}; + +class MeshCollisionEvent : public Event { + private: + std::string m_last_collision_attribute; + Object *m_object; + BVHTreeFromMesh m_bvhtree_data; + float4x4 m_local_to_world_begin; + float4x4 m_world_to_local_begin; + float4x4 m_local_to_world_end; + float4x4 m_world_to_local_end; + ParticleAction &m_action; + + struct RayCastResult { + bool success; + int index; + float3 normal; + float distance; + }; + + public: + MeshCollisionEvent(StringRef last_collision_attribute, + Object *object, + ParticleAction &action, + float4x4 local_to_world_begin, + float4x4 local_to_world_end) + : m_last_collision_attribute(last_collision_attribute), m_object(object), m_action(action) + { + BLI_assert(object->type == OB_MESH); + m_local_to_world_begin = local_to_world_begin; + m_local_to_world_end = local_to_world_end; + m_world_to_local_begin = m_local_to_world_begin.inverted__LocRotScale(); + m_world_to_local_end = m_local_to_world_end.inverted__LocRotScale(); + + BKE_bvhtree_from_mesh_get(&m_bvhtree_data, (Mesh *)object->data, BVHTREE_FROM_LOOPTRI, 2); + } + + ~MeshCollisionEvent() + { + free_bvhtree_from_mesh(&m_bvhtree_data); + } + + void filter(EventFilterInterface &interface) override; + void execute(EventExecuteInterface &interface) override; + + private: + RayCastResult ray_cast(float3 start, float3 normalized_direction, float max_distance); +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/force_interface.cpp b/source/blender/simulations/bparticles/force_interface.cpp new file mode 100644 index 00000000000..def469c58bb --- /dev/null +++ b/source/blender/simulations/bparticles/force_interface.cpp @@ -0,0 +1,4 @@ +#include "force_interface.hpp" + +namespace ForceInterface { +} diff --git a/source/blender/simulations/bparticles/force_interface.hpp b/source/blender/simulations/bparticles/force_interface.hpp new file mode 100644 index 00000000000..e5c0ec91a35 --- /dev/null +++ b/source/blender/simulations/bparticles/force_interface.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "BLI_index_mask.h" + +#include "block_step_data.hpp" + +namespace BParticles { + +using BLI::float3; +using BLI::IndexMask; + +class ForceInterface : public BlockStepDataAccess { + private: + IndexMask m_mask; + MutableArrayRef<float3> m_destination; + + public: + ForceInterface(BlockStepData &step_data, IndexMask mask, MutableArrayRef<float3> destination) + : BlockStepDataAccess(step_data), m_mask(mask), m_destination(destination) + { + } + + IndexMask mask() + { + return m_mask; + } + + MutableArrayRef<float3> combined_destination() + { + return m_destination; + } +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/forces.cpp b/source/blender/simulations/bparticles/forces.cpp new file mode 100644 index 00000000000..79a0f026008 --- /dev/null +++ b/source/blender/simulations/bparticles/forces.cpp @@ -0,0 +1,24 @@ +#include "BLI_noise.h" + +#include "forces.hpp" + +namespace BParticles { + +Force::~Force() +{ +} + +void CustomForce::add_force(ForceInterface &interface) +{ + MutableArrayRef<float3> dst = interface.combined_destination(); + + ParticleFunctionEvaluator inputs{m_inputs_fn, interface.mask(), interface.attributes()}; + inputs.context_builder().set_buffer_cache(interface.buffer_cache()); + inputs.compute(); + + for (uint pindex : interface.mask()) { + dst[pindex] += inputs.get_single<float3>("Force", 0, pindex); + } +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/forces.hpp b/source/blender/simulations/bparticles/forces.hpp new file mode 100644 index 00000000000..d2f3e9bc0b4 --- /dev/null +++ b/source/blender/simulations/bparticles/forces.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "BLI_kdopbvh.h" +#include "BLI_kdtree.h" + +#include "BKE_bvhutils.h" + +#include "DNA_object_types.h" + +#include "actions.hpp" +#include "force_interface.hpp" + +namespace BParticles { + +using BLI::float4x4; + +class Force { + public: + virtual ~Force() = 0; + virtual void add_force(ForceInterface &interface) = 0; +}; + +class CustomForce : public Force { + private: + const ParticleFunction &m_inputs_fn; + + public: + CustomForce(const ParticleFunction &inputs_fn) : m_inputs_fn(inputs_fn) + { + } + + void add_force(ForceInterface &interface) override; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/integrator.cpp b/source/blender/simulations/bparticles/integrator.cpp new file mode 100644 index 00000000000..3ab4fba5fc7 --- /dev/null +++ b/source/blender/simulations/bparticles/integrator.cpp @@ -0,0 +1,96 @@ +#include "integrator.hpp" + +namespace BParticles { + +using BLI::float3; + +ConstantVelocityIntegrator::ConstantVelocityIntegrator() +{ + FN::AttributesInfoBuilder builder; + builder.add<float3>("Position", {0, 0, 0}); + m_offset_attributes_info = BLI::make_unique<AttributesInfo>(builder); +} + +const AttributesInfo &ConstantVelocityIntegrator::offset_attributes_info() +{ + return *m_offset_attributes_info; +} + +void ConstantVelocityIntegrator::integrate(IntegratorInterface &interface) +{ + auto velocities = interface.attributes().get<float3>("Velocity"); + auto position_offsets = interface.attribute_offsets().get<float3>("Position"); + auto durations = interface.remaining_durations(); + + for (uint pindex : interface.mask()) { + position_offsets[pindex] = velocities[pindex] * durations[pindex]; + } +} + +EulerIntegrator::EulerIntegrator(ArrayRef<Force *> forces) : m_forces(forces) +{ + FN::AttributesInfoBuilder builder; + builder.add<float3>("Position", {0, 0, 0}); + builder.add<float3>("Velocity", {0, 0, 0}); + m_offset_attributes_info = BLI::make_unique<AttributesInfo>(builder); +} + +EulerIntegrator::~EulerIntegrator() +{ +} + +const AttributesInfo &EulerIntegrator::offset_attributes_info() +{ + return *m_offset_attributes_info; +} + +void EulerIntegrator::integrate(IntegratorInterface &interface) +{ + MutableAttributesRef r_offsets = interface.attribute_offsets(); + ArrayRef<float> durations = interface.remaining_durations(); + + Array<float3> combined_force(interface.array_size()); + this->compute_combined_force(interface, combined_force); + + auto last_velocities = interface.attributes().get<float3>("Velocity"); + + auto position_offsets = r_offsets.get<float3>("Position"); + auto velocity_offsets = r_offsets.get<float3>("Velocity"); + this->compute_offsets(interface.mask(), + durations, + last_velocities, + combined_force, + position_offsets, + velocity_offsets); +} + +BLI_NOINLINE void EulerIntegrator::compute_combined_force(IntegratorInterface &interface, + MutableArrayRef<float3> r_force) +{ + r_force.fill({0, 0, 0}); + + ForceInterface force_interface(interface.step_data(), interface.mask(), r_force); + + for (Force *force : m_forces) { + force->add_force(force_interface); + } +} + +BLI_NOINLINE void EulerIntegrator::compute_offsets(IndexMask mask, + ArrayRef<float> durations, + ArrayRef<float3> last_velocities, + ArrayRef<float3> combined_force, + MutableArrayRef<float3> r_position_offsets, + MutableArrayRef<float3> r_velocity_offsets) +{ + for (uint pindex : mask) { + float mass = 1.0f; + float duration = durations[pindex]; + + r_velocity_offsets[pindex] = duration * combined_force[pindex] / mass; + r_position_offsets[pindex] = duration * + (last_velocities[pindex] + r_velocity_offsets[pindex] * 0.5f); + } +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/integrator.hpp b/source/blender/simulations/bparticles/integrator.hpp new file mode 100644 index 00000000000..3a2f1cb6041 --- /dev/null +++ b/source/blender/simulations/bparticles/integrator.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "forces.hpp" +#include "integrator_interface.hpp" + +namespace BParticles { + +class ConstantVelocityIntegrator : public Integrator { + std::unique_ptr<AttributesInfo> m_offset_attributes_info; + + public: + ConstantVelocityIntegrator(); + + const AttributesInfo &offset_attributes_info() override; + void integrate(IntegratorInterface &interface) override; +}; + +class EulerIntegrator : public Integrator { + private: + std::unique_ptr<AttributesInfo> m_offset_attributes_info; + Vector<Force *> m_forces; + + public: + EulerIntegrator(ArrayRef<Force *> forces); + ~EulerIntegrator(); + + const AttributesInfo &offset_attributes_info() override; + void integrate(IntegratorInterface &interface) override; + + private: + void compute_combined_force(IntegratorInterface &interface, MutableArrayRef<float3> r_force); + + void compute_offsets(IndexMask mask, + ArrayRef<float> durations, + ArrayRef<float3> last_velocities, + ArrayRef<float3> combined_force, + MutableArrayRef<float3> r_position_offsets, + MutableArrayRef<float3> r_velocity_offsets); +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/integrator_interface.hpp b/source/blender/simulations/bparticles/integrator_interface.hpp new file mode 100644 index 00000000000..95caf5cdb88 --- /dev/null +++ b/source/blender/simulations/bparticles/integrator_interface.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "BLI_index_mask.h" + +#include "block_step_data.hpp" + +namespace BParticles { + +using BLI::IndexMask; + +/** + * Interface between the Integrator->integrate() function and the core simulation code. + */ +class IntegratorInterface : public BlockStepDataAccess { + private: + IndexMask m_mask; + + public: + IntegratorInterface(BlockStepData &step_data, IndexMask mask) + : BlockStepDataAccess(step_data), m_mask(mask) + { + } + + IndexMask mask() + { + return m_mask; + } +}; + +/** + * The integrator is the core of the particle system. It's main task is to determine how the + * simulation would go if there were no events. + */ +class Integrator { + public: + virtual ~Integrator() + { + } + + /** + * Specify which attributes are integrated (usually Position and Velocity). + */ + virtual const AttributesInfo &offset_attributes_info() = 0; + + /** + * Compute the offsets for all integrated attributes. Those are not applied immediately, because + * there might be events that modify the attributes within a time step. + */ + virtual void integrate(IntegratorInterface &interface) = 0; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/node_frontend.cpp b/source/blender/simulations/bparticles/node_frontend.cpp new file mode 100644 index 00000000000..da336d654fd --- /dev/null +++ b/source/blender/simulations/bparticles/node_frontend.cpp @@ -0,0 +1,1200 @@ +#include "BLI_color.h" +#include "BLI_multi_map.h" +#include "BLI_set.h" +#include "BLI_timeit.h" + +#include "BKE_deform.h" +#include "BKE_id_data_cache.h" +#include "BKE_surface_hook.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "FN_generic_tuple.h" +#include "FN_multi_function_common_contexts.h" +#include "FN_multi_function_dependencies.h" +#include "FN_multi_functions.h" +#include "FN_node_tree.h" +#include "FN_node_tree_multi_function_network_generation.h" + +#include "emitters.hpp" +#include "events.hpp" +#include "integrator.hpp" +#include "node_frontend.hpp" +#include "offset_handlers.hpp" +#include "simulate.hpp" + +namespace BParticles { + +using BKE::IDDataCache; +using BKE::IDHandleLookup; +using BKE::ObjectIDHandle; +using BLI::destruct_ptr; +using BLI::MultiMap; +using BLI::ResourceCollector; +using BLI::rgba_f; +using BLI::ScopedVector; +using BLI::Set; +using BLI::StringMultiMap; +using FN::AttributesInfoBuilder; +using FN::CPPType; +using FN::FGroupInput; +using FN::FInputSocket; +using FN::FNode; +using FN::FOutputSocket; +using FN::FSocket; +using FN::FunctionTreeMFNetwork; +using FN::MFInputSocket; +using FN::MFOutputSocket; +using FN::MultiFunction; +using FN::NamedGenericTupleRef; + +static StringRef particle_system_idname = "fn_ParticleSystemNode"; +static StringRef combine_influences_idname = "fn_CombineInfluencesNode"; + +class FunctionTreeData; +class InfluencesCollector; +class FSocketActionBuilder; + +using ActionParserCallback = std::function<void(FSocketActionBuilder &builder)>; +extern StringMap<ActionParserCallback, BLI::RawAllocator> action_parsers_map; + +class InfluencesCollector { + public: + Vector<Emitter *> m_emitters; + StringMultiMap<Force *> m_forces; + StringMultiMap<Event *> m_events; + StringMultiMap<OffsetHandler *> m_offset_handlers; + StringMap<AttributesInfoBuilder *> m_attributes; +}; + +class FunctionTreeData { + private: + /* Keep this at the beginning, so that it is destructed last. */ + ResourceCollector m_resources; + FunctionTreeMFNetwork &m_function_tree_data_graph; + IDDataCache m_id_data_cache; + IDHandleLookup m_id_handle_lookup; + + public: + FunctionTreeData(FunctionTreeMFNetwork &function_tree_data) + : m_function_tree_data_graph(function_tree_data) + { + FN::add_ids_used_by_nodes(m_id_handle_lookup, function_tree_data.function_tree()); + } + + const FunctionTree &function_tree() + { + return m_function_tree_data_graph.function_tree(); + } + + const FN::MFNetwork &data_graph() + { + return m_function_tree_data_graph.network(); + } + + const FunctionTreeMFNetwork &function_tree_data_graph() + { + return m_function_tree_data_graph; + } + + IDHandleLookup &id_handle_lookup() + { + return m_id_handle_lookup; + } + + IDDataCache &id_data_cache() + { + return m_id_data_cache; + } + + template<typename T, typename... Args> T &construct(const char *name, Args &&... args) + { + void *buffer = m_resources.allocate(sizeof(T), alignof(T)); + T *value = new (buffer) T(std::forward<Args>(args)...); + m_resources.add(BLI::destruct_ptr<T>(value), name); + return *value; + } + + ParticleFunction *particle_function_for_all_inputs(const FNode &fnode) + { + Vector<const MFInputSocket *> sockets_to_compute; + Vector<std::string> names_to_compute; + for (const FInputSocket *fsocket : fnode.inputs()) { + if (m_function_tree_data_graph.is_mapped(*fsocket)) { + sockets_to_compute.append(&m_function_tree_data_graph.lookup_dummy_socket(*fsocket)); + names_to_compute.append(fsocket->name()); + } + } + + return this->particle_function_for_sockets(std::move(sockets_to_compute), + std::move(names_to_compute)); + } + + ParticleFunction *particle_function_for_inputs(const FNode &fnode, ArrayRef<uint> input_indices) + { + Vector<const MFInputSocket *> sockets_to_compute; + Vector<std::string> names_to_compute; + for (uint i : input_indices) { + const MFInputSocket &socket = m_function_tree_data_graph.lookup_dummy_socket(fnode.input(i)); + sockets_to_compute.append(&socket); + names_to_compute.append(fnode.input(i).name()); + } + + return this->particle_function_for_sockets(std::move(sockets_to_compute), + std::move(names_to_compute)); + } + + ParticleFunction *particle_function_for_sockets(Vector<const MFInputSocket *> sockets_to_compute, + Vector<std::string> names_to_compute) + { + + const MultiFunction &fn = this->construct<FN::MF_EvaluateNetwork>( + "Evaluate Network", Vector<const MFOutputSocket *>(), std::move(sockets_to_compute)); + ParticleFunction &particle_fn = this->construct<ParticleFunction>( + "Particle Function", fn, std::move(names_to_compute), m_id_data_cache, m_id_handle_lookup); + + return &particle_fn; + } + + Optional<NamedGenericTupleRef> compute_inputs(const FNode &fnode, ArrayRef<uint> input_indices) + { + const MultiFunction *fn = this->function_for_inputs(fnode, input_indices); + if (fn == nullptr) { + return {}; + } + if (fn->uses_element_context<FN::ParticleAttributesContext>()) { + std::cout << "Inputs may not depend on particle attributes: " << fnode.name() << "\n"; + return {}; + } + + Vector<const CPPType *> computed_types; + for (uint i : input_indices) { + FN::MFDataType data_type = + m_function_tree_data_graph.lookup_dummy_socket(fnode.input(i)).data_type(); + BLI_assert(data_type.is_single()); + computed_types.append(&data_type.single__cpp_type()); + } + + auto &tuple_info = this->construct<FN::GenericTupleInfo>(__func__, std::move(computed_types)); + void *tuple_buffer = m_resources.allocate(tuple_info.size_of_data_and_init(), + tuple_info.alignment()); + FN::GenericTupleRef tuple = FN::GenericTupleRef::FromAlignedBuffer(tuple_info, tuple_buffer); + tuple.set_all_uninitialized(); + + FN::MFParamsBuilder params_builder(*fn, 1); + FN::MFContextBuilder context_builder; + context_builder.add_global_context(m_id_handle_lookup); + context_builder.add_global_context(m_id_data_cache); + + for (uint i = 0; i < input_indices.size(); i++) { + params_builder.add_single_output( + FN::GenericMutableArrayRef(tuple.info().type_at_index(i), tuple.element_ptr(i), 1)); + } + fn->call(BLI::IndexMask(1), params_builder, context_builder); + tuple.set_all_initialized(); + + Vector<std::string> computed_names; + for (uint i : input_indices) { + computed_names.append(fnode.input(i).name()); + } + + auto &name_provider = this->construct<FN::CustomGenericTupleNameProvider>( + __func__, std::move(computed_names)); + NamedGenericTupleRef named_tuple_ref{tuple, name_provider}; + + return named_tuple_ref; + } + + Optional<NamedGenericTupleRef> compute_all_data_inputs(const FNode &fnode) + { + ScopedVector<uint> data_input_indices; + for (uint i : fnode.inputs().index_range()) { + if (m_function_tree_data_graph.is_mapped(fnode.input(i))) { + data_input_indices.append(i); + } + } + + return this->compute_inputs(fnode, data_input_indices); + } + + ArrayRef<std::string> find_target_system_names(const FOutputSocket &output_fsocket) + { + VectorSet<const FNode *> system_fnodes; + this->find_target_system_nodes__recursive(output_fsocket, system_fnodes); + + auto &system_names = this->construct<Vector<std::string>>(__func__); + for (const FNode *fnode : system_fnodes) { + system_names.append(fnode->name()); + } + + return system_names; + } + + ParticleAction *build_action(InfluencesCollector &collector, + const FInputSocket &start, + ArrayRef<std::string> system_names); + + ParticleAction &build_action_list(InfluencesCollector &collector, + const FNode &start_fnode, + StringRef name, + ArrayRef<std::string> system_names) + { + Vector<const FInputSocket *> execute_sockets = this->find_execute_sockets(start_fnode, name); + Vector<ParticleAction *> actions; + for (const FInputSocket *socket : execute_sockets) { + ParticleAction *action = this->build_action(collector, *socket, system_names); + if (action != nullptr) { + actions.append(action); + } + } + ParticleAction &sequence = this->construct<ActionSequence>(__func__, std::move(actions)); + return sequence; + } + + const FN::MultiFunction *function_for_inputs(const FNode &fnode, ArrayRef<uint> input_indices) + { + Vector<const MFInputSocket *> sockets_to_compute; + for (uint index : input_indices) { + sockets_to_compute.append( + &m_function_tree_data_graph.lookup_dummy_socket(fnode.input(index))); + } + + if (m_function_tree_data_graph.network().find_dummy_dependencies(sockets_to_compute).size() > + 0) { + return nullptr; + } + + auto fn = BLI::make_unique<FN::MF_EvaluateNetwork>(ArrayRef<const MFOutputSocket *>(), + sockets_to_compute); + const FN::MultiFunction *fn_ptr = fn.get(); + m_resources.add(std::move(fn), __func__); + return fn_ptr; + } + + bool try_add_attribute(InfluencesCollector &collector, + ArrayRef<std::string> system_names, + StringRef name, + const CPPType &type, + const void *default_value) + { + bool collides_with_existing = false; + for (StringRef system_name : system_names) { + AttributesInfoBuilder *attributes = collector.m_attributes.lookup(system_name); + collides_with_existing = collides_with_existing || + attributes->name_and_type_collide_with_existing(name, type); + } + + if (collides_with_existing) { + return false; + } + + for (StringRef system_name : system_names) { + collector.m_attributes.lookup(system_name)->add(name, type, default_value); + } + + return true; + } + + private: + Vector<const FNode *> find_target_system_nodes(const FOutputSocket &fsocket) + { + VectorSet<const FNode *> type_nodes; + find_target_system_nodes__recursive(fsocket, type_nodes); + return Vector<const FNode *>(type_nodes); + } + + void find_target_system_nodes__recursive(const FOutputSocket &output_fsocket, + VectorSet<const FNode *> &r_nodes) + { + for (const FInputSocket *connected : output_fsocket.linked_sockets()) { + const FNode &connected_fnode = connected->node(); + if (connected_fnode.idname() == particle_system_idname) { + r_nodes.add(&connected_fnode); + } + else if (connected_fnode.idname() == combine_influences_idname) { + find_target_system_nodes__recursive(connected_fnode.output(0), r_nodes); + } + } + } + + Vector<const FInputSocket *> find_execute_sockets(const FNode &fnode, StringRef name_prefix) + { + int first_index = -1; + for (const FInputSocket *fsocket : fnode.inputs()) { + if (fsocket->name() == name_prefix) { + first_index = fsocket->index(); + break; + } + } + BLI_assert(first_index >= 0); + + Vector<const FInputSocket *> execute_sockets; + for (const FInputSocket *fsocket : fnode.inputs().drop_front(first_index)) { + if (fsocket->idname() == "fn_OperatorSocket") { + break; + } + else { + execute_sockets.append(fsocket); + } + } + + return execute_sockets; + } +}; + +class FSocketActionBuilder { + private: + InfluencesCollector &m_influences_collector; + FunctionTreeData &m_function_tree_data; + const FSocket &m_execute_fsocket; + ArrayRef<std::string> m_system_names; + ParticleAction *m_built_action = nullptr; + + public: + FSocketActionBuilder(InfluencesCollector &influences_collector, + FunctionTreeData &function_tree_data, + const FSocket &execute_fsocket, + ArrayRef<std::string> system_names) + : m_influences_collector(influences_collector), + m_function_tree_data(function_tree_data), + m_execute_fsocket(execute_fsocket), + m_system_names(system_names) + { + } + + ParticleAction *built_action() + { + return m_built_action; + } + + const FSocket &fsocket() const + { + return m_execute_fsocket; + } + + ArrayRef<std::string> system_names() const + { + return m_system_names; + } + + const CPPType &base_type_of(const FInputSocket &fsocket) const + { + return m_function_tree_data.function_tree_data_graph() + .lookup_dummy_socket(fsocket) + .data_type() + .single__cpp_type(); + } + + template<typename T, typename... Args> T &construct(Args &&... args) + { + return m_function_tree_data.construct<T>("construct action", std::forward<Args>(args)...); + } + + template<typename T, typename... Args> T &set_constructed(Args &&... args) + { + BLI_STATIC_ASSERT((std::is_base_of<ParticleAction, T>::value), ""); + T &action = this->construct<T>(std::forward<Args>(args)...); + this->set(action); + return action; + } + + void set(ParticleAction &action) + { + m_built_action = &action; + } + + ParticleFunction *particle_function_for_all_inputs() + { + return m_function_tree_data.particle_function_for_all_inputs(m_execute_fsocket.node()); + } + + ParticleFunction *particle_function_for_inputs(ArrayRef<uint> input_indices) + { + return m_function_tree_data.particle_function_for_inputs(m_execute_fsocket.node(), + input_indices); + } + + const MultiFunction *function_for_inputs(ArrayRef<uint> input_indices) + { + return m_function_tree_data.function_for_inputs(m_execute_fsocket.node(), input_indices); + } + + FN::MFDataType data_type_of_input(const FInputSocket &fsocket) + { + return m_function_tree_data.function_tree_data_graph() + .lookup_dummy_socket(fsocket) + .data_type(); + } + + PointerRNA *node_rna() + { + return m_execute_fsocket.node().rna(); + } + + ParticleAction &build_input_action_list(StringRef name, ArrayRef<std::string> system_names) + { + return m_function_tree_data.build_action_list( + m_influences_collector, m_execute_fsocket.node(), name, system_names); + } + + ArrayRef<std::string> find_system_target_names(uint output_index, StringRef expected_name) + { + const FOutputSocket &fsocket = m_execute_fsocket.node().output(output_index, expected_name); + return m_function_tree_data.find_target_system_names(fsocket); + } + + Optional<NamedGenericTupleRef> compute_all_data_inputs() + { + return m_function_tree_data.compute_all_data_inputs(m_execute_fsocket.node()); + } + + Optional<NamedGenericTupleRef> compute_inputs(ArrayRef<uint> input_indices) + { + return m_function_tree_data.compute_inputs(m_execute_fsocket.node(), input_indices); + } + + template<typename T> + bool try_add_attribute_to_affected_particles(StringRef name, T default_value) + { + return this->try_add_attribute_to_affected_particles( + name, FN::CPP_TYPE<T>(), (const void *)&default_value); + } + + bool try_add_attribute_to_affected_particles(StringRef name, + const CPPType &type, + const void *default_value = nullptr) + { + /* Add attribute to all particle systems for now. */ + ScopedVector<std::string> system_names; + m_influences_collector.m_attributes.foreach_key( + [&](StringRef name) { system_names.append(name); }); + + return m_function_tree_data.try_add_attribute( + m_influences_collector, system_names, name, type, default_value); + } + + bool try_add_attribute(ArrayRef<std::string> system_names, + StringRef name, + const CPPType &type, + const void *default_value = nullptr) + { + return m_function_tree_data.try_add_attribute( + m_influences_collector, system_names, name, type, default_value); + } + + IDHandleLookup &id_handle_lookup() + { + return m_function_tree_data.id_handle_lookup(); + } + + BKE::IDDataCache &id_data_cache() + { + return m_function_tree_data.id_data_cache(); + } +}; + +ParticleAction *FunctionTreeData::build_action(InfluencesCollector &collector, + const FInputSocket &start, + ArrayRef<std::string> system_names) +{ + if (start.linked_sockets().size() != 1) { + return nullptr; + } + + const FSocket &execute_socket = *start.linked_sockets()[0]; + if (execute_socket.idname() != "fn_ExecuteSocket") { + return nullptr; + } + + ActionParserCallback *parser = action_parsers_map.lookup_ptr(execute_socket.node().idname()); + if (parser == nullptr) { + std::cout << "Expected to find parser for: " << execute_socket.node().idname() << "\n"; + return nullptr; + } + + FSocketActionBuilder builder{collector, *this, execute_socket, system_names}; + (*parser)(builder); + + return builder.built_action(); +} + +static void ACTION_spawn(FSocketActionBuilder &builder) +{ + const FNode &fnode = builder.fsocket().node(); + const FInputSocket &first_execute_socket = *fnode.input_with_name_prefix("Execute on Birth"); + ArrayRef<const FInputSocket *> data_inputs = fnode.inputs().take_front( + first_execute_socket.index()); + ArrayRef<uint> input_indices = IndexRange(data_inputs.size()).as_array_ref(); + const ParticleFunction *inputs_fn = builder.particle_function_for_inputs(input_indices); + if (inputs_fn == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_system_target_names(1, "Spawn System"); + if (builder.system_names().intersects__linear_search(system_names)) { + std::cout << "Warning: cannot spawn particles in same system yet.\n"; + return; + } + + Vector<std::string> attribute_names; + for (const FInputSocket *fsocket : data_inputs) { + StringRef attribute_name = fsocket->name(); + attribute_names.append(attribute_name); + const CPPType *attribute_type = nullptr; + + FN::MFDataType data_type = builder.data_type_of_input(*fsocket); + if (data_type.is_single()) { + attribute_type = &data_type.single__cpp_type(); + } + else if (data_type.is_vector()) { + attribute_type = &data_type.vector__cpp_base_type(); + } + else { + BLI_assert(false); + } + + builder.try_add_attribute(system_names, attribute_name, *attribute_type); + } + + ParticleAction &action = builder.build_input_action_list("Execute on Birth", system_names); + + builder.set_constructed<SpawnParticlesAction>( + system_names, *inputs_fn, std::move(attribute_names), action); +} + +static void ACTION_condition(FSocketActionBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + ParticleAction &action_true = builder.build_input_action_list("Execute If True", + builder.system_names()); + ParticleAction &action_false = builder.build_input_action_list("Execute If False", + builder.system_names()); + builder.set_constructed<ConditionAction>(*inputs_fn, action_true, action_false); +} + +static void ACTION_set_attribute(FSocketActionBuilder &builder) +{ + Optional<NamedGenericTupleRef> values = builder.compute_inputs({0}); + if (!values.has_value()) { + return; + } + + ParticleFunction *inputs_fn = builder.particle_function_for_inputs({1}); + if (inputs_fn == nullptr) { + return; + } + + const CPPType &attribute_type = builder.base_type_of(builder.fsocket().node().input(1)); + std::string attribute_name = values->relocate_out<std::string>(0, "Name"); + + bool attribute_added = builder.try_add_attribute_to_affected_particles(attribute_name, + attribute_type); + if (!attribute_added) { + return; + } + + builder.set_constructed<SetAttributeAction>(attribute_name, attribute_type, *inputs_fn); +} + +static void ACTION_multi_execute(FSocketActionBuilder &builder) +{ + ParticleAction &action = builder.build_input_action_list("Execute", builder.system_names()); + builder.set(action); +} + +class FNodeInfluencesBuilder { + private: + InfluencesCollector &m_influences_collector; + FunctionTreeData &m_function_tree_data; + WorldTransition &m_world_transition; + const FNode &m_fnode; + + public: + FNodeInfluencesBuilder(InfluencesCollector &influences_collector, + FunctionTreeData &function_tree_data, + WorldTransition &world_transition, + const FNode &fnode) + : m_influences_collector(influences_collector), + m_function_tree_data(function_tree_data), + m_world_transition(world_transition), + m_fnode(fnode) + { + } + + const FNode &fnode() const + { + return m_fnode; + } + + Optional<NamedGenericTupleRef> compute_all_data_inputs() + { + return m_function_tree_data.compute_all_data_inputs(m_fnode); + } + + Optional<NamedGenericTupleRef> compute_inputs(ArrayRef<uint> input_indices) + { + return m_function_tree_data.compute_inputs(m_fnode, input_indices); + } + + const MultiFunction *function_for_inputs(ArrayRef<uint> input_indices) + { + return m_function_tree_data.function_for_inputs(m_fnode, input_indices); + } + + ParticleAction &build_action_list(StringRef name, ArrayRef<std::string> system_names) + { + return m_function_tree_data.build_action_list( + m_influences_collector, m_fnode, name, system_names); + } + + ArrayRef<std::string> find_target_system_names(uint output_index, StringRef expected_name) + { + return m_function_tree_data.find_target_system_names( + m_fnode.output(output_index, expected_name)); + } + + WorldTransition &world_transition() + { + return m_world_transition; + } + + template<typename T, typename... Args> T &construct(Args &&... args) + { + return m_function_tree_data.construct<T>(__func__, std::forward<Args>(args)...); + } + + void add_emitter(Emitter &emitter) + { + m_influences_collector.m_emitters.append(&emitter); + } + + void add_force(ArrayRef<std::string> system_names, Force &force) + { + for (StringRef system_name : system_names) { + m_influences_collector.m_forces.add(system_name, &force); + } + } + + void add_event(ArrayRef<std::string> system_names, Event &event) + { + for (StringRef system_name : system_names) { + m_influences_collector.m_events.add(system_name, &event); + } + } + + void add_offset_handler(ArrayRef<std::string> system_names, OffsetHandler &offset_handler) + { + for (StringRef system_name : system_names) { + m_influences_collector.m_offset_handlers.add(system_name, &offset_handler); + } + } + + std::string node_identifier() + { + std::stringstream ss; + ss << "private/node"; + for (const FN::FParentNode *parent = m_fnode.parent(); parent; parent = parent->parent()) { + ss << "/" << parent->vnode().name(); + } + ss << "/" << m_fnode.name(); + + std::string identifier = ss.str(); + return identifier; + } + + IDHandleLookup &id_handle_lookup() + { + return m_function_tree_data.id_handle_lookup(); + } + + BKE::IDDataCache &id_data_cache() + { + return m_function_tree_data.id_data_cache(); + } + + PointerRNA *node_rna() + { + return m_fnode.rna(); + } + + ParticleFunction *particle_function_for_all_inputs() + { + return m_function_tree_data.particle_function_for_all_inputs(m_fnode); + } + + FN::MFDataType data_type_of_input(const FInputSocket &fsocket) + { + return m_function_tree_data.function_tree_data_graph() + .lookup_dummy_socket(fsocket) + .data_type(); + } + + template<typename T> + bool try_add_attribute(ArrayRef<std::string> system_names, StringRef name, T default_value) + { + return this->try_add_attribute( + system_names, name, FN::CPP_TYPE<T>(), (const void *)&default_value); + } + + bool try_add_attribute(ArrayRef<std::string> system_names, + StringRef name, + const CPPType &type, + const void *default_value = nullptr) + { + return m_function_tree_data.try_add_attribute( + m_influences_collector, system_names, name, type, default_value); + } +}; + +using ParseNodeCallback = std::function<void(FNodeInfluencesBuilder &builder)>; + +static void PARSE_point_emitter(FNodeInfluencesBuilder &builder) +{ + Optional<NamedGenericTupleRef> inputs = builder.compute_all_data_inputs(); + if (!inputs.has_value()) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Emitter"); + ParticleAction &action = builder.build_action_list("Execute on Birth", system_names); + + std::string identifier = builder.node_identifier(); + + WorldTransition &world_transition = builder.world_transition(); + VaryingFloat3 position = world_transition.update_float3( + identifier, "Position", inputs->get<float3>(0, "Position")); + VaryingFloat3 velocity = world_transition.update_float3( + identifier, "Velocity", inputs->get<float3>(1, "Velocity")); + VaryingFloat size = world_transition.update_float( + identifier, "Size", inputs->get<float>(2, "Size")); + + Emitter &emitter = builder.construct<PointEmitter>( + std::move(system_names), position, velocity, size, action); + builder.add_emitter(emitter); +} + +static void PARSE_custom_emitter(FNodeInfluencesBuilder &builder) +{ + const FNode &fnode = builder.fnode(); + const FInputSocket &first_execute_socket = *fnode.input_with_name_prefix("Execute on Birth"); + ArrayRef<const FInputSocket *> data_inputs = fnode.inputs().take_front( + first_execute_socket.index()); + ArrayRef<uint> input_indices = IndexRange(data_inputs.size()).as_array_ref(); + const MultiFunction *emitter_function = builder.function_for_inputs(input_indices); + if (emitter_function == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Emitter"); + + Vector<std::string> attribute_names; + for (const FInputSocket *socket : data_inputs) { + StringRef attribute_name = socket->name(); + attribute_names.append(attribute_name); + const CPPType *attribute_type = nullptr; + + FN::MFDataType data_type = builder.data_type_of_input(*socket); + if (data_type.is_single()) { + attribute_type = &data_type.single__cpp_type(); + } + else if (data_type.is_vector()) { + attribute_type = &data_type.vector__cpp_base_type(); + } + else { + BLI_assert(false); + } + + builder.try_add_attribute(system_names, attribute_name, *attribute_type); + } + + ParticleAction &action = builder.build_action_list("Execute on Birth", system_names); + BirthTimeModes::Enum birth_time_mode = (BirthTimeModes::Enum)RNA_enum_get(builder.node_rna(), + "birth_time_mode"); + + Emitter &emitter = builder.construct<CustomEmitter>(system_names, + *emitter_function, + std::move(attribute_names), + action, + birth_time_mode, + builder.id_handle_lookup(), + builder.id_data_cache()); + builder.add_emitter(emitter); +} + +static Vector<float> compute_emitter_vertex_weights(PointerRNA *node_rna, + NamedGenericTupleRef inputs, + Object *object) +{ + uint density_mode = RNA_enum_get(node_rna, "density_mode"); + + Mesh *mesh = (Mesh *)object->data; + Vector<float> vertex_weights(mesh->totvert); + + if (density_mode == 0) { + /* Mode: 'UNIFORM' */ + vertex_weights.fill(1.0f); + } + else if (density_mode == 1) { + /* Mode: 'VERTEX_WEIGHTS' */ + std::string group_name = inputs.relocate_out<std::string>(2, "Density Group"); + + MDeformVert *vertices = mesh->dvert; + int group_index = BKE_object_defgroup_name_index(object, group_name.c_str()); + if (group_index == -1 || vertices == nullptr) { + vertex_weights.fill(0); + } + else { + for (uint i = 0; i < mesh->totvert; i++) { + vertex_weights[i] = BKE_defvert_find_weight(vertices + i, group_index); + } + } + } + + return vertex_weights; +} + +static void PARSE_mesh_emitter(FNodeInfluencesBuilder &builder) +{ + Optional<NamedGenericTupleRef> inputs = builder.compute_all_data_inputs(); + if (!inputs.has_value()) { + return; + } + + ObjectIDHandle object_handle = inputs->relocate_out<ObjectIDHandle>(0, "Object"); + Object *object = builder.id_handle_lookup().lookup(object_handle); + if (object == nullptr || object->type != OB_MESH) { + return; + } + + auto vertex_weights = compute_emitter_vertex_weights(builder.node_rna(), *inputs, object); + + VaryingFloat4x4 transform = builder.world_transition().update_float4x4( + object->id.name, "obmat", object->obmat); + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Emitter"); + ParticleAction &on_birth_action = builder.build_action_list("Execute on Birth", system_names); + + Emitter &emitter = builder.construct<SurfaceEmitter>(system_names, + on_birth_action, + object, + transform, + inputs->get<float>(1, "Rate"), + std::move(vertex_weights)); + builder.add_emitter(emitter); +} + +static void PARSE_custom_force(FNodeInfluencesBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Force"); + CustomForce &force = builder.construct<CustomForce>(*inputs_fn); + builder.add_force(system_names, force); +} + +static void PARSE_age_reached_event(FNodeInfluencesBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Event"); + std::string is_triggered_attribute = builder.node_identifier(); + + bool attribute_added = builder.try_add_attribute<bool>( + system_names, is_triggered_attribute, false); + if (!attribute_added) { + return; + } + + ParticleAction &action = builder.build_action_list("Execute on Event", system_names); + Event &event = builder.construct<AgeReachedEvent>(is_triggered_attribute, *inputs_fn, action); + builder.add_event(system_names, event); +} + +static void PARSE_trails(FNodeInfluencesBuilder &builder) +{ + ArrayRef<std::string> main_system_names = builder.find_target_system_names(0, "Main System"); + ArrayRef<std::string> trail_system_names = builder.find_target_system_names(1, "Trail System"); + + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + if (main_system_names.intersects__linear_search(trail_system_names)) { + return; + } + + ParticleAction &action = builder.build_action_list("Execute on Birth", trail_system_names); + OffsetHandler &offset_handler = builder.construct<CreateTrailHandler>( + trail_system_names, *inputs_fn, action); + builder.add_offset_handler(main_system_names, offset_handler); +} + +static void PARSE_initial_grid_emitter(FNodeInfluencesBuilder &builder) +{ + Optional<NamedGenericTupleRef> inputs = builder.compute_all_data_inputs(); + if (!inputs.has_value()) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Emitter"); + ParticleAction &action = builder.build_action_list("Execute on Birth", system_names); + + Emitter &emitter = builder.construct<InitialGridEmitter>( + std::move(system_names), + std::max(0, inputs->get<int>(0, "Amount X")), + std::max(0, inputs->get<int>(1, "Amount Y")), + inputs->get<float>(2, "Step X"), + inputs->get<float>(3, "Step Y"), + inputs->get<float>(4, "Size"), + action); + builder.add_emitter(emitter); +} + +static void PARSE_mesh_collision(FNodeInfluencesBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + Optional<NamedGenericTupleRef> inputs = builder.compute_inputs({0}); + if (!inputs.has_value()) { + return; + } + + ObjectIDHandle object_handle = inputs->relocate_out<ObjectIDHandle>(0, "Object"); + Object *object = builder.id_handle_lookup().lookup(object_handle); + if (object == nullptr || object->type != OB_MESH) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Event"); + ParticleAction &action = builder.build_action_list("Execute on Event", system_names); + + float4x4 local_to_world_end = object->obmat; + float4x4 local_to_world_begin = + builder.world_transition().update_float4x4(object->id.name, "obmat", object->obmat).start; + + std::string last_collision_attribute = builder.node_identifier(); + bool attribute_added = builder.try_add_attribute<int32_t>( + system_names, last_collision_attribute, -1); + if (!attribute_added) { + return; + } + + Event &event = builder.construct<MeshCollisionEvent>( + last_collision_attribute, object, action, local_to_world_begin, local_to_world_end); + builder.add_event(system_names, event); +} + +static void PARSE_size_over_time(FNodeInfluencesBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Influence"); + OffsetHandler &offset_handler = builder.construct<SizeOverTimeHandler>(*inputs_fn); + builder.add_offset_handler(system_names, offset_handler); +} + +static void PARSE_custom_event(FNodeInfluencesBuilder &builder) +{ + ParticleFunction *inputs_fn = builder.particle_function_for_all_inputs(); + if (inputs_fn == nullptr) { + return; + } + + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Event"); + ParticleAction &action = builder.build_action_list("Execute on Event", system_names); + + Event &event = builder.construct<CustomEvent>(*inputs_fn, action); + builder.add_event(system_names, event); +} + +static void PARSE_always_execute(FNodeInfluencesBuilder &builder) +{ + ArrayRef<std::string> system_names = builder.find_target_system_names(0, "Influence"); + ParticleAction &action = builder.build_action_list("Execute", system_names); + + OffsetHandler &offset_handler = builder.construct<AlwaysExecuteHandler>(action); + builder.add_offset_handler(system_names, offset_handler); +} + +static StringMap<ParseNodeCallback, BLI::RawAllocator> create_node_parsers_map() +{ + StringMap<ParseNodeCallback, BLI::RawAllocator> map; + map.add_new("fn_PointEmitterNode", PARSE_point_emitter); + map.add_new("fn_CustomEmitterNode", PARSE_custom_emitter); + map.add_new("fn_MeshEmitterNode", PARSE_mesh_emitter); + map.add_new("fn_AgeReachedEventNode", PARSE_age_reached_event); + map.add_new("fn_ParticleTrailsNode", PARSE_trails); + map.add_new("fn_InitialGridEmitterNode", PARSE_initial_grid_emitter); + map.add_new("fn_MeshCollisionEventNode", PARSE_mesh_collision); + map.add_new("fn_SizeOverTimeNode", PARSE_size_over_time); + map.add_new("fn_CustomEventNode", PARSE_custom_event); + map.add_new("fn_AlwaysExecuteNode", PARSE_always_execute); + map.add_new("fn_ForceNode", PARSE_custom_force); + return map; +} + +static StringMap<ActionParserCallback, BLI::RawAllocator> create_action_parsers_map() +{ + StringMap<ActionParserCallback, BLI::RawAllocator> map; + map.add_new("fn_SpawnParticlesNode", ACTION_spawn); + map.add_new("fn_ParticleConditionNode", ACTION_condition); + map.add_new("fn_SetParticleAttributeNode", ACTION_set_attribute); + map.add_new("fn_MultiExecuteNode", ACTION_multi_execute); + return map; +} + +static StringMap<ParseNodeCallback, BLI::RawAllocator> node_parsers_map = + create_node_parsers_map(); +StringMap<ActionParserCallback, BLI::RawAllocator> action_parsers_map = + create_action_parsers_map(); + +static void collect_influences(FunctionTreeData &function_tree_data, + WorldTransition &world_transition, + Vector<std::string> &r_system_names, + InfluencesCollector &collector, + StringMap<Integrator *> &r_integrators) +{ + SCOPED_TIMER(__func__); + + for (const FNode *fnode : + function_tree_data.function_tree().nodes_with_idname(particle_system_idname)) { + StringRef name = fnode->name(); + r_system_names.append(name); + + AttributesInfoBuilder *attributes = new AttributesInfoBuilder(); + attributes->add<bool>("Dead", false); + attributes->add<int32_t>("ID", 0); + attributes->add<float>("Birth Time", 0); + attributes->add<float3>("Position", float3(0, 0, 0)); + attributes->add<float3>("Velocity", float3(0, 0, 0)); + attributes->add<float>("Size", 0.05f); + attributes->add<rgba_f>("Color", rgba_f(1, 1, 1, 1)); + attributes->add<BKE::SurfaceHook>("Emit Hook", {}); + + collector.m_attributes.add_new(name, attributes); + } + + for (const FNode *fnode : function_tree_data.function_tree().all_nodes()) { + StringRef idname = fnode->idname(); + ParseNodeCallback *callback = node_parsers_map.lookup_ptr(idname); + if (callback != nullptr) { + FNodeInfluencesBuilder builder{collector, function_tree_data, world_transition, *fnode}; + (*callback)(builder); + } + } + + for (std::string &system_name : r_system_names) { + ArrayRef<Force *> forces = collector.m_forces.lookup_default(system_name); + EulerIntegrator &integrator = function_tree_data.construct<EulerIntegrator>("integrator", + forces); + + r_integrators.add_new(system_name, &integrator); + } +} + +class NodeTreeStepSimulator : public StepSimulator { + private: + FN::BTreeVTreeMap m_function_trees; + FunctionTree m_function_tree; + + public: + NodeTreeStepSimulator(bNodeTree *btree) : m_function_tree(btree, m_function_trees) + { + // m_function_tree.to_dot__clipboard(); + } + + void simulate(SimulationState &simulation_state) override + { + WorldState &old_world_state = simulation_state.world(); + WorldState new_world_state; + WorldTransition world_transition = {old_world_state, new_world_state}; + + ParticlesState &particles_state = simulation_state.particles(); + + ResourceCollector resources; + std::unique_ptr<FunctionTreeMFNetwork> data_graph = + FN::MFGeneration::generate_node_tree_multi_function_network(m_function_tree, resources); + if (data_graph.get() == nullptr) { + return; + } + FunctionTreeData function_tree_data(*data_graph); + + Vector<std::string> system_names; + StringMap<Integrator *> integrators; + InfluencesCollector influences_collector; + collect_influences( + function_tree_data, world_transition, system_names, influences_collector, integrators); + + auto &containers = particles_state.particle_containers(); + + StringMap<ParticleSystemInfo> systems_to_simulate; + for (std::string name : system_names) { + AttributesInfoBuilder &system_attributes = *influences_collector.m_attributes.lookup(name); + + /* Keep old attributes. */ + ParticleSet *particles = containers.lookup_default(name, nullptr); + if (particles != nullptr) { + system_attributes.add(particles->attributes_info()); + } + + this->ensure_particle_container_exist_and_has_attributes( + particles_state, name, system_attributes); + + ParticleSystemInfo type_info = { + integrators.lookup(name), + influences_collector.m_events.lookup_default(name), + influences_collector.m_offset_handlers.lookup_default(name), + }; + systems_to_simulate.add_new(name, type_info); + } + + simulate_particles(simulation_state, influences_collector.m_emitters, systems_to_simulate); + + influences_collector.m_attributes.foreach_value( + [](AttributesInfoBuilder *builder) { delete builder; }); + + simulation_state.world() = std::move(new_world_state); + } + + private: + void ensure_particle_container_exist_and_has_attributes( + ParticlesState &particles_state, + StringRef name, + const AttributesInfoBuilder &attributes_info_builder) + { + auto &containers = particles_state.particle_containers(); + ParticleSet *particles = containers.lookup_default(name, nullptr); + AttributesInfo *attributes_info = new AttributesInfo(attributes_info_builder); + if (particles == nullptr) { + ParticleSet *new_particle_set = new ParticleSet(*attributes_info, true); + containers.add_new(name, new_particle_set); + } + else { + particles->update_attributes(attributes_info); + } + } +}; + +std::unique_ptr<StepSimulator> simulator_from_node_tree(bNodeTree *btree) +{ + return std::unique_ptr<StepSimulator>(new NodeTreeStepSimulator(btree)); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/node_frontend.hpp b/source/blender/simulations/bparticles/node_frontend.hpp new file mode 100644 index 00000000000..f17512984d7 --- /dev/null +++ b/source/blender/simulations/bparticles/node_frontend.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "FN_node_tree.h" + +#include "step_simulator.hpp" +#include "world_state.hpp" + +namespace BParticles { + +using FN::FunctionTree; + +std::unique_ptr<StepSimulator> simulator_from_node_tree(bNodeTree *btree); + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/offset_handler_interface.hpp b/source/blender/simulations/bparticles/offset_handler_interface.hpp new file mode 100644 index 00000000000..3b78356d92d --- /dev/null +++ b/source/blender/simulations/bparticles/offset_handler_interface.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "BLI_index_mask.h" + +#include "block_step_data.hpp" +#include "particle_allocator.hpp" + +namespace BParticles { + +using BLI::IndexMask; + +class OffsetHandlerInterface : public BlockStepDataAccess { + private: + IndexMask m_mask; + ArrayRef<float> m_time_factors; + ParticleAllocator &m_particle_allocator; + + public: + OffsetHandlerInterface(BlockStepData &step_data, + IndexMask mask, + ArrayRef<float> time_factors, + ParticleAllocator &particle_allocator) + : BlockStepDataAccess(step_data), + m_mask(mask), + m_time_factors(time_factors), + m_particle_allocator(particle_allocator) + { + } + + ArrayRef<uint> mask() + { + return m_mask; + } + + ArrayRef<float> time_factors() + { + return m_time_factors; + } + + ParticleAllocator &particle_allocator() + { + return m_particle_allocator; + } +}; + +class OffsetHandler { + public: + virtual ~OffsetHandler() + { + } + + virtual void execute(OffsetHandlerInterface &interface) = 0; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/offset_handlers.cpp b/source/blender/simulations/bparticles/offset_handlers.cpp new file mode 100644 index 00000000000..3d66afa63b4 --- /dev/null +++ b/source/blender/simulations/bparticles/offset_handlers.cpp @@ -0,0 +1,82 @@ +#include "offset_handlers.hpp" +#include "BLI_color.h" + +namespace BParticles { + +using BLI::rgba_f; + +void CreateTrailHandler::execute(OffsetHandlerInterface &interface) +{ + auto positions = interface.attributes().get<float3>("Position"); + auto position_offsets = interface.attribute_offsets().get<float3>("Position"); + auto colors = interface.attributes().get<rgba_f>("Color"); + + ParticleFunctionEvaluator inputs{m_inputs_fn, interface.mask(), interface.attributes()}; + inputs.compute(); + + Vector<float3> new_positions; + Vector<rgba_f> new_colors; + Vector<float> new_birth_times; + for (uint pindex : interface.mask()) { + float rate = inputs.get_single<float>("Rate", 0, pindex); + if (rate <= 0.0f) { + continue; + } + + FloatInterval time_span = interface.time_span(pindex); + + rgba_f color = colors[pindex]; + + float factor_start, factor_step; + time_span.uniform_sample_range(rate, factor_start, factor_step); + + float3 total_offset = position_offsets[pindex] * interface.time_factors()[pindex]; + for (float factor = factor_start; factor < 1.0f; factor += factor_step) { + float time = time_span.value_at(factor); + new_positions.append(positions[pindex] + total_offset * factor); + new_birth_times.append(time); + new_colors.append(color); + } + } + + for (StringRef system_name : m_systems_to_emit) { + auto new_particles = interface.particle_allocator().request(system_name, new_positions.size()); + new_particles.set<float3>("Position", new_positions); + new_particles.set<float>("Birth Time", new_birth_times); + new_particles.set<rgba_f>("Color", new_colors); + + m_on_birth_action.execute_for_new_particles(new_particles, interface); + } +} + +void SizeOverTimeHandler::execute(OffsetHandlerInterface &interface) +{ + auto birth_times = interface.attributes().get<float>("Birth Time"); + auto sizes = interface.attributes().get<float>("Size"); + + ParticleFunctionEvaluator inputs{m_inputs_fn, interface.mask(), interface.attributes()}; + inputs.compute(); + + for (uint pindex : interface.mask()) { + float final_size = inputs.get_single<float>("Final Size", 0, pindex); + float final_age = inputs.get_single<float>("Final Age", 1, pindex); + + FloatInterval time_span = interface.time_span(pindex); + float age = time_span.start() - birth_times[pindex]; + float time_until_end = final_age - age; + if (time_until_end <= 0.0f) { + continue; + } + + float factor = std::min(time_span.size() / time_until_end, 1.0f); + float new_size = final_size * factor + sizes[pindex] * (1.0f - factor); + sizes[pindex] = new_size; + } +} + +void AlwaysExecuteHandler::execute(OffsetHandlerInterface &interface) +{ + m_action.execute_from_offset_handler(interface); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/offset_handlers.hpp b/source/blender/simulations/bparticles/offset_handlers.hpp new file mode 100644 index 00000000000..bc64f252b6d --- /dev/null +++ b/source/blender/simulations/bparticles/offset_handlers.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "offset_handler_interface.hpp" +#include "particle_function.hpp" + +namespace BParticles { + +class CreateTrailHandler : public OffsetHandler { + private: + ArrayRef<std::string> m_systems_to_emit; + const ParticleFunction &m_inputs_fn; + ParticleAction &m_on_birth_action; + + public: + CreateTrailHandler(ArrayRef<std::string> systems_to_emit, + const ParticleFunction &inputs_fn, + ParticleAction &on_birth_action) + : m_systems_to_emit(systems_to_emit), + m_inputs_fn(inputs_fn), + m_on_birth_action(on_birth_action) + { + } + + void execute(OffsetHandlerInterface &interface) override; +}; + +class SizeOverTimeHandler : public OffsetHandler { + private: + const ParticleFunction &m_inputs_fn; + + public: + SizeOverTimeHandler(const ParticleFunction &inputs_fn) : m_inputs_fn(inputs_fn) + { + } + + void execute(OffsetHandlerInterface &interface) override; +}; + +class AlwaysExecuteHandler : public OffsetHandler { + private: + ParticleAction &m_action; + + public: + AlwaysExecuteHandler(ParticleAction &action) : m_action(action) + { + } + + void execute(OffsetHandlerInterface &interface) override; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_action.cpp b/source/blender/simulations/bparticles/particle_action.cpp new file mode 100644 index 00000000000..83dd7dc1ec5 --- /dev/null +++ b/source/blender/simulations/bparticles/particle_action.cpp @@ -0,0 +1,133 @@ +#include "particle_action.hpp" + +BLI_CREATE_CLASS_ID(BParticles::ParticleCurrentTimesContext) +BLI_CREATE_CLASS_ID(BParticles::ParticleIntegratedOffsets) +BLI_CREATE_CLASS_ID(BParticles::ParticleRemainingTimeInStep) + +namespace BParticles { + +ParticleAction::~ParticleAction() +{ +} + +void ParticleAction::execute_from_emitter(AttributesRefGroup &new_particles, + EmitterInterface &emitter_interface) +{ + BufferCache buffer_cache; + std::array<BLI::class_id_t, 1> context_ids = {BLI::get_class_id<ParticleCurrentTimesContext>()}; + + for (MutableAttributesRef attributes : new_particles) { + ParticleCurrentTimesContext current_times_context; + current_times_context.current_times = attributes.get<float>("Birth Time"); + + ParticleActionContext context(emitter_interface.particle_allocator(), + IndexMask(attributes.size()), + attributes, + buffer_cache, + context_ids, + {(void *)¤t_times_context}); + this->execute(context); + } +} + +void ParticleAction::execute_for_new_particles(AttributesRefGroup &new_particles, + ParticleActionContext &parent_context) +{ + std::array<BLI::class_id_t, 1> context_ids = {BLI::get_class_id<ParticleCurrentTimesContext>()}; + + for (MutableAttributesRef attributes : new_particles) { + ParticleCurrentTimesContext current_times_context; + current_times_context.current_times = attributes.get<float>("Birth Time"); + + ParticleActionContext context(parent_context.particle_allocator(), + IndexMask(attributes.size()), + attributes, + parent_context.buffer_cache(), + context_ids, + {(void *)¤t_times_context}); + this->execute(context); + } +} + +void ParticleAction::execute_for_new_particles(AttributesRefGroup &new_particles, + OffsetHandlerInterface &offset_handler_interface) +{ + std::array<BLI::class_id_t, 1> context_ids = {BLI::get_class_id<ParticleCurrentTimesContext>()}; + + for (MutableAttributesRef attributes : new_particles) { + ParticleCurrentTimesContext current_times_context; + current_times_context.current_times = attributes.get<float>("Birth Time"); + + ParticleActionContext context(offset_handler_interface.particle_allocator(), + IndexMask(attributes.size()), + attributes, + offset_handler_interface.buffer_cache(), + context_ids, + {(void *)¤t_times_context}); + this->execute(context); + } +} + +void ParticleAction::execute_from_event(EventExecuteInterface &event_interface) +{ + ParticleCurrentTimesContext current_times_context; + current_times_context.current_times = event_interface.current_times(); + ParticleIntegratedOffsets offsets_context = {event_interface.attribute_offsets()}; + ParticleRemainingTimeInStep remaining_time_context; + remaining_time_context.remaining_times = event_interface.remaining_durations(); + + std::array<BLI::class_id_t, 3> context_ids = {BLI::get_class_id<ParticleCurrentTimesContext>(), + BLI::get_class_id<ParticleIntegratedOffsets>(), + BLI::get_class_id<ParticleRemainingTimeInStep>()}; + std::array<void *, 3> contexts = { + (void *)¤t_times_context, (void *)&offsets_context, (void *)&remaining_time_context}; + + ParticleActionContext context(event_interface.particle_allocator(), + event_interface.pindices(), + event_interface.attributes(), + event_interface.buffer_cache(), + context_ids, + contexts); + this->execute(context); +} + +void ParticleAction::execute_for_subset(IndexMask mask, ParticleActionContext &parent_context) +{ + ParticleActionContext context(parent_context.particle_allocator(), + mask, + parent_context.attributes(), + parent_context.buffer_cache(), + parent_context.custom_context_ids(), + parent_context.custom_contexts()); + this->execute(context); +} + +void ParticleAction::execute_from_offset_handler(OffsetHandlerInterface &offset_handler_interface) +{ + Array<float> current_times(offset_handler_interface.array_size()); + for (uint pindex : offset_handler_interface.mask()) { + current_times[pindex] = offset_handler_interface.time_span(pindex).start(); + } + + ParticleCurrentTimesContext current_times_context; + current_times_context.current_times = current_times; + ParticleIntegratedOffsets offsets_context = {offset_handler_interface.attribute_offsets()}; + ParticleRemainingTimeInStep remaining_time_context; + remaining_time_context.remaining_times = offset_handler_interface.remaining_durations(); + + std::array<BLI::class_id_t, 3> context_ids = {BLI::get_class_id<ParticleCurrentTimesContext>(), + BLI::get_class_id<ParticleIntegratedOffsets>(), + BLI::get_class_id<ParticleRemainingTimeInStep>()}; + std::array<void *, 3> contexts = { + (void *)¤t_times_context, (void *)&offsets_context, (void *)&remaining_time_context}; + + ParticleActionContext context(offset_handler_interface.particle_allocator(), + offset_handler_interface.mask(), + offset_handler_interface.attributes(), + offset_handler_interface.buffer_cache(), + context_ids, + contexts); + this->execute(context); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_action.hpp b/source/blender/simulations/bparticles/particle_action.hpp new file mode 100644 index 00000000000..a7909f5353d --- /dev/null +++ b/source/blender/simulations/bparticles/particle_action.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include "BLI_index_mask.h" +#include "BLI_static_class_ids.h" + +#include "emitter_interface.hpp" +#include "event_interface.hpp" +#include "offset_handler_interface.hpp" +#include "particle_allocator.hpp" + +namespace BParticles { + +using BLI::IndexMask; + +class ParticleActionContext { + private: + ParticleAllocator &m_particle_allocator; + IndexMask m_mask; + MutableAttributesRef m_attributes; + BufferCache &m_buffer_cache; + + ArrayRef<BLI::class_id_t> m_custom_context_ids; + ArrayRef<void *> m_custom_contexts; + + public: + ParticleActionContext(ParticleAllocator &particle_allocator, + IndexMask mask, + MutableAttributesRef attributes, + BufferCache &buffer_cache, + ArrayRef<BLI::class_id_t> custom_context_ids, + ArrayRef<void *> custom_contexts) + : m_particle_allocator(particle_allocator), + m_mask(mask), + m_attributes(attributes), + m_buffer_cache(buffer_cache), + m_custom_context_ids(custom_context_ids), + m_custom_contexts(custom_contexts) + { + BLI::assert_same_size(m_custom_context_ids, m_custom_contexts); + } + + ArrayRef<BLI::class_id_t> custom_context_ids() const + { + return m_custom_context_ids; + } + + ArrayRef<void *> custom_contexts() const + { + return m_custom_contexts; + } + + ParticleAllocator &particle_allocator() + { + return m_particle_allocator; + } + + IndexMask mask() const + { + return m_mask; + } + + MutableAttributesRef attributes() + { + return m_attributes; + } + + template<typename T> T *try_find() const + { + BLI::class_id_t context_id = BLI::get_class_id<T>(); + int index = m_custom_context_ids.first_index_try(context_id); + if (index >= 0) { + return reinterpret_cast<T *>(m_custom_contexts[index]); + } + else { + return nullptr; + } + } + + BufferCache &buffer_cache() + { + return m_buffer_cache; + } +}; + +class ParticleAction { + public: + virtual ~ParticleAction(); + + virtual void execute(ParticleActionContext &context) = 0; + + void execute_from_emitter(AttributesRefGroup &new_particles, + EmitterInterface &emitter_interface); + void execute_for_new_particles(AttributesRefGroup &new_particles, + ParticleActionContext &parent_context); + void execute_for_new_particles(AttributesRefGroup &new_particles, + OffsetHandlerInterface &offset_handler_interface); + void execute_from_event(EventExecuteInterface &event_interface); + void execute_for_subset(IndexMask mask, ParticleActionContext &parent_context); + void execute_from_offset_handler(OffsetHandlerInterface &offset_handler_interface); +}; + +struct ParticleCurrentTimesContext { + ArrayRef<float> current_times; +}; + +struct ParticleIntegratedOffsets { + MutableAttributesRef offsets; +}; + +struct ParticleRemainingTimeInStep { + ArrayRef<float> remaining_times; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_allocator.cpp b/source/blender/simulations/bparticles/particle_allocator.cpp new file mode 100644 index 00000000000..ae4abf1cc4d --- /dev/null +++ b/source/blender/simulations/bparticles/particle_allocator.cpp @@ -0,0 +1,56 @@ +#include "particle_allocator.hpp" + +namespace BParticles { + +ParticleAllocator::ParticleAllocator(ParticlesState &state) : m_state(state) +{ +} + +void ParticleAllocator::initialize_new_particles(AttributesRefGroup &attributes_group) +{ + const AttributesInfo &info = attributes_group.info(); + + for (MutableAttributesRef attributes : attributes_group) { + for (uint i : info.indices()) { + StringRef attribute_name = info.name_of(i); + const void *default_value = info.default_of(attribute_name); + attributes.get(i).fill__uninitialized(default_value); + } + + MutableArrayRef<int32_t> particle_ids = attributes.get<int32_t>("ID"); + IndexRange new_ids = m_state.get_new_particle_ids(attributes.size()); + BLI::assert_same_size(particle_ids, new_ids); + for (uint i = 0; i < new_ids.size(); i++) { + particle_ids[i] = new_ids[i]; + } + } +} + +AttributesRefGroup ParticleAllocator::request(StringRef particle_system_name, uint size) +{ + ParticleSet &main_set = m_state.particle_container(particle_system_name); + const AttributesInfo &attributes_info = main_set.attributes_info(); + + ParticleSet *particles = new ParticleSet(attributes_info, false); + particles->reserve(size); + particles->increase_size_without_realloc(size); + MutableAttributesRef attributes = particles->attributes(); + + { + std::lock_guard<std::mutex> lock(m_request_mutex); + m_allocated_particles.add(particle_system_name, particles); + } + + Vector<ArrayRef<void *>> buffers; + Vector<IndexRange> ranges; + buffers.append(attributes.internal_buffers()); + ranges.append(attributes.internal_range()); + + AttributesRefGroup attributes_group(attributes_info, std::move(buffers), std::move(ranges)); + + this->initialize_new_particles(attributes_group); + + return attributes_group; +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_allocator.hpp b/source/blender/simulations/bparticles/particle_allocator.hpp new file mode 100644 index 00000000000..304d5e0d5a0 --- /dev/null +++ b/source/blender/simulations/bparticles/particle_allocator.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <mutex> + +#include "BLI_string_multi_map.h" + +#include "particles_state.hpp" + +namespace BParticles { + +using BLI::StringMultiMap; +using FN::AttributesRefGroup; + +class ParticleAllocator : BLI::NonCopyable, BLI::NonMovable { + private: + ParticlesState &m_state; + StringMultiMap<ParticleSet *> m_allocated_particles; + std::mutex m_request_mutex; + + public: + ParticleAllocator(ParticlesState &state); + + /** + * Access all particles that have been allocated by this allocator. + */ + StringMultiMap<ParticleSet *> allocated_particles(); + + /** + * Get memory buffers for new particles. + */ + AttributesRefGroup request(StringRef particle_system_name, uint size); + + private: + void initialize_new_particles(AttributesRefGroup &attributes_group); +}; + +/* ParticleAllocator inline functions + ********************************************/ + +inline StringMultiMap<ParticleSet *> ParticleAllocator::allocated_particles() +{ + return m_allocated_particles; +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_function.cpp b/source/blender/simulations/bparticles/particle_function.cpp new file mode 100644 index 00000000000..d6ac4c44ce1 --- /dev/null +++ b/source/blender/simulations/bparticles/particle_function.cpp @@ -0,0 +1,95 @@ +#include "FN_multi_function_common_contexts.h" + +#include "particle_function.hpp" + +namespace BParticles { + +using FN::CPPType; +using FN::IndexMask; +using FN::MFContextBuilder; +using FN::MFDataType; +using FN::MFParamsBuilder; +using FN::MFParamType; + +ParticleFunction::ParticleFunction(const MultiFunction &fn, + Vector<std::string> computed_names, + const BKE::IDDataCache &id_data_cache, + const BKE::IDHandleLookup &id_handle_lookup) + : m_fn(fn), + m_computed_names(computed_names), + m_id_data_cache(id_data_cache), + m_id_handle_lookup(id_handle_lookup) +{ + uint single_count = 0; + uint vector_count = 0; + + for (uint param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + BLI_assert(param_type.is_output()); + switch (param_type.data_type().category()) { + case MFDataType::Single: { + m_index_mapping.append(single_count++); + break; + } + case MFDataType::Vector: { + m_index_mapping.append(vector_count++); + break; + } + } + } +} + +ParticleFunctionEvaluator::~ParticleFunctionEvaluator() +{ + BLI_assert(m_is_computed); + for (GenericVectorArray *vector_array : m_computed_vector_arrays) { + delete vector_array; + } + for (GenericMutableArrayRef array : m_computed_arrays) { + array.destruct_indices(m_mask.indices()); + MEM_freeN(array.buffer()); + } +} + +void ParticleFunctionEvaluator::compute() +{ + BLI_assert(!m_is_computed); + + uint array_size = m_mask.min_array_size(); + + FN::ParticleAttributesContext attributes_context = {m_particle_attributes}; + m_context_builder.add_element_context(attributes_context, + FN::MFElementContextIndices::FromDirectMapping()); + m_context_builder.add_global_context(m_particle_fn.m_id_data_cache); + m_context_builder.add_global_context(m_particle_fn.m_id_handle_lookup); + + const MultiFunction &fn = m_particle_fn.fn(); + MFParamsBuilder params_builder(fn, array_size); + for (uint param_index : fn.param_indices()) { + MFParamType param_type = fn.param_type(param_index); + MFDataType data_type = param_type.data_type(); + BLI_assert(param_type.is_output()); + switch (data_type.category()) { + case MFDataType::Single: { + const CPPType &type = data_type.single__cpp_type(); + void *buffer = MEM_mallocN_aligned(array_size * type.size(), type.alignment(), __func__); + GenericMutableArrayRef array{type, buffer, array_size}; + params_builder.add_single_output(array); + m_computed_arrays.append(array); + break; + } + case MFDataType::Vector: { + const CPPType &base_type = data_type.vector__cpp_base_type(); + GenericVectorArray *vector_array = new GenericVectorArray(base_type, array_size); + params_builder.add_vector_output(*vector_array); + m_computed_vector_arrays.append(vector_array); + break; + } + } + } + + fn.call(m_mask, params_builder, m_context_builder); + m_is_computed = true; +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_function.hpp b/source/blender/simulations/bparticles/particle_function.hpp new file mode 100644 index 00000000000..ef36b119cfe --- /dev/null +++ b/source/blender/simulations/bparticles/particle_function.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "BLI_array_cxx.h" + +#include "force_interface.hpp" +#include "particle_action.hpp" + +#include "FN_multi_function.h" +#include "FN_multi_function_common_contexts.h" + +#include "BKE_id_data_cache.h" + +namespace BParticles { + +using BLI::ArrayRef; +using BLI::Optional; +using BLI::Vector; +using FN::GenericArrayRef; +using FN::GenericMutableArrayRef; +using FN::GenericVectorArray; +using FN::MultiFunction; + +class ParticleFunction { + private: + const MultiFunction &m_fn; + Vector<std::string> m_computed_names; + Vector<uint> m_index_mapping; + + const BKE::IDDataCache &m_id_data_cache; + const BKE::IDHandleLookup &m_id_handle_lookup; + + friend class ParticleFunctionEvaluator; + + public: + ParticleFunction(const MultiFunction &fn, + Vector<std::string> computed_names, + const BKE::IDDataCache &id_data_cache, + const BKE::IDHandleLookup &id_handle_lookup); + + const MultiFunction &fn() const + { + return m_fn; + } +}; + +class ParticleFunctionEvaluator { + private: + const ParticleFunction &m_particle_fn; + IndexMask m_mask; + AttributesRef m_particle_attributes; + bool m_is_computed = false; + + FN::MFContextBuilder m_context_builder; + + Vector<GenericVectorArray *> m_computed_vector_arrays; + Vector<GenericMutableArrayRef> m_computed_arrays; + + public: + ParticleFunctionEvaluator(const ParticleFunction &particle_fn, + IndexMask mask, + AttributesRef particle_attributes) + : m_particle_fn(particle_fn), m_mask(mask), m_particle_attributes(particle_attributes) + { + } + + ~ParticleFunctionEvaluator(); + + FN::MFContextBuilder &context_builder() + { + return m_context_builder; + } + + void compute(); + + /* Access computed values + *********************************************/ + + const void *get_single(StringRef expected_name, uint param_index, uint pindex) + { + BLI_assert(m_is_computed); + UNUSED_VARS_NDEBUG(expected_name); +#ifdef DEBUG + StringRef actual_name = m_particle_fn.m_computed_names[param_index]; + BLI_assert(expected_name == actual_name); +#endif + uint corrected_index = m_particle_fn.m_index_mapping[param_index]; + return m_computed_arrays[corrected_index][pindex]; + } + + template<typename T> const T &get_single(StringRef expected_name, uint param_index, uint pindex) + { + BLI_assert(m_is_computed); + UNUSED_VARS_NDEBUG(expected_name); +#ifdef DEBUG + StringRef actual_name = m_particle_fn.m_computed_names[param_index]; + BLI_assert(expected_name == actual_name); +#endif + uint corrected_index = m_particle_fn.m_index_mapping[param_index]; + ArrayRef<T> array = m_computed_arrays[corrected_index].as_typed_ref<T>(); + return array[pindex]; + } + + template<typename T> + ArrayRef<T> get_vector(StringRef expected_name, uint param_index, uint pindex) + { + BLI_assert(m_is_computed); + UNUSED_VARS_NDEBUG(expected_name); +#ifdef DEBUG + StringRef actual_name = m_particle_fn.m_computed_names[param_index]; + BLI_assert(expected_name == actual_name); +#endif + uint corrected_index = m_particle_fn.m_index_mapping[param_index]; + GenericVectorArray &vector_array = *m_computed_vector_arrays[corrected_index]; + return vector_array[pindex].as_typed_ref<T>(); + } + + GenericVectorArray &computed_vector_array(uint param_index) + { + BLI_assert(m_is_computed); + uint corrected_index = m_particle_fn.m_index_mapping[param_index]; + return *m_computed_vector_arrays[corrected_index]; + } + + GenericArrayRef computed_array(uint param_index) + { + BLI_assert(m_is_computed); + uint corrected_index = m_particle_fn.m_index_mapping[param_index]; + return m_computed_arrays[corrected_index]; + } +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_set.cpp b/source/blender/simulations/bparticles/particle_set.cpp new file mode 100644 index 00000000000..6e16c7e12a9 --- /dev/null +++ b/source/blender/simulations/bparticles/particle_set.cpp @@ -0,0 +1,90 @@ +#include "particle_set.hpp" + +namespace BParticles { + +ParticleSet::ParticleSet(const AttributesInfo &attributes_info, bool own_attributes_info) + : m_attributes_info(&attributes_info), + m_attribute_buffers(attributes_info.size(), nullptr), + m_size(0), + m_capacity(0), + m_own_attributes_info(own_attributes_info) +{ +} + +ParticleSet::~ParticleSet() +{ + for (uint i : m_attributes_info->indices()) { + const CPPType &type = m_attributes_info->type_of(i); + void *buffer = m_attribute_buffers[i]; + + if (buffer != nullptr) { + type.destruct_n(m_attribute_buffers[i], m_size); + MEM_freeN(buffer); + } + } + + if (m_own_attributes_info) { + delete m_attributes_info; + } +} + +void ParticleSet::update_attributes(const AttributesInfo *new_attributes_info) +{ + FN::AttributesInfoDiff diff{*m_attributes_info, *new_attributes_info}; + + Array<void *> new_buffers(diff.new_buffer_amount()); + diff.update(m_capacity, m_size, m_attribute_buffers, new_buffers); + + m_attribute_buffers = std::move(new_buffers); + + if (m_own_attributes_info) { + delete m_attributes_info; + } + m_attributes_info = new_attributes_info; +} + +void ParticleSet::destruct_and_reorder(IndexMask indices_to_destruct) +{ + this->attributes().destruct_and_reorder(indices_to_destruct); + m_size = m_size - indices_to_destruct.size(); +} + +void ParticleSet::add_particles(ParticleSet &particles) +{ + BLI_assert(m_attributes_info == particles.m_attributes_info); + + uint required_size = m_size + particles.size(); + if (required_size > m_capacity) { + this->realloc_particle_attributes(required_size); + } + + MutableAttributesRef dst{ + *m_attributes_info, m_attribute_buffers, IndexRange(m_size, particles.size())}; + MutableAttributesRef::RelocateUninitialized(particles.attributes(), dst); + m_size = required_size; +} + +void ParticleSet::realloc_particle_attributes(uint min_size) +{ + if (min_size <= m_capacity) { + return; + } + + uint new_capacity = power_of_2_max_u(min_size); + + for (uint index : m_attributes_info->indices()) { + const CPPType &type = m_attributes_info->type_of(index); + void *new_buffer = MEM_mallocN_aligned(type.size() * new_capacity, type.alignment(), __func__); + + void *old_buffer = m_attribute_buffers[index]; + if (old_buffer != nullptr) { + type.relocate_to_uninitialized_n(old_buffer, new_buffer, m_size); + MEM_freeN(old_buffer); + } + + m_attribute_buffers[index] = new_buffer; + } + m_capacity = new_capacity; +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particle_set.hpp b/source/blender/simulations/bparticles/particle_set.hpp new file mode 100644 index 00000000000..73128ed6b3e --- /dev/null +++ b/source/blender/simulations/bparticles/particle_set.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "FN_attributes_ref.h" + +namespace BParticles { + +using BLI::Array; +using BLI::IndexMask; +using BLI::IndexRange; +using BLI::Vector; +using FN::AttributesInfo; +using FN::AttributesInfoBuilder; +using FN::AttributesRef; +using FN::CPPType; +using FN::MutableAttributesRef; + +class ParticleSet : BLI::NonCopyable, BLI::NonMovable { + private: + const AttributesInfo *m_attributes_info; + Array<void *> m_attribute_buffers; + uint m_size; + uint m_capacity; + bool m_own_attributes_info; + + public: + ParticleSet(const AttributesInfo &attributes_info, bool own_attributes_info); + ~ParticleSet(); + + const AttributesInfo &attributes_info() const + { + return *m_attributes_info; + } + + MutableAttributesRef attributes() + { + return MutableAttributesRef(*m_attributes_info, m_attribute_buffers, m_size); + } + + MutableAttributesRef attributes_all() + { + return MutableAttributesRef(*m_attributes_info, m_attribute_buffers, m_capacity); + } + + uint size() const + { + return m_size; + } + + uint remaining_size() const + { + return m_capacity - m_size; + } + + void increase_size_without_realloc(uint amount) + { + BLI_assert(m_size + amount <= m_capacity); + m_size += amount; + } + + void reserve(uint amount) + { + this->realloc_particle_attributes(std::max(amount, m_capacity)); + } + + void add_particles(ParticleSet &particles); + + void update_attributes(const AttributesInfo *new_attributes_info); + void destruct_and_reorder(IndexMask indices_to_destruct); + + friend bool operator==(const ParticleSet &a, const ParticleSet &b) + { + return &a == &b; + } + + private: + void realloc_particle_attributes(uint min_size); +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particles_state.cpp b/source/blender/simulations/bparticles/particles_state.cpp new file mode 100644 index 00000000000..284cf251db2 --- /dev/null +++ b/source/blender/simulations/bparticles/particles_state.cpp @@ -0,0 +1,10 @@ +#include "particles_state.hpp" + +namespace BParticles { + +ParticlesState::~ParticlesState() +{ + m_container_by_id.foreach_value([](ParticleSet *particles) { delete particles; }); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/particles_state.hpp b/source/blender/simulations/bparticles/particles_state.hpp new file mode 100644 index 00000000000..b1ba2cdf7c0 --- /dev/null +++ b/source/blender/simulations/bparticles/particles_state.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include <atomic> + +#include "particle_set.hpp" + +namespace BParticles { + +using BLI::ArrayRef; +using BLI::IndexRange; +using BLI::Map; +using BLI::MutableArrayRef; +using BLI::StringMap; +using BLI::StringRef; +using BLI::StringRefNull; +using BLI::Vector; +using BLI::VectorSet; +using FN::AttributesInfo; +using FN::AttributesRef; +using FN::MutableAttributesRef; + +class ParticlesState { + private: + StringMap<ParticleSet *> m_container_by_id; + std::atomic<uint> m_next_id; + + public: + ParticlesState() : m_next_id(0) + { + } + ParticlesState(ParticlesState &other) = delete; + ~ParticlesState(); + + /** + * Access the mapping from particle system names to their corresponding containers. + */ + StringMap<ParticleSet *> &particle_containers(); + + /** + * Get the container corresponding to a particle system name. + * Asserts when the container does not exist. + */ + ParticleSet &particle_container(StringRef name); + + /** + * Get the name of a container in the context of this particle state. + */ + StringRefNull particle_container_name(ParticleSet &container); + + /** + * Get range of unique particle ids. + */ + IndexRange get_new_particle_ids(uint amount) + { + uint start = m_next_id.fetch_add(amount); + return IndexRange(start, amount); + } +}; + +/* ParticlesState inline functions + ********************************************/ + +inline StringMap<ParticleSet *> &ParticlesState::particle_containers() +{ + return m_container_by_id; +} + +inline ParticleSet &ParticlesState::particle_container(StringRef name) +{ + return *m_container_by_id.lookup(name); +} + +inline StringRefNull ParticlesState::particle_container_name(ParticleSet &container) +{ + StringRefNull result = m_container_by_id.find_key_for_value(&container); + return result; +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/simulate.cpp b/source/blender/simulations/bparticles/simulate.cpp new file mode 100644 index 00000000000..bf96c971c2e --- /dev/null +++ b/source/blender/simulations/bparticles/simulate.cpp @@ -0,0 +1,493 @@ + +#include "BLI_array_cxx.h" +#include "BLI_parallel.h" +#include "BLI_timeit.h" +#include "BLI_vector_adaptor.h" + +#include "FN_cpp_type.h" + +#include "simulate.hpp" + +namespace BParticles { + +using BLI::ScopedVector; +using BLI::VectorAdaptor; +using FN::CPPType; + +BLI_NOINLINE static void find_next_event_per_particle( + BlockStepData &step_data, + IndexMask mask, + ArrayRef<Event *> events, + MutableArrayRef<int> r_next_event_indices, + MutableArrayRef<float> r_time_factors_to_next_event, + ScopedVector<uint> &r_pindices_with_event) +{ + r_next_event_indices.fill_indices(mask, -1); + r_time_factors_to_next_event.fill_indices(mask, 1.0f); + + for (uint event_index : events.index_range()) { + Vector<uint> triggered_pindices; + Vector<float> triggered_time_factors; + + Event *event = events[event_index]; + EventFilterInterface interface( + step_data, mask, r_time_factors_to_next_event, triggered_pindices, triggered_time_factors); + event->filter(interface); + + for (uint i : triggered_pindices.index_range()) { + uint pindex = triggered_pindices[i]; + float time_factor = triggered_time_factors[i]; + BLI_assert(time_factor <= r_time_factors_to_next_event[pindex]); + + r_next_event_indices[pindex] = event_index; + r_time_factors_to_next_event[pindex] = time_factor; + } + } + + for (uint pindex : mask) { + if (r_next_event_indices[pindex] != -1) { + r_pindices_with_event.append(pindex); + } + } +} + +BLI_NOINLINE static void forward_particles_to_next_event_or_end( + BlockStepData &step_data, + ParticleAllocator &particle_allocator, + IndexMask mask, + ArrayRef<float> time_factors_to_next_event, + ArrayRef<OffsetHandler *> offset_handlers) +{ + OffsetHandlerInterface interface( + step_data, mask, time_factors_to_next_event, particle_allocator); + for (OffsetHandler *handler : offset_handlers) { + handler->execute(interface); + } + + auto attributes = step_data.attributes; + auto attribute_offsets = step_data.attribute_offsets; + for (uint attribute_index : attribute_offsets.info().indices()) { + StringRef name = attribute_offsets.info().name_of(attribute_index); + + /* Only vectors can be integrated for now. */ + auto values = attributes.get<float3>(name); + auto offsets = attribute_offsets.get<float3>(attribute_index); + + for (uint pindex : mask) { + float time_factor = time_factors_to_next_event[pindex]; + values[pindex] += time_factor * offsets[pindex]; + } + } +} + +BLI_NOINLINE static void update_remaining_attribute_offsets( + IndexMask mask, + ArrayRef<float> time_factors_to_next_event, + MutableAttributesRef attribute_offsets) +{ + for (uint attribute_index : attribute_offsets.info().indices()) { + /* Only vectors can be integrated for now. */ + auto offsets = attribute_offsets.get<float3>(attribute_index); + + for (uint pindex : mask) { + float factor = 1.0f - time_factors_to_next_event[pindex]; + offsets[pindex] *= factor; + } + } +} + +BLI_NOINLINE static void update_remaining_durations(IndexMask mask, + ArrayRef<float> time_factors_to_next_event, + MutableArrayRef<float> remaining_durations) +{ + for (uint pindex : mask) { + remaining_durations[pindex] *= (1.0f - time_factors_to_next_event[pindex]); + } +} + +BLI_NOINLINE static void find_pindices_per_event( + IndexMask mask, + ArrayRef<int> next_event_indices, + MutableArrayRef<Vector<uint>> r_particles_per_event) +{ + for (uint pindex : mask) { + int event_index = next_event_indices[pindex]; + BLI_assert(event_index >= 0); + r_particles_per_event[event_index].append(pindex); + } +} + +BLI_NOINLINE static void compute_current_time_per_particle(IndexMask mask, + ArrayRef<float> remaining_durations, + float end_time, + MutableArrayRef<float> r_current_times) +{ + for (uint pindex : mask) { + r_current_times[pindex] = end_time - remaining_durations[pindex]; + } +} + +BLI_NOINLINE static void find_unfinished_particles(IndexMask mask, + ArrayRef<float> time_factors_to_next_event, + ArrayRef<bool> kill_states, + VectorAdaptor<uint> &r_unfinished_pindices) +{ + for (uint pindex : mask) { + if (kill_states[pindex] == 0) { + float time_factor = time_factors_to_next_event[pindex]; + + if (time_factor < 1.0f) { + r_unfinished_pindices.append(pindex); + } + } + } +} + +BLI_NOINLINE static void execute_events(BlockStepData &step_data, + ParticleAllocator &particle_allocator, + ArrayRef<Vector<uint>> pindices_per_event, + ArrayRef<float> current_times, + ArrayRef<Event *> events) +{ + BLI::assert_same_size(events, pindices_per_event); + + for (uint event_index : events.index_range()) { + Event *event = events[event_index]; + ArrayRef<uint> pindices = pindices_per_event[event_index]; + + if (pindices.size() == 0) { + continue; + } + + EventExecuteInterface interface(step_data, pindices, current_times, particle_allocator); + event->execute(interface); + } +} + +BLI_NOINLINE static void simulate_to_next_event(BlockStepData &step_data, + ParticleAllocator &particle_allocator, + IndexMask mask, + ParticleSystemInfo &system_info, + VectorAdaptor<uint> &r_unfinished_pindices) +{ + uint amount = step_data.array_size(); + Array<int> next_event_indices(amount); + Array<float> time_factors_to_next_event(amount); + ScopedVector<uint> pindices_with_event; + + find_next_event_per_particle(step_data, + mask, + system_info.events, + next_event_indices, + time_factors_to_next_event, + pindices_with_event); + + forward_particles_to_next_event_or_end(step_data, + particle_allocator, + mask, + time_factors_to_next_event, + system_info.offset_handlers); + + update_remaining_attribute_offsets( + pindices_with_event, time_factors_to_next_event, step_data.attribute_offsets); + + update_remaining_durations( + pindices_with_event, time_factors_to_next_event, step_data.remaining_durations); + + Vector<Vector<uint>> particles_per_event(system_info.events.size()); + find_pindices_per_event(pindices_with_event, next_event_indices, particles_per_event); + + Array<float> current_times(amount); + compute_current_time_per_particle( + pindices_with_event, step_data.remaining_durations, step_data.step_end_time, current_times); + + execute_events( + step_data, particle_allocator, particles_per_event, current_times, system_info.events); + + find_unfinished_particles(pindices_with_event, + time_factors_to_next_event, + step_data.attributes.get<bool>("Dead"), + r_unfinished_pindices); +} + +BLI_NOINLINE static void simulate_with_max_n_events(BlockStepData &step_data, + ParticleAllocator &particle_allocator, + uint max_events, + ParticleSystemInfo &system_info, + ScopedVector<uint> &r_unfinished_pindices) +{ + Array<uint> pindices_A(step_data.array_size()); + Array<uint> pindices_B(step_data.array_size()); + + uint amount_left = step_data.attributes.size(); + + { + /* Handle first event separately to be able to use the static number range. */ + VectorAdaptor<uint> pindices_output(pindices_A.begin(), amount_left); + simulate_to_next_event(step_data, + particle_allocator, + IndexRange(amount_left).as_array_ref(), + system_info, + pindices_output); + amount_left = pindices_output.size(); + } + + for (uint iteration = 0; iteration < max_events - 1 && amount_left > 0; iteration++) { + VectorAdaptor<uint> pindices_input(pindices_A.begin(), amount_left, amount_left); + VectorAdaptor<uint> pindices_output(pindices_B.begin(), amount_left, 0); + + simulate_to_next_event( + step_data, particle_allocator, pindices_input, system_info, pindices_output); + amount_left = pindices_output.size(); + std::swap(pindices_A, pindices_B); + } + + for (uint i = 0; i < amount_left; i++) { + r_unfinished_pindices.append(pindices_A[i]); + } +} + +BLI_NOINLINE static void apply_remaining_offsets(BlockStepData &step_data, + ParticleAllocator &particle_allocator, + ArrayRef<OffsetHandler *> offset_handlers, + IndexMask mask) +{ + if (offset_handlers.size() > 0) { + Array<float> time_factors(step_data.array_size()); + time_factors.fill_indices(mask, 1.0f); + + OffsetHandlerInterface interface(step_data, mask, time_factors, particle_allocator); + for (OffsetHandler *handler : offset_handlers) { + handler->execute(interface); + } + } + + auto attributes = step_data.attributes; + auto attribute_offsets = step_data.attribute_offsets; + + for (uint attribute_index : attribute_offsets.info().indices()) { + StringRef name = attribute_offsets.info().name_of(attribute_index); + + /* Only vectors can be integrated for now. */ + auto values = attributes.get<float3>(name); + auto offsets = attribute_offsets.get<float3>(attribute_index); + + for (uint pindex : mask) { + values[pindex] += offsets[pindex]; + } + } +} + +BLI_NOINLINE static void simulate_particle_chunk(SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + MutableAttributesRef attributes, + ParticleSystemInfo &system_info, + MutableArrayRef<float> remaining_durations, + float end_time) +{ + uint amount = attributes.size(); + BLI_assert(amount == remaining_durations.size()); + + BufferCache buffer_cache; + + Integrator &integrator = *system_info.integrator; + const AttributesInfo &offsets_info = integrator.offset_attributes_info(); + Vector<void *> offset_buffers; + for (const CPPType *type : offsets_info.types()) { + void *ptr = buffer_cache.allocate(type->size() * amount, type->alignment()); + offset_buffers.append(ptr); + } + MutableAttributesRef attribute_offsets(offsets_info, offset_buffers, amount); + + BlockStepData step_data = {simulation_state, + buffer_cache, + attributes, + attribute_offsets, + remaining_durations, + end_time}; + + IntegratorInterface interface(step_data, IndexRange(amount).as_array_ref()); + integrator.integrate(interface); + + if (system_info.events.size() == 0) { + apply_remaining_offsets(step_data, + particle_allocator, + system_info.offset_handlers, + IndexRange(amount).as_array_ref()); + } + else { + ScopedVector<uint> unfinished_pindices; + simulate_with_max_n_events( + step_data, particle_allocator, 10, system_info, unfinished_pindices); + + /* Not sure yet, if this really should be done. */ + if (unfinished_pindices.size() > 0) { + apply_remaining_offsets( + step_data, particle_allocator, system_info.offset_handlers, unfinished_pindices); + } + } + + for (void *buffer : offset_buffers) { + buffer_cache.deallocate(buffer); + } +} + +BLI_NOINLINE static void delete_tagged_particles_and_reorder(ParticleSet &particles) +{ + auto kill_states = particles.attributes().get<bool>("Dead"); + ScopedVector<uint> indices_to_delete; + + for (uint i : kill_states.index_range()) { + if (kill_states[i]) { + indices_to_delete.append(i); + } + } + + particles.destruct_and_reorder(indices_to_delete.as_ref()); +} + +BLI_NOINLINE static void simulate_particles_for_time_span(SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + ParticleSystemInfo &system_info, + FloatInterval time_span, + MutableAttributesRef particle_attributes) +{ + BLI::blocked_parallel_for(IndexRange(particle_attributes.size()), 1000, [&](IndexRange range) { + Array<float> remaining_durations(range.size(), time_span.size()); + simulate_particle_chunk(simulation_state, + particle_allocator, + particle_attributes.slice(range), + system_info, + remaining_durations, + time_span.end()); + }); +} + +BLI_NOINLINE static void simulate_particles_from_birth_to_end_of_step( + SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + ParticleSystemInfo &system_info, + float end_time, + MutableAttributesRef particle_attributes) +{ + ArrayRef<float> all_birth_times = particle_attributes.get<float>("Birth Time"); + + BLI::blocked_parallel_for(IndexRange(particle_attributes.size()), 1000, [&](IndexRange range) { + ArrayRef<float> birth_times = all_birth_times.slice(range); + + Array<float> remaining_durations(range.size()); + for (uint i : remaining_durations.index_range()) { + remaining_durations[i] = end_time - birth_times[i]; + } + + simulate_particle_chunk(simulation_state, + particle_allocator, + particle_attributes.slice(range), + system_info, + remaining_durations, + end_time); + }); +} + +BLI_NOINLINE static void simulate_existing_particles( + SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + StringMap<ParticleSystemInfo> &systems_to_simulate) +{ + FloatInterval simulation_time_span = simulation_state.time().current_update_time(); + + BLI::parallel_map_items(simulation_state.particles().particle_containers(), + [&](StringRef system_name, ParticleSet *particle_set) { + ParticleSystemInfo *system_info = systems_to_simulate.lookup_ptr( + system_name); + if (system_info == nullptr) { + return; + } + + simulate_particles_for_time_span(simulation_state, + particle_allocator, + *system_info, + simulation_time_span, + particle_set->attributes()); + }); +} + +BLI_NOINLINE static void create_particles_from_emitters(SimulationState &simulation_state, + ParticleAllocator &particle_allocator, + ArrayRef<Emitter *> emitters, + FloatInterval time_span) +{ + BLI::parallel_for(emitters.index_range(), [&](uint emitter_index) { + Emitter &emitter = *emitters[emitter_index]; + EmitterInterface interface(simulation_state, particle_allocator, time_span); + emitter.emit(interface); + }); +} + +void simulate_particles(SimulationState &simulation_state, + ArrayRef<Emitter *> emitters, + StringMap<ParticleSystemInfo> &systems_to_simulate) +{ + SCOPED_TIMER(__func__); + + ParticlesState &particles_state = simulation_state.particles(); + FloatInterval simulation_time_span = simulation_state.time().current_update_time(); + + StringMultiMap<ParticleSet *> all_newly_created_particles; + StringMultiMap<ParticleSet *> newly_created_particles; + { + ParticleAllocator particle_allocator(particles_state); + BLI::parallel_invoke( + [&]() { + simulate_existing_particles(simulation_state, particle_allocator, systems_to_simulate); + }, + [&]() { + create_particles_from_emitters( + simulation_state, particle_allocator, emitters, simulation_time_span); + }); + + newly_created_particles = particle_allocator.allocated_particles(); + all_newly_created_particles = newly_created_particles; + } + + while (newly_created_particles.key_amount() > 0) { + ParticleAllocator particle_allocator(particles_state); + + BLI::parallel_map_items( + newly_created_particles, [&](StringRef name, ArrayRef<ParticleSet *> new_particle_sets) { + ParticleSystemInfo *system_info = systems_to_simulate.lookup_ptr(name); + if (system_info == nullptr) { + return; + } + + BLI::parallel_for(new_particle_sets.index_range(), [&](uint index) { + ParticleSet &particle_set = *new_particle_sets[index]; + simulate_particles_from_birth_to_end_of_step(simulation_state, + particle_allocator, + *system_info, + simulation_time_span.end(), + particle_set.attributes()); + }); + }); + + newly_created_particles = particle_allocator.allocated_particles(); + all_newly_created_particles.add_multiple(newly_created_particles); + } + + BLI::parallel_map_items(all_newly_created_particles, + [&](StringRef name, ArrayRef<ParticleSet *> new_particle_sets) { + ParticleSet &main_set = particles_state.particle_container(name); + + for (ParticleSet *set : new_particle_sets) { + main_set.add_particles(*set); + delete set; + } + }); + + BLI::parallel_map_keys(systems_to_simulate, [&](StringRef name) { + ParticleSet &particles = particles_state.particle_container(name); + delete_tagged_particles_and_reorder(particles); + }); +} + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/simulate.hpp b/source/blender/simulations/bparticles/simulate.hpp new file mode 100644 index 00000000000..e48b231c27c --- /dev/null +++ b/source/blender/simulations/bparticles/simulate.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "emitter_interface.hpp" +#include "event_interface.hpp" +#include "integrator_interface.hpp" +#include "offset_handler_interface.hpp" +#include "simulation_state.hpp" + +namespace BParticles { + +struct ParticleSystemInfo { + Integrator *integrator; + ArrayRef<Event *> events; + ArrayRef<OffsetHandler *> offset_handlers; +}; + +void simulate_particles(SimulationState &state, + ArrayRef<Emitter *> emitters, + StringMap<ParticleSystemInfo> &systems_to_simulate); + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/simulation_state.hpp b/source/blender/simulations/bparticles/simulation_state.hpp new file mode 100644 index 00000000000..1a686afcd52 --- /dev/null +++ b/source/blender/simulations/bparticles/simulation_state.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "BLI_float_interval.h" + +#include "particles_state.hpp" +#include "world_state.hpp" + +namespace BParticles { + +using BLI::FloatInterval; + +class SimulationTimeState { + private: + bool m_is_updating = false; + float m_simulation_time = 0.0f; + float m_update_start_time = 0.0f; + float m_update_duration = 0.0f; + uint m_current_update_index = 0; + + public: + bool is_updating() const + { + return m_is_updating; + } + + FloatInterval current_update_time() const + { + BLI_assert(m_is_updating); + return FloatInterval(m_update_start_time, m_update_duration); + } + + uint current_update_index() const + { + BLI_assert(m_is_updating); + return m_current_update_index; + } + + void start_update(float time_step) + { + BLI_assert(time_step >= 0); + BLI_assert(!m_is_updating); + m_is_updating = true; + m_update_start_time = m_simulation_time; + m_update_duration = time_step; + m_current_update_index++; + } + + void end_update() + { + BLI_assert(m_is_updating); + m_is_updating = false; + m_simulation_time = m_update_start_time + m_update_duration; + } +}; + +class SimulationState { + private: + ParticlesState m_particles; + WorldState m_world; + SimulationTimeState m_time_state; + + public: + ParticlesState &particles() + { + return m_particles; + } + + WorldState &world() + { + return m_world; + } + + SimulationTimeState &time() + { + return m_time_state; + } +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/step_simulator.hpp b/source/blender/simulations/bparticles/step_simulator.hpp new file mode 100644 index 00000000000..1c611a70fd0 --- /dev/null +++ b/source/blender/simulations/bparticles/step_simulator.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "simulation_state.hpp" + +namespace BParticles { + +class StepSimulator { + public: + virtual ~StepSimulator() + { + } + + virtual void simulate(SimulationState &simulation_state) = 0; +}; + +} // namespace BParticles diff --git a/source/blender/simulations/bparticles/world_state.hpp b/source/blender/simulations/bparticles/world_state.hpp new file mode 100644 index 00000000000..dfba91d3a3d --- /dev/null +++ b/source/blender/simulations/bparticles/world_state.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "BLI_float3.h" +#include "BLI_float4x4.h" +#include "BLI_map.h" +#include "BLI_string_map.h" +#include "BLI_string_ref.h" + +namespace BParticles { + +using BLI::ArrayRef; +using BLI::float3; +using BLI::float4x4; +using BLI::Map; +using BLI::MutableArrayRef; +using BLI::StringMap; +using BLI::StringRef; + +struct VaryingFloat { + float start, end; + + float interpolate(float t) const + { + return start * (1.0f - t) + end * t; + } +}; + +struct VaryingFloat3 { + float3 start, end; + + float3 interpolate(float t) const + { + return float3::interpolate(start, end, t); + } +}; + +struct VaryingFloat4x4 { + /* TODO: store decomposed matrices */ + float4x4 start, end; + + float4x4 interpolate(float t) const + { + if (memcmp(&start, &end, sizeof(float4x4)) == 0) { + return start; + } + return float4x4::interpolate(start, end, t); + } + + void interpolate(ArrayRef<float> times, + float time_offset, + MutableArrayRef<float4x4> r_results) const + { + BLI::assert_same_size(times, r_results); + for (uint i : times.index_range()) { + r_results[i] = this->interpolate(times[i] + time_offset); + } + } +}; + +class WorldTransition; + +class WorldState { + private: + StringMap<float> m_states_float; + StringMap<float3> m_states_float3; + StringMap<float4x4> m_states_float4x4; + + friend WorldTransition; + + public: + void store_state(StringRef main_id, StringRef sub_id, float value) + { + m_states_float.add(main_id + sub_id, value); + } + + void store_state(StringRef main_id, StringRef sub_id, float3 value) + { + m_states_float3.add(main_id + sub_id, value); + } + + void store_state(StringRef main_id, StringRef sub_id, float4x4 value) + { + m_states_float4x4.add(main_id + sub_id, value); + } +}; + +class WorldTransition { + private: + WorldState &m_old_state; + WorldState &m_new_state; + + public: + WorldTransition(WorldState &old_state, WorldState &new_state) + : m_old_state(old_state), m_new_state(new_state) + { + } + + VaryingFloat update_float(StringRef main_id, StringRef sub_id, float current) + { + std::string id = main_id + sub_id; + m_new_state.store_state(main_id, sub_id, current); + float old_value = m_old_state.m_states_float.lookup_default(id, current); + return {old_value, current}; + } + + VaryingFloat3 update_float3(StringRef main_id, StringRef sub_id, float3 current) + { + std::string id = main_id + sub_id; + m_new_state.store_state(main_id, sub_id, current); + float3 old_value = m_old_state.m_states_float3.lookup_default(id, current); + return {old_value, current}; + } + + VaryingFloat4x4 update_float4x4(StringRef main_id, StringRef sub_id, float4x4 current) + { + std::string id = main_id + sub_id; + m_new_state.store_state(main_id, sub_id, current); + float4x4 old_value = m_old_state.m_states_float4x4.lookup_default(id, current); + return {old_value, current}; + } +}; + +} // namespace BParticles diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 90ff7bb8f85..577cb140be2 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -36,6 +36,7 @@ set(INC ../makesdna ../makesrna ../nodes + ../functions ../render/extern/include ../../../intern/clog ../../../intern/ghost diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 17d697840a0..beba03e5b3d 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -131,6 +131,8 @@ #include "DRW_engine.h" +#include "FN_initialize.h" + #ifdef WITH_OPENSUBDIV # include "BKE_subsurf.h" #endif @@ -657,6 +659,8 @@ void WM_exit_ex(bContext *C, const bool do_python) BKE_blender_atexit(); + FN_exit(); + if (MEM_get_memory_blocks_in_use() != 0) { size_t mem_in_use = MEM_get_memory_in_use() + MEM_get_memory_in_use(); printf("Error: Not freed memory blocks: %u, total unfreed memory %f MB\n", diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 4b51f9738b3..fa25248523c 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -29,6 +29,7 @@ blender_include_dirs( ../blender/blenloader ../blender/depsgraph ../blender/editors/include + ../blender/functions ../blender/imbuf ../blender/makesrna ../blender/render/extern/include diff --git a/source/creator/creator.c b/source/creator/creator.c index 75523024aa4..94f1cbc4901 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -80,6 +80,8 @@ #include "RNA_define.h" +#include "FN_initialize.h" + #ifdef WITH_FREESTYLE # include "FRS_freestyle.h" #endif @@ -377,6 +379,7 @@ int main(int argc, BKE_shaderfx_init(); BKE_volumes_init(); DEG_register_node_types(); + FN_initialize(); BKE_brush_system_init(); RE_texture_rng_init(); diff --git a/tests/gtests/blenlib/BLI_linear_allocator_test.cc b/tests/gtests/blenlib/BLI_linear_allocator_test.cc new file mode 100644 index 00000000000..4c1ff44e846 --- /dev/null +++ b/tests/gtests/blenlib/BLI_linear_allocator_test.cc @@ -0,0 +1,63 @@ +#include "BLI_linear_allocator.h" +#include "testing/testing.h" + +using namespace BLI; + +static bool is_aligned(void *ptr, uint alignment) +{ + BLI_assert(is_power_of_2_i(alignment)); + return (POINTER_AS_UINT(ptr) & (alignment - 1)) == 0; +} + +TEST(linear_allocator, AllocationAlignment) +{ + LinearAllocator<> allocator; + + EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 8), 8)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 16), 16)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 64), 64)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 64), 64)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 8), 8)); + EXPECT_TRUE(is_aligned(allocator.allocate(10, 128), 128)); +} + +TEST(linear_allocator, PackedAllocation) +{ + LinearAllocator<> allocator; + BLI::AlignedBuffer<256, 32> buffer; + allocator.provide_buffer(buffer); + + uintptr_t ptr1 = (uintptr_t)allocator.allocate(10, 4); /* 0 - 10 */ + uintptr_t ptr2 = (uintptr_t)allocator.allocate(10, 4); /* 12 - 22 */ + uintptr_t ptr3 = (uintptr_t)allocator.allocate(8, 32); /* 32 - 40 */ + uintptr_t ptr4 = (uintptr_t)allocator.allocate(16, 8); /* 40 - 56 */ + uintptr_t ptr5 = (uintptr_t)allocator.allocate(1, 8); /* 56 - 57 */ + uintptr_t ptr6 = (uintptr_t)allocator.allocate(1, 4); /* 60 - 61 */ + uintptr_t ptr7 = (uintptr_t)allocator.allocate(1, 1); /* 61 - 62 */ + + EXPECT_EQ(ptr2 - ptr1, 12); /* 12 - 0 = 12 */ + EXPECT_EQ(ptr3 - ptr2, 20); /* 32 - 12 = 20 */ + EXPECT_EQ(ptr4 - ptr3, 8); /* 40 - 32 = 8 */ + EXPECT_EQ(ptr5 - ptr4, 16); /* 56 - 40 = 16 */ + EXPECT_EQ(ptr6 - ptr5, 4); /* 60 - 56 = 4 */ + EXPECT_EQ(ptr7 - ptr6, 1); /* 61 - 60 = 1 */ +} + +TEST(linear_allocator, CopyString) +{ + LinearAllocator<> allocator; + BLI::AlignedBuffer<256, 1> buffer; + allocator.provide_buffer(buffer); + + StringRefNull ref1 = allocator.copy_string("Hello"); + StringRefNull ref2 = allocator.copy_string("World"); + + EXPECT_EQ(ref1, "Hello"); + EXPECT_EQ(ref2, "World"); + EXPECT_EQ(ref2.data() - ref1.data(), 6); +} diff --git a/tests/gtests/blenlib/BLI_multi_map_test.cc b/tests/gtests/blenlib/BLI_multi_map_test.cc new file mode 100644 index 00000000000..2b64160be7e --- /dev/null +++ b/tests/gtests/blenlib/BLI_multi_map_test.cc @@ -0,0 +1,111 @@ +#include "BLI_multi_map.h" +#include "testing/testing.h" + +using namespace BLI; + +using IntMultiMap = MultiMap<int, int>; + +TEST(multi_map, DefaultConstructor) +{ + IntMultiMap map; + EXPECT_EQ(map.key_amount(), 0); +} + +TEST(multi_map, AddNewSingle) +{ + IntMultiMap map; + map.add_new(2, 5); + EXPECT_EQ(map.key_amount(), 1); + EXPECT_TRUE(map.contains(2)); + EXPECT_FALSE(map.contains(5)); + EXPECT_EQ(map.lookup(2)[0], 5); +} + +TEST(multi_map, AddMultipleforSameKey) +{ + IntMultiMap map; + map.add(3, 5); + map.add(3, 1); + map.add(3, 7); + EXPECT_EQ(map.key_amount(), 1); + EXPECT_EQ(map.lookup(3).size(), 3); + EXPECT_EQ(map.lookup(3)[0], 5); + EXPECT_EQ(map.lookup(3)[1], 1); + EXPECT_EQ(map.lookup(3)[2], 7); +} + +TEST(multi_map, AddMany) +{ + IntMultiMap map; + for (uint i = 0; i < 100; i++) { + int key = i % 10; + map.add(key, i); + } + + EXPECT_EQ(map.key_amount(), 10); + EXPECT_TRUE(map.contains(3)); + EXPECT_FALSE(map.contains(11)); + EXPECT_EQ(map.lookup(2)[4], 42); + EXPECT_EQ(map.lookup(6)[1], 16); + EXPECT_EQ(map.lookup(7).size(), 10); +} + +TEST(multi_map, AddMultiple) +{ + IntMultiMap map; + map.add_multiple(2, {6, 7, 8}); + map.add_multiple(3, {1, 2}); + map.add_multiple(2, {9, 1}); + EXPECT_EQ(map.key_amount(), 2); + EXPECT_EQ(map.lookup_default(2).size(), 5); + EXPECT_EQ(map.lookup_default(3).size(), 2); + EXPECT_EQ(map.lookup_default(2)[0], 6); + EXPECT_EQ(map.lookup_default(2)[1], 7); + EXPECT_EQ(map.lookup_default(2)[2], 8); + EXPECT_EQ(map.lookup_default(2)[3], 9); + EXPECT_EQ(map.lookup_default(2)[4], 1); +} + +TEST(multi_map, AddMultipleNew) +{ + IntMultiMap map; + map.add_multiple_new(3, {6, 7, 8}); + map.add_multiple_new(2, {1, 2, 5, 7}); + + EXPECT_EQ(map.key_amount(), 2); + EXPECT_TRUE(map.contains(3)); + EXPECT_TRUE(map.contains(2)); + EXPECT_TRUE(map.lookup(2).contains(2)); + EXPECT_FALSE(map.lookup(2).contains(3)); +} + +TEST(multi_map, ValuesForKey) +{ + IntMultiMap map; + map.add(3, 5); + map.add(3, 7); + map.add(3, 8); + map.add(4, 2); + map.add(4, 3); + EXPECT_EQ(map.value_amount(3), 3); + EXPECT_EQ(map.value_amount(4), 2); +} + +TEST(multi_map, Keys) +{ + IntMultiMap map; + map.add(3, 6); + map.add(3, 3); + map.add(3, 4); + map.add(4, 1); + map.add(2, 1); + + Vector<int> values; + for (auto value : map.keys()) { + values.append(value); + } + EXPECT_EQ(values.size(), 3); + EXPECT_TRUE(values.contains(3)); + EXPECT_TRUE(values.contains(4)); + EXPECT_TRUE(values.contains(2)); +} diff --git a/tests/gtests/blenlib/BLI_stack_cpp_test.cc b/tests/gtests/blenlib/BLI_stack_cpp_test.cc new file mode 100644 index 00000000000..b7ef5c6d838 --- /dev/null +++ b/tests/gtests/blenlib/BLI_stack_cpp_test.cc @@ -0,0 +1,52 @@ +#include "BLI_stack_cxx.h" +#include "testing/testing.h" + +using IntStack = BLI::Stack<int>; + +TEST(stack, DefaultConstructor) +{ + IntStack stack; + EXPECT_EQ(stack.size(), 0); + EXPECT_TRUE(stack.empty()); +} + +TEST(stack, ArrayRefConstructor) +{ + std::array<int, 3> array = {4, 7, 2}; + IntStack stack(array); + EXPECT_EQ(stack.size(), 3); + EXPECT_EQ(stack.pop(), 2); + EXPECT_EQ(stack.pop(), 7); + EXPECT_EQ(stack.pop(), 4); + EXPECT_TRUE(stack.empty()); +} + +TEST(stack, Push) +{ + IntStack stack; + EXPECT_EQ(stack.size(), 0); + stack.push(3); + EXPECT_EQ(stack.size(), 1); + stack.push(5); + EXPECT_EQ(stack.size(), 2); +} + +TEST(stack, Pop) +{ + IntStack stack; + stack.push(4); + stack.push(6); + EXPECT_EQ(stack.pop(), 6); + EXPECT_EQ(stack.pop(), 4); +} + +TEST(stack, Peek) +{ + IntStack stack; + stack.push(3); + stack.push(4); + EXPECT_EQ(stack.peek(), 4); + EXPECT_EQ(stack.peek(), 4); + stack.pop(); + EXPECT_EQ(stack.peek(), 3); +} diff --git a/tests/gtests/blenlib/BLI_string_ref_test.cc b/tests/gtests/blenlib/BLI_string_ref_test.cc index a268bf3215a..ba9e86ade11 100644 --- a/tests/gtests/blenlib/BLI_string_ref_test.cc +++ b/tests/gtests/blenlib/BLI_string_ref_test.cc @@ -237,3 +237,14 @@ TEST(string_ref, Substr) EXPECT_EQ(ref.substr(3, 4), "lo w"); EXPECT_EQ(ref.substr(6, 5), "world"); } + +TEST(string_ref, Copy) +{ + StringRef ref("hello"); + char dst[10]; + memset(dst, 0xFF, 10); + ref.copy(dst); + EXPECT_EQ(dst[5], '\0'); + EXPECT_EQ(dst[6], 0xFF); + EXPECT_EQ(ref, dst); +} diff --git a/tests/gtests/blenlib/BLI_vector_adaptor_test.cc b/tests/gtests/blenlib/BLI_vector_adaptor_test.cc new file mode 100644 index 00000000000..ae4f541573a --- /dev/null +++ b/tests/gtests/blenlib/BLI_vector_adaptor_test.cc @@ -0,0 +1,101 @@ +#include "BLI_vector_adaptor.h" +#include "testing/testing.h" +#include <vector> + +using IntVectorAdaptor = BLI::VectorAdaptor<int>; + +TEST(vector_adaptor, DefaultConstructor) +{ + IntVectorAdaptor vec; + EXPECT_EQ(vec.size(), 0); + EXPECT_EQ(vec.capacity(), 0); +} + +TEST(vector_adaptor, PointerConstructor) +{ + int *array = new int[3]; + IntVectorAdaptor vec(array, 3); + EXPECT_EQ(vec.size(), 0); + EXPECT_EQ(vec.capacity(), 3); + delete[] array; +} + +TEST(vector_adaptor, ArrayConstructor) +{ + int array[5]; + IntVectorAdaptor vec(array); + EXPECT_EQ(vec.size(), 0); + EXPECT_EQ(vec.capacity(), 5); +} + +TEST(vector_adaptor, AppendOnce) +{ + int array[5]; + IntVectorAdaptor vec(array); + vec.append(42); + EXPECT_EQ(vec.size(), 1); + EXPECT_EQ(vec[0], 42); +} + +TEST(vector_adaptor, AppendFull) +{ + int array[5]; + IntVectorAdaptor vec(array); + vec.append(3); + vec.append(4); + vec.append(5); + vec.append(6); + vec.append(7); + EXPECT_EQ(vec.size(), 5); + EXPECT_EQ(vec[0], 3); + EXPECT_EQ(vec[1], 4); + EXPECT_EQ(vec[2], 5); + EXPECT_EQ(vec[3], 6); + EXPECT_EQ(vec[4], 7); +} + +TEST(vector_adaptor, Iterate) +{ + int array[4]; + IntVectorAdaptor vec(array); + vec.append(10); + vec.append(11); + vec.append(12); + + std::vector<int> std_vector; + for (int value : vec) { + std_vector.push_back(value); + } + + EXPECT_EQ(std_vector.size(), 3); + EXPECT_EQ(std_vector[0], 10); + EXPECT_EQ(std_vector[1], 11); + EXPECT_EQ(std_vector[2], 12); +} + +TEST(vector_adaptor, Extend) +{ + int array[6]; + IntVectorAdaptor vec(array); + vec.extend({1, 3}); + vec.extend({2, 5}); + EXPECT_EQ(vec.size(), 4); + EXPECT_EQ(vec[0], 1); + EXPECT_EQ(vec[1], 3); + EXPECT_EQ(vec[2], 2); + EXPECT_EQ(vec[3], 5); +} + +TEST(vector_adaptor, AppendNTimes) +{ + int array[6]; + IntVectorAdaptor vec(array); + vec.append_n_times(10, 2); + vec.append_n_times(5, 3); + EXPECT_EQ(vec.size(), 5); + EXPECT_EQ(vec[0], 10); + EXPECT_EQ(vec[1], 10); + EXPECT_EQ(vec[2], 5); + EXPECT_EQ(vec[3], 5); + EXPECT_EQ(vec[4], 5); +} diff --git a/tests/gtests/blenlib/BLI_virtual_list_list_ref_test.cc b/tests/gtests/blenlib/BLI_virtual_list_list_ref_test.cc new file mode 100644 index 00000000000..2dfe5725634 --- /dev/null +++ b/tests/gtests/blenlib/BLI_virtual_list_list_ref_test.cc @@ -0,0 +1,60 @@ +#include "BLI_virtual_list_list_ref.h" +#include "testing/testing.h" +#include <array> +#include <vector> + +using namespace BLI; + +TEST(virtual_list_list_ref, DefaultConstruct) +{ + VirtualListListRef<int> list; + EXPECT_EQ(list.size(), 0); +} + +TEST(virtual_list_list_ref, FromSingleArray) +{ + std::array<int, 3> values = {3, 4, 5}; + VirtualListListRef<int> list = VirtualListListRef<int>::FromSingleArray(values, 6); + EXPECT_EQ(list.size(), 6); + + EXPECT_EQ(list[0].size(), 3); + EXPECT_EQ(list[1].size(), 3); + EXPECT_EQ(list[2].size(), 3); + EXPECT_EQ(list[3].size(), 3); + EXPECT_EQ(list[4].size(), 3); + EXPECT_EQ(list[5].size(), 3); + + EXPECT_EQ(list[2][0], 3); + EXPECT_EQ(list[2][1], 4); + EXPECT_EQ(list[2][2], 5); +} + +TEST(virtual_list_list_ref, FromListOfStartPointers) +{ + std::array<int, 3> values1 = {1, 2, 3}; + std::array<int, 2> values2 = {4, 5}; + std::array<int, 4> values3 = {6, 7, 8, 9}; + + std::array<const int *, 3> starts = {values1.data(), values2.data(), values3.data()}; + std::array<uint, 3> sizes = {values1.size(), values2.size(), values3.size()}; + + VirtualListListRef<int> list = VirtualListListRef<int>::FromListOfStartPointers(starts, sizes); + + EXPECT_EQ(list.size(), 3); + + EXPECT_EQ(list[0].size(), 3); + EXPECT_EQ(list[1].size(), 2); + EXPECT_EQ(list[2].size(), 4); + + EXPECT_EQ(list[0][0], 1); + EXPECT_EQ(list[0][1], 2); + EXPECT_EQ(list[0][2], 3); + + EXPECT_EQ(list[1][0], 4); + EXPECT_EQ(list[1][1], 5); + + EXPECT_EQ(list[2][0], 6); + EXPECT_EQ(list[2][1], 7); + EXPECT_EQ(list[2][2], 8); + EXPECT_EQ(list[2][3], 9); +} diff --git a/tests/gtests/blenlib/BLI_virtual_list_ref_test.cc b/tests/gtests/blenlib/BLI_virtual_list_ref_test.cc new file mode 100644 index 00000000000..6c327516f6c --- /dev/null +++ b/tests/gtests/blenlib/BLI_virtual_list_ref_test.cc @@ -0,0 +1,61 @@ +#include "BLI_virtual_list_ref.h" +#include "testing/testing.h" +#include <array> +#include <vector> + +using namespace BLI; + +TEST(virtual_list_ref, DefaultConstruct) +{ + VirtualListRef<int> list; + EXPECT_EQ(list.size(), 0); +} + +TEST(virtual_list_ref, FromSingle) +{ + int value = 5; + auto list = VirtualListRef<int>::FromSingle(&value, 3); + EXPECT_EQ(list.size(), 3); + EXPECT_EQ(list[0], 5); + EXPECT_EQ(list[1], 5); + EXPECT_EQ(list[2], 5); +} + +TEST(virtual_list_ref, FromFullArray) +{ + std::vector<int> values = {5, 6, 7, 8}; + auto list = VirtualListRef<int>::FromFullArray(values); + EXPECT_EQ(list.size(), 4); + EXPECT_EQ(list[0], 5); + EXPECT_EQ(list[1], 6); + EXPECT_EQ(list[2], 7); + EXPECT_EQ(list[3], 8); +} + +TEST(virtual_list_ref, FromFullPointerArray) +{ + int a1 = 3; + int a2 = 6; + int a3 = 2; + std::array<const int *, 5> pointers = {&a1, &a3, &a1, &a2, &a2}; + + auto list = VirtualListRef<int>::FromFullPointerArray(pointers); + EXPECT_EQ(list.size(), 5); + EXPECT_EQ(list[0], 3); + EXPECT_EQ(list[1], 2); + EXPECT_EQ(list[2], 3); + EXPECT_EQ(list[3], 6); + EXPECT_EQ(list[4], 6); +} + +TEST(virtual_list_ref, FromRepeatedArray) +{ + std::vector<int> values = {3, 4}; + auto list = VirtualListRef<int>::FromRepeatedArray(values, 5); + EXPECT_EQ(list.size(), 5); + EXPECT_EQ(list[0], 3); + EXPECT_EQ(list[1], 4); + EXPECT_EQ(list[2], 3); + EXPECT_EQ(list[3], 4); + EXPECT_EQ(list[4], 3); +} diff --git a/tests/gtests/blenlib/CMakeLists.txt b/tests/gtests/blenlib/CMakeLists.txt index 119b54fa0d4..b7bf8d3c7cc 100644 --- a/tests/gtests/blenlib/CMakeLists.txt +++ b/tests/gtests/blenlib/CMakeLists.txt @@ -60,6 +60,8 @@ BLENDER_TEST(BLI_math_color "bf_blenlib") BLENDER_TEST(BLI_math_geom "bf_blenlib") BLENDER_TEST(BLI_math_vector "bf_blenlib") BLENDER_TEST(BLI_memiter "bf_blenlib") +BLENDER_TEST(BLI_linear_allocator "bf_blenlib") +BLENDER_TEST(BLI_multi_map "bf_blenlib") BLENDER_TEST(BLI_optional "bf_blenlib") BLENDER_TEST(BLI_path_util "${BLI_path_util_extra_libs}") BLENDER_TEST(BLI_polyfill_2d "bf_blenlib") @@ -72,7 +74,10 @@ BLENDER_TEST(BLI_string_ref "bf_blenlib") BLENDER_TEST(BLI_string_utf8 "bf_blenlib") BLENDER_TEST(BLI_task "bf_blenlib;bf_intern_numaapi") BLENDER_TEST(BLI_vector "bf_blenlib") +BLENDER_TEST(BLI_vector_adaptor "bf_blenlib") BLENDER_TEST(BLI_vector_set "bf_blenlib") +BLENDER_TEST(BLI_virtual_list_list_ref "bf_blenlib") +BLENDER_TEST(BLI_virtual_list_ref "bf_blenlib") BLENDER_TEST_PERFORMANCE(BLI_ghash_performance "bf_blenlib") BLENDER_TEST_PERFORMANCE(BLI_task_performance "bf_blenlib") |