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:
Diffstat (limited to 'source/blender')
-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
170 files changed, 22872 insertions, 16 deletions
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 5f5145cab70..f1f5089c2d6 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -253,6 +253,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 a47c2e3ea13..f771a859459 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -5868,6 +5868,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 f0280c78407..2cea7bff718 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -1804,6 +1804,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 2babf27eb61..a050b71325b 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.c
@@ -90,6 +90,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);
@@ -2674,3 +2676,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 855bf8434be..61d9ee79ec5 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;
@@ -2103,6 +2107,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 54470dc59fc..0d9336ac62b 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 36db4e5b33a..a951c0731a6 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:
@@ -6511,6 +6523,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;
@@ -6636,6 +6756,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 b107c89388d..5354cf463eb 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -5983,6 +5983,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 c0014a2c0cd..0472d8fccd7 100644
--- a/source/blender/modifiers/intern/MOD_util.c
+++ b/source/blender/modifiers/intern/MOD_util.c
@@ -307,5 +307,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",