diff options
Diffstat (limited to 'source/blender')
98 files changed, 7218 insertions, 829 deletions
diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index aab962d42a6..55a841d8fd1 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -40,13 +40,11 @@ struct ReportList; /* Attribute.domain */ typedef enum AttributeDomain { - ATTR_DOMAIN_VERTEX = 0, /* Mesh Vertex */ + ATTR_DOMAIN_POINT = 0, /* Mesh, Hair or PointCloud Point */ ATTR_DOMAIN_EDGE = 1, /* Mesh Edge */ ATTR_DOMAIN_CORNER = 2, /* Mesh Corner */ ATTR_DOMAIN_POLYGON = 3, /* Mesh Polygon */ - - ATTR_DOMAIN_POINT = 4, /* Hair or PointCloud Point */ - ATTR_DOMAIN_CURVE = 5, /* Hair Curve */ + ATTR_DOMAIN_CURVE = 4, /* Hair Curve */ ATTR_DOMAIN_NUM } AttributeDomain; diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh new file mode 100644 index 00000000000..e58fba36342 --- /dev/null +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -0,0 +1,255 @@ +/* + * 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. + */ + +#pragma once + +#include <mutex> + +#include "FN_cpp_type.hh" +#include "FN_spans.hh" + +#include "BKE_attribute.h" + +#include "BLI_float3.hh" + +struct Mesh; + +namespace blender::bke { + +using fn::CPPType; + +/** + * This class offers an indirection for reading an attribute. + * This is useful for the following reasons: + * - Blender does not store all attributes the same way. + * The simplest case are custom data layers with primitive types. + * A bit more complex are mesh attributes like the position of vertices, + * which are embedded into the MVert struct. + * Even more complex to access are vertex weights. + * - Sometimes attributes are stored on one domain, but we want to access + * the attribute on a different domain. Therefore, we have to interpolate + * between the domains. + */ +class ReadAttribute { + protected: + const AttributeDomain domain_; + const CPPType &cpp_type_; + const int64_t size_; + + /* Protects the span below, so that no two threads initialize it at the same time. */ + mutable std::mutex span_mutex_; + /* When it is not null, it points to the attribute array or a temporary array that contains all + * the attribute values. */ + mutable void *array_buffer_ = nullptr; + /* Is true when the buffer above is owned by the attribute accessor. */ + mutable bool array_is_temporary_ = false; + + public: + ReadAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size) + : domain_(domain), cpp_type_(cpp_type), size_(size) + { + } + + virtual ~ReadAttribute(); + + AttributeDomain domain() const + { + return domain_; + } + + const CPPType &cpp_type() const + { + return cpp_type_; + } + + int64_t size() const + { + return size_; + } + + void get(const int64_t index, void *r_value) const + { + BLI_assert(index < size_); + this->get_internal(index, r_value); + } + + /* Get a span that contains all attribute values. */ + fn::GSpan get_span() const; + + protected: + /* r_value is expected to be uninitialized. */ + virtual void get_internal(const int64_t index, void *r_value) const = 0; + + virtual void initialize_span() const; +}; + +/** + * This exists for similar reasons as the ReadAttribute class, except that + * it does not deal with interpolation between domains. + */ +class WriteAttribute { + protected: + const AttributeDomain domain_; + const CPPType &cpp_type_; + const int64_t size_; + + /* When not null, this points either to the attribute array or to a temporary array. */ + void *array_buffer_ = nullptr; + /* True, when the buffer points to a temporary array. */ + bool array_is_temporary_ = false; + /* This helps to protect agains forgetting to apply changes done to the array. */ + bool array_should_be_applied_ = false; + + public: + WriteAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size) + : domain_(domain), cpp_type_(cpp_type), size_(size) + { + } + + virtual ~WriteAttribute(); + + AttributeDomain domain() const + { + return domain_; + } + + const CPPType &cpp_type() const + { + return cpp_type_; + } + + int64_t size() const + { + return size_; + } + + void get(const int64_t index, void *r_value) const + { + BLI_assert(index < size_); + this->get_internal(index, r_value); + } + + void set(const int64_t index, const void *value) + { + BLI_assert(index < size_); + this->set_internal(index, value); + } + + /* Get a span that new attribute values can be written into. When all values have been changed, + * #apply_span has to be called. The span might not contain the original attribute values. */ + fn::GMutableSpan get_span(); + /* Write the changes to the span into the actual attribute, if they aren't already. */ + void apply_span(); + + protected: + virtual void get_internal(const int64_t index, void *r_value) const = 0; + virtual void set_internal(const int64_t index, const void *value) = 0; + + virtual void initialize_span(); + virtual void apply_span_if_necessary(); +}; + +using ReadAttributePtr = std::unique_ptr<ReadAttribute>; +using WriteAttributePtr = std::unique_ptr<WriteAttribute>; + +/* This provides type safe access to an attribute. */ +template<typename T> class TypedReadAttribute { + private: + ReadAttributePtr attribute_; + + public: + TypedReadAttribute(ReadAttributePtr attribute) : attribute_(std::move(attribute)) + { + BLI_assert(attribute_); + BLI_assert(attribute_->cpp_type().is<T>()); + } + + int64_t size() const + { + return attribute_->size(); + } + + T operator[](const int64_t index) const + { + BLI_assert(index < attribute_->size()); + T value; + value.~T(); + attribute_->get(index, &value); + return value; + } + + /* Get a span to that contains all attribute values for faster and more convenient access. */ + Span<T> get_span() const + { + return attribute_->get_span().template typed<T>(); + } +}; + +/* This provides type safe access to an attribute. */ +template<typename T> class TypedWriteAttribute { + private: + WriteAttributePtr attribute_; + + public: + TypedWriteAttribute(WriteAttributePtr attribute) : attribute_(std::move(attribute)) + { + BLI_assert(attribute_); + BLI_assert(attribute_->cpp_type().is<T>()); + } + + int64_t size() const + { + return attribute_->size(); + } + + T operator[](const int64_t index) const + { + BLI_assert(index < attribute_->size()); + T value; + value.~T(); + attribute_->get(index, &value); + return value; + } + + void set(const int64_t index, const T &value) + { + attribute_->set(index, &value); + } + + /* Get a span that new values can be written into. Once all values have been updated #apply_span + * has to be called. The span might *not* contain the initial attribute values, so one should + * generally only write to the span. */ + MutableSpan<T> get_span() + { + return attribute_->get_span().typed<T>(); + } + + /* Write back all changes to the actual attribute, if necessary. */ + void apply_span() + { + attribute_->apply_span(); + } +}; + +using FloatReadAttribute = TypedReadAttribute<float>; +using Float3ReadAttribute = TypedReadAttribute<float3>; +using FloatWriteAttribute = TypedWriteAttribute<float>; +using Float3WriteAttribute = TypedWriteAttribute<float3>; + +const CPPType *custom_data_type_to_cpp_type(const CustomDataType type); +CustomDataType cpp_type_to_custom_data_type(const CPPType &type); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h new file mode 100644 index 00000000000..026f4d39d51 --- /dev/null +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct Object; +struct GeometrySet; + +void BKE_geometry_set_free(struct GeometrySet *geometry_set); + +bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set); + +int BKE_geometry_set_instances(const struct GeometrySet *geometry_set, + float (**r_positions)[3], + float (**r_rotations)[3], + float (**r_scales)[3], + struct Object ***r_objects); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh new file mode 100644 index 00000000000..ef3ae3c381c --- /dev/null +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -0,0 +1,373 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include <atomic> +#include <iostream> + +#include "BLI_float3.hh" +#include "BLI_hash.hh" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_user_counter.hh" + +#include "BKE_attribute_access.hh" +#include "BKE_geometry_set.h" + +struct Mesh; +struct PointCloud; +struct Object; + +/* Each geometry component has a specific type. The type determines what kind of data the component + * stores. Functions modifying a geometry will usually just modify a subset of the component types. + */ +enum class GeometryComponentType { + Mesh = 0, + PointCloud = 1, + Instances = 2, +}; + +enum class GeometryOwnershipType { + /* The geometry is owned. This implies that it can be changed. */ + Owned = 0, + /* The geometry can be changed, but someone else is responsible for freeing it. */ + Editable = 1, + /* The geometry cannot be changed and someone else is responsible for freeing it. */ + ReadOnly = 2, +}; + +/* Make it possible to use the component type as key in hash tables. */ +namespace blender { +template<> struct DefaultHash<GeometryComponentType> { + uint64_t operator()(const GeometryComponentType &value) const + { + return (uint64_t)value; + } +}; +} // namespace blender + +/** + * This is the base class for specialized geometry component types. + */ +class GeometryComponent { + private: + /* The reference count has two purposes. When it becomes zero, the component is freed. When it is + * larger than one, the component becomes immutable. */ + mutable std::atomic<int> users_ = 1; + GeometryComponentType type_; + + public: + GeometryComponent(GeometryComponentType type); + virtual ~GeometryComponent(); + static GeometryComponent *create(GeometryComponentType component_type); + + /* The returned component should be of the same type as the type this is called on. */ + virtual GeometryComponent *copy() const = 0; + + void user_add() const; + void user_remove() const; + bool is_mutable() const; + + GeometryComponentType type() const; + + /* Returns true when the geometry component supports this attribute domain. */ + virtual bool attribute_domain_supported(const AttributeDomain domain) const; + /* Returns true when the given data type is supported in the given domain. */ + virtual bool attribute_domain_with_type_supported(const AttributeDomain domain, + const CustomDataType data_type) const; + /* Can only be used with supported domain types. */ + virtual int attribute_domain_size(const AttributeDomain domain) const; + /* Attributes with these names cannot be created or removed via this api. */ + virtual bool attribute_is_builtin(const blender::StringRef attribute_name) const; + + /* Get read-only access to the highest priority attribute with the given name. + * Returns null if the attribute does not exist. */ + virtual blender::bke::ReadAttributePtr attribute_try_get_for_read( + const blender::StringRef attribute_name) const; + + /* Get read and write access to the highest priority attribute with the given name. + * Returns null if the attribute does not exist. */ + virtual blender::bke::WriteAttributePtr attribute_try_get_for_write( + const blender::StringRef attribute_name); + + /* Get a read-only attribute for the domain based on the given attribute. This can be used to + * interpolate from one domain to another. + * Returns null if the interpolation is not implemented. */ + virtual blender::bke::ReadAttributePtr attribute_try_adapt_domain( + blender::bke::ReadAttributePtr attribute, const AttributeDomain domain) const; + + /* Returns true when the attribute has been deleted. */ + virtual bool attribute_try_delete(const blender::StringRef attribute_name); + + /* Returns true when the attribute has been created. */ + virtual bool attribute_try_create(const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type); + + virtual blender::Set<std::string> attribute_names() const; + virtual bool is_empty() const; + + /* Get a read-only attribute for the given domain and data type. + * Returns null when it does not exist. */ + blender::bke::ReadAttributePtr attribute_try_get_for_read( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) const; + + /* Get a read-only attribute for the given domain and data type. + * Returns a constant attribute based on the default value if the attribute does not exist. + * Never returns null. */ + blender::bke::ReadAttributePtr attribute_get_for_read(const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) const; + + /* Get a typed read-only attribute for the given domain and type. */ + template<typename T> + blender::bke::TypedReadAttribute<T> attribute_get_for_read( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const T &default_value) const + { + const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); + const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + return this->attribute_get_for_read(attribute_name, domain, type, &default_value); + } + + /* Get a read-only dummy attribute that always returns the same value. */ + blender::bke::ReadAttributePtr attribute_get_constant_for_read(const AttributeDomain domain, + const CustomDataType data_type, + const void *value) const; + + /* Get a read-only dummy attribute that always returns the same value. */ + template<typename T> + blender::bke::TypedReadAttribute<T> attribute_get_constant_for_read(const AttributeDomain domain, + const T &value) const + { + const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); + const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + return this->attribute_get_constant_for_read(domain, type, &value); + } + + /** + * Returns the attribute with the given parameters if it exists. + * If an exact match does not exist, other attributes with the same name are deleted and a new + * attribute is created if possible. + */ + blender::bke::WriteAttributePtr attribute_try_ensure_for_write( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type); +}; + +template<typename T> +inline constexpr bool is_geometry_component_v = std::is_base_of_v<GeometryComponent, T>; + +/** + * A geometry set contains zero or more geometry components. There is at most one component of each + * type. Individual components might be shared between multiple geometries. Shared components are + * copied automatically when write access is requested. + * + * Copying a geometry set is a relatively cheap operation, because it does not copy the referenced + * geometry components. + */ +struct GeometrySet { + private: + using GeometryComponentPtr = blender::UserCounter<class GeometryComponent>; + blender::Map<GeometryComponentType, GeometryComponentPtr> components_; + + public: + GeometryComponent &get_component_for_write(GeometryComponentType component_type); + template<typename Component> Component &get_component_for_write() + { + BLI_STATIC_ASSERT(is_geometry_component_v<Component>, ""); + return static_cast<Component &>(this->get_component_for_write(Component::static_type)); + } + + const GeometryComponent *get_component_for_read(GeometryComponentType component_type) const; + template<typename Component> const Component *get_component_for_read() const + { + BLI_STATIC_ASSERT(is_geometry_component_v<Component>, ""); + return static_cast<const Component *>(get_component_for_read(Component::static_type)); + } + + bool has(const GeometryComponentType component_type) const; + template<typename Component> bool has() const + { + BLI_STATIC_ASSERT(is_geometry_component_v<Component>, ""); + return this->has(Component::static_type); + } + + void remove(const GeometryComponentType component_type); + template<typename Component> void remove() + { + BLI_STATIC_ASSERT(is_geometry_component_v<Component>, ""); + return this->remove(Component::static_type); + } + + void add(const GeometryComponent &component); + + void compute_boundbox_without_instances(blender::float3 *r_min, blender::float3 *r_max) const; + + friend std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set); + friend bool operator==(const GeometrySet &a, const GeometrySet &b); + uint64_t hash() const; + + /* Utility methods for creation. */ + static GeometrySet create_with_mesh( + Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + static GeometrySet create_with_pointcloud( + PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + + /* Utility methods for access. */ + bool has_mesh() const; + bool has_pointcloud() const; + bool has_instances() const; + const Mesh *get_mesh_for_read() const; + const PointCloud *get_pointcloud_for_read() const; + Mesh *get_mesh_for_write(); + PointCloud *get_pointcloud_for_write(); + + /* Utility methods for replacement. */ + void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + void replace_pointcloud(PointCloud *pointcloud, + GeometryOwnershipType ownership = GeometryOwnershipType::Owned); +}; + +/** A geometry component that can store a mesh. */ +class MeshComponent : public GeometryComponent { + private: + Mesh *mesh_ = nullptr; + GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned; + /* Due to historical design choices, vertex group data is stored in the mesh, but the vertex + * group names are stored on an object. Since we don't have an object here, we copy over the + * names into this map. */ + blender::Map<std::string, int> vertex_group_names_; + + public: + MeshComponent(); + ~MeshComponent(); + GeometryComponent *copy() const override; + + void clear(); + bool has_mesh() const; + void replace(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + Mesh *release(); + + void copy_vertex_group_names_from_object(const struct Object &object); + + const Mesh *get_for_read() const; + Mesh *get_for_write(); + + bool attribute_domain_supported(const AttributeDomain domain) const final; + bool attribute_domain_with_type_supported(const AttributeDomain domain, + const CustomDataType data_type) const final; + int attribute_domain_size(const AttributeDomain domain) const final; + bool attribute_is_builtin(const blender::StringRef attribute_name) const final; + + blender::bke::ReadAttributePtr attribute_try_get_for_read( + const blender::StringRef attribute_name) const final; + blender::bke::WriteAttributePtr attribute_try_get_for_write( + const blender::StringRef attribute_name) final; + + bool attribute_try_delete(const blender::StringRef attribute_name) final; + bool attribute_try_create(const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) final; + + blender::Set<std::string> attribute_names() const final; + bool is_empty() const final; + + static constexpr inline GeometryComponentType static_type = GeometryComponentType::Mesh; +}; + +/** A geometry component that stores a point cloud. */ +class PointCloudComponent : public GeometryComponent { + private: + PointCloud *pointcloud_ = nullptr; + GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned; + + public: + PointCloudComponent(); + ~PointCloudComponent(); + GeometryComponent *copy() const override; + + void clear(); + bool has_pointcloud() const; + void replace(PointCloud *pointcloud, + GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + PointCloud *release(); + + const PointCloud *get_for_read() const; + PointCloud *get_for_write(); + + bool attribute_domain_supported(const AttributeDomain domain) const final; + bool attribute_domain_with_type_supported(const AttributeDomain domain, + const CustomDataType data_type) const final; + int attribute_domain_size(const AttributeDomain domain) const final; + bool attribute_is_builtin(const blender::StringRef attribute_name) const final; + + blender::bke::ReadAttributePtr attribute_try_get_for_read( + const blender::StringRef attribute_name) const final; + blender::bke::WriteAttributePtr attribute_try_get_for_write( + const blender::StringRef attribute_name) final; + + bool attribute_try_delete(const blender::StringRef attribute_name) final; + bool attribute_try_create(const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) final; + + blender::Set<std::string> attribute_names() const final; + bool is_empty() const final; + + static constexpr inline GeometryComponentType static_type = GeometryComponentType::PointCloud; +}; + +/** A geometry component that stores instances. */ +class InstancesComponent : public GeometryComponent { + private: + blender::Vector<blender::float3> positions_; + blender::Vector<blender::float3> rotations_; + blender::Vector<blender::float3> scales_; + blender::Vector<const Object *> objects_; + + public: + InstancesComponent(); + ~InstancesComponent() = default; + GeometryComponent *copy() const override; + + void clear(); + void add_instance(const Object *object, + blender::float3 position, + blender::float3 rotation = {0, 0, 0}, + blender::float3 scale = {1, 1, 1}); + + blender::Span<const Object *> objects() const; + blender::Span<blender::float3> positions() const; + blender::Span<blender::float3> rotations() const; + blender::Span<blender::float3> scales() const; + blender::MutableSpan<blender::float3> positions(); + int instances_amount() const; + + bool is_empty() const final; + + static constexpr inline GeometryComponentType static_type = GeometryComponentType::Instances; +}; diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index a2c3787bcd2..2491f0953c0 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -43,6 +43,7 @@ struct ModifierData; struct Object; struct Scene; struct bArmature; +struct GeometrySet; typedef enum { /* Should not be used, only for None modifier type */ @@ -246,9 +247,9 @@ typedef struct ModifierTypeInfo { struct Hair *(*modifyHair)(struct ModifierData *md, const struct ModifierEvalContext *ctx, struct Hair *hair); - struct PointCloud *(*modifyPointCloud)(struct ModifierData *md, - const struct ModifierEvalContext *ctx, - struct PointCloud *pointcloud); + void (*modifyPointCloud)(struct ModifierData *md, + const struct ModifierEvalContext *ctx, + struct GeometrySet *geometry_set); struct Volume *(*modifyVolume)(struct ModifierData *md, const struct ModifierEvalContext *ctx, struct Volume *volume); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index e6fb9d86b0a..905316a937d 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -112,20 +112,26 @@ namespace blender { namespace nodes { class SocketMFNetworkBuilder; class NodeMFNetworkBuilder; +class GeoNodeExecParams; } // namespace nodes namespace fn { +class CPPType; class MFDataType; -} +} // namespace fn } // namespace blender using NodeExpandInMFNetworkFunction = void (*)(blender::nodes::NodeMFNetworkBuilder &builder); -using SocketGetMFDataTypeFunction = blender::fn::MFDataType (*)(); +using NodeGeometryExecFunction = void (*)(blender::nodes::GeoNodeExecParams params); +using SocketGetCPPTypeFunction = const blender::fn::CPPType *(*)(); +using SocketGetCPPValueFunction = void (*)(const struct bNodeSocket &socket, void *r_value); using SocketExpandInMFNetworkFunction = void (*)(blender::nodes::SocketMFNetworkBuilder &builder); #else typedef void *NodeExpandInMFNetworkFunction; -typedef void *SocketGetMFDataTypeFunction; typedef void *SocketExpandInMFNetworkFunction; +typedef void *NodeGeometryExecFunction; +typedef void *SocketGetCPPTypeFunction; +typedef void *SocketGetCPPValueFunction; #endif /** @@ -181,10 +187,12 @@ typedef struct bNodeSocketType { /* Callback to free the socket type. */ void (*free_self)(struct bNodeSocketType *stype); - /* Returns the multi-function data type of this socket type. */ - SocketGetMFDataTypeFunction get_mf_data_type; /* Expands the socket into a multi-function node that outputs the socket value. */ SocketExpandInMFNetworkFunction expand_in_mf_network; + /* Return the CPPType of this socket. */ + SocketGetCPPTypeFunction get_cpp_type; + /* Get the value of this socket in a generic way. */ + SocketGetCPPValueFunction get_cpp_value; } bNodeSocketType; typedef void *(*NodeInitExecFunction)(struct bNodeExecContext *context, @@ -302,6 +310,9 @@ typedef struct bNodeType { /* Expands the bNode into nodes in a multi-function network, which will be evaluated later on. */ NodeExpandInMFNetworkFunction expand_in_mf_network; + /* Execute a geometry node. */ + NodeGeometryExecFunction geometry_node_execute; + /* RNA integration */ ExtensionRNA rna_ext; } bNodeType; @@ -438,7 +449,7 @@ bool ntreeHasType(const struct bNodeTree *ntree, int type); bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup); void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree); void ntreeUpdateAllNew(struct Main *main); -void ntreeUpdateAllUsers(struct Main *main, struct ID *ngroup); +void ntreeUpdateAllUsers(struct Main *main, struct bNodeTree *ngroup); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***deplist, int *totnodes); @@ -1323,6 +1334,24 @@ int ntreeTexExecTree(struct bNodeTree *ntree, /** \} */ /* -------------------------------------------------------------------- */ +/** \name Geometry Nodes + * \{ */ + +#define GEO_NODE_TRIANGULATE 1000 +#define GEO_NODE_EDGE_SPLIT 1001 +#define GEO_NODE_TRANSFORM 1002 +#define GEO_NODE_BOOLEAN 1003 +#define GEO_NODE_POINT_DISTRIBUTE 1004 +#define GEO_NODE_POINT_INSTANCE 1005 +#define GEO_NODE_SUBDIVISION_SURFACE 1006 +#define GEO_NODE_OBJECT_INFO 1007 +#define GEO_NODE_RANDOM_ATTRIBUTE 1008 +#define GEO_NODE_ATTRIBUTE_MATH 1009 +#define GEO_NODE_JOIN_GEOMETRY 1010 + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Function Nodes * \{ */ diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index 985a8cc3ca7..d2d390dc786 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -38,8 +38,10 @@ extern const char *POINTCLOUD_ATTR_RADIUS; void *BKE_pointcloud_add(struct Main *bmain, const char *name); void *BKE_pointcloud_add_default(struct Main *bmain, const char *name); +struct PointCloud *BKE_pointcloud_new_nomain(const int totpoint); struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); +void BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); void BKE_pointcloud_update_customdata_pointers(struct PointCloud *pointcloud); bool BKE_pointcloud_customdata_required(struct PointCloud *pointcloud, diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index c89a45b2a43..1a90d6fadd3 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -78,6 +78,7 @@ set(SRC intern/armature_deform.c intern/armature_update.c intern/attribute.c + intern/attribute_access.cc intern/autoexec.c intern/blender.c intern/blender_copybuffer.c @@ -124,6 +125,7 @@ set(SRC intern/fmodifier.c intern/font.c intern/freestyle.c + intern/geometry_set.cc intern/gpencil.c intern/gpencil_curve.c intern/gpencil_geom.c @@ -266,6 +268,7 @@ set(SRC BKE_appdir.h BKE_armature.h BKE_attribute.h + BKE_attribute_access.hh BKE_autoexec.h BKE_blender.h BKE_blender_copybuffer.h @@ -311,6 +314,8 @@ set(SRC BKE_fluid.h BKE_font.h BKE_freestyle.h + BKE_geometry_set.h + BKE_geometry_set.hh BKE_global.h BKE_gpencil.h BKE_gpencil_curve.h diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c index 9ad73133f9e..d8fd3a19303 100644 --- a/source/blender/blenkernel/intern/attribute.c +++ b/source/blender/blenkernel/intern/attribute.c @@ -63,8 +63,8 @@ static void get_domains(ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) } case ID_ME: { Mesh *mesh = (Mesh *)id; - info[ATTR_DOMAIN_VERTEX].customdata = &mesh->vdata; - info[ATTR_DOMAIN_VERTEX].length = mesh->totvert; + info[ATTR_DOMAIN_POINT].customdata = &mesh->vdata; + info[ATTR_DOMAIN_POINT].length = mesh->totvert; info[ATTR_DOMAIN_EDGE].customdata = &mesh->edata; info[ATTR_DOMAIN_EDGE].length = mesh->totedge; info[ATTR_DOMAIN_CORNER].customdata = &mesh->ldata; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc new file mode 100644 index 00000000000..2345c834be4 --- /dev/null +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -0,0 +1,1080 @@ +/* + * 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 <utility> + +#include "BKE_attribute_access.hh" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_geometry_set.hh" +#include "BKE_mesh.h" +#include "BKE_pointcloud.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BLI_color.hh" +#include "BLI_float2.hh" +#include "BLI_span.hh" + +#include "CLG_log.h" + +#include "NOD_node_tree_multi_function.hh" + +static CLG_LogRef LOG = {"bke.attribute_access"}; + +using blender::float3; +using blender::Set; +using blender::StringRef; +using blender::bke::ReadAttributePtr; +using blender::bke::WriteAttributePtr; + +/* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ +extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); + +namespace blender::bke { + +/* -------------------------------------------------------------------- */ +/** \name Attribute Accessor implementations + * \{ */ + +ReadAttribute::~ReadAttribute() +{ + if (array_is_temporary_ && array_buffer_ != nullptr) { + cpp_type_.destruct_n(array_buffer_, size_); + MEM_freeN(array_buffer_); + } +} + +fn::GSpan ReadAttribute::get_span() const +{ + if (size_ == 0) { + return fn::GSpan(cpp_type_); + } + if (array_buffer_ == nullptr) { + std::lock_guard lock{span_mutex_}; + if (array_buffer_ == nullptr) { + this->initialize_span(); + } + } + return fn::GSpan(cpp_type_, array_buffer_, size_); +} + +void ReadAttribute::initialize_span() const +{ + const int element_size = cpp_type_.size(); + array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); + array_is_temporary_ = true; + for (const int i : IndexRange(size_)) { + this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); + } +} + +WriteAttribute::~WriteAttribute() +{ + if (array_should_be_applied_) { + CLOG_ERROR(&LOG, "Forgot to call apply_span."); + } + if (array_is_temporary_ && array_buffer_ != nullptr) { + cpp_type_.destruct_n(array_buffer_, size_); + MEM_freeN(array_buffer_); + } +} + +/** + * Get a mutable span that can be modified. When all modifications to the attribute are done, + * #apply_span_if_necessary should be called. + */ +fn::GMutableSpan WriteAttribute::get_span() +{ + if (size_ == 0) { + return fn::GMutableSpan(cpp_type_); + } + if (array_buffer_ == nullptr) { + this->initialize_span(); + } + array_should_be_applied_ = true; + return fn::GMutableSpan(cpp_type_, array_buffer_, size_); +} + +void WriteAttribute::initialize_span() +{ + array_buffer_ = MEM_mallocN_aligned(cpp_type_.size() * size_, cpp_type_.alignment(), __func__); + array_is_temporary_ = true; + /* This does nothing for trivial types, but is necessary for general correctness. */ + cpp_type_.construct_default_n(array_buffer_, size_); +} + +void WriteAttribute::apply_span() +{ + this->apply_span_if_necessary(); + array_should_be_applied_ = false; +} + +void WriteAttribute::apply_span_if_necessary() +{ + /* Only works when the span has been initialized beforehand. */ + BLI_assert(array_buffer_ != nullptr); + + const int element_size = cpp_type_.size(); + for (const int i : IndexRange(size_)) { + this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); + } +} + +class VertexWeightWriteAttribute final : public WriteAttribute { + private: + MDeformVert *dverts_; + const int dvert_index_; + + public: + VertexWeightWriteAttribute(MDeformVert *dverts, const int totvert, const int dvert_index) + : WriteAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), + dverts_(dverts), + dvert_index_(dvert_index) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + get_internal(dverts_, dvert_index_, index, r_value); + } + + void set_internal(const int64_t index, const void *value) override + { + MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); + weight->weight = *reinterpret_cast<const float *>(value); + } + + static void get_internal(const MDeformVert *dverts, + const int dvert_index, + const int64_t index, + void *r_value) + { + if (dverts == nullptr) { + *(float *)r_value = 0.0f; + return; + } + const MDeformVert &dvert = dverts[index]; + for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { + if (weight.def_nr == dvert_index) { + *(float *)r_value = weight.weight; + return; + } + } + *(float *)r_value = 0.0f; + } +}; + +class VertexWeightReadAttribute final : public ReadAttribute { + private: + const MDeformVert *dverts_; + const int dvert_index_; + + public: + VertexWeightReadAttribute(const MDeformVert *dverts, const int totvert, const int dvert_index) + : ReadAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), + dverts_(dverts), + dvert_index_(dvert_index) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + VertexWeightWriteAttribute::get_internal(dverts_, dvert_index_, index, r_value); + } +}; + +template<typename T> class ArrayWriteAttribute final : public WriteAttribute { + private: + MutableSpan<T> data_; + + public: + ArrayWriteAttribute(AttributeDomain domain, MutableSpan<T> data) + : WriteAttribute(domain, CPPType::get<T>(), data.size()), data_(data) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + new (r_value) T(data_[index]); + } + + void set_internal(const int64_t index, const void *value) override + { + data_[index] = *reinterpret_cast<const T *>(value); + } + + void initialize_span() override + { + array_buffer_ = data_.data(); + array_is_temporary_ = false; + } + + void apply_span_if_necessary() override + { + /* Do nothing, because the span contains the attribute itself already. */ + } +}; + +template<typename T> class ArrayReadAttribute final : public ReadAttribute { + private: + Span<T> data_; + + public: + ArrayReadAttribute(AttributeDomain domain, Span<T> data) + : ReadAttribute(domain, CPPType::get<T>(), data.size()), data_(data) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + new (r_value) T(data_[index]); + } + + void initialize_span() const override + { + /* The data will not be modified, so this const_cast is fine. */ + array_buffer_ = const_cast<T *>(data_.data()); + array_is_temporary_ = false; + } +}; + +template<typename StructT, typename ElemT, typename GetFuncT, typename SetFuncT> +class DerivedArrayWriteAttribute final : public WriteAttribute { + private: + MutableSpan<StructT> data_; + GetFuncT get_function_; + SetFuncT set_function_; + + public: + DerivedArrayWriteAttribute(AttributeDomain domain, + MutableSpan<StructT> data, + GetFuncT get_function, + SetFuncT set_function) + : WriteAttribute(domain, CPPType::get<ElemT>(), data.size()), + data_(data), + get_function_(std::move(get_function)), + set_function_(std::move(set_function)) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + const StructT &struct_value = data_[index]; + const ElemT value = get_function_(struct_value); + new (r_value) ElemT(value); + } + + void set_internal(const int64_t index, const void *value) override + { + StructT &struct_value = data_[index]; + const ElemT &typed_value = *reinterpret_cast<const ElemT *>(value); + set_function_(struct_value, typed_value); + } +}; + +template<typename StructT, typename ElemT, typename GetFuncT> +class DerivedArrayReadAttribute final : public ReadAttribute { + private: + Span<StructT> data_; + GetFuncT get_function_; + + public: + DerivedArrayReadAttribute(AttributeDomain domain, Span<StructT> data, GetFuncT get_function) + : ReadAttribute(domain, CPPType::get<ElemT>(), data.size()), + data_(data), + get_function_(std::move(get_function)) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + const StructT &struct_value = data_[index]; + const ElemT value = get_function_(struct_value); + new (r_value) ElemT(value); + } +}; + +class ConstantReadAttribute final : public ReadAttribute { + private: + void *value_; + + public: + ConstantReadAttribute(AttributeDomain domain, + const int64_t size, + const CPPType &type, + const void *value) + : ReadAttribute(domain, type, size) + { + value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_to_uninitialized(value, value_); + } + + ~ConstantReadAttribute() + { + this->cpp_type_.destruct(value_); + MEM_freeN(value_); + } + + void get_internal(const int64_t UNUSED(index), void *r_value) const override + { + this->cpp_type_.copy_to_uninitialized(value_, r_value); + } + + void initialize_span() const override + { + const int element_size = cpp_type_.size(); + array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); + array_is_temporary_ = true; + cpp_type_.fill_uninitialized(value_, array_buffer_, size_); + } +}; + +class ConvertedReadAttribute final : public ReadAttribute { + private: + const CPPType &from_type_; + const CPPType &to_type_; + ReadAttributePtr base_attribute_; + const nodes::DataTypeConversions &conversions_; + + static constexpr int MaxValueSize = 64; + static constexpr int MaxValueAlignment = 64; + + public: + ConvertedReadAttribute(ReadAttributePtr base_attribute, const CPPType &to_type) + : ReadAttribute(base_attribute->domain(), to_type, base_attribute->size()), + from_type_(base_attribute->cpp_type()), + to_type_(to_type), + base_attribute_(std::move(base_attribute)), + conversions_(nodes::get_implicit_type_conversions()) + { + if (from_type_.size() > MaxValueSize || from_type_.alignment() > MaxValueAlignment) { + throw std::runtime_error( + "type is larger than expected, the buffer size has to be increased"); + } + } + + void get_internal(const int64_t index, void *r_value) const override + { + AlignedBuffer<MaxValueSize, MaxValueAlignment> buffer; + base_attribute_->get(index, buffer.ptr()); + conversions_.convert(from_type_, to_type_, buffer.ptr(), r_value); + } +}; + +/** \} */ + +const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) +{ + switch (type) { + case CD_PROP_FLOAT: + return &CPPType::get<float>(); + case CD_PROP_FLOAT2: + return &CPPType::get<float2>(); + case CD_PROP_FLOAT3: + return &CPPType::get<float3>(); + case CD_PROP_INT32: + return &CPPType::get<int>(); + case CD_PROP_COLOR: + return &CPPType::get<Color4f>(); + default: + return nullptr; + } + return nullptr; +} + +CustomDataType cpp_type_to_custom_data_type(const blender::fn::CPPType &type) +{ + if (type.is<float>()) { + return CD_PROP_FLOAT; + } + if (type.is<float2>()) { + return CD_PROP_FLOAT2; + } + if (type.is<float3>()) { + return CD_PROP_FLOAT3; + } + if (type.is<int>()) { + return CD_PROP_INT32; + } + if (type.is<Color4f>()) { + return CD_PROP_COLOR; + } + return static_cast<CustomDataType>(-1); +} + +} // namespace blender::bke + +/* -------------------------------------------------------------------- */ +/** \name Utilities for Accessing Attributes + * \{ */ + +static ReadAttributePtr read_attribute_from_custom_data(const CustomData &custom_data, + const int size, + const StringRef attribute_name, + const AttributeDomain domain) +{ + using namespace blender; + using namespace blender::bke; + for (const CustomDataLayer &layer : Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name != nullptr && layer.name == attribute_name) { + switch (layer.type) { + case CD_PROP_FLOAT: + return std::make_unique<ArrayReadAttribute<float>>( + domain, Span(static_cast<float *>(layer.data), size)); + case CD_PROP_FLOAT2: + return std::make_unique<ArrayReadAttribute<float2>>( + domain, Span(static_cast<float2 *>(layer.data), size)); + case CD_PROP_FLOAT3: + return std::make_unique<ArrayReadAttribute<float3>>( + domain, Span(static_cast<float3 *>(layer.data), size)); + case CD_PROP_INT32: + return std::make_unique<ArrayReadAttribute<int>>( + domain, Span(static_cast<int *>(layer.data), size)); + case CD_PROP_COLOR: + return std::make_unique<ArrayReadAttribute<Color4f>>( + domain, Span(static_cast<Color4f *>(layer.data), size)); + } + } + } + return {}; +} + +static WriteAttributePtr write_attribute_from_custom_data( + CustomData &custom_data, + const int size, + const StringRef attribute_name, + const AttributeDomain domain, + const std::function<void()> &update_customdata_pointers) +{ + + using namespace blender; + using namespace blender::bke; + for (const CustomDataLayer &layer : Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name != nullptr && layer.name == attribute_name) { + const void *data_before = layer.data; + /* The data layer might be shared with someone else. Since the caller wants to modify it, we + * copy it first. */ + CustomData_duplicate_referenced_layer_named(&custom_data, layer.type, layer.name, size); + if (data_before != layer.data) { + update_customdata_pointers(); + } + switch (layer.type) { + case CD_PROP_FLOAT: + return std::make_unique<ArrayWriteAttribute<float>>( + domain, MutableSpan(static_cast<float *>(layer.data), size)); + case CD_PROP_FLOAT2: + return std::make_unique<ArrayWriteAttribute<float2>>( + domain, MutableSpan(static_cast<float2 *>(layer.data), size)); + case CD_PROP_FLOAT3: + return std::make_unique<ArrayWriteAttribute<float3>>( + domain, MutableSpan(static_cast<float3 *>(layer.data), size)); + case CD_PROP_INT32: + return std::make_unique<ArrayWriteAttribute<int>>( + domain, MutableSpan(static_cast<int *>(layer.data), size)); + case CD_PROP_COLOR: + return std::make_unique<ArrayWriteAttribute<Color4f>>( + domain, MutableSpan(static_cast<Color4f *>(layer.data), size)); + } + } + } + return {}; +} + +/* Returns true when the layer was found and is deleted. */ +static bool delete_named_custom_data_layer(CustomData &custom_data, + const StringRef attribute_name, + const int size) +{ + for (const int index : blender::IndexRange(custom_data.totlayer)) { + const CustomDataLayer &layer = custom_data.layers[index]; + if (layer.name == attribute_name) { + CustomData_free_layer(&custom_data, layer.type, size, index); + return true; + } + } + return false; +} + +static void get_custom_data_layer_attribute_names(const CustomData &custom_data, + const GeometryComponent &component, + const AttributeDomain domain, + Set<std::string> &r_names) +{ + for (const CustomDataLayer &layer : blender::Span(custom_data.layers, custom_data.totlayer)) { + if (component.attribute_domain_with_type_supported(domain, + static_cast<CustomDataType>(layer.type))) { + r_names.add(layer.name); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Geometry Component + * \{ */ + +bool GeometryComponent::attribute_domain_supported(const AttributeDomain UNUSED(domain)) const +{ + return false; +} + +bool GeometryComponent::attribute_domain_with_type_supported( + const AttributeDomain UNUSED(domain), const CustomDataType UNUSED(data_type)) const +{ + return false; +} + +int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain)) const +{ + BLI_assert(false); + return 0; +} + +bool GeometryComponent::attribute_is_builtin(const StringRef UNUSED(attribute_name)) const +{ + return true; +} + +ReadAttributePtr GeometryComponent::attribute_try_get_for_read( + const StringRef UNUSED(attribute_name)) const +{ + return {}; +} + +ReadAttributePtr GeometryComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, + const AttributeDomain domain) const +{ + if (attribute && attribute->domain() == domain) { + return attribute; + } + return {}; +} + +WriteAttributePtr GeometryComponent::attribute_try_get_for_write( + const StringRef UNUSED(attribute_name)) +{ + return {}; +} + +bool GeometryComponent::attribute_try_delete(const StringRef UNUSED(attribute_name)) +{ + return false; +} + +bool GeometryComponent::attribute_try_create(const StringRef UNUSED(attribute_name), + const AttributeDomain UNUSED(domain), + const CustomDataType UNUSED(data_type)) +{ + return false; +} + +Set<std::string> GeometryComponent::attribute_names() const +{ + return {}; +} + +static ReadAttributePtr try_adapt_data_type(ReadAttributePtr attribute, + const blender::fn::CPPType &to_type) +{ + const blender::fn::CPPType &from_type = attribute->cpp_type(); + if (from_type == to_type) { + return attribute; + } + + const blender::nodes::DataTypeConversions &conversions = + blender::nodes::get_implicit_type_conversions(); + if (!conversions.is_convertible(from_type, to_type)) { + return {}; + } + + return std::make_unique<blender::bke::ConvertedReadAttribute>(std::move(attribute), to_type); +} + +ReadAttributePtr GeometryComponent::attribute_try_get_for_read( + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) const +{ + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return {}; + } + + ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + if (!attribute) { + return {}; + } + + if (attribute->domain() != domain) { + attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); + if (!attribute) { + return {}; + } + } + + const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + if (attribute->cpp_type() != *cpp_type) { + attribute = try_adapt_data_type(std::move(attribute), *cpp_type); + if (!attribute) { + return {}; + } + } + + return attribute; +} + +ReadAttributePtr GeometryComponent::attribute_get_for_read(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) const +{ + BLI_assert(this->attribute_domain_with_type_supported(domain, data_type)); + + ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name, domain, data_type); + if (attribute) { + return attribute; + } + return this->attribute_get_constant_for_read(domain, data_type, default_value); +} + +blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read( + const AttributeDomain domain, const CustomDataType data_type, const void *value) const +{ + BLI_assert(this->attribute_domain_supported(domain)); + const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + if (value == nullptr) { + value = cpp_type->default_value(); + } + const int domain_size = this->attribute_domain_size(domain); + return std::make_unique<blender::bke::ConstantReadAttribute>( + domain, domain_size, *cpp_type, value); +} + +WriteAttributePtr GeometryComponent::attribute_try_ensure_for_write(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + + WriteAttributePtr attribute = this->attribute_try_get_for_write(attribute_name); + if (attribute && attribute->domain() == domain && attribute->cpp_type() == *cpp_type) { + return attribute; + } + + if (attribute) { + if (!this->attribute_try_delete(attribute_name)) { + return {}; + } + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return {}; + } + if (!this->attribute_try_create(attribute_name, domain, data_type)) { + return {}; + } + return this->attribute_try_get_for_write(attribute_name); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Point Cloud Component + * \{ */ + +bool PointCloudComponent::attribute_domain_supported(const AttributeDomain domain) const +{ + return domain == ATTR_DOMAIN_POINT; +} + +bool PointCloudComponent::attribute_domain_with_type_supported( + const AttributeDomain domain, const CustomDataType data_type) const +{ + return domain == ATTR_DOMAIN_POINT && ELEM(data_type, + CD_PROP_FLOAT, + CD_PROP_FLOAT2, + CD_PROP_FLOAT3, + CD_PROP_INT32, + CD_PROP_COLOR); +} + +int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) const +{ + BLI_assert(domain == ATTR_DOMAIN_POINT); + UNUSED_VARS_NDEBUG(domain); + if (pointcloud_ == nullptr) { + return 0; + } + return pointcloud_->totpoint; +} + +bool PointCloudComponent::attribute_is_builtin(const StringRef attribute_name) const +{ + return attribute_name == "position"; +} + +ReadAttributePtr PointCloudComponent::attribute_try_get_for_read( + const StringRef attribute_name) const +{ + if (pointcloud_ == nullptr) { + return {}; + } + + return read_attribute_from_custom_data( + pointcloud_->pdata, pointcloud_->totpoint, attribute_name, ATTR_DOMAIN_POINT); +} + +WriteAttributePtr PointCloudComponent::attribute_try_get_for_write(const StringRef attribute_name) +{ + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return {}; + } + + return write_attribute_from_custom_data( + pointcloud->pdata, pointcloud->totpoint, attribute_name, ATTR_DOMAIN_POINT, [&]() { + BKE_pointcloud_update_customdata_pointers(pointcloud); + }); +} + +bool PointCloudComponent::attribute_try_delete(const StringRef attribute_name) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return false; + } + delete_named_custom_data_layer(pointcloud->pdata, attribute_name, pointcloud->totpoint); + return true; +} + +static bool custom_data_has_layer_with_name(const CustomData &custom_data, const StringRef name) +{ + for (const CustomDataLayer &layer : blender::Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name == name) { + return true; + } + } + return false; +} + +bool PointCloudComponent::attribute_try_create(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return false; + } + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return false; + } + if (custom_data_has_layer_with_name(pointcloud->pdata, attribute_name)) { + return false; + } + + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + CustomData_add_layer_named( + &pointcloud->pdata, data_type, CD_DEFAULT, nullptr, pointcloud_->totpoint, attribute_name_c); + return true; +} + +Set<std::string> PointCloudComponent::attribute_names() const +{ + if (pointcloud_ == nullptr) { + return {}; + } + + Set<std::string> names; + get_custom_data_layer_attribute_names(pointcloud_->pdata, *this, ATTR_DOMAIN_POINT, names); + return names; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mesh Component + * \{ */ + +bool MeshComponent::attribute_domain_supported(const AttributeDomain domain) const +{ + return ELEM( + domain, ATTR_DOMAIN_CORNER, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_POLYGON); +} + +bool MeshComponent::attribute_domain_with_type_supported(const AttributeDomain domain, + const CustomDataType data_type) const +{ + if (!this->attribute_domain_supported(domain)) { + return false; + } + return ELEM( + data_type, CD_PROP_FLOAT, CD_PROP_FLOAT2, CD_PROP_FLOAT3, CD_PROP_INT32, CD_PROP_COLOR); +} + +int MeshComponent::attribute_domain_size(const AttributeDomain domain) const +{ + BLI_assert(this->attribute_domain_supported(domain)); + if (mesh_ == nullptr) { + return 0; + } + switch (domain) { + case ATTR_DOMAIN_CORNER: + return mesh_->totloop; + case ATTR_DOMAIN_POINT: + return mesh_->totvert; + case ATTR_DOMAIN_EDGE: + return mesh_->totedge; + case ATTR_DOMAIN_POLYGON: + return mesh_->totpoly; + default: + BLI_assert(false); + break; + } + return 0; +} + +bool MeshComponent::attribute_is_builtin(const StringRef attribute_name) const +{ + return attribute_name == "position"; +} + +ReadAttributePtr MeshComponent::attribute_try_get_for_read(const StringRef attribute_name) const +{ + if (mesh_ == nullptr) { + return {}; + } + + if (attribute_name == "position") { + auto get_vertex_position = [](const MVert &vert) { return float3(vert.co); }; + return std::make_unique< + blender::bke::DerivedArrayReadAttribute<MVert, float3, decltype(get_vertex_position)>>( + ATTR_DOMAIN_POINT, blender::Span(mesh_->mvert, mesh_->totvert), get_vertex_position); + } + + ReadAttributePtr corner_attribute = read_attribute_from_custom_data( + mesh_->ldata, mesh_->totloop, attribute_name, ATTR_DOMAIN_CORNER); + if (corner_attribute) { + return corner_attribute; + } + + const int vertex_group_index = vertex_group_names_.lookup_default(attribute_name, -1); + if (vertex_group_index >= 0) { + return std::make_unique<blender::bke::VertexWeightReadAttribute>( + mesh_->dvert, mesh_->totvert, vertex_group_index); + } + + ReadAttributePtr vertex_attribute = read_attribute_from_custom_data( + mesh_->vdata, mesh_->totvert, attribute_name, ATTR_DOMAIN_POINT); + if (vertex_attribute) { + return vertex_attribute; + } + + ReadAttributePtr edge_attribute = read_attribute_from_custom_data( + mesh_->edata, mesh_->totedge, attribute_name, ATTR_DOMAIN_EDGE); + if (edge_attribute) { + return edge_attribute; + } + + ReadAttributePtr polygon_attribute = read_attribute_from_custom_data( + mesh_->pdata, mesh_->totpoly, attribute_name, ATTR_DOMAIN_POLYGON); + if (polygon_attribute) { + return polygon_attribute; + } + + return {}; +} + +WriteAttributePtr MeshComponent::attribute_try_get_for_write(const StringRef attribute_name) +{ + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return {}; + } + + const std::function<void()> update_mesh_pointers = [&]() { + BKE_mesh_update_customdata_pointers(mesh, false); + }; + + if (attribute_name == "position") { + CustomData_duplicate_referenced_layer(&mesh->vdata, CD_MVERT, mesh->totvert); + update_mesh_pointers(); + + auto get_vertex_position = [](const MVert &vert) { return float3(vert.co); }; + auto set_vertex_position = [](MVert &vert, const float3 &co) { copy_v3_v3(vert.co, co); }; + return std::make_unique< + blender::bke::DerivedArrayWriteAttribute<MVert, + float3, + decltype(get_vertex_position), + decltype(set_vertex_position)>>( + ATTR_DOMAIN_POINT, + blender::MutableSpan(mesh_->mvert, mesh_->totvert), + get_vertex_position, + set_vertex_position); + } + + WriteAttributePtr corner_attribute = write_attribute_from_custom_data( + mesh_->ldata, mesh_->totloop, attribute_name, ATTR_DOMAIN_CORNER, update_mesh_pointers); + if (corner_attribute) { + return corner_attribute; + } + + const int vertex_group_index = vertex_group_names_.lookup_default_as(attribute_name, -1); + if (vertex_group_index >= 0) { + if (mesh_->dvert == nullptr) { + BKE_object_defgroup_data_create(&mesh_->id); + } + return std::make_unique<blender::bke::VertexWeightWriteAttribute>( + mesh_->dvert, mesh_->totvert, vertex_group_index); + } + + WriteAttributePtr vertex_attribute = write_attribute_from_custom_data( + mesh_->vdata, mesh_->totvert, attribute_name, ATTR_DOMAIN_POINT, update_mesh_pointers); + if (vertex_attribute) { + return vertex_attribute; + } + + WriteAttributePtr edge_attribute = write_attribute_from_custom_data( + mesh_->edata, mesh_->totedge, attribute_name, ATTR_DOMAIN_EDGE, update_mesh_pointers); + if (edge_attribute) { + return edge_attribute; + } + + WriteAttributePtr polygon_attribute = write_attribute_from_custom_data( + mesh_->pdata, mesh_->totpoly, attribute_name, ATTR_DOMAIN_POLYGON, update_mesh_pointers); + if (polygon_attribute) { + return polygon_attribute; + } + + return {}; +} + +bool MeshComponent::attribute_try_delete(const StringRef attribute_name) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return false; + } + + delete_named_custom_data_layer(mesh_->ldata, attribute_name, mesh_->totloop); + delete_named_custom_data_layer(mesh_->vdata, attribute_name, mesh_->totvert); + delete_named_custom_data_layer(mesh_->edata, attribute_name, mesh_->totedge); + delete_named_custom_data_layer(mesh_->pdata, attribute_name, mesh_->totpoly); + + const int vertex_group_index = vertex_group_names_.lookup_default_as(attribute_name, -1); + if (vertex_group_index != -1) { + for (MDeformVert &dvert : blender::MutableSpan(mesh_->dvert, mesh_->totvert)) { + MDeformWeight *weight = BKE_defvert_find_index(&dvert, vertex_group_index); + BKE_defvert_remove_group(&dvert, weight); + } + vertex_group_names_.remove_as(attribute_name); + } + + return true; +} + +bool MeshComponent::attribute_try_create(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return false; + } + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return false; + } + + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + + switch (domain) { + case ATTR_DOMAIN_CORNER: { + if (custom_data_has_layer_with_name(mesh->ldata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->ldata, data_type, CD_DEFAULT, nullptr, mesh->totloop, attribute_name_c); + return true; + } + case ATTR_DOMAIN_POINT: { + if (custom_data_has_layer_with_name(mesh->vdata, attribute_name)) { + return false; + } + if (vertex_group_names_.contains_as(attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->vdata, data_type, CD_DEFAULT, nullptr, mesh->totvert, attribute_name_c); + return true; + } + case ATTR_DOMAIN_EDGE: { + if (custom_data_has_layer_with_name(mesh->edata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->edata, data_type, CD_DEFAULT, nullptr, mesh->totedge, attribute_name_c); + return true; + } + case ATTR_DOMAIN_POLYGON: { + if (custom_data_has_layer_with_name(mesh->pdata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->pdata, data_type, CD_DEFAULT, nullptr, mesh->totpoly, attribute_name_c); + return true; + } + default: + return false; + } +} + +Set<std::string> MeshComponent::attribute_names() const +{ + if (mesh_ == nullptr) { + return {}; + } + + Set<std::string> names; + names.add("position"); + for (StringRef name : vertex_group_names_.keys()) { + names.add(name); + } + get_custom_data_layer_attribute_names(mesh_->pdata, *this, ATTR_DOMAIN_CORNER, names); + get_custom_data_layer_attribute_names(mesh_->vdata, *this, ATTR_DOMAIN_POINT, names); + get_custom_data_layer_attribute_names(mesh_->edata, *this, ATTR_DOMAIN_EDGE, names); + get_custom_data_layer_attribute_names(mesh_->pdata, *this, ATTR_DOMAIN_POLYGON, names); + return names; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 434456a922e..73ca490c395 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -4559,7 +4559,7 @@ void BKE_fluid_particle_system_destroy(struct Object *ob, const int particle_typ if (psys->part->type == particle_type) { /* clear modifier */ pfmd = psys_get_modifier(ob, psys); - BLI_remlink(&ob->modifiers, pfmd); + BLI_remlink(&ob->modifiers, (ModifierData *)pfmd); BKE_modifier_free((ModifierData *)pfmd); /* clear particle system */ diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc new file mode 100644 index 00000000000..28695d769d3 --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -0,0 +1,554 @@ +/* + * 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 "BKE_geometry_set.hh" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_pointcloud.h" + +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +using blender::float3; +using blender::MutableSpan; +using blender::Span; +using blender::StringRef; +using blender::Vector; + +/* -------------------------------------------------------------------- */ +/** \name Geometry Component + * \{ */ + +GeometryComponent::GeometryComponent(GeometryComponentType type) : type_(type) +{ +} + +GeometryComponent ::~GeometryComponent() +{ +} + +GeometryComponent *GeometryComponent::create(GeometryComponentType component_type) +{ + switch (component_type) { + case GeometryComponentType::Mesh: + return new MeshComponent(); + case GeometryComponentType::PointCloud: + return new PointCloudComponent(); + case GeometryComponentType::Instances: + return new InstancesComponent(); + } + BLI_assert(false); + return nullptr; +} + +void GeometryComponent::user_add() const +{ + users_.fetch_add(1); +} + +void GeometryComponent::user_remove() const +{ + const int new_users = users_.fetch_sub(1) - 1; + if (new_users == 0) { + delete this; + } +} + +bool GeometryComponent::is_mutable() const +{ + /* If the item is shared, it is read-only. */ + /* The user count can be 0, when this is called from the destructor. */ + return users_ <= 1; +} + +GeometryComponentType GeometryComponent::type() const +{ + return type_; +} + +bool GeometryComponent::is_empty() const +{ + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Geometry Set + * \{ */ + +/* This method can only be used when the geometry set is mutable. It returns a mutable geometry + * component of the given type. + */ +GeometryComponent &GeometrySet::get_component_for_write(GeometryComponentType component_type) +{ + return components_.add_or_modify( + component_type, + [&](GeometryComponentPtr *value_ptr) -> GeometryComponent & { + /* If the component did not exist before, create a new one. */ + new (value_ptr) GeometryComponentPtr(GeometryComponent::create(component_type)); + return **value_ptr; + }, + [&](GeometryComponentPtr *value_ptr) -> GeometryComponent & { + GeometryComponentPtr &value = *value_ptr; + if (value->is_mutable()) { + /* If the referenced component is already mutable, return it directly. */ + return *value; + } + /* If the referenced component is shared, make a copy. The copy is not shared and is + * therefore mutable. */ + GeometryComponent *copied_component = value->copy(); + value = GeometryComponentPtr{copied_component}; + return *copied_component; + }); +} + +/* Get the component of the given type. Might return null if the component does not exist yet. */ +const GeometryComponent *GeometrySet::get_component_for_read( + GeometryComponentType component_type) const +{ + const GeometryComponentPtr *component = components_.lookup_ptr(component_type); + if (component != nullptr) { + return component->get(); + } + return nullptr; +} + +bool GeometrySet::has(const GeometryComponentType component_type) const +{ + return components_.contains(component_type); +} + +void GeometrySet::remove(const GeometryComponentType component_type) +{ + components_.remove(component_type); +} + +void GeometrySet::add(const GeometryComponent &component) +{ + BLI_assert(!components_.contains(component.type())); + component.user_add(); + GeometryComponentPtr component_ptr{const_cast<GeometryComponent *>(&component)}; + components_.add_new(component.type(), std::move(component_ptr)); +} + +void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_max) const +{ + const PointCloud *pointcloud = this->get_pointcloud_for_read(); + if (pointcloud != nullptr) { + BKE_pointcloud_minmax(pointcloud, *r_min, *r_max); + } + const Mesh *mesh = this->get_mesh_for_read(); + if (mesh != nullptr) { + BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max); + } +} + +std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) +{ + stream << "<GeometrySet at " << &geometry_set << ", " << geometry_set.components_.size() + << " components>"; + return stream; +} + +/* This generally should not be used. It is necessary currently, so that GeometrySet can by used by + * the CPPType system. */ +bool operator==(const GeometrySet &UNUSED(a), const GeometrySet &UNUSED(b)) +{ + return false; +} + +/* This generally should not be used. It is necessary currently, so that GeometrySet can by used by + * the CPPType system. */ +uint64_t GeometrySet::hash() const +{ + return reinterpret_cast<uint64_t>(this); +} + +/* Returns a read-only mesh or null. */ +const Mesh *GeometrySet::get_mesh_for_read() const +{ + const MeshComponent *component = this->get_component_for_read<MeshComponent>(); + return (component == nullptr) ? nullptr : component->get_for_read(); +} + +/* Returns true when the geometry set has a mesh component that has a mesh. */ +bool GeometrySet::has_mesh() const +{ + const MeshComponent *component = this->get_component_for_read<MeshComponent>(); + return component != nullptr && component->has_mesh(); +} + +/* Returns a read-only point cloud of null. */ +const PointCloud *GeometrySet::get_pointcloud_for_read() const +{ + const PointCloudComponent *component = this->get_component_for_read<PointCloudComponent>(); + return (component == nullptr) ? nullptr : component->get_for_read(); +} + +/* Returns true when the geometry set has a point cloud component that has a point cloud. */ +bool GeometrySet::has_pointcloud() const +{ + const PointCloudComponent *component = this->get_component_for_read<PointCloudComponent>(); + return component != nullptr && component->has_pointcloud(); +} + +/* Returns true when the geometry set has an instances component that has at least one instance. */ +bool GeometrySet::has_instances() const +{ + const InstancesComponent *component = this->get_component_for_read<InstancesComponent>(); + return component != nullptr && component->instances_amount() >= 1; +} + +/* Create a new geometry set that only contains the given mesh. */ +GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) +{ + GeometrySet geometry_set; + MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>(); + component.replace(mesh, ownership); + return geometry_set; +} + +/* Create a new geometry set that only contains the given point cloud. */ +GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud, + GeometryOwnershipType ownership) +{ + GeometrySet geometry_set; + PointCloudComponent &component = geometry_set.get_component_for_write<PointCloudComponent>(); + component.replace(pointcloud, ownership); + return geometry_set; +} + +/* Clear the existing mesh and replace it with the given one. */ +void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) +{ + MeshComponent &component = this->get_component_for_write<MeshComponent>(); + component.replace(mesh, ownership); +} + +/* Clear the existing point cloud and replace with the given one. */ +void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership) +{ + PointCloudComponent &pointcloud_component = this->get_component_for_write<PointCloudComponent>(); + pointcloud_component.replace(pointcloud, ownership); +} + +/* Returns a mutable mesh or null. No ownership is transferred. */ +Mesh *GeometrySet::get_mesh_for_write() +{ + MeshComponent &component = this->get_component_for_write<MeshComponent>(); + return component.get_for_write(); +} + +/* Returns a mutable point cloud or null. No ownership is transferred. */ +PointCloud *GeometrySet::get_pointcloud_for_write() +{ + PointCloudComponent &component = this->get_component_for_write<PointCloudComponent>(); + return component.get_for_write(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mesh Component + * \{ */ + +MeshComponent::MeshComponent() : GeometryComponent(GeometryComponentType::Mesh) +{ +} + +MeshComponent::~MeshComponent() +{ + this->clear(); +} + +GeometryComponent *MeshComponent::copy() const +{ + MeshComponent *new_component = new MeshComponent(); + if (mesh_ != nullptr) { + new_component->mesh_ = BKE_mesh_copy_for_eval(mesh_, false); + new_component->ownership_ = GeometryOwnershipType::Owned; + } + return new_component; +} + +void MeshComponent::clear() +{ + BLI_assert(this->is_mutable()); + if (mesh_ != nullptr) { + if (ownership_ == GeometryOwnershipType::Owned) { + BKE_id_free(nullptr, mesh_); + } + mesh_ = nullptr; + } + vertex_group_names_.clear(); +} + +bool MeshComponent::has_mesh() const +{ + return mesh_ != nullptr; +} + +/* Clear the component and replace it with the new mesh. */ +void MeshComponent::replace(Mesh *mesh, GeometryOwnershipType ownership) +{ + BLI_assert(this->is_mutable()); + this->clear(); + mesh_ = mesh; + ownership_ = ownership; +} + +/* Return the mesh and clear the component. The caller takes over responsibility for freeing the + * mesh (if the component was responsible before). */ +Mesh *MeshComponent::release() +{ + BLI_assert(this->is_mutable()); + Mesh *mesh = mesh_; + mesh_ = nullptr; + return mesh; +} + +void MeshComponent::copy_vertex_group_names_from_object(const Object &object) +{ + BLI_assert(this->is_mutable()); + vertex_group_names_.clear(); + int index = 0; + LISTBASE_FOREACH (const bDeformGroup *, group, &object.defbase) { + vertex_group_names_.add(group->name, index); + index++; + } +} + +/* Get the mesh from this component. This method can be used by multiple threads at the same + * time. Therefore, the returned mesh should not be modified. No ownership is transferred. */ +const Mesh *MeshComponent::get_for_read() const +{ + return mesh_; +} + +/* Get the mesh from this component. This method can only be used when the component is mutable, + * i.e. it is not shared. The returned mesh can be modified. No ownership is transferred. */ +Mesh *MeshComponent::get_for_write() +{ + BLI_assert(this->is_mutable()); + if (ownership_ == GeometryOwnershipType::ReadOnly) { + mesh_ = BKE_mesh_copy_for_eval(mesh_, false); + ownership_ = GeometryOwnershipType::Owned; + } + return mesh_; +} + +bool MeshComponent::is_empty() const +{ + return mesh_ == nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pointcloud Component + * \{ */ + +PointCloudComponent::PointCloudComponent() : GeometryComponent(GeometryComponentType::PointCloud) +{ +} + +PointCloudComponent::~PointCloudComponent() +{ + this->clear(); +} + +GeometryComponent *PointCloudComponent::copy() const +{ + PointCloudComponent *new_component = new PointCloudComponent(); + if (pointcloud_ != nullptr) { + new_component->pointcloud_ = BKE_pointcloud_copy_for_eval(pointcloud_, false); + new_component->ownership_ = GeometryOwnershipType::Owned; + } + return new_component; +} + +void PointCloudComponent::clear() +{ + BLI_assert(this->is_mutable()); + if (pointcloud_ != nullptr) { + if (ownership_ == GeometryOwnershipType::Owned) { + BKE_id_free(nullptr, pointcloud_); + } + pointcloud_ = nullptr; + } +} + +bool PointCloudComponent::has_pointcloud() const +{ + return pointcloud_ != nullptr; +} + +/* Clear the component and replace it with the new point cloud. */ +void PointCloudComponent::replace(PointCloud *pointcloud, GeometryOwnershipType ownership) +{ + BLI_assert(this->is_mutable()); + this->clear(); + pointcloud_ = pointcloud; + ownership_ = ownership; +} + +/* Return the point cloud and clear the component. The caller takes over responsibility for freeing + * the point cloud (if the component was responsible before). */ +PointCloud *PointCloudComponent::release() +{ + BLI_assert(this->is_mutable()); + PointCloud *pointcloud = pointcloud_; + pointcloud_ = nullptr; + return pointcloud; +} + +/* Get the point cloud from this component. This method can be used by multiple threads at the same + * time. Therefore, the returned point cloud should not be modified. No ownership is transferred. + */ +const PointCloud *PointCloudComponent::get_for_read() const +{ + return pointcloud_; +} + +/* Get the point cloud from this component. This method can only be used when the component is + * mutable, i.e. it is not shared. The returned point cloud can be modified. No ownership is + * transferred. */ +PointCloud *PointCloudComponent::get_for_write() +{ + BLI_assert(this->is_mutable()); + if (ownership_ == GeometryOwnershipType::ReadOnly) { + pointcloud_ = BKE_pointcloud_copy_for_eval(pointcloud_, false); + ownership_ = GeometryOwnershipType::Owned; + } + return pointcloud_; +} + +bool PointCloudComponent::is_empty() const +{ + return pointcloud_ == nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Instances Component + * \{ */ + +InstancesComponent::InstancesComponent() : GeometryComponent(GeometryComponentType::Instances) +{ +} + +GeometryComponent *InstancesComponent::copy() const +{ + InstancesComponent *new_component = new InstancesComponent(); + new_component->positions_ = positions_; + new_component->rotations_ = rotations_; + new_component->scales_ = scales_; + new_component->objects_ = objects_; + return new_component; +} + +void InstancesComponent::clear() +{ + objects_.clear(); + positions_.clear(); + rotations_.clear(); + scales_.clear(); +} +void InstancesComponent::add_instance(const Object *object, + blender::float3 position, + blender::float3 rotation, + blender::float3 scale) +{ + objects_.append(object); + positions_.append(position); + rotations_.append(rotation); + scales_.append(scale); +} + +Span<const Object *> InstancesComponent::objects() const +{ + return objects_; +} + +Span<float3> InstancesComponent::positions() const +{ + return positions_; +} + +blender::Span<blender::float3> InstancesComponent::rotations() const +{ + return rotations_; +} + +blender::Span<blender::float3> InstancesComponent::scales() const +{ + return scales_; +} + +MutableSpan<float3> InstancesComponent::positions() +{ + return positions_; +} + +int InstancesComponent::instances_amount() const +{ + BLI_assert(positions_.size() == objects_.size()); + return objects_.size(); +} + +bool InstancesComponent::is_empty() const +{ + return positions_.size() == 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name C API + * \{ */ + +void BKE_geometry_set_free(GeometrySet *geometry_set) +{ + delete geometry_set; +} + +bool BKE_geometry_set_has_instances(const GeometrySet *geometry_set) +{ + return geometry_set->get_component_for_read<InstancesComponent>() != nullptr; +} + +int BKE_geometry_set_instances(const GeometrySet *geometry_set, + float (**r_positions)[3], + float (**r_rotations)[3], + float (**r_scales)[3], + Object ***r_objects) +{ + const InstancesComponent *component = geometry_set->get_component_for_read<InstancesComponent>(); + if (component == nullptr) { + return 0; + } + *r_positions = (float(*)[3])component->positions().data(); + *r_rotations = (float(*)[3])component->rotations().data(); + *r_scales = (float(*)[3])component->scales().data(); + *r_objects = (Object **)component->objects().data(); + return component->instances_amount(); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c index 554919ad1a0..a44b054e366 100644 --- a/source/blender/blenkernel/intern/hair.c +++ b/source/blender/blenkernel/intern/hair.c @@ -49,8 +49,8 @@ #include "BLO_read_write.h" -static const char *HAIR_ATTR_POSITION = "Position"; -static const char *HAIR_ATTR_RADIUS = "Radius"; +static const char *HAIR_ATTR_POSITION = "position"; +static const char *HAIR_ATTR_RADIUS = "radius"; /* Hair datablock */ diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index f0031d4191d..9e3189afee9 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -342,7 +342,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id) { /* Update all group nodes using a node group. */ - ntreeUpdateAllUsers(bmain, new_id); + ntreeUpdateAllUsers(bmain, (bNodeTree *)new_id); } /** diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c index e639bb319a3..b564a4c468b 100644 --- a/source/blender/blenkernel/intern/node.c +++ b/source/blender/blenkernel/intern/node.c @@ -39,6 +39,7 @@ #include "DNA_light_types.h" #include "DNA_linestyle_types.h" #include "DNA_material_types.h" +#include "DNA_modifier_types.h" #include "DNA_node_types.h" #include "DNA_scene_types.h" #include "DNA_simulation_types.h" @@ -65,7 +66,6 @@ #include "BKE_lib_query.h" #include "BKE_main.h" #include "BKE_node.h" -#include "BKE_simulation.h" #include "BLI_ghash.h" #include "BLI_threads.h" @@ -75,8 +75,8 @@ #include "NOD_common.h" #include "NOD_composite.h" #include "NOD_function.h" +#include "NOD_geometry.h" #include "NOD_shader.h" -#include "NOD_simulation.h" #include "NOD_socket.h" #include "NOD_texture.h" @@ -85,6 +85,8 @@ #include "BLO_read_write.h" +#include "MOD_nodes.h" + #define NODE_DEFAULT_MAX_WIDTH 700 /* Fallback types for undefined tree, nodes, sockets */ @@ -3958,14 +3960,18 @@ void ntreeUpdateAllNew(Main *main) FOREACH_NODETREE_END; } -void ntreeUpdateAllUsers(Main *main, ID *ngroup) +void ntreeUpdateAllUsers(Main *main, bNodeTree *ngroup) { + if (ngroup == NULL) { + return; + } + /* Update all users of ngroup, to add/remove sockets as needed. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { bool need_update = false; LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id == ngroup) { + if (node->id == &ngroup->id) { if (node->typeinfo->group_update_func) { node->typeinfo->group_update_func(ntree, node); } @@ -3979,6 +3985,19 @@ void ntreeUpdateAllUsers(Main *main, ID *ngroup) } } FOREACH_NODETREE_END; + + if (ngroup->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (Object *, object, &main->objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + if (nmd->node_group == ngroup) { + MOD_nodes_update_interface(object, nmd); + } + } + } + } + } } void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) @@ -4022,7 +4041,7 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) } if (bmain) { - ntreeUpdateAllUsers(bmain, &ntree->id); + ntreeUpdateAllUsers(bmain, ntree); } if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { @@ -4659,9 +4678,21 @@ static void registerTextureNodes(void) register_node_type_tex_proc_distnoise(); } -static void registerSimulationNodes(void) +static void registerGeometryNodes(void) { - register_node_type_sim_group(); + register_node_type_geo_group(); + + register_node_type_geo_triangulate(); + register_node_type_geo_edge_split(); + register_node_type_geo_transform(); + register_node_type_geo_subdivision_surface(); + register_node_type_geo_boolean(); + register_node_type_geo_point_distribute(); + register_node_type_geo_point_instance(); + register_node_type_geo_object_info(); + register_node_type_geo_random_attribute(); + register_node_type_geo_attribute_math(); + register_node_type_geo_join_geometry(); } static void registerFunctionNodes(void) @@ -4688,7 +4719,7 @@ void BKE_node_system_init(void) register_node_tree_type_cmp(); register_node_tree_type_sh(); register_node_tree_type_tex(); - register_node_tree_type_sim(); + register_node_tree_type_geo(); register_node_type_frame(); register_node_type_reroute(); @@ -4698,7 +4729,7 @@ void BKE_node_system_init(void) registerCompositNodes(); registerShaderNodes(); registerTextureNodes(); - registerSimulationNodes(); + registerGeometryNodes(); registerFunctionNodes(); } diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 242c0edd5a4..31d2e3738f4 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -94,6 +94,7 @@ #include "BKE_fcurve.h" #include "BKE_fcurve_driver.h" #include "BKE_font.h" +#include "BKE_geometry_set.h" #include "BKE_global.h" #include "BKE_gpencil.h" #include "BKE_gpencil_geom.h" @@ -1284,8 +1285,7 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); } if (ob->type == OB_POINTCLOUD) { - return (mti->modifyPointCloud != NULL) || - (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); + return (mti->modifyPointCloud != NULL); } if (ob->type == OB_VOLUME) { return (mti->modifyVolume != NULL); @@ -1507,6 +1507,9 @@ void BKE_object_eval_assign_data(Object *object_eval, ID *data_eval, bool is_own object_eval->data = data_eval; } } + + /* Is set separately currently. */ + object_eval->runtime.geometry_set_eval = NULL; } /** @@ -1551,6 +1554,11 @@ void BKE_object_free_derived_caches(Object *ob) BKE_gpencil_eval_delete(ob->runtime.gpd_eval); ob->runtime.gpd_eval = NULL; } + + if (ob->runtime.geometry_set_eval != NULL) { + BKE_geometry_set_free(ob->runtime.geometry_set_eval); + ob->runtime.geometry_set_eval = NULL; + } } void BKE_object_free_caches(Object *object) @@ -1771,6 +1779,10 @@ int BKE_object_visibility(const Object *ob, const int dag_eval_mode) visibility |= OB_VISIBLE_INSTANCES; } + if (ob->runtime.geometry_set_eval != NULL) { + visibility |= OB_VISIBLE_INSTANCES; + } + /* Optional hiding of self if there are particles or instancers. */ if (visibility & (OB_VISIBLE_PARTICLES | OB_VISIBLE_INSTANCES)) { switch ((eEvaluationMode)dag_eval_mode) { @@ -4880,6 +4892,7 @@ void BKE_object_runtime_reset_on_copy(Object *object, const int UNUSED(flag)) runtime->mesh_deform_eval = NULL; runtime->curve_cache = NULL; runtime->object_as_temp_mesh = NULL; + runtime->geometry_set_eval = NULL; } /** diff --git a/source/blender/blenkernel/intern/object_dupli.c b/source/blender/blenkernel/intern/object_dupli.c index a1b01bce6c9..9a8c560f116 100644 --- a/source/blender/blenkernel/intern/object_dupli.c +++ b/source/blender/blenkernel/intern/object_dupli.c @@ -47,6 +47,7 @@ #include "BKE_editmesh.h" #include "BKE_editmesh_cache.h" #include "BKE_font.h" +#include "BKE_geometry_set.h" #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_lattice.h" @@ -807,6 +808,45 @@ static const DupliGenerator gen_dupli_verts_pointcloud = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Instances Geometry Component Implementation + * \{ */ + +static void make_duplis_instances_component(const DupliContext *ctx) +{ + float(*positions)[3]; + float(*rotations)[3]; + float(*scales)[3]; + Object **objects; + const int amount = BKE_geometry_set_instances( + ctx->object->runtime.geometry_set_eval, &positions, &rotations, &scales, &objects); + + for (int i = 0; i < amount; i++) { + Object *object = objects[i]; + if (object == NULL) { + continue; + } + float scale_matrix[4][4]; + size_to_mat4(scale_matrix, scales[i]); + float rotation_matrix[4][4]; + eul_to_mat4(rotation_matrix, rotations[i]); + float matrix[4][4]; + mul_m4_m4m4(matrix, rotation_matrix, scale_matrix); + copy_v3_v3(matrix[3], positions[i]); + mul_m4_m4_pre(matrix, ctx->object->obmat); + + make_dupli(ctx, object, matrix, i); + make_recursive_duplis(ctx, object, matrix, i); + } +} + +static const DupliGenerator gen_dupli_instances_component = { + 0, + make_duplis_instances_component, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Dupli-Faces Implementation (#OB_DUPLIFACES) * \{ */ @@ -1473,7 +1513,7 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) int transflag = ctx->object->transflag; int restrictflag = ctx->object->restrictflag; - if ((transflag & OB_DUPLI) == 0) { + if ((transflag & OB_DUPLI) == 0 && ctx->object->runtime.geometry_set_eval == NULL) { return NULL; } @@ -1483,6 +1523,12 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) return NULL; } + if (ctx->object->runtime.geometry_set_eval != NULL) { + if (BKE_geometry_set_has_instances(ctx->object->runtime.geometry_set_eval)) { + return &gen_dupli_instances_component; + } + } + if (transflag & OB_DUPLIPARTS) { return &gen_dupli_particles; } diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c index 72115db4fb8..14e049384ef 100644 --- a/source/blender/blenkernel/intern/pointcache.c +++ b/source/blender/blenkernel/intern/pointcache.c @@ -1198,9 +1198,6 @@ static bool foreach_object_modifier_ptcache(Object *object, } } } - else if (md->type == eModifierType_Simulation) { - /* TODO(jacques): */ - } } return true; } diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index cbfb1086e8d..5f6685817b9 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -33,12 +33,14 @@ #include "BKE_anim_data.h" #include "BKE_customdata.h" +#include "BKE_geometry_set.hh" #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_object.h" #include "BKE_pointcloud.h" @@ -53,8 +55,8 @@ static void pointcloud_random(PointCloud *pointcloud); -const char *POINTCLOUD_ATTR_POSITION = "Position"; -const char *POINTCLOUD_ATTR_RADIUS = "Radius"; +const char *POINTCLOUD_ATTR_POSITION = "position"; +const char *POINTCLOUD_ATTR_RADIUS = "radius"; static void pointcloud_init_data(ID *id) { @@ -86,6 +88,8 @@ static void pointcloud_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_s alloc_type, pointcloud_dst->totpoint); BKE_pointcloud_update_customdata_pointers(pointcloud_dst); + + pointcloud_dst->batch_cache = nullptr; } static void pointcloud_free_data(ID *id) @@ -230,10 +234,46 @@ void *BKE_pointcloud_add_default(Main *bmain, const char *name) return pointcloud; } +PointCloud *BKE_pointcloud_new_nomain(const int totpoint) +{ + PointCloud *pointcloud = static_cast<PointCloud *>(BKE_libblock_alloc( + nullptr, ID_PT, BKE_idtype_idcode_to_name(ID_PT), LIB_ID_CREATE_LOCALIZE)); + + pointcloud_init_data(&pointcloud->id); + + pointcloud->totpoint = totpoint; + + CustomData_add_layer_named(&pointcloud->pdata, + CD_PROP_FLOAT, + CD_CALLOC, + nullptr, + pointcloud->totpoint, + POINTCLOUD_ATTR_RADIUS); + + pointcloud->totpoint = totpoint; + CustomData_realloc(&pointcloud->pdata, pointcloud->totpoint); + BKE_pointcloud_update_customdata_pointers(pointcloud); + + return pointcloud; +} + +void BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]) +{ + float(*pointcloud_co)[3] = pointcloud->co; + float *pointcloud_radius = pointcloud->radius; + for (int a = 0; a < pointcloud->totpoint; a++) { + float *co = pointcloud_co[a]; + float radius = (pointcloud_radius) ? pointcloud_radius[a] : 0.0f; + const float co_min[3] = {co[0] - radius, co[1] - radius, co[2] - radius}; + const float co_max[3] = {co[0] + radius, co[1] + radius, co[2] + radius}; + DO_MIN(co_min, r_min); + DO_MAX(co_max, r_max); + } +} + BoundBox *BKE_pointcloud_boundbox_get(Object *ob) { BLI_assert(ob->type == OB_POINTCLOUD); - PointCloud *pointcloud = static_cast<PointCloud *>(ob->data); if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { return ob->runtime.bb; @@ -241,23 +281,18 @@ BoundBox *BKE_pointcloud_boundbox_get(Object *ob) if (ob->runtime.bb == nullptr) { ob->runtime.bb = static_cast<BoundBox *>(MEM_callocN(sizeof(BoundBox), "pointcloud boundbox")); + } - float min[3], max[3]; - INIT_MINMAX(min, max); - - float(*pointcloud_co)[3] = pointcloud->co; - float *pointcloud_radius = pointcloud->radius; - for (int a = 0; a < pointcloud->totpoint; a++) { - float *co = pointcloud_co[a]; - float radius = (pointcloud_radius) ? pointcloud_radius[a] : 0.0f; - const float co_min[3] = {co[0] - radius, co[1] - radius, co[2] - radius}; - const float co_max[3] = {co[0] + radius, co[1] + radius, co[2] + radius}; - DO_MIN(co_min, min); - DO_MAX(co_max, max); - } - - BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + blender::float3 min, max; + INIT_MINMAX(min, max); + if (ob->runtime.geometry_set_eval != nullptr) { + ob->runtime.geometry_set_eval->compute_boundbox_without_instances(&min, &max); + } + else { + const PointCloud *pointcloud = static_cast<PointCloud *>(ob->data); + BKE_pointcloud_minmax(pointcloud, min, max); } + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); return ob->runtime.bb; } @@ -306,13 +341,11 @@ PointCloud *BKE_pointcloud_copy_for_eval(struct PointCloud *pointcloud_src, bool return result; } -static PointCloud *pointcloud_evaluate_modifiers(struct Depsgraph *depsgraph, - struct Scene *scene, - Object *object, - PointCloud *pointcloud_input) +static void pointcloud_evaluate_modifiers(struct Depsgraph *depsgraph, + struct Scene *scene, + Object *object, + GeometrySet &geometry_set) { - PointCloud *pointcloud = pointcloud_input; - /* Modifier evaluation modes. */ const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); const int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; @@ -332,40 +365,10 @@ static PointCloud *pointcloud_evaluate_modifiers(struct Depsgraph *depsgraph, continue; } - if ((mti->type == eModifierTypeType_OnlyDeform) && - (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly)) { - /* Ensure we are not modifying the input. */ - if (pointcloud == pointcloud_input) { - pointcloud = BKE_pointcloud_copy_for_eval(pointcloud, true); - } - - /* Ensure we are not overwriting referenced data. */ - CustomData_duplicate_referenced_layer_named( - &pointcloud->pdata, CD_PROP_FLOAT3, POINTCLOUD_ATTR_POSITION, pointcloud->totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud); - - /* Created deformed coordinates array on demand. */ - mti->deformVerts(md, &mectx, nullptr, pointcloud->co, pointcloud->totpoint); - } - else if (mti->modifyPointCloud) { - /* Ensure we are not modifying the input. */ - if (pointcloud == pointcloud_input) { - pointcloud = BKE_pointcloud_copy_for_eval(pointcloud, true); - } - - PointCloud *pointcloud_next = mti->modifyPointCloud(md, &mectx, pointcloud); - - if (pointcloud_next && pointcloud_next != pointcloud) { - /* If the modifier returned a new pointcloud, release the old one. */ - if (pointcloud != pointcloud_input) { - BKE_id_free(nullptr, pointcloud); - } - pointcloud = pointcloud_next; - } + if (mti->modifyPointCloud) { + mti->modifyPointCloud(md, &mectx, &geometry_set); } } - - return pointcloud; } void BKE_pointcloud_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Object *object) @@ -375,12 +378,14 @@ void BKE_pointcloud_data_update(struct Depsgraph *depsgraph, struct Scene *scene /* Evaluate modifiers. */ PointCloud *pointcloud = static_cast<PointCloud *>(object->data); - PointCloud *pointcloud_eval = pointcloud_evaluate_modifiers( - depsgraph, scene, object, pointcloud); + GeometrySet geometry_set = GeometrySet::create_with_pointcloud(pointcloud, + GeometryOwnershipType::ReadOnly); + pointcloud_evaluate_modifiers(depsgraph, scene, object, geometry_set); /* Assign evaluated object. */ - const bool is_owned = (pointcloud != pointcloud_eval); - BKE_object_eval_assign_data(object, &pointcloud_eval->id, is_owned); + PointCloud *dummy_pointcloud = BKE_pointcloud_new_nomain(0); + BKE_object_eval_assign_data(object, &dummy_pointcloud->id, true); + object->runtime.geometry_set_eval = new GeometrySet(geometry_set); } /* Draw Cache */ diff --git a/source/blender/blenkernel/intern/simulation.cc b/source/blender/blenkernel/intern/simulation.cc index 861779ec700..14e6ce63023 100644 --- a/source/blender/blenkernel/intern/simulation.cc +++ b/source/blender/blenkernel/intern/simulation.cc @@ -48,8 +48,8 @@ #include "BKE_pointcache.h" #include "BKE_simulation.h" +#include "NOD_geometry.h" #include "NOD_node_tree_multi_function.hh" -#include "NOD_simulation.h" #include "BLI_map.hh" #include "BLT_translation.h" @@ -70,7 +70,7 @@ static void simulation_init_data(ID *id) MEMCPY_STRUCT_AFTER(simulation, DNA_struct_default_get(Simulation), id); - bNodeTree *ntree = ntreeAddTree(nullptr, "Simulation Nodetree", ntreeType_Simulation->idname); + bNodeTree *ntree = ntreeAddTree(nullptr, "Geometry Nodetree", ntreeType_Geometry->idname); simulation->nodetree = ntree; } diff --git a/source/blender/blenlib/BLI_rand.h b/source/blender/blenlib/BLI_rand.h index 78a323c2431..ec74cef9311 100644 --- a/source/blender/blenlib/BLI_rand.h +++ b/source/blender/blenlib/BLI_rand.h @@ -60,6 +60,12 @@ void BLI_rng_get_tri_sample_float_v2(struct RNG *rng, const float v2[2], const float v3[2], float r_pt[2]) ATTR_NONNULL(); +void BLI_rng_get_tri_sample_float_v3(RNG *rng, + const float v1[3], + const float v2[3], + const float v3[3], + float r_pt[3]) ATTR_NONNULL(); + void BLI_rng_shuffle_array(struct RNG *rng, void *data, unsigned int elem_size_i, diff --git a/source/blender/blenlib/BLI_rand.hh b/source/blender/blenlib/BLI_rand.hh index 1b321bd7bcd..0c0dd464d4d 100644 --- a/source/blender/blenlib/BLI_rand.hh +++ b/source/blender/blenlib/BLI_rand.hh @@ -118,6 +118,7 @@ class RandomNumberGenerator { float2 get_unit_float2(); float3 get_unit_float3(); float2 get_triangle_sample(float2 v1, float2 v2, float2 v3); + float3 get_triangle_sample_3d(float3 v1, float3 v2, float3 v3); void get_bytes(MutableSpan<char> r_bytes); /** diff --git a/source/blender/blenlib/BLI_user_counter.hh b/source/blender/blenlib/BLI_user_counter.hh new file mode 100644 index 00000000000..ef276814981 --- /dev/null +++ b/source/blender/blenlib/BLI_user_counter.hh @@ -0,0 +1,158 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include <atomic> + +namespace blender { + +/** + * A simple automatic reference counter. It is similar to std::shared_ptr, but expects that the + * reference count is inside the object. + */ +template<typename T> class UserCounter { + private: + T *data_ = nullptr; + + public: + UserCounter() = default; + + UserCounter(T *data) : data_(data) + { + } + + UserCounter(const UserCounter &other) : data_(other.data_) + { + this->user_add(data_); + } + + UserCounter(UserCounter &&other) : data_(other.data_) + { + other.data_ = nullptr; + } + + ~UserCounter() + { + this->user_remove(data_); + } + + UserCounter &operator=(const UserCounter &other) + { + if (this == &other) { + return *this; + } + + this->user_remove(data_); + data_ = other.data_; + this->user_add(data_); + return *this; + } + + UserCounter &operator=(UserCounter &&other) + { + if (this == &other) { + return *this; + } + + this->user_remove(data_); + data_ = other.data_; + other.data_ = nullptr; + return *this; + } + + T *operator->() + { + BLI_assert(data_ != nullptr); + return data_; + } + + T &operator*() + { + BLI_assert(data_ != nullptr); + return *data_; + } + + operator bool() const + { + return data_ != nullptr; + } + + T *get() + { + return data_; + } + + const T *get() const + { + return data_; + } + + T *release() + { + T *data = data_; + data_ = nullptr; + return data; + } + + void reset() + { + this->user_remove(data_); + data_ = nullptr; + } + + bool has_value() const + { + return data_ != nullptr; + } + + uint64_t hash() const + { + return DefaultHash<T *>{}(data_); + } + + friend bool operator==(const UserCounter &a, const UserCounter &b) + { + return a.data_ == b.data_; + } + + friend std::ostream &operator<<(std::ostream &stream, const UserCounter &value) + { + stream << value.data_; + return stream; + } + + private: + static void user_add(T *data) + { + if (data != nullptr) { + data->user_add(); + } + } + + static void user_remove(T *data) + { + if (data != nullptr) { + data->user_remove(); + } + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 9736d8f9b3b..46a3ad87dfe 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -279,6 +279,7 @@ set(SRC BLI_timecode.h BLI_timeit.hh BLI_timer.h + BLI_user_counter.hh BLI_utildefines.h BLI_utildefines_iter.h BLI_utildefines_stack.h diff --git a/source/blender/blenlib/intern/rand.cc b/source/blender/blenlib/intern/rand.cc index 224db31471d..c61e17e6627 100644 --- a/source/blender/blenlib/intern/rand.cc +++ b/source/blender/blenlib/intern/rand.cc @@ -141,6 +141,12 @@ void BLI_rng_get_tri_sample_float_v2( copy_v2_v2(r_pt, rng->rng.get_triangle_sample(v1, v2, v3)); } +void BLI_rng_get_tri_sample_float_v3( + RNG *rng, const float v1[3], const float v2[3], const float v3[3], float r_pt[3]) +{ + copy_v3_v3(r_pt, rng->rng.get_triangle_sample_3d(v1, v2, v3)); +} + void BLI_rng_shuffle_array(RNG *rng, void *data, unsigned int elem_size_i, unsigned int elem_tot) { const uint elem_size = elem_size_i; @@ -425,6 +431,25 @@ float2 RandomNumberGenerator::get_triangle_sample(float2 v1, float2 v2, float2 v return sample; } +float3 RandomNumberGenerator::get_triangle_sample_3d(float3 v1, float3 v2, float3 v3) +{ + float u = this->get_float(); + float v = this->get_float(); + + if (u + v > 1.0f) { + u = 1.0f - u; + v = 1.0f - v; + } + + float3 side_u = v2 - v1; + float3 side_v = v3 - v1; + + float3 sample = v1; + sample += side_u * u; + sample += side_v * v; + return sample; +} + void RandomNumberGenerator::get_bytes(MutableSpan<char> r_bytes) { constexpr int64_t mask_bytes = 2; diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index aca8d2f3439..9278ff51b8d 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -519,6 +519,20 @@ static void do_versions_point_attributes(CustomData *pdata) } } +static void do_versions_point_attribute_names(CustomData *pdata) +{ + /* Change from capital initial letter to lower case (T82693). */ + for (int i = 0; i < pdata->totlayer; i++) { + CustomDataLayer *layer = &pdata->layers[i]; + if (layer->type == CD_PROP_FLOAT3 && STREQ(layer->name, "Position")) { + STRNCPY(layer->name, "position"); + } + else if (layer->type == CD_PROP_FLOAT && STREQ(layer->name, "Radius")) { + STRNCPY(layer->name, "radius"); + } + } +} + /* Move FCurve handles towards the control point in such a way that the curve itself doesn't * change. Since 2.91 FCurves are computed slightly differently, which requires this update to keep * the same animation result. Previous versions scaled down overlapping handles during evaluation. @@ -1201,5 +1215,13 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + /* Hair and PointCloud attributes names. */ + LISTBASE_FOREACH (Hair *, hair, &bmain->hairs) { + do_versions_point_attribute_names(&hair->pdata); + } + LISTBASE_FOREACH (PointCloud *, pointcloud, &bmain->pointclouds) { + do_versions_point_attribute_names(&pointcloud->pdata); + } } } diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 2f613a0d416..95ae587f4ce 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -24,6 +24,7 @@ set(INC ../blenlib ../bmesh ../draw + ../functions ../makesdna ../makesrna ../modifiers diff --git a/source/blender/depsgraph/DEG_depsgraph_build.h b/source/blender/depsgraph/DEG_depsgraph_build.h index 2147a584765..f894bdabba4 100644 --- a/source/blender/depsgraph/DEG_depsgraph_build.h +++ b/source/blender/depsgraph/DEG_depsgraph_build.h @@ -141,6 +141,9 @@ void DEG_add_object_relation(struct DepsNodeHandle *node_handle, void DEG_add_simulation_relation(struct DepsNodeHandle *node_handle, struct Simulation *simulation, const char *description); +void DEG_add_node_tree_relation(struct DepsNodeHandle *node_handle, + struct bNodeTree *node_tree, + const char *description); void DEG_add_bone_relation(struct DepsNodeHandle *handle, struct Object *object, const char *bone_name, diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc index 96c17ae4dc5..6717ba521f6 100644 --- a/source/blender/depsgraph/intern/depsgraph_build.cc +++ b/source/blender/depsgraph/intern/depsgraph_build.cc @@ -32,6 +32,7 @@ #include "PIL_time_utildefines.h" #include "DNA_cachefile_types.h" +#include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_simulation_types.h" @@ -116,6 +117,17 @@ void DEG_add_simulation_relation(DepsNodeHandle *node_handle, deg_node_handle->builder->add_node_handle_relation(operation_key, deg_node_handle, description); } +void DEG_add_node_tree_relation(DepsNodeHandle *node_handle, + bNodeTree *node_tree, + const char *description) +{ + /* Using shading key, because that's the one that exists right now. Should use something else in + * the future. */ + deg::ComponentKey shading_key(&node_tree->id, deg::NodeType::SHADING); + deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle); + deg_node_handle->builder->add_node_handle_relation(shading_key, deg_node_handle, description); +} + void DEG_add_object_cache_relation(DepsNodeHandle *node_handle, CacheFile *cache_file, eDepsObjectComponentType component, diff --git a/source/blender/editors/include/ED_node.h b/source/blender/editors/include/ED_node.h index ecb98a46f99..417cae800ea 100644 --- a/source/blender/editors/include/ED_node.h +++ b/source/blender/editors/include/ED_node.h @@ -96,7 +96,7 @@ void ED_node_set_tree_type(struct SpaceNode *snode, struct bNodeTreeType *typein bool ED_node_is_compositor(struct SpaceNode *snode); bool ED_node_is_shader(struct SpaceNode *snode); bool ED_node_is_texture(struct SpaceNode *snode); -bool ED_node_is_simulation(struct SpaceNode *snode); +bool ED_node_is_geometry(struct SpaceNode *snode); void ED_node_shader_default(const struct bContext *C, struct ID *id); void ED_node_composit_default(const struct bContext *C, struct Scene *scene); diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index 0b22afb7e66..6bd95cd8e51 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -90,6 +90,8 @@ #include "ED_screen.h" #include "ED_sculpt.h" +#include "MOD_nodes.h" + #include "UI_interface.h" #include "WM_api.h" @@ -232,6 +234,9 @@ ModifierData *ED_object_modifier_add( /* ensure skin-node customdata exists */ BKE_mesh_ensure_skin_customdata(ob->data); } + else if (type == eModifierType_Nodes) { + MOD_nodes_init(bmain, (NodesModifierData *)new_md); + } } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 0757f840375..d8f31161c20 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -73,5 +73,8 @@ if(WITH_OPENIMAGEDENOISE) add_definitions(-DWITH_OPENIMAGEDENOISE) endif() +if(WITH_OPENSUBDIV) + add_definitions(-DWITH_OPENSUBDIV) +endif() blender_add_lib(bf_editor_space_node "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index a0e03490fa3..8fdc86f8599 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -71,8 +71,8 @@ #include "IMB_imbuf_types.h" #include "NOD_composite.h" +#include "NOD_geometry.h" #include "NOD_shader.h" -#include "NOD_simulation.h" #include "NOD_texture.h" #include "node_intern.h" /* own include */ @@ -3142,10 +3142,65 @@ static void node_texture_set_butfunc(bNodeType *ntype) } } -/* ****************** BUTTON CALLBACKS FOR SIMULATION NODES ***************** */ +/* ****************** BUTTON CALLBACKS FOR GEOMETRY NODES ***************** */ -static void node_simulation_set_butfunc(bNodeType *UNUSED(ntype)) +static void node_geometry_buts_boolean_math(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { + uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE); +} + +static void node_geometry_buts_subdivision_surface(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *UNUSED(ptr)) +{ +#ifndef WITH_OPENSUBDIV + uiItemL(layout, IFACE_("Disabled, built without OpenSubdiv"), ICON_ERROR); +#else + UNUSED_VARS(layout); +#endif +} + +static void node_geometry_buts_triangulate(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "quad_method", DEFAULT_FLAGS, "", ICON_NONE); + uiItemR(layout, ptr, "ngon_method", DEFAULT_FLAGS, "", ICON_NONE); +} + +static void node_geometry_buts_random_attribute(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", DEFAULT_FLAGS, "", ICON_NONE); +} + +static void node_geometry_buts_attribute_math(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE); + uiItemR(layout, ptr, "input_type_a", DEFAULT_FLAGS, IFACE_("Type A"), ICON_NONE); + uiItemR(layout, ptr, "input_type_b", DEFAULT_FLAGS, IFACE_("Type B"), ICON_NONE); +} + +static void node_geometry_set_butfunc(bNodeType *ntype) +{ + switch (ntype->type) { + case GEO_NODE_BOOLEAN: + ntype->draw_buttons = node_geometry_buts_boolean_math; + break; + case GEO_NODE_SUBDIVISION_SURFACE: + ntype->draw_buttons = node_geometry_buts_subdivision_surface; + break; + case GEO_NODE_TRIANGULATE: + ntype->draw_buttons = node_geometry_buts_triangulate; + break; + case GEO_NODE_RANDOM_ATTRIBUTE: + ntype->draw_buttons = node_geometry_buts_random_attribute; + break; + case GEO_NODE_ATTRIBUTE_MATH: + ntype->draw_buttons = node_geometry_buts_attribute_math; + break; + } } /* ****************** BUTTON CALLBACKS FOR FUNCTION NODES ***************** */ @@ -3290,7 +3345,7 @@ void ED_node_init_butfuncs(void) node_composit_set_butfunc(ntype); node_shader_set_butfunc(ntype); node_texture_set_butfunc(ntype); - node_simulation_set_butfunc(ntype); + node_geometry_set_butfunc(ntype); node_function_set_butfunc(ntype); /* define update callbacks for socket properties */ @@ -3302,7 +3357,7 @@ void ED_node_init_butfuncs(void) ntreeType_Composite->ui_icon = ICON_NODE_COMPOSITING; ntreeType_Shader->ui_icon = ICON_NODE_MATERIAL; ntreeType_Texture->ui_icon = ICON_NODE_TEXTURE; - ntreeType_Simulation->ui_icon = ICON_PHYSICS; /* TODO: Use correct icon. */ + ntreeType_Geometry->ui_icon = ICON_PHYSICS; /* TODO: Use correct icon. */ } void ED_init_custom_node_type(bNodeType *ntype) diff --git a/source/blender/editors/space_node/node_draw.c b/source/blender/editors/space_node/node_draw.c index a567ae41a54..fb83414e435 100644 --- a/source/blender/editors/space_node/node_draw.c +++ b/source/blender/editors/space_node/node_draw.c @@ -138,6 +138,9 @@ void ED_node_tag_update_id(ID *id) DEG_id_tag_update(id, 0); WM_main_add_notifier(NC_TEXTURE | ND_NODES, id); } + else if (ntree->type == NTREE_GEOMETRY) { + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, id); + } else if (id == &ntree->id) { /* node groups */ DEG_id_tag_update(id, 0); diff --git a/source/blender/editors/space_node/node_edit.c b/source/blender/editors/space_node/node_edit.c index a39e84419d0..039ddad71ef 100644 --- a/source/blender/editors/space_node/node_edit.c +++ b/source/blender/editors/space_node/node_edit.c @@ -68,8 +68,8 @@ #include "IMB_imbuf_types.h" #include "NOD_composite.h" +#include "NOD_geometry.h" #include "NOD_shader.h" -#include "NOD_simulation.h" #include "NOD_texture.h" #include "node_intern.h" /* own include */ @@ -391,6 +391,7 @@ void snode_dag_update(bContext *C, SpaceNode *snode) } DEG_id_tag_update(snode->id, 0); + DEG_id_tag_update(&snode->nodetree->id, 0); } void snode_notify(bContext *C, SpaceNode *snode) @@ -416,6 +417,9 @@ void snode_notify(bContext *C, SpaceNode *snode) else if (ED_node_is_texture(snode)) { WM_event_add_notifier(C, NC_TEXTURE | ND_NODES, id); } + else if (ED_node_is_geometry(snode)) { + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, id); + } } void ED_node_set_tree_type(SpaceNode *snode, bNodeTreeType *typeinfo) @@ -443,9 +447,9 @@ bool ED_node_is_texture(struct SpaceNode *snode) return STREQ(snode->tree_idname, ntreeType_Texture->idname); } -bool ED_node_is_simulation(struct SpaceNode *snode) +bool ED_node_is_geometry(struct SpaceNode *snode) { - return STREQ(snode->tree_idname, ntreeType_Simulation->idname); + return STREQ(snode->tree_idname, ntreeType_Geometry->idname); } /* assumes nothing being done in ntree yet, sets the default in/out node */ @@ -1696,7 +1700,7 @@ static int node_mute_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_simulation(snode); + do_tag_update |= ED_node_is_geometry(snode); snode_notify(C, snode); if (do_tag_update) { @@ -1740,7 +1744,7 @@ static int node_delete_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_simulation(snode); + do_tag_update |= ED_node_is_geometry(snode); ntreeUpdateTree(CTX_data_main(C), snode->edittree); diff --git a/source/blender/editors/space_node/node_group.c b/source/blender/editors/space_node/node_group.c index 654b95858bb..f2abe272f48 100644 --- a/source/blender/editors/space_node/node_group.c +++ b/source/blender/editors/space_node/node_group.c @@ -77,7 +77,7 @@ static bool node_group_operator_active_poll(bContext *C) "ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree", - "SimulationNodeTree")) { + "GeometryNodeTree")) { return true; } } @@ -94,7 +94,7 @@ static bool node_group_operator_editable(bContext *C) * with same keymap. */ if (ED_node_is_shader(snode) || ED_node_is_compositor(snode) || ED_node_is_texture(snode) || - ED_node_is_simulation(snode)) { + ED_node_is_geometry(snode)) { return true; } } @@ -120,8 +120,8 @@ static const char *group_node_idname(bContext *C) if (ED_node_is_texture(snode)) { return "TextureNodeGroup"; } - if (ED_node_is_simulation(snode)) { - return "SimulationNodeGroup"; + if (ED_node_is_geometry(snode)) { + return "GeometryNodeGroup"; } return ""; diff --git a/source/blender/editors/space_node/node_relationships.c b/source/blender/editors/space_node/node_relationships.c index b6d9cdc729d..ba1e752e276 100644 --- a/source/blender/editors/space_node/node_relationships.c +++ b/source/blender/editors/space_node/node_relationships.c @@ -655,7 +655,7 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links) } ntree->is_updating = false; - do_tag_update |= ED_node_is_simulation(snode); + do_tag_update |= ED_node_is_geometry(snode); ntreeUpdateTree(bmain, ntree); snode_notify(C, snode); @@ -1052,7 +1052,7 @@ static int cut_links_exec(bContext *C, wmOperator *op) } } - do_tag_update |= ED_node_is_simulation(snode); + do_tag_update |= ED_node_is_geometry(snode); if (found) { ntreeUpdateTree(CTX_data_main(C), snode->edittree); @@ -1932,6 +1932,7 @@ void ED_node_link_insert(Main *bmain, ScrArea *area) ntreeUpdateTree(bmain, snode->edittree); /* needed for pointers */ snode_update(snode, select); + ED_node_tag_update_id((ID *)snode->edittree); ED_node_tag_update_id(snode->id); } } diff --git a/source/blender/editors/space_node/space_node.c b/source/blender/editors/space_node/space_node.c index 76c25799e2c..609fc43b435 100644 --- a/source/blender/editors/space_node/space_node.c +++ b/source/blender/editors/space_node/space_node.c @@ -932,7 +932,7 @@ static void node_space_subtype_item_extend(bContext *C, EnumPropertyItem **item, const EnumPropertyItem *item_src = RNA_enum_node_tree_types_itemf_impl(C, &free); for (const EnumPropertyItem *item_iter = item_src; item_iter->identifier; item_iter++) { if (!U.experimental.use_new_geometry_nodes && - STREQ(item_iter->identifier, "SimulationNodeTree")) { + STREQ(item_iter->identifier, "GeometryNodeTree")) { continue; } RNA_enum_item_add(item, totitem, item_iter); diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index e005753e228..429959f9c33 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRC FN_attributes_ref.hh FN_cpp_type.hh FN_generic_pointer.hh + FN_generic_value_map.hh FN_generic_vector_array.hh FN_multi_function.hh FN_multi_function_builder.hh diff --git a/source/blender/functions/FN_generic_value_map.hh b/source/blender/functions/FN_generic_value_map.hh new file mode 100644 index 00000000000..2c1b37c0461 --- /dev/null +++ b/source/blender/functions/FN_generic_value_map.hh @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#pragma once + +#include "BLI_linear_allocator.hh" +#include "BLI_map.hh" + +#include "FN_generic_pointer.hh" + +namespace blender::fn { + +/** + * This is a map that stores key-value-pairs. What makes it special is that the type of values does + * not have to be known at compile time. There just has to be a corresponding CPPType. + */ +template<typename Key> class GValueMap { + private: + /* Used to allocate values owned by this container. */ + LinearAllocator<> &allocator_; + Map<Key, GMutablePointer> values_; + + public: + GValueMap(LinearAllocator<> &allocator) : allocator_(allocator) + { + } + + ~GValueMap() + { + /* Destruct all values that are still in the map. */ + for (GMutablePointer value : values_.values()) { + value.destruct(); + } + } + + /* Add a value to the container. The container becomes responsible for destructing the value that + * is passed in. The caller remains responsible for freeing the value after it has been + * destructed. */ + template<typename ForwardKey> void add_new_direct(ForwardKey &&key, GMutablePointer value) + { + values_.add_new_as(std::forward<ForwardKey>(key), value); + } + + /* Add a value to the container that is move constructed from the given value. The caller remains + * responsible for destructing and freeing the given value. */ + template<typename ForwardKey> void add_new_by_move(ForwardKey &&key, GMutablePointer value) + { + const CPPType &type = *value.type(); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.move_to_uninitialized(value.get(), buffer); + values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer}); + } + + /* Add a value to the container that is copy constructed from the given value. The caller remains + * responsible for destructing and freeing the given value. */ + template<typename ForwardKey> void add_new_by_copy(ForwardKey &&key, GMutablePointer value) + { + const CPPType &type = *value.type(); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(value.get(), buffer); + values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer}); + } + + /* Add a value to the container. */ + template<typename ForwardKey, typename T> void add_new(ForwardKey &&key, T &&value) + { + if constexpr (std::is_rvalue_reference_v<T>) { + this->add_new_by_move(std::forward<ForwardKey>(key), &value); + } + else { + this->add_new_by_copy(std::forward<ForwardKey>(key), &value); + } + } + + /* Remove the value for the given name from the container and remove it. The caller is + * responsible for freeing it. The lifetime of the referenced memory might be bound to lifetime + * of the container. */ + template<typename ForwardKey> GMutablePointer extract(const ForwardKey &key) + { + return values_.pop_as(key); + } + + /* Remove the value for the given name from the container and remove it. */ + template<typename T, typename ForwardKey> T extract(const ForwardKey &key) + { + GMutablePointer value = values_.pop_as(key); + const CPPType &type = *value.type(); + BLI_assert(type.is<T>()); + T return_value; + type.relocate_to_initialized(value.get(), &return_value); + return return_value; + } + + template<typename T, typename ForwardKey> T lookup(const ForwardKey &key) const + { + GMutablePointer value = values_.lookup_as(key); + const CPPType &type = *value.type(); + BLI_assert(type.is<T>()); + T return_value; + type.copy_to_initialized(value.get(), &return_value); + return return_value; + } + + template<typename ForwardKey> bool contains(const ForwardKey &key) const + { + return values_.contains_as(key); + } +}; + +} // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh index bf431984946..d8924b3cf23 100644 --- a/source/blender/functions/FN_multi_function.hh +++ b/source/blender/functions/FN_multi_function.hh @@ -122,4 +122,17 @@ inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, int64_t m extern const MultiFunction &dummy_multi_function; +namespace multi_function_types { +using fn::CPPType; +using fn::GMutableSpan; +using fn::GSpan; +using fn::MFContext; +using fn::MFContextBuilder; +using fn::MFDataType; +using fn::MFParams; +using fn::MFParamsBuilder; +using fn::MFParamType; +using fn::MultiFunction; +} // namespace multi_function_types + } // namespace blender::fn diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index b5bcfa4d157..f73f43ddade 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -573,7 +573,7 @@ .flag = 0, \ } -#define _DNA_DEFAULT_SimulationModifierData \ +#define _DNA_DEFAULT_NodesModifierData \ { 0 } #define _DNA_DEFAULT_SkinModifierData \ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 7b5bdac47f5..43ed532a65b 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -94,7 +94,7 @@ typedef enum ModifierType { eModifierType_WeightedNormal = 54, eModifierType_Weld = 55, eModifierType_Fluid = 56, - eModifierType_Simulation = 57, + eModifierType_Nodes = 57, eModifierType_MeshToVolume = 58, eModifierType_VolumeDisplace = 59, eModifierType_VolumeToMesh = 60, @@ -2224,9 +2224,16 @@ enum { #define MOD_MESHSEQ_READ_ALL \ (MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR) -typedef struct SimulationModifierData { +typedef struct NodesModifierSettings { + /* This stores data that is passed into the node group. */ + struct IDProperty *properties; +} NodesModifierSettings; + +typedef struct NodesModifierData { ModifierData modifier; -} SimulationModifierData; + struct bNodeTree *node_group; + struct NodesModifierSettings settings; +} NodesModifierData; typedef struct MeshToVolumeModifierData { ModifierData modifier; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index fdae3559599..29c83d2d4ed 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -504,7 +504,7 @@ typedef struct bNodeTree { #define NTREE_SHADER 0 #define NTREE_COMPOSIT 1 #define NTREE_TEXTURE 2 -#define NTREE_SIMULATION 3 +#define NTREE_GEOMETRY 3 /* ntree->init, flag */ #define NTREE_TYPE_INIT 1 @@ -1437,17 +1437,32 @@ typedef enum NodeShaderOutputTarget { SHD_OUTPUT_CYCLES = 2, } NodeShaderOutputTarget; -/* Particle Time Step Event node */ -typedef enum NodeSimParticleTimeStepEventType { - NODE_PARTICLE_TIME_STEP_EVENT_BEGIN = 0, - NODE_PARTICLE_TIME_STEP_EVENT_END = 1, -} NodeSimParticleTimeStepEventType; - -/* Simulation Time node */ -typedef enum NodeSimInputTimeType { - NODE_SIM_INPUT_SIMULATION_TIME = 0, - NODE_SIM_INPUT_SCENE_TIME = 1, -} NodeSimInputTimeType; +/* Geometry Nodes */ + +/* Boolean Node */ +typedef enum GeometryNodeBooleanOperation { + GEO_NODE_BOOLEAN_INTERSECT = 0, + GEO_NODE_BOOLEAN_UNION = 1, + GEO_NODE_BOOLEAN_DIFFERENCE = 2, +} GeometryNodeBooleanOperation; + +/* Triangulate Node */ +typedef enum GeometryNodeTriangulateNGons { + GEO_NODE_TRIANGULATE_NGON_BEAUTY = 0, + GEO_NODE_TRIANGULATE_NGON_EARCLIP = 1, +} GeometryNodeTriangulateNGons; + +typedef enum GeometryNodeTriangulateQuads { + GEO_NODE_TRIANGULATE_QUAD_BEAUTY = 0, + GEO_NODE_TRIANGULATE_QUAD_FIXED = 1, + GEO_NODE_TRIANGULATE_QUAD_ALTERNATE = 2, + GEO_NODE_TRIANGULATE_QUAD_SHORTEDGE = 3, +} GeometryNodeTriangulateQuads; + +typedef enum GeometryNodeUseAttributeFlag { + GEO_NODE_USE_ATTRIBUTE_A = (1 << 0), + GEO_NODE_USE_ATTRIBUTE_B = (1 << 1), +} GeometryNodeUseAttributeFlag; #ifdef __cplusplus } diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 5a00b6479d5..328e4d2ba22 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -51,6 +51,7 @@ struct RigidBodyOb; struct SculptSession; struct SoftBody; struct bGPdata; +struct GeometrySet; /* Vertex Groups - Name Info */ typedef struct bDeformGroup { @@ -153,6 +154,13 @@ typedef struct Object_Runtime { * It has all modifiers applied. */ struct ID *data_eval; + + /** + * Some objects support evaluating to a geometry set instead of a single ID. In those cases the + * evaluated geometry will be stored here instead of in #data_eval. + */ + struct GeometrySet *geometry_set_eval; + /** * Mesh structure created during object evaluation. * It has deformation only modifiers applied on it. diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 8c95a6d2a31..1a8bd25215f 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -271,7 +271,7 @@ SDNA_DEFAULT_DECL_STRUCT(ScrewModifierData); /* Shape key modifier has no items. */ SDNA_DEFAULT_DECL_STRUCT(ShrinkwrapModifierData); SDNA_DEFAULT_DECL_STRUCT(SimpleDeformModifierData); -SDNA_DEFAULT_DECL_STRUCT(SimulationModifierData); +SDNA_DEFAULT_DECL_STRUCT(NodesModifierData); SDNA_DEFAULT_DECL_STRUCT(SkinModifierData); SDNA_DEFAULT_DECL_STRUCT(SmoothModifierData); /* Softbody modifier skipped for now. */ @@ -491,7 +491,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { /* Shape key modifier has no items. */ SDNA_DEFAULT_DECL(ShrinkwrapModifierData), SDNA_DEFAULT_DECL(SimpleDeformModifierData), - SDNA_DEFAULT_DECL(SimulationModifierData), + SDNA_DEFAULT_DECL(NodesModifierData), SDNA_DEFAULT_DECL(SkinModifierData), SDNA_DEFAULT_DECL(SmoothModifierData), /* Softbody modifier skipped for now. */ diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 6dfd7b0fdf5..bc25a404de8 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -566,10 +566,11 @@ extern StructRNA RNA_SimpleDeformModifier; extern StructRNA RNA_SimplifyGpencilModifier; extern StructRNA RNA_Simulation; #ifdef WITH_GEOMETRY_NODES -extern StructRNA RNA_SimulationModifier; +extern StructRNA RNA_NodesModifier; +extern StructRNA RNA_NodesModifierSettings; #endif -extern StructRNA RNA_SimulationNode; -extern StructRNA RNA_SimulationNodeTree; +extern StructRNA RNA_GeometryNode; +extern StructRNA RNA_GeometryNodeTree; extern StructRNA RNA_SkinModifier; extern StructRNA RNA_SmoothGpencilModifier; extern StructRNA RNA_SmoothModifier; diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index d638d6f66cc..3ebbb98934e 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -373,6 +373,7 @@ blender_include_dirs( ../../ikplugin ../../imbuf ../../makesdna + ../../modifiers ../../nodes/ ../../sequencer ../../simulation diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index ceccccc3291..4991f34c3f6 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1160,8 +1160,8 @@ PropertySubType RNA_property_subtype(PropertyRNA *prop) if (prop->magic != RNA_MAGIC) { IDProperty *idprop = (IDProperty *)prop; - /* Restrict to arrays only for now for performance reasons. */ - if (idprop->type == IDP_ARRAY && ELEM(idprop->subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE)) { + if (ELEM(idprop->type, IDP_INT, IDP_FLOAT, IDP_DOUBLE) || + ((idprop->type == IDP_ARRAY) && ELEM(idprop->subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE))) { const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idp_ui) { diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index 7f0ab2df70d..ad615026343 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -50,13 +50,12 @@ const EnumPropertyItem rna_enum_attribute_type_items[] = { const EnumPropertyItem rna_enum_attribute_domain_items[] = { /* Not implement yet */ // {ATTR_DOMAIN_GEOMETRY, "GEOMETRY", 0, "Geometry", "Attribute on (whole) geometry"}, - {ATTR_DOMAIN_VERTEX, "VERTEX", 0, "Vertex", "Attribute on mesh vertex"}, + {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, {ATTR_DOMAIN_CORNER, "CORNER", 0, "Corner", "Attribute on mesh polygon corner"}, {ATTR_DOMAIN_POLYGON, "POLYGON", 0, "Polygon", "Attribute on mesh polygons"}, /* Not implement yet */ // {ATTR_DOMAIN_GRIDS, "GRIDS", 0, "Grids", "Attribute on mesh multires grids"}, - {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", "Attribute on hair curve"}, {0, NULL, 0, NULL, NULL}, }; @@ -117,7 +116,7 @@ const EnumPropertyItem *rna_enum_attribute_domain_itemf(ID *id, bool *r_free) if (id_type == ID_HA && !ELEM(domain_item->value, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { continue; } - if (id_type == ID_ME && ELEM(domain_item->value, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + if (id_type == ID_ME && ELEM(domain_item->value, ATTR_DOMAIN_CURVE)) { continue; } @@ -619,7 +618,7 @@ static void rna_def_attribute_group(BlenderRNA *brna) parm = RNA_def_enum(func, "domain", rna_enum_attribute_domain_items, - ATTR_DOMAIN_VERTEX, + ATTR_DOMAIN_POINT, "Domain", "Type of element that attribute is stored on"); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index c0f3197b2d1..09c1869b84b 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -29,7 +29,6 @@ #include "DNA_object_force_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" -#include "DNA_simulation_types.h" #include "MEM_guardedalloc.h" @@ -43,6 +42,7 @@ #include "BKE_dynamicpaint.h" #include "BKE_effect.h" #include "BKE_fluid.h" /* For BKE_fluid_modifier_free & BKE_fluid_modifier_create_type_data */ +#include "BKE_idprop.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_remap.h" #include "BKE_multires.h" @@ -57,6 +57,8 @@ #include "WM_api.h" #include "WM_types.h" +#include "MOD_nodes.h" + const EnumPropertyItem rna_enum_object_modifier_type_items[] = { {0, "", 0, N_("Modify"), ""}, {eModifierType_DataTransfer, @@ -141,6 +143,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_MOD_EDGESPLIT, "Edge Split", "Split away joined faces at the edges"}, + {eModifierType_Nodes, "NODES", ICON_NODETREE, "Geometry Nodes", ""}, {eModifierType_Mask, "MASK", ICON_MOD_MASK, @@ -305,11 +308,6 @@ 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_Simulation, - "SIMULATION", - ICON_PHYSICS, - "Simulation", - ""}, /* TODO: Use correct icon. */ {0, NULL, 0, NULL, NULL}, }; @@ -1588,6 +1586,37 @@ static int rna_MeshSequenceCacheModifier_read_velocity_get(PointerRNA *ptr) # endif } +static bool rna_NodesModifier_node_group_poll(PointerRNA *ptr, PointerRNA value) +{ + NodesModifierData *nmd = ptr->data; + bNodeTree *ntree = value.data; + UNUSED_VARS(nmd, ntree); + return true; +} + +static void rna_NodesModifier_node_group_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + Object *object = (Object *)ptr->owner_id; + NodesModifierData *nmd = ptr->data; + rna_Modifier_dependency_update(bmain, scene, ptr); + MOD_nodes_update_interface(object, nmd); +} + +static IDProperty *rna_NodesModifierSettings_properties(PointerRNA *ptr, bool create) +{ + NodesModifierSettings *settings = ptr->data; + if (create && settings->properties == NULL) { + IDPropertyTemplate val = {0}; + settings->properties = IDP_New(IDP_GROUP, &val, "Nodes Modifier Settings"); + } + return settings->properties; +} + +static char *rna_NodesModifierSettings_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("settings"); +} + #else static void rna_def_property_subdivision_common(StructRNA *srna) @@ -6907,18 +6936,43 @@ static void rna_def_modifier_weightednormal(BlenderRNA *brna) } # ifdef WITH_GEOMETRY_NODES -static void rna_def_modifier_simulation(BlenderRNA *brna) +static void rna_def_modifier_nodes_settings(BlenderRNA *brna) { StructRNA *srna; - srna = RNA_def_struct(brna, "SimulationModifier", "Modifier"); - RNA_def_struct_ui_text(srna, "Simulation Modifier", ""); - RNA_def_struct_sdna(srna, "SimulationModifierData"); - RNA_def_struct_ui_icon(srna, ICON_PHYSICS); /* TODO: Use correct icon. */ + srna = RNA_def_struct(brna, "NodesModifierSettings", NULL); + RNA_def_struct_nested(brna, srna, "NodesModifier"); + RNA_def_struct_path_func(srna, "rna_NodesModifierSettings_path"); + RNA_def_struct_ui_text( + srna, "Nodes Modifier Settings", "Settings that are passed into the node group"); + RNA_def_struct_idprops_func(srna, "rna_NodesModifierSettings_properties"); +} + +static void rna_def_modifier_nodes(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NodesModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "Nodes Modifier", ""); + RNA_def_struct_sdna(srna, "NodesModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MESH_DATA); /* TODO: Use correct icon. */ RNA_define_lib_overridable(true); + prop = RNA_def_property(srna, "node_group", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Node Group", "Node group that controls what this modifier does"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_NodesModifier_node_group_poll"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_update(prop, 0, "rna_NodesModifier_node_group_update"); + + prop = RNA_def_property(srna, "settings", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_ui_text(prop, "Settings", "Settings that are passed into the node group"); + RNA_define_lib_overridable(false); + + rna_def_modifier_nodes_settings(brna); } # endif @@ -7277,7 +7331,7 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_surfacedeform(brna); rna_def_modifier_weightednormal(brna); # ifdef WITH_GEOMETRY_NODES - rna_def_modifier_simulation(brna); + rna_def_modifier_nodes(brna); # endif rna_def_modifier_mesh_to_volume(brna); rna_def_modifier_volume_displace(brna); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 67bb5e89254..11d57919fb1 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -36,9 +36,9 @@ #include "DNA_texture_types.h" #include "BKE_animsys.h" +#include "BKE_attribute.h" #include "BKE_image.h" #include "BKE_node.h" -#include "BKE_simulation.h" #include "BKE_texture.h" #include "RNA_access.h" @@ -369,6 +369,72 @@ static const EnumPropertyItem prop_shader_output_target_items[] = { {SHD_OUTPUT_CYCLES, "CYCLES", 0, "Cycles", "Use shaders for Cycles renderer"}, {0, NULL, 0, NULL, NULL}, }; + +static const EnumPropertyItem rna_node_geometry_boolean_method_items[] = { + {GEO_NODE_BOOLEAN_INTERSECT, + "INTERSECT", + 0, + "Intersect", + "Keep the part of the mesh that is common between all operands"}, + {GEO_NODE_BOOLEAN_UNION, "UNION", 0, "Union", "Combine meshes in an additive way"}, + {GEO_NODE_BOOLEAN_DIFFERENCE, + "DIFFERENCE", + 0, + "Difference", + "Combine meshes in a subtractive way"}, + {0, NULL, 0, NULL, NULL}, +}; + +static const EnumPropertyItem rna_node_geometry_triangulate_quad_method_items[] = { + {GEO_NODE_TRIANGULATE_QUAD_BEAUTY, + "BEAUTY", + 0, + "Beauty", + "Split the quads in nice triangles, slower method"}, + {GEO_NODE_TRIANGULATE_QUAD_FIXED, + "FIXED", + 0, + "Fixed", + "Split the quads on the first and third vertices"}, + {GEO_NODE_TRIANGULATE_QUAD_ALTERNATE, + "FIXED_ALTERNATE", + 0, + "Fixed Alternate", + "Split the quads on the 2nd and 4th vertices"}, + {GEO_NODE_TRIANGULATE_QUAD_SHORTEDGE, + "SHORTEST_DIAGONAL", + 0, + "Shortest Diagonal", + "Split the quads based on the distance between the vertices"}, + {0, NULL, 0, NULL, NULL}, +}; + +static const EnumPropertyItem rna_node_geometry_triangulate_ngon_method_items[] = { + {GEO_NODE_TRIANGULATE_NGON_BEAUTY, + "BEAUTY", + 0, + "Beauty", + "Arrange the new triangles evenly (slow)"}, + {GEO_NODE_TRIANGULATE_NGON_EARCLIP, + "CLIP", + 0, + "Clip", + "Split the polygons with an ear clipping algorithm"}, + {0, NULL, 0, NULL, NULL}, +}; + +static const EnumPropertyItem rna_node_geometry_attribute_input_a_items[] = { + {0, "FLOAT", 0, "Float", ""}, + {GEO_NODE_USE_ATTRIBUTE_A, "ATTRIBUTE", 0, "Attribute", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +static const EnumPropertyItem rna_node_geometry_attribute_input_b_items[] = { + {0, "FLOAT", 0, "Float", ""}, + {GEO_NODE_USE_ATTRIBUTE_B, "ATTRIBUTE", 0, "Attribute", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #endif #ifdef RNA_RUNTIME @@ -727,9 +793,9 @@ static const EnumPropertyItem *rna_node_static_type_itemf(bContext *UNUSED(C), # undef DefNode } - if (RNA_struct_is_a(ptr->type, &RNA_SimulationNode)) { + if (RNA_struct_is_a(ptr->type, &RNA_GeometryNode)) { # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ - if (STREQ(#Category, "SimulationNode")) { \ + if (STREQ(#Category, "GeometryNode")) { \ tmp.value = ID; \ tmp.identifier = EnumName; \ tmp.name = UIName; \ @@ -1774,6 +1840,61 @@ static StructRNA *rna_Node_register(Main *bmain, return nt->rna_ext.srna; } +static const EnumPropertyItem *itemf_function_check( + const EnumPropertyItem *original_item_array, + bool (*value_supported)(const EnumPropertyItem *item)) +{ + EnumPropertyItem *item_array = NULL; + int items_len = 0; + + for (const EnumPropertyItem *item = original_item_array; item->identifier != NULL; item++) { + if (value_supported(item)) { + RNA_enum_item_add(&item_array, &items_len, item); + } + } + + RNA_enum_item_end(&item_array, &items_len); + return item_array; +} + +static bool attribute_random_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3); +} +static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_type_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, attribute_random_type_supported); +} + +static bool attribute_random_domain_supported(const EnumPropertyItem *item) +{ + return item->value == ATTR_DOMAIN_POINT; +} +static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_domain_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_domain_items, attribute_random_domain_supported); +} + +static bool attribute_math_operation_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, + NODE_MATH_ADD, + NODE_MATH_SUBTRACT, + NODE_MATH_MULTIPLY, + NODE_MATH_DIVIDE) && + (item->identifier[0] != '\0'); +} +static const EnumPropertyItem *rna_GeometryNodeAttributeMath_operation_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_node_math_items, attribute_math_operation_supported); +} + static StructRNA *rna_ShaderNode_register(Main *bmain, ReportList *reports, void *data, @@ -1840,16 +1961,16 @@ static StructRNA *rna_TextureNode_register(Main *bmain, return nt->rna_ext.srna; } -static StructRNA *rna_SimulationNode_register(Main *bmain, - ReportList *reports, - void *data, - const char *identifier, - StructValidateFunc validate, - StructCallbackFunc call, - StructFreeFunc free) +static StructRNA *rna_GeometryNode_register(Main *bmain, + ReportList *reports, + void *data, + const char *identifier, + StructValidateFunc validate, + StructCallbackFunc call, + StructFreeFunc free) { bNodeType *nt = rna_Node_register_base( - bmain, reports, &RNA_SimulationNode, data, identifier, validate, call, free); + bmain, reports, &RNA_GeometryNode, data, identifier, validate, call, free); if (!nt) { return NULL; } @@ -2812,6 +2933,7 @@ static void rna_NodeSocketStandard_value_and_relation_update(struct bContext *C, bNodeTree *ntree = (bNodeTree *)ptr->owner_id; Main *bmain = CTX_data_main(C); ntreeUpdateTree(bmain, ntree); + DEG_relations_tag_update(bmain); } /* ******** Node Types ******** */ @@ -3697,7 +3819,16 @@ static void rna_ShaderNode_socket_update(Main *bmain, Scene *scene, PointerRNA * rna_Node_update(bmain, scene, ptr); } -static void rna_FunctionNode_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) +static void rna_Node_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNode *node = (bNode *)ptr->data; + + nodeUpdate(ntree, node); + rna_Node_update(bmain, scene, ptr); +} + +static void rna_GeometryNode_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; @@ -4149,7 +4280,7 @@ static void def_boolean_math(StructRNA *srna) RNA_def_property_enum_sdna(prop, NULL, "custom1"); RNA_def_property_enum_items(prop, rna_enum_node_boolean_math_items); RNA_def_property_ui_text(prop, "Operation", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_FunctionNode_socket_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_float_compare(StructRNA *srna) @@ -4160,7 +4291,7 @@ static void def_float_compare(StructRNA *srna) RNA_def_property_enum_sdna(prop, NULL, "custom1"); RNA_def_property_enum_items(prop, rna_enum_node_float_compare_items); RNA_def_property_ui_text(prop, "Operation", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_FunctionNode_socket_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_fn_switch(StructRNA *srna) @@ -4171,7 +4302,7 @@ static void def_fn_switch(StructRNA *srna) RNA_def_property_enum_sdna(prop, NULL, "custom1"); RNA_def_property_enum_items(prop, node_socket_data_type_items); RNA_def_property_ui_text(prop, "Data Type", "Data type for inputs and outputs"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_FunctionNode_socket_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_vector_math(StructRNA *srna) @@ -8140,6 +8271,103 @@ static void def_tex_bricks(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +/* -- Geometry Nodes --------------------------------------------------------- */ + +static void def_geo_boolean(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_node_geometry_boolean_method_items); + RNA_def_property_enum_default(prop, GEO_NODE_BOOLEAN_INTERSECT); + RNA_def_property_ui_text(prop, "Operation", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_triangulate(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "quad_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_node_geometry_triangulate_quad_method_items); + RNA_def_property_enum_default(prop, GEO_NODE_TRIANGULATE_QUAD_SHORTEDGE); + RNA_def_property_ui_text(prop, "Quad Method", "Method for splitting the quads into triangles"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "ngon_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_node_geometry_triangulate_ngon_method_items); + RNA_def_property_enum_default(prop, GEO_NODE_TRIANGULATE_NGON_BEAUTY); + RNA_def_property_ui_text( + prop, "Polygon Method", "Method for splitting the polygons into triangles"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +/** + * \note Passing the item functions as arguments here allows reusing the same + * original list of items from Attribute RNA. + */ +static void def_geo_attribute_create_common(StructRNA *srna, + const char *type_items_func, + const char *domain_items_func) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + if (type_items_func != NULL) { + RNA_def_property_enum_funcs(prop, NULL, NULL, type_items_func); + } + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + if (domain_items_func != NULL) { + RNA_def_property_enum_funcs(prop, NULL, NULL, domain_items_func); + } + RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); + RNA_def_property_ui_text(prop, "Domain", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_random_attribute(StructRNA *srna) +{ + def_geo_attribute_create_common(srna, + "rna_GeometryNodeAttributeRandom_type_itemf", + "rna_GeometryNodeAttributeRandom_domain_itemf"); +} + +static void def_geo_attribute_math(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_enum_node_math_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeMath_operation_itemf"); + RNA_def_property_enum_default(prop, NODE_MATH_ADD); + RNA_def_property_ui_text(prop, "Operation", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "input_type_a", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_a_items); + RNA_def_property_ui_text(prop, "Input Type A", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_b", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_b_items); + RNA_def_property_ui_text(prop, "Input Type B", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) @@ -8177,14 +8405,14 @@ static void rna_def_texture_node(BlenderRNA *brna) RNA_def_struct_register_funcs(srna, "rna_TextureNode_register", "rna_Node_unregister", NULL); } -static void rna_def_simulation_node(BlenderRNA *brna) +static void rna_def_geometry_node(BlenderRNA *brna) { StructRNA *srna; - srna = RNA_def_struct(brna, "SimulationNode", "NodeInternal"); - RNA_def_struct_ui_text(srna, "Simulation Node", ""); + srna = RNA_def_struct(brna, "GeometryNode", "NodeInternal"); + RNA_def_struct_ui_text(srna, "Geometry Node", ""); RNA_def_struct_sdna(srna, "bNode"); - RNA_def_struct_register_funcs(srna, "rna_SimulationNode_register", "rna_Node_unregister", NULL); + RNA_def_struct_register_funcs(srna, "rna_GeometryNode_register", "rna_Node_unregister", NULL); } static void rna_def_function_node(BlenderRNA *brna) @@ -9623,7 +9851,7 @@ static void rna_def_nodetree(BlenderRNA *brna) {NTREE_SHADER, "SHADER", ICON_MATERIAL, "Shader", "Shader nodes"}, {NTREE_TEXTURE, "TEXTURE", ICON_TEXTURE, "Texture", "Texture nodes"}, {NTREE_COMPOSIT, "COMPOSITING", ICON_RENDERLAYERS, "Compositing", "Compositing nodes"}, - {NTREE_SIMULATION, "SIMULATION", ICON_PHYSICS, "Simulation", "Simulation nodes"}, + {NTREE_GEOMETRY, "GEOMETRY", ICON_MESH_DATA, "Geometry", "Geometry nodes"}, {0, NULL, 0, NULL, NULL}, }; @@ -9847,15 +10075,15 @@ static void rna_def_texture_nodetree(BlenderRNA *brna) RNA_def_struct_ui_icon(srna, ICON_TEXTURE); } -static void rna_def_simulation_nodetree(BlenderRNA *brna) +static void rna_def_geometry_nodetree(BlenderRNA *brna) { StructRNA *srna; - srna = RNA_def_struct(brna, "SimulationNodeTree", "NodeTree"); + srna = RNA_def_struct(brna, "GeometryNodeTree", "NodeTree"); RNA_def_struct_ui_text( - srna, "Simulation Node Tree", "Node tree consisting of linked nodes used for simulations"); + srna, "Geometry Node Tree", "Node tree consisting of linked nodes used for geometries"); RNA_def_struct_sdna(srna, "bNodeTree"); - RNA_def_struct_ui_icon(srna, ICON_PHYSICS); /* TODO: Use correct icon. */ + RNA_def_struct_ui_icon(srna, ICON_MESH_DATA); /* TODO: Use correct icon. */ } static StructRNA *define_specific_node(BlenderRNA *brna, @@ -9944,7 +10172,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_shader_node(brna); rna_def_compositor_node(brna); rna_def_texture_node(brna); - rna_def_simulation_node(brna); + rna_def_geometry_node(brna); rna_def_function_node(brna); rna_def_nodetree(brna); @@ -9954,7 +10182,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); + rna_def_geometry_nodetree(brna); # define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \ { \ @@ -9971,13 +10199,13 @@ void RNA_def_nodetree(BlenderRNA *brna) */ # include "../../nodes/NOD_static_types.h" - /* Node group types need to be defined for shader, compositor, texture, simulation nodes + /* Node group types need to be defined for shader, compositor, texture, geometry nodes * individually. Cannot use the static types header for this, since they share the same int id. */ define_specific_node(brna, "ShaderNodeGroup", "ShaderNode", "Group", "", def_group); define_specific_node(brna, "CompositorNodeGroup", "CompositorNode", "Group", "", def_group); define_specific_node(brna, "TextureNodeGroup", "TextureNode", "Group", "", def_group); - define_specific_node(brna, "SimulationNodeGroup", "SimulationNode", "Group", "", def_group); + define_specific_node(brna, "GeometryNodeGroup", "GeometryNode", "Group", "", def_group); def_custom_group(brna, "ShaderNodeCustomGroup", "ShaderNode", diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index e6e54abebd4..dc3e48b9c70 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -44,7 +44,6 @@ #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_sequence_types.h" -#include "DNA_simulation_types.h" #include "DNA_space_types.h" #include "DNA_view3d_types.h" #include "DNA_workspace_types.h" @@ -2179,40 +2178,6 @@ static void rna_SpaceNodeEditor_node_tree_update(const bContext *C, PointerRNA * ED_node_tree_update(C); } -# ifdef WITH_GEOMETRY_NODES -static PointerRNA rna_SpaceNodeEditor_simulation_get(PointerRNA *ptr) -{ - SpaceNode *snode = (SpaceNode *)ptr->data; - ID *id = snode->id; - if (id && GS(id->name) == ID_SIM) { - return rna_pointer_inherit_refine(ptr, &RNA_Simulation, snode->id); - } - else { - return PointerRNA_NULL; - } -} - -static void rna_SpaceNodeEditor_simulation_set(PointerRNA *ptr, - const PointerRNA value, - struct ReportList *UNUSED(reports)) -{ - SpaceNode *snode = (SpaceNode *)ptr->data; - if (!STREQ(snode->tree_idname, "SimulationNodeTree")) { - return; - } - - Simulation *sim = (Simulation *)value.data; - if (sim != NULL) { - bNodeTree *ntree = sim->nodetree; - ED_node_tree_start(snode, ntree, NULL, NULL); - } - else { - ED_node_tree_start(snode, NULL, NULL, NULL); - } - snode->id = &sim->id; -} -# endif - static int rna_SpaceNodeEditor_tree_type_get(PointerRNA *ptr) { SpaceNode *snode = (SpaceNode *)ptr->data; @@ -6350,19 +6315,6 @@ static void rna_def_space_node(BlenderRNA *brna) RNA_def_property_ui_text( prop, "ID From", "Data-block from which the edited data-block is linked"); -# ifdef WITH_GEOMETRY_NODES - prop = RNA_def_property(srna, "simulation", PROP_POINTER, PROP_NONE); - RNA_def_property_flag(prop, PROP_EDITABLE); - RNA_def_property_struct_type(prop, "Simulation"); - RNA_def_property_ui_text(prop, "Simulation", "Simulation that is being edited"); - RNA_def_property_pointer_funcs(prop, - "rna_SpaceNodeEditor_simulation_get", - "rna_SpaceNodeEditor_simulation_set", - NULL, - NULL); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, NULL); -# endif - prop = RNA_def_property(srna, "path", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "treepath", NULL); RNA_def_property_struct_type(prop, "NodeTreePath"); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 549751ce267..34713392afb 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -29,8 +29,10 @@ set(INC ../bmesh ../depsgraph ../editors/include + ../functions ../makesdna ../makesrna + ../nodes ../render ../windowmanager ../../../intern/eigen @@ -86,7 +88,7 @@ set(SRC intern/MOD_shapekey.c intern/MOD_shrinkwrap.c intern/MOD_simpledeform.c - intern/MOD_simulation.cc + intern/MOD_nodes.cc intern/MOD_skin.c intern/MOD_smooth.c intern/MOD_softbody.c @@ -114,6 +116,7 @@ set(SRC intern/MOD_wireframe.c MOD_modifiertypes.h + MOD_nodes.h intern/MOD_meshcache_util.h intern/MOD_solidify_util.h intern/MOD_ui_common.h diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h index 9c488780366..f36cb7ded9c 100644 --- a/source/blender/modifiers/MOD_modifiertypes.h +++ b/source/blender/modifiers/MOD_modifiertypes.h @@ -85,7 +85,7 @@ extern ModifierTypeInfo modifierType_CorrectiveSmooth; extern ModifierTypeInfo modifierType_MeshSequenceCache; extern ModifierTypeInfo modifierType_SurfaceDeform; extern ModifierTypeInfo modifierType_WeightedNormal; -extern ModifierTypeInfo modifierType_Simulation; +extern ModifierTypeInfo modifierType_Nodes; extern ModifierTypeInfo modifierType_MeshToVolume; extern ModifierTypeInfo modifierType_VolumeDisplace; extern ModifierTypeInfo modifierType_VolumeToMesh; diff --git a/source/blender/nodes/NOD_simulation.h b/source/blender/modifiers/MOD_nodes.h index 6b3d51b46a9..9c75e7e3416 100644 --- a/source/blender/nodes/NOD_simulation.h +++ b/source/blender/modifiers/MOD_nodes.h @@ -16,15 +16,17 @@ #pragma once +struct Main; +struct Object; +struct NodesModifierData; + #ifdef __cplusplus extern "C" { #endif -extern struct bNodeTreeType *ntreeType_Simulation; - -void register_node_tree_type_sim(void); +void MOD_nodes_update_interface(struct Object *object, struct NodesModifierData *nmd); -void register_node_type_sim_group(void); +void MOD_nodes_init(struct Main *bmain, struct NodesModifierData *nmd); #ifdef __cplusplus } diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index 882d080c08d..30c38623f68 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -53,7 +53,10 @@ #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" -static Mesh *doEdgeSplit(Mesh *mesh, EdgeSplitModifierData *emd) +/* For edge split modifier node. */ +Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd); + +Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd) { Mesh *result; BMesh *bm; diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc new file mode 100644 index 00000000000..b1849b3dbf2 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -0,0 +1,1088 @@ +/* + * 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. + */ + +/** \file + * \ingroup modifiers + */ + +#include <cstring> +#include <iostream> +#include <string> + +#include "MEM_guardedalloc.h" + +#include "BLI_float3.hh" +#include "BLI_listbase.h" +#include "BLI_set.hh" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_defaults.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 "DNA_pointcloud_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BKE_customdata.h" +#include "BKE_idprop.h" +#include "BKE_lib_query.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_pointcloud.h" +#include "BKE_screen.h" +#include "BKE_simulation.h" + +#include "BLO_read_write.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" +#include "RNA_enum_types.h" + +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "MOD_modifiertypes.h" +#include "MOD_nodes.h" +#include "MOD_ui_common.h" + +#include "NOD_derived_node_tree.hh" +#include "NOD_geometry.h" +#include "NOD_geometry_exec.hh" +#include "NOD_node_tree_multi_function.hh" +#include "NOD_type_callbacks.hh" + +using blender::float3; +using blender::IndexRange; +using blender::Map; +using blender::Set; +using blender::Span; +using blender::StringRef; +using blender::Vector; +using blender::fn::GMutablePointer; +using blender::fn::GValueMap; +using blender::nodes::GeoNodeExecParams; +using namespace blender::nodes::derived_node_tree_types; +using namespace blender::fn::multi_function_types; + +static void initData(ModifierData *md) +{ + NodesModifierData *nmd = (NodesModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier)); + + MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier); +} + +static void addIdsUsedBySocket(const ListBase *sockets, Set<ID *> &ids) +{ + LISTBASE_FOREACH (const bNodeSocket *, socket, sockets) { + if (socket->type == SOCK_OBJECT) { + Object *object = ((bNodeSocketValueObject *)socket->default_value)->value; + if (object != nullptr) { + ids.add(&object->id); + } + } + } +} + +static void findUsedIds(const bNodeTree &tree, Set<ID *> &ids) +{ + Set<const bNodeTree *> handled_groups; + + LISTBASE_FOREACH (const bNode *, node, &tree.nodes) { + addIdsUsedBySocket(&node->inputs, ids); + addIdsUsedBySocket(&node->outputs, ids); + + if (node->type == NODE_GROUP) { + const bNodeTree *group = (bNodeTree *)node->id; + if (group != nullptr && handled_groups.add(group)) { + findUsedIds(*group, ids); + } + } + } +} + +static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + if (nmd->node_group != nullptr) { + DEG_add_node_tree_relation(ctx->node, nmd->node_group, "Nodes Modifier"); + + Set<ID *> used_ids; + findUsedIds(*nmd->node_group, used_ids); + for (ID *id : used_ids) { + if (GS(id->name) == ID_OB) { + Object *object = reinterpret_cast<Object *>(id); + DEG_add_object_relation(ctx->node, object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier"); + if (id != &ctx->object->id) { + if (object->type != OB_EMPTY) { + DEG_add_object_relation( + ctx->node, (Object *)id, DEG_OB_COMP_GEOMETRY, "Nodes Modifier"); + } + } + } + } + } + + /* TODO: Add relations for IDs in settings. */ +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + walk(userData, ob, (ID **)&nmd->node_group, IDWALK_CB_USER); + + struct ForeachSettingData { + IDWalkFunc walk; + void *userData; + Object *ob; + } settings = {walk, userData, ob}; + + IDP_foreach_property( + nmd->settings.properties, + IDP_TYPE_FILTER_ID, + [](IDProperty *id_prop, void *user_data) { + ForeachSettingData *settings = (ForeachSettingData *)user_data; + settings->walk( + settings->userData, settings->ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER); + }, + &settings); +} + +static bool isDisabled(const struct Scene *UNUSED(scene), + ModifierData *md, + bool UNUSED(useRenderParams)) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + + if (nmd->node_group == nullptr) { + return true; + } + + return false; +} + +class GeometryNodesEvaluator { + private: + blender::LinearAllocator<> allocator_; + Map<const DInputSocket *, GMutablePointer> value_by_input_; + Vector<const DInputSocket *> group_outputs_; + blender::nodes::MultiFunctionByNode &mf_by_node_; + const blender::nodes::DataTypeConversions &conversions_; + const blender::bke::PersistentDataHandleMap &handle_map_; + const Object *self_object_; + + public: + GeometryNodesEvaluator(const Map<const DOutputSocket *, GMutablePointer> &group_input_data, + Vector<const DInputSocket *> group_outputs, + blender::nodes::MultiFunctionByNode &mf_by_node, + const blender::bke::PersistentDataHandleMap &handle_map, + const Object *self_object) + : group_outputs_(std::move(group_outputs)), + mf_by_node_(mf_by_node), + conversions_(blender::nodes::get_implicit_type_conversions()), + handle_map_(handle_map), + self_object_(self_object) + { + for (auto item : group_input_data.items()) { + this->forward_to_inputs(*item.key, item.value); + } + } + + Vector<GMutablePointer> execute() + { + Vector<GMutablePointer> results; + for (const DInputSocket *group_output : group_outputs_) { + GMutablePointer result = this->get_input_value(*group_output); + results.append(result); + } + for (GMutablePointer value : value_by_input_.values()) { + value.destruct(); + } + return results; + } + + private: + GMutablePointer get_input_value(const DInputSocket &socket_to_compute) + { + std::optional<GMutablePointer> value = value_by_input_.pop_try(&socket_to_compute); + if (value.has_value()) { + /* This input has been computed before, return it directly. */ + return *value; + } + + Span<const DOutputSocket *> from_sockets = socket_to_compute.linked_sockets(); + Span<const DGroupInput *> from_group_inputs = socket_to_compute.linked_group_inputs(); + const int total_inputs = from_sockets.size() + from_group_inputs.size(); + BLI_assert(total_inputs <= 1); + + if (total_inputs == 0) { + /* The input is not connected, use the value from the socket itself. */ + return get_unlinked_input_value(socket_to_compute); + } + if (from_group_inputs.size() == 1) { + /* The input gets its value from the input of a group that is not further connected. */ + return get_unlinked_input_value(socket_to_compute); + } + + /* Compute the socket now. */ + const DOutputSocket &from_socket = *from_sockets[0]; + this->compute_output_and_forward(from_socket); + return value_by_input_.pop(&socket_to_compute); + } + + void compute_output_and_forward(const DOutputSocket &socket_to_compute) + { + const DNode &node = socket_to_compute.node(); + const bNode &bnode = *node.bnode(); + + if (!socket_to_compute.is_available()) { + /* If the output is not available, use a default value. */ + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute.typeinfo()); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(type.default_value(), buffer); + this->forward_to_inputs(socket_to_compute, {type, buffer}); + return; + } + + /* Prepare inputs required to execute the node. */ + GValueMap<StringRef> node_inputs_map{allocator_}; + for (const DInputSocket *input_socket : node.inputs()) { + if (input_socket->is_available()) { + GMutablePointer value = this->get_input_value(*input_socket); + node_inputs_map.add_new_direct(input_socket->identifier(), value); + } + } + + /* Execute the node. */ + GValueMap<StringRef> node_outputs_map{allocator_}; + GeoNodeExecParams params{bnode, node_inputs_map, node_outputs_map, handle_map_, self_object_}; + this->execute_node(node, params); + + /* Forward computed outputs to linked input sockets. */ + for (const DOutputSocket *output_socket : node.outputs()) { + if (output_socket->is_available()) { + GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); + this->forward_to_inputs(*output_socket, value); + } + } + } + + void execute_node(const DNode &node, GeoNodeExecParams params) + { + const bNode &bnode = params.node(); + if (bnode.typeinfo->geometry_node_execute != nullptr) { + bnode.typeinfo->geometry_node_execute(params); + return; + } + + /* Use the multi-function implementation of the node. */ + const MultiFunction &fn = *mf_by_node_.lookup(&node); + MFContextBuilder fn_context; + MFParamsBuilder fn_params{fn, 1}; + Vector<GMutablePointer> input_data; + for (const DInputSocket *dsocket : node.inputs()) { + if (dsocket->is_available()) { + GMutablePointer data = params.extract_input(dsocket->identifier()); + fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); + input_data.append(data); + } + } + Vector<GMutablePointer> output_data; + for (const DOutputSocket *dsocket : node.outputs()) { + if (dsocket->is_available()) { + const CPPType &type = *blender::nodes::socket_cpp_type_get(*dsocket->typeinfo()); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + fn_params.add_uninitialized_single_output(GMutableSpan(type, buffer, 1)); + output_data.append(GMutablePointer(type, buffer)); + } + } + fn.call(IndexRange(1), fn_params, fn_context); + for (GMutablePointer value : input_data) { + value.destruct(); + } + int output_index = 0; + for (const int i : node.outputs().index_range()) { + if (node.output(i).is_available()) { + GMutablePointer value = output_data[output_index]; + params.set_output_by_move(node.output(i).identifier(), value); + value.destruct(); + output_index++; + } + } + } + + void forward_to_inputs(const DOutputSocket &from_socket, GMutablePointer value_to_forward) + { + Span<const DInputSocket *> to_sockets_all = from_socket.linked_sockets(); + + const CPPType &from_type = *value_to_forward.type(); + + Vector<const DInputSocket *> to_sockets_same_type; + for (const DInputSocket *to_socket : to_sockets_all) { + const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); + if (from_type == to_type) { + to_sockets_same_type.append(to_socket); + } + else { + void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); + if (conversions_.is_convertible(from_type, to_type)) { + conversions_.convert(from_type, to_type, value_to_forward.get(), buffer); + } + else { + to_type.copy_to_uninitialized(to_type.default_value(), buffer); + } + value_by_input_.add_new(to_socket, GMutablePointer{to_type, buffer}); + } + } + + if (to_sockets_same_type.size() == 0) { + /* This value is not further used, so destruct it. */ + value_to_forward.destruct(); + } + else if (to_sockets_same_type.size() == 1) { + /* This value is only used on one input socket, no need to copy it. */ + const DInputSocket *to_socket = to_sockets_same_type[0]; + value_by_input_.add_new(to_socket, value_to_forward); + } + else { + /* Multiple inputs use the value, make a copy for every input except for one. */ + const DInputSocket *first_to_socket = to_sockets_same_type[0]; + Span<const DInputSocket *> other_to_sockets = to_sockets_same_type.as_span().drop_front(1); + const CPPType &type = *value_to_forward.type(); + + value_by_input_.add_new(first_to_socket, value_to_forward); + for (const DInputSocket *to_socket : other_to_sockets) { + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(value_to_forward.get(), buffer); + value_by_input_.add_new(to_socket, GMutablePointer{type, buffer}); + } + } + } + + GMutablePointer get_unlinked_input_value(const DInputSocket &socket) + { + bNodeSocket *bsocket; + if (socket.linked_group_inputs().size() == 0) { + bsocket = socket.bsocket(); + } + else { + bsocket = socket.linked_group_inputs()[0]->bsocket(); + } + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket.typeinfo()); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + + if (bsocket->type == SOCK_OBJECT) { + Object *object = ((bNodeSocketValueObject *)bsocket->default_value)->value; + blender::bke::PersistentObjectHandle object_handle = handle_map_.lookup(object); + new (buffer) blender::bke::PersistentObjectHandle(object_handle); + } + else { + blender::nodes::socket_cpp_value_get(*bsocket, buffer); + } + + return {type, buffer}; + } +}; + +/** + * This code is responsible for creating the new property and also creating the group of + * properties in the prop_ui_container group for the UI info, the mapping for which is + * scattered about in RNA_access.c. + * + * TODO(Hans): Codify this with some sort of table or refactor IDProperty use in RNA_access.c. + */ +struct SocketPropertyType { + /* Create the actual propery used to store the data for the modifier. */ + IDProperty *(*create_prop)(const bNodeSocket &socket, const char *name); + /* Reused to build the "soft_min" property too. */ + IDProperty *(*create_min_ui_prop)(const bNodeSocket &socket, const char *name); + /* Reused to build the "soft_max" property too. */ + IDProperty *(*create_max_ui_prop)(const bNodeSocket &socket, const char *name); + /* This uses the same values as #create_prop, but sometimes the type is different, so it can't + * be the same function. */ + IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name); + PropertyType (*rna_subtype_get)(const bNodeSocket &socket); + bool (*is_correct_type)(const IDProperty &property); + void (*init_cpp_value)(const IDProperty &property, void *r_value); +}; + +static IDProperty *socket_add_property(IDProperty *settings_prop_group, + IDProperty *ui_container, + const SocketPropertyType &property_type, + const bNodeSocket &socket) +{ + const char *new_prop_name = socket.identifier; + /* Add the property actually storing the data to the modifier's group. */ + IDProperty *prop = property_type.create_prop(socket, new_prop_name); + IDP_AddToGroup(settings_prop_group, prop); + + /* Make the group in the ui container group to hold the property's UI settings. */ + IDProperty *prop_ui_group; + { + IDPropertyTemplate idprop = {0}; + prop_ui_group = IDP_New(IDP_GROUP, &idprop, new_prop_name); + IDP_AddToGroup(ui_container, prop_ui_group); + } + + /* Create the properties for the socket's UI settings. */ + if (property_type.create_min_ui_prop != nullptr) { + IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "min")); + IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "soft_min")); + } + if (property_type.create_max_ui_prop != nullptr) { + IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "max")); + IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "soft_max")); + } + if (property_type.create_default_ui_prop != nullptr) { + IDP_AddToGroup(prop_ui_group, property_type.create_default_ui_prop(socket, "default")); + } + if (property_type.rna_subtype_get != nullptr) { + const char *subtype_identifier = nullptr; + RNA_enum_identifier(rna_enum_property_subtype_items, + property_type.rna_subtype_get(socket), + &subtype_identifier); + + if (subtype_identifier != nullptr) { + IDPropertyTemplate idprop = {0}; + idprop.string.str = subtype_identifier; + idprop.string.len = BLI_strnlen(subtype_identifier, MAX_NAME) + 1; + IDP_AddToGroup(prop_ui_group, IDP_New(IDP_STRING, &idprop, "subtype")); + } + } + + return prop; +} + +static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bsocket) +{ + switch (bsocket.type) { + case SOCK_FLOAT: { + static const SocketPropertyType float_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.f = value->value; + return IDP_New(IDP_FLOAT, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.d = value->min; + return IDP_New(IDP_DOUBLE, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.d = value->max; + return IDP_New(IDP_DOUBLE, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.d = value->value; + return IDP_New(IDP_DOUBLE, &idprop, name); + }, + [](const bNodeSocket &socket) { + return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype; + }, + [](const IDProperty &property) { return property.type == IDP_FLOAT; }, + [](const IDProperty &property, void *r_value) { + *(float *)r_value = IDP_Float(&property); + }, + }; + return &float_type; + } + case SOCK_INT: { + static const SocketPropertyType int_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->min; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->max; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &socket) { + return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype; + }, + [](const IDProperty &property) { return property.type == IDP_INT; }, + [](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); }, + }; + return &int_type; + } + case SOCK_VECTOR: { + static const SocketPropertyType vector_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.array.len = 3; + idprop.array.type = IDP_FLOAT; + IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name); + copy_v3_v3((float *)IDP_Array(property), value->value); + return property; + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.d = value->min; + return IDP_New(IDP_DOUBLE, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.d = value->max; + return IDP_New(IDP_DOUBLE, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.array.len = 3; + idprop.array.type = IDP_FLOAT; + IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name); + copy_v3_v3((float *)IDP_Array(property), value->value); + return property; + }, + [](const bNodeSocket &socket) { + return (PropertyType)((bNodeSocketValueVector *)socket.default_value)->subtype; + }, + [](const IDProperty &property) { + return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && + property.len == 3; + }, + [](const IDProperty &property, void *r_value) { + copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + }, + }; + return &vector_type; + } + case SOCK_BOOLEAN: { + static const SocketPropertyType boolean_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value != 0; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &UNUSED(socket), const char *name) { + IDPropertyTemplate idprop = {0}; + idprop.i = 0; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &UNUSED(socket), const char *name) { + IDPropertyTemplate idprop = {0}; + idprop.i = 1; + return IDP_New(IDP_INT, &idprop, name); + }, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value != 0; + return IDP_New(IDP_INT, &idprop, name); + }, + nullptr, + [](const IDProperty &property) { return property.type == IDP_INT; }, + [](const IDProperty &property, void *r_value) { + *(bool *)r_value = IDP_Int(&property) != 0; + }, + }; + return &boolean_type; + } + case SOCK_STRING: { + static const SocketPropertyType string_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; + return IDP_NewString( + value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1); + }, + nullptr, + nullptr, + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; + return IDP_NewString( + value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1); + }, + nullptr, + [](const IDProperty &property) { return property.type == IDP_STRING; }, + [](const IDProperty &property, void *r_value) { + new (r_value) std::string(IDP_String(&property)); + }, + }; + return &string_type; + } + default: { + return nullptr; + } + } +} + +/** + * Rebuild the list of properties based on the sockets exposed as the modifier's node group + * inputs. If any properties correspond to the old properties by name and type, carry over + * the values. + */ +void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) +{ + if (nmd->node_group == nullptr) { + return; + } + + IDProperty *old_properties = nmd->settings.properties; + + { + IDPropertyTemplate idprop = {0}; + nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings"); + } + + IDProperty *ui_container_group; + { + IDPropertyTemplate idprop = {0}; + ui_container_group = IDP_New(IDP_GROUP, &idprop, "_RNA_UI"); + IDP_AddToGroup(nmd->settings.properties, ui_container_group); + } + + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { + const SocketPropertyType *property_type = get_socket_property_type(*socket); + if (property_type == nullptr) { + continue; + } + + IDProperty *new_prop = socket_add_property( + nmd->settings.properties, ui_container_group, *property_type, *socket); + + if (old_properties != nullptr) { + IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier); + if (old_prop != nullptr && property_type->is_correct_type(*old_prop)) { + IDP_CopyPropertyContent(new_prop, old_prop); + } + } + } + + if (old_properties != nullptr) { + IDP_FreeProperty(old_properties); + } + + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); +} + +void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) +{ + bNodeTree *ntree = ntreeAddTree(bmain, "Geometry Nodes", ntreeType_Geometry->idname); + nmd->node_group = ntree; + + ntreeAddSocketInterface(ntree, SOCK_IN, "NodeSocketGeometry", "Geometry"); + ntreeAddSocketInterface(ntree, SOCK_OUT, "NodeSocketGeometry", "Geometry"); + + bNode *group_input_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_INPUT); + bNode *group_output_node = nodeAddStaticNode(nullptr, ntree, NODE_GROUP_OUTPUT); + + group_input_node->locx = -200 - group_input_node->width; + group_output_node->locx = 200; + group_output_node->flag |= NODE_DO_OUTPUT; + + nodeAddLink(ntree, + group_output_node, + (bNodeSocket *)group_output_node->inputs.first, + group_input_node, + (bNodeSocket *)group_input_node->outputs.first); + + ntreeUpdateTree(bmain, ntree); +} + +static void initialize_group_input(NodesModifierData &nmd, + const bNodeSocket &socket, + const CPPType &cpp_type, + void *r_value) +{ + const SocketPropertyType *property_type = get_socket_property_type(socket); + if (property_type == nullptr) { + cpp_type.copy_to_uninitialized(cpp_type.default_value(), r_value); + return; + } + if (nmd.settings.properties == nullptr) { + blender::nodes::socket_cpp_value_get(socket, r_value); + return; + } + const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties, + socket.identifier); + if (property == nullptr) { + blender::nodes::socket_cpp_value_get(socket, r_value); + return; + } + if (!property_type->is_correct_type(*property)) { + blender::nodes::socket_cpp_value_get(socket, r_value); + } + property_type->init_cpp_value(*property, r_value); +} + +static void fill_data_handle_map(const DerivedNodeTree &tree, + blender::bke::PersistentDataHandleMap &handle_map) +{ + Set<ID *> used_ids; + findUsedIds(*tree.btree(), used_ids); + + int current_handle = 0; + for (ID *id : used_ids) { + handle_map.add(current_handle, *id); + current_handle++; + } +} + +/** + * Evaluate a node group to compute the output geometry. + * Currently, this uses a fairly basic and inefficient algorithm that might compute things more + * often than necessary. It's going to be replaced soon. + */ +static GeometrySet compute_geometry(const DerivedNodeTree &tree, + Span<const DOutputSocket *> group_input_sockets, + const DInputSocket &socket_to_compute, + GeometrySet input_geometry_set, + NodesModifierData *nmd, + const ModifierEvalContext *ctx) +{ + blender::ResourceCollector resources; + blender::LinearAllocator<> &allocator = resources.linear_allocator(); + blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, resources); + + Map<const DOutputSocket *, GMutablePointer> group_inputs; + + if (group_input_sockets.size() > 0) { + Span<const DOutputSocket *> remaining_input_sockets = group_input_sockets; + + /* If the group expects a geometry as first input, use the geometry that has been passed to + * modifier. */ + const DOutputSocket *first_input_socket = group_input_sockets[0]; + if (first_input_socket->bsocket()->type == SOCK_GEOMETRY) { + GeometrySet *geometry_set_in = allocator.construct<GeometrySet>( + std::move(input_geometry_set)); + group_inputs.add_new(first_input_socket, geometry_set_in); + remaining_input_sockets = remaining_input_sockets.drop_front(1); + } + + /* Initialize remaining group inputs. */ + for (const DOutputSocket *socket : remaining_input_sockets) { + const CPPType &cpp_type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); + void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); + initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in); + group_inputs.add_new(socket, {cpp_type, value_in}); + } + } + + Vector<const DInputSocket *> group_outputs; + group_outputs.append(&socket_to_compute); + + blender::bke::PersistentDataHandleMap handle_map; + fill_data_handle_map(tree, handle_map); + + GeometryNodesEvaluator evaluator{ + group_inputs, group_outputs, mf_by_node, handle_map, ctx->object}; + Vector<GMutablePointer> results = evaluator.execute(); + BLI_assert(results.size() == 1); + GMutablePointer result = results[0]; + + GeometrySet output_geometry = std::move(*(GeometrySet *)result.get()); + return output_geometry; +} + +/** + * \note This could be done in #initialize_group_input, though that would require adding the + * the object as a parameter, so it's likely better to this check as a separate step. + */ +static void check_property_socket_sync(const Object *ob, ModifierData *md) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + + int i = 0; + LISTBASE_FOREACH_INDEX (const bNodeSocket *, socket, &nmd->node_group->inputs, i) { + /* The first socket is the special geometry socket for the modifier object. */ + if (i == 0 && socket->type == SOCK_GEOMETRY) { + continue; + } + + IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket->identifier); + if (property == nullptr) { + if (socket->type == SOCK_STRING) { + BKE_modifier_set_error(ob, md, "String socket can not be exposed in the modifier"); + } + else if (socket->type == SOCK_OBJECT) { + BKE_modifier_set_error(ob, md, "Object socket can not be exposed in the modifier"); + } + else if (socket->type == SOCK_GEOMETRY) { + BKE_modifier_set_error(ob, md, "Node group can only have one geometry input"); + } + else { + BKE_modifier_set_error(ob, md, "Missing property for input socket \"%s\"", socket->name); + } + continue; + } + + const SocketPropertyType *property_type = get_socket_property_type(*socket); + if (!property_type->is_correct_type(*property)) { + BKE_modifier_set_error( + ob, md, "Property type does not match input socket \"(%s)\"", socket->name); + continue; + } + } + + bool has_geometry_output = false; + LISTBASE_FOREACH (const bNodeSocket *, socket, &nmd->node_group->outputs) { + if (socket->type == SOCK_GEOMETRY) { + has_geometry_output = true; + } + } + + if (!has_geometry_output) { + BKE_modifier_set_error(ob, md, "Node group must have a geometry output"); + } +} + +static void modifyGeometry(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet &geometry_set) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + if (nmd->node_group == nullptr) { + return; + } + + check_property_socket_sync(ctx->object, md); + + blender::nodes::NodeTreeRefMap tree_refs; + DerivedNodeTree tree{nmd->node_group, tree_refs}; + + if (tree.has_link_cycles()) { + BKE_modifier_set_error(ctx->object, md, "Node group has cycles"); + return; + } + + Span<const DNode *> input_nodes = tree.nodes_by_type("NodeGroupInput"); + Span<const DNode *> output_nodes = tree.nodes_by_type("NodeGroupOutput"); + + if (input_nodes.size() > 1) { + return; + } + if (output_nodes.size() != 1) { + return; + } + + Span<const DOutputSocket *> group_inputs = (input_nodes.size() == 1) ? + input_nodes[0]->outputs().drop_back(1) : + Span<const DOutputSocket *>{}; + Span<const DInputSocket *> group_outputs = output_nodes[0]->inputs().drop_back(1); + + if (group_outputs.size() == 0) { + return; + } + + const DInputSocket *group_output = group_outputs[0]; + if (group_output->idname() != "NodeSocketGeometry") { + return; + } + + geometry_set = compute_geometry( + tree, group_inputs, *group_outputs[0], std::move(geometry_set), nmd, ctx); +} + +static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) +{ + GeometrySet geometry_set = GeometrySet::create_with_mesh(mesh, GeometryOwnershipType::Editable); + geometry_set.get_component_for_write<MeshComponent>().copy_vertex_group_names_from_object( + *ctx->object); + modifyGeometry(md, ctx, geometry_set); + Mesh *new_mesh = geometry_set.get_component_for_write<MeshComponent>().release(); + if (new_mesh == nullptr) { + return BKE_mesh_new_nomain(0, 0, 0, 0, 0); + } + return new_mesh; +} + +static void modifyPointCloud(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + modifyGeometry(md, ctx, *geometry_set); +} + +/* Drawing the properties manually with #uiItemR instead of #uiDefAutoButsRNA allows using + * the node socket identifier for the property names, since they are unique, but also having + * the correct label displayed in the UI. */ +static void draw_property_for_socket(uiLayout *layout, + PointerRNA *settings_ptr, + const IDProperty *modifier_props, + const bNodeSocket &socket) +{ + const SocketPropertyType *property_type = get_socket_property_type(socket); + if (property_type == nullptr) { + return; + } + + /* The property should be created in #MOD_nodes_update_interface with the correct type. */ + IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier); + + /* IDProperties can be removed with python, so there could be a situation where + * there isn't a property for a socket or it doesn't have the correct type. */ + if (property != nullptr && property_type->is_correct_type(*property)) { + char rna_path[128]; + BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket.identifier); + uiItemR(layout, settings_ptr, rna_path, 0, socket.name, ICON_NONE); + } +} + +static void panel_draw(const bContext *C, Panel *panel) +{ +#ifdef WITH_GEOMETRY_NODES + uiLayout *layout = panel->layout; + + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); + NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); + + uiLayoutSetPropSep(layout, true); + /* This should be removed, but animation currently doesn't work with the IDProperties. */ + uiLayoutSetPropDecorate(layout, false); + + uiTemplateID(layout, + C, + ptr, + "node_group", + "node.new_geometry_node_group_assign", + nullptr, + nullptr, + 0, + false, + nullptr); + + if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { + PointerRNA settings_ptr; + RNA_pointer_create(ptr->owner_id, &RNA_NodesModifierSettings, &nmd->settings, &settings_ptr); + + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { + draw_property_for_socket(layout, &settings_ptr, nmd->settings.properties, *socket); + } + } + + modifier_panel_end(layout, ptr); +#endif +} + +static void panelRegister(ARegionType *region_type) +{ + modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); +} + +static void blendWrite(BlendWriter *writer, const ModifierData *md) +{ + const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md); + if (nmd->settings.properties != nullptr) { + /* Note that the property settings are based on the socket type info + * and don't necessarily need to be written, but we can't just free them. */ + IDP_BlendWrite(writer, nmd->settings.properties); + } +} + +static void blendRead(BlendDataReader *reader, ModifierData *md) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + BLO_read_data_address(reader, &nmd->settings.properties); + IDP_BlendDataRead(reader, &nmd->settings.properties); +} + +static void copyData(const ModifierData *md, ModifierData *target, const int flag) +{ + const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md); + NodesModifierData *tnmd = reinterpret_cast<NodesModifierData *>(target); + + BKE_modifier_copydata_generic(md, target, flag); + + if (nmd->settings.properties != nullptr) { + tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag); + } +} + +static void freeData(ModifierData *md) +{ + NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); + if (nmd->settings.properties != nullptr) { + IDP_FreeProperty_ex(nmd->settings.properties, false); + nmd->settings.properties = nullptr; + } +} + +ModifierTypeInfo modifierType_Nodes = { + /* name */ "GeometryNodes", + /* structName */ "NodesModifierData", + /* structSize */ sizeof(NodesModifierData), +#ifdef WITH_GEOMETRY_NODES + /* srna */ &RNA_NodesModifier, +#else + /* srna */ &RNA_Modifier, +#endif + /* type */ eModifierTypeType_Constructive, + /* flags */ + static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | + eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode), + /* icon */ ICON_MESH_DATA, /* TODO: Use correct icon. */ + + /* copyData */ copyData, + + /* deformVerts */ nullptr, + /* deformMatrices */ nullptr, + /* deformVertsEM */ nullptr, + /* deformMatricesEM */ nullptr, + /* modifyMesh */ modifyMesh, + /* modifyHair */ nullptr, + /* modifyPointCloud */ modifyPointCloud, + /* modifyVolume */ nullptr, + + /* initData */ initData, + /* requiredDataMask */ nullptr, + /* freeData */ freeData, + /* isDisabled */ isDisabled, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ nullptr, + /* dependsOnNormals */ nullptr, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ nullptr, + /* freeRuntimeData */ nullptr, + /* panelRegister */ panelRegister, + /* blendWrite */ blendWrite, + /* blendRead */ blendRead, +}; diff --git a/source/blender/modifiers/intern/MOD_simulation.cc b/source/blender/modifiers/intern/MOD_simulation.cc deleted file mode 100644 index 0766c59cda6..00000000000 --- a/source/blender/modifiers/intern/MOD_simulation.cc +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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. - */ - -/** \file - * \ingroup modifiers - */ - -#include <cstring> -#include <iostream> -#include <string> - -#include "MEM_guardedalloc.h" - -#include "BLI_float3.hh" -#include "BLI_listbase.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "DNA_defaults.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_modifier_types.h" -#include "DNA_object_types.h" -#include "DNA_pointcloud_types.h" -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" -#include "DNA_simulation_types.h" - -#include "BKE_customdata.h" -#include "BKE_lib_query.h" -#include "BKE_mesh.h" -#include "BKE_modifier.h" -#include "BKE_pointcloud.h" -#include "BKE_screen.h" -#include "BKE_simulation.h" - -#include "BLO_read_write.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "RNA_access.h" - -#include "DEG_depsgraph_build.h" -#include "DEG_depsgraph_query.h" - -#include "MOD_modifiertypes.h" -#include "MOD_ui_common.h" - -using blender::float3; - -static void initData(ModifierData *md) -{ - SimulationModifierData *smd = (SimulationModifierData *)md; - - BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(smd, modifier)); - - MEMCPY_STRUCT_AFTER(smd, DNA_struct_default_get(SimulationModifierData), modifier); -} - -static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *UNUSED(ctx)) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd); -} - -static void foreachIDLink(ModifierData *md, - Object *UNUSED(ob), - IDWalkFunc UNUSED(walk), - void *UNUSED(userData)) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd); -} - -static bool isDisabled(const struct Scene *UNUSED(scene), - ModifierData *md, - bool UNUSED(useRenderParams)) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd); - return false; -} - -static PointCloud *modifyPointCloud(ModifierData *md, - const ModifierEvalContext *UNUSED(ctx), - PointCloud *pointcloud) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd); - return pointcloud; -} - -static void panel_draw(const bContext *UNUSED(C), Panel *panel) -{ - uiLayout *layout = panel->layout; - - PointerRNA ob_ptr; - PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); - - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - - uiItemL(layout, "This modifier does nothing currently", ICON_INFO); - - modifier_panel_end(layout, ptr); -} - -static void panelRegister(ARegionType *region_type) -{ - modifier_panel_register(region_type, eModifierType_Simulation, panel_draw); -} - -static void blendWrite(BlendWriter *writer, const ModifierData *md) -{ - const SimulationModifierData *smd = reinterpret_cast<const SimulationModifierData *>(md); - UNUSED_VARS(smd, writer); -} - -static void blendRead(BlendDataReader *reader, ModifierData *md) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd, reader); -} - -static void copyData(const ModifierData *md, ModifierData *target, const int flag) -{ - const SimulationModifierData *smd = reinterpret_cast<const SimulationModifierData *>(md); - SimulationModifierData *tsmd = reinterpret_cast<SimulationModifierData *>(target); - UNUSED_VARS(smd, tsmd); - - BKE_modifier_copydata_generic(md, target, flag); -} - -static void freeData(ModifierData *md) -{ - SimulationModifierData *smd = reinterpret_cast<SimulationModifierData *>(md); - UNUSED_VARS(smd); -} - -ModifierTypeInfo modifierType_Simulation = { - /* name */ "Simulation", - /* structName */ "SimulationModifierData", - /* structSize */ sizeof(SimulationModifierData), -#ifdef WITH_GEOMETRY_NODES - /* srna */ &RNA_SimulationModifier, -#else - /* srna */ &RNA_Modifier, -#endif - /* type */ eModifierTypeType_None, - /* flags */ (ModifierTypeFlag)0, - /* icon */ ICON_PHYSICS, /* TODO: Use correct icon. */ - - /* copyData */ copyData, - - /* deformVerts */ nullptr, - /* deformMatrices */ nullptr, - /* deformVertsEM */ nullptr, - /* deformMatricesEM */ nullptr, - /* modifyMesh */ nullptr, - /* modifyHair */ nullptr, - /* modifyPointCloud */ modifyPointCloud, - /* modifyVolume */ nullptr, - - /* initData */ initData, - /* requiredDataMask */ nullptr, - /* freeData */ freeData, - /* isDisabled */ isDisabled, - /* updateDepsgraph */ updateDepsgraph, - /* dependsOnTime */ nullptr, - /* dependsOnNormals */ nullptr, - /* foreachIDLink */ foreachIDLink, - /* foreachTexLink */ nullptr, - /* freeRuntimeData */ nullptr, - /* panelRegister */ panelRegister, - /* blendWrite */ blendWrite, - /* blendRead */ blendRead, -}; diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index 5a07c4e5e64..1930a38b825 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -48,11 +48,17 @@ #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" -static Mesh *triangulate_mesh(Mesh *mesh, - const int quad_method, - const int ngon_method, - const int min_vertices, - const int flag) +Mesh *triangulate_mesh(Mesh *mesh, + const int quad_method, + const int ngon_method, + const int min_vertices, + const int flag); + +Mesh *triangulate_mesh(Mesh *mesh, + const int quad_method, + const int ngon_method, + const int min_vertices, + const int flag) { Mesh *result; BMesh *bm; diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index e0802dc5fb4..55409cba114 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -342,9 +342,9 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(MeshSequenceCache); INIT_TYPE(SurfaceDeform); INIT_TYPE(WeightedNormal); - INIT_TYPE(Simulation); INIT_TYPE(MeshToVolume); INIT_TYPE(VolumeDisplace); INIT_TYPE(VolumeToMesh); + INIT_TYPE(Nodes); #undef INIT_TYPE } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index f88d50167ac..a367f40dca7 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -24,11 +24,12 @@ set(INC function intern shader - simulation + geometry texture ../blenkernel ../blenlib ../blentranslation + ../bmesh ../depsgraph ../functions ../gpu @@ -137,6 +138,22 @@ set(SRC function/nodes/node_fn_switch.cc function/node_function_util.cc + geometry/nodes/node_geo_attribute_math.cc + geometry/nodes/node_geo_common.cc + geometry/nodes/node_geo_boolean.cc + geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_join_geometry.cc + geometry/nodes/node_geo_object_info.cc + geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_point_distribute.cc + geometry/nodes/node_geo_point_instance.cc + geometry/nodes/node_geo_random_attribute.cc + geometry/nodes/node_geo_transform.cc + geometry/nodes/node_geo_triangulate.cc + geometry/node_geometry_exec.cc + geometry/node_geometry_tree.cc + geometry/node_geometry_util.cc + shader/nodes/node_shader_add_shader.c shader/nodes/node_shader_ambient_occlusion.c shader/nodes/node_shader_attribute.c @@ -230,10 +247,6 @@ set(SRC shader/node_shader_tree.c shader/node_shader_util.c - simulation/nodes/node_sim_common.cc - simulation/node_simulation_tree.cc - simulation/node_simulation_util.cc - texture/nodes/node_texture_at.c texture/nodes/node_texture_bricks.c texture/nodes/node_texture_checker.c @@ -261,18 +274,21 @@ set(SRC texture/node_texture_util.c intern/derived_node_tree.cc + intern/math_functions.cc intern/node_common.c intern/node_exec.c + intern/node_geometry_exec.cc intern/node_socket.cc intern/node_tree_dependencies.cc intern/node_tree_multi_function.cc intern/node_tree_ref.cc intern/node_util.c + intern/type_callbacks.cc composite/node_composite_util.h function/node_function_util.hh shader/node_shader_util.h - simulation/node_simulation_util.h + geometry/node_geometry_util.hh texture/node_texture_util.h NOD_common.h @@ -283,10 +299,12 @@ set(SRC NOD_node_tree_multi_function.hh NOD_node_tree_ref.hh NOD_shader.h - NOD_simulation.h + NOD_geometry.h + NOD_math_functions.hh NOD_socket.h NOD_static_types.h NOD_texture.h + NOD_type_callbacks.hh intern/node_common.h intern/node_exec.h intern/node_util.h @@ -295,6 +313,7 @@ set(SRC set(LIB bf_functions bf_intern_sky + bf_bmesh ) if(WITH_PYTHON) @@ -326,9 +345,12 @@ if(WITH_COMPOSITOR) add_definitions(-DWITH_COMPOSITOR) endif() - if(WITH_FREESTYLE) add_definitions(-DWITH_FREESTYLE) endif() +if(WITH_OPENSUBDIV) + add_definitions(-DWITH_OPENSUBDIV) +endif() + blender_add_lib(bf_nodes "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index 9c71ae79cf2..087bfac4442 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -31,6 +31,8 @@ #include "NOD_node_tree_ref.hh" +#include "BLI_vector_set.hh" + namespace blender::nodes { class DSocket; @@ -65,6 +67,8 @@ class DSocket : NonCopyable, NonMovable { PointerRNA *rna() const; StringRefNull idname() const; StringRefNull name() const; + StringRefNull identifier() const; + bNodeSocketType *typeinfo() const; const SocketRef &socket_ref() const; bNodeSocket *bsocket() const; @@ -147,6 +151,8 @@ class DNode : NonCopyable, NonMovable { PointerRNA *rna() const; StringRefNull idname() const; StringRefNull name() const; + bNode *bnode() const; + bNodeType *typeinfo() const; private: void destruct_with_sockets(); @@ -180,11 +186,15 @@ class DerivedNodeTree : NonCopyable, NonMovable { Vector<DOutputSocket *> output_sockets_; MultiValueMap<const bNodeType *, DNode *> nodes_by_type_; + VectorSet<const NodeTreeRef *> used_node_tree_refs_; + bNodeTree *btree_; public: DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs); ~DerivedNodeTree(); + bNodeTree *btree() const; + Span<const DNode *> nodes() const; Span<const DNode *> nodes_by_type(StringRefNull idname) const; Span<const DNode *> nodes_by_type(const bNodeType *nodetype) const; @@ -195,6 +205,10 @@ class DerivedNodeTree : NonCopyable, NonMovable { Span<const DGroupInput *> group_inputs() const; + Span<const NodeTreeRef *> used_node_tree_refs() const; + + bool has_link_cycles() const; + std::string to_dot() const; private: @@ -229,6 +243,15 @@ class DerivedNodeTree : NonCopyable, NonMovable { Vector<DParentNode *> &&all_parent_nodes); }; +namespace derived_node_tree_types { +using nodes::DerivedNodeTree; +using nodes::DGroupInput; +using nodes::DInputSocket; +using nodes::DNode; +using nodes::DOutputSocket; +using nodes::DParentNode; +}; // namespace derived_node_tree_types + /* -------------------------------------------------------------------- * DSocket inline methods. */ @@ -288,6 +311,16 @@ inline StringRefNull DSocket::name() const return socket_ref_->name(); } +inline StringRefNull DSocket::identifier() const +{ + return socket_ref_->identifier(); +} + +inline bNodeSocketType *DSocket::typeinfo() const +{ + return socket_ref_->bsocket()->typeinfo; +} + inline const SocketRef &DSocket::socket_ref() const { return *socket_ref_; @@ -445,6 +478,16 @@ inline StringRefNull DNode::name() const return node_ref_->name(); } +inline bNode *DNode::bnode() const +{ + return node_ref_->bnode(); +} + +inline bNodeType *DNode::typeinfo() const +{ + return node_ref_->bnode()->typeinfo; +} + /* -------------------------------------------------------------------- * DParentNode inline methods. */ @@ -468,6 +511,11 @@ inline int DParentNode::id() const * DerivedNodeTree inline methods. */ +inline bNodeTree *DerivedNodeTree::btree() const +{ + return btree_; +} + inline Span<const DNode *> DerivedNodeTree::nodes() const { return nodes_by_id_; @@ -504,4 +552,9 @@ inline Span<const DGroupInput *> DerivedNodeTree::group_inputs() const return group_inputs_; } +inline Span<const NodeTreeRef *> DerivedNodeTree::used_node_tree_refs() const +{ + return used_node_tree_refs_; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h new file mode 100644 index 00000000000..0532547375f --- /dev/null +++ b/source/blender/nodes/NOD_geometry.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct bNodeTreeType *ntreeType_Geometry; + +void register_node_tree_type_geo(void); + +void register_node_type_geo_group(void); + +void register_node_type_geo_boolean(void); +void register_node_type_geo_edge_split(void); +void register_node_type_geo_transform(void); +void register_node_type_geo_subdivision_surface(void); +void register_node_type_geo_triangulate(void); +void register_node_type_geo_point_distribute(void); +void register_node_type_geo_point_instance(void); +void register_node_type_geo_object_info(void); +void register_node_type_geo_random_attribute(void); +void register_node_type_geo_attribute_math(void); +void register_node_type_geo_join_geometry(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh new file mode 100644 index 00000000000..2b95f76d06b --- /dev/null +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#pragma once + +#include "FN_generic_value_map.hh" + +#include "BKE_attribute_access.hh" +#include "BKE_geometry_set.hh" +#include "BKE_persistent_data_handle.hh" + +#include "DNA_node_types.h" + +namespace blender::nodes { + +using bke::Float3ReadAttribute; +using bke::Float3WriteAttribute; +using bke::FloatReadAttribute; +using bke::FloatWriteAttribute; +using bke::PersistentDataHandleMap; +using bke::PersistentObjectHandle; +using bke::ReadAttribute; +using bke::ReadAttributePtr; +using bke::WriteAttribute; +using bke::WriteAttributePtr; +using fn::CPPType; +using fn::GMutablePointer; +using fn::GValueMap; + +class GeoNodeExecParams { + private: + const bNode &node_; + GValueMap<StringRef> &input_values_; + GValueMap<StringRef> &output_values_; + const PersistentDataHandleMap &handle_map_; + const Object *self_object_; + + public: + GeoNodeExecParams(const bNode &node, + GValueMap<StringRef> &input_values, + GValueMap<StringRef> &output_values, + const PersistentDataHandleMap &handle_map, + const Object *self_object) + : node_(node), + input_values_(input_values), + output_values_(output_values), + handle_map_(handle_map), + self_object_(self_object) + { + } + + /** + * Get the input value for the input socket with the given identifier. + * + * The node calling becomes responsible for destructing the value before it is done + * executing. This method can only be called once for each identifier. + */ + GMutablePointer extract_input(StringRef identifier) + { +#ifdef DEBUG + this->check_extract_input(identifier); +#endif + return input_values_.extract(identifier); + } + + /** + * Get the input value for the input socket with the given identifier. + * + * This method can only be called once for each identifier. + */ + template<typename T> T extract_input(StringRef identifier) + { +#ifdef DEBUG + this->check_extract_input(identifier, &CPPType::get<T>()); +#endif + return input_values_.extract<T>(identifier); + } + + /** + * Get the input value for the input socket with the given identifier. + * + * This makes a copy of the value, which is fine for most types but should be avoided for + * geometry sets. + */ + template<typename T> T get_input(StringRef identifier) const + { +#ifdef DEBUG + this->check_extract_input(identifier, &CPPType::get<T>()); +#endif + return input_values_.lookup<T>(identifier); + } + + /** + * Move-construct a new value based on the given value and store it for the given socket + * identifier. + */ + void set_output_by_move(StringRef identifier, GMutablePointer value) + { +#ifdef DEBUG + BLI_assert(value.type() != nullptr); + BLI_assert(value.get() != nullptr); + this->check_set_output(identifier, *value.type()); +#endif + output_values_.add_new_by_move(identifier, value); + } + + /** + * Store the output value for the given socket identifier. + */ + template<typename T> void set_output(StringRef identifier, T &&value) + { +#ifdef DEBUG + this->check_set_output(identifier, CPPType::get<std::decay_t<T>>()); +#endif + output_values_.add_new(identifier, std::forward<T>(value)); + } + + /** + * Get the node that is currently being executed. + */ + const bNode &node() const + { + return node_; + } + + const PersistentDataHandleMap &handle_map() const + { + return handle_map_; + } + + const Object *self_object() const + { + return self_object_; + } + + private: + /* Utilities for detecting common errors at when using this class. */ + void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const; + void check_set_output(StringRef identifier, const CPPType &value_type) const; +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_math_functions.hh b/source/blender/nodes/NOD_math_functions.hh new file mode 100644 index 00000000000..70e4174a844 --- /dev/null +++ b/source/blender/nodes/NOD_math_functions.hh @@ -0,0 +1,200 @@ +/* + * 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. + */ + +#pragma once + +#include "DNA_node_types.h" + +#include "BLI_math_base_safe.h" +#include "BLI_math_rotation.h" +#include "BLI_string_ref.hh" + +namespace blender::nodes { + +struct FloatMathOperationInfo { + StringRefNull title_case_name; + StringRefNull shader_name; + + FloatMathOperationInfo() = delete; + FloatMathOperationInfo(StringRefNull title_case_name, StringRefNull shader_name) + : title_case_name(title_case_name), shader_name(shader_name) + { + } +}; + +const FloatMathOperationInfo *get_float_math_operation_info(const int operation); + +/** + * This calls the `callback` with two arguments: + * 1. The math function that takes a float as input and outputs a new float. + * 2. A #FloatMathOperationInfo struct reference. + * Returns true when the callback has been called, otherwise false. + * + * The math function that is passed to the callback is actually a lambda function that is different + * for every operation. Therefore, if the callback is templated on the math function, it will get + * instantiated for every operation separately. This has two benefits: + * - The compiler can optimize the callback for every operation separately. + * - A static variable declared in the callback will be generated for every operation separately. + * + * If separate instantiations are not desired, the callback can also take a function pointer with + * the following signature as input instead: float (*math_function)(float a). + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_EXPONENT: + return dispatch([](float a) { return expf(a); }); + case NODE_MATH_SQRT: + return dispatch([](float a) { return safe_sqrtf(a); }); + case NODE_MATH_INV_SQRT: + return dispatch([](float a) { return safe_inverse_sqrtf(a); }); + case NODE_MATH_ABSOLUTE: + return dispatch([](float a) { return fabs(a); }); + case NODE_MATH_RADIANS: + return dispatch([](float a) { return (float)DEG2RAD(a); }); + case NODE_MATH_DEGREES: + return dispatch([](float a) { return (float)RAD2DEG(a); }); + case NODE_MATH_SIGN: + return dispatch([](float a) { return compatible_signf(a); }); + case NODE_MATH_ROUND: + return dispatch([](float a) { return floorf(a + 0.5f); }); + case NODE_MATH_FLOOR: + return dispatch([](float a) { return floorf(a); }); + case NODE_MATH_CEIL: + return dispatch([](float a) { return ceilf(a); }); + case NODE_MATH_FRACTION: + return dispatch([](float a) { return a - floorf(a); }); + case NODE_MATH_TRUNC: + return dispatch([](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); }); + case NODE_MATH_SINE: + return dispatch([](float a) { return sinf(a); }); + case NODE_MATH_COSINE: + return dispatch([](float a) { return cosf(a); }); + case NODE_MATH_TANGENT: + return dispatch([](float a) { return tanf(a); }); + case NODE_MATH_SINH: + return dispatch([](float a) { return sinhf(a); }); + case NODE_MATH_COSH: + return dispatch([](float a) { return coshf(a); }); + case NODE_MATH_TANH: + return dispatch([](float a) { return tanhf(a); }); + case NODE_MATH_ARCSINE: + return dispatch([](float a) { return safe_asinf(a); }); + case NODE_MATH_ARCCOSINE: + return dispatch([](float a) { return safe_acosf(a); }); + case NODE_MATH_ARCTANGENT: + return dispatch([](float a) { return atanf(a); }); + } + return false; +} + +/** + * This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature. + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_ADD: + return dispatch([](float a, float b) { return a + b; }); + case NODE_MATH_SUBTRACT: + return dispatch([](float a, float b) { return a - b; }); + case NODE_MATH_MULTIPLY: + return dispatch([](float a, float b) { return a * b; }); + case NODE_MATH_DIVIDE: + return dispatch([](float a, float b) { return safe_divide(a, b); }); + case NODE_MATH_POWER: + return dispatch([](float a, float b) { return safe_powf(a, b); }); + case NODE_MATH_LOGARITHM: + return dispatch([](float a, float b) { return safe_logf(a, b); }); + case NODE_MATH_MINIMUM: + return dispatch([](float a, float b) { return std::min(a, b); }); + case NODE_MATH_MAXIMUM: + return dispatch([](float a, float b) { return std::max(a, b); }); + case NODE_MATH_LESS_THAN: + return dispatch([](float a, float b) { return (float)(a < b); }); + case NODE_MATH_GREATER_THAN: + return dispatch([](float a, float b) { return (float)(a > b); }); + case NODE_MATH_MODULO: + return dispatch([](float a, float b) { return safe_modf(a, b); }); + case NODE_MATH_SNAP: + return dispatch([](float a, float b) { return floorf(safe_divide(a, b)) * b; }); + case NODE_MATH_ARCTAN2: + return dispatch([](float a, float b) { return atan2f(a, b); }); + case NODE_MATH_PINGPONG: + return dispatch([](float a, float b) { return pingpongf(a, b); }); + } + return false; +} + +/** + * This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature. + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_fl_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_MULTIPLY_ADD: + return dispatch([](float a, float b, float c) { return a * b + c; }); + case NODE_MATH_COMPARE: + return dispatch([](float a, float b, float c) -> float { + return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f; + }); + case NODE_MATH_SMOOTH_MIN: + return dispatch([](float a, float b, float c) { return smoothminf(a, b, c); }); + case NODE_MATH_SMOOTH_MAX: + return dispatch([](float a, float b, float c) { return -smoothminf(-a, -b, -c); }); + case NODE_MATH_WRAP: + return dispatch([](float a, float b, float c) { return wrapf(a, b, c); }); + } + return false; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_node_tree_multi_function.hh b/source/blender/nodes/NOD_node_tree_multi_function.hh index 25787231afa..552ef5509fa 100644 --- a/source/blender/nodes/NOD_node_tree_multi_function.hh +++ b/source/blender/nodes/NOD_node_tree_multi_function.hh @@ -26,21 +26,12 @@ #include "FN_multi_function_network.hh" #include "NOD_derived_node_tree.hh" +#include "NOD_type_callbacks.hh" #include "BLI_resource_collector.hh" namespace blender::nodes { -/* Maybe this should be moved to BKE_node.h. */ -inline bool is_multi_function_data_socket(const bNodeSocket *bsocket) -{ - if (bsocket->typeinfo->get_mf_data_type != nullptr) { - BLI_assert(bsocket->typeinfo->expand_in_mf_network != nullptr); - return true; - } - return false; -} - /** * A MFNetworkTreeMap maps various components of a DerivedNodeTree to components of a * fn::MFNetwork. This is necessary for further processing of a multi-function network that has @@ -149,7 +140,7 @@ class MFNetworkTreeMap { if (!dsocket->is_available()) { continue; } - if (!is_multi_function_data_socket(dsocket->bsocket())) { + if (!socket_is_mf_data_socket(*dsocket->bsocket()->typeinfo)) { continue; } fn::MFSocket *socket = sockets[used_sockets]; @@ -299,6 +290,11 @@ class SocketMFNetworkBuilder : public MFNetworkBuilderBase { { this->construct_generator_fn<fn::CustomMF_Constant<T>>(std::move(value)); } + void set_constant_value(const CPPType &type, const void *value) + { + /* The value has live as long as the generated mf network. */ + this->construct_generator_fn<fn::CustomMF_GenericConstant>(type, value); + } template<typename T, typename... Args> void construct_generator_fn(Args &&... args) { @@ -397,4 +393,37 @@ MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, const DerivedNodeTree &tree, ResourceCollector &resources); +using MultiFunctionByNode = Map<const DNode *, const fn::MultiFunction *>; +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, + ResourceCollector &resources); + +class DataTypeConversions { + private: + Map<std::pair<fn::MFDataType, fn::MFDataType>, const fn::MultiFunction *> conversions_; + + public: + void add(fn::MFDataType from_type, fn::MFDataType to_type, const fn::MultiFunction &fn) + { + conversions_.add_new({from_type, to_type}, &fn); + } + + const fn::MultiFunction *get_conversion(fn::MFDataType from, fn::MFDataType to) const + { + return conversions_.lookup_default({from, to}, nullptr); + } + + bool is_convertible(const CPPType &from_type, const CPPType &to_type) const + { + return conversions_.contains( + {fn::MFDataType::ForSingle(from_type), fn::MFDataType::ForSingle(to_type)}); + } + + void convert(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const; +}; + +const DataTypeConversions &get_implicit_type_conversions(); + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 3b085248a39..ebdd8b7fe49 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -101,6 +101,7 @@ class SocketRef : NonCopyable, NonMovable { StringRefNull idname() const; StringRefNull name() const; + StringRefNull identifier() const; bNodeSocket *bsocket() const; bNode *bnode() const; @@ -176,6 +177,8 @@ class NodeTreeRef : NonCopyable, NonMovable { Span<const InputSocketRef *> input_sockets() const; Span<const OutputSocketRef *> output_sockets() const; + bool has_link_cycles() const; + bNodeTree *btree() const; std::string to_dot() const; @@ -272,6 +275,11 @@ inline StringRefNull SocketRef::name() const return bsocket_->name; } +inline StringRefNull SocketRef::identifier() const +{ + return bsocket_->identifier; +} + inline bNodeSocket *SocketRef::bsocket() const { return bsocket_; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 0173706b570..09e0908140c 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -266,6 +266,17 @@ DefNode(FunctionNode, FN_NODE_COMBINE_STRINGS, 0, "COMBINE_STRINGS DefNode(FunctionNode, FN_NODE_OBJECT_TRANSFORMS, 0, "OBJECT_TRANSFORMS", ObjectTransforms, "Object Transforms", "") DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") +DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") +DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, 0, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, 0, "POINT_INSTANCE", PointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, 0, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_RANDOM_ATTRIBUTE, def_geo_random_attribute, "RANDOM_ATTRIBUTE", RandomAttribute, "Random Attribute", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/NOD_type_callbacks.hh b/source/blender/nodes/NOD_type_callbacks.hh new file mode 100644 index 00000000000..d1a4bd3ad7a --- /dev/null +++ b/source/blender/nodes/NOD_type_callbacks.hh @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#pragma once + +#include <optional> + +#include "BKE_node.h" + +#include "FN_multi_function_data_type.hh" + +namespace blender::nodes { + +using fn::CPPType; +using fn::MFDataType; + +const CPPType *socket_cpp_type_get(const bNodeSocketType &stype); +std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype); +bool socket_is_mf_data_socket(const bNodeSocketType &stype); +bool socket_cpp_value_get(const bNodeSocket &socket, void *r_value); +void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder); + +} // namespace blender::nodes diff --git a/source/blender/nodes/function/node_function_util.cc b/source/blender/nodes/function/node_function_util.cc index 342c330a8fa..05b452e61a5 100644 --- a/source/blender/nodes/function/node_function_util.cc +++ b/source/blender/nodes/function/node_function_util.cc @@ -20,7 +20,7 @@ bool fn_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { /* Function nodes are only supported in simulation node trees so far. */ - return STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "GeometryNodeTree"); } void fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc index f8bd9a30940..3d4006b5953 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc @@ -100,7 +100,7 @@ void register_node_type_fn_float_compare() { static bNodeType ntype; - fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Boolean Math", 0, 0); + fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Float Compare", 0, 0); node_type_socket_templates(&ntype, fn_node_float_compare_in, fn_node_float_compare_out); node_type_label(&ntype, node_float_compare_label); node_type_update(&ntype, node_float_compare_update); diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/node_fn_random_float.cc index 584c544946e..f3401a2c00d 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_float.cc +++ b/source/blender/nodes/function/nodes/node_fn_random_float.cc @@ -21,7 +21,7 @@ static bNodeSocketTemplate fn_node_random_float_in[] = { {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_INT, N_("Seed")}, + {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; diff --git a/source/blender/nodes/geometry/node_geometry_exec.cc b/source/blender/nodes/geometry/node_geometry_exec.cc new file mode 100644 index 00000000000..64a04d79076 --- /dev/null +++ b/source/blender/nodes/geometry/node_geometry_exec.cc @@ -0,0 +1,23 @@ +/* + * 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 "NOD_geometry_exec.hh" + +MAKE_CPP_TYPE(GeometrySet, GeometrySet); + +namespace blender::nodes { + +} diff --git a/source/blender/nodes/simulation/node_simulation_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index eb3257d7e66..d4a9805f311 100644 --- a/source/blender/nodes/simulation/node_simulation_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -18,7 +18,7 @@ #include "MEM_guardedalloc.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" #include "BKE_node.h" @@ -28,18 +28,18 @@ #include "RNA_access.h" -bNodeTreeType *ntreeType_Simulation; +bNodeTreeType *ntreeType_Geometry; -void register_node_tree_type_sim(void) +void register_node_tree_type_geo(void) { - bNodeTreeType *tt = ntreeType_Simulation = static_cast<bNodeTreeType *>( - MEM_callocN(sizeof(bNodeTreeType), "simulation node tree type")); - tt->type = NTREE_SIMULATION; - strcpy(tt->idname, "SimulationNodeTree"); - strcpy(tt->ui_name, N_("Simulation Editor")); + bNodeTreeType *tt = ntreeType_Geometry = static_cast<bNodeTreeType *>( + MEM_callocN(sizeof(bNodeTreeType), "geometry node tree type")); + tt->type = NTREE_GEOMETRY; + strcpy(tt->idname, "GeometryNodeTree"); + strcpy(tt->ui_name, N_("Geometry Node Editor")); tt->ui_icon = 0; /* defined in drawnode.c */ - strcpy(tt->ui_description, N_("Simulation nodes")); - tt->rna_ext.srna = &RNA_SimulationNodeTree; + strcpy(tt->ui_description, N_("Geometry nodes")); + tt->rna_ext.srna = &RNA_GeometryNodeTree; ntreeTypeAdd(tt); } diff --git a/source/blender/nodes/simulation/node_simulation_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index ae875335da8..e8d2494f91d 100644 --- a/source/blender/nodes/simulation/node_simulation_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -14,16 +14,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_simulation_util.h" +#include "node_geometry_util.hh" #include "node_util.h" -bool sim_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { - return STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "GeometryNodeTree"); } -void sim_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) +void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) { node_type_base(ntype, type, name, nclass, flag); - ntype->poll = sim_node_poll_default; + ntype->poll = geo_node_poll_default; } diff --git a/source/blender/nodes/simulation/node_simulation_util.h b/source/blender/nodes/geometry/node_geometry_util.hh index 76a10715cff..bb26763642b 100644 --- a/source/blender/nodes/simulation/node_simulation_util.h +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -18,6 +18,7 @@ #include <string.h> +#include "BLI_float3.hh" #include "BLI_utildefines.h" #include "MEM_guardedalloc.h" @@ -28,10 +29,11 @@ #include "BLT_translation.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" +#include "NOD_geometry_exec.hh" #include "node_util.h" -void sim_node_type_base( +void geo_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); -bool sim_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool geo_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc new file mode 100644 index 00000000000..5e2830d2f4e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -0,0 +1,167 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BKE_attribute.h" +#include "BKE_attribute_access.hh" + +#include "BLI_array.hh" +#include "BLI_math_base_safe.h" +#include "BLI_rand.hh" + +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +#include "NOD_math_functions.hh" + +static bNodeSocketTemplate geo_node_attribute_math_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute A")}, + {SOCK_FLOAT, N_("A"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Attribute B")}, + {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_math_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_math_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = NODE_MATH_ADD; + node->custom2 = GEO_NODE_USE_ATTRIBUTE_A | GEO_NODE_USE_ATTRIBUTE_B; +} + +static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_attribute_a = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_float_a = sock_attribute_a->next; + bNodeSocket *sock_attribute_b = sock_float_a->next; + bNodeSocket *sock_float_b = sock_attribute_b->next; + + GeometryNodeUseAttributeFlag flag = static_cast<GeometryNodeUseAttributeFlag>(node->custom2); + + nodeSetSocketAvailability(sock_attribute_a, flag & GEO_NODE_USE_ATTRIBUTE_A); + nodeSetSocketAvailability(sock_attribute_b, flag & GEO_NODE_USE_ATTRIBUTE_B); + nodeSetSocketAvailability(sock_float_a, !(flag & GEO_NODE_USE_ATTRIBUTE_A)); + nodeSetSocketAvailability(sock_float_b, !(flag & GEO_NODE_USE_ATTRIBUTE_B)); +} + +namespace blender::nodes { + +static void do_math_operation(const FloatReadAttribute &input_a, + const FloatReadAttribute &input_b, + FloatWriteAttribute result, + const int operation) +{ + const int size = input_a.size(); + + Span<float> span_a = input_a.get_span(); + Span<float> span_b = input_b.get_span(); + MutableSpan<float> span_result = result.get_span(); + + bool success = try_dispatch_float_math_fl_fl_to_fl( + operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { + for (const int i : IndexRange(size)) { + const float in1 = span_a[i]; + const float in2 = span_b[i]; + const float out = math_function(in1, in2); + span_result[i] = out; + } + }); + + result.apply_span(); + + /* The operation is not supported by this node currently. */ + BLI_assert(success); + UNUSED_VARS_NDEBUG(success); +} + +static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + const int operation = node.custom1; + + /* The result type of this node is always float. */ + const CustomDataType result_type = CD_PROP_FLOAT; + /* The result domain is always point for now. */ + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + + /* Get result attribute first, in case it has to overwrite one of the existing attributes. */ + const std::string result_name = params.get_input<std::string>("Result"); + WriteAttributePtr attribute_result = component.attribute_try_ensure_for_write( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + GeometryNodeUseAttributeFlag flag = static_cast<GeometryNodeUseAttributeFlag>(node.custom2); + + auto get_input_attribute = [&](GeometryNodeUseAttributeFlag use_flag, + StringRef attribute_socket_identifier, + StringRef value_socket_identifier) { + if (flag & use_flag) { + const std::string attribute_name = params.get_input<std::string>( + attribute_socket_identifier); + return component.attribute_try_get_for_read(attribute_name, result_domain, result_type); + } + const float value = params.get_input<float>(value_socket_identifier); + return component.attribute_get_constant_for_read(result_domain, result_type, &value); + }; + + ReadAttributePtr attribute_a = get_input_attribute(GEO_NODE_USE_ATTRIBUTE_A, "Attribute A", "A"); + ReadAttributePtr attribute_b = get_input_attribute(GEO_NODE_USE_ATTRIBUTE_B, "Attribute B", "B"); + + if (!attribute_a || !attribute_b) { + /* Attribute wasn't found. */ + return; + } + + do_math_operation( + std::move(attribute_a), std::move(attribute_b), std::move(attribute_result), operation); +} + +static void geo_node_attribute_math_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_math() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MATH, "Attribute Math", 0, 0); + node_type_socket_templates(&ntype, geo_node_attribute_math_in, geo_node_attribute_math_out); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec; + node_type_update(&ntype, geo_node_attribute_math_update); + node_type_init(&ntype, geo_node_attribute_math_init); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc new file mode 100644 index 00000000000..a0ba8e3bf81 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -0,0 +1,152 @@ +/* + * 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 "MEM_guardedalloc.h" + +#include "BLI_alloca.h" +#include "BLI_math_matrix.h" + +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" + +#include "RNA_enum_types.h" + +#include "BKE_mesh.h" + +#include "bmesh.h" +#include "tools/bmesh_boolean.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_boolean_in[] = { + {SOCK_GEOMETRY, N_("Geometry A")}, + {SOCK_GEOMETRY, N_("Geometry B")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_boolean_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) +{ + return BM_elem_flag_test(f, BM_ELEM_DRAW) ? 1 : 0; +} + +static Mesh *mesh_boolean_calc(const Mesh *mesh_a, const Mesh *mesh_b, int boolean_mode) +{ + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh_a, mesh_b); + + BMesh *bm; + { + struct BMeshCreateParams bmesh_create_params = {0}; + bmesh_create_params.use_toolflags = false; + bm = BM_mesh_create(&allocsize, &bmesh_create_params); + } + + { + struct BMeshFromMeshParams bmesh_from_mesh_params = {0}; + bmesh_from_mesh_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, mesh_b, &bmesh_from_mesh_params); + BM_mesh_bm_from_me(bm, mesh_a, &bmesh_from_mesh_params); + } + + const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); + int tottri; + BMLoop *(*looptris)[3] = (BMLoop * + (*)[3])(MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__)); + BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); + + const int i_faces_end = mesh_b->totpoly; + + /* We need face normals because of 'BM_face_split_edgenet' + * we could calculate on the fly too (before calling split). */ + + int i = 0; + BMIter iter; + BMFace *bm_face; + BM_ITER_MESH (bm_face, &iter, bm, BM_FACES_OF_MESH) { + normalize_v3(bm_face->no); + + /* Temp tag to test which side split faces are from. */ + BM_elem_flag_enable(bm_face, BM_ELEM_DRAW); + + i++; + if (i == i_faces_end) { + break; + } + } + + BM_mesh_boolean( + bm, looptris, tottri, bm_face_isect_pair, nullptr, 2, false, false, boolean_mode); + + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh_a); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + MEM_freeN(looptris); + + return result; +} + +namespace blender::nodes { +static void geo_node_boolean_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_in_a = params.extract_input<GeometrySet>("Geometry A"); + GeometrySet geometry_set_in_b = params.extract_input<GeometrySet>("Geometry B"); + GeometrySet geometry_set_out; + + GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; + if (operation < 0 || operation > 2) { + BLI_assert(false); + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const Mesh *mesh_in_a = geometry_set_in_a.get_mesh_for_read(); + const Mesh *mesh_in_b = geometry_set_in_b.get_mesh_for_read(); + + if (mesh_in_a == nullptr || mesh_in_b == nullptr) { + if (operation == GEO_NODE_BOOLEAN_UNION) { + if (mesh_in_a != nullptr) { + params.set_output("Geometry", geometry_set_in_a); + } + else { + params.set_output("Geometry", geometry_set_in_b); + } + } + else { + params.set_output("Geometry", geometry_set_in_a); + } + return; + } + + Mesh *mesh_out = mesh_boolean_calc(mesh_in_a, mesh_in_b, operation); + geometry_set_out = GeometrySet::create_with_mesh(mesh_out); + + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_boolean() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", 0, 0); + node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); + ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/simulation/nodes/node_sim_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index fd26d450129..8adc3962698 100644 --- a/source/blender/nodes/simulation/nodes/node_sim_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -16,23 +16,23 @@ #include "BKE_node.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" #include "NOD_common.h" #include "node_common.h" -#include "node_simulation_util.h" +#include "node_geometry_util.hh" -void register_node_type_sim_group(void) +void register_node_type_geo_group(void) { static bNodeType ntype; - node_type_base_custom(&ntype, "SimulationNodeGroup", "Group", 0, 0); + node_type_base_custom(&ntype, "GeometryNodeGroup", "Group", 0, 0); ntype.type = NODE_GROUP; - ntype.poll = sim_node_poll_default; + ntype.poll = geo_node_poll_default; ntype.poll_instance = node_group_poll_instance; ntype.insert_link = node_insert_link_default; ntype.update_internal_links = node_update_internal_links_default; - ntype.rna_ext.srna = RNA_struct_find("SimulationNodeGroup"); + ntype.rna_ext.srna = RNA_struct_find("GeometryNodeGroup"); BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc new file mode 100644 index 00000000000..22e75b3fe03 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -0,0 +1,96 @@ +/* + * 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 "BLI_math_base.h" +#include "BLI_math_rotation.h" + +#include "DNA_modifier_types.h" + +#include "node_geometry_util.hh" + +extern "C" { +Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd); +} + +static bNodeSocketTemplate geo_node_edge_split_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_BOOLEAN, N_("Edge Angle"), true}, + {SOCK_FLOAT, + N_("Angle"), + DEG2RADF(30.0f), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + DEG2RADF(180.0f), + PROP_ANGLE}, + {SOCK_BOOLEAN, N_("Sharp Edges")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_edge_split_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_edge_split_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const bool use_sharp_flag = params.extract_input<bool>("Sharp Edges"); + const bool use_edge_angle = params.extract_input<bool>("Edge Angle"); + + if (!use_edge_angle && !use_sharp_flag) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const float split_angle = params.extract_input<float>("Angle"); + const Mesh *mesh_in = geometry_set.get_mesh_for_read(); + + /* Use modifier struct to pass arguments to the modifier code. */ + EdgeSplitModifierData emd; + memset(&emd, 0, sizeof(EdgeSplitModifierData)); + emd.split_angle = split_angle; + if (use_edge_angle) { + emd.flags = MOD_EDGESPLIT_FROMANGLE; + } + if (use_sharp_flag) { + emd.flags |= MOD_EDGESPLIT_FROMFLAG; + } + + Mesh *mesh_out = doEdgeSplit(mesh_in, &emd); + geometry_set.replace_mesh(mesh_out); + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_edge_split() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_EDGE_SPLIT, "Edge Split", 0, 0); + node_type_socket_templates(&ntype, geo_node_edge_split_in, geo_node_edge_split_out); + ntype.geometry_node_execute = blender::nodes::geo_node_edge_split_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc new file mode 100644 index 00000000000..3bf560f86f5 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -0,0 +1,276 @@ +/* + * 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 "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_pointcloud.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_join_geometry_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_join_geometry_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components) +{ + int totverts = 0; + int totloops = 0; + int totedges = 0; + int totpolys = 0; + + for (const MeshComponent *mesh_component : src_components) { + const Mesh *mesh = mesh_component->get_for_read(); + totverts += mesh->totvert; + totloops += mesh->totloop; + totedges += mesh->totedge; + totpolys += mesh->totpoly; + } + + const Mesh *first_input_mesh = src_components[0]->get_for_read(); + Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); + BKE_mesh_copy_settings(new_mesh, first_input_mesh); + + int vert_offset = 0; + int loop_offset = 0; + int edge_offset = 0; + int poly_offset = 0; + for (const MeshComponent *mesh_component : src_components) { + const Mesh *mesh = mesh_component->get_for_read(); + if (mesh == nullptr) { + continue; + } + + for (const int i : IndexRange(mesh->totvert)) { + const MVert &old_vert = mesh->mvert[i]; + MVert &new_vert = new_mesh->mvert[vert_offset + i]; + new_vert = old_vert; + } + + for (const int i : IndexRange(mesh->totedge)) { + const MEdge &old_edge = mesh->medge[i]; + MEdge &new_edge = new_mesh->medge[edge_offset + i]; + new_edge = old_edge; + new_edge.v1 += vert_offset; + new_edge.v2 += vert_offset; + } + for (const int i : IndexRange(mesh->totloop)) { + const MLoop &old_loop = mesh->mloop[i]; + MLoop &new_loop = new_mesh->mloop[loop_offset + i]; + new_loop = old_loop; + new_loop.v += vert_offset; + new_loop.e += edge_offset; + } + for (const int i : IndexRange(mesh->totpoly)) { + const MPoly &old_poly = mesh->mpoly[i]; + MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; + new_poly = old_poly; + new_poly.loopstart += loop_offset; + } + + vert_offset += mesh->totvert; + loop_offset += mesh->totloop; + edge_offset += mesh->totedge; + poly_offset += mesh->totpoly; + } + + return new_mesh; +} + +template<typename Component> +static Array<const GeometryComponent *> to_base_components(Span<const Component *> components) +{ + return components; +} + +static Set<std::string> find_all_attribute_names(Span<const GeometryComponent *> components) +{ + Set<std::string> attribute_names; + for (const GeometryComponent *component : components) { + Set<std::string> names = component->attribute_names(); + for (const std::string &name : names) { + attribute_names.add(name); + } + } + return attribute_names; +} + +static void determine_final_data_type_and_domain(Span<const GeometryComponent *> components, + StringRef attribute_name, + CustomDataType *r_type, + AttributeDomain *r_domain) +{ + for (const GeometryComponent *component : components) { + ReadAttributePtr attribute = component->attribute_try_get_for_read(attribute_name); + if (attribute) { + /* TODO: Use data type with most information. */ + *r_type = bke::cpp_type_to_custom_data_type(attribute->cpp_type()); + /* TODO: Use highest priority domain. */ + *r_domain = attribute->domain(); + return; + } + } + BLI_assert(false); +} + +static void fill_new_attribute(Span<const GeometryComponent *> src_components, + StringRef attribute_name, + const CustomDataType data_type, + const AttributeDomain domain, + fn::GMutableSpan dst_span) +{ + const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + + int offset = 0; + for (const GeometryComponent *component : src_components) { + const int domain_size = component->attribute_domain_size(domain); + ReadAttributePtr read_attribute = component->attribute_get_for_read( + attribute_name, domain, data_type, nullptr); + + fn::GSpan src_span = read_attribute->get_span(); + const void *src_buffer = src_span.data(); + void *dst_buffer = dst_span[offset]; + cpp_type->copy_to_initialized_n(src_buffer, dst_buffer, domain_size); + + offset += domain_size; + } +} + +static void join_attributes(Span<const GeometryComponent *> src_components, + GeometryComponent &result, + Span<StringRef> ignored_attributes = {}) +{ + Set<std::string> attribute_names = find_all_attribute_names(src_components); + for (StringRef name : ignored_attributes) { + attribute_names.remove(name); + } + + for (const std::string &attribute_name : attribute_names) { + CustomDataType data_type; + AttributeDomain domain; + determine_final_data_type_and_domain(src_components, attribute_name, &data_type, &domain); + + result.attribute_try_create(attribute_name, domain, data_type); + WriteAttributePtr write_attribute = result.attribute_try_get_for_write(attribute_name); + if (!write_attribute || + &write_attribute->cpp_type() != bke::custom_data_type_to_cpp_type(data_type) || + write_attribute->domain() != domain) { + continue; + } + fn::GMutableSpan dst_span = write_attribute->get_span(); + fill_new_attribute(src_components, attribute_name, data_type, domain, dst_span); + write_attribute->apply_span(); + } +} + +static void join_components(Span<const MeshComponent *> src_components, GeometrySet &result) +{ + Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(src_components); + + MeshComponent &dst_component = result.get_component_for_write<MeshComponent>(); + dst_component.replace(new_mesh); + + /* The position attribute is handled above already. */ + join_attributes(to_base_components(src_components), dst_component, {"position"}); +} + +static void join_components(Span<const PointCloudComponent *> src_components, GeometrySet &result) +{ + int totpoints = 0; + for (const PointCloudComponent *pointcloud_component : src_components) { + totpoints += pointcloud_component->attribute_domain_size(ATTR_DOMAIN_POINT); + } + + PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); + PointCloud *pointcloud = BKE_pointcloud_new_nomain(totpoints); + dst_component.replace(pointcloud); + + join_attributes(to_base_components(src_components), dst_component); +} + +static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result) +{ + InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); + for (const InstancesComponent *component : src_components) { + const int size = component->instances_amount(); + Span<const Object *> objects = component->objects(); + Span<float3> positions = component->positions(); + Span<float3> rotations = component->rotations(); + Span<float3> scales = component->scales(); + for (const int i : IndexRange(size)) { + dst_component.add_instance(objects[i], positions[i], rotations[i], scales[i]); + } + } +} + +template<typename Component> +static void join_component_type(Span<const GeometrySet *> src_geometry_sets, GeometrySet &result) +{ + Vector<const Component *> components; + for (const GeometrySet *geometry_set : src_geometry_sets) { + const Component *component = geometry_set->get_component_for_read<Component>(); + if (component != nullptr && !component->is_empty()) { + components.append(component); + } + } + + if (components.size() == 0) { + return; + } + if (components.size() == 1) { + result.add(*components[0]); + return; + } + join_components(components, result); +} + +static void geo_node_join_geometry_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_a = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_b = params.extract_input<GeometrySet>("Geometry_001"); + GeometrySet geometry_set_result; + + std::array<const GeometrySet *, 2> src_geometry_sets = {&geometry_set_a, &geometry_set_b}; + + join_component_type<MeshComponent>(src_geometry_sets, geometry_set_result); + join_component_type<PointCloudComponent>(src_geometry_sets, geometry_set_result); + join_component_type<InstancesComponent>(src_geometry_sets, geometry_set_result); + + params.set_output("Geometry", std::move(geometry_set_result)); +} +} // namespace blender::nodes + +void register_node_type_geo_join_geometry() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", 0, 0); + node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out); + ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc new file mode 100644 index 00000000000..8d80e1ce40a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -0,0 +1,94 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" + +#include "BLI_math_matrix.h" + +static bNodeSocketTemplate geo_node_object_info_in[] = { + {SOCK_OBJECT, N_("Object")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_object_info_out[] = { + {SOCK_VECTOR, N_("Location")}, + {SOCK_VECTOR, N_("Rotation")}, + {SOCK_VECTOR, N_("Scale")}, + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_object_info_exec(GeoNodeExecParams params) +{ + bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( + "Object"); + Object *object = params.handle_map().lookup(object_handle); + + float3 location = {0, 0, 0}; + float3 rotation = {0, 0, 0}; + float3 scale = {0, 0, 0}; + GeometrySet geometry_set; + + const Object *self_object = params.self_object(); + + if (object != nullptr) { + float quaternion[4]; + mat4_decompose(location, quaternion, scale, object->obmat); + quat_to_eul(rotation, quaternion); + + if (object != self_object) { + if (object->type == OB_MESH) { + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object, false); + if (mesh != nullptr) { + BKE_mesh_wrapper_ensure_mdata(mesh); + + /* Make a copy because the life time of the other mesh might be shorter. */ + Mesh *copied_mesh = BKE_mesh_copy_for_eval(mesh, false); + + /* Transform into the local space of the object that is being modified. */ + float transform[4][4]; + mul_m4_m4m4(transform, self_object->imat, object->obmat); + BKE_mesh_transform(copied_mesh, transform, true); + + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + mesh_component.replace(copied_mesh); + mesh_component.copy_vertex_group_names_from_object(*object); + } + } + } + } + + params.set_output("Location", location); + params.set_output("Rotation", rotation); + params.set_output("Scale", scale); + params.set_output("Geometry", geometry_set); +} +} // namespace blender::nodes + +void register_node_type_geo_object_info() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_OBJECT_INFO, "Object Info", 0, 0); + node_type_socket_templates(&ntype, geo_node_object_info_in, geo_node_object_info_out); + ntype.geometry_node_execute = blender::nodes::geo_node_object_info_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc new file mode 100644 index 00000000000..7f94ca35e6e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -0,0 +1,139 @@ +/* + * 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 "BLI_float3.hh" +#include "BLI_hash.h" +#include "BLI_math_vector.h" +#include "BLI_rand.hh" +#include "BLI_span.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_deform.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_pointcloud.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_point_distribute_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_FLOAT, N_("Density"), 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_STRING, N_("Density Attribute")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_point_distribute_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, + const float density, + const FloatReadAttribute &density_factors) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(mesh); + + Vector<float3> points; + + for (const int looptri_index : IndexRange(looptris_len)) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_index = mesh->mloop[looptri.tri[0]].v; + const int v1_index = mesh->mloop[looptri.tri[1]].v; + const int v2_index = mesh->mloop[looptri.tri[2]].v; + const float3 v0_pos = mesh->mvert[v0_index].co; + const float3 v1_pos = mesh->mvert[v1_index].co; + const float3 v2_pos = mesh->mvert[v2_index].co; + const float v0_density_factor = std::max(0.0f, density_factors[v0_index]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_index]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_index]); + const float looptri_density_factor = (v0_density_factor + v1_density_factor + + v2_density_factor) / + 3.0f; + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = BLI_hash_int(looptri_index); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coords = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coords); + points.append(point_pos); + } + } + + return points; +} + +static void geo_node_point_distribute_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_out; + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const float density = params.extract_input<float>("Density"); + const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); + + if (density <= 0.0f) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); + const Mesh *mesh_in = mesh_component.get_for_read(); + + const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( + density_attribute, ATTR_DOMAIN_POINT, 1.0f); + + Vector<float3> points = scatter_points_from_mesh(mesh_in, density, density_factors); + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(points.size()); + memcpy(pointcloud->co, points.data(), sizeof(float3) * points.size()); + for (const int i : points.index_range()) { + *(float3 *)(pointcloud->co + i) = points[i]; + pointcloud->radius[i] = 0.05f; + } + + geometry_set_out.replace_pointcloud(pointcloud); + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_point_distribute() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", 0, 0); + node_type_socket_templates(&ntype, geo_node_point_distribute_in, geo_node_point_distribute_out); + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc new file mode 100644 index 00000000000..bb8f1ff4909 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -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. + */ + +#include "BKE_mesh.h" +#include "BKE_persistent_data_handle.hh" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_point_instance_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_OBJECT, N_("Object")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_point_instance_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void add_instances_from_geometry_component(InstancesComponent &instances, + const GeometryComponent &src_geometry, + Object *object) +{ + Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>( + "rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); + Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>( + "scale", ATTR_DOMAIN_POINT, {1, 1, 1}); + + for (const int i : IndexRange(positions.size())) { + instances.add_instance(object, positions[i], rotations[i], scales[i]); + } +} + +static void geo_node_point_instance_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_out; + + bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( + "Object"); + Object *object = params.handle_map().lookup(object_handle); + + if (object != nullptr && object != params.self_object()) { + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + if (geometry_set.has<MeshComponent>()) { + add_instances_from_geometry_component( + instances, *geometry_set.get_component_for_read<MeshComponent>(), object); + } + if (geometry_set.has<PointCloudComponent>()) { + add_instances_from_geometry_component( + instances, *geometry_set.get_component_for_read<PointCloudComponent>(), object); + } + } + + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_point_instance() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", 0, 0); + node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out); + ntype.geometry_node_execute = blender::nodes::geo_node_point_instance_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc new file mode 100644 index 00000000000..68ea5481028 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc @@ -0,0 +1,160 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BLI_rand.hh" + +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +static bNodeSocketTemplate geo_node_random_attribute_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_VECTOR, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_random_attribute_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_random_attribute_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; +} + +static void geo_node_random_attribute_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); + bNodeSocket *sock_max_vector = sock_min_vector->next; + bNodeSocket *sock_min_float = sock_max_vector->next; + bNodeSocket *sock_max_float = sock_min_float->next; + + const int data_type = node->custom1; + + nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_max_float, data_type == CD_PROP_FLOAT); +} + +namespace blender::nodes { + +static void randomize_attribute(FloatWriteAttribute &attribute, + float min, + float max, + RandomNumberGenerator &rng) +{ + MutableSpan<float> attribute_span = attribute.get_span(); + for (const int i : IndexRange(attribute.size())) { + const float value = rng.get_float() * (max - min) + min; + attribute_span[i] = value; + } + attribute.apply_span(); +} + +static void randomize_attribute(Float3WriteAttribute &attribute, + float3 min, + float3 max, + RandomNumberGenerator &rng) +{ + MutableSpan<float3> attribute_span = attribute.get_span(); + for (const int i : IndexRange(attribute.size())) { + const float x = rng.get_float(); + const float y = rng.get_float(); + const float z = rng.get_float(); + const float3 value = float3(x, y, z) * (max - min) + min; + attribute_span[i] = value; + } + attribute.apply_span(); +} + +static void randomize_attribute(GeometryComponent &component, + const GeoNodeExecParams ¶ms, + RandomNumberGenerator &rng) +{ + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + const std::string attribute_name = params.get_input<std::string>("Attribute"); + if (attribute_name.empty()) { + return; + } + + WriteAttributePtr attribute = component.attribute_try_ensure_for_write( + attribute_name, domain, data_type); + if (!attribute) { + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + FloatWriteAttribute float_attribute = std::move(attribute); + const float min_value = params.get_input<float>("Min_001"); + const float max_value = params.get_input<float>("Max_001"); + randomize_attribute(float_attribute, min_value, max_value, rng); + break; + } + case CD_PROP_FLOAT3: { + Float3WriteAttribute float3_attribute = std::move(attribute); + const float3 min_value = params.get_input<float3>("Min"); + const float3 max_value = params.get_input<float3>("Max"); + randomize_attribute(float3_attribute, min_value, max_value, rng); + break; + } + default: + break; + } +} + +static void geo_node_random_attribute_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const int seed = params.get_input<int>("Seed"); + + if (geometry_set.has<MeshComponent>()) { + RandomNumberGenerator rng; + rng.seed_random(seed); + randomize_attribute(geometry_set.get_component_for_write<MeshComponent>(), params, rng); + } + if (geometry_set.has<PointCloudComponent>()) { + RandomNumberGenerator rng; + rng.seed_random(seed + 3245231); + randomize_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params, rng); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_random_attribute() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RANDOM_ATTRIBUTE, "Random Attribute", 0, 0); + node_type_socket_templates(&ntype, geo_node_random_attribute_in, geo_node_random_attribute_out); + node_type_init(&ntype, geo_node_random_attribute_init); + node_type_update(&ntype, geo_node_random_attribute_update); + ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc new file mode 100644 index 00000000000..dccdf94243e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -0,0 +1,113 @@ +/* + * 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 "MEM_guardedalloc.h" + +#include "BKE_subdiv.h" +#include "BKE_subdiv_mesh.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_subdivision_surface_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Level"), 1, 0, 0, 0, 0, 6}, + {SOCK_BOOLEAN, N_("Use Creases")}, + {SOCK_BOOLEAN, N_("Boundary Smooth")}, + {SOCK_BOOLEAN, N_("Smooth UVs")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_subdivision_surface_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", geometry_set); + return; + } + +#ifndef WITH_OPENSUBDIV + /* Return input geometry if Blender is built without OpenSubdiv. */ + params.set_output("Geometry", std::move(geometry_set)); + return; +#else + const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 30); + + /* Only process subdivion if level is greater than 0. */ + if (subdiv_level == 0) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const bool use_crease = params.extract_input<bool>("Use Creases"); + const bool boundary_smooth = params.extract_input<bool>("Boundary Smooth"); + const bool smooth_uvs = params.extract_input<bool>("Smooth UVs"); + const Mesh *mesh_in = geometry_set.get_mesh_for_read(); + + /* Initialize mesh settings. */ + SubdivToMeshSettings mesh_settings; + mesh_settings.resolution = (1 << subdiv_level) + 1; + mesh_settings.use_optimal_display = false; + + /* Initialize subdivision settings. */ + SubdivSettings subdiv_settings; + subdiv_settings.is_simple = false; + subdiv_settings.is_adaptive = false; + subdiv_settings.use_creases = use_crease; + subdiv_settings.level = subdiv_level; + + subdiv_settings.vtx_boundary_interpolation = BKE_subdiv_vtx_boundary_interpolation_from_subsurf( + boundary_smooth); + subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth( + smooth_uvs); + + /* Apply subdivision to mesh. */ + Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh_in); + + /* In case of bad topology, skip to input mesh. */ + if (subdiv == nullptr) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh_in); + + geometry_set.replace_mesh(mesh_out); + + // BKE_subdiv_stats_print(&subdiv->stats); + BKE_subdiv_free(subdiv); + + params.set_output("Geometry", std::move(geometry_set)); +#endif +} +} // namespace blender::nodes + +void register_node_type_geo_subdivision_surface() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SUBDIVISION_SURFACE, "Subdivision Surface", 0, 0); + node_type_socket_templates( + &ntype, geo_node_subdivision_surface_in, geo_node_subdivision_surface_out); + ntype.geometry_node_execute = blender::nodes::geo_node_subdivision_surface_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc new file mode 100644 index 00000000000..6360a3dd9f7 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -0,0 +1,145 @@ +/* + * 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 "BLI_math_matrix.h" + +#include "DNA_pointcloud_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_transform_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_VECTOR, N_("Translation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_TRANSLATION}, + {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, + {SOCK_VECTOR, N_("Scale"), 1.0f, 1.0f, 1.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_transform_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static bool use_translate(const float3 rotation, const float3 scale) +{ + if (compare_ff(rotation.length_squared(), 0.0f, 1e-9f) != 1) { + return false; + } + if (compare_ff(scale.x, 1.0f, 1e-9f) != 1 || compare_ff(scale.y, 1.0f, 1e-9f) != 1 || + compare_ff(scale.z, 1.0f, 1e-9f) != 1) { + return false; + } + return true; +} + +static void transform_mesh(Mesh *mesh, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + /* Use only translation if rotation and scale are zero. */ + if (use_translate(rotation, scale)) { + BKE_mesh_translate(mesh, translation, true); + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + BKE_mesh_transform(mesh, mat, true); + BKE_mesh_calc_normals(mesh); + } +} + +static void transform_pointcloud(PointCloud *pointcloud, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + /* Use only translation if rotation and scale don't apply. */ + if (use_translate(rotation, scale)) { + for (int i = 0; i < pointcloud->totpoint; i++) { + add_v3_v3(pointcloud->co[i], translation); + } + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + for (int i = 0; i < pointcloud->totpoint; i++) { + mul_m4_v3(mat, pointcloud->co[i]); + } + } +} + +static void transform_instances(InstancesComponent &instances, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + MutableSpan<float3> positions = instances.positions(); + + /* Use only translation if rotation and scale don't apply. */ + if (use_translate(rotation, scale)) { + for (float3 &position : positions) { + add_v3_v3(position, translation); + } + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + for (float3 &position : positions) { + mul_m4_v3(mat, position); + } + } +} + +static void geo_node_transform_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const float3 translation = params.extract_input<float3>("Translation"); + const float3 rotation = params.extract_input<float3>("Rotation"); + const float3 scale = params.extract_input<float3>("Scale"); + + if (geometry_set.has_mesh()) { + Mesh *mesh = geometry_set.get_mesh_for_write(); + transform_mesh(mesh, translation, rotation, scale); + } + + if (geometry_set.has_pointcloud()) { + PointCloud *pointcloud = geometry_set.get_pointcloud_for_write(); + transform_pointcloud(pointcloud, translation, rotation, scale); + } + + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + transform_instances(instances, translation, rotation, scale); + } + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_transform() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_TRANSFORM, "Transform", 0, 0); + node_type_socket_templates(&ntype, geo_node_transform_in, geo_node_transform_out); + ntype.geometry_node_execute = blender::nodes::geo_node_transform_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc new file mode 100644 index 00000000000..cec717e4a61 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -0,0 +1,79 @@ +/* + * 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 "DNA_node_types.h" + +#include "RNA_enum_types.h" + +#include "node_geometry_util.hh" + +extern "C" { +Mesh *triangulate_mesh(Mesh *mesh, + const int quad_method, + const int ngon_method, + const int min_vertices, + const int flag); +} + +static bNodeSocketTemplate geo_node_triangulate_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Minimum Vertices"), 4, 0, 0, 0, 4, 10000}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_triangulate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_triangulate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + node->custom1 = GEO_NODE_TRIANGULATE_QUAD_SHORTEDGE; + node->custom2 = GEO_NODE_TRIANGULATE_NGON_BEAUTY; +} + +namespace blender::nodes { +static void geo_node_triangulate_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const int min_vertices = std::max(params.extract_input<int>("Minimum Vertices"), 4); + + GeometryNodeTriangulateQuads quad_method = static_cast<GeometryNodeTriangulateQuads>( + params.node().custom1); + GeometryNodeTriangulateNGons ngon_method = static_cast<GeometryNodeTriangulateNGons>( + params.node().custom2); + + /* #triangulate_mesh might modify the input mesh currently. */ + Mesh *mesh_in = geometry_set.get_mesh_for_write(); + if (mesh_in != nullptr) { + Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); + geometry_set.replace_mesh(mesh_out); + } + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_triangulate() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_TRIANGULATE, "Triangulate", 0, 0); + node_type_socket_templates(&ntype, geo_node_triangulate_in, geo_node_triangulate_out); + node_type_init(&ntype, geo_triangulate_init); + ntype.geometry_node_execute = blender::nodes::geo_node_triangulate_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/intern/derived_node_tree.cc b/source/blender/nodes/intern/derived_node_tree.cc index 4612a479ebc..5a380897e3f 100644 --- a/source/blender/nodes/intern/derived_node_tree.cc +++ b/source/blender/nodes/intern/derived_node_tree.cc @@ -28,9 +28,12 @@ static const NodeTreeRef &get_tree_ref(NodeTreeRefMap &node_tree_refs, bNodeTree [&]() { return std::make_unique<NodeTreeRef>(btree); }); } -DerivedNodeTree::DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs) +DerivedNodeTree::DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs) : btree_(btree) { + BLI_assert(btree != nullptr); + const NodeTreeRef &main_tree_ref = get_tree_ref(node_tree_refs, btree); + used_node_tree_refs_.add_new(&main_tree_ref); Vector<DNode *> all_nodes; Vector<DGroupInput *> all_group_inputs; @@ -137,6 +140,7 @@ BLI_NOINLINE void DerivedNodeTree::expand_group_node(DNode &group_node, } const NodeTreeRef &group_ref = get_tree_ref(node_tree_refs, btree); + used_node_tree_refs_.add(&group_ref); DParentNode &parent = *allocator_.construct<DParentNode>(); parent.id_ = all_parent_nodes.append_and_get_index(&parent); @@ -358,6 +362,16 @@ DerivedNodeTree::~DerivedNodeTree() } } +bool DerivedNodeTree::has_link_cycles() const +{ + for (const NodeTreeRef *tree : used_node_tree_refs_) { + if (tree->has_link_cycles()) { + return true; + } + } + return false; +} + static dot::Cluster *get_cluster_for_parent(dot::DirectedGraph &graph, Map<const DParentNode *, dot::Cluster *> &clusters, const DParentNode *parent) diff --git a/source/blender/nodes/intern/math_functions.cc b/source/blender/nodes/intern/math_functions.cc new file mode 100644 index 00000000000..cc5e9547a96 --- /dev/null +++ b/source/blender/nodes/intern/math_functions.cc @@ -0,0 +1,117 @@ +/* + * 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 "NOD_math_functions.hh" + +namespace blender::nodes { + +const FloatMathOperationInfo *get_float_math_operation_info(const int operation) +{ + +#define RETURN_OPERATION_INFO(title_case_name, shader_name) \ + { \ + static const FloatMathOperationInfo info{title_case_name, shader_name}; \ + return &info; \ + } \ + ((void)0) + + switch (operation) { + case NODE_MATH_ADD: + RETURN_OPERATION_INFO("Add", "math_add"); + case NODE_MATH_SUBTRACT: + RETURN_OPERATION_INFO("Subtract", "math_subtract"); + case NODE_MATH_MULTIPLY: + RETURN_OPERATION_INFO("Multiply", "math_multiply"); + case NODE_MATH_DIVIDE: + RETURN_OPERATION_INFO("Divide", "math_divide"); + case NODE_MATH_SINE: + RETURN_OPERATION_INFO("Sine", "math_sine"); + case NODE_MATH_COSINE: + RETURN_OPERATION_INFO("Cosine", "math_cosine"); + case NODE_MATH_ARCSINE: + RETURN_OPERATION_INFO("Arc Sine", "math_arcsine"); + case NODE_MATH_ARCCOSINE: + RETURN_OPERATION_INFO("Arc Cosine", "math_arccosine"); + case NODE_MATH_ARCTANGENT: + RETURN_OPERATION_INFO("Arc Tangent", "math_arctangent"); + case NODE_MATH_POWER: + RETURN_OPERATION_INFO("Power", "math_power"); + case NODE_MATH_LOGARITHM: + RETURN_OPERATION_INFO("Logarithm", "math_logarithm"); + case NODE_MATH_MINIMUM: + RETURN_OPERATION_INFO("Minimum", "math_minimum"); + case NODE_MATH_MAXIMUM: + RETURN_OPERATION_INFO("Maximum", "math_maximum"); + case NODE_MATH_ROUND: + RETURN_OPERATION_INFO("Round", "math_round"); + case NODE_MATH_LESS_THAN: + RETURN_OPERATION_INFO("Less Than", "math_less_than"); + case NODE_MATH_GREATER_THAN: + RETURN_OPERATION_INFO("Greater Than", "math_greater_than"); + case NODE_MATH_MODULO: + RETURN_OPERATION_INFO("Modulo", "math_modulo"); + case NODE_MATH_ABSOLUTE: + RETURN_OPERATION_INFO("Absolute", "math_absolute"); + case NODE_MATH_ARCTAN2: + RETURN_OPERATION_INFO("Arc Tangent 2", "math_arctan2"); + case NODE_MATH_FLOOR: + RETURN_OPERATION_INFO("Floor", "math_floor"); + case NODE_MATH_CEIL: + RETURN_OPERATION_INFO("Ceil", "math_ceil"); + case NODE_MATH_FRACTION: + RETURN_OPERATION_INFO("Fraction", "math_fraction"); + case NODE_MATH_SQRT: + RETURN_OPERATION_INFO("Sqrt", "math_sqrt"); + case NODE_MATH_INV_SQRT: + RETURN_OPERATION_INFO("Inverse Sqrt", "math_inversesqrt"); + case NODE_MATH_SIGN: + RETURN_OPERATION_INFO("Sign", "math_sign"); + case NODE_MATH_EXPONENT: + RETURN_OPERATION_INFO("Exponent", "math_exponent"); + case NODE_MATH_RADIANS: + RETURN_OPERATION_INFO("Radians", "math_radians"); + case NODE_MATH_DEGREES: + RETURN_OPERATION_INFO("Degrees", "math_degrees"); + case NODE_MATH_SINH: + RETURN_OPERATION_INFO("Hyperbolic Sine", "math_sinh"); + case NODE_MATH_COSH: + RETURN_OPERATION_INFO("Hyperbolic Cosine", "math_cosh"); + case NODE_MATH_TANH: + RETURN_OPERATION_INFO("Hyperbolic Tangent", "math_tanh"); + case NODE_MATH_TRUNC: + RETURN_OPERATION_INFO("Truncate", "math_trunc"); + case NODE_MATH_SNAP: + RETURN_OPERATION_INFO("Snap", "math_snap"); + case NODE_MATH_WRAP: + RETURN_OPERATION_INFO("Wrap", "math_wrap"); + case NODE_MATH_COMPARE: + RETURN_OPERATION_INFO("Compare", "math_compare"); + case NODE_MATH_MULTIPLY_ADD: + RETURN_OPERATION_INFO("Multiply Add", "math_multiply_add"); + case NODE_MATH_PINGPONG: + RETURN_OPERATION_INFO("Ping Pong", "math_pingpong"); + case NODE_MATH_SMOOTH_MIN: + RETURN_OPERATION_INFO("Smooth Min", "math_smoothmin"); + case NODE_MATH_SMOOTH_MAX: + RETURN_OPERATION_INFO("Smooth Max", "math_smoothmax"); + } + +#undef RETURN_OPERATION_INFO + + return nullptr; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc new file mode 100644 index 00000000000..50292cb8cfb --- /dev/null +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -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. + */ + +#include "NOD_geometry_exec.hh" +#include "NOD_type_callbacks.hh" + +namespace blender::nodes { + +void GeoNodeExecParams::check_extract_input(StringRef identifier, + const CPPType *requested_type) const +{ + bNodeSocket *found_socket = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.inputs) { + if (identifier == socket->identifier) { + found_socket = socket; + break; + } + } + if (found_socket == nullptr) { + std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n"; + std::cout << "Possible identifiers are: "; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.inputs) { + if ((socket->flag & SOCK_UNAVAIL) == 0) { + std::cout << "'" << socket->identifier << "', "; + } + } + std::cout << "\n"; + BLI_assert(false); + } + else if (found_socket->flag & SOCK_UNAVAIL) { + std::cout << "The socket corresponding to the identifier '" << identifier + << "' is disabled.\n"; + BLI_assert(false); + } + else if (!input_values_.contains(identifier)) { + std::cout << "The identifier '" << identifier + << "' is valid, but there is no value for it anymore.\n"; + std::cout << "Most likely it has been extracted before.\n"; + BLI_assert(false); + } + else if (requested_type != nullptr) { + const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); + if (*requested_type != expected_type) { + std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '" + << expected_type.name() << "'.\n"; + BLI_assert(false); + } + } +} + +void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const +{ + bNodeSocket *found_socket = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.outputs) { + if (identifier == socket->identifier) { + found_socket = socket; + break; + } + } + if (found_socket == nullptr) { + std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n"; + std::cout << "Possible identifiers are: "; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.outputs) { + if ((socket->flag & SOCK_UNAVAIL) == 0) { + std::cout << "'" << socket->identifier << "', "; + } + } + std::cout << "\n"; + BLI_assert(false); + } + else if (found_socket->flag & SOCK_UNAVAIL) { + std::cout << "The socket corresponding to the identifier '" << identifier + << "' is disabled.\n"; + BLI_assert(false); + } + else if (output_values_.contains(identifier)) { + std::cout << "The identifier '" << identifier << "' has been set already.\n"; + BLI_assert(false); + } + else { + const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); + if (value_type != expected_type) { + std::cout << "The value type '" << value_type.name() << "' is incorrect. Expected '" + << expected_type.name() << "'.\n"; + BLI_assert(false); + } + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index b3803b20ac1..d4b1df2f3f0 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -32,6 +32,7 @@ #include "BLI_string.h" #include "BLI_utildefines.h" +#include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_node.h" #include "BKE_persistent_data_handle.hh" @@ -552,10 +553,9 @@ static bNodeSocketType *make_socket_type_virtual() static bNodeSocketType *make_socket_type_bool() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_BOOLEAN, PROP_NONE); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<bool>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - bool value = builder.socket_default_value<bNodeSocketValueBoolean>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<bool>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value; }; return socktype; } @@ -563,10 +563,9 @@ static bNodeSocketType *make_socket_type_bool() static bNodeSocketType *make_socket_type_float(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_FLOAT, subtype); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<float>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - float value = builder.socket_default_value<bNodeSocketValueFloat>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<float>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value; }; return socktype; } @@ -574,10 +573,9 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype) static bNodeSocketType *make_socket_type_int(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_INT, subtype); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<int>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - int value = builder.socket_default_value<bNodeSocketValueInt>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<int>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value; }; return socktype; } @@ -585,12 +583,9 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype) static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_VECTOR, subtype); - socktype->get_mf_data_type = []() { - return blender::fn::MFDataType::ForSingle<blender::float3>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - blender::float3 value = builder.socket_default_value<bNodeSocketValueVector>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::float3>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value; }; return socktype; } @@ -598,12 +593,9 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) static bNodeSocketType *make_socket_type_rgba() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_RGBA, PROP_NONE); - socktype->get_mf_data_type = []() { - return blender::fn::MFDataType::ForSingle<blender::Color4f>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - blender::Color4f value = builder.socket_default_value<bNodeSocketValueRGBA>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::Color4f>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(blender::Color4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; return socktype; } @@ -611,10 +603,9 @@ static bNodeSocketType *make_socket_type_rgba() static bNodeSocketType *make_socket_type_string() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_STRING, PROP_NONE); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<std::string>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - std::string value = builder.socket_default_value<bNodeSocketValueString>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<std::string>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value); }; return socktype; } @@ -661,9 +652,9 @@ MAKE_CPP_TYPE(PersistentObjectHandle, blender::bke::PersistentObjectHandle); static bNodeSocketType *make_socket_type_object() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE); - socktype->get_mf_data_type = []() { + socktype->get_cpp_type = []() { /* Objects are not passed along as raw pointers, but as handles. */ - return blender::fn::MFDataType::ForSingle<blender::bke::PersistentObjectHandle>(); + return &blender::fn::CPPType::get<blender::bke::PersistentObjectHandle>(); }; socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { Object *object = builder.socket_default_value<bNodeSocketValueObject>()->value; @@ -675,6 +666,10 @@ static bNodeSocketType *make_socket_type_object() static bNodeSocketType *make_socket_type_geometry() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_GEOMETRY, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<GeometrySet>(); }; + socktype->get_cpp_value = [](const bNodeSocket &UNUSED(socket), void *r_value) { + new (r_value) GeometrySet(); + }; return socktype; } diff --git a/source/blender/nodes/intern/node_tree_multi_function.cc b/source/blender/nodes/intern/node_tree_multi_function.cc index 09a80fd23f4..8440e996651 100644 --- a/source/blender/nodes/intern/node_tree_multi_function.cc +++ b/source/blender/nodes/intern/node_tree_multi_function.cc @@ -16,21 +16,13 @@ #include "NOD_node_tree_multi_function.hh" +#include "FN_multi_function_network_evaluation.hh" + #include "BLI_color.hh" #include "BLI_float3.hh" namespace blender::nodes { -/* Maybe this should be moved to BKE_node.h. */ -static std::optional<fn::MFDataType> try_get_multi_function_data_type_of_socket( - const bNodeSocket *bsocket) -{ - if (bsocket->typeinfo->get_mf_data_type == nullptr) { - return {}; - } - return bsocket->typeinfo->get_mf_data_type(); -} - const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) { Vector<fn::MFDataType, 10> input_types; @@ -38,8 +30,7 @@ const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) for (const DInputSocket *dsocket : dnode_.inputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { input_types.append(*data_type); } @@ -47,8 +38,7 @@ const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) } for (const DOutputSocket *dsocket : dnode_.outputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { output_types.append(*data_type); } @@ -70,8 +60,7 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d for (const DInputSocket *dsocket : dnode.inputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { input_types.append(*data_type); input_names.append(dsocket->name()); @@ -86,8 +75,7 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d for (const DOutputSocket *dsocket : dnode.outputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { output_types.append(*data_type); output_names.append(dsocket->name()); @@ -106,12 +94,12 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d static bool has_data_sockets(const DNode &dnode) { for (const DInputSocket *socket : dnode.inputs()) { - if (is_multi_function_data_socket(socket->bsocket())) { + if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) { return true; } } for (const DOutputSocket *socket : dnode.outputs()) { - if (is_multi_function_data_socket(socket->bsocket())) { + if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) { return true; } } @@ -140,7 +128,7 @@ static void insert_group_inputs(CommonMFNetworkBuilderData &common) { for (const DGroupInput *group_input : common.tree.group_inputs()) { bNodeSocket *bsocket = group_input->bsocket(); - if (is_multi_function_data_socket(bsocket)) { + if (socket_is_mf_data_socket(*bsocket->typeinfo)) { bNodeSocketType *socktype = bsocket->typeinfo; BLI_assert(socktype->expand_in_mf_network != nullptr); @@ -171,44 +159,43 @@ static fn::MFOutputSocket *try_find_origin(CommonMFNetworkBuilderData &common, if (!from_dsocket.is_available()) { return nullptr; } - if (is_multi_function_data_socket(from_dsocket.bsocket())) { + if (socket_is_mf_data_socket(*from_dsocket.bsocket()->typeinfo)) { return &common.network_map.lookup(from_dsocket); } return nullptr; } const DGroupInput &from_group_input = *from_group_inputs[0]; - if (is_multi_function_data_socket(from_group_input.bsocket())) { + if (socket_is_mf_data_socket(*from_group_input.bsocket()->typeinfo)) { return &common.network_map.lookup(from_group_input); } return nullptr; } -using ImplicitConversionsMap = - Map<std::pair<fn::MFDataType, fn::MFDataType>, const fn::MultiFunction *>; - template<typename From, typename To> -static void add_implicit_conversion(ImplicitConversionsMap &map) +static void add_implicit_conversion(DataTypeConversions &conversions) { static fn::CustomMF_Convert<From, To> function; - map.add({fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>()}, &function); + conversions.add(fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>(), function); } template<typename From, typename To, typename ConversionF> -static void add_implicit_conversion(ImplicitConversionsMap &map, +static void add_implicit_conversion(DataTypeConversions &conversions, StringRef name, ConversionF conversion) { static fn::CustomMF_SI_SO<From, To> function{name, conversion}; - map.add({fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>()}, &function); + conversions.add(fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>(), function); } -static ImplicitConversionsMap get_implicit_conversions() +static DataTypeConversions create_implicit_conversions() { - ImplicitConversionsMap conversions; + DataTypeConversions conversions; add_implicit_conversion<float, int32_t>(conversions); add_implicit_conversion<float, float3>(conversions); add_implicit_conversion<int32_t, float>(conversions); + add_implicit_conversion<float, bool>(conversions); + add_implicit_conversion<bool, float>(conversions); add_implicit_conversion<float3, float>( conversions, "Vector Length", [](float3 a) { return a.length(); }); add_implicit_conversion<int32_t, float3>( @@ -220,11 +207,26 @@ static ImplicitConversionsMap get_implicit_conversions() return conversions; } -static const fn::MultiFunction *try_get_conversion_function(fn::MFDataType from, fn::MFDataType to) +const DataTypeConversions &get_implicit_type_conversions() { - static const ImplicitConversionsMap conversions = get_implicit_conversions(); - const fn::MultiFunction *function = conversions.lookup_default({from, to}, nullptr); - return function; + static const DataTypeConversions conversions = create_implicit_conversions(); + return conversions; +} + +void DataTypeConversions::convert(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const +{ + const fn::MultiFunction *fn = this->get_conversion(MFDataType::ForSingle(from_type), + MFDataType::ForSingle(to_type)); + BLI_assert(fn != nullptr); + + fn::MFContextBuilder context; + fn::MFParamsBuilder params{*fn, 1}; + params.add_readonly_single_input(fn::GSpan(from_type, from_value, 1)); + params.add_uninitialized_single_output(fn::GMutableSpan(to_type, to_value, 1)); + fn->call({0}, params, context); } static fn::MFOutputSocket &insert_default_value_for_type(CommonMFNetworkBuilderData &common, @@ -253,7 +255,7 @@ static void insert_links(CommonMFNetworkBuilderData &common) if (!to_dsocket->is_linked()) { continue; } - if (!is_multi_function_data_socket(to_dsocket->bsocket())) { + if (!socket_is_mf_data_socket(*to_dsocket->bsocket()->typeinfo)) { continue; } @@ -269,7 +271,8 @@ static void insert_links(CommonMFNetworkBuilderData &common) fn::MFDataType from_type = from_socket->data_type(); if (from_type != to_type) { - const fn::MultiFunction *conversion_fn = try_get_conversion_function(from_type, to_type); + const fn::MultiFunction *conversion_fn = get_implicit_type_conversions().get_conversion( + from_type, to_type); if (conversion_fn != nullptr) { fn::MFNode &node = common.network.add_function(*conversion_fn); common.network.add_link(*from_socket, node.input(0)); @@ -308,7 +311,7 @@ static void insert_unlinked_inputs(CommonMFNetworkBuilderData &common) Vector<const DInputSocket *> unlinked_data_inputs; for (const DInputSocket *dsocket : common.tree.input_sockets()) { if (dsocket->is_available()) { - if (is_multi_function_data_socket(dsocket->bsocket())) { + if (socket_is_mf_data_socket(*dsocket->bsocket()->typeinfo)) { if (!dsocket->is_linked()) { insert_unlinked_input(common, *dsocket); } @@ -340,4 +343,150 @@ MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, return network_map; } +/** + * A single node is allowed to expand into multiple nodes before evaluation. Depending on what + * nodes it expands to, it belongs a different type of the ones below. + */ +enum class NodeExpandType { + SingleFunctionNode, + MultipleFunctionNodes, + HasDummyNodes, +}; + +/** + * Checks how the given node expanded in the multi-function network. If it is only a single + * function node, the corresponding function is returned as well. + */ +static NodeExpandType get_node_expand_type(MFNetworkTreeMap &network_map, + const DNode &dnode, + const fn::MultiFunction **r_single_function) +{ + const fn::MFFunctionNode *single_function_node = nullptr; + bool has_multiple_nodes = false; + bool has_dummy_nodes = false; + + auto check_mf_node = [&](fn::MFNode &mf_node) { + if (mf_node.is_function()) { + if (single_function_node == nullptr) { + single_function_node = &mf_node.as_function(); + } + if (&mf_node != single_function_node) { + has_multiple_nodes = true; + } + } + else { + BLI_assert(mf_node.is_dummy()); + has_dummy_nodes = true; + } + }; + + for (const DInputSocket *dsocket : dnode.inputs()) { + if (dsocket->is_available()) { + for (fn::MFInputSocket *mf_input : network_map.lookup(*dsocket)) { + check_mf_node(mf_input->node()); + } + } + } + for (const DOutputSocket *dsocket : dnode.outputs()) { + if (dsocket->is_available()) { + fn::MFOutputSocket &mf_output = network_map.lookup(*dsocket); + check_mf_node(mf_output.node()); + } + } + + if (has_dummy_nodes) { + return NodeExpandType::HasDummyNodes; + } + if (has_multiple_nodes) { + return NodeExpandType::MultipleFunctionNodes; + } + *r_single_function = &single_function_node->function(); + return NodeExpandType::SingleFunctionNode; +} + +static const fn::MultiFunction &create_function_for_node_that_expands_into_multiple( + const DNode &dnode, + fn::MFNetwork &network, + MFNetworkTreeMap &network_map, + ResourceCollector &resources) +{ + Vector<const fn::MFOutputSocket *> dummy_fn_inputs; + for (const DInputSocket *dsocket : dnode.inputs()) { + if (dsocket->is_available()) { + MFDataType data_type = *socket_mf_type_get(*dsocket->typeinfo()); + fn::MFOutputSocket &fn_input = network.add_input(data_type.to_string(), data_type); + for (fn::MFInputSocket *mf_input : network_map.lookup(*dsocket)) { + network.add_link(fn_input, *mf_input); + dummy_fn_inputs.append(&fn_input); + } + } + } + Vector<const fn::MFInputSocket *> dummy_fn_outputs; + for (const DOutputSocket *dsocket : dnode.outputs()) { + if (dsocket->is_available()) { + fn::MFOutputSocket &mf_output = network_map.lookup(*dsocket); + MFDataType data_type = mf_output.data_type(); + fn::MFInputSocket &fn_output = network.add_output(data_type.to_string(), data_type); + network.add_link(mf_output, fn_output); + dummy_fn_outputs.append(&fn_output); + } + } + + fn::MFNetworkEvaluator &fn_evaluator = resources.construct<fn::MFNetworkEvaluator>( + __func__, std::move(dummy_fn_inputs), std::move(dummy_fn_outputs)); + return fn_evaluator; +} + +/** + * Returns a single multi-function for every node that supports it. This makes it easier to reuse + * the multi-function implementation of nodes in different contexts. + */ +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, + ResourceCollector &resources) +{ + /* Build a network that nodes can insert themselves into. However, the individual nodes are not + * connected. */ + fn::MFNetwork &network = resources.construct<fn::MFNetwork>(__func__); + MFNetworkTreeMap network_map{tree, network}; + MultiFunctionByNode functions_by_node; + + CommonMFNetworkBuilderData common{resources, network, network_map, tree}; + + for (const DNode *dnode : tree.nodes()) { + const bNodeType *node_type = dnode->typeinfo(); + if (node_type->expand_in_mf_network == nullptr) { + /* This node does not have a multi-function implementation. */ + continue; + } + + NodeMFNetworkBuilder builder{common, *dnode}; + node_type->expand_in_mf_network(builder); + + const fn::MultiFunction *single_function = nullptr; + const NodeExpandType expand_type = get_node_expand_type(network_map, *dnode, &single_function); + + switch (expand_type) { + case NodeExpandType::HasDummyNodes: { + /* Dummy nodes cannot be executed, so skip them. */ + break; + } + case NodeExpandType::SingleFunctionNode: { + /* This is the common case. Most nodes just expand to a single function. */ + functions_by_node.add_new(dnode, single_function); + break; + } + case NodeExpandType::MultipleFunctionNodes: { + /* If a node expanded into multiple functions, a new function has to be created that + * combines those. */ + const fn::MultiFunction &fn = create_function_for_node_that_expands_into_multiple( + *dnode, network, network_map, resources); + functions_by_node.add_new(dnode, &fn); + break; + } + } + } + + return functions_by_node; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 96ad1e0280e..9dcd90f9f50 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -137,6 +137,48 @@ void NodeTreeRef::find_targets_skipping_reroutes(OutputSocketRef &socket, } } +static bool has_link_cycles_recursive(const NodeRef &node, + MutableSpan<bool> visited, + MutableSpan<bool> is_in_stack) +{ + const int node_id = node.id(); + if (is_in_stack[node_id]) { + return true; + } + if (visited[node_id]) { + return false; + } + + visited[node_id] = true; + is_in_stack[node_id] = true; + + for (const OutputSocketRef *from_socket : node.outputs()) { + for (const InputSocketRef *to_socket : from_socket->directly_linked_sockets()) { + const NodeRef &to_node = to_socket->node(); + if (has_link_cycles_recursive(to_node, visited, is_in_stack)) { + return true; + } + } + } + + is_in_stack[node_id] = false; + return false; +} + +bool NodeTreeRef::has_link_cycles() const +{ + const int node_amount = nodes_by_id_.size(); + Array<bool> visited(node_amount, false); + Array<bool> is_in_stack(node_amount, false); + + for (const NodeRef *node : nodes_by_id_) { + if (has_link_cycles_recursive(*node, visited, is_in_stack)) { + return true; + } + } + return false; +} + std::string NodeTreeRef::to_dot() const { dot::DirectedGraph digraph; diff --git a/source/blender/nodes/intern/type_callbacks.cc b/source/blender/nodes/intern/type_callbacks.cc new file mode 100644 index 00000000000..1f207f880bc --- /dev/null +++ b/source/blender/nodes/intern/type_callbacks.cc @@ -0,0 +1,76 @@ +/* + * 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 "NOD_node_tree_multi_function.hh" +#include "NOD_type_callbacks.hh" + +namespace blender::nodes { + +const CPPType *socket_cpp_type_get(const bNodeSocketType &stype) +{ + if (stype.get_cpp_type != nullptr) { + return stype.get_cpp_type(); + } + return nullptr; +} + +std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype) +{ + const CPPType *cpp_type = socket_cpp_type_get(stype); + if (cpp_type != nullptr) { + return MFDataType::ForSingle(*cpp_type); + } + return {}; +} + +bool socket_is_mf_data_socket(const bNodeSocketType &stype) +{ + if (!socket_mf_type_get(stype).has_value()) { + return false; + } + if (stype.expand_in_mf_network == nullptr && stype.get_cpp_value == nullptr) { + return false; + } + return true; +} + +bool socket_cpp_value_get(const bNodeSocket &socket, void *r_value) +{ + if (socket.typeinfo->get_cpp_value != nullptr) { + socket.typeinfo->get_cpp_value(socket, r_value); + return true; + } + return false; +} + +void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder) +{ + bNodeSocket &socket = builder.bsocket(); + if (socket.typeinfo->expand_in_mf_network != nullptr) { + socket.typeinfo->expand_in_mf_network(builder); + } + else if (socket.typeinfo->get_cpp_value != nullptr) { + const CPPType &type = *socket_cpp_type_get(*socket.typeinfo); + void *buffer = builder.resources().linear_allocator().allocate(type.size(), type.alignment()); + socket.typeinfo->get_cpp_value(socket, buffer); + builder.set_constant_value(type, buffer); + } + else { + BLI_assert(false); + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_util.c b/source/blender/nodes/shader/node_shader_util.c index 4464a61c48c..25d6aef69e5 100644 --- a/source/blender/nodes/shader/node_shader_util.c +++ b/source/blender/nodes/shader/node_shader_util.c @@ -34,7 +34,7 @@ bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) static bool sh_fn_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { - return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "GeometryNodeTree"); } void sh_node_type_base( diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index e7bbadfbcb0..f54914ceba9 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -23,6 +23,8 @@ #include "node_shader_util.h" +#include "NOD_math_functions.hh" + /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate sh_node_math_in[] = { {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, @@ -34,93 +36,15 @@ static bNodeSocketTemplate sh_node_math_out[] = {{SOCK_FLOAT, N_("Value")}, {-1, static const char *gpu_shader_get_name(int mode) { - switch (mode) { - case NODE_MATH_ADD: - return "math_add"; - case NODE_MATH_SUBTRACT: - return "math_subtract"; - case NODE_MATH_MULTIPLY: - return "math_multiply"; - case NODE_MATH_DIVIDE: - return "math_divide"; - case NODE_MATH_MULTIPLY_ADD: - return "math_multiply_add"; - - case NODE_MATH_POWER: - return "math_power"; - case NODE_MATH_LOGARITHM: - return "math_logarithm"; - case NODE_MATH_EXPONENT: - return "math_exponent"; - case NODE_MATH_SQRT: - return "math_sqrt"; - case NODE_MATH_INV_SQRT: - return "math_inversesqrt"; - case NODE_MATH_ABSOLUTE: - return "math_absolute"; - case NODE_MATH_RADIANS: - return "math_radians"; - case NODE_MATH_DEGREES: - return "math_degrees"; - - case NODE_MATH_MINIMUM: - return "math_minimum"; - case NODE_MATH_MAXIMUM: - return "math_maximum"; - case NODE_MATH_LESS_THAN: - return "math_less_than"; - case NODE_MATH_GREATER_THAN: - return "math_greater_than"; - case NODE_MATH_SIGN: - return "math_sign"; - case NODE_MATH_COMPARE: - return "math_compare"; - case NODE_MATH_SMOOTH_MIN: - return "math_smoothmin"; - case NODE_MATH_SMOOTH_MAX: - return "math_smoothmax"; - - case NODE_MATH_ROUND: - return "math_round"; - case NODE_MATH_FLOOR: - return "math_floor"; - case NODE_MATH_CEIL: - return "math_ceil"; - case NODE_MATH_FRACTION: - return "math_fraction"; - case NODE_MATH_MODULO: - return "math_modulo"; - case NODE_MATH_TRUNC: - return "math_trunc"; - case NODE_MATH_SNAP: - return "math_snap"; - case NODE_MATH_WRAP: - return "math_wrap"; - case NODE_MATH_PINGPONG: - return "math_pingpong"; - - case NODE_MATH_SINE: - return "math_sine"; - case NODE_MATH_COSINE: - return "math_cosine"; - case NODE_MATH_TANGENT: - return "math_tangent"; - case NODE_MATH_SINH: - return "math_sinh"; - case NODE_MATH_COSH: - return "math_cosh"; - case NODE_MATH_TANH: - return "math_tanh"; - case NODE_MATH_ARCSINE: - return "math_arcsine"; - case NODE_MATH_ARCCOSINE: - return "math_arccosine"; - case NODE_MATH_ARCTANGENT: - return "math_arctangent"; - case NODE_MATH_ARCTAN2: - return "math_arctan2"; + const blender::nodes::FloatMathOperationInfo *info = + blender::nodes::get_float_math_operation_info(mode); + if (!info) { + return nullptr; + } + if (info->shader_name.is_empty()) { + return nullptr; } - return nullptr; + return info->shader_name.c_str(); } static int gpu_shader_math(GPUMaterial *mat, @@ -149,201 +73,39 @@ static const blender::fn::MultiFunction &get_base_multi_function( blender::nodes::NodeMFNetworkBuilder &builder) { const int mode = builder.bnode().custom1; - switch (mode) { - case NODE_MATH_ADD: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Add", [](float a, float b) { return a + b; }}; - return fn; - } - case NODE_MATH_SUBTRACT: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Subtract", [](float a, float b) { return a - b; }}; - return fn; - } - case NODE_MATH_MULTIPLY: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Multiply", [](float a, float b) { return a * b; }}; - return fn; - } - case NODE_MATH_DIVIDE: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Divide", safe_divide}; - return fn; - } - case NODE_MATH_MULTIPLY_ADD: { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ - "Multiply Add", [](float a, float b, float c) { return a * b + c; }}; - return fn; - } - case NODE_MATH_POWER: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Power", safe_powf}; - return fn; - } - case NODE_MATH_LOGARITHM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Logarithm", safe_logf}; - return fn; - } - case NODE_MATH_EXPONENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Exponent", expf}; - return fn; - } - case NODE_MATH_SQRT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Sqrt", safe_sqrtf}; - return fn; - } - case NODE_MATH_INV_SQRT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Inverse Sqrt", safe_inverse_sqrtf}; - return fn; - }; - case NODE_MATH_ABSOLUTE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Absolute", - [](float a) { return fabs(a); }}; - return fn; - } - case NODE_MATH_RADIANS: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Radians", - [](float a) { return DEG2RAD(a); }}; - return fn; - } - case NODE_MATH_DEGREES: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Degrees", - [](float a) { return RAD2DEG(a); }}; - return fn; - } + const blender::fn::MultiFunction *base_fn = nullptr; - case NODE_MATH_MINIMUM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Minimum", [](float a, float b) { return std::min(a, b); }}; - return fn; - } - case NODE_MATH_MAXIMUM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Maximum", [](float a, float b) { return std::max(a, b); }}; - return fn; - } - case NODE_MATH_LESS_THAN: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Less Than", [](float a, float b) { return (float)(a < b); }}; - return fn; - } - case NODE_MATH_GREATER_THAN: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Greater Than", [](float a, float b) { return (float)(a > b); }}; - return fn; - } - case NODE_MATH_SIGN: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Sign", [](float a) { return compatible_signf(a); }}; - return fn; - } - case NODE_MATH_COMPARE: { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ - "Compare", [](float a, float b, float c) -> float { - return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f; - }}; - return fn; - } - case NODE_MATH_SMOOTH_MIN: { - return builder.get_not_implemented_fn(); - } - case NODE_MATH_SMOOTH_MAX: { - return builder.get_not_implemented_fn(); - } - - case NODE_MATH_ROUND: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Round", [](float a) { return floorf(a + 0.5f); }}; - return fn; - } - case NODE_MATH_FLOOR: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Floor", - [](float a) { return floorf(a); }}; - return fn; - } - case NODE_MATH_CEIL: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Ceil", - [](float a) { return ceilf(a); }}; - return fn; - } - case NODE_MATH_FRACTION: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Fraction", - [](float a) { return a - floorf(a); }}; - return fn; - } - case NODE_MATH_MODULO: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Modulo", [](float a, float b) { return safe_modf(a, b); }}; - return fn; - } - case NODE_MATH_TRUNC: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Trunc", [](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); }}; - return fn; - } - case NODE_MATH_SNAP: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Snap", [](float a, float b) { return floorf(safe_divide(a, b)) * b; }}; - return fn; - } - case NODE_MATH_WRAP: { - return builder.get_not_implemented_fn(); - } - case NODE_MATH_PINGPONG: { - return builder.get_not_implemented_fn(); - } + blender::nodes::try_dispatch_float_math_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SO<float, float> fn{info.title_case_name, function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; + } - case NODE_MATH_SINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Sine", [](float a) { return sinf(a); }}; - return fn; - } - case NODE_MATH_COSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Cosine", - [](float a) { return cosf(a); }}; - return fn; - } - case NODE_MATH_TANGENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Tangent", - [](float a) { return tanf(a); }}; - return fn; - } - case NODE_MATH_SINH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Sine", - [](float a) { return sinhf(a); }}; - return fn; - } - case NODE_MATH_COSH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Cosine", - [](float a) { return coshf(a); }}; - return fn; - } - case NODE_MATH_TANH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Tangent", - [](float a) { return tanhf(a); }}; - return fn; - } - case NODE_MATH_ARCSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Sine", safe_asinf}; - return fn; - } - case NODE_MATH_ARCCOSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Cosine", safe_acosf}; - return fn; - } - case NODE_MATH_ARCTANGENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Tangent", - [](float a) { return atanf(a); }}; - return fn; - } - case NODE_MATH_ARCTAN2: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Arc Tangent 2", [](float a, float b) { return atan2f(a, b); }}; - return fn; - } + blender::nodes::try_dispatch_float_math_fl_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{info.title_case_name, + function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; + } - default: - BLI_assert(false); - return builder.get_not_implemented_fn(); + blender::nodes::try_dispatch_float_math_fl_fl_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ + info.title_case_name, function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; } + + return builder.get_not_implemented_fn(); } static void sh_node_math_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) |