Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build_files/cmake/Modules/FindLLVM.cmake1
-rw-r--r--release/scripts/startup/bl_operators/__init__.py1
-rw-r--r--release/scripts/startup/bl_operators/modifiers.py91
-rw-r--r--release/scripts/startup/bl_ui/properties_data_modifier.py38
-rw-r--r--release/scripts/startup/bl_ui/space_node.py5
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py1
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py4
-rw-r--r--release/scripts/startup/nodes/__init__.py3
-rw-r--r--release/scripts/startup/nodes/auto_load.py138
-rw-r--r--release/scripts/startup/nodes/base.py303
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/__init__.py0
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/always_execute.py26
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/combine_influences.py11
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/condition.py18
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/custom_attributes.py46
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/custom_emitter.py169
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/events.py60
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/forces.py13
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/initial_grid_emitter.py19
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/mesh_emitter.py32
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/particle_system.py15
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/point_emitter.py17
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/size_over_time.py13
-rw-r--r--release/scripts/startup/nodes/bparticle_nodes/trails.py16
-rw-r--r--release/scripts/startup/nodes/declaration/__init__.py12
-rw-r--r--release/scripts/startup/nodes/declaration/base.py38
-rw-r--r--release/scripts/startup/nodes/declaration/base_list_variadic.py152
-rw-r--r--release/scripts/startup/nodes/declaration/bparticles.py180
-rw-r--r--release/scripts/startup/nodes/declaration/dynamic_list.py41
-rw-r--r--release/scripts/startup/nodes/declaration/fixed_type.py38
-rw-r--r--release/scripts/startup/nodes/declaration/vectorized.py94
-rw-r--r--release/scripts/startup/nodes/file_load.py14
-rw-r--r--release/scripts/startup/nodes/function_nodes/__init__.py0
-rw-r--r--release/scripts/startup/nodes/function_nodes/color.py58
-rw-r--r--release/scripts/startup/nodes/function_nodes/float_range.py31
-rw-r--r--release/scripts/startup/nodes/function_nodes/groups.py524
-rw-r--r--release/scripts/startup/nodes/function_nodes/list.py69
-rw-r--r--release/scripts/startup/nodes/function_nodes/math.py129
-rw-r--r--release/scripts/startup/nodes/function_nodes/node_instance_identifier.py12
-rw-r--r--release/scripts/startup/nodes/function_nodes/noise.py143
-rw-r--r--release/scripts/startup/nodes/function_nodes/object_mesh.py113
-rw-r--r--release/scripts/startup/nodes/function_nodes/object_transforms.py11
-rw-r--r--release/scripts/startup/nodes/function_nodes/switch.py79
-rw-r--r--release/scripts/startup/nodes/function_nodes/text.py20
-rw-r--r--release/scripts/startup/nodes/function_nodes/time.py10
-rw-r--r--release/scripts/startup/nodes/function_nodes/value.py29
-rw-r--r--release/scripts/startup/nodes/function_nodes/vector.py71
-rw-r--r--release/scripts/startup/nodes/function_tree.py57
-rw-r--r--release/scripts/startup/nodes/graph.py87
-rw-r--r--release/scripts/startup/nodes/inferencing.py291
-rw-r--r--release/scripts/startup/nodes/keymap.py16
-rw-r--r--release/scripts/startup/nodes/menu.py95
-rw-r--r--release/scripts/startup/nodes/node_builder.py240
-rw-r--r--release/scripts/startup/nodes/node_operators.py151
-rw-r--r--release/scripts/startup/nodes/problems.py34
-rw-r--r--release/scripts/startup/nodes/search.py94
-rw-r--r--release/scripts/startup/nodes/sockets.py228
-rw-r--r--release/scripts/startup/nodes/sync.py183
-rw-r--r--release/scripts/startup/nodes/tree_data.py133
-rw-r--r--release/scripts/startup/nodes/tree_panel.py17
-rw-r--r--release/scripts/startup/nodes/types.py16
-rw-r--r--release/scripts/startup/nodes/types_base.py164
-rw-r--r--release/scripts/startup/nodes/ui.py6
-rw-r--r--release/scripts/startup/nodes/utils/enum_items_cache.py20
-rw-r--r--release/scripts/startup/nodes/utils/generic.py21
-rw-r--r--release/scripts/startup/nodes/utils/graph.py19
-rw-r--r--release/scripts/startup/nodes/utils/pie_menu_helper.py38
-rw-r--r--source/blender/CMakeLists.txt2
-rw-r--r--source/blender/blenkernel/BKE_fcurve.h2
-rw-r--r--source/blender/blenkernel/BKE_id_data_cache.h34
-rw-r--r--source/blender/blenkernel/BKE_id_handle.h108
-rw-r--r--source/blender/blenkernel/BKE_surface_hook.h97
-rw-r--r--source/blender/blenkernel/BKE_virtual_node_tree.h367
-rw-r--r--source/blender/blenkernel/CMakeLists.txt9
-rw-r--r--source/blender/blenkernel/intern/fcurve.c12
-rw-r--r--source/blender/blenkernel/intern/id_data_cache.cc31
-rw-r--r--source/blender/blenkernel/intern/id_handle.cc33
-rw-r--r--source/blender/blenkernel/intern/shrinkwrap.c2
-rw-r--r--source/blender/blenkernel/intern/surface_hook.cc7
-rw-r--r--source/blender/blenkernel/intern/virtual_node_tree.cc115
-rw-r--r--source/blender/blenlib/BLI_buffer_cache.h83
-rw-r--r--source/blender/blenlib/BLI_color.h87
-rw-r--r--source/blender/blenlib/BLI_dot_export.h274
-rw-r--r--source/blender/blenlib/BLI_dot_export_attribute_enums.h112
-rw-r--r--source/blender/blenlib/BLI_float2.h90
-rw-r--r--source/blender/blenlib/BLI_float3.h229
-rw-r--r--source/blender/blenlib/BLI_float4x4.h103
-rw-r--r--source/blender/blenlib/BLI_float_interval.h94
-rw-r--r--source/blender/blenlib/BLI_index_mask.h118
-rw-r--r--source/blender/blenlib/BLI_index_to_ref_map.h121
-rw-r--r--source/blender/blenlib/BLI_linear_allocated_vector.h230
-rw-r--r--source/blender/blenlib/BLI_linear_allocator.h172
-rw-r--r--source/blender/blenlib/BLI_multi_map.h225
-rw-r--r--source/blender/blenlib/BLI_parallel.h153
-rw-r--r--source/blender/blenlib/BLI_rand_cxx.h26
-rw-r--r--source/blender/blenlib/BLI_resource_collector.h105
-rw-r--r--source/blender/blenlib/BLI_static_class_ids.h28
-rw-r--r--source/blender/blenlib/BLI_string_map.h6
-rw-r--r--source/blender/blenlib/BLI_string_multi_map.h81
-rw-r--r--source/blender/blenlib/BLI_string_ref.h18
-rw-r--r--source/blender/blenlib/BLI_timeit.h53
-rw-r--r--source/blender/blenlib/BLI_vector_adaptor.h176
-rw-r--r--source/blender/blenlib/BLI_virtual_list_list_ref.h85
-rw-r--r--source/blender/blenlib/BLI_virtual_list_ref.h187
-rw-r--r--source/blender/blenlib/CMakeLists.txt20
-rw-r--r--source/blender/blenlib/intern/BLI_lazy_init.cc53
-rw-r--r--source/blender/blenlib/intern/dot_export.cc289
-rw-r--r--source/blender/blenloader/intern/readfile.c24
-rw-r--r--source/blender/blenloader/intern/writefile.c31
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc1
-rw-r--r--source/blender/editors/object/CMakeLists.txt1
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_modifier.c53
-rw-r--r--source/blender/editors/object/object_ops.c2
-rw-r--r--source/blender/editors/space_graph/graph_buttons.c13
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c4
-rw-r--r--source/blender/functions/CMakeLists.txt94
-rw-r--r--source/blender/functions/FN_attributes_ref.h574
-rw-r--r--source/blender/functions/FN_cpp_type.h379
-rw-r--r--source/blender/functions/FN_generic_array_ref.h171
-rw-r--r--source/blender/functions/FN_generic_tuple.h477
-rw-r--r--source/blender/functions/FN_generic_vector_array.h245
-rw-r--r--source/blender/functions/FN_generic_virtual_list_list_ref.h194
-rw-r--r--source/blender/functions/FN_generic_virtual_list_ref.h238
-rw-r--r--source/blender/functions/FN_initialize.h10
-rw-r--r--source/blender/functions/FN_multi_function.h456
-rw-r--r--source/blender/functions/FN_multi_function_common_contexts.h45
-rw-r--r--source/blender/functions/FN_multi_function_context.h181
-rw-r--r--source/blender/functions/FN_multi_function_data_type.h119
-rw-r--r--source/blender/functions/FN_multi_function_dependencies.h72
-rw-r--r--source/blender/functions/FN_multi_function_network.h868
-rw-r--r--source/blender/functions/FN_multi_function_network_optimization.h18
-rw-r--r--source/blender/functions/FN_multi_function_param_type.h163
-rw-r--r--source/blender/functions/FN_multi_functions.h14
-rw-r--r--source/blender/functions/FN_node_tree.h456
-rw-r--r--source/blender/functions/FN_node_tree_multi_function_network.h155
-rw-r--r--source/blender/functions/FN_node_tree_multi_function_network_generation.h22
-rw-r--r--source/blender/functions/intern/attributes_ref.cc164
-rw-r--r--source/blender/functions/intern/cpp_type.cc9
-rw-r--r--source/blender/functions/intern/cpp_types.cc222
-rw-r--r--source/blender/functions/intern/cpp_types.h11
-rw-r--r--source/blender/functions/intern/generic_array_ref.cc14
-rw-r--r--source/blender/functions/intern/generic_tuple.cc31
-rw-r--r--source/blender/functions/intern/initialize.cc18
-rw-r--r--source/blender/functions/intern/multi_function.cc5
-rw-r--r--source/blender/functions/intern/multi_function_common_contexts.cc13
-rw-r--r--source/blender/functions/intern/multi_function_context.cc5
-rw-r--r--source/blender/functions/intern/multi_function_network.cc638
-rw-r--r--source/blender/functions/intern/multi_function_network_optimization.cc280
-rw-r--r--source/blender/functions/intern/multi_functions/constants.cc74
-rw-r--r--source/blender/functions/intern/multi_functions/constants.h61
-rw-r--r--source/blender/functions/intern/multi_functions/customizable.h197
-rw-r--r--source/blender/functions/intern/multi_functions/global_functions.cc40
-rw-r--r--source/blender/functions/intern/multi_functions/global_functions.h17
-rw-r--r--source/blender/functions/intern/multi_functions/lists.cc158
-rw-r--r--source/blender/functions/intern/multi_functions/lists.h110
-rw-r--r--source/blender/functions/intern/multi_functions/mixed.cc896
-rw-r--r--source/blender/functions/intern/multi_functions/mixed.h205
-rw-r--r--source/blender/functions/intern/multi_functions/network.cc999
-rw-r--r--source/blender/functions/intern/multi_functions/network.h47
-rw-r--r--source/blender/functions/intern/multi_functions/particles.cc129
-rw-r--r--source/blender/functions/intern/multi_functions/particles.h34
-rw-r--r--source/blender/functions/intern/multi_functions/sampling_util.cc89
-rw-r--r--source/blender/functions/intern/multi_functions/sampling_util.h19
-rw-r--r--source/blender/functions/intern/multi_functions/surface_hook.cc625
-rw-r--r--source/blender/functions/intern/multi_functions/surface_hook.h46
-rw-r--r--source/blender/functions/intern/multi_functions/util.h48
-rw-r--r--source/blender/functions/intern/multi_functions/vectorize.cc128
-rw-r--r--source/blender/functions/intern/multi_functions/vectorize.h19
-rw-r--r--source/blender/functions/intern/node_tree.cc480
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/builder.cc117
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/builder.h405
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/generate.cc321
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/mappings.cc28
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/mappings.h42
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/mappings_nodes.cc606
-rw-r--r--source/blender/functions/intern/node_tree_multi_function_network/mappings_sockets.cc188
-rw-r--r--source/blender/makesdna/DNA_anim_types.h2
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h66
-rw-r--r--source/blender/makesdna/DNA_userdef_types.h1
-rw-r--r--source/blender/makesrna/RNA_access.h1
-rw-r--r--source/blender/makesrna/intern/makesrna.c14
-rw-r--r--source/blender/makesrna/intern/rna_fcurve.c1
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c124
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c12
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c7
-rw-r--r--source/blender/modifiers/CMakeLists.txt10
-rw-r--r--source/blender/modifiers/MOD_modifiertypes.h4
-rw-r--r--source/blender/modifiers/intern/MOD_bparticles.c227
-rw-r--r--source/blender/modifiers/intern/MOD_bparticles.h10
-rw-r--r--source/blender/modifiers/intern/MOD_bparticles_output.c134
-rw-r--r--source/blender/modifiers/intern/MOD_functiondeform.c128
-rw-r--r--source/blender/modifiers/intern/MOD_functiondeform_cxx.cc82
-rw-r--r--source/blender/modifiers/intern/MOD_functionpoints.c113
-rw-r--r--source/blender/modifiers/intern/MOD_functionpoints_cxx.cc80
-rw-r--r--source/blender/modifiers/intern/MOD_util.c4
-rw-r--r--source/blender/simulations/BParticles.h45
-rw-r--r--source/blender/simulations/CMakeLists.txt73
-rw-r--r--source/blender/simulations/bparticles/actions.cpp220
-rw-r--r--source/blender/simulations/bparticles/actions.hpp80
-rw-r--r--source/blender/simulations/bparticles/block_step_data.hpp87
-rw-r--r--source/blender/simulations/bparticles/c_wrapper.cpp348
-rw-r--r--source/blender/simulations/bparticles/emitter_interface.hpp79
-rw-r--r--source/blender/simulations/bparticles/emitters.cpp487
-rw-r--r--source/blender/simulations/bparticles/emitters.hpp143
-rw-r--r--source/blender/simulations/bparticles/event_interface.hpp138
-rw-r--r--source/blender/simulations/bparticles/events.cpp155
-rw-r--r--source/blender/simulations/bparticles/events.hpp99
-rw-r--r--source/blender/simulations/bparticles/force_interface.cpp4
-rw-r--r--source/blender/simulations/bparticles/force_interface.hpp34
-rw-r--r--source/blender/simulations/bparticles/forces.cpp24
-rw-r--r--source/blender/simulations/bparticles/forces.hpp35
-rw-r--r--source/blender/simulations/bparticles/integrator.cpp96
-rw-r--r--source/blender/simulations/bparticles/integrator.hpp41
-rw-r--r--source/blender/simulations/bparticles/integrator_interface.hpp52
-rw-r--r--source/blender/simulations/bparticles/node_frontend.cpp1200
-rw-r--r--source/blender/simulations/bparticles/node_frontend.hpp14
-rw-r--r--source/blender/simulations/bparticles/offset_handler_interface.hpp55
-rw-r--r--source/blender/simulations/bparticles/offset_handlers.cpp82
-rw-r--r--source/blender/simulations/bparticles/offset_handlers.hpp51
-rw-r--r--source/blender/simulations/bparticles/particle_action.cpp133
-rw-r--r--source/blender/simulations/bparticles/particle_action.hpp114
-rw-r--r--source/blender/simulations/bparticles/particle_allocator.cpp56
-rw-r--r--source/blender/simulations/bparticles/particle_allocator.hpp45
-rw-r--r--source/blender/simulations/bparticles/particle_function.cpp95
-rw-r--r--source/blender/simulations/bparticles/particle_function.hpp132
-rw-r--r--source/blender/simulations/bparticles/particle_set.cpp90
-rw-r--r--source/blender/simulations/bparticles/particle_set.hpp79
-rw-r--r--source/blender/simulations/bparticles/particles_state.cpp10
-rw-r--r--source/blender/simulations/bparticles/particles_state.hpp79
-rw-r--r--source/blender/simulations/bparticles/simulate.cpp493
-rw-r--r--source/blender/simulations/bparticles/simulate.hpp21
-rw-r--r--source/blender/simulations/bparticles/simulation_state.hpp79
-rw-r--r--source/blender/simulations/bparticles/step_simulator.hpp16
-rw-r--r--source/blender/simulations/bparticles/world_state.hpp123
-rw-r--r--source/blender/windowmanager/CMakeLists.txt1
-rw-r--r--source/blender/windowmanager/intern/wm_init_exit.c4
-rw-r--r--source/creator/CMakeLists.txt1
-rw-r--r--source/creator/creator.c3
-rw-r--r--tests/gtests/blenlib/BLI_linear_allocator_test.cc63
-rw-r--r--tests/gtests/blenlib/BLI_multi_map_test.cc111
-rw-r--r--tests/gtests/blenlib/BLI_stack_cpp_test.cc52
-rw-r--r--tests/gtests/blenlib/BLI_string_ref_test.cc11
-rw-r--r--tests/gtests/blenlib/BLI_vector_adaptor_test.cc101
-rw-r--r--tests/gtests/blenlib/BLI_virtual_list_list_ref_test.cc60
-rw-r--r--tests/gtests/blenlib/BLI_virtual_list_ref_test.cc61
-rw-r--r--tests/gtests/blenlib/CMakeLists.txt5
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 &current : *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 &current = *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 = &params.vector_output(0, "List");
+ is_mutating_first_list = false;
+ }
+ else if (this->input_is_list(0)) {
+ vector_array = &params.mutable_vector(0, "List");
+ is_mutating_first_list = true;
+ }
+ else {
+ vector_array = &params.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 &current_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 &params_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 *)&current_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 *)&current_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 *)&current_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 *)&current_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 *)&current_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")