diff options
Diffstat (limited to 'source/blender/nodes')
107 files changed, 8550 insertions, 2144 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 42679f39f18..54925ea3317 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -47,6 +47,7 @@ set(INC set(SRC composite/nodes/node_composite_alphaOver.c + composite/nodes/node_composite_antialiasing.c composite/nodes/node_composite_bilateralblur.c composite/nodes/node_composite_blur.c composite/nodes/node_composite_bokehblur.c @@ -140,33 +141,48 @@ set(SRC function/node_function_util.cc geometry/nodes/node_geo_align_rotation_to_vector.cc + geometry/nodes/node_geo_attribute_clamp.cc geometry/nodes/node_geo_attribute_color_ramp.cc geometry/nodes/node_geo_attribute_combine_xyz.cc geometry/nodes/node_geo_attribute_compare.cc geometry/nodes/node_geo_attribute_convert.cc + geometry/nodes/node_geo_attribute_curve_map.cc geometry/nodes/node_geo_attribute_fill.cc + geometry/nodes/node_geo_attribute_map_range.cc geometry/nodes/node_geo_attribute_math.cc geometry/nodes/node_geo_attribute_mix.cc geometry/nodes/node_geo_attribute_proximity.cc geometry/nodes/node_geo_attribute_randomize.cc + geometry/nodes/node_geo_attribute_remove.cc geometry/nodes/node_geo_attribute_sample_texture.cc geometry/nodes/node_geo_attribute_separate_xyz.cc + geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc - geometry/nodes/node_geo_attribute_remove.cc + geometry/nodes/node_geo_attribute_vector_rotate.cc geometry/nodes/node_geo_boolean.cc + geometry/nodes/node_geo_bounding_box.cc geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc + geometry/nodes/node_geo_convex_hull.cc + geometry/nodes/node_geo_curve_length.cc + geometry/nodes/node_geo_curve_to_mesh.cc + geometry/nodes/node_geo_curve_resample.cc + geometry/nodes/node_geo_delete_geometry.cc geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc + geometry/nodes/node_geo_material_assign.cc + geometry/nodes/node_geo_material_replace.cc geometry/nodes/node_geo_mesh_primitive_circle.cc geometry/nodes/node_geo_mesh_primitive_cone.cc geometry/nodes/node_geo_mesh_primitive_cube.cc geometry/nodes/node_geo_mesh_primitive_cylinder.cc + geometry/nodes/node_geo_mesh_primitive_grid.cc geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc geometry/nodes/node_geo_mesh_primitive_line.cc - geometry/nodes/node_geo_mesh_primitive_plane.cc geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc + geometry/nodes/node_geo_mesh_to_curve.cc geometry/nodes/node_geo_object_info.cc geometry/nodes/node_geo_point_distribute.cc geometry/nodes/node_geo_point_instance.cc @@ -175,8 +191,10 @@ set(SRC geometry/nodes/node_geo_point_separate.cc geometry/nodes/node_geo_point_translate.cc geometry/nodes/node_geo_points_to_volume.cc + geometry/nodes/node_geo_select_by_material.cc geometry/nodes/node_geo_subdivide.cc geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc geometry/nodes/node_geo_triangulate.cc geometry/nodes/node_geo_volume_to_mesh.cc @@ -207,7 +225,7 @@ set(SRC shader/nodes/node_shader_camera.c shader/nodes/node_shader_clamp.cc shader/nodes/node_shader_common.c - shader/nodes/node_shader_curves.c + shader/nodes/node_shader_curves.cc shader/nodes/node_shader_displacement.c shader/nodes/node_shader_eevee_specular.c shader/nodes/node_shader_emission.c @@ -225,7 +243,7 @@ set(SRC shader/nodes/node_shader_map_range.cc shader/nodes/node_shader_mapping.c shader/nodes/node_shader_math.cc - shader/nodes/node_shader_mixRgb.c + shader/nodes/node_shader_mixRgb.cc shader/nodes/node_shader_mix_shader.c shader/nodes/node_shader_normal.c shader/nodes/node_shader_normal_map.c @@ -306,13 +324,14 @@ set(SRC intern/derived_node_tree.cc intern/math_functions.cc intern/node_common.c - intern/node_exec.c + intern/node_exec.cc intern/node_geometry_exec.cc intern/node_socket.cc intern/node_tree_multi_function.cc intern/node_tree_ref.cc intern/node_util.c intern/type_callbacks.cc + intern/type_conversions.cc composite/node_composite_util.h function/node_function_util.hh @@ -334,6 +353,7 @@ set(SRC NOD_static_types.h NOD_texture.h NOD_type_callbacks.hh + NOD_type_conversions.hh intern/node_common.h intern/node_exec.h intern/node_util.h @@ -345,6 +365,23 @@ set(LIB bf_intern_sky ) +if(WITH_BULLET) + list(APPEND INC_SYS + ${BULLET_INCLUDE_DIRS} + "../../../intern/rigidbody/" + ) + if(NOT WITH_SYSTEM_BULLET) + list(APPEND LIB + extern_bullet + ) + endif() + + list(APPEND LIB + ${BULLET_LIBRARIES} + ) + add_definitions(-DWITH_BULLET) +endif() + if(WITH_PYTHON) list(APPEND INC ../python @@ -389,6 +426,18 @@ if(WITH_OPENSUBDIV) add_definitions(-DWITH_OPENSUBDIV) endif() +if(WITH_GMP) + add_definitions(-DWITH_GMP) + + list(APPEND INC_SYS + ${GMP_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${GMP_LIBRARIES} + ) +endif() + if(WITH_OPENVDB) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index b0dd0edeec5..258e4c961c9 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -79,6 +79,7 @@ void register_node_type_cmp_inpaint(void); void register_node_type_cmp_despeckle(void); void register_node_type_cmp_defocus(void); void register_node_type_cmp_denoise(void); +void register_node_type_cmp_antialiasing(void); void register_node_type_cmp_valtorgb(void); void register_node_type_cmp_rgbtobw(void); diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index c29de611e18..de9e4c8c812 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -59,6 +59,7 @@ class DTreeContext { const NodeTreeRef *tree_; /* All the children contexts of this context. */ Map<const NodeRef *, DTreeContext *> children_; + DerivedNodeTree *derived_tree_; friend DerivedNodeTree; @@ -67,6 +68,7 @@ class DTreeContext { const DTreeContext *parent_context() const; const NodeRef *parent_node() const; const DTreeContext *child_context(const NodeRef &node) const; + const DerivedNodeTree &derived_tree() const; bool is_root() const; }; @@ -90,6 +92,12 @@ class DNode { operator bool() const; uint64_t hash() const; + + DInputSocket input(int index) const; + DOutputSocket output(int index) const; + + DInputSocket input_by_identifier(StringRef identifier) const; + DOutputSocket output_by_identifier(StringRef identifier) const; }; /* A (nullable) reference to a socket and the context it is in. It is unique within an entire @@ -117,6 +125,8 @@ class DSocket { operator bool() const; uint64_t hash() const; + + DNode node() const; }; /* A (nullable) reference to an input socket and the context it is in. */ @@ -132,7 +142,7 @@ class DInputSocket : public DSocket { DOutputSocket get_corresponding_group_node_output() const; Vector<DOutputSocket, 4> get_corresponding_group_input_sockets() const; - void foreach_origin_socket(FunctionRef<void(DSocket)> callback) const; + void foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) const; }; /* A (nullable) reference to an output socket and the context it is in. */ @@ -148,7 +158,8 @@ class DOutputSocket : public DSocket { DInputSocket get_corresponding_group_node_input() const; DInputSocket get_active_corresponding_group_output_socket() const; - void foreach_target_socket(FunctionRef<void(DInputSocket)> callback) const; + void foreach_target_socket(FunctionRef<void(DInputSocket)> target_fn, + FunctionRef<void(DSocket)> skipped_fn) const; }; class DerivedNodeTree { @@ -165,6 +176,7 @@ class DerivedNodeTree { Span<const NodeTreeRef *> used_node_tree_refs() const; bool has_link_cycles() const; + bool has_undefined_nodes_or_sockets() const; void foreach_node(FunctionRef<void(DNode)> callback) const; std::string to_dot() const; @@ -214,6 +226,11 @@ inline const DTreeContext *DTreeContext::child_context(const NodeRef &node) cons return children_.lookup_default(&node, nullptr); } +inline const DerivedNodeTree &DTreeContext::derived_tree() const +{ + return *derived_tree_; +} + inline bool DTreeContext::is_root() const { return parent_context_ == nullptr; @@ -264,6 +281,26 @@ inline uint64_t DNode::hash() const return get_default_hash_2(context_, node_ref_); } +inline DInputSocket DNode::input(int index) const +{ + return {context_, &node_ref_->input(index)}; +} + +inline DOutputSocket DNode::output(int index) const +{ + return {context_, &node_ref_->output(index)}; +} + +inline DInputSocket DNode::input_by_identifier(StringRef identifier) const +{ + return {context_, &node_ref_->input_by_identifier(identifier)}; +} + +inline DOutputSocket DNode::output_by_identifier(StringRef identifier) const +{ + return {context_, &node_ref_->output_by_identifier(identifier)}; +} + /* -------------------------------------------------------------------- * DSocket inline methods. */ @@ -319,6 +356,12 @@ inline uint64_t DSocket::hash() const return get_default_hash_2(context_, socket_ref_); } +inline DNode DSocket::node() const +{ + BLI_assert(socket_ref_ != nullptr); + return {context_, &socket_ref_->node()}; +} + /* -------------------------------------------------------------------- * DInputSocket inline methods. */ diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 03115852d80..b7e1b0b657c 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -20,38 +20,56 @@ extern "C" { #endif +#include "BKE_node.h" + extern struct bNodeTreeType *ntreeType_Geometry; void register_node_tree_type_geo(void); void register_node_type_geo_group(void); +void register_node_type_geo_custom_group(bNodeType *ntype); void register_node_type_geo_align_rotation_to_vector(void); +void register_node_type_geo_attribute_clamp(void); void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_attribute_combine_xyz(void); void register_node_type_geo_attribute_compare(void); void register_node_type_geo_attribute_convert(void); +void register_node_type_geo_attribute_curve_map(void); void register_node_type_geo_attribute_fill(void); +void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); void register_node_type_geo_attribute_mix(void); void register_node_type_geo_attribute_proximity(void); void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_separate_xyz(void); +void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); +void register_node_type_geo_attribute_vector_rotate(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_boolean(void); +void register_node_type_geo_bounding_box(void); void register_node_type_geo_collection_info(void); +void register_node_type_geo_convex_hull(void); +void register_node_type_geo_curve_length(void); +void register_node_type_geo_curve_to_mesh(void); +void register_node_type_geo_curve_resample(void); +void register_node_type_geo_delete_geometry(void); void register_node_type_geo_edge_split(void); +void register_node_type_geo_input_material(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); +void register_node_type_geo_material_assign(void); +void register_node_type_geo_material_replace(void); void register_node_type_geo_mesh_primitive_circle(void); void register_node_type_geo_mesh_primitive_cone(void); void register_node_type_geo_mesh_primitive_cube(void); -void register_node_type_geo_mesh_primitive_plane(void); void register_node_type_geo_mesh_primitive_cylinder(void); +void register_node_type_geo_mesh_primitive_grid(void); void register_node_type_geo_mesh_primitive_ico_sphere(void); void register_node_type_geo_mesh_primitive_line(void); void register_node_type_geo_mesh_primitive_uv_sphere(void); +void register_node_type_geo_mesh_to_curve(void); void register_node_type_geo_object_info(void); void register_node_type_geo_point_distribute(void); void register_node_type_geo_point_instance(void); @@ -61,8 +79,10 @@ void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); void register_node_type_geo_points_to_volume(void); void register_node_type_geo_sample_texture(void); +void register_node_type_geo_select_by_material(void); void register_node_type_geo_subdivide(void); void register_node_type_geo_subdivision_surface(void); +void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); void register_node_type_geo_triangulate(void); void register_node_type_geo_volume_to_mesh(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index ba606dacdb0..86c98525814 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -22,7 +22,6 @@ #include "BKE_geometry_set.hh" #include "BKE_geometry_set_instances.hh" #include "BKE_node_ui_storage.hh" -#include "BKE_persistent_data_handle.hh" #include "DNA_node_types.h" @@ -33,55 +32,90 @@ struct ModifierData; namespace blender::nodes { -using bke::BooleanReadAttribute; -using bke::BooleanWriteAttribute; -using bke::Color4fReadAttribute; -using bke::Color4fWriteAttribute; -using bke::Float2ReadAttribute; -using bke::Float2WriteAttribute; -using bke::Float3ReadAttribute; -using bke::Float3WriteAttribute; -using bke::FloatReadAttribute; -using bke::FloatWriteAttribute; using bke::geometry_set_realize_instances; -using bke::Int32ReadAttribute; -using bke::Int32WriteAttribute; -using bke::PersistentDataHandleMap; -using bke::PersistentObjectHandle; -using bke::ReadAttribute; -using bke::ReadAttributePtr; -using bke::WriteAttribute; -using bke::WriteAttributePtr; +using bke::OutputAttribute; +using bke::OutputAttribute_Typed; +using bke::ReadAttributeLookup; +using bke::WriteAttributeLookup; using fn::CPPType; using fn::GMutablePointer; +using fn::GMutableSpan; using fn::GPointer; +using fn::GSpan; using fn::GValueMap; +using fn::GVArray; +using fn::GVArray_GSpan; +using fn::GVArray_Span; +using fn::GVArray_Typed; +using fn::GVArrayPtr; +using fn::GVMutableArray; +using fn::GVMutableArray_GSpan; +using fn::GVMutableArray_Typed; +using fn::GVMutableArrayPtr; + +/** + * This class exists to separate the memory management details of the geometry nodes evaluator from + * the node execution functions and related utilities. + */ +class GeoNodeExecParamsProvider { + public: + DNode dnode; + const Object *self_object = nullptr; + const ModifierData *modifier = nullptr; + Depsgraph *depsgraph = nullptr; + + /** + * Returns true when the node is allowed to get/extract the input value. The identifier is + * expected to be valid. This may return false if the input value has been consumed already. + */ + virtual bool can_get_input(StringRef identifier) const = 0; + + /** + * Returns true when the node is allowed to set the output value. The identifier is expected to + * be valid. This may return false if the output value has been set already. + */ + virtual bool can_set_output(StringRef identifier) const = 0; + + /** + * Take ownership of an input value. The caller is responsible for destructing the value. It does + * not have to be freed, because the memory is managed by the geometry nodes evaluator. + */ + virtual GMutablePointer extract_input(StringRef identifier) = 0; + + /** + * Similar to #extract_input, but has to be used for multi-input sockets. + */ + virtual Vector<GMutablePointer> extract_multi_input(StringRef identifier) = 0; + + /** + * Get the input value for the identifier without taking ownership of it. + */ + virtual GPointer get_input(StringRef identifier) const = 0; + + /** + * Prepare a memory buffer for an output value of the node. The returned memory has to be + * initialized by the caller. The identifier and type are expected to be correct. + */ + virtual GMutablePointer alloc_output_value(const CPPType &type) = 0; + + /** + * The value has been allocated with #alloc_output_value. + */ + virtual void set_output(StringRef identifier, GMutablePointer value) = 0; + + /* A description for these methods is provided in GeoNodeExecParams. */ + virtual void set_input_unused(StringRef identifier) = 0; + virtual bool output_is_required(StringRef identifier) const = 0; + virtual bool lazy_require_input(StringRef identifier) = 0; + virtual bool lazy_output_is_required(StringRef identifier) const = 0; +}; class GeoNodeExecParams { private: - const DNode node_; - GValueMap<StringRef> &input_values_; - GValueMap<StringRef> &output_values_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; + GeoNodeExecParamsProvider *provider_; public: - GeoNodeExecParams(const DNode node, - GValueMap<StringRef> &input_values, - GValueMap<StringRef> &output_values, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph) - : node_(node), - input_values_(input_values), - output_values_(output_values), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph) + GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider) { } @@ -94,9 +128,9 @@ class GeoNodeExecParams { GMutablePointer extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier); + this->check_input_access(identifier); #endif - return input_values_.extract(identifier); + return provider_->extract_input(identifier); } /** @@ -107,9 +141,10 @@ class GeoNodeExecParams { template<typename T> T extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - return input_values_.extract<T>(identifier); + GMutablePointer gvalue = this->extract_input(identifier); + return gvalue.relocate_out<T>(); } /** @@ -119,18 +154,10 @@ class GeoNodeExecParams { */ template<typename T> Vector<T> extract_multi_input(StringRef identifier) { + Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier); Vector<T> values; - int index = 0; - while (true) { - std::string sub_identifier = identifier; - if (index > 0) { - sub_identifier += "[" + std::to_string(index) + "]"; - } - if (!input_values_.contains(sub_identifier)) { - break; - } - values.append(input_values_.extract<T, StringRef>(sub_identifier)); - index++; + for (GMutablePointer gvalue : gvalues) { + values.append(gvalue.relocate_out<T>()); } return values; } @@ -141,67 +168,83 @@ class GeoNodeExecParams { template<typename T> const T &get_input(StringRef identifier) const { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - return input_values_.lookup<T>(identifier); + GPointer gvalue = provider_->get_input(identifier); + BLI_assert(gvalue.is_type<T>()); + return *(const T *)gvalue.get(); } /** - * Move-construct a new value based on the given value and store it for the given socket - * identifier. + * Store the output value for the given socket identifier. */ - void set_output_by_move(StringRef identifier, GMutablePointer value) + template<typename T> void set_output(StringRef identifier, T &&value) { + using StoredT = std::decay_t<T>; + const CPPType &type = CPPType::get<std::decay_t<T>>(); #ifdef DEBUG - BLI_assert(value.type() != nullptr); - BLI_assert(value.get() != nullptr); - this->check_set_output(identifier, *value.type()); + this->check_output_access(identifier, type); #endif - output_values_.add_new_by_move(identifier, value); + GMutablePointer gvalue = provider_->alloc_output_value(type); + new (gvalue.get()) StoredT(std::forward<T>(value)); + provider_->set_output(identifier, gvalue); } - void set_output_by_copy(StringRef identifier, GPointer value) + /** + * Tell the evaluator that a specific input won't be used anymore. + */ + void set_input_unused(StringRef identifier) { -#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_copy(identifier, value); + provider_->set_input_unused(identifier); } /** - * Store the output value for the given socket identifier. + * Returns true when the output has to be computed. + * Nodes that support laziness could use the #lazy_output_is_required variant to possibly avoid + * some computations. */ - template<typename T> void set_output(StringRef identifier, T &&value) + bool output_is_required(StringRef identifier) const { -#ifdef DEBUG - this->check_set_output(identifier, CPPType::get<std::decay_t<T>>()); -#endif - output_values_.add_new(identifier, std::forward<T>(value)); + return provider_->output_is_required(identifier); } /** - * Get the node that is currently being executed. + * Tell the evaluator that a specific input is required. + * This returns true when the input will only be available in the next execution. + * False is returned if the input is available already. + * This can only be used when the node supports laziness. */ - const bNode &node() const + bool lazy_require_input(StringRef identifier) { - return *node_->bnode(); + return provider_->lazy_require_input(identifier); } - const PersistentDataHandleMap &handle_map() const + /** + * Asks the evaluator if a specific output is required right now. If this returns false, the + * value might still need to be computed later. + * This can only be used when the node supports laziness. + */ + bool lazy_output_is_required(StringRef identifier) + { + return provider_->lazy_output_is_required(identifier); + } + + /** + * Get the node that is currently being executed. + */ + const bNode &node() const { - return handle_map_; + return *provider_->dnode->bnode(); } const Object *self_object() const { - return self_object_; + return provider_->self_object; } Depsgraph *depsgraph() const { - return depsgraph_; + return provider_->depsgraph; } /** @@ -217,20 +260,21 @@ class GeoNodeExecParams { * \note This will add an error message if the string socket is active and * the input attribute does not exist. */ - ReadAttributePtr get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const CustomDataType type, - const void *default_value) const; + GVArrayPtr get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const CustomDataType type, + const void *default_value) const; template<typename T> - bke::TypedReadAttribute<T> get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const T &default_value) const + GVArray_Typed<T> get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const T &default_value) const { const CustomDataType type = bke::cpp_type_to_custom_data_type(CPPType::get<T>()); - return this->get_input_attribute(name, component, domain, type, &default_value); + GVArrayPtr varray = this->get_input_attribute(name, component, domain, type, &default_value); + return GVArray_Typed<T>(std::move(varray)); } /** @@ -247,8 +291,8 @@ class GeoNodeExecParams { 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; + void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const; + void check_output_access(StringRef identifier, const CPPType &value_type) const; /* Find the active socket socket with the input name (not the identifier). */ const bNodeSocket *find_available_socket(const StringRef name) const; diff --git a/source/blender/nodes/NOD_math_functions.hh b/source/blender/nodes/NOD_math_functions.hh index 08496e044c5..f3eb1c24087 100644 --- a/source/blender/nodes/NOD_math_functions.hh +++ b/source/blender/nodes/NOD_math_functions.hh @@ -347,6 +347,8 @@ inline bool try_dispatch_float_math_fl3_fl3_fl3_to_fl3(const NodeVectorMathOpera }; switch (operation) { + case NODE_VECTOR_MATH_MULTIPLY_ADD: + return dispatch([](float3 a, float3 b, float3 c) { return a * b + c; }); case NODE_VECTOR_MATH_WRAP: return dispatch([](float3 a, float3 b, float3 c) { return float3(wrapf(a.x, b.x, c.x), wrapf(a.y, b.y, c.y), wrapf(a.z, b.z, c.z)); @@ -379,7 +381,8 @@ inline bool try_dispatch_float_math_fl3_fl3_fl_to_fl3(const NodeVectorMathOperat switch (operation) { case NODE_VECTOR_MATH_REFRACT: - return dispatch([](float3 a, float3 b, float c) { return float3::refract(a, b.normalized(), c); }); + return dispatch( + [](float3 a, float3 b, float c) { return float3::refract(a, b.normalized(), c); }); default: return false; } diff --git a/source/blender/nodes/NOD_node_tree_multi_function.hh b/source/blender/nodes/NOD_node_tree_multi_function.hh index df31ee18369..e3f31e011e4 100644 --- a/source/blender/nodes/NOD_node_tree_multi_function.hh +++ b/source/blender/nodes/NOD_node_tree_multi_function.hh @@ -29,7 +29,7 @@ #include "NOD_type_callbacks.hh" #include "BLI_multi_value_map.hh" -#include "BLI_resource_collector.hh" +#include "BLI_resource_scope.hh" namespace blender::nodes { @@ -190,7 +190,7 @@ class MFNetworkTreeMap { * This data is necessary throughout the generation of a MFNetwork from a node tree. */ struct CommonMFNetworkBuilderData { - ResourceCollector &resources; + ResourceScope &scope; fn::MFNetwork &network; MFNetworkTreeMap &network_map; const DerivedNodeTree &tree; @@ -225,9 +225,9 @@ class MFNetworkBuilderBase { * Returns a resource collector that will only be destructed after the multi-function network is * destructed. */ - ResourceCollector &resources() + ResourceScope &resource_scope() { - return common_.resources; + return common_.scope; } /** @@ -236,9 +236,9 @@ class MFNetworkBuilderBase { template<typename T, typename... Args> T &construct_fn(Args &&... args) { BLI_STATIC_ASSERT((std::is_base_of_v<fn::MultiFunction, T>), ""); - void *buffer = common_.resources.linear_allocator().allocate(sizeof(T), alignof(T)); + void *buffer = common_.scope.linear_allocator().allocate(sizeof(T), alignof(T)); T *fn = new (buffer) T(std::forward<Args>(args)...); - common_.resources.add(destruct_ptr<T>(fn), fn->name().c_str()); + common_.scope.add(destruct_ptr<T>(fn), fn->name().c_str()); return *fn; } }; @@ -382,39 +382,9 @@ class NodeMFNetworkBuilder : public MFNetworkBuilderBase { MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, const DerivedNodeTree &tree, - ResourceCollector &resources); + ResourceScope &scope); using MultiFunctionByNode = Map<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(); +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, ResourceScope &scope); } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 3710bd2fe00..b028fc28bbc 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -70,6 +70,8 @@ class NodeTreeRef; class LinkRef; class InternalLinkRef; +using SocketIndexByIdentifierMap = Map<std::string, int>; + class SocketRef : NonCopyable, NonMovable { protected: NodeRef *node_; @@ -81,15 +83,19 @@ class SocketRef : NonCopyable, NonMovable { Vector<LinkRef *> directly_linked_links_; /* These sockets are linked directly, i.e. with a single link in between. */ - MutableSpan<SocketRef *> directly_linked_sockets_; + MutableSpan<const SocketRef *> directly_linked_sockets_; /* These sockets are linked when reroutes, muted links and muted nodes have been taken into * account. */ - MutableSpan<SocketRef *> logically_linked_sockets_; + MutableSpan<const SocketRef *> logically_linked_sockets_; + /* These are the sockets that have been skipped when searching for logically linked sockets. + * That includes for example the input and output socket of an intermediate reroute node. */ + MutableSpan<const SocketRef *> logically_linked_skipped_sockets_; friend NodeTreeRef; public: Span<const SocketRef *> logically_linked_sockets() const; + Span<const SocketRef *> logically_linked_skipped_sockets() const; Span<const SocketRef *> directly_linked_sockets() const; Span<const LinkRef *> directly_linked_links() const; @@ -121,6 +127,7 @@ class SocketRef : NonCopyable, NonMovable { bNodeTree *btree() const; bool is_available() const; + bool is_undefined() const; void *default_value() const; template<typename T> T *default_value() const; @@ -128,16 +135,31 @@ class SocketRef : NonCopyable, NonMovable { class InputSocketRef final : public SocketRef { public: + friend NodeTreeRef; + Span<const OutputSocketRef *> logically_linked_sockets() const; Span<const OutputSocketRef *> directly_linked_sockets() const; bool is_multi_input_socket() const; + + private: + void foreach_logical_origin(FunctionRef<void(const OutputSocketRef &)> origin_fn, + FunctionRef<void(const SocketRef &)> skipped_fn, + bool only_follow_first_input_link, + Vector<const InputSocketRef *> &handled_sockets) const; }; class OutputSocketRef final : public SocketRef { public: + friend NodeTreeRef; + Span<const InputSocketRef *> logically_linked_sockets() const; Span<const InputSocketRef *> directly_linked_sockets() const; + + private: + void foreach_logical_target(FunctionRef<void(const InputSocketRef &)> target_fn, + FunctionRef<void(const SocketRef &)> skipped_fn, + Vector<const OutputSocketRef *> &handled_sockets) const; }; class NodeRef : NonCopyable, NonMovable { @@ -149,6 +171,8 @@ class NodeRef : NonCopyable, NonMovable { Vector<InputSocketRef *> inputs_; Vector<OutputSocketRef *> outputs_; Vector<InternalLinkRef *> internal_links_; + SocketIndexByIdentifierMap *input_index_by_identifier_; + SocketIndexByIdentifierMap *output_index_by_identifier_; friend NodeTreeRef; @@ -162,6 +186,9 @@ class NodeRef : NonCopyable, NonMovable { const InputSocketRef &input(int index) const; const OutputSocketRef &output(int index) const; + const InputSocketRef &input_by_identifier(StringRef identifier) const; + const OutputSocketRef &output_by_identifier(StringRef identifier) const; + bNode *bnode() const; bNodeTree *btree() const; @@ -178,6 +205,7 @@ class NodeRef : NonCopyable, NonMovable { bool is_group_output_node() const; bool is_muted() const; bool is_frame() const; + bool is_undefined() const; void *storage() const; template<typename T> T *storage() const; @@ -225,6 +253,7 @@ class NodeTreeRef : NonCopyable, NonMovable { Vector<OutputSocketRef *> output_sockets_; Vector<LinkRef *> links_; MultiValueMap<const bNodeType *, NodeRef *> nodes_by_type_; + Vector<std::unique_ptr<SocketIndexByIdentifierMap>> owned_identifier_maps_; public: NodeTreeRef(bNodeTree *btree); @@ -241,6 +270,7 @@ class NodeTreeRef : NonCopyable, NonMovable { Span<const LinkRef *> links() const; bool has_link_cycles() const; + bool has_undefined_nodes_or_sockets() const; bNodeTree *btree() const; StringRefNull name() const; @@ -257,12 +287,7 @@ class NodeTreeRef : NonCopyable, NonMovable { bNodeSocket *bsocket); void create_linked_socket_caches(); - - void foreach_logical_origin(InputSocketRef &socket, - FunctionRef<void(OutputSocketRef &)> callback, - bool only_follow_first_input_link = false); - void foreach_logical_target(OutputSocketRef &socket, - FunctionRef<void(InputSocketRef &)> callback); + void create_socket_identifier_maps(); }; using NodeTreeRefMap = Map<bNodeTree *, std::unique_ptr<const NodeTreeRef>>; @@ -287,6 +312,11 @@ inline Span<const SocketRef *> SocketRef::logically_linked_sockets() const return logically_linked_sockets_; } +inline Span<const SocketRef *> SocketRef::logically_linked_skipped_sockets() const +{ + return logically_linked_skipped_sockets_; +} + inline Span<const SocketRef *> SocketRef::directly_linked_sockets() const { return directly_linked_sockets_; @@ -399,6 +429,11 @@ inline bool SocketRef::is_available() const return (bsocket_->flag & SOCK_UNAVAIL) == 0; } +inline bool SocketRef::is_undefined() const +{ + return bsocket_->typeinfo == &NodeSocketTypeUndefined; +} + inline void *SocketRef::default_value() const { return bsocket_->default_value; @@ -476,6 +511,18 @@ inline const OutputSocketRef &NodeRef::output(int index) const return *outputs_[index]; } +inline const InputSocketRef &NodeRef::input_by_identifier(StringRef identifier) const +{ + const int index = input_index_by_identifier_->lookup_as(identifier); + return this->input(index); +} + +inline const OutputSocketRef &NodeRef::output_by_identifier(StringRef identifier) const +{ + const int index = output_index_by_identifier_->lookup_as(identifier); + return this->output(index); +} + inline bNode *NodeRef::bnode() const { return bnode_; @@ -518,7 +565,7 @@ inline bool NodeRef::is_reroute_node() const inline bool NodeRef::is_group_node() const { - return bnode_->type == NODE_GROUP; + return bnode_->type == NODE_GROUP || bnode_->type == NODE_CUSTOM_GROUP; } inline bool NodeRef::is_group_input_node() const @@ -536,6 +583,11 @@ inline bool NodeRef::is_frame() const return bnode_->type == NODE_FRAME; } +inline bool NodeRef::is_undefined() const +{ + return bnode_->typeinfo == &NodeTypeUndefined; +} + inline bool NodeRef::is_muted() const { return (bnode_->flag & NODE_MUTED) != 0; diff --git a/source/blender/nodes/NOD_socket.h b/source/blender/nodes/NOD_socket.h index 3344a25bdea..6bcfda70d04 100644 --- a/source/blender/nodes/NOD_socket.h +++ b/source/blender/nodes/NOD_socket.h @@ -41,7 +41,7 @@ extern "C" { struct bNodeSocket *node_add_socket_from_template(struct bNodeTree *ntree, struct bNode *node, struct bNodeSocketTemplate *stemp, - int in_out); + eNodeSocketInOut in_out); void node_verify_socket_templates(struct bNodeTree *ntree, struct bNode *node); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ec687ae6b70..b255c6e5f23 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -224,6 +224,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE, def_cmp_cryptomatte, "CRYPTO DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy, "CRYPTOMATTE", Cryptomatte, "Cryptomatte (Legacy)", "" ) DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" ) DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" ) +DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" ) DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" ) DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" ) @@ -262,50 +263,67 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DI DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") +DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") +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, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") -DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") -DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") -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", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "") -DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "") -DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "") -DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") -DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") -DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, def_geo_attribute_sample_texture, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") +DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") +DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") +DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") +DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") +DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") +DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") -DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_REPLACE, 0, "MATERIAL_REPLACE", MaterialReplace, "Material Replace", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Circle", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CYLINDER, def_geo_mesh_cylinder, "MESH_PRIMITIVE_CYLINDER", MeshCylinder, "Cylinder", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO_SPHERE", MeshIcoSphere, "Ico Sphere", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Line", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_PLANE, 0, "MESH_PRIMITIVE_PLANE", MeshPlane, "Plane", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") +DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") +DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/NOD_type_conversions.hh b/source/blender/nodes/NOD_type_conversions.hh new file mode 100644 index 00000000000..ec4859f0657 --- /dev/null +++ b/source/blender/nodes/NOD_type_conversions.hh @@ -0,0 +1,83 @@ +/* + * 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_multi_function.hh" + +namespace blender::nodes { + +using fn::CPPType; +using fn::GVArray; + +struct ConversionFunctions { + const fn::MultiFunction *multi_function; + void (*convert_single_to_initialized)(const void *src, void *dst); + void (*convert_single_to_uninitialized)(const void *src, void *dst); +}; + +class DataTypeConversions { + private: + Map<std::pair<fn::MFDataType, fn::MFDataType>, ConversionFunctions> conversions_; + + public: + void add(fn::MFDataType from_type, + fn::MFDataType to_type, + const fn::MultiFunction &fn, + void (*convert_single_to_initialized)(const void *src, void *dst), + void (*convert_single_to_uninitialized)(const void *src, void *dst)) + { + conversions_.add_new({from_type, to_type}, + {&fn, convert_single_to_initialized, convert_single_to_uninitialized}); + } + + const ConversionFunctions *get_conversion_functions(fn::MFDataType from, fn::MFDataType to) const + { + return conversions_.lookup_ptr({from, to}); + } + + const ConversionFunctions *get_conversion_functions(const CPPType &from, const CPPType &to) const + { + return this->get_conversion_functions(fn::MFDataType::ForSingle(from), + fn::MFDataType::ForSingle(to)); + } + + const fn::MultiFunction *get_conversion_multi_function(fn::MFDataType from, + fn::MFDataType to) const + { + const ConversionFunctions *functions = this->get_conversion_functions(from, to); + return functions ? functions->multi_function : 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_to_uninitialized(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const; + + fn::GVArrayPtr try_convert(fn::GVArrayPtr varray, const CPPType &to_type) const; + + fn::GVMutableArrayPtr try_convert(fn::GVMutableArrayPtr varray, const CPPType &to_type) const; +}; + +const DataTypeConversions &get_implicit_type_conversions(); + +} // namespace blender::nodes diff --git a/source/blender/nodes/composite/node_composite_tree.c b/source/blender/nodes/composite/node_composite_tree.c index 085b5c463b9..19815d01278 100644 --- a/source/blender/nodes/composite/node_composite_tree.c +++ b/source/blender/nodes/composite/node_composite_tree.c @@ -205,6 +205,12 @@ static void composite_node_add_init(bNodeTree *UNUSED(bnodetree), bNode *bnode) } } +static bool composite_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); +} + bNodeTreeType *ntreeType_Composite; void register_node_tree_type_cmp(void) @@ -227,6 +233,7 @@ void register_node_tree_type_cmp(void) tt->update = update; tt->get_from_context = composite_get_from_context; tt->node_add_init = composite_node_add_init; + tt->valid_socket_type = composite_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_CompositorNodeTree; diff --git a/source/blender/nodes/composite/node_composite_util.c b/source/blender/nodes/composite/node_composite_util.c index b6cbffea413..6cc17d8c272 100644 --- a/source/blender/nodes/composite/node_composite_util.c +++ b/source/blender/nodes/composite/node_composite_util.c @@ -23,9 +23,15 @@ #include "node_composite_util.h" -bool cmp_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool cmp_node_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { - return STREQ(ntree->idname, "CompositorNodeTree"); + if (!STREQ(ntree->idname, "CompositorNodeTree")) { + *r_disabled_hint = "Not a compositor node tree"; + return false; + } + return true; } void cmp_node_update_default(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/composite/node_composite_util.h b/source/blender/nodes/composite/node_composite_util.h index 800c55df4d6..4fcccbb79f0 100644 --- a/source/blender/nodes/composite/node_composite_util.h +++ b/source/blender/nodes/composite/node_composite_util.h @@ -54,7 +54,9 @@ extern "C" { #define CMP_SCALE_MAX 12000 -bool cmp_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool cmp_node_poll_default(struct bNodeType *ntype, + struct bNodeTree *ntree, + const char **r_disabled_info); void cmp_node_update_default(struct bNodeTree *ntree, struct bNode *node); void cmp_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); diff --git a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c b/source/blender/nodes/composite/nodes/node_composite_antialiasing.c new file mode 100644 index 00000000000..7437496d878 --- /dev/null +++ b/source/blender/nodes/composite/nodes/node_composite_antialiasing.c @@ -0,0 +1,65 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2017 Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): IRIE Shinsuke + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/nodes/composite/nodes/node_composite_antialiasing.c + * \ingroup cmpnodes + */ + +#include "node_composite_util.h" + +/* **************** Anti-Aliasing (SMAA 1x) ******************** */ + +static bNodeSocketTemplate cmp_node_antialiasing_in[] = { + {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; + +static bNodeSocketTemplate cmp_node_antialiasing_out[] = {{SOCK_RGBA, N_("Image")}, {-1, ""}}; + +static void node_composit_init_antialiasing(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAntiAliasingData *data = MEM_callocN(sizeof(NodeAntiAliasingData), "node antialiasing data"); + + data->threshold = 1.0f; + data->contrast_limit = 0.2f; + data->corner_rounding = 0.25f; + + node->storage = data; +} + +void register_node_type_cmp_antialiasing(void) +{ + static bNodeType ntype; + + cmp_node_type_base( + &ntype, CMP_NODE_ANTIALIASING, "Anti-Aliasing", NODE_CLASS_OP_FILTER, NODE_PREVIEW); + node_type_socket_templates(&ntype, cmp_node_antialiasing_in, cmp_node_antialiasing_out); + node_type_size(&ntype, 170, 140, 200); + node_type_init(&ntype, node_composit_init_antialiasing); + node_type_storage( + &ntype, "NodeAntiAliasingData", node_free_standard_storage, node_copy_standard_storage); + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index c5135482ec2..51dd73a86af 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -40,61 +40,74 @@ /** \name Cryptomatte * \{ */ +static blender::bke::cryptomatte::CryptomatteSessionPtr cryptomatte_init_from_node_render( + const bNode &node, const bool use_meta_data) +{ + blender::bke::cryptomatte::CryptomatteSessionPtr session; + + Scene *scene = (Scene *)node.id; + if (!scene) { + return session; + } + BLI_assert(GS(scene->id.name) == ID_SCE); + + if (use_meta_data) { + Render *render = RE_GetSceneRender(scene); + RenderResult *render_result = render ? RE_AcquireResultRead(render) : nullptr; + if (render_result) { + session = blender::bke::cryptomatte::CryptomatteSessionPtr( + BKE_cryptomatte_init_from_render_result(render_result)); + } + if (render) { + RE_ReleaseResult(render); + } + } + + if (session == nullptr) { + session = blender::bke::cryptomatte::CryptomatteSessionPtr( + BKE_cryptomatte_init_from_scene(scene)); + } + return session; +} + +static blender::bke::cryptomatte::CryptomatteSessionPtr cryptomatte_init_from_node_image( + const Scene &scene, const bNode &node) +{ + blender::bke::cryptomatte::CryptomatteSessionPtr session; + Image *image = (Image *)node.id; + if (!image) { + return session; + } + BLI_assert(GS(image->id.name) == ID_IM); + + NodeCryptomatte *node_cryptomatte = static_cast<NodeCryptomatte *>(node.storage); + ImageUser *iuser = &node_cryptomatte->iuser; + BKE_image_user_frame_calc(image, iuser, scene.r.cfra); + ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, nullptr); + RenderResult *render_result = image->rr; + if (render_result) { + session = blender::bke::cryptomatte::CryptomatteSessionPtr( + BKE_cryptomatte_init_from_render_result(render_result)); + } + BKE_image_release_ibuf(image, ibuf, nullptr); + return session; +} static blender::bke::cryptomatte::CryptomatteSessionPtr cryptomatte_init_from_node( - const bNode &node, const int frame_number, const bool use_meta_data) + const Scene &scene, const bNode &node, const bool use_meta_data) { blender::bke::cryptomatte::CryptomatteSessionPtr session; if (node.type != CMP_NODE_CRYPTOMATTE) { return session; } - NodeCryptomatte *node_cryptomatte = static_cast<NodeCryptomatte *>(node.storage); switch (node.custom1) { case CMP_CRYPTOMATTE_SRC_RENDER: { - Scene *scene = (Scene *)node.id; - if (!scene) { - return session; - } - BLI_assert(GS(scene->id.name) == ID_SCE); - - if (use_meta_data) { - Render *render = (scene) ? RE_GetSceneRender(scene) : nullptr; - RenderResult *render_result = render ? RE_AcquireResultRead(render) : nullptr; - if (render_result) { - session = blender::bke::cryptomatte::CryptomatteSessionPtr( - BKE_cryptomatte_init_from_render_result(render_result)); - } - if (render) { - RE_ReleaseResult(render); - } - } - - if (session == nullptr) { - session = blender::bke::cryptomatte::CryptomatteSessionPtr( - BKE_cryptomatte_init_from_scene(scene)); - } - - break; + return cryptomatte_init_from_node_render(node, use_meta_data); } case CMP_CRYPTOMATTE_SRC_IMAGE: { - Image *image = (Image *)node.id; - if (!image) { - break; - } - BLI_assert(GS(image->id.name) == ID_IM); - - ImageUser *iuser = &node_cryptomatte->iuser; - BKE_image_user_frame_calc(image, iuser, frame_number); - ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, nullptr); - RenderResult *render_result = image->rr; - if (render_result) { - session = blender::bke::cryptomatte::CryptomatteSessionPtr( - BKE_cryptomatte_init_from_render_result(render_result)); - } - BKE_image_release_ibuf(image, ibuf, nullptr); - break; + return cryptomatte_init_from_node_image(scene, node); } } return session; @@ -111,7 +124,10 @@ static CryptomatteEntry *cryptomatte_find(const NodeCryptomatte &n, float encode return nullptr; } -static void cryptomatte_add(bNode &node, NodeCryptomatte &node_cryptomatte, float encoded_hash) +static void cryptomatte_add(const Scene &scene, + bNode &node, + NodeCryptomatte &node_cryptomatte, + float encoded_hash) { /* Check if entry already exist. */ if (cryptomatte_find(node_cryptomatte, encoded_hash)) { @@ -121,9 +137,8 @@ static void cryptomatte_add(bNode &node, NodeCryptomatte &node_cryptomatte, floa CryptomatteEntry *entry = static_cast<CryptomatteEntry *>( MEM_callocN(sizeof(CryptomatteEntry), __func__)); entry->encoded_hash = encoded_hash; - /* TODO(jbakker): Get current frame from scene. */ blender::bke::cryptomatte::CryptomatteSessionPtr session = cryptomatte_init_from_node( - node, 0, true); + scene, node, true); if (session) { BKE_cryptomatte_find_name(session.get(), encoded_hash, entry->name, sizeof(entry->name)); } @@ -151,12 +166,12 @@ static bNodeSocketTemplate cmp_node_cryptomatte_out[] = { {-1, ""}, }; -void ntreeCompositCryptomatteSyncFromAdd(bNode *node) +void ntreeCompositCryptomatteSyncFromAdd(const Scene *scene, bNode *node) { BLI_assert(ELEM(node->type, CMP_NODE_CRYPTOMATTE, CMP_NODE_CRYPTOMATTE_LEGACY)); NodeCryptomatte *n = static_cast<NodeCryptomatte *>(node->storage); if (n->runtime.add[0] != 0.0f) { - cryptomatte_add(*node, *n, n->runtime.add[0]); + cryptomatte_add(*scene, *node, *n, n->runtime.add[0]); zero_v3(n->runtime.add); } } @@ -170,14 +185,14 @@ void ntreeCompositCryptomatteSyncFromRemove(bNode *node) zero_v3(n->runtime.remove); } } -void ntreeCompositCryptomatteUpdateLayerNames(bNode *node) +void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node) { BLI_assert(node->type == CMP_NODE_CRYPTOMATTE); NodeCryptomatte *n = static_cast<NodeCryptomatte *>(node->storage); BLI_freelistN(&n->runtime.layers); blender::bke::cryptomatte::CryptomatteSessionPtr session = cryptomatte_init_from_node( - *node, 0, false); + *scene, *node, false); if (session) { for (blender::StringRef layer_name : @@ -190,12 +205,15 @@ void ntreeCompositCryptomatteUpdateLayerNames(bNode *node) } } -void ntreeCompositCryptomatteLayerPrefix(const bNode *node, char *r_prefix, size_t prefix_len) +void ntreeCompositCryptomatteLayerPrefix(const Scene *scene, + const bNode *node, + char *r_prefix, + size_t prefix_len) { BLI_assert(node->type == CMP_NODE_CRYPTOMATTE); NodeCryptomatte *node_cryptomatte = (NodeCryptomatte *)node->storage; blender::bke::cryptomatte::CryptomatteSessionPtr session = cryptomatte_init_from_node( - *node, 0, false); + *scene, *node, false); std::string first_layer_name; if (session) { @@ -216,10 +234,10 @@ void ntreeCompositCryptomatteLayerPrefix(const bNode *node, char *r_prefix, size BLI_strncpy(r_prefix, cstr, prefix_len); } -CryptomatteSession *ntreeCompositCryptomatteSession(bNode *node) +CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node) { blender::bke::cryptomatte::CryptomatteSessionPtr session_ptr = cryptomatte_init_from_node( - *node, 0, true); + *scene, *node, true); return session_ptr.release(); } @@ -263,7 +281,9 @@ static void node_copy_cryptomatte(bNodeTree *UNUSED(dest_ntree), dest_node->storage = dest_nc; } -static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype), bNodeTree *ntree) +static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { if (STREQ(ntree->idname, "CompositorNodeTree")) { Scene *scene; @@ -276,8 +296,13 @@ static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype), bNodeTree *ntree) } } + if (scene == nullptr) { + *r_disabled_hint = + "The node tree must be the compositing node tree of any scene in the file"; + } return scene != nullptr; } + *r_disabled_hint = "Not a compositor node tree"; return false; } diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.c index 53ea02ff8a7..243300b0a44 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.c +++ b/source/blender/nodes/composite/nodes/node_composite_image.c @@ -526,24 +526,32 @@ static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) } } -static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), bNodeTree *ntree) +static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { - if (STREQ(ntree->idname, "CompositorNodeTree")) { - Scene *scene; + if (!STREQ(ntree->idname, "CompositorNodeTree")) { + *r_disabled_hint = "Not a compositor node tree"; + return false; + } - /* XXX ugly: check if ntree is a local scene node tree. - * Render layers node can only be used in local scene->nodetree, - * since it directly links to the scene. - */ - for (scene = G.main->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree == ntree) { - break; - } + Scene *scene; + + /* XXX ugly: check if ntree is a local scene node tree. + * Render layers node can only be used in local scene->nodetree, + * since it directly links to the scene. + */ + for (scene = G.main->scenes.first; scene; scene = scene->id.next) { + if (scene->nodetree == ntree) { + break; } + } - return (scene != NULL); + if (scene == NULL) { + *r_disabled_hint = "The node tree must be the compositing node tree of any scene in the file"; + return false; } - return false; + return true; } static void node_composit_free_rlayers(bNode *node) diff --git a/source/blender/nodes/function/node_function_util.cc b/source/blender/nodes/function/node_function_util.cc index 827572d1069..8ff8b416310 100644 --- a/source/blender/nodes/function/node_function_util.cc +++ b/source/blender/nodes/function/node_function_util.cc @@ -17,10 +17,16 @@ #include "node_function_util.hh" #include "node_util.h" -bool fn_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +static bool fn_node_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { /* Function nodes are only supported in simulation node trees so far. */ - return STREQ(ntree->idname, "GeometryNodeTree"); + if (!STREQ(ntree->idname, "GeometryNodeTree")) { + *r_disabled_hint = "Not a geometry node tree"; + return false; + } + return true; } void fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) diff --git a/source/blender/nodes/function/node_function_util.hh b/source/blender/nodes/function/node_function_util.hh index d57d1383019..9fbd6712827 100644 --- a/source/blender/nodes/function/node_function_util.hh +++ b/source/blender/nodes/function/node_function_util.hh @@ -38,4 +38,3 @@ void fn_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); -bool fn_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 2c4c7da64cc..f4cd00b88ed 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -67,6 +67,8 @@ static void geometry_node_tree_get_from_context(const bContext *C, static void geometry_node_tree_update(bNodeTree *ntree) { + ntreeSetOutput(ntree); + /* Needed to give correct types to reroutes. */ ntree_update_reroute_nodes(ntree); } @@ -82,6 +84,34 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa func(calldata, NODE_CLASS_LAYOUT, N_("Layout")); } +static bool geometry_node_tree_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link) +{ + /* Geometry, string, object, material, texture and collection sockets can only be connected to + * themselves. The other types can be converted between each other. */ + if (ELEM(link->fromsock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) && + ELEM(link->tosock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) { + return true; + } + return (link->tosock->type == link->fromsock->type); +} + +static bool geometry_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_INT, + SOCK_STRING, + SOCK_OBJECT, + SOCK_GEOMETRY, + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_MATERIAL); +} + void register_node_tree_type_geo(void) { bNodeTreeType *tt = ntreeType_Geometry = static_cast<bNodeTreeType *>( @@ -95,6 +125,8 @@ void register_node_tree_type_geo(void) tt->update = geometry_node_tree_update; tt->get_from_context = geometry_node_tree_get_from_context; tt->foreach_nodeclass = foreach_nodeclass; + tt->valid_socket_type = geometry_node_tree_socket_type_valid; + tt->validate_link = geometry_node_tree_validate_link; ntreeTypeAdd(tt); } diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index 0f725ecf211..93cada2982b 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -56,9 +56,15 @@ void update_attribute_input_socket_availabilities(bNode &node, } // namespace blender::nodes -bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool geo_node_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { - return STREQ(ntree->idname, "GeometryNodeTree"); + if (!STREQ(ntree->idname, "GeometryNodeTree")) { + *r_disabled_hint = "Not a geometry node tree"; + return false; + } + return true; } void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index fb80bd08797..79a98c5ebf0 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -36,7 +36,9 @@ void geo_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); -bool geo_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool geo_node_poll_default(struct bNodeType *ntype, + struct bNodeTree *ntree, + const char **r_disabled_hint); namespace blender::nodes { void update_attribute_input_socket_availabilities(bNode &node, @@ -58,4 +60,14 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top, const int verts_num, const GeometryNodeMeshCircleFillType fill_type); +Mesh *create_cube_mesh(const float size); + +/** + * Copies the point domain attributes from `in_component` that are in the mask to `out_component`. + */ +void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, + GeometryComponent &result_component, + Span<bool> masks, + const bool invert); + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index fca460e3566..d1b71d6f2ba 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -14,13 +14,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "BLI_math_rotation.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_align_rotation_to_vector_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Factor")}, @@ -50,91 +51,117 @@ static void geo_node_align_rotation_to_vector_layout(uiLayout *layout, namespace blender::nodes { -static void align_rotations_auto_pivot(const Float3ReadAttribute &vectors, - const FloatReadAttribute &factors, +static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); + + node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; + node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); +} + +static void align_rotations_auto_pivot(const VArray<float3> &vectors, + const VArray<float> &factors, const float3 local_main_axis, - MutableSpan<float3> rotations) + const MutableSpan<float3> rotations) { - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - const float3 new_axis = vector.normalized(); - float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); - if (is_zero_v3(rotation_axis)) { - /* The vectors are linearly dependent, so we fall back to another axis. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + const float3 new_axis = vector.normalized(); + float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); if (is_zero_v3(rotation_axis)) { - /* This is now guaranteed to not be zero. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + /* The vectors are linearly dependent, so we fall back to another axis. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + if (is_zero_v3(rotation_axis)) { + /* This is now guaranteed to not be zero. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + } } - } - const float full_angle = angle_normalized_v3v3(old_axis, new_axis); - const float angle = factors[i] * full_angle; + const float full_angle = angle_normalized_v3v3(old_axis, new_axis); + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, rotation_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, rotation_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } -static void align_rotations_fixed_pivot(const Float3ReadAttribute &vectors, - const FloatReadAttribute &factors, +static void align_rotations_fixed_pivot(const VArray<float3> &vectors, + const VArray<float> &factors, const float3 local_main_axis, const float3 local_pivot_axis, - MutableSpan<float3> rotations) + const MutableSpan<float3> rotations) { if (local_main_axis == local_pivot_axis) { /* Can't compute any meaningful rotation angle in this case. */ return; } - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - float3 pivot_axis; - mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); - - float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); - if (full_angle > M_PI) { - /* Make sure the point is rotated as little as possible. */ - full_angle -= 2.0f * M_PI; - } - const float angle = factors[i] * full_angle; + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float3 pivot_axis; + mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); + + float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); + if (full_angle > M_PI) { + /* Make sure the point is rotated as little as possible. */ + full_angle -= 2.0f * M_PI; + } + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, pivot_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, pivot_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } static void align_rotations_on_component(GeometryComponent &component, @@ -144,30 +171,30 @@ static void align_rotations_on_component(GeometryComponent &component, const NodeGeometryAlignRotationToVector &storage = *(const NodeGeometryAlignRotationToVector *) node.storage; - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - if (!rotation_attribute) { + OutputAttribute_Typed<float3> rotations = component.attribute_try_get_for_output<float3>( + "rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); + if (!rotations) { return; } - MutableSpan<float3> rotations = rotation_attribute->get_span<float3>(); - FloatReadAttribute factors = params.get_input_attribute<float>( + GVArray_Typed<float> factors = params.get_input_attribute<float>( "Factor", component, ATTR_DOMAIN_POINT, 1.0f); - Float3ReadAttribute vectors = params.get_input_attribute<float3>( + GVArray_Typed<float3> vectors = params.get_input_attribute<float3>( "Vector", component, ATTR_DOMAIN_POINT, {0, 0, 1}); float3 local_main_axis{0, 0, 0}; local_main_axis[storage.axis] = 1; if (storage.pivot_axis == GEO_NODE_ALIGN_ROTATION_TO_VECTOR_PIVOT_AXIS_AUTO) { - align_rotations_auto_pivot(vectors, factors, local_main_axis, rotations); + align_rotations_auto_pivot(vectors, factors, local_main_axis, rotations.as_span()); } else { float3 local_pivot_axis{0, 0, 0}; local_pivot_axis[storage.pivot_axis - 1] = 1; - align_rotations_fixed_pivot(vectors, factors, local_main_axis, local_pivot_axis, rotations); + align_rotations_fixed_pivot( + vectors, factors, local_main_axis, local_pivot_axis, rotations.as_span()); } - rotation_attribute.apply_span_and_save(); + rotations.save(); } static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) @@ -183,32 +210,13 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) align_rotations_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + align_rotations_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); - - node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; - node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); -} - } // namespace blender::nodes void register_node_type_geo_align_rotation_to_vector() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc new file mode 100644 index 00000000000..71643df1cb6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc @@ -0,0 +1,284 @@ + +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_clamp_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {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_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -100000, 100000}, + {SOCK_INT, N_("Max"), 100.0f, 0.0f, 0.0f, 0.0f, -100000, 100000}, + {SOCK_RGBA, N_("Min"), 0.5, 0.5, 0.5, 1.0}, + {SOCK_RGBA, N_("Max"), 0.5, 0.5, 0.5, 1.0}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_clamp_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_clamp_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); +} + +static void geo_node_attribute_clamp_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeAttributeClamp *data = (NodeAttributeClamp *)MEM_callocN(sizeof(NodeAttributeClamp), + __func__); + data->data_type = CD_PROP_FLOAT; + data->operation = NODE_CLAMP_MINMAX; + node->storage = data; +} + +static void geo_node_attribute_clamp_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 3); + bNodeSocket *sock_max_vector = sock_min_vector->next; + bNodeSocket *sock_min_float = sock_max_vector->next; + bNodeSocket *sock_max_float = sock_min_float->next; + bNodeSocket *sock_min_int = sock_max_float->next; + bNodeSocket *sock_max_int = sock_min_int->next; + bNodeSocket *sock_min_color = sock_max_int->next; + bNodeSocket *sock_max_color = sock_min_color->next; + + const NodeAttributeClamp &storage = *(const NodeAttributeClamp *)node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + 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); + nodeSetSocketAvailability(sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_max_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_min_color, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(sock_max_color, data_type == CD_PROP_COLOR); +} + +namespace blender::nodes { + +template<typename T> T clamp_value(const T val, const T min, const T max); + +template<> inline float clamp_value(const float val, const float min, const float max) +{ + return std::min(std::max(val, min), max); +} + +template<> inline int clamp_value(const int val, const int min, const int max) +{ + return std::min(std::max(val, min), max); +} + +template<> inline float3 clamp_value(const float3 val, const float3 min, const float3 max) +{ + float3 tmp; + tmp.x = std::min(std::max(val.x, min.x), max.x); + tmp.y = std::min(std::max(val.y, min.y), max.y); + tmp.z = std::min(std::max(val.z, min.z), max.z); + return tmp; +} + +template<> +inline ColorGeometry4f clamp_value(const ColorGeometry4f val, + const ColorGeometry4f min, + const ColorGeometry4f max) +{ + ColorGeometry4f tmp; + tmp.r = std::min(std::max(val.r, min.r), max.r); + tmp.g = std::min(std::max(val.g, min.g), max.g); + tmp.b = std::min(std::max(val.b, min.b), max.b); + tmp.a = std::min(std::max(val.a, min.a), max.a); + return tmp; +} + +template<typename T> +static void clamp_attribute(const VArray<T> &inputs, + const MutableSpan<T> outputs, + const T min, + const T max) +{ + for (const int i : IndexRange(outputs.size())) { + outputs[i] = clamp_value<T>(inputs[i], min, max); + } +} + +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef source_name, + StringRef result_name) +{ + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; + } + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return source_info->domain; + } + return ATTR_DOMAIN_POINT; +} + +static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const std::string attribute_name = params.get_input<std::string>("Attribute"); + const std::string result_name = params.get_input<std::string>("Result"); + + if (attribute_name.empty() || result_name.empty()) { + return; + } + + if (!component.attribute_exists(attribute_name)) { + params.error_message_add(NodeWarningType::Error, + TIP_("No attribute with name \"") + attribute_name + "\""); + return; + } + + const NodeAttributeClamp &storage = *(const NodeAttributeClamp *)params.node().storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const AttributeDomain domain = get_result_domain(component, attribute_name, result_name); + const int operation = static_cast<int>(storage.operation); + + GVArrayPtr attribute_input = component.attribute_try_get_for_read( + attribute_name, domain, data_type); + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, domain, data_type); + + if (!attribute_result) { + params.error_message_add(NodeWarningType::Error, + TIP_("Could not find or create attribute with name \"") + + result_name + "\""); + return; + } + + switch (data_type) { + case CD_PROP_FLOAT3: { + float3 min = params.get_input<float3>("Min"); + float3 max = params.get_input<float3>("Max"); + if (operation == NODE_CLAMP_RANGE) { + if (min.x > max.x) { + std::swap(min.x, max.x); + } + if (min.y > max.y) { + std::swap(min.y, max.y); + } + if (min.z > max.z) { + std::swap(min.z, max.z); + } + } + MutableSpan<float3> results = attribute_result.as_span<float3>(); + clamp_attribute<float3>(attribute_input->typed<float3>(), results, min, max); + break; + } + case CD_PROP_FLOAT: { + const float min = params.get_input<float>("Min_001"); + const float max = params.get_input<float>("Max_001"); + MutableSpan<float> results = attribute_result.as_span<float>(); + if (operation == NODE_CLAMP_RANGE && min > max) { + clamp_attribute<float>(attribute_input->typed<float>(), results, max, min); + } + else { + clamp_attribute<float>(attribute_input->typed<float>(), results, min, max); + } + break; + } + case CD_PROP_INT32: { + const int min = params.get_input<int>("Min_002"); + const int max = params.get_input<int>("Max_002"); + MutableSpan<int> results = attribute_result.as_span<int>(); + if (operation == NODE_CLAMP_RANGE && min > max) { + clamp_attribute<int>(attribute_input->typed<int>(), results, max, min); + } + else { + clamp_attribute<int>(attribute_input->typed<int>(), results, min, max); + } + break; + } + case CD_PROP_COLOR: { + ColorGeometry4f min = params.get_input<ColorGeometry4f>("Min_003"); + ColorGeometry4f max = params.get_input<ColorGeometry4f>("Max_003"); + if (operation == NODE_CLAMP_RANGE) { + if (min.r > max.r) { + std::swap(min.r, max.r); + } + if (min.g > max.g) { + std::swap(min.g, max.g); + } + if (min.b > max.b) { + std::swap(min.b, max.b); + } + if (min.a > max.a) { + std::swap(min.a, max.a); + } + } + MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); + clamp_attribute<ColorGeometry4f>( + attribute_input->typed<ColorGeometry4f>(), results, min, max); + break; + } + default: { + BLI_assert(false); + break; + } + } + + attribute_result.save(); +} + +static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + clamp_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + clamp_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + if (geometry_set.has<CurveComponent>()) { + clamp_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_clamp() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates(&ntype, geo_node_attribute_clamp_in, geo_node_attribute_clamp_out); + node_type_init(&ntype, geo_node_attribute_clamp_init); + node_type_update(&ntype, geo_node_attribute_clamp_update); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_clamp_exec; + ntype.draw_buttons = geo_node_attribute_clamp_layout; + node_type_storage( + &ntype, "NodeAttributeClamp", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index 98bf612f589..5293dd8c876 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -14,13 +14,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" +#include "BLI_task.hh" #include "BKE_colorband.h" #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_color_ramp_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Attribute")}, @@ -42,20 +44,28 @@ static void geo_node_attribute_color_ramp_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( + sizeof(NodeAttributeColorRamp), __func__); + BKE_colorband_init(&node_storage->color_ramp, true); + node->storage = node_storage; +} + static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef input_name, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the input attribute's domain if it exists. */ - ReadAttributePtr input_attribute = component.attribute_try_get_for_read(input_name); - if (input_attribute) { - return input_attribute->domain(); + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(input_name); + if (source_info) { + return source_info->domain; } return ATTR_DOMAIN_POINT; @@ -71,27 +81,27 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon /* Always output a color attribute for now. We might want to allow users to customize. * Using the type of an existing attribute could work, but does not have a real benefit * currently. */ - const CustomDataType result_type = CD_PROP_COLOR; const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<ColorGeometry4f> attribute_result = + component.attribute_try_get_for_output_only<ColorGeometry4f>(result_name, result_domain); if (!attribute_result) { return; } - FloatReadAttribute attribute_in = component.attribute_get_for_read<float>( + GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, 0.0f); - Span<float> data_in = attribute_in.get_span(); - MutableSpan<Color4f> data_out = attribute_result->get_span_for_write_only<Color4f>(); + MutableSpan<ColorGeometry4f> results = attribute_result.as_span(); ColorBand *color_ramp = &node_storage->color_ramp; - for (const int i : data_in.index_range()) { - BKE_colorband_evaluate(color_ramp, data_in[i], data_out[i]); - } + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_colorband_evaluate(color_ramp, attribute_in[i], results[i]); + } + }); - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) @@ -106,18 +116,13 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( - sizeof(NodeAttributeColorRamp), __func__); - BKE_colorband_init(&node_storage->color_ramp, true); - node->storage = node_storage; -} - } // namespace blender::nodes void register_node_type_geo_attribute_color_ramp() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index 9c5c7e270b1..d8c52d16f41 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -14,11 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_combine_xyz_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("X")}, @@ -77,9 +77,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -94,27 +94,25 @@ static void combine_attributes(GeometryComponent &component, const GeoNodeExecPa } const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> attribute_result = + component.attribute_try_get_for_output_only<float3>(result_name, result_domain); if (!attribute_result) { return; } - FloatReadAttribute attribute_x = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_x = params.get_input_attribute<float>( "X", component, result_domain, 0.0f); - FloatReadAttribute attribute_y = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_y = params.get_input_attribute<float>( "Y", component, result_domain, 0.0f); - FloatReadAttribute attribute_z = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_z = params.get_input_attribute<float>( "Z", component, result_domain, 0.0f); - MutableSpan<float3> results = attribute_result->get_span_for_write_only<float3>(); - for (const int i : results.index_range()) { + for (const int i : IndexRange(attribute_result->size())) { const float x = attribute_x[i]; const float y = attribute_y[i]; const float z = attribute_z[i]; - const float3 result = float3(x, y, z); - results[i] = result; + attribute_result->set(i, {x, y, z}); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) @@ -129,6 +127,9 @@ static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { combine_attributes(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + combine_attributes(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index db1a5d18744..57ac68b4cd4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -14,23 +14,13 @@ * 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 "UI_interface.h" #include "UI_resources.h" #include "NOD_math_functions.hh" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_compare_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("A")}, @@ -91,21 +81,18 @@ static void geo_node_attribute_compare_update(bNodeTree *UNUSED(ntree), bNode *n nodeSetSocketAvailability(socket_threshold, operation_tests_equality(*node_storage)); } -static void do_math_operation(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_math_operation(const VArray<float> &input_a, + const VArray<float> &input_b, const FloatCompareOperation operation, MutableSpan<bool> span_result) { const int size = input_a.size(); - Span<float> span_a = input_a.get_span(); - Span<float> span_b = input_b.get_span(); - if (try_dispatch_float_math_fl_fl_to_bool( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { for (const int i : IndexRange(size)) { - const float a = span_a[i]; - const float b = span_b[i]; + const float a = input_a[i]; + const float b = input_b[i]; const bool out = math_function(a, b); span_result[i] = out; } @@ -117,8 +104,8 @@ static void do_math_operation(const FloatReadAttribute &input_a, BLI_assert(false); } -static void do_equal_operation_float(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_equal_operation_float(const VArray<float> &input_a, + const VArray<float> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -130,8 +117,8 @@ static void do_equal_operation_float(const FloatReadAttribute &input_a, } } -static void do_equal_operation_float3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, +static void do_equal_operation_float3(const VArray<float3> &input_a, + const VArray<float3> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -144,22 +131,22 @@ static void do_equal_operation_float3(const Float3ReadAttribute &input_a, } } -static void do_equal_operation_color4f(const Color4fReadAttribute &input_a, - const Color4fReadAttribute &input_b, +static void do_equal_operation_color4f(const VArray<ColorGeometry4f> &input_a, + const VArray<ColorGeometry4f> &input_b, const float threshold, MutableSpan<bool> span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { - const Color4f a = input_a[i]; - const Color4f b = input_b[i]; + const ColorGeometry4f a = input_a[i]; + const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) < threshold_squared; } } -static void do_equal_operation_bool(const BooleanReadAttribute &input_a, - const BooleanReadAttribute &input_b, +static void do_equal_operation_bool(const VArray<bool> &input_a, + const VArray<bool> &input_b, const float UNUSED(threshold), MutableSpan<bool> span_result) { @@ -171,8 +158,8 @@ static void do_equal_operation_bool(const BooleanReadAttribute &input_a, } } -static void do_not_equal_operation_float(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_not_equal_operation_float(const VArray<float> &input_a, + const VArray<float> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -184,8 +171,8 @@ static void do_not_equal_operation_float(const FloatReadAttribute &input_a, } } -static void do_not_equal_operation_float3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, +static void do_not_equal_operation_float3(const VArray<float3> &input_a, + const VArray<float3> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -198,22 +185,22 @@ static void do_not_equal_operation_float3(const Float3ReadAttribute &input_a, } } -static void do_not_equal_operation_color4f(const Color4fReadAttribute &input_a, - const Color4fReadAttribute &input_b, +static void do_not_equal_operation_color4f(const VArray<ColorGeometry4f> &input_a, + const VArray<ColorGeometry4f> &input_b, const float threshold, MutableSpan<bool> span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { - const Color4f a = input_a[i]; - const Color4f b = input_b[i]; + const ColorGeometry4f a = input_a[i]; + const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) >= threshold_squared; } } -static void do_not_equal_operation_bool(const BooleanReadAttribute &input_a, - const BooleanReadAttribute &input_b, +static void do_not_equal_operation_bool(const VArray<bool> &input_a, + const VArray<bool> &input_b, const float UNUSED(threshold), MutableSpan<bool> span_result) { @@ -247,9 +234,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -264,20 +251,19 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx node_storage->operation); const std::string result_name = params.get_input<std::string>("Result"); - const CustomDataType result_type = CD_PROP_BOOL; const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<bool> attribute_result = component.attribute_try_get_for_output_only<bool>( + result_name, result_domain); if (!attribute_result) { return; } const CustomDataType input_data_type = get_data_type(component, params, *node_storage); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, input_data_type, nullptr); - ReadAttributePtr attribute_b = params.get_input_attribute( + GVArrayPtr attribute_b = params.get_input_attribute( "B", component, result_domain, input_data_type, nullptr); if (!attribute_a || !attribute_b) { @@ -285,7 +271,7 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx return; } - MutableSpan<bool> result_span = attribute_result->get_span_for_write_only<bool>(); + MutableSpan<bool> result_span = attribute_result.as_span(); /* Use specific types for correct equality operations, but for other operations we use implicit * conversions and float comparison. In other words, the comparison is not element-wise. */ @@ -293,38 +279,51 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx const float threshold = params.get_input<float>("Threshold"); if (operation == NODE_FLOAT_COMPARE_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { - do_equal_operation_float(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_float( + attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { - do_equal_operation_float3(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_float3( + attribute_a->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { - do_equal_operation_color4f(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_color4f(attribute_a->typed<ColorGeometry4f>(), + attribute_b->typed<ColorGeometry4f>(), + threshold, + result_span); } else if (input_data_type == CD_PROP_BOOL) { - do_equal_operation_bool(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_bool( + attribute_a->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); } } else if (operation == NODE_FLOAT_COMPARE_NOT_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { - do_not_equal_operation_float(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_float( + attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { - do_not_equal_operation_float3(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_float3( + attribute_a->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { - do_not_equal_operation_color4f(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_color4f(attribute_a->typed<ColorGeometry4f>(), + attribute_b->typed<ColorGeometry4f>(), + threshold, + result_span); } else if (input_data_type == CD_PROP_BOOL) { - do_not_equal_operation_bool(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_bool( + attribute_a->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); } } } else { - do_math_operation(*attribute_a, *attribute_b, operation, result_span); + do_math_operation( + attribute_a->typed<float>(), attribute_b->typed<float>(), operation, result_span); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_compare_exec(GeoNodeExecParams params) @@ -339,6 +338,9 @@ static void geo_node_attribute_compare_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_compare_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_compare_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc index 11d220dd903..7b40456b180 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc @@ -14,11 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_convert_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Attribute")}, @@ -35,8 +35,10 @@ static void geo_node_attribute_convert_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); - uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, nullptr, ICON_NONE); + uiItemR(layout, ptr, "data_type", 0, IFACE_("Type"), ICON_NONE); } static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node) @@ -44,41 +46,69 @@ static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node NodeAttributeConvert *data = (NodeAttributeConvert *)MEM_callocN(sizeof(NodeAttributeConvert), __func__); - data->data_type = CD_PROP_FLOAT; + data->data_type = CD_AUTO_FROM_NAME; data->domain = ATTR_DOMAIN_AUTO; node->storage = data; } namespace blender::nodes { -static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef source_name, - StringRef result_name) +static AttributeMetaData get_result_domain_and_type(const GeometryComponent &component, + const StringRef source_name, + const StringRef result_name) +{ + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return *result_info; + } + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return *source_info; + } + /* The node won't do anything in this case, but we still have to return a value. */ + return AttributeMetaData{ATTR_DOMAIN_POINT, CD_PROP_BOOL}; +} + +static bool conversion_can_be_skipped(const GeometryComponent &component, + const StringRef source_name, + const StringRef result_name, + const AttributeDomain result_domain, + const CustomDataType result_type) { - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + if (source_name != result_name) { + return false; } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read(source_name); - if (source_attribute) { - return source_attribute->domain(); + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(result_name); + if (!info) { + return false; } - return ATTR_DOMAIN_POINT; + if (info->domain != result_domain) { + return false; + } + if (info->data_type != result_type) { + return false; + } + return true; } static void attribute_convert_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms, const StringRef source_name, const StringRef result_name, - const CustomDataType result_type, + const CustomDataType data_type, const AttributeDomain domain) { - const AttributeDomain result_domain = (domain == ATTR_DOMAIN_AUTO) ? - get_result_domain( - component, source_name, result_name) : - domain; + const AttributeMetaData auto_info = get_result_domain_and_type( + component, source_name, result_name); + const AttributeDomain result_domain = (domain == ATTR_DOMAIN_AUTO) ? auto_info.domain : domain; + const CustomDataType result_type = (data_type == CD_AUTO_FROM_NAME) ? auto_info.data_type : + data_type; + + if (conversion_can_be_skipped(component, source_name, result_name, result_domain, result_type)) { + return; + } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read( + GVArrayPtr source_attribute = component.attribute_try_get_for_read( source_name, result_domain, result_type); if (!source_attribute) { params.error_message_add(NodeWarningType::Error, @@ -86,25 +116,22 @@ static void attribute_convert_calc(GeometryComponent &component, return; } - OutputAttributePtr result_attribute = component.attribute_try_get_for_output( + OutputAttribute result_attribute = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!result_attribute) { return; } - fn::GSpan source_span = source_attribute->get_span(); - fn::GMutableSpan result_span = result_attribute->get_span_for_write_only(); - if (source_span.is_empty() || result_span.is_empty()) { - return; - } + GVArray_GSpan source_span{*source_attribute}; + GMutableSpan result_span = result_attribute.as_span(); + BLI_assert(source_span.size() == result_span.size()); const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(result_type); BLI_assert(cpp_type != nullptr); cpp_type->copy_to_initialized_n(source_span.data(), result_span.data(), result_span.size()); - - result_attribute.apply_span_and_save(); + result_attribute.save(); } static void geo_node_attribute_convert_exec(GeoNodeExecParams params) @@ -140,6 +167,14 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params) data_type, domain); } + if (geometry_set.has<CurveComponent>()) { + attribute_convert_calc(geometry_set.get_component_for_write<CurveComponent>(), + params, + source_name, + result_name, + data_type, + domain); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc new file mode 100644 index 00000000000..599c9e58e52 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -0,0 +1,232 @@ +/* + * 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_blenlib.h" +#include "BLI_task.hh" + +#include "BKE_colortools.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_curve_map_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_curve_map_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_curve_map_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + bNode *node = (bNode *)ptr->data; + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + switch (data->data_type) { + case CD_PROP_FLOAT: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 0, false, false, false, false); + break; + case CD_PROP_FLOAT3: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 'v', false, false, false, false); + break; + case CD_PROP_COLOR: + uiTemplateCurveMapping(layout, ptr, "curve_rgb", 'c', false, false, false, false); + break; + } +} + +static void geo_node_attribute_curve_map_free_storage(bNode *node) +{ + if (node->storage) { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_free(data->curve_vec); + BKE_curvemapping_free(data->curve_rgb); + MEM_freeN(node->storage); + } +} + +static void geo_node_attribute_curve_map_copy_storage(bNodeTree *UNUSED(dest_ntree), + bNode *dest_node, + const bNode *src_node) +{ + dest_node->storage = MEM_dupallocN(src_node->storage); + NodeAttributeCurveMap *src_data = (NodeAttributeCurveMap *)src_node->storage; + NodeAttributeCurveMap *dest_data = (NodeAttributeCurveMap *)dest_node->storage; + dest_data->curve_vec = BKE_curvemapping_copy(src_data->curve_vec); + dest_data->curve_rgb = BKE_curvemapping_copy(src_data->curve_rgb); +} + +static void geo_node_attribute_curve_map_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)MEM_callocN(sizeof(NodeAttributeCurveMap), + __func__); + + data->data_type = CD_PROP_FLOAT; + data->curve_vec = BKE_curvemapping_add(4, -1.0f, -1.0f, 1.0f, 1.0f); + data->curve_vec->cur = 3; + data->curve_rgb = BKE_curvemapping_add(4, 0.0f, 0.0f, 1.0f, 1.0f); + node->storage = data; +} + +static void geo_node_attribute_curve_map_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + /* Set the active curve when data type is changed. */ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + if (data->data_type == CD_PROP_FLOAT) { + data->curve_vec->cur = 3; + } + else if (data->data_type == CD_PROP_FLOAT3) { + data->curve_vec->cur = 0; + } +} + +namespace blender::nodes { + +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef input_name, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributeLookup result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute.domain; + } + + /* Otherwise use the input attribute's domain if it exists. */ + ReadAttributeLookup input_attribute = component.attribute_try_get_for_read(input_name); + if (input_attribute) { + return input_attribute.domain; + } + + return ATTR_DOMAIN_POINT; +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap &node_storage = *(NodeAttributeCurveMap *)bnode.storage; + const std::string result_name = params.get_input<std::string>("Result"); + const std::string input_name = params.get_input<std::string>("Attribute"); + + const CustomDataType result_type = (CustomDataType)node_storage.data_type; + const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + switch (result_type) { + case CD_PROP_FLOAT: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( + input_name, result_domain, float(0.0f)); + MutableSpan<float> results = attribute_result.as_span<float>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + results[i] = BKE_curvemapping_evaluateF(cumap, 3, attribute_in[i]); + } + }); + break; + } + case CD_PROP_FLOAT3: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float3> attribute_in = component.attribute_get_for_read<float3>( + input_name, result_domain, float3(0.0f)); + MutableSpan<float3> results = attribute_result.as_span<float3>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluate3F(cumap, results[i], attribute_in[i]); + } + }); + break; + } + case CD_PROP_COLOR: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_rgb; + GVArray_Typed<ColorGeometry4f> attribute_in = + component.attribute_get_for_read<ColorGeometry4f>( + input_name, result_domain, ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f)); + MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluateRGBF(cumap, results[i], attribute_in[i]); + } + }); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + + attribute_result.save(); +} + +static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)bnode.storage; + BKE_curvemapping_init(data->curve_vec); + BKE_curvemapping_init(data->curve_rgb); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_curve_map() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_curve_map_in, geo_node_attribute_curve_map_out); + node_type_update(&ntype, geo_node_attribute_curve_map_update); + node_type_init(&ntype, geo_node_attribute_curve_map_init); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_storage(&ntype, + "NodeAttributeCurveMap", + geo_node_attribute_curve_map_free_storage, + geo_node_attribute_curve_map_copy_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_curve_map_exec; + ntype.draw_buttons = geo_node_attribute_curve_map_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index cbe44c54fb4..51fd65f65fd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -27,6 +27,11 @@ #include "WM_types.h" +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_fill_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Attribute")}, @@ -188,13 +193,12 @@ static void geo_node_attribute_fill_layout(uiLayout *layout, bContext *C, Pointe draw_input_socket(C, layout, node_ptr, "Value", "data_type"); } -static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef attribute_name) +static AttributeDomain get_result_domain(const GeometryComponent &component, const StringRef name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(name); + if (result_info) { + return result_info->domain; } return ATTR_DOMAIN_POINT; } @@ -213,7 +217,7 @@ static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams get_result_domain(component, attribute_name) : domain; - OutputAttributePtr attribute = component.attribute_try_get_for_output( + OutputAttribute attribute = component.attribute_try_get_for_output_only( attribute_name, result_domain, data_type); if (!attribute) { return; @@ -222,38 +226,34 @@ static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams switch (data_type) { case CD_PROP_FLOAT: { const float value = params.get_input<float>("Value_001"); - MutableSpan<float> attribute_span = attribute->get_span_for_write_only<float>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_FLOAT3: { const float3 value = params.get_input<float3>("Value"); - MutableSpan<float3> attribute_span = attribute->get_span_for_write_only<float3>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_COLOR: { - const Color4f value = params.get_input<Color4f>("Value_002"); - MutableSpan<Color4f> attribute_span = attribute->get_span_for_write_only<Color4f>(); - attribute_span.fill(value); + const ColorGeometry4f value = params.get_input<ColorGeometry4f>("Value_002"); + attribute->fill(&value); break; } case CD_PROP_BOOL: { const bool value = params.get_input<bool>("Value_003"); - MutableSpan<bool> attribute_span = attribute->get_span_for_write_only<bool>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_INT32: { const int value = params.get_input<int>("Value_004"); - MutableSpan<int> attribute_span = attribute->get_span_for_write_only<int>(); - attribute_span.fill(value); + attribute->fill(&value); + break; } default: break; } - attribute.apply_span_and_save(); + attribute.save(); } static void geo_node_attribute_fill_exec(GeoNodeExecParams params) @@ -268,6 +268,9 @@ static void geo_node_attribute_fill_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { fill_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + fill_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc new file mode 100644 index 00000000000..40fe675bd6c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -0,0 +1,437 @@ +/* + * 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_safe.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_map_range_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {SOCK_FLOAT, N_("From Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("From Max"), 1.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("To Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("To Max"), 1.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Steps"), 4.0f, 4.0f, 4.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("From Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("From Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("To Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("To Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("Steps"), 4.0f, 4.0f, 4.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_BOOLEAN, N_("Clamp")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_map_range_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void fn_attribute_map_range_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "interpolation_type", 0, "", ICON_NONE); +} + +static void geo_node_attribute_map_range_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMapRange *data = (NodeAttributeMapRange *)MEM_callocN(sizeof(NodeAttributeMapRange), + __func__); + data->data_type = CD_PROP_FLOAT; + data->interpolation_type = NODE_MAP_RANGE_LINEAR; + + node->storage = data; +} +static void geo_node_attribute_map_range_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMapRange &node_storage = *(NodeAttributeMapRange *)node->storage; + + bNodeSocket *sock_from_min_float = (bNodeSocket *)BLI_findlink(&node->inputs, 3); + bNodeSocket *sock_from_max_float = sock_from_min_float->next; + bNodeSocket *sock_to_min_float = sock_from_max_float->next; + bNodeSocket *sock_to_max_float = sock_to_min_float->next; + bNodeSocket *sock_steps_float = sock_to_max_float->next; + + bNodeSocket *sock_from_min_vector = sock_steps_float->next; + bNodeSocket *sock_from_max_vector = sock_from_min_vector->next; + bNodeSocket *sock_to_min_vector = sock_from_max_vector->next; + bNodeSocket *sock_to_max_vector = sock_to_min_vector->next; + bNodeSocket *sock_steps_vector = sock_to_max_vector->next; + + bNodeSocket *sock_clamp = sock_steps_vector->next; + + const CustomDataType data_type = static_cast<CustomDataType>(node_storage.data_type); + + nodeSetSocketAvailability(sock_clamp, + node_storage.interpolation_type == NODE_MAP_RANGE_LINEAR || + node_storage.interpolation_type == NODE_MAP_RANGE_STEPPED); + + nodeSetSocketAvailability(sock_from_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_from_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_to_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_to_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_steps_float, + data_type == CD_PROP_FLOAT && + node_storage.interpolation_type == NODE_MAP_RANGE_STEPPED); + + nodeSetSocketAvailability(sock_from_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_from_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_to_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_to_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_steps_vector, + data_type == CD_PROP_FLOAT3 && + node_storage.interpolation_type == NODE_MAP_RANGE_STEPPED); +} + +namespace blender::nodes { + +static float map_linear(const float value, + const float min_from, + const float max_from, + const float min_to, + const float max_to) +{ + /* First we calculate a fraction that measures how far along + * the [min_from, max_from] interval the value lies. + * + * value + * min_from [------>|------------------------] max_from + * factor (e.g. 0.25) + * + * Then to find where the value is mapped, we add the same fraction + * of the [min_to, max_to] interval to min_to. + * + * min_to [--->|-----------] max_to + * v + * min_to + (max_to - min_to) * factor + */ + const float factor = safe_divide(value - min_from, max_from - min_from); + return min_to + factor * (max_to - min_to); +} + +static float map_stepped(const float value, + const float min_from, + const float max_from, + const float min_to, + const float max_to, + const float steps) +{ + /* First the factor is calculated here in the same way as for the linear mapping. + * + * Then the factor is mapped to multiples of 1.0 / steps. + * This is best understood with a few examples. Assume steps == 3. + * ____________________________________ + * | factor | * 4.0 | floor() | / 3.0 | + * |--------|-------|---------|-------| + * | 0.0 | 0.0 | 0.0 | 0.0 | + * | 0.1 | 0.4 | 0.0 | 0.0 | + * | 0.25 | 1.0 | 1.0 | 0.333 | + * | 0.45 | 1.8 | 1.0 | 0.333 | + * | 0.5 | 2.0 | 2.0 | 0.666 | + * | 0.55 | 2.2 | 2.0 | 0.666 | + * | 0.999 | 3.999 | 3.0 | 1.0 | + * | 1.0 | 4.0 | 4.0 | 1.333 | + * ------------------------------------ + * Note that the factor is not always mapped the closest multiple of 1.0 /steps. + */ + const float factor = safe_divide(value - min_from, max_from - min_from); + const float factor_mapped = safe_divide(floorf(factor * (steps + 1.0f)), steps); + return min_to + factor_mapped * (max_to - min_to); +} + +static float smoothstep_polynomial(float x) +{ + /* This polynomial is only meant to be used for the [0, 1] range. */ + return (3.0f - 2.0f * x) * (x * x); +} + +static float map_smoothstep(const float value, + const float min_from, + const float max_from, + const float min_to, + const float max_to) +{ + const float factor = safe_divide(value - min_from, max_from - min_from); + const float factor_clamped = std::clamp(factor, 0.0f, 1.0f); + const float factor_mapped = smoothstep_polynomial(factor_clamped); + return min_to + factor_mapped * (max_to - min_to); +} + +static float smootherstep_polynomial(float x) +{ + /* This polynomial is only meant to be used for the [0, 1] range. */ + return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); +} + +static float map_smootherstep(const float value, + const float min_from, + const float max_from, + const float min_to, + const float max_to) +{ + const float factor = safe_divide(value - min_from, max_from - min_from); + const float factor_clamped = std::clamp(factor, 0.0f, 1.0f); + const float factor_mapped = smootherstep_polynomial(factor_clamped); + return min_to + factor_mapped * (max_to - min_to); +} + +static void map_range_float(const VArray<float> &attribute_input, + MutableSpan<float> results, + const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + NodeAttributeMapRange &node_storage = *(NodeAttributeMapRange *)node.storage; + const int interpolation_type = node_storage.interpolation_type; + const float min_from = params.get_input<float>("From Min"); + const float max_from = params.get_input<float>("From Max"); + const float min_to = params.get_input<float>("To Min"); + const float max_to = params.get_input<float>("To Max"); + + VArray_Span<float> span{attribute_input}; + + switch (interpolation_type) { + case NODE_MAP_RANGE_LINEAR: { + parallel_for(span.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_linear(span[i], min_from, max_from, min_to, max_to); + } + }); + break; + } + case NODE_MAP_RANGE_STEPPED: { + const float steps = params.get_input<float>("Steps"); + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); + } + }); + break; + } + case NODE_MAP_RANGE_SMOOTHSTEP: { + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); + } + }); + break; + } + case NODE_MAP_RANGE_SMOOTHERSTEP: { + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); + } + }); + break; + } + } + + if (ELEM(interpolation_type, NODE_MAP_RANGE_LINEAR, NODE_MAP_RANGE_STEPPED) && + params.get_input<bool>("Clamp")) { + /* Users can specify min_to > max_to, but clamping expects min < max. */ + const float clamp_min = min_to < max_to ? min_to : max_to; + const float clamp_max = min_to < max_to ? max_to : min_to; + + parallel_for(results.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = std::clamp(results[i], clamp_min, clamp_max); + } + }); + } +} + +static void map_range_float3(const VArray<float3> &attribute_input, + const MutableSpan<float3> results, + const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + NodeAttributeMapRange &node_storage = *(NodeAttributeMapRange *)node.storage; + const int interpolation_type = node_storage.interpolation_type; + const float3 min_from = params.get_input<float3>("From Min_001"); + const float3 max_from = params.get_input<float3>("From Max_001"); + const float3 min_to = params.get_input<float3>("To Min_001"); + const float3 max_to = params.get_input<float3>("To Max_001"); + + VArray_Span<float3> span{attribute_input}; + + switch (interpolation_type) { + case NODE_MAP_RANGE_LINEAR: { + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_linear(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); + break; + } + case NODE_MAP_RANGE_STEPPED: { + const float3 steps = params.get_input<float3>("Steps_001"); + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_stepped( + span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); + results[i].y = map_stepped( + span[i].y, min_from.y, max_from.y, min_to.y, max_to.y, steps.y); + results[i].z = map_stepped( + span[i].z, min_from.z, max_from.z, min_to.z, max_to.z, steps.z); + } + }); + break; + } + case NODE_MAP_RANGE_SMOOTHSTEP: { + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smoothstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); + break; + } + case NODE_MAP_RANGE_SMOOTHERSTEP: { + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smootherstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); + break; + } + } + + if (ELEM(interpolation_type, NODE_MAP_RANGE_LINEAR, NODE_MAP_RANGE_STEPPED) && + params.get_input<bool>("Clamp")) { + /* Users can specify min_to > max_to, but clamping expects min < max. */ + float3 clamp_min; + float3 clamp_max; + clamp_min.x = min_to.x < max_to.x ? min_to.x : max_to.x; + clamp_max.x = min_to.x < max_to.x ? max_to.x : min_to.x; + clamp_min.y = min_to.y < max_to.y ? min_to.y : max_to.y; + clamp_max.y = min_to.y < max_to.y ? max_to.y : min_to.y; + clamp_min.z = min_to.z < max_to.z ? min_to.z : max_to.z; + clamp_max.z = min_to.z < max_to.z ? max_to.z : min_to.z; + + for (int i : results.index_range()) { + clamp_v3_v3v3(results[i], clamp_min, clamp_max); + } + } +} + +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef source_name, + StringRef result_name) +{ + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; + } + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return source_info->domain; + } + return ATTR_DOMAIN_POINT; +} + +static void map_range_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const std::string input_name = params.get_input<std::string>("Attribute"); + const std::string result_name = params.get_input<std::string>("Result"); + + if (input_name.empty() || result_name.empty()) { + return; + } + + const bNode &node = params.node(); + NodeAttributeMapRange &node_storage = *(NodeAttributeMapRange *)node.storage; + const CustomDataType data_type = static_cast<CustomDataType>(node_storage.data_type); + + const AttributeDomain domain = get_result_domain(component, input_name, result_name); + + GVArrayPtr attribute_input = component.attribute_try_get_for_read(input_name, domain, data_type); + + if (!attribute_input) { + params.error_message_add(NodeWarningType::Error, + TIP_("No attribute with name \"") + input_name + "\""); + return; + } + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, domain, data_type); + if (!attribute_result) { + params.error_message_add(NodeWarningType::Error, + TIP_("Could not find or create attribute with name \"") + + result_name + "\""); + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + map_range_float(attribute_input->typed<float>(), attribute_result.as_span<float>(), params); + break; + } + case CD_PROP_FLOAT3: { + map_range_float3( + attribute_input->typed<float3>(), attribute_result.as_span<float3>(), params); + break; + } + default: + BLI_assert_unreachable(); + } + + attribute_result.save(); +} + +static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + map_range_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + map_range_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + if (geometry_set.has<CurveComponent>()) { + map_range_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_map_range() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_map_range_in, geo_node_attribute_map_range_out); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_map_range_exec; + node_type_init(&ntype, geo_node_attribute_map_range_init); + node_type_update(&ntype, geo_node_attribute_map_range_update); + node_type_storage( + &ntype, "NodeAttributeMapRange", node_free_standard_storage, node_copy_standard_storage); + ntype.draw_buttons = fn_attribute_map_range_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index 8c6d49e322c..5b78b4cd39e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -14,23 +14,15 @@ * 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 "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" #include "NOD_math_functions.hh" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_math_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("A")}, @@ -163,46 +155,52 @@ static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node operation_use_input_c(operation)); } -static void do_math_operation(Span<float> span_a, - Span<float> span_b, - Span<float> span_c, +static void do_math_operation(const VArray<float> &span_a, + const VArray<float> &span_b, + const VArray<float> &span_c, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); - } + parallel_for(IndexRange(span_result.size()), 512, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation(Span<float> span_a, - Span<float> span_b, +static void do_math_operation(const VArray<float> &span_a, + const VArray<float> &span_b, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation(Span<float> span_input, +static void do_math_operation(const VArray<float> &span_input, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_input[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_input[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); @@ -214,9 +212,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -238,56 +236,39 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP const std::string result_name = params.get_input<std::string>("Result"); /* The result type of this node is always float. */ - const CustomDataType result_type = CD_PROP_FLOAT; const AttributeDomain result_domain = get_result_domain( component, params, operation, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<float> attribute_result = + component.attribute_try_get_for_output_only<float>(result_name, result_domain); if (!attribute_result) { return; } - ReadAttributePtr attribute_a = params.get_input_attribute( - "A", component, result_domain, result_type, nullptr); - if (!attribute_a) { - return; - } + GVArray_Typed<float> attribute_a = params.get_input_attribute<float>( + "A", component, result_domain, 0.0f); + + MutableSpan<float> result_span = attribute_result.as_span(); - /* Note that passing the data with `get_span<float>()` works + /* Note that passing the data with `get_internal_span<float>()` works * because the attributes were accessed with #CD_PROP_FLOAT. */ if (operation_use_input_b(operation)) { - ReadAttributePtr attribute_b = params.get_input_attribute( - "B", component, result_domain, result_type, nullptr); - if (!attribute_b) { - return; - } + GVArray_Typed<float> attribute_b = params.get_input_attribute<float>( + "B", component, result_domain, 0.0f); if (operation_use_input_c(operation)) { - ReadAttributePtr attribute_c = params.get_input_attribute( - "C", component, result_domain, result_type, nullptr); - if (!attribute_c) { - return; - } - do_math_operation(attribute_a->get_span<float>(), - attribute_b->get_span<float>(), - attribute_c->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + GVArray_Typed<float> attribute_c = params.get_input_attribute<float>( + "C", component, result_domain, 0.0f); + do_math_operation(attribute_a, attribute_b, attribute_c, result_span, operation); } else { - do_math_operation(attribute_a->get_span<float>(), - attribute_b->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + do_math_operation(attribute_a, attribute_b, result_span, operation); } } else { - do_math_operation(attribute_a->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + do_math_operation(attribute_a, result_span, operation); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_math_exec(GeoNodeExecParams params) @@ -302,6 +283,9 @@ static void geo_node_attribute_math_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 9d8cd3dfa82..a6bd6c0ee32 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "BKE_material.h" #include "DNA_material_types.h" @@ -57,73 +59,110 @@ static void geo_node_attribute_mix_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), + "attribute mix node"); + data->blend_type = MA_RAMP_BLEND; + data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node->storage = data; +} + +static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + update_attribute_input_socket_availabilities( + *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); +} + static void do_mix_operation_float(const int blend_mode, - const FloatReadAttribute &factors, - const FloatReadAttribute &inputs_a, - const FloatReadAttribute &inputs_b, - FloatWriteAttribute results) + const VArray<float> &factors, + const VArray<float> &inputs_a, + const VArray<float> &inputs_b, + VMutableArray<float> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a{inputs_a[i]}; - const float3 b{inputs_b[i]}; - ramp_blend(blend_mode, a, factor, b); - const float result = a.x; - results.set(i, result); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a{inputs_a[i]}; + const float3 b{inputs_b[i]}; + ramp_blend(blend_mode, a, factor, b); + const float result = a.x; + results.set(i, result); + } + }); } static void do_mix_operation_float3(const int blend_mode, - const FloatReadAttribute &factors, - const Float3ReadAttribute &inputs_a, - const Float3ReadAttribute &inputs_b, - Float3WriteAttribute results) + const VArray<float> &factors, + const VArray<float3> &inputs_a, + const VArray<float3> &inputs_b, + VMutableArray<float3> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a = inputs_a[i]; - const float3 b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a = inputs_a[i]; + const float3 b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation_color4f(const int blend_mode, - const FloatReadAttribute &factors, - const Color4fReadAttribute &inputs_a, - const Color4fReadAttribute &inputs_b, - Color4fWriteAttribute results) + const VArray<float> &factors, + const VArray<ColorGeometry4f> &inputs_a, + const VArray<ColorGeometry4f> &inputs_b, + VMutableArray<ColorGeometry4f> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - Color4f a = inputs_a[i]; - const Color4f b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + ColorGeometry4f a = inputs_a[i]; + const ColorGeometry4f b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation(const CustomDataType result_type, int blend_mode, - const FloatReadAttribute &attribute_factor, - const ReadAttribute &attribute_a, - const ReadAttribute &attribute_b, - WriteAttribute &attribute_result) + const VArray<float> &attribute_factor, + const GVArray &attribute_a, + const GVArray &attribute_b, + GVMutableArray &attribute_result) { if (result_type == CD_PROP_FLOAT) { - do_mix_operation_float( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_float(blend_mode, + attribute_factor, + attribute_a.typed<float>(), + attribute_b.typed<float>(), + attribute_result.typed<float>()); } else if (result_type == CD_PROP_FLOAT3) { - do_mix_operation_float3( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_float3(blend_mode, + attribute_factor, + attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_result.typed<float3>()); } else if (result_type == CD_PROP_COLOR) { - do_mix_operation_color4f( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_color4f(blend_mode, + attribute_factor, + attribute_a.typed<ColorGeometry4f>(), + attribute_b.typed<ColorGeometry4f>(), + attribute_result.typed<ColorGeometry4f>()); } } @@ -132,9 +171,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -158,17 +197,17 @@ static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecPa const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!attribute_result) { return; } - FloatReadAttribute attribute_factor = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_factor = params.get_input_attribute<float>( "Factor", component, result_domain, 0.5f); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, result_type, nullptr); - ReadAttributePtr attribute_b = params.get_input_attribute( + GVArrayPtr attribute_b = params.get_input_attribute( "B", component, result_domain, result_type, nullptr); do_mix_operation(result_type, @@ -192,32 +231,13 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_mix_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_mix_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), - "attribute mix node"); - data->blend_type = MA_RAMP_BLEND; - data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - node->storage = data; -} - -static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); - update_attribute_input_socket_availabilities( - *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); -} - } // namespace blender::nodes void register_node_type_geo_attribute_mix() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index f09a9bf056e..9c22b7fa87f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -62,7 +62,7 @@ namespace blender::nodes { static void proximity_calc(MutableSpan<float> distance_span, MutableSpan<float3> location_span, - Span<float3> positions, + const VArray<float3> &positions, BVHTreeFromMesh &tree_data_mesh, BVHTreeFromPointCloud &tree_data_pointcloud, const bool bvh_mesh_success, @@ -169,19 +169,18 @@ static void attribute_calc_proximity(GeometryComponent &component, const AttributeDomain result_domain = ATTR_DOMAIN_POINT; const std::string distance_attribute_name = params.get_input<std::string>("Distance"); - OutputAttributePtr distance_attribute = component.attribute_try_get_for_output( - distance_attribute_name, result_domain, CD_PROP_FLOAT); + OutputAttribute_Typed<float> distance_attribute = + component.attribute_try_get_for_output_only<float>(distance_attribute_name, result_domain); const std::string location_attribute_name = params.get_input<std::string>("Position"); - OutputAttributePtr location_attribute = component.attribute_try_get_for_output( - location_attribute_name, result_domain, CD_PROP_FLOAT3); - - ReadAttributePtr position_attribute = component.attribute_try_get_for_read("position"); - BLI_assert(position_attribute->custom_data_type() == CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> location_attribute = + component.attribute_try_get_for_output_only<float3>(location_attribute_name, result_domain); + ReadAttributeLookup position_attribute = component.attribute_try_get_for_read("position"); if (!position_attribute || (!distance_attribute && !location_attribute)) { return; } + BLI_assert(position_attribute.varray->type().is<float3>()); const bNode &node = params.node(); const NodeGeometryAttributeProximity &storage = *(const NodeGeometryAttributeProximity *) @@ -204,18 +203,15 @@ static void attribute_calc_proximity(GeometryComponent &component, tree_data_pointcloud); } - Span<float3> position_span = position_attribute->get_span<float3>(); - - MutableSpan<float> distance_span = distance_attribute ? - distance_attribute->get_span_for_write_only<float>() : - MutableSpan<float>(); - MutableSpan<float3> location_span = location_attribute ? - location_attribute->get_span_for_write_only<float3>() : - MutableSpan<float3>(); + GVArray_Typed<float3> positions{*position_attribute.varray}; + MutableSpan<float> distance_span = distance_attribute ? distance_attribute.as_span() : + MutableSpan<float>(); + MutableSpan<float3> location_span = location_attribute ? location_attribute.as_span() : + MutableSpan<float3>(); proximity_calc(distance_span, location_span, - position_span, + positions, tree_data_mesh, tree_data_pointcloud, bvh_mesh_success, @@ -231,10 +227,10 @@ static void attribute_calc_proximity(GeometryComponent &component, } if (distance_attribute) { - distance_attribute.apply_span_and_save(); + distance_attribute.save(); } if (location_attribute) { - location_attribute.apply_span_and_save(); + location_attribute.save(); } } @@ -257,6 +253,10 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) attribute_calc_proximity( geometry_set.get_component_for_write<PointCloudComponent>(), geometry_set_target, params); } + if (geometry_set.has<CurveComponent>()) { + attribute_calc_proximity( + geometry_set.get_component_for_write<CurveComponent>(), geometry_set_target, params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 27c35da7d37..286411b7d28 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -14,16 +14,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "BLI_hash.h" #include "BLI_rand.hh" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" -#include "DNA_mesh_types.h" -#include "DNA_pointcloud_types.h" +#include "node_geometry_util.hh" static bNodeSocketTemplate geo_node_attribute_randomize_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, @@ -128,28 +126,36 @@ static void randomize_attribute(MutableSpan<T> span, /* The operations could be templated too, but it doesn't make the code much shorter. */ switch (operation) { case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] + random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] + random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] - random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] - random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] * random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] * random_value; + } + }); break; default: BLI_assert(false); @@ -164,10 +170,12 @@ static void randomize_attribute_bool(MutableSpan<bool> span, { BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); UNUSED_VARS_NDEBUG(operation); - for (const int i : span.index_range()) { - const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; - span[i] = random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; + span[i] = random_value; + } + }); } Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, @@ -176,15 +184,17 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo const int domain_size = component.attribute_domain_size(domain); /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ - ReadAttributePtr hash_attribute = component.attribute_try_get_for_read("id", domain); + GVArrayPtr hash_attribute = component.attribute_try_get_for_read("id", domain); Array<uint32_t> hashes(domain_size); if (hash_attribute) { BLI_assert(hashes.size() == hash_attribute->size()); - const CPPType &cpp_type = hash_attribute->cpp_type(); - fn::GSpan items = hash_attribute->get_span(); - for (const int i : hashes.index_range()) { - hashes[i] = cpp_type.hash(items[i]); - } + const CPPType &cpp_type = hash_attribute->type(); + GVArray_GSpan items{*hash_attribute}; + parallel_for(hashes.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + hashes[i] = cpp_type.hash(items[i]); + } + }); } else { /* If there is no "id" attribute for per-point variation, just create it here. */ @@ -199,12 +209,12 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, - StringRef attribute_name) + const StringRef name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(name); + if (result_info) { + return result_info->domain; } /* Otherwise use the input domain chosen in the interface. */ @@ -231,15 +241,13 @@ static void randomize_attribute_on_component(GeometryComponent &component, const AttributeDomain domain = get_result_domain(component, params, attribute_name); - OutputAttributePtr attribute = component.attribute_try_get_for_output( + OutputAttribute attribute = component.attribute_try_get_for_output( attribute_name, domain, data_type); if (!attribute) { return; } - fn::GMutableSpan span = (operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) ? - attribute->get_span_for_write_only() : - attribute->get_span(); + GMutableSpan span = attribute.as_span(); Array<uint32_t> hashes = get_geometry_element_ids_as_uints(component, domain); @@ -272,8 +280,8 @@ static void randomize_attribute_on_component(GeometryComponent &component, } } - attribute.apply_span_and_save(); -} // namespace blender::nodes + attribute.save(); +} static void geo_node_random_attribute_exec(GeoNodeExecParams params) { @@ -307,6 +315,14 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) operation, seed); } + if (geometry_set.has<CurveComponent>()) { + randomize_attribute_on_component(geometry_set.get_component_for_write<CurveComponent>(), + params, + attribute_name, + data_type, + operation, + seed); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index 837f0c3629a..e4f3230ebb9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -70,6 +70,10 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) remove_attribute( geometry_set.get_component_for_write<PointCloudComponent>(), params, attribute_names); } + if (geometry_set.has<CurveComponent>()) { + remove_attribute( + geometry_set.get_component_for_write<CurveComponent>(), params, attribute_names); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index d0b2595b5b9..d6b1ad3e9e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -15,6 +15,7 @@ */ #include "BLI_compiler_attrs.h" +#include "BLI_task.hh" #include "DNA_texture_types.h" @@ -29,6 +30,7 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_TEXTURE, N_("Texture")}, {SOCK_STRING, N_("Mapping")}, {SOCK_STRING, N_("Result")}, {-1, ""}, @@ -39,29 +41,22 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_out[] = { {-1, ""}, }; -static void geo_node_attribute_sample_texture_layout(uiLayout *layout, - bContext *C, - PointerRNA *ptr) -{ - uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr, 0, ICON_NONE, nullptr); -} - namespace blender::nodes { static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef result_attribute_name, - StringRef map_attribute_name) + const StringRef result_name, + const StringRef map_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the name of the map attribute. */ - ReadAttributePtr map_attribute = component.attribute_try_get_for_read(map_attribute_name); - if (map_attribute) { - return map_attribute->domain(); + std::optional<AttributeMetaData> map_info = component.attribute_get_meta_data(map_name); + if (map_info) { + return map_info->domain; } /* The node won't execute in this case, but we still have to return a value. */ @@ -70,8 +65,7 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, static void execute_on_component(GeometryComponent &component, const GeoNodeExecParams ¶ms) { - const bNode &node = params.node(); - Tex *texture = reinterpret_cast<Tex *>(node.id); + Tex *texture = params.get_input<Tex *>("Texture"); if (texture == nullptr) { return; } @@ -85,25 +79,29 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec const AttributeDomain result_domain = get_result_domain( component, result_attribute_name, mapping_name); - OutputAttributePtr attribute_out = component.attribute_try_get_for_output( - result_attribute_name, result_domain, CD_PROP_COLOR); + OutputAttribute_Typed<ColorGeometry4f> attribute_out = + component.attribute_try_get_for_output_only<ColorGeometry4f>(result_attribute_name, + result_domain); if (!attribute_out) { return; } - Float3ReadAttribute mapping_attribute = component.attribute_get_for_read<float3>( + GVArray_Typed<float3> mapping_attribute = component.attribute_get_for_read<float3>( mapping_name, result_domain, {0, 0, 0}); - MutableSpan<Color4f> colors = attribute_out->get_span<Color4f>(); - for (const int i : IndexRange(mapping_attribute.size())) { - TexResult texture_result = {0}; - const float3 position = mapping_attribute[i]; - /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ - const float3 remapped_position = position * 2.0f - float3(1.0f); - BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); - colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; - } - attribute_out.apply_span_and_save(); + MutableSpan<ColorGeometry4f> colors = attribute_out.as_span(); + parallel_for(IndexRange(mapping_attribute.size()), 128, [&](IndexRange range) { + for (const int i : range) { + TexResult texture_result = {0}; + const float3 position = mapping_attribute[i]; + /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ + const float3 remapped_position = position * 2.0f - float3(1.0f); + BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); + colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; + } + }); + + attribute_out.save(); } static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) @@ -118,6 +116,9 @@ static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } @@ -137,6 +138,5 @@ void register_node_type_geo_sample_texture() node_type_socket_templates( &ntype, geo_node_attribute_sample_texture_in, geo_node_attribute_sample_texture_out); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_sample_texture_exec; - ntype.draw_buttons = geo_node_attribute_sample_texture_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index 55b933e8582..137a72bb707 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -14,13 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "DNA_material_types.h" - -#include "node_geometry_util.hh" - #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_separate_xyz_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Vector")}, @@ -73,23 +71,23 @@ static void extract_input(const int index, const Span<float3> &input, MutableSpa static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, - StringRef result_name_x, - StringRef result_name_y, - StringRef result_name_z) + const StringRef name_x, + const StringRef name_y, + const StringRef name_z) { /* Use the highest priority domain from any existing attribute outputs. */ Vector<AttributeDomain, 3> output_domains; - ReadAttributePtr attribute_x = component.attribute_try_get_for_read(result_name_x); - ReadAttributePtr attribute_y = component.attribute_try_get_for_read(result_name_y); - ReadAttributePtr attribute_z = component.attribute_try_get_for_read(result_name_z); - if (attribute_x) { - output_domains.append(attribute_x->domain()); + std::optional<AttributeMetaData> info_x = component.attribute_get_meta_data(name_x); + std::optional<AttributeMetaData> info_y = component.attribute_get_meta_data(name_y); + std::optional<AttributeMetaData> info_z = component.attribute_get_meta_data(name_z); + if (info_x) { + output_domains.append(info_x->domain); } - if (attribute_y) { - output_domains.append(attribute_y->domain()); + if (info_y) { + output_domains.append(info_y->domain); } - if (attribute_z) { - output_domains.append(attribute_z->domain()); + if (info_z) { + output_domains.append(info_z->domain); } if (output_domains.size() > 0) { return bke::attribute_domain_highest_priority(output_domains); @@ -109,37 +107,32 @@ static void separate_attribute(GeometryComponent &component, const GeoNodeExecPa } /* The node is only for float3 to float conversions. */ - const CustomDataType input_type = CD_PROP_FLOAT3; - const CustomDataType result_type = CD_PROP_FLOAT; const AttributeDomain result_domain = get_result_domain( component, params, result_name_x, result_name_y, result_name_z); - ReadAttributePtr attribute_input = params.get_input_attribute( - "Vector", component, result_domain, input_type, nullptr); - if (!attribute_input) { - return; - } - const Span<float3> input_span = attribute_input->get_span<float3>(); + GVArray_Typed<float3> attribute_input = params.get_input_attribute<float3>( + "Vector", component, result_domain, {0, 0, 0}); + VArray_Span<float3> input_span{*attribute_input}; - OutputAttributePtr attribute_result_x = component.attribute_try_get_for_output( - result_name_x, result_domain, result_type); - OutputAttributePtr attribute_result_y = component.attribute_try_get_for_output( - result_name_y, result_domain, result_type); - OutputAttributePtr attribute_result_z = component.attribute_try_get_for_output( - result_name_z, result_domain, result_type); + OutputAttribute_Typed<float> attribute_result_x = + component.attribute_try_get_for_output_only<float>(result_name_x, result_domain); + OutputAttribute_Typed<float> attribute_result_y = + component.attribute_try_get_for_output_only<float>(result_name_y, result_domain); + OutputAttribute_Typed<float> attribute_result_z = + component.attribute_try_get_for_output_only<float>(result_name_z, result_domain); /* Only extract the components for the outputs with a given attribute. */ if (attribute_result_x) { - extract_input(0, input_span, attribute_result_x->get_span_for_write_only<float>()); - attribute_result_x.apply_span_and_save(); + extract_input(0, input_span, attribute_result_x.as_span()); + attribute_result_x.save(); } if (attribute_result_y) { - extract_input(1, input_span, attribute_result_y->get_span_for_write_only<float>()); - attribute_result_y.apply_span_and_save(); + extract_input(1, input_span, attribute_result_y.as_span()); + attribute_result_y.save(); } if (attribute_result_z) { - extract_input(2, input_span, attribute_result_z->get_span_for_write_only<float>()); - attribute_result_z.apply_span_and_save(); + extract_input(2, input_span, attribute_result_z.as_span()); + attribute_result_z.save(); } } @@ -155,6 +148,9 @@ static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { separate_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + separate_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc new file mode 100644 index 00000000000..4b677dc5c82 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -0,0 +1,597 @@ +/* + * 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_kdopbvh.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_transfer_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Source Geometry")}, + {SOCK_STRING, N_("Source")}, + {SOCK_STRING, N_("Destination")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_transfer_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_transfer_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, IFACE_("Domain"), ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_attribute_transfer_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryAttributeTransfer *data = (NodeGeometryAttributeTransfer *)MEM_callocN( + sizeof(NodeGeometryAttributeTransfer), __func__); + data->domain = ATTR_DOMAIN_AUTO; + node->storage = data; +} + +static void get_result_domain_and_data_type(const GeometrySet &src_geometry, + const GeometryComponent &dst_component, + const StringRef attribute_name, + CustomDataType *r_data_type, + AttributeDomain *r_domain) +{ + Vector<CustomDataType> data_types; + Vector<AttributeDomain> domains; + + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read<PointCloudComponent>(); + if (pointcloud_component != nullptr) { + std::optional<AttributeMetaData> meta_data = pointcloud_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + const MeshComponent *mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + if (mesh_component != nullptr) { + std::optional<AttributeMetaData> meta_data = mesh_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + *r_data_type = bke::attribute_data_type_highest_complexity(data_types); + + if (dst_component.type() == GEO_COMPONENT_TYPE_POINT_CLOUD) { + *r_domain = ATTR_DOMAIN_POINT; + } + else { + *r_domain = bke::attribute_domain_highest_priority(domains); + } +} + +static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) +{ + /* 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); + return {looptris, looptris_len}; +} + +static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray<float3> &positions, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(positions.size() == r_indices.size() || r_indices.is_empty()); + BLI_assert(positions.size() == r_distances_sq.size() || r_distances_sq.is_empty()); + BLI_assert(positions.size() == r_positions.size() || r_positions.is_empty()); + + for (const int i : positions.index_range()) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + if (!r_indices.is_empty()) { + r_indices[i] = nearest.index; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = nearest.dist_sq; + } + if (!r_positions.is_empty()) { + r_positions[i] = nearest.co; + } + } +} + +static void get_closest_pointcloud_points(const PointCloud &pointcloud, + const VArray<float3> &positions, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq) +{ + BLI_assert(positions.size() == r_indices.size()); + BLI_assert(pointcloud.totpoint > 0); + + BVHTreeFromPointCloud tree_data; + BKE_bvhtree_from_pointcloud_get(&tree_data, &pointcloud, 2); + + for (const int i : positions.index_range()) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + r_indices[i] = nearest.index; + r_distances_sq[i] = nearest.dist_sq; + } + + free_bvhtree_from_pointcloud(&tree_data); +} + +static void get_closest_mesh_points(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_point_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totvert > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_VERTS, 2); + get_closest_in_bvhtree(tree_data, positions, r_point_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_edges(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_edge_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totedge > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_EDGES, 2); + get_closest_in_bvhtree(tree_data, positions, r_edge_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_looptris(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_looptri_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree(tree_data, positions, r_looptri_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_polygons(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_poly_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + + Array<int> looptri_indices(positions.size()); + get_closest_mesh_looptris(mesh, positions, looptri_indices, r_distances_sq, r_positions); + + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + for (const int i : positions.index_range()) { + const MLoopTri &looptri = looptris[looptri_indices[i]]; + r_poly_indices[i] = looptri.poly; + } +} + +/* The closest corner is defined to be the closest corner on the closest face. */ +static void get_closest_mesh_corners(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_corner_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totloop > 0); + Array<int> poly_indices(positions.size()); + get_closest_mesh_polygons(mesh, positions, poly_indices, {}, {}); + + for (const int i : positions.index_range()) { + const float3 position = positions[i]; + const int poly_index = poly_indices[i]; + const MPoly &poly = mesh.mpoly[poly_index]; + + /* Find the closest vertex in the polygon. */ + float min_distance_sq = FLT_MAX; + const MVert *closest_mvert; + int closest_loop_index = 0; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int vertex_index = loop.v; + const MVert &mvert = mesh.mvert[vertex_index]; + const float distance_sq = float3::distance_squared(position, mvert.co); + if (distance_sq < min_distance_sq) { + min_distance_sq = distance_sq; + closest_loop_index = loop_index; + closest_mvert = &mvert; + } + } + if (!r_corner_indices.is_empty()) { + r_corner_indices[i] = closest_loop_index; + } + if (!r_positions.is_empty()) { + r_positions[i] = closest_mvert->co; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = min_distance_sq; + } + } +} + +static void get_barycentric_coords(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> positions, + const MutableSpan<float3> r_bary_coords) +{ + BLI_assert(r_bary_coords.size() == positions.size()); + BLI_assert(r_bary_coords.size() == looptri_indices.size()); + + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : r_bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + 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; + + interp_weights_tri_v3(r_bary_coords[i], + mesh.mvert[v0_index].co, + mesh.mvert[v1_index].co, + mesh.mvert[v2_index].co, + positions[i]); + } +} + +static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray<float3> &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const int tot_samples = dst_positions.size(); + const MeshComponent *component = src_geometry.get_component_for_read<MeshComponent>(); + if (component == nullptr) { + return; + } + const Mesh *mesh = component->get_for_read(); + if (mesh == nullptr) { + return; + } + if (mesh->totpoly == 0) { + return; + } + ReadAttributeLookup src_attribute = component->attribute_try_get_for_read(src_name, data_type); + if (!src_attribute) { + return; + } + + /* Find closest points on the mesh surface. */ + Array<int> looptri_indices(tot_samples); + Array<float3> positions(tot_samples); + get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + GMutableSpan dst_span = dst_attribute.as_span(); + Array<float3> bary_coords; + + /* Compute barycentric coordinates only when they are needed. */ + if (src_attribute.domain != ATTR_DOMAIN_FACE) { + bary_coords.reinitialize(tot_samples); + get_barycentric_coords(*mesh, looptri_indices, positions, bary_coords); + } + /* Interpolate the source attribute on the surface. */ + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + *mesh, looptri_indices, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Not yet supported. */ + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + dst_attribute.save(); +} + +static void transfer_attribute_nearest(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray<float3> &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + + /* Get pointcloud data from geometry. */ + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read<PointCloudComponent>(); + const PointCloud *pointcloud = pointcloud_component ? pointcloud_component->get_for_read() : + nullptr; + + /* Get mesh data from geometry. */ + const MeshComponent *mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + const Mesh *mesh = mesh_component ? mesh_component->get_for_read() : nullptr; + + const int tot_samples = dst_positions.size(); + + Array<int> pointcloud_indices; + Array<float> pointcloud_distances_sq; + bool use_pointcloud = false; + + /* Depending on where what domain the source attribute lives, these indices are either vertex, + * corner, edge or polygon indices. */ + Array<int> mesh_indices; + Array<float> mesh_distances_sq; + bool use_mesh = false; + + /* If there is a pointcloud, find the closest points. */ + if (pointcloud != nullptr && pointcloud->totpoint > 0) { + if (pointcloud_component->attribute_exists(src_name)) { + use_pointcloud = true; + pointcloud_indices.reinitialize(tot_samples); + pointcloud_distances_sq.reinitialize(tot_samples); + get_closest_pointcloud_points( + *pointcloud, dst_positions, pointcloud_indices, pointcloud_distances_sq); + } + } + + /* If there is a mesh, find the closest mesh elements. */ + if (mesh != nullptr) { + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name); + if (src_attribute) { + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + if (mesh->totvert > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_points(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_EDGE: { + if (mesh->totedge > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_edges(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_FACE: { + if (mesh->totpoly > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_polygons(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_CORNER: { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_corners(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + break; + } + default: { + break; + } + } + } + } + + if (!use_pointcloud && !use_mesh) { + return; + } + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + + /* Create a buffer for intermediate values. */ + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + + if (use_mesh && use_pointcloud) { + /* When there is a mesh and a pointcloud, we still have to check whether a pointcloud point or + * a mesh element is closer to every point. */ + ReadAttributeLookup pointcloud_src_attribute = + pointcloud_component->attribute_try_get_for_read(src_name, data_type); + ReadAttributeLookup mesh_src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { + /* Pointcloud point is closer. */ + const int index = pointcloud_indices[i]; + pointcloud_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + else { + /* Mesh element is closer. */ + const int index = mesh_indices[i]; + mesh_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + } + else if (use_pointcloud) { + /* The source geometry only has a pointcloud. */ + ReadAttributeLookup src_attribute = pointcloud_component->attribute_try_get_for_read( + src_name, data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = pointcloud_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + else if (use_mesh) { + /* The source geometry only has a mesh. */ + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = mesh_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + + dst_attribute.save(); +} + +static void transfer_attribute(const GeoNodeExecParams ¶ms, + const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const StringRef src_name, + const StringRef dst_name) +{ + const NodeGeometryAttributeTransfer &storage = + *(const NodeGeometryAttributeTransfer *)params.node().storage; + const GeometryNodeAttributeTransferMapMode mapping = (GeometryNodeAttributeTransferMapMode) + storage.mapping; + const AttributeDomain input_domain = (AttributeDomain)storage.domain; + + CustomDataType data_type; + AttributeDomain auto_domain; + get_result_domain_and_data_type(src_geometry, dst_component, src_name, &data_type, &auto_domain); + const AttributeDomain dst_domain = (input_domain == ATTR_DOMAIN_AUTO) ? auto_domain : + input_domain; + + GVArray_Typed<float3> dst_positions = dst_component.attribute_get_for_read<float3>( + "position", dst_domain, {0, 0, 0}); + + switch (mapping) { + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { + transfer_attribute_nearest_face_interpolated( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { + transfer_attribute_nearest( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + } +} + +static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) +{ + GeometrySet dst_geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet src_geometry_set = params.extract_input<GeometrySet>("Source Geometry"); + const std::string src_attribute_name = params.extract_input<std::string>("Source"); + const std::string dst_attribute_name = params.extract_input<std::string>("Destination"); + + if (src_attribute_name.empty() || dst_attribute_name.empty()) { + params.set_output("Geometry", dst_geometry_set); + return; + } + + dst_geometry_set = bke::geometry_set_realize_instances(dst_geometry_set); + src_geometry_set = bke::geometry_set_realize_instances(src_geometry_set); + + if (dst_geometry_set.has<MeshComponent>()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write<MeshComponent>(), + src_attribute_name, + dst_attribute_name); + } + if (dst_geometry_set.has<PointCloudComponent>()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write<PointCloudComponent>(), + src_attribute_name, + dst_attribute_name); + } + + params.set_output("Geometry", dst_geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_transfer() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_transfer_in, geo_node_attribute_transfer_out); + node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); + node_type_storage(&ntype, + "NodeGeometryAttributeTransfer", + node_free_standard_storage, + node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_transfer_exec; + ntype.draw_buttons = geo_node_attribute_transfer_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index ee2fd763ead..5a5fc099779 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -14,23 +14,16 @@ * 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 "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" #include "NOD_math_functions.hh" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_attribute_vector_math_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("A")}, @@ -66,8 +59,11 @@ static bool operation_use_input_b(const NodeVectorMathOperation operation) static bool operation_use_input_c(const NodeVectorMathOperation operation) { - return ELEM( - operation, NODE_VECTOR_MATH_WRAP, NODE_VECTOR_MATH_REFRACT, NODE_VECTOR_MATH_FACEFORWARD); + return ELEM(operation, + NODE_VECTOR_MATH_WRAP, + NODE_VECTOR_MATH_REFRACT, + NODE_VECTOR_MATH_FACEFORWARD, + NODE_VECTOR_MATH_MULTIPLY_ADD); } static CustomDataType operation_get_read_type_b(const NodeVectorMathOperation operation) @@ -129,6 +125,7 @@ static CustomDataType operation_get_result_type(const NodeVectorMathOperation op case NODE_VECTOR_MATH_TANGENT: case NODE_VECTOR_MATH_REFRACT: case NODE_VECTOR_MATH_FACEFORWARD: + case NODE_VECTOR_MATH_MULTIPLY_ADD: return CD_PROP_FLOAT3; case NODE_VECTOR_MATH_DOT_PRODUCT: case NODE_VECTOR_MATH_DISTANCE: @@ -182,196 +179,210 @@ static void geo_node_attribute_vector_math_update(bNodeTree *UNUSED(ntree), bNod operation_use_input_c(operation)); } -static void do_math_operation_fl3_fl3_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_fl3_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - const Float3ReadAttribute &input_c, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_fl3_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + const VArray<float3> &input_c, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - Span<float3> span_c = input_c.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VArray_Span<float3> span_c{input_c}; + VMutableArray_Span<float3> span_result{result}; bool success = try_dispatch_float_math_fl3_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_fl_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - const FloatReadAttribute &input_c, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_fl_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + const VArray<float> &input_c, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - Span<float> span_c = input_c.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VArray_Span<float> span_c{input_c}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_to_fl(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - FloatWriteAttribute result, +static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, + const VArray<float3> &input_b, + VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - MutableSpan<float> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VMutableArray_Span<float> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl_to_fl3(const Float3ReadAttribute &input_a, - const FloatReadAttribute &input_b, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, + const VArray<float> &input_b, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float> span_b = input_b.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float> span_b{input_b}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_to_fl3(const Float3ReadAttribute &input_a, - Float3WriteAttribute result, +static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float3 out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float3 out = math_function(in); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_to_fl(const Float3ReadAttribute &input_a, - FloatWriteAttribute result, +static void do_math_operation_fl3_to_fl(const VArray<float3> &input_a, + VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - MutableSpan<float> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VMutableArray_Span<float> span_result{result, false}; bool success = try_dispatch_float_math_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float out = math_function(in); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); @@ -384,9 +395,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + ReadAttributeLookup result_attribute = component.attribute_try_get_for_read(result_name); if (result_attribute) { - return result_attribute->domain(); + return result_attribute.domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -420,13 +431,13 @@ static void attribute_vector_math_calc(GeometryComponent &component, const AttributeDomain result_domain = get_result_domain( component, params, operation, result_name); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, read_type_a, nullptr); if (!attribute_a) { return; } - ReadAttributePtr attribute_b; - ReadAttributePtr attribute_c; + GVArrayPtr attribute_b; + GVArrayPtr attribute_c; if (use_input_b) { attribute_b = params.get_input_attribute("B", component, result_domain, read_type_b, nullptr); if (!attribute_b) { @@ -441,7 +452,7 @@ static void attribute_vector_math_calc(GeometryComponent &component, } /* Get result attribute first, in case it has to overwrite one of the existing attributes. */ - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!attribute_result) { return; @@ -459,17 +470,27 @@ static void attribute_vector_math_calc(GeometryComponent &component, case NODE_VECTOR_MATH_MODULO: case NODE_VECTOR_MATH_MINIMUM: case NODE_VECTOR_MATH_MAXIMUM: - do_math_operation_fl3_fl3_to_fl3(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl3_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_DOT_PRODUCT: case NODE_VECTOR_MATH_DISTANCE: - do_math_operation_fl3_fl3_to_fl(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl3_to_fl(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_result->typed<float>(), + operation); break; case NODE_VECTOR_MATH_LENGTH: - do_math_operation_fl3_to_fl(*attribute_a, *attribute_result, operation); + do_math_operation_fl3_to_fl( + attribute_a->typed<float3>(), attribute_result->typed<float>(), operation); break; case NODE_VECTOR_MATH_SCALE: - do_math_operation_fl3_fl_to_fl3(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_NORMALIZE: case NODE_VECTOR_MATH_FLOOR: @@ -479,16 +500,24 @@ static void attribute_vector_math_calc(GeometryComponent &component, case NODE_VECTOR_MATH_SINE: case NODE_VECTOR_MATH_COSINE: case NODE_VECTOR_MATH_TANGENT: - do_math_operation_fl3_to_fl3(*attribute_a, *attribute_result, operation); + do_math_operation_fl3_to_fl3( + attribute_a->typed<float3>(), attribute_result->typed<float3>(), operation); break; case NODE_VECTOR_MATH_WRAP: case NODE_VECTOR_MATH_FACEFORWARD: - do_math_operation_fl3_fl3_fl3_to_fl3( - *attribute_a, *attribute_b, *attribute_c, *attribute_result, operation); + case NODE_VECTOR_MATH_MULTIPLY_ADD: + do_math_operation_fl3_fl3_fl3_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_c->typed<float3>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_REFRACT: - do_math_operation_fl3_fl3_fl_to_fl3( - *attribute_a, *attribute_b, *attribute_c, *attribute_result, operation); + do_math_operation_fl3_fl3_fl_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_c->typed<float>(), + attribute_result->typed<float3>(), + operation); break; } attribute_result.save(); @@ -507,6 +536,9 @@ static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params) attribute_vector_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_vector_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc new file mode 100644 index 00000000000..4d568ab5c3a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -0,0 +1,352 @@ +/* + * 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_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Vector")}, + {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, + {SOCK_STRING, N_("Center")}, + {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {SOCK_STRING, N_("Axis")}, + {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE}, + {SOCK_STRING, N_("Angle")}, + {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE}, + {SOCK_STRING, N_("Rotation")}, + {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, + {SOCK_BOOLEAN, N_("Invert")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + bNode *node = (bNode *)ptr->data; + const NodeAttributeVectorRotate &node_storage = *(NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage.mode; + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiLayout *column = uiLayoutColumn(layout, false); + + uiItemR(column, ptr, "rotation_mode", 0, "", ICON_NONE); + + uiItemR(column, ptr, "input_type_vector", 0, IFACE_("Vector"), ICON_NONE); + uiItemR(column, ptr, "input_type_center", 0, IFACE_("Center"), ICON_NONE); + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS) { + uiItemR(column, ptr, "input_type_axis", 0, IFACE_("Axis"), ICON_NONE); + } + if (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_angle", 0, IFACE_("Angle"), ICON_NONE); + } + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_rotation", 0, IFACE_("Rotation"), ICON_NONE); + } +} + +namespace blender::nodes { + +static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); + update_attribute_input_socket_availabilities( + *node, "Center", (GeometryNodeAttributeInputMode)node_storage->input_type_center); + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS)); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); +} + +static float3 vector_rotate_around_axis(const float3 vector, + const float3 center, + const float3 axis, + const float angle) +{ + float3 result = vector - center; + float mat[3][3]; + axis_angle_to_mat3(mat, axis, angle); + mul_m3_v3(mat, result); + return result + center; +} + +static void geo_node_attribute_vector_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)MEM_callocN( + sizeof(NodeAttributeVectorRotate), __func__); + + node_storage->mode = GEO_NODE_VECTOR_ROTATE_TYPE_AXIS; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node_storage->input_type_center = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static float3 vector_rotate_euler(const float3 vector, + const float3 center, + const float3 rotation, + const bool invert) +{ + float mat[3][3]; + float3 result = vector - center; + eul_to_mat3(mat, rotation); + if (invert) { + invert_m3(mat); + } + mul_m3_v3(mat, result); + return result + center; +} + +static void do_vector_rotate_around_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_axis{axis}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], span_axis[i], angle); + } + }); +} + +static void do_vector_rotate_around_fixed_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const float3 axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], axis, angle); + } + }); +} + +static void do_vector_rotate_euler(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &rotation, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_rotation{rotation}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = vector_rotate_euler(span_vector[i], span_center[i], span_rotation[i], invert); + } + }); +} + +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(result_name); + if (meta_data) { + return meta_data->domain; + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + const AttributeDomain default_domain = ATTR_DOMAIN_POINT; + return params.get_highest_priority_input_domain({"Vector", "Center"}, component, default_domain); +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &node = params.node(); + const NodeAttributeVectorRotate *node_storage = (const NodeAttributeVectorRotate *)node.storage; + const GeometryNodeAttributeVectorRotateMode mode = (GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + const std::string result_name = params.get_input<std::string>("Result"); + const AttributeDomain result_domain = get_result_domain(component, params, result_name); + const bool invert = params.get_input<bool>("Invert"); + + GVArrayPtr attribute_vector = params.get_input_attribute( + "Vector", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_vector) { + return; + } + GVArrayPtr attribute_center = params.get_input_attribute( + "Center", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_center) { + return; + } + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, CD_PROP_FLOAT3); + if (!attribute_result) { + return; + } + + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + GVArrayPtr attribute_rotation = params.get_input_attribute( + "Rotation", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_rotation) { + return; + } + do_vector_rotate_euler(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_rotation->typed<float3>(), + attribute_result.as_span<float3>(), + invert); + attribute_result.save(); + return; + } + + GVArrayPtr attribute_angle = params.get_input_attribute( + "Angle", component, result_domain, CD_PROP_FLOAT, nullptr); + if (!attribute_angle) { + return; + } + + switch (mode) { + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS: { + GVArrayPtr attribute_axis = params.get_input_attribute( + "Axis", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_axis) { + return; + } + do_vector_rotate_around_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_axis->typed<float3>(), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + } break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(1.0f, 0.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 1.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 0.0f, 1.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ: + /* Euler is handled before other modes to avoid processing the unavailable angle socket. */ + BLI_assert_unreachable(); + break; + } + attribute_result.save(); +} + +static void geo_node_attribute_vector_rotate_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_vector_rotate() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, + "Attribute Vector Rotate", + NODE_CLASS_ATTRIBUTE, + 0); + node_type_socket_templates( + &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out); + node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update); + node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init); + node_type_size(&ntype, 165, 100, 600); + node_type_storage( + &ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec; + ntype.draw_buttons = geo_node_attribute_vector_rotate_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 9f331190420..d8029ea1eeb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -14,29 +14,29 @@ * 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_boolean_convert.hh" #include "UI_interface.h" #include "UI_resources.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 1")}, - {SOCK_GEOMETRY, N_("Geometry 2")}, + {SOCK_GEOMETRY, + N_("Geometry 2"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_MULTI_INPUT}, + {SOCK_BOOLEAN, N_("Self Intersection")}, + {SOCK_BOOLEAN, N_("Hole Tolerant")}, {-1, ""}, }; @@ -50,109 +50,87 @@ static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), Point uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } -static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) +static void geo_node_boolean_update(bNodeTree *UNUSED(ntree), bNode *node) { - 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); + GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)node->custom1; - /* Temp tag to test which side split faces are from. */ - BM_elem_flag_enable(bm_face, BM_ELEM_DRAW); + bNodeSocket *geometry_1_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *geometry_2_socket = geometry_1_socket->next; - i++; - if (i == i_faces_end) { + switch (operation) { + case GEO_NODE_BOOLEAN_INTERSECT: + case GEO_NODE_BOOLEAN_UNION: + nodeSetSocketAvailability(geometry_1_socket, false); + nodeSetSocketAvailability(geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Geometry")); + break; + case GEO_NODE_BOOLEAN_DIFFERENCE: + nodeSetSocketAvailability(geometry_1_socket, true); + nodeSetSocketAvailability(geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Geometry 2")); break; - } } +} - BM_mesh_boolean( - bm, looptris, tottri, bm_face_isect_pair, nullptr, 2, false, 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; +static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE; } namespace blender::nodes { + static void geo_node_boolean_exec(GeoNodeExecParams params) { - GeometrySet geometry_set_in_a = params.extract_input<GeometrySet>("Geometry 1"); - GeometrySet geometry_set_in_b = params.extract_input<GeometrySet>("Geometry 2"); - 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 bool use_self = params.get_input<bool>("Self Intersection"); + const bool hole_tolerant = params.get_input<bool>("Hole Tolerant"); + +#ifndef WITH_GMP + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without GMP")); +#endif + + Vector<const Mesh *> meshes; + Vector<const float4x4 *> transforms; + + GeometrySet set_a; + if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) { + set_a = params.extract_input<GeometrySet>("Geometry 1"); + /* Note that it technically wouldn't be necessary to realize the instances for the first + * geometry input, but the boolean code expects the first shape for the difference operation + * to be a single mesh. */ + set_a = geometry_set_realize_instances(set_a); + const Mesh *mesh_in_a = set_a.get_mesh_for_read(); + if (mesh_in_a != nullptr) { + meshes.append(mesh_in_a); + transforms.append(nullptr); + } } - /* TODO: Boolean does support an input of multiple meshes. Currently they must all be - * converted to BMesh before running the operation though. D9957 will make it possible - * to use the mesh structure directly. */ - geometry_set_in_a = geometry_set_realize_instances(geometry_set_in_a); - geometry_set_in_b = geometry_set_realize_instances(geometry_set_in_b); - - 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(); + /* The instance transform matrices are owned by the instance group, so we have to + * keep all of them around for use during the boolean operation. */ + Vector<bke::GeometryInstanceGroup> set_groups; + Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Geometry 2"); + for (const GeometrySet &geometry_set : geometry_sets) { + bke::geometry_set_gather_instances(geometry_set, set_groups); + } - 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); + for (const bke::GeometryInstanceGroup &set_group : set_groups) { + const Mesh *mesh_in = set_group.geometry_set.get_mesh_for_read(); + if (mesh_in != nullptr) { + meshes.append_n_times(mesh_in, set_group.transforms.size()); + for (const int i : set_group.transforms.index_range()) { + transforms.append(set_group.transforms.begin() + i); } - 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); + Mesh *result = blender::meshintersect::direct_mesh_boolean( + meshes, transforms, float4x4::identity(), {}, use_self, hole_tolerant, operation); - params.set_output("Geometry", std::move(geometry_set_out)); + params.set_output("Geometry", GeometrySet::create_with_mesh(result)); } + } // namespace blender::nodes void register_node_type_geo_boolean() @@ -162,6 +140,8 @@ void register_node_type_geo_boolean() geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); ntype.draw_buttons = geo_node_boolean_layout; + ntype.updatefunc = geo_node_boolean_update; + node_type_init(&ntype, geo_node_boolean_init); ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc new file mode 100644 index 00000000000..83d3558a7cd --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc @@ -0,0 +1,177 @@ +/* + * 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_spline.hh" +#include "BKE_volume.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_bounding_box_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_bounding_box_out[] = { + {SOCK_GEOMETRY, N_("Bounding Box")}, + {SOCK_VECTOR, N_("Min")}, + {SOCK_VECTOR, N_("Max")}, + {-1, ""}, +}; + +namespace blender::nodes { + +using bke::GeometryInstanceGroup; + +static void compute_min_max_from_position_and_transform(const GeometryComponent &component, + Span<float4x4> transforms, + float3 &r_min, + float3 &r_max) +{ + GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + + for (const float4x4 &transform : transforms) { + for (const int i : positions.index_range()) { + const float3 position = positions[i]; + const float3 transformed_position = transform * position; + minmax_v3v3_v3(r_min, r_max, transformed_position); + } + } +} + +static void compute_min_max_from_volume_and_transforms(const VolumeComponent &volume_component, + Span<float4x4> transforms, + float3 &r_min, + float3 &r_max) +{ +#ifdef WITH_OPENVDB + const Volume *volume = volume_component.get_for_read(); + if (volume == nullptr) { + return; + } + for (const int i : IndexRange(BKE_volume_num_grids(volume))) { + const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); + openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + + for (const float4x4 &transform : transforms) { + openvdb::GridBase::ConstPtr instance_grid = BKE_volume_grid_shallow_transform(grid, + transform); + float3 grid_min = float3(FLT_MAX); + float3 grid_max = float3(-FLT_MAX); + if (BKE_volume_grid_bounds(instance_grid, grid_min, grid_max)) { + DO_MIN(grid_min, r_min); + DO_MAX(grid_max, r_max); + } + } + } +#else + UNUSED_VARS(volume_component, transforms, r_min, r_max); +#endif +} + +static void compute_min_max_from_curve_and_transforms(const CurveComponent &curve_component, + Span<float4x4> transforms, + float3 &r_min, + float3 &r_max) +{ + const CurveEval *curve = curve_component.get_for_read(); + if (curve == nullptr) { + return; + } + for (const SplinePtr &spline : curve->splines()) { + Span<float3> positions = spline->evaluated_positions(); + + for (const float4x4 &transform : transforms) { + for (const int i : positions.index_range()) { + const float3 position = positions[i]; + const float3 transformed_position = transform * position; + minmax_v3v3_v3(r_min, r_max, transformed_position); + } + } + } +} + +static void compute_geometry_set_instances_boundbox(const GeometrySet &geometry_set, + float3 &r_min, + float3 &r_max) +{ + Vector<GeometryInstanceGroup> set_groups; + bke::geometry_set_gather_instances(geometry_set, set_groups); + + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + Span<float4x4> transforms = set_group.transforms; + + if (set.has<PointCloudComponent>()) { + compute_min_max_from_position_and_transform( + *set.get_component_for_read<PointCloudComponent>(), transforms, r_min, r_max); + } + if (set.has<MeshComponent>()) { + compute_min_max_from_position_and_transform( + *set.get_component_for_read<MeshComponent>(), transforms, r_min, r_max); + } + if (set.has<VolumeComponent>()) { + compute_min_max_from_volume_and_transforms( + *set.get_component_for_read<VolumeComponent>(), transforms, r_min, r_max); + } + if (set.has<CurveComponent>()) { + compute_min_max_from_curve_and_transforms( + *set.get_component_for_read<CurveComponent>(), transforms, r_min, r_max); + } + } +} + +static void geo_node_bounding_box_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + float3 min = float3(FLT_MAX); + float3 max = float3(-FLT_MAX); + + if (geometry_set.has_instances()) { + compute_geometry_set_instances_boundbox(geometry_set, min, max); + } + else { + geometry_set.compute_boundbox_without_instances(&min, &max); + } + + if (min == float3(FLT_MAX)) { + params.set_output("Bounding Box", GeometrySet()); + params.set_output("Min", float3(0)); + params.set_output("Max", float3(0)); + } + else { + const float3 scale = max - min; + const float3 center = min + scale / 2.0f; + Mesh *mesh = create_cube_mesh(1.0f); + transform_mesh(mesh, center, float3(0), scale); + params.set_output("Bounding Box", GeometrySet::create_with_mesh(mesh)); + params.set_output("Min", min); + params.set_output("Max", max); + } +} + +} // namespace blender::nodes + +void register_node_type_geo_bounding_box() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_BOUNDING_BOX, "Bounding Box", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_bounding_box_in, geo_node_bounding_box_out); + ntype.geometry_node_execute = blender::nodes::geo_node_bounding_box_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index 8991a26ba4b..b2dc4661e9c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -14,8 +14,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "BLI_math_matrix.h" #include "DNA_collection_types.h" @@ -23,8 +21,19 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_collection_info_in[] = { - {SOCK_COLLECTION, N_("Collection")}, + {SOCK_COLLECTION, + N_("Collection"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_HIDE_LABEL}, {-1, ""}, }; @@ -40,11 +49,17 @@ static void geo_node_collection_info_layout(uiLayout *layout, bContext *UNUSED(C namespace blender::nodes { +static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( + sizeof(NodeGeometryCollectionInfo), __func__); + data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; + node->storage = data; +} + static void geo_node_collection_info_exec(GeoNodeExecParams params) { - bke::PersistentCollectionHandle collection_handle = - params.extract_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); GeometrySet geometry_set_out; @@ -58,10 +73,6 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); float transform_mat[4][4]; @@ -73,17 +84,11 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) mul_m4_m4_pre(transform_mat, self_object->imat); } - instances.add_instance(instance, transform_mat, -1); - params.set_output("Geometry", geometry_set_out); -} + const int handle = instances.add_reference(*collection); + instances.add_instance(handle, transform_mat, -1); -static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( - sizeof(NodeGeometryCollectionInfo), __func__); - data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; - node->storage = data; + params.set_output("Geometry", geometry_set_out); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index 441ad6bdc13..e2bb7e9f939 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -43,3 +43,17 @@ void register_node_type_geo_group(void) nodeRegisterType(&ntype); } + +void register_node_type_geo_custom_group(bNodeType *ntype) +{ + /* These methods can be overridden but need a default implementation otherwise. */ + if (ntype->poll == nullptr) { + ntype->poll = geo_node_poll_default; + } + if (ntype->insert_link == nullptr) { + ntype->insert_link = node_insert_link_default; + } + if (ntype->update_internal_links == nullptr) { + ntype->update_internal_links = node_update_internal_links_default; + } +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc new file mode 100644 index 00000000000..b1b17a321b8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -0,0 +1,321 @@ +/* + * 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_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +#ifdef WITH_BULLET +# include "RBI_hull_api.h" +#endif + +static bNodeSocketTemplate geo_node_convex_hull_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_convex_hull_out[] = { + {SOCK_GEOMETRY, N_("Convex Hull")}, + {-1, ""}, +}; + +namespace blender::nodes { + +using bke::GeometryInstanceGroup; + +#ifdef WITH_BULLET + +static Mesh *hull_from_bullet(const Mesh *mesh, Span<float3> coords) +{ + plConvexHull hull = plConvexHullCompute((float(*)[3])coords.data(), coords.size()); + + const int num_verts = plConvexHullNumVertices(hull); + const int num_faces = num_verts <= 2 ? 0 : plConvexHullNumFaces(hull); + const int num_loops = num_verts <= 2 ? 0 : plConvexHullNumLoops(hull); + /* Half as many edges as loops, because the mesh is manifold. */ + const int num_edges = num_verts == 2 ? 1 : num_verts < 2 ? 0 : num_loops / 2; + + /* Create Mesh *result with proper capacity. */ + Mesh *result; + if (mesh) { + result = BKE_mesh_new_nomain_from_template( + mesh, num_verts, num_edges, 0, num_loops, num_faces); + } + else { + result = BKE_mesh_new_nomain(num_verts, num_edges, 0, num_loops, num_faces); + BKE_id_material_eval_ensure_default_slot(&result->id); + } + + /* Copy vertices. */ + for (const int i : IndexRange(num_verts)) { + float co[3]; + int original_index; + plConvexHullGetVertex(hull, i, co, &original_index); + + if (original_index >= 0 && original_index < coords.size()) { +# if 0 /* Disabled because it only works for meshes, not predictable enough. */ + /* Copy custom data on vertices, like vertex groups etc. */ + if (mesh && original_index < mesh->totvert) { + CustomData_copy_data(&mesh->vdata, &result->vdata, (int)original_index, (int)i, 1); + } +# endif + /* Copy the position of the original point. */ + copy_v3_v3(result->mvert[i].co, co); + } + else { + BLI_assert(!"Unexpected new vertex in hull output"); + } + } + + /* Copy edges and loops. */ + + /* NOTE: ConvexHull from Bullet uses a half-edge data structure + * for its mesh. To convert that, each half-edge needs to be converted + * to a loop and edges need to be created from that. */ + Array<MLoop> mloop_src(num_loops); + uint edge_index = 0; + for (const int i : IndexRange(num_loops)) { + int v_from; + int v_to; + plConvexHullGetLoop(hull, i, &v_from, &v_to); + + mloop_src[i].v = (uint)v_from; + /* Add edges for ascending order loops only. */ + if (v_from < v_to) { + MEdge &edge = result->medge[edge_index]; + edge.v1 = v_from; + edge.v2 = v_to; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + + /* Write edge index into both loops that have it. */ + int reverse_index = plConvexHullGetReversedLoopIndex(hull, i); + mloop_src[i].e = edge_index; + mloop_src[reverse_index].e = edge_index; + edge_index++; + } + } + if (num_edges == 1) { + /* In this case there are no loops. */ + MEdge &edge = result->medge[0]; + edge.v1 = 0; + edge.v2 = 1; + edge.flag |= ME_EDGEDRAW | ME_EDGERENDER | ME_LOOSEEDGE; + edge_index++; + } + BLI_assert(edge_index == num_edges); + + /* Copy faces. */ + Array<int> loops; + int j = 0; + MLoop *loop = result->mloop; + for (const int i : IndexRange(num_faces)) { + const int len = plConvexHullGetFaceSize(hull, i); + + BLI_assert(len > 2); + + /* Get face loop indices. */ + loops.reinitialize(len); + plConvexHullGetFaceLoops(hull, i, loops.data()); + + MPoly &face = result->mpoly[i]; + face.loopstart = j; + face.totloop = len; + for (const int k : IndexRange(len)) { + MLoop &src_loop = mloop_src[loops[k]]; + loop->v = src_loop.v; + loop->e = src_loop.e; + loop++; + } + j += len; + } + + plConvexHullDelete(hull); + + BKE_mesh_calc_normals(result); + return result; +} + +static Mesh *compute_hull(const GeometrySet &geometry_set) +{ + int span_count = 0; + int count = 0; + int total_size = 0; + + Span<float3> positions_span; + + if (geometry_set.has_mesh()) { + count++; + const MeshComponent *component = geometry_set.get_component_for_read<MeshComponent>(); + total_size += component->attribute_domain_size(ATTR_DOMAIN_POINT); + } + + if (geometry_set.has_pointcloud()) { + count++; + span_count++; + const PointCloudComponent *component = + geometry_set.get_component_for_read<PointCloudComponent>(); + GVArray_Typed<float3> varray = component->attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + total_size += varray->size(); + positions_span = varray->get_internal_span(); + } + + if (geometry_set.has_curve()) { + const CurveEval &curve = *geometry_set.get_curve_for_read(); + for (const SplinePtr &spline : curve.splines()) { + positions_span = spline->evaluated_positions(); + total_size += positions_span.size(); + count++; + span_count++; + } + } + + if (count == 0) { + return nullptr; + } + + /* If there is only one positions virtual array and it is already contiguous, avoid copying + * all of the positions and instead pass the span directly to the convex hull function. */ + if (span_count == 1 && count == 1) { + return hull_from_bullet(nullptr, positions_span); + } + + Array<float3> positions(total_size); + int offset = 0; + + if (geometry_set.has_mesh()) { + const MeshComponent *component = geometry_set.get_component_for_read<MeshComponent>(); + GVArray_Typed<float3> varray = component->attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + varray->materialize(positions.as_mutable_span().slice(offset, varray.size())); + offset += varray.size(); + } + + if (geometry_set.has_pointcloud()) { + const PointCloudComponent *component = + geometry_set.get_component_for_read<PointCloudComponent>(); + GVArray_Typed<float3> varray = component->attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + varray->materialize(positions.as_mutable_span().slice(offset, varray.size())); + offset += varray.size(); + } + + if (geometry_set.has_curve()) { + const CurveEval &curve = *geometry_set.get_curve_for_read(); + for (const SplinePtr &spline : curve.splines()) { + Span<float3> array = spline->evaluated_positions(); + positions.as_mutable_span().slice(offset, array.size()).copy_from(array); + offset += array.size(); + } + } + + return hull_from_bullet(geometry_set.get_mesh_for_read(), positions); +} + +static void read_positions(const GeometryComponent &component, + Span<float4x4> transforms, + Vector<float3> *r_coords) +{ + GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + + /* NOTE: could use convex hull operation here to + * cut out some vertices, before accumulating, + * but can also be done by the user beforehand. */ + + r_coords->reserve(r_coords->size() + positions.size() * transforms.size()); + for (const float4x4 &transform : transforms) { + for (const int i : positions.index_range()) { + const float3 position = positions[i]; + const float3 transformed_position = transform * position; + r_coords->append(transformed_position); + } + } +} + +static void read_curve_positions(const CurveEval &curve, + Span<float4x4> transforms, + Vector<float3> *r_coords) +{ + const Array<int> offsets = curve.evaluated_point_offsets(); + const int total_size = offsets.last(); + r_coords->reserve(r_coords->size() + total_size * transforms.size()); + for (const SplinePtr &spline : curve.splines()) { + Span<float3> positions = spline->evaluated_positions(); + for (const float4x4 &transform : transforms) { + for (const float3 &position : positions) { + r_coords->append(transform * position); + } + } + } +} + +#endif /* WITH_BULLET */ + +static void geo_node_convex_hull_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + +#ifdef WITH_BULLET + Mesh *mesh = nullptr; + if (geometry_set.has_instances()) { + Vector<GeometryInstanceGroup> set_groups; + bke::geometry_set_gather_instances(geometry_set, set_groups); + + Vector<float3> coords; + + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + Span<float4x4> transforms = set_group.transforms; + + if (set.has_pointcloud()) { + read_positions(*set.get_component_for_read<PointCloudComponent>(), transforms, &coords); + } + if (set.has_mesh()) { + read_positions(*set.get_component_for_read<MeshComponent>(), transforms, &coords); + } + if (set.has_curve()) { + read_curve_positions(*set.get_curve_for_read(), transforms, &coords); + } + } + mesh = hull_from_bullet(nullptr, coords); + } + else { + mesh = compute_hull(geometry_set); + } + params.set_output("Convex Hull", GeometrySet::create_with_mesh(mesh)); +#else + params.set_output("Convex Hull", geometry_set); +#endif /* WITH_BULLET */ +} + +} // namespace blender::nodes + +void register_node_type_geo_convex_hull() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CONVEX_HULL, "Convex Hull", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_convex_hull_in, geo_node_convex_hull_out); + ntype.geometry_node_execute = blender::nodes::geo_node_convex_hull_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc new file mode 100644 index 00000000000..306085e3b75 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc @@ -0,0 +1,58 @@ +/* + * 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_spline.hh" +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_length_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_length_out[] = { + {SOCK_FLOAT, N_("Length")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void geo_node_curve_length_exec(GeoNodeExecParams params) +{ + GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); + curve_set = bke::geometry_set_realize_instances(curve_set); + if (!curve_set.has_curve()) { + params.set_output("Length", 0.0f); + return; + } + const CurveEval &curve = *curve_set.get_curve_for_read(); + float length = 0.0f; + for (const SplinePtr &spline : curve.splines()) { + length += spline->length(); + } + params.set_output("Length", length); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_length() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_LENGTH, "Curve Length", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_length_in, geo_node_curve_length_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_length_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc new file mode 100644 index 00000000000..e879ec624c0 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -0,0 +1,211 @@ +/* + * 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_array.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_Span; +using blender::fn::GVArray_Typed; + +static bNodeSocketTemplate geo_node_curve_resample_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Count"), 10, 0, 0, 0, 1, 100000}, + {SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.001f, FLT_MAX, PROP_DISTANCE}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_resample_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveResample *data = (NodeGeometryCurveResample *)MEM_callocN( + sizeof(NodeGeometryCurveResample), __func__); + + data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + node->storage = data; +} + +static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length_socket = count_socket->next; + + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +namespace blender::nodes { + +struct SampleModeParam { + GeometryNodeCurveSampleMode mode; + std::optional<float> length; + std::optional<int> count; +}; + +static SplinePtr resample_spline(const Spline &input_spline, const int count) +{ + std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>(); + output_spline->set_cyclic(input_spline.is_cyclic()); + output_spline->normal_mode = input_spline.normal_mode; + + if (input_spline.evaluated_edges_size() < 1 || count == 1) { + output_spline->add_point(input_spline.positions().first(), + input_spline.tilts().first(), + input_spline.radii().first()); + output_spline->attributes.reallocate(1); + return output_spline; + } + + output_spline->resize(count); + + Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); + + input_spline.sample_based_on_index_factors<float3>( + input_spline.evaluated_positions(), uniform_samples, output_spline->positions()); + + input_spline.sample_based_on_index_factors<float>( + input_spline.interpolate_to_evaluated_points(input_spline.radii()), + uniform_samples, + output_spline->radii()); + + input_spline.sample_based_on_index_factors<float>( + input_spline.interpolate_to_evaluated_points(input_spline.tilts()), + uniform_samples, + output_spline->tilts()); + + output_spline->attributes.reallocate(count); + input_spline.attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name); + BLI_assert(input_attribute); + if (!output_spline->attributes.create(name, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write( + name); + if (!output_attribute) { + BLI_assert_unreachable(); + return false; + } + + input_spline.sample_based_on_index_factors( + *input_spline.interpolate_to_evaluated_points(*input_attribute), + uniform_samples, + *output_attribute); + + return true; + }, + ATTR_DOMAIN_POINT); + + return output_spline; +} + +static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, + const SampleModeParam &mode_param) +{ + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + + for (const SplinePtr &spline : input_curve.splines()) { + if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + BLI_assert(mode_param.count); + output_curve->add_spline(resample_spline(*spline, *mode_param.count)); + } + else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + BLI_assert(mode_param.length); + const float length = spline->length(); + const int count = std::max(int(length / *mode_param.length), 1); + output_curve->add_spline(resample_spline(*spline, count)); + } + } + + output_curve->attributes = input_curve.attributes; + + return output_curve; +} + +static void geo_node_resample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", GeometrySet()); + return; + } + + const CurveEval &input_curve = *geometry_set.get_curve_for_read(); + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + SampleModeParam mode_param; + mode_param.mode = mode; + if (mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + const int count = params.extract_input<int>("Count"); + if (count < 1) { + params.set_output("Geometry", GeometrySet()); + return; + } + mode_param.count.emplace(count); + } + else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + /* Don't allow asymptotic count increase for low resolution values. */ + const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + mode_param.length.emplace(resolution); + } + + std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_resample() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_RESAMPLE, "Resample Curve", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_resample_in, geo_node_curve_resample_out); + ntype.draw_buttons = geo_node_curve_resample_layout; + node_type_storage( + &ntype, "NodeGeometryCurveResample", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, geo_node_curve_resample_init); + node_type_update(&ntype, geo_node_curve_resample_update); + ntype.geometry_node_execute = blender::nodes::geo_node_resample_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc new file mode 100644 index 00000000000..b6f04352929 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -0,0 +1,313 @@ +/* + * 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_array.hh" +#include "BLI_float4x4.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_to_mesh_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_GEOMETRY, N_("Profile Curve")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_to_mesh_out[] = { + {SOCK_GEOMETRY, N_("Mesh")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void vert_extrude_to_mesh_data(const Spline &spline, + const float3 profile_vert, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + int &vert_offset, + int &edge_offset) +{ + Span<float3> positions = spline.evaluated_positions(); + + for (const int i : IndexRange(positions.size() - 1)) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset + i; + edge.v2 = vert_offset + i + 1; + edge.flag = ME_LOOSEEDGE; + } + + if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset; + edge.v2 = vert_offset + positions.size() - 1; + edge.flag = ME_LOOSEEDGE; + } + + for (const int i : positions.index_range()) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, positions[i] + profile_vert); + } +} + +static void mark_edges_sharp(MutableSpan<MEdge> edges) +{ + for (MEdge &edge : edges) { + edge.flag |= ME_SHARP; + } +} + +static void spline_extrude_to_mesh_data(const Spline &spline, + const Spline &profile_spline, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + MutableSpan<MLoop> r_loops, + MutableSpan<MPoly> r_polys, + int &vert_offset, + int &edge_offset, + int &loop_offset, + int &poly_offset) +{ + const int spline_vert_len = spline.evaluated_points_size(); + const int spline_edge_len = spline.evaluated_edges_size(); + const int profile_vert_len = profile_spline.evaluated_points_size(); + const int profile_edge_len = profile_spline.evaluated_edges_size(); + if (spline_vert_len == 0) { + return; + } + + if (profile_vert_len == 1) { + vert_extrude_to_mesh_data(spline, + profile_spline.evaluated_positions()[0], + r_verts, + r_edges, + vert_offset, + edge_offset); + return; + } + + /* Add the edges running along the length of the curve, starting at each profile vertex. */ + const int spline_edges_start = edge_offset; + for (const int i_profile : IndexRange(profile_vert_len)) { + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = next_ring_vert_offset + i_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Add the edges running along each profile ring. */ + const int profile_edges_start = edge_offset; + for (const int i_ring : IndexRange(spline_vert_len)) { + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = ring_vert_offset + i_next_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Calculate poly and corner indices. */ + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile; + + MPoly &poly = r_polys[poly_offset++]; + poly.loopstart = loop_offset; + poly.totloop = 4; + poly.flag = ME_SMOOTH; + + MLoop &loop_a = r_loops[loop_offset++]; + loop_a.v = ring_vert_offset + i_profile; + loop_a.e = ring_edge_start + i_profile; + MLoop &loop_b = r_loops[loop_offset++]; + loop_b.v = ring_vert_offset + i_next_profile; + loop_b.e = next_spline_edge_start + i_ring; + MLoop &loop_c = r_loops[loop_offset++]; + loop_c.v = next_ring_vert_offset + i_next_profile; + loop_c.e = next_ring_edge_offset + i_profile; + MLoop &loop_d = r_loops[loop_offset++]; + loop_d.v = next_ring_vert_offset + i_profile; + loop_d.e = spline_edge_start + i_ring; + } + } + + /* Calculate the positions of each profile ring profile along the spline. */ + Span<float3> positions = spline.evaluated_positions(); + Span<float3> tangents = spline.evaluated_tangents(); + Span<float3> normals = spline.evaluated_normals(); + Span<float3> profile_positions = profile_spline.evaluated_positions(); + + GVArray_Typed<float> radii = spline.interpolate_to_evaluated_points(spline.radii()); + for (const int i_ring : IndexRange(spline_vert_len)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + positions[i_ring], normals[i_ring], tangents[i_ring]); + + point_matrix.apply_scale(radii[i_ring]); + + for (const int i_profile : IndexRange(profile_vert_len)) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + + /* Mark edge loops from sharp vector control points sharp. */ + if (profile_spline.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); + Span<int> control_point_offsets = bezier_spline.control_point_offsets(); + for (const int i : IndexRange(bezier_spline.size())) { + if (bezier_spline.point_is_sharp(i)) { + mark_edges_sharp(r_edges.slice( + spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); + } + } + } +} + +static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile_curve) +{ + int profile_vert_total = 0; + int profile_edge_total = 0; + for (const SplinePtr &profile_spline : profile_curve.splines()) { + profile_vert_total += profile_spline->evaluated_points_size(); + profile_edge_total += profile_spline->evaluated_edges_size(); + } + + int vert_total = 0; + int edge_total = 0; + int poly_total = 0; + for (const SplinePtr &spline : curve.splines()) { + const int spline_vert_len = spline->evaluated_points_size(); + const int spline_edge_len = spline->evaluated_edges_size(); + vert_total += spline_vert_len * profile_vert_total; + poly_total += spline_edge_len * profile_edge_total; + + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len; + } + const int corner_total = poly_total * 4; + + if (vert_total == 0) { + return nullptr; + } + + Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); + + int vert_offset = 0; + int edge_offset = 0; + int loop_offset = 0; + int poly_offset = 0; + for (const SplinePtr &spline : curve.splines()) { + for (const SplinePtr &profile_spline : profile_curve.splines()) { + spline_extrude_to_mesh_data(*spline, + *profile_spline, + verts, + edges, + loops, + polys, + vert_offset, + edge_offset, + loop_offset, + poly_offset); + } + } + + BKE_mesh_calc_normals(mesh); + + return mesh; +} + +static CurveEval get_curve_single_vert() +{ + CurveEval curve; + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->add_point(float3(0), 0, 0.0f); + curve.add_spline(std::move(spline)); + + return curve; +} + +static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) +{ + GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); + GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); + + curve_set = bke::geometry_set_realize_instances(curve_set); + profile_set = bke::geometry_set_realize_instances(profile_set); + + if (!curve_set.has_curve()) { + params.set_output("Mesh", GeometrySet()); + return; + } + + const CurveEval *profile_curve = profile_set.get_curve_for_read(); + + static const CurveEval vert_curve = get_curve_single_vert(); + + Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(), + (profile_curve == nullptr) ? vert_curve : *profile_curve); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_to_mesh() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_to_mesh_in, geo_node_curve_to_mesh_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_mesh_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc new file mode 100644 index 00000000000..910adc467d6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -0,0 +1,678 @@ +/* + * 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_array.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_pointcloud.h" +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +using blender::bke::CustomDataAttributes; + +/* Code from the mask modifier in MOD_mask.cc. */ +extern void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + blender::Span<int> vertex_map); +extern void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + blender::Span<int> vertex_map, + blender::Span<int> edge_map); +extern void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + blender::Span<int> vertex_map, + blender::Span<int> edge_map, + blender::Span<int> masked_poly_indices, + blender::Span<int> new_loop_starts); + +static bNodeSocketTemplate geo_node_delete_geometry_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Selection")}, + {SOCK_BOOLEAN, N_("Invert")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_delete_geometry_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +template<typename T> static void copy_data(Span<T> data, MutableSpan<T> r_data, IndexMask mask) +{ + for (const int i_out : mask.index_range()) { + r_data[i_out] = data[mask[i_out]]; + } +} + +static void spline_copy_builtin_attributes(const Spline &spline, + Spline &r_spline, + const IndexMask mask) +{ + copy_data(spline.positions(), r_spline.positions(), mask); + copy_data(spline.radii(), r_spline.radii(), mask); + copy_data(spline.tilts(), r_spline.tilts(), mask); + switch (spline.type()) { + case Spline::Type::Poly: + break; + case Spline::Type::Bezier: { + const BezierSpline &src = static_cast<const BezierSpline &>(spline); + BezierSpline &dst = static_cast<BezierSpline &>(r_spline); + copy_data(src.handle_positions_left(), dst.handle_positions_left(), mask); + copy_data(src.handle_positions_right(), dst.handle_positions_right(), mask); + copy_data(src.handle_types_left(), dst.handle_types_left(), mask); + copy_data(src.handle_types_right(), dst.handle_types_right(), mask); + break; + } + case Spline::Type::NURBS: { + const NURBSpline &src = static_cast<const NURBSpline &>(spline); + NURBSpline &dst = static_cast<NURBSpline &>(r_spline); + copy_data(src.weights(), dst.weights(), mask); + break; + } + } +} + +static void copy_dynamic_attributes(const CustomDataAttributes &src, + CustomDataAttributes &dst, + const IndexMask mask) +{ + src.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.get_for_read(name); + BLI_assert(src_attribute); + + if (!dst.create(name, meta_data.data_type)) { + /* Since the source spline of the same type had the attribute, adding it should work. */ + BLI_assert_unreachable(); + } + + std::optional<GMutableSpan> new_attribute = dst.get_for_write(name); + BLI_assert(new_attribute); + + attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) { + using T = decltype(dummy); + copy_data(src_attribute->typed<T>(), new_attribute->typed<T>(), mask); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr spline_delete(const Spline &spline, const IndexMask mask) +{ + SplinePtr new_spline = spline.copy_settings(); + new_spline->resize(mask.size()); + + spline_copy_builtin_attributes(spline, *new_spline, mask); + copy_dynamic_attributes(spline.attributes, new_spline->attributes, mask); + + return new_spline; +} + +static std::unique_ptr<CurveEval> curve_delete(const CurveEval &input_curve, + const StringRef name, + const bool invert) +{ + Span<SplinePtr> input_splines = input_curve.splines(); + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + + /* Keep track of which splines were copied to the result to copy spline domain attributes. */ + Vector<int64_t> copied_splines; + + if (input_curve.attributes.get_for_read(name)) { + GVArray_Typed<bool> selection = input_curve.attributes.get_for_read<bool>(name, false); + for (const int i : input_splines.index_range()) { + if (selection[i] == invert) { + output_curve->add_spline(input_splines[i]->copy()); + copied_splines.append(i); + } + } + } + else { + /* Reuse index vector for each spline. */ + Vector<int64_t> indices_to_copy; + + for (const int i : input_splines.index_range()) { + const Spline &spline = *input_splines[i]; + GVArray_Typed<bool> selection = spline.attributes.get_for_read<bool>(name, false); + + indices_to_copy.clear(); + for (const int i_point : IndexRange(spline.size())) { + if (selection[i_point] == invert) { + indices_to_copy.append(i_point); + } + } + + /* Avoid creating an empty spline. */ + if (indices_to_copy.is_empty()) { + continue; + } + + SplinePtr new_spline = spline_delete(spline, IndexMask(indices_to_copy)); + output_curve->add_spline(std::move(new_spline)); + copied_splines.append(i); + } + } + + if (copied_splines.is_empty()) { + return {}; + } + + output_curve->attributes.reallocate(output_curve->splines().size()); + copy_dynamic_attributes( + input_curve.attributes, output_curve->attributes, IndexMask(copied_splines)); + + return output_curve; +} + +static void delete_curve_selection(const CurveComponent &in_component, + CurveComponent &r_component, + const StringRef selection_name, + const bool invert) +{ + std::unique_ptr<CurveEval> r_curve = curve_delete( + *in_component.get_for_read(), selection_name, invert); + if (r_curve) { + r_component.replace(r_curve.release()); + } + else { + r_component.clear(); + } +} + +static void delete_point_cloud_selection(const PointCloudComponent &in_component, + PointCloudComponent &out_component, + const StringRef selection_name, + const bool invert) +{ + const GVArray_Typed<bool> selection_attribute = in_component.attribute_get_for_read<bool>( + selection_name, ATTR_DOMAIN_POINT, false); + VArray_Span<bool> selection{selection_attribute}; + + const int total = selection.count(invert); + if (total == 0) { + out_component.clear(); + return; + } + out_component.replace(BKE_pointcloud_new_nomain(total)); + + /* Invert the inversion, because this deletes the selected points instead of keeping them. */ + copy_point_attributes_based_on_mask(in_component, out_component, selection, !invert); +} + +static void compute_selected_vertices_from_vertex_selection(const VArray<bool> &vertex_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + uint *r_num_selected_vertices) +{ + BLI_assert(vertex_selection.size() == r_vertex_map.size()); + + uint num_selected_vertices = 0; + for (const int i : r_vertex_map.index_range()) { + if (vertex_selection[i] != invert) { + r_vertex_map[i] = num_selected_vertices; + num_selected_vertices++; + } + else { + r_vertex_map[i] = -1; + } + } + + *r_num_selected_vertices = num_selected_vertices; +} + +static void compute_selected_edges_from_vertex_selection(const Mesh &mesh, + const VArray<bool> &vertex_selection, + const bool invert, + MutableSpan<int> r_edge_map, + uint *r_num_selected_edges) +{ + BLI_assert(mesh.totedge == r_edge_map.size()); + + uint num_selected_edges = 0; + for (const int i : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[i]; + + /* Only add the edge if both vertices will be in the new mesh. */ + if (vertex_selection[edge.v1] != invert && vertex_selection[edge.v2] != invert) { + r_edge_map[i] = num_selected_edges; + num_selected_edges++; + } + else { + r_edge_map[i] = -1; + } + } + + *r_num_selected_edges = num_selected_edges; +} + +static void compute_selected_polygons_from_vertex_selection(const Mesh &mesh, + const VArray<bool> &vertex_selection, + const bool invert, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_polys, + uint *r_num_selected_loops) +{ + BLI_assert(mesh.totvert == vertex_selection.size()); + + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + uint num_selected_loops = 0; + for (const int i : IndexRange(mesh.totpoly)) { + const MPoly &poly_src = mesh.mpoly[i]; + + bool all_verts_in_selection = true; + Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop); + for (const MLoop &loop : loops_src) { + if (vertex_selection[loop.v] == invert) { + all_verts_in_selection = false; + break; + } + } + + if (all_verts_in_selection) { + r_selected_poly_indices.append_unchecked(i); + r_loop_starts.append_unchecked(num_selected_loops); + num_selected_loops += poly_src.totloop; + } + } + + *r_num_selected_polys = r_selected_poly_indices.size(); + *r_num_selected_loops = num_selected_loops; +} + +/** + * Checks for every edge if it is in `edge_selection`. If it is, then the two vertices of the edge + * are kept along with the edge. + */ +static void compute_selected_vertices_and_edges_from_edge_selection( + const Mesh &mesh, + const VArray<bool> &edge_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + uint *r_num_selected_vertices, + uint *r_num_selected_edges) +{ + BLI_assert(mesh.totedge == edge_selection.size()); + + uint num_selected_edges = 0; + uint num_selected_vertices = 0; + for (const int i : IndexRange(mesh.totedge)) { + const MEdge &edge = mesh.medge[i]; + if (edge_selection[i] != invert) { + r_edge_map[i] = num_selected_edges; + num_selected_edges++; + if (r_vertex_map[edge.v1] == -1) { + r_vertex_map[edge.v1] = num_selected_vertices; + num_selected_vertices++; + } + if (r_vertex_map[edge.v2] == -1) { + r_vertex_map[edge.v2] = num_selected_vertices; + num_selected_vertices++; + } + } + else { + r_edge_map[i] = -1; + } + } + + *r_num_selected_vertices = num_selected_vertices; + *r_num_selected_edges = num_selected_edges; +} + +/** + * Checks for every polygon if all the edges are in `edge_selection`. If they are, then that + * polygon is kept. + */ +static void compute_selected_polygons_from_edge_selection(const Mesh &mesh, + const VArray<bool> &edge_selection, + const bool invert, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_polys, + uint *r_num_selected_loops) +{ + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + uint num_selected_loops = 0; + for (const int i : IndexRange(mesh.totpoly)) { + const MPoly &poly_src = mesh.mpoly[i]; + + bool all_edges_in_selection = true; + Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop); + for (const MLoop &loop : loops_src) { + if (edge_selection[loop.e] == invert) { + all_edges_in_selection = false; + break; + } + } + + if (all_edges_in_selection) { + r_selected_poly_indices.append_unchecked(i); + r_loop_starts.append_unchecked(num_selected_loops); + num_selected_loops += poly_src.totloop; + } + } + + *r_num_selected_polys = r_selected_poly_indices.size(); + *r_num_selected_loops = num_selected_loops; +} + +/** + * Checks for every vertex if it is in `vertex_selection`. The polygons and edges are kept if all + * vertices of that polygon or edge are in the selection. + */ +static void compute_selected_mesh_data_from_vertex_selection(const Mesh &mesh, + const VArray<bool> &vertex_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_vertices, + uint *r_num_selected_edges, + uint *r_num_selected_polys, + uint *r_num_selected_loops) +{ + compute_selected_vertices_from_vertex_selection( + vertex_selection, invert, r_vertex_map, r_num_selected_vertices); + + compute_selected_edges_from_vertex_selection( + mesh, vertex_selection, invert, r_edge_map, r_num_selected_edges); + + compute_selected_polygons_from_vertex_selection(mesh, + vertex_selection, + invert, + r_selected_poly_indices, + r_loop_starts, + r_num_selected_polys, + r_num_selected_loops); +} + +/** + * Checks for every edge if it is in `edge_selection`. If it is, the vertices belonging to + * that edge are kept as well. The polygons are kept if all edges are in the selection. + */ +static void compute_selected_mesh_data_from_edge_selection(const Mesh &mesh, + const VArray<bool> &edge_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_vertices, + uint *r_num_selected_edges, + uint *r_num_selected_polys, + uint *r_num_selected_loops) +{ + r_vertex_map.fill(-1); + compute_selected_vertices_and_edges_from_edge_selection(mesh, + edge_selection, + invert, + r_vertex_map, + r_edge_map, + r_num_selected_vertices, + r_num_selected_edges); + compute_selected_polygons_from_edge_selection(mesh, + edge_selection, + invert, + r_selected_poly_indices, + r_loop_starts, + r_num_selected_polys, + r_num_selected_loops); +} + +/** + * Checks for every polygon if it is in `poly_selection`. If it is, the edges and vertices + * belonging to that polygon are kept as well. + */ +static void compute_selected_mesh_data_from_poly_selection(const Mesh &mesh, + const VArray<bool> &poly_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_vertices, + uint *r_num_selected_edges, + uint *r_num_selected_polys, + uint *r_num_selected_loops) +{ + BLI_assert(mesh.totpoly == poly_selection.size()); + BLI_assert(mesh.totedge == r_edge_map.size()); + r_vertex_map.fill(-1); + r_edge_map.fill(-1); + + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + uint num_selected_loops = 0; + uint num_selected_vertices = 0; + uint num_selected_edges = 0; + for (const int i : IndexRange(mesh.totpoly)) { + const MPoly &poly_src = mesh.mpoly[i]; + /* We keep this one. */ + if (poly_selection[i] != invert) { + r_selected_poly_indices.append_unchecked(i); + r_loop_starts.append_unchecked(num_selected_loops); + num_selected_loops += poly_src.totloop; + + /* Add the vertices and the edges. */ + Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop); + for (const MLoop &loop : loops_src) { + /* Check first if it has not yet been added. */ + if (r_vertex_map[loop.v] == -1) { + r_vertex_map[loop.v] = num_selected_vertices; + num_selected_vertices++; + } + if (r_edge_map[loop.e] == -1) { + r_edge_map[loop.e] = num_selected_edges; + num_selected_edges++; + } + } + } + } + *r_num_selected_vertices = num_selected_vertices; + *r_num_selected_edges = num_selected_edges; + *r_num_selected_polys = r_selected_poly_indices.size(); + *r_num_selected_loops = num_selected_loops; +} + +using FillMapsFunction = void (*)(const Mesh &mesh, + const VArray<bool> &selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + uint *r_num_selected_vertices, + uint *r_num_selected_edges, + uint *r_num_selected_polys, + uint *r_num_selected_loops); + +/** + * Delete the parts of the mesh that are in the selection. The `fill_maps_function` + * depends on the selection type: vertices, edges or faces. + */ +static Mesh *delete_mesh_selection(const Mesh &mesh_in, + const VArray<bool> &selection, + const bool invert, + FillMapsFunction fill_maps_function) +{ + Array<int> vertex_map(mesh_in.totvert); + uint num_selected_vertices; + + Array<int> edge_map(mesh_in.totedge); + uint num_selected_edges; + + Vector<int> selected_poly_indices; + Vector<int> new_loop_starts; + uint num_selected_polys; + uint num_selected_loops; + + /* Fill all the maps based on the selection. We delete everything + * in the selection instead of keeping it, so we need to invert it. */ + fill_maps_function(mesh_in, + selection, + !invert, + vertex_map, + edge_map, + selected_poly_indices, + new_loop_starts, + &num_selected_vertices, + &num_selected_edges, + &num_selected_polys, + &num_selected_loops); + + Mesh *result = BKE_mesh_new_nomain_from_template(&mesh_in, + num_selected_vertices, + num_selected_edges, + 0, + num_selected_loops, + num_selected_polys); + + /* Copy the selected parts of the mesh over to the new mesh. */ + copy_masked_vertices_to_new_mesh(mesh_in, *result, vertex_map); + copy_masked_edges_to_new_mesh(mesh_in, *result, vertex_map, edge_map); + copy_masked_polys_to_new_mesh( + mesh_in, *result, vertex_map, edge_map, selected_poly_indices, new_loop_starts); + BKE_mesh_calc_edges_loose(result); + /* Tag to recalculate normals later. */ + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + + return result; +} + +static AttributeDomain get_mesh_selection_domain(MeshComponent &component, const StringRef name) +{ + std::optional<AttributeMetaData> selection_attribute = component.attribute_get_meta_data(name); + if (!selection_attribute) { + /* The node will not do anything in this case, but this function must return something. */ + return ATTR_DOMAIN_POINT; + } + + /* Corners can't be deleted separately, so interpolate corner attributes + * to the face domain. Note that this choice is somewhat arbitrary. */ + if (selection_attribute->domain == ATTR_DOMAIN_CORNER) { + return ATTR_DOMAIN_FACE; + } + + return selection_attribute->domain; +} + +static void delete_mesh_selection(MeshComponent &component, + const Mesh &mesh_in, + const StringRef selection_name, + const bool invert) +{ + /* Figure out the best domain to use. */ + const AttributeDomain selection_domain = get_mesh_selection_domain(component, selection_name); + + /* This already checks if the attribute exists, and displays a warning in that case. */ + GVArray_Typed<bool> selection = component.attribute_get_for_read<bool>( + selection_name, selection_domain, false); + + /* Check if there is anything to delete. */ + bool delete_nothing = true; + for (const int i : selection.index_range()) { + if (selection[i] != invert) { + delete_nothing = false; + break; + } + } + if (delete_nothing) { + return; + } + + Mesh *mesh_out; + switch (selection_domain) { + case ATTR_DOMAIN_POINT: + mesh_out = delete_mesh_selection( + mesh_in, selection, invert, compute_selected_mesh_data_from_vertex_selection); + break; + case ATTR_DOMAIN_EDGE: + mesh_out = delete_mesh_selection( + mesh_in, selection, invert, compute_selected_mesh_data_from_edge_selection); + break; + case ATTR_DOMAIN_FACE: + mesh_out = delete_mesh_selection( + mesh_in, selection, invert, compute_selected_mesh_data_from_poly_selection); + break; + default: + BLI_assert_unreachable(); + mesh_out = nullptr; + break; + } + component.replace_mesh_but_keep_vertex_group_names(mesh_out); +} + +static void geo_node_delete_geometry_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + const bool invert = params.extract_input<bool>("Invert"); + const std::string selection_name = params.extract_input<std::string>("Selection"); + if (selection_name.empty()) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + GeometrySet out_set(geometry_set); + if (geometry_set.has<PointCloudComponent>()) { + delete_point_cloud_selection(*geometry_set.get_component_for_read<PointCloudComponent>(), + out_set.get_component_for_write<PointCloudComponent>(), + selection_name, + invert); + } + if (geometry_set.has<MeshComponent>()) { + delete_mesh_selection(out_set.get_component_for_write<MeshComponent>(), + *geometry_set.get_mesh_for_read(), + selection_name, + invert); + } + if (geometry_set.has<CurveComponent>()) { + delete_curve_selection(*geometry_set.get_component_for_read<CurveComponent>(), + out_set.get_component_for_write<CurveComponent>(), + selection_name, + invert); + } + + params.set_output("Geometry", std::move(out_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_delete_geometry() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_delete_geometry_in, geo_node_delete_geometry_out); + ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec; + nodeRegisterType(&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 index 1c794d1f43e..740b828d503 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -14,9 +14,6 @@ * 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" @@ -47,6 +44,7 @@ static bNodeSocketTemplate geo_node_edge_split_out[] = { }; namespace blender::nodes { + static void geo_node_edge_split_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -85,6 +83,7 @@ static void geo_node_edge_split_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_edge_split() diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_material.cc b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc new file mode 100644 index 00000000000..6bad71a62a2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc @@ -0,0 +1,51 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_input_material_out[] = { + {SOCK_MATERIAL, N_("Material")}, + {-1, ""}, +}; + +static void geo_node_input_material_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "material", 0, "", ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_input_material_exec(GeoNodeExecParams params) +{ + Material *material = (Material *)params.node().id; + params.set_output("Material", material); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_material() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL, "Material", NODE_CLASS_INPUT, 0); + node_type_socket_templates(&ntype, nullptr, geo_node_input_material_out); + ntype.draw_buttons = geo_node_input_material_layout; + ntype.geometry_node_execute = blender::nodes::geo_node_input_material_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc b/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc index 667beaa1722..ec875b9f983 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc @@ -14,10 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "DEG_depsgraph_query.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_is_viewport_out[] = { {SOCK_BOOLEAN, N_("Is Viewport")}, {-1, ""}, diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 52512769a47..adfd924f185 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -14,9 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -56,6 +58,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const MeshComponent *mesh_component : src_components) { const Mesh *mesh = mesh_component->get_for_read(); totverts += mesh->totvert; @@ -66,12 +70,22 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent cd_dirty_poly |= mesh->runtime.cd_dirty_poly; cd_dirty_edge |= mesh->runtime.cd_dirty_edge; cd_dirty_loop |= mesh->runtime.cd_dirty_loop; + + for (const int slot_index : IndexRange(mesh->totcol)) { + Material *material = mesh->mat[slot_index]; + materials.add(material); + } } 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); + for (const int i : IndexRange(materials.size())) { + Material *material = materials[i]; + BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); + } + new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; @@ -87,6 +101,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent continue; } + Array<int> material_index_map(mesh->totcol); + for (const int i : IndexRange(mesh->totcol)) { + Material *material = mesh->mat[i]; + const int new_material_index = materials.index_of(material); + material_index_map[i] = new_material_index; + } + for (const int i : IndexRange(mesh->totvert)) { const MVert &old_vert = mesh->mvert[i]; MVert &new_vert = new_mesh->mvert[vert_offset + i]; @@ -112,6 +133,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; new_poly = old_poly; new_poly.loopstart += loop_offset; + if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh->totcol) { + new_poly.mat_nr = material_index_map[new_poly.mat_nr]; + } + else { + /* The material index was invalid before. */ + new_poly.mat_nr = 0; + } } vert_offset += mesh->totvert; @@ -149,10 +177,10 @@ static void determine_final_data_type_and_domain(Span<const GeometryComponent *> Vector<CustomDataType> data_types; Vector<AttributeDomain> domains; for (const GeometryComponent *component : components) { - ReadAttributePtr attribute = component->attribute_try_get_for_read(attribute_name); + ReadAttributeLookup attribute = component->attribute_try_get_for_read(attribute_name); if (attribute) { - data_types.append(attribute->custom_data_type()); - domains.append(attribute->domain()); + data_types.append(bke::cpp_type_to_custom_data_type(attribute.varray->type())); + domains.append(attribute.domain); } } @@ -164,7 +192,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, StringRef attribute_name, const CustomDataType data_type, const AttributeDomain domain, - fn::GMutableSpan dst_span) + GMutableSpan dst_span) { const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); @@ -175,10 +203,10 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, if (domain_size == 0) { continue; } - ReadAttributePtr read_attribute = component->attribute_get_for_read( + GVArrayPtr read_attribute = component->attribute_get_for_read( attribute_name, domain, data_type, nullptr); - fn::GSpan src_span = read_attribute->get_span(); + GVArray_GSpan src_span{*read_attribute}; 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); @@ -201,16 +229,14 @@ static void join_attributes(Span<const GeometryComponent *> src_components, AttributeDomain domain; determine_final_data_type_and_domain(src_components, attribute_name, &data_type, &domain); - OutputAttributePtr write_attribute = result.attribute_try_get_for_output( + OutputAttribute write_attribute = result.attribute_try_get_for_output_only( attribute_name, domain, data_type); - if (!write_attribute || - &write_attribute->cpp_type() != bke::custom_data_type_to_cpp_type(data_type) || - write_attribute->domain() != domain) { + if (!write_attribute) { continue; } - fn::GMutableSpan dst_span = write_attribute->get_span_for_write_only(); + GMutableSpan dst_span = write_attribute.as_span(); fill_new_attribute(src_components, attribute_name, data_type, domain, dst_span); - write_attribute.apply_span_and_save(); + write_attribute.save(); } } @@ -244,12 +270,30 @@ static void join_components(Span<const PointCloudComponent *> src_components, Ge 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<InstancedData> instanced_data = component->instanced_data(); - Span<float4x4> transforms = component->transforms(); - for (const int i : IndexRange(size)) { - dst_component.add_instance(instanced_data[i], transforms[i]); + + int tot_instances = 0; + for (const InstancesComponent *src_component : src_components) { + tot_instances += src_component->instances_amount(); + } + dst_component.reserve(tot_instances); + + for (const InstancesComponent *src_component : src_components) { + Span<InstanceReference> src_references = src_component->references(); + Array<int> handle_map(src_references.size()); + for (const int src_handle : src_references.index_range()) { + handle_map[src_handle] = dst_component.add_reference(src_references[src_handle]); + } + + Span<float4x4> src_transforms = src_component->instance_transforms(); + Span<int> src_ids = src_component->instance_ids(); + Span<int> src_reference_handles = src_component->instance_reference_handles(); + + for (const int i : src_transforms.index_range()) { + const int src_handle = src_reference_handles[i]; + const int dst_handle = handle_map[src_handle]; + const float4x4 &transform = src_transforms[i]; + const int id = src_ids[i]; + dst_component.add_instance(dst_handle, transform, id); } } } @@ -262,6 +306,48 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet UNUSED_VARS(src_components, dst_component); } +static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result) +{ + Vector<CurveComponent *> src_components; + for (GeometrySet &geometry_set : src_geometry_sets) { + if (geometry_set.has_curve()) { + /* Retrieving with write access seems counterintuitive, but it can allow avoiding a copy + * in the case where the input spline has no other users, because the splines can be + * moved from the source curve rather than copied from a read-only source. Retrieving + * the curve for write will make a copy only when it has a user elsewhere. */ + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + src_components.append(&component); + } + } + + if (src_components.size() == 0) { + return; + } + if (src_components.size() == 1) { + result.add(*src_components[0]); + return; + } + + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + CurveEval *dst_curve = new CurveEval(); + for (CurveComponent *component : src_components) { + CurveEval *src_curve = component->get_for_write(); + for (SplinePtr &spline : src_curve->splines()) { + dst_curve->add_spline(std::move(spline)); + } + } + + /* For now, remove all custom attributes, since they might have different types, + * or an attribute might not exist on all splines. */ + dst_curve->attributes.reallocate(dst_curve->splines().size()); + CustomData_reset(&dst_curve->attributes.data); + for (SplinePtr &spline : dst_curve->splines()) { + CustomData_reset(&spline->attributes.data); + } + + dst_component.replace(dst_curve); +} + template<typename Component> static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result) { @@ -292,6 +378,7 @@ static void geo_node_join_geometry_exec(GeoNodeExecParams params) join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result); join_component_type<InstancesComponent>(geometry_sets, geometry_set_result); join_component_type<VolumeComponent>(geometry_sets, geometry_set_result); + join_curve_components(geometry_sets, geometry_set_result); params.set_output("Geometry", std::move(geometry_set_result)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc new file mode 100644 index 00000000000..02b2d685bdd --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -0,0 +1,107 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_material_assign_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, + N_("Material"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_HIDE_LABEL}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_material_assign_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +{ + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } + } + if (new_material_index == -1) { + /* Append a new material index. */ + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); + } + + mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); + for (const int i : IndexRange(mesh.totpoly)) { + if (face_mask[i]) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } + } +} + +static void geo_node_material_assign_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string mask_name = params.extract_input<std::string>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( + mask_name, ATTR_DOMAIN_FACE, true); + assign_material_to_faces(*mesh, face_mask, material); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_assign() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_material_assign_in, geo_node_material_assign_out); + ntype.geometry_node_execute = blender::nodes::geo_node_material_assign_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc new file mode 100644 index 00000000000..40ecab98dea --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -0,0 +1,75 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_material_replace_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, N_("Old")}, + {SOCK_MATERIAL, N_("New")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_material_replace_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void geo_node_material_replace_exec(GeoNodeExecParams params) +{ + Material *old_material = params.extract_input<Material *>("Old"); + Material *new_material = params.extract_input<Material *>("New"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + for (const int i : IndexRange(mesh->totcol)) { + if (mesh->mat[i] == old_material) { + mesh->mat[i] = new_material; + } + } + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_replace() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_MATERIAL_REPLACE, "Material Replace", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_material_replace_in, geo_node_material_replace_out); + ntype.geometry_node_execute = blender::nodes::geo_node_material_replace_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc index 01016ec9b44..2915a17d2c8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc @@ -17,6 +17,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" @@ -119,6 +120,7 @@ static Mesh *create_circle_mesh(const float radius, 0, circle_corner_total(fill_type, verts_num), circle_face_total(fill_type, verts_num)); + BKE_id_material_eval_ensure_default_slot(&mesh->id); MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; @@ -161,6 +163,7 @@ static Mesh *create_circle_mesh(const float radius, MEdge &edge = edges[verts_num + i]; edge.v1 = verts_num; edge.v2 = i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index e9228a2942b..925ed0f8da8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -17,14 +17,12 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_lib_id.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" #include "UI_resources.h" -#include "bmesh.h" - #include "node_geometry_util.hh" static bNodeSocketTemplate geo_node_mesh_primitive_cone_in[] = { @@ -189,45 +187,358 @@ static int face_total(const GeometryNodeMeshCircleFillType fill_type, return face_total; } +static void calculate_uvs(Mesh *mesh, + const bool top_is_point, + const bool bottom_is_point, + const int verts_num, + const GeometryNodeMeshCircleFillType fill_type) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + + Array<float2> circle(verts_num); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num); + for (const int i : IndexRange(verts_num)) { + circle[i].x = std::cos(angle) * 0.225f + 0.25f; + circle[i].y = std::sin(angle) * 0.225f + 0.25f; + angle += angle_delta; + } + + int loop_index = 0; + if (!top_is_point) { + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + for (const int i : IndexRange(verts_num)) { + uvs[loop_index++] = circle[i]; + } + } + else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + uvs[loop_index++] = circle[i]; + uvs[loop_index++] = circle[(i + 1) % verts_num]; + uvs[loop_index++] = float2(0.25f, 0.25f); + } + } + } + + /* Create side corners and faces. */ + if (!top_is_point && !bottom_is_point) { + const float bottom = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; + /* Quads connect the top and bottom. */ + for (const int i : IndexRange(verts_num)) { + const float vert = static_cast<float>(i); + uvs[loop_index++] = float2(vert / verts_num, bottom); + uvs[loop_index++] = float2(vert / verts_num, 1.0f); + uvs[loop_index++] = float2((vert + 1.0f) / verts_num, 1.0f); + uvs[loop_index++] = float2((vert + 1.0f) / verts_num, bottom); + } + } + else { + /* Triangles connect the top and bottom section. */ + if (!top_is_point) { + for (const int i : IndexRange(verts_num)) { + uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); + uvs[loop_index++] = float2(0.75f, 0.25f); + uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); + } + } + else { + BLI_assert(!bottom_is_point); + for (const int i : IndexRange(verts_num)) { + uvs[loop_index++] = circle[i]; + uvs[loop_index++] = circle[(i + 1) % verts_num]; + uvs[loop_index++] = float2(0.25f, 0.25f); + } + } + } + + /* Create bottom corners and faces. */ + if (!bottom_is_point) { + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + for (const int i : IndexRange(verts_num)) { + /* Go backwards because of reversed face normal. */ + uvs[loop_index++] = circle[verts_num - 1 - i] + float2(0.5f, 0.0f); + } + } + else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); + uvs[loop_index++] = float2(0.75f, 0.25f); + uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); + } + } + } + + uv_attribute.save(); +} + Mesh *create_cylinder_or_cone_mesh(const float radius_top, const float radius_bottom, const float depth, const int verts_num, const GeometryNodeMeshCircleFillType fill_type) { - const float4x4 transform = float4x4::identity(); - const bool top_is_point = radius_top == 0.0f; const bool bottom_is_point = radius_bottom == 0.0f; + const float height = depth * 0.5f; + /* Handle the case of a line / single point before everything else to avoid + * the need to check for it later. */ + if (top_is_point && bottom_is_point) { + const bool single_vertex = height == 0.0f; + Mesh *mesh = BKE_mesh_new_nomain(single_vertex ? 1 : 2, single_vertex ? 0 : 1, 0, 0, 0); + copy_v3_v3(mesh->mvert[0].co, float3(0.0f, 0.0f, height)); + if (single_vertex) { + const short up[3] = {0, 0, SHRT_MAX}; + copy_v3_v3_short(mesh->mvert[0].no, up); + return mesh; + } + copy_v3_v3(mesh->mvert[1].co, float3(0.0f, 0.0f, -height)); + mesh->medge[0].v1 = 0; + mesh->medge[0].v2 = 1; + mesh->medge[0].flag |= ME_LOOSEEDGE; + BKE_mesh_calc_normals(mesh); + return mesh; + } - const BMeshCreateParams bmcp = {true}; - const BMAllocTemplate allocsize = { + Mesh *mesh = BKE_mesh_new_nomain( vert_total(fill_type, verts_num, top_is_point, bottom_is_point), edge_total(fill_type, verts_num, top_is_point, bottom_is_point), + 0, corner_total(fill_type, verts_num, top_is_point, bottom_is_point), - face_total(fill_type, verts_num, top_is_point, bottom_is_point)}; - BMesh *bm = BM_mesh_create(&allocsize, &bmcp); - - const bool cap_end = (fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE); - const bool cap_tri = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN); - BMO_op_callf(bm, - BMO_FLAG_DEFAULTS, - "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b " - "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b", - verts_num, - radius_bottom, - radius_top, - cap_end, - cap_tri, - depth, - transform.values, - true); - - BMeshToMeshParams params{}; - params.calc_object_remap = false; - Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); - BM_mesh_free(bm); + face_total(fill_type, verts_num, top_is_point, bottom_is_point)); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + + /* Calculate vertex positions. */ + const int top_verts_start = 0; + const int bottom_verts_start = top_verts_start + (!top_is_point ? verts_num : 1); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num); + for (const int i : IndexRange(verts_num)) { + const float x = std::cos(angle); + const float y = std::sin(angle); + if (!top_is_point) { + copy_v3_v3(verts[top_verts_start + i].co, float3(x * radius_top, y * radius_top, height)); + } + if (!bottom_is_point) { + copy_v3_v3(verts[bottom_verts_start + i].co, + float3(x * radius_bottom, y * radius_bottom, -height)); + } + angle += angle_delta; + } + if (top_is_point) { + copy_v3_v3(verts[top_verts_start].co, float3(0.0f, 0.0f, height)); + } + if (bottom_is_point) { + copy_v3_v3(verts[bottom_verts_start].co, float3(0.0f, 0.0f, -height)); + } + + /* Add center vertices for the triangle fans at the end. */ + const int top_center_vert_index = bottom_verts_start + (bottom_is_point ? 1 : verts_num); + const int bottom_center_vert_index = top_center_vert_index + (top_is_point ? 0 : 1); + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + if (!top_is_point) { + copy_v3_v3(verts[top_center_vert_index].co, float3(0.0f, 0.0f, height)); + } + if (!bottom_is_point) { + copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height)); + } + } + + /* Create top edges. */ + const int top_edges_start = 0; + const int top_fan_edges_start = (!top_is_point && + fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? + top_edges_start + verts_num : + top_edges_start; + if (!top_is_point) { + for (const int i : IndexRange(verts_num)) { + MEdge &edge = edges[top_edges_start + i]; + edge.v1 = top_verts_start + i; + edge.v2 = top_verts_start + (i + 1) % verts_num; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + MEdge &edge = edges[top_fan_edges_start + i]; + edge.v1 = top_center_vert_index; + edge.v2 = top_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + } + + /* Create connecting edges. */ + const int connecting_edges_start = top_fan_edges_start + (!top_is_point ? verts_num : 0); + for (const int i : IndexRange(verts_num)) { + MEdge &edge = edges[connecting_edges_start + i]; + edge.v1 = top_verts_start + (!top_is_point ? i : 0); + edge.v2 = bottom_verts_start + (!bottom_is_point ? i : 0); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + + /* Create bottom edges. */ + const int bottom_edges_start = connecting_edges_start + verts_num; + const int bottom_fan_edges_start = (!bottom_is_point && + fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? + bottom_edges_start + verts_num : + bottom_edges_start; + if (!bottom_is_point) { + for (const int i : IndexRange(verts_num)) { + MEdge &edge = edges[bottom_edges_start + i]; + edge.v1 = bottom_verts_start + i; + edge.v2 = bottom_verts_start + (i + 1) % verts_num; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + MEdge &edge = edges[bottom_fan_edges_start + i]; + edge.v1 = bottom_center_vert_index; + edge.v2 = bottom_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + } + + /* Create top corners and faces. */ + int loop_index = 0; + int poly_index = 0; + if (!top_is_point) { + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = verts_num; + + for (const int i : IndexRange(verts_num)) { + MLoop &loop = loops[loop_index++]; + loop.v = top_verts_start + i; + loop.e = top_edges_start + i; + } + } + else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = top_verts_start + i; + loop_a.e = top_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = top_verts_start + (i + 1) % verts_num; + loop_b.e = top_fan_edges_start + (i + 1) % verts_num; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_center_vert_index; + loop_c.e = top_fan_edges_start + i; + } + } + } + + /* Create side corners and faces. */ + if (!top_is_point && !bottom_is_point) { + /* Quads connect the top and bottom. */ + for (const int i : IndexRange(verts_num)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 4; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = top_verts_start + i; + loop_a.e = connecting_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = bottom_verts_start + i; + loop_b.e = bottom_edges_start + i; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = bottom_verts_start + (i + 1) % verts_num; + loop_c.e = connecting_edges_start + (i + 1) % verts_num; + MLoop &loop_d = loops[loop_index++]; + loop_d.v = top_verts_start + (i + 1) % verts_num; + loop_d.e = top_edges_start + i; + } + } + else { + /* Triangles connect the top and bottom section. */ + if (!top_is_point) { + for (const int i : IndexRange(verts_num)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = top_verts_start + i; + loop_a.e = connecting_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = bottom_verts_start; + loop_b.e = connecting_edges_start + (i + 1) % verts_num; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_verts_start + (i + 1) % verts_num; + loop_c.e = top_edges_start + i; + } + } + else { + BLI_assert(!bottom_is_point); + for (const int i : IndexRange(verts_num)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = bottom_verts_start + i; + loop_a.e = bottom_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = bottom_verts_start + (i + 1) % verts_num; + loop_b.e = connecting_edges_start + (i + 1) % verts_num; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_verts_start; + loop_c.e = connecting_edges_start + i; + } + } + } + + /* Create bottom corners and faces. */ + if (!bottom_is_point) { + if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = verts_num; + + for (const int i : IndexRange(verts_num)) { + /* Go backwards to reverse surface normal. */ + MLoop &loop = loops[loop_index++]; + loop.v = bottom_verts_start + verts_num - 1 - i; + loop.e = bottom_edges_start + verts_num - 1 - (i + 1) % verts_num; + } + } + else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { + for (const int i : IndexRange(verts_num)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = bottom_verts_start + i; + loop_a.e = bottom_fan_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = bottom_center_vert_index; + loop_b.e = bottom_fan_edges_start + (i + 1) % verts_num; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = bottom_verts_start + (i + 1) % verts_num; + loop_c.e = bottom_edges_start + i; + } + } + } + + BKE_mesh_calc_normals(mesh); + + calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type); + + BLI_assert(BKE_mesh_is_valid(mesh)); return mesh; } @@ -253,6 +564,7 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) Mesh *mesh = create_cylinder_or_cone_mesh( radius_top, radius_bottom, depth, verts_num, fill_type); + /* Transform the mesh so that the base of the cone is at the origin. */ BKE_mesh_translate(mesh, float3(0.0f, 0.0f, depth * 0.5f), false); params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index f8a9bfd2ed1..9651301cb34 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -17,6 +17,7 @@ #include "DNA_mesh_types.h" #include "BKE_lib_id.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "bmesh.h" @@ -35,7 +36,7 @@ static bNodeSocketTemplate geo_node_mesh_primitive_cube_out[] = { namespace blender::nodes { -static Mesh *create_cube_mesh(const float size) +Mesh *create_cube_mesh(const float size) { const float4x4 transform = float4x4::identity(); @@ -53,6 +54,7 @@ static Mesh *create_cube_mesh(const float size) BMeshToMeshParams params{}; params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); + BKE_id_material_eval_ensure_default_slot(&mesh->id); BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc index f443b4387d3..1767f765da4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc @@ -17,6 +17,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_plane.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index eff84d7d1ad..ac2f5a23a4d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_plane.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -14,12 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BLI_map.hh" -#include "BLI_math_matrix.h" - #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" @@ -27,39 +25,47 @@ #include "node_geometry_util.hh" -static bNodeSocketTemplate geo_node_mesh_primitive_plane_in[] = { - {SOCK_FLOAT, N_("Size"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE}, - {SOCK_INT, N_("Vertices X"), 10, 0.0f, 0.0f, 0.0f, 2, 1000}, - {SOCK_INT, N_("Vertices Y"), 10, 0.0f, 0.0f, 0.0f, 2, 1000}, +static bNodeSocketTemplate geo_node_mesh_primitive_grid_in[] = { + {SOCK_FLOAT, N_("Size X"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE}, + {SOCK_FLOAT, N_("Size Y"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE}, + {SOCK_INT, N_("Vertices X"), 3, 0.0f, 0.0f, 0.0f, 2, 1000}, + {SOCK_INT, N_("Vertices Y"), 3, 0.0f, 0.0f, 0.0f, 2, 1000}, {-1, ""}, }; -static bNodeSocketTemplate geo_node_mesh_primitive_plane_out[] = { +static bNodeSocketTemplate geo_node_mesh_primitive_grid_out[] = { {SOCK_GEOMETRY, N_("Geometry")}, {-1, ""}, }; namespace blender::nodes { -static void calculate_uvs(Mesh *mesh, Span<MVert> verts, Span<MLoop> loops, const float size) +static void calculate_uvs( + Mesh *mesh, Span<MVert> verts, Span<MLoop> loops, const float size_x, const float size_y) { MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output( - "uv", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr); - MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>(); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x; + const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y; for (const int i : loops.index_range()) { const float3 &co = verts[loops[i].v].co; - uvs[i].x = (co.x + size) / (size * 2.0f); - uvs[i].y = (co.y + size) / (size * 2.0f); + uvs[i].x = (co.x + size_x * 0.5f) * dx; + uvs[i].y = (co.y + size_y * 0.5f) * dy; } - uv_attribute.apply_span_and_save(); + uv_attribute.save(); } -static Mesh *create_plane_mesh(const int verts_x, const int verts_y, const float size) +static Mesh *create_grid_mesh(const int verts_x, + const int verts_y, + const float size_x, + const float size_y) { + BLI_assert(verts_x > 1 && verts_y > 1); const int edges_x = verts_x - 1; const int edges_y = verts_y - 1; Mesh *mesh = BKE_mesh_new_nomain(verts_x * verts_y, @@ -73,11 +79,11 @@ static Mesh *create_plane_mesh(const int verts_x, const int verts_y, const float MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; { - const float dx = size / edges_x; - const float dy = size / edges_y; - float x = -size; + const float dx = size_x / edges_x; + const float dy = size_y / edges_y; + float x = -size_x * 0.5; for (const int x_index : IndexRange(verts_x)) { - float y = -size; + float y = -size_y * 0.5; for (const int y_index : IndexRange(verts_y)) { const int vert_index = x_index * verts_y + y_index; verts[vert_index].co[0] = x; @@ -104,6 +110,7 @@ static Mesh *create_plane_mesh(const int verts_x, const int verts_y, const float MEdge &edge = edges[edge_index++]; edge.v1 = vert_index; edge.v2 = vert_index + 1; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } @@ -115,6 +122,7 @@ static Mesh *create_plane_mesh(const int verts_x, const int verts_y, const float MEdge &edge = edges[edge_index++]; edge.v1 = vert_index; edge.v2 = vert_index + verts_y; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } @@ -142,14 +150,15 @@ static Mesh *create_plane_mesh(const int verts_x, const int verts_y, const float } } - calculate_uvs(mesh, verts, loops, size); + calculate_uvs(mesh, verts, loops, size_x, size_y); return mesh; } -static void geo_node_mesh_primitive_plane_exec(GeoNodeExecParams params) +static void geo_node_mesh_primitive_grid_exec(GeoNodeExecParams params) { - const float size = params.extract_input<float>("Size"); + const float size_x = params.extract_input<float>("Size X"); + const float size_y = params.extract_input<float>("Size Y"); const int verts_x = params.extract_input<int>("Vertices X"); const int verts_y = params.extract_input<int>("Vertices Y"); if (verts_x < 2 || verts_y < 2) { @@ -157,21 +166,22 @@ static void geo_node_mesh_primitive_plane_exec(GeoNodeExecParams params) return; } - Mesh *mesh = create_plane_mesh(verts_x, verts_y, size); + Mesh *mesh = create_grid_mesh(verts_x, verts_y, size_x, size_y); BLI_assert(BKE_mesh_is_valid(mesh)); + BKE_id_material_eval_ensure_default_slot(&mesh->id); params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); } } // namespace blender::nodes -void register_node_type_geo_mesh_primitive_plane() +void register_node_type_geo_mesh_primitive_grid() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_PLANE, "Plane", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_GRID, "Grid", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates( - &ntype, geo_node_mesh_primitive_plane_in, geo_node_mesh_primitive_plane_out); - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_plane_exec; + &ntype, geo_node_mesh_primitive_grid_in, geo_node_mesh_primitive_grid_out); + ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_grid_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index 242cc6ed7df..a3a1b72006c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -17,6 +17,7 @@ #include "DNA_mesh_types.h" #include "BKE_lib_id.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "bmesh.h" @@ -55,6 +56,7 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) BMeshToMeshParams params{}; params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); + BKE_id_material_eval_ensure_default_slot(&mesh->id); BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc index a7d40571c39..e841455e58c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc @@ -14,12 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BLI_map.hh" -#include "BLI_math_matrix.h" - #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" @@ -113,6 +111,7 @@ static Mesh *create_line_mesh(const float3 start, const float3 delta, const int } Mesh *mesh = BKE_mesh_new_nomain(count, count - 1, 0, 0, 0); + BKE_id_material_eval_ensure_default_slot(&mesh->id); MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; @@ -154,7 +153,10 @@ static void geo_node_mesh_primitive_line_exec(GeoNodeExecParams params) } else if (count_mode == GEO_NODE_MESH_LINE_COUNT_TOTAL) { const int count = params.extract_input<int>("Count"); - if (count > 1) { + if (count == 1) { + mesh = create_line_mesh(start, float3(0), count); + } + else { const float3 delta = total_delta / (float)(count - 1); mesh = create_line_mesh(start, delta, count); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index 8efba91da1a..599c59e4a2e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -17,19 +17,17 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_lib_id.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "UI_interface.h" #include "UI_resources.h" -#include "bmesh.h" - #include "node_geometry_util.hh" static bNodeSocketTemplate geo_node_mesh_primitive_uv_sphere_in[] = { {SOCK_INT, N_("Segments"), 32, 0.0f, 0.0f, 0.0f, 3, 1024}, - {SOCK_INT, N_("Rings"), 16, 0.0f, 0.0f, 0.0f, 3, 1024}, + {SOCK_INT, N_("Rings"), 16, 0.0f, 0.0f, 0.0f, 2, 1024}, {SOCK_FLOAT, N_("Radius"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE}, {-1, ""}, }; @@ -65,31 +63,225 @@ static int sphere_face_total(const int segments, const int rings) return quads + triangles; } -static Mesh *create_uv_sphere_mesh_bmesh(const float radius, const int segments, const int rings) +static void calculate_sphere_vertex_data(MutableSpan<MVert> verts, + const float radius, + const int segments, + const int rings) +{ + const float delta_theta = M_PI / rings; + const float delta_phi = (2 * M_PI) / segments; + + copy_v3_v3(verts[0].co, float3(0.0f, 0.0f, radius)); + normal_float_to_short_v3(verts[0].no, float3(0.0f, 0.0f, 1.0f)); + + int vert_index = 1; + float theta = delta_theta; + for (const int UNUSED(ring) : IndexRange(rings - 1)) { + float phi = 0.0f; + const float z = cosf(theta); + for (const int UNUSED(segment) : IndexRange(segments)) { + const float sin_theta = std::sin(theta); + const float x = sin_theta * std::cos(phi); + const float y = sin_theta * std::sin(phi); + copy_v3_v3(verts[vert_index].co, float3(x, y, z) * radius); + normal_float_to_short_v3(verts[vert_index].no, float3(x, y, z)); + phi += delta_phi; + vert_index++; + } + theta += delta_theta; + } + + copy_v3_v3(verts.last().co, float3(0.0f, 0.0f, -radius)); + normal_float_to_short_v3(verts.last().no, float3(0.0f, 0.0f, -1.0f)); +} + +static void calculate_sphere_edge_indices(MutableSpan<MEdge> edges, + const int segments, + const int rings) +{ + int edge_index = 0; + + /* Add the edges connecting the top vertex to the first ring. */ + const int first_vert_ring_index_start = 1; + for (const int segment : IndexRange(segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = 0; + edge.v2 = first_vert_ring_index_start + segment; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + + int ring_vert_index_start = 1; + for (const int ring : IndexRange(rings - 1)) { + const int next_ring_vert_index_start = ring_vert_index_start + segments; + + /* Add the edges running along each ring. */ + for (const int segment : IndexRange(segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = ring_vert_index_start + segment; + edge.v2 = ring_vert_index_start + ((segment + 1) % segments); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + + /* Add the edges connecting to the next ring. */ + if (ring < rings - 2) { + for (const int segment : IndexRange(segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = ring_vert_index_start + segment; + edge.v2 = next_ring_vert_index_start + segment; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + ring_vert_index_start += segments; + } + + /* Add the edges connecting the last ring to the bottom vertex. */ + const int last_vert_index = sphere_vert_total(segments, rings) - 1; + const int last_vert_ring_start = last_vert_index - segments; + for (const int segment : IndexRange(segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = last_vert_index; + edge.v2 = last_vert_ring_start + segment; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } +} + +static void calculate_sphere_faces(MutableSpan<MLoop> loops, + MutableSpan<MPoly> polys, + const int segments, + const int rings) { - const float4x4 transform = float4x4::identity(); - - const BMeshCreateParams bmcp = {true}; - const BMAllocTemplate allocsize = {sphere_vert_total(segments, rings), - sphere_edge_total(segments, rings), - sphere_corner_total(segments, rings), - sphere_face_total(segments, rings)}; - BMesh *bm = BM_mesh_create(&allocsize, &bmcp); - - BMO_op_callf(bm, - BMO_FLAG_DEFAULTS, - "create_uvsphere u_segments=%i v_segments=%i diameter=%f matrix=%m4 calc_uvs=%b", - segments, - rings, - radius, - transform.values, - true); - - BMeshToMeshParams params{}; - params.calc_object_remap = false; - Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); - BM_mesh_free(bm); + int loop_index = 0; + int poly_index = 0; + + /* Add the triangles connected to the top vertex. */ + const int first_vert_ring_index_start = 1; + for (const int segment : IndexRange(segments)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + MLoop &loop_a = loops[loop_index++]; + loop_a.v = 0; + loop_a.e = segment; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = first_vert_ring_index_start + segment; + loop_b.e = segments + segment; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = first_vert_ring_index_start + (segment + 1) % segments; + loop_c.e = (segment + 1) % segments; + } + + int ring_vert_index_start = 1; + int ring_edge_index_start = segments; + for (const int UNUSED(ring) : IndexRange(1, rings - 2)) { + const int next_ring_vert_index_start = ring_vert_index_start + segments; + const int next_ring_edge_index_start = ring_edge_index_start + segments * 2; + const int ring_vertical_edge_index_start = ring_edge_index_start + segments; + + for (const int segment : IndexRange(segments)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 4; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = ring_vert_index_start + segment; + loop_a.e = ring_vertical_edge_index_start + segment; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = next_ring_vert_index_start + segment; + loop_b.e = next_ring_edge_index_start + segment; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = next_ring_vert_index_start + (segment + 1) % segments; + loop_c.e = ring_vertical_edge_index_start + (segment + 1) % segments; + MLoop &loop_d = loops[loop_index++]; + loop_d.v = ring_vert_index_start + (segment + 1) % segments; + loop_d.e = ring_edge_index_start + segment; + } + ring_vert_index_start += segments; + ring_edge_index_start += segments * 2; + } + + /* Add the triangles connected to the bottom vertex. */ + const int last_edge_ring_start = segments * (rings - 2) * 2 + segments; + const int bottom_edge_fan_start = last_edge_ring_start + segments; + const int last_vert_index = sphere_vert_total(segments, rings) - 1; + const int last_vert_ring_start = last_vert_index - segments; + for (const int segment : IndexRange(segments)) { + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = 3; + + MLoop &loop_a = loops[loop_index++]; + loop_a.v = last_vert_index; + loop_a.e = bottom_edge_fan_start + (segment + 1) % segments; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = last_vert_ring_start + (segment + 1) % segments; + loop_b.e = last_edge_ring_start + segment; + MLoop &loop_c = loops[loop_index++]; + loop_c.v = last_vert_ring_start + segment; + loop_c.e = bottom_edge_fan_start + segment; + } +} + +static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float rings) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + + int loop_index = 0; + const float dy = 1.0f / rings; + + for (const int i_segment : IndexRange(segments)) { + const float segment = static_cast<float>(i_segment); + uvs[loop_index++] = float2((segment + 0.5f) / segments, 0.0f); + uvs[loop_index++] = float2(segment / segments, dy); + uvs[loop_index++] = float2((segment + 1.0f) / segments, dy); + } + + for (const int i_ring : IndexRange(1, rings - 2)) { + const float ring = static_cast<float>(i_ring); + for (const int i_segment : IndexRange(segments)) { + const float segment = static_cast<float>(i_segment); + uvs[loop_index++] = float2(segment / segments, ring / rings); + uvs[loop_index++] = float2(segment / segments, (ring + 1.0f) / rings); + uvs[loop_index++] = float2((segment + 1.0f) / segments, (ring + 1.0f) / rings); + uvs[loop_index++] = float2((segment + 1.0f) / segments, ring / rings); + } + } + + for (const int i_segment : IndexRange(segments)) { + const float segment = static_cast<float>(i_segment); + uvs[loop_index++] = float2((segment + 0.5f) / segments, 1.0f); + uvs[loop_index++] = float2((segment + 1.0f) / segments, 1.0f - dy); + uvs[loop_index++] = float2(segment / segments, 1.0f - dy); + } + + uv_attribute.save(); +} + +static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const int rings) +{ + Mesh *mesh = BKE_mesh_new_nomain(sphere_vert_total(segments, rings), + sphere_edge_total(segments, rings), + 0, + sphere_corner_total(segments, rings), + sphere_face_total(segments, rings)); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + + calculate_sphere_vertex_data(verts, radius, segments, rings); + + calculate_sphere_edge_indices(edges, segments, rings); + + calculate_sphere_faces(loops, polys, segments, rings); + + calculate_sphere_uvs(mesh, segments, rings); + + BLI_assert(BKE_mesh_is_valid(mesh)); return mesh; } @@ -98,14 +290,14 @@ static void geo_node_mesh_primitive_uv_sphere_exec(GeoNodeExecParams params) { const int segments_num = params.extract_input<int>("Segments"); const int rings_num = params.extract_input<int>("Rings"); - if (segments_num < 3 || rings_num < 3) { + if (segments_num < 3 || rings_num < 2) { params.set_output("Geometry", GeometrySet()); return; } const float radius = params.extract_input<float>("Radius"); - Mesh *mesh = create_uv_sphere_mesh_bmesh(radius, segments_num, rings_num); + Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num); params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc new file mode 100644 index 00000000000..0fb7910c904 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -0,0 +1,316 @@ +/* + * 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_array.hh" +#include "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +using blender::Array; + +static bNodeSocketTemplate geo_node_mesh_to_curve_in[] = { + {SOCK_GEOMETRY, N_("Mesh")}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_mesh_to_curve_out[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {-1, ""}, +}; + +namespace blender::nodes { + +template<typename T> +static void copy_attribute_to_points(const VArray<T> &source_data, + Span<int> map, + MutableSpan<T> dest_data) +{ + for (const int point_index : map.index_range()) { + const int vert_index = map[point_index]; + dest_data[point_index] = source_data[vert_index]; + } +} + +static void copy_attributes_to_points(CurveEval &curve, + const MeshComponent &mesh_component, + Span<Vector<int>> point_to_vert_maps) +{ + MutableSpan<SplinePtr> splines = curve.splines(); + Set<std::string> source_attribute_names = mesh_component.attribute_names(); + + /* Copy builtin control point attributes. */ + if (source_attribute_names.contains_as("tilt")) { + const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>( + "tilt", ATTR_DOMAIN_POINT, 0.0f); + parallel_for(splines.index_range(), 256, [&](IndexRange range) { + for (const int i : range) { + copy_attribute_to_points<float>( + *tilt_attribute, point_to_vert_maps[i], splines[i]->tilts()); + } + }); + source_attribute_names.remove_contained_as("tilt"); + } + if (source_attribute_names.contains_as("radius")) { + const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>( + "radius", ATTR_DOMAIN_POINT, 1.0f); + parallel_for(splines.index_range(), 256, [&](IndexRange range) { + for (const int i : range) { + copy_attribute_to_points<float>( + *radius_attribute, point_to_vert_maps[i], splines[i]->radii()); + } + }); + source_attribute_names.remove_contained_as("radius"); + } + + /* Don't copy other builtin control point attributes. */ + source_attribute_names.remove_as("position"); + + /* Copy dynamic control point attributes. */ + for (const StringRef name : source_attribute_names) { + const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name, + ATTR_DOMAIN_POINT); + /* Some attributes might not exist if they were builtin attribute on domains that don't + * have any elements, i.e. a face attribute on the output of the line primitive node. */ + if (!mesh_attribute) { + continue; + } + + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(mesh_attribute->type()); + + parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + /* Create attribute on the spline points. */ + splines[i]->attributes.create(name, data_type); + std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name); + BLI_assert(spline_attribute); + + /* Copy attribute based on the map for this spline. */ + attribute_math::convert_to_static_type(mesh_attribute->type(), [&](auto dummy) { + using T = decltype(dummy); + copy_attribute_to_points<T>( + mesh_attribute->typed<T>(), point_to_vert_maps[i], spline_attribute->typed<T>()); + }); + } + }); + } + + curve.assert_valid_point_attributes(); +} + +struct CurveFromEdgesOutput { + std::unique_ptr<CurveEval> curve; + Vector<Vector<int>> point_to_vert_maps; +}; + +static CurveFromEdgesOutput mesh_to_curve(Span<MVert> verts, Span<std::pair<int, int>> edges) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + Vector<Vector<int>> point_to_vert_maps; + + /* Compute the number of edges connecting to each vertex. */ + Array<int> neighbor_count(verts.size(), 0); + for (const std::pair<int, int> &edge : edges) { + neighbor_count[edge.first]++; + neighbor_count[edge.second]++; + } + + /* Compute an offset into the array of neighbor edges based on the counts. */ + Array<int> neighbor_offsets(verts.size()); + int start = 0; + for (const int i : verts.index_range()) { + neighbor_offsets[i] = start; + start += neighbor_count[i]; + } + + /* Use as an index into the "neighbor group" for each vertex. */ + Array<int> used_slots(verts.size(), 0); + /* Calculate the indices of each vertex's neighboring edges. */ + Array<int> neighbors(edges.size() * 2); + for (const int i : edges.index_range()) { + const int v1 = edges[i].first; + const int v2 = edges[i].second; + neighbors[neighbor_offsets[v1] + used_slots[v1]] = v2; + neighbors[neighbor_offsets[v2] + used_slots[v2]] = v1; + used_slots[v1]++; + used_slots[v2]++; + } + + /* Now use the neighbor group offsets calculated above as a count used edges at each vertex. */ + Array<int> unused_edges = std::move(used_slots); + + for (const int start_vert : verts.index_range()) { + /* The vertex will be part of a cyclic spline. */ + if (neighbor_count[start_vert] == 2) { + continue; + } + + /* The vertex has no connected edges, or they were already used. */ + if (unused_edges[start_vert] == 0) { + continue; + } + + for (const int i : IndexRange(neighbor_count[start_vert])) { + int current_vert = start_vert; + int next_vert = neighbors[neighbor_offsets[current_vert] + i]; + + if (unused_edges[next_vert] == 0) { + continue; + } + + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + Vector<int> point_to_vert_map; + + spline->add_point(verts[current_vert].co, 1.0f, 0.0f); + point_to_vert_map.append(current_vert); + + /* Follow connected edges until we read a vertex with more than two connected edges. */ + while (true) { + int last_vert = current_vert; + current_vert = next_vert; + + spline->add_point(verts[current_vert].co, 1.0f, 0.0f); + point_to_vert_map.append(current_vert); + unused_edges[current_vert]--; + unused_edges[last_vert]--; + + if (neighbor_count[current_vert] != 2) { + break; + } + + const int offset = neighbor_offsets[current_vert]; + const int next_a = neighbors[offset]; + const int next_b = neighbors[offset + 1]; + next_vert = (last_vert == next_a) ? next_b : next_a; + } + + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + point_to_vert_maps.append(std::move(point_to_vert_map)); + } + } + + /* All remaining edges are part of cyclic splines (we skipped vertices with two edges before). */ + for (const int start_vert : verts.index_range()) { + if (unused_edges[start_vert] != 2) { + continue; + } + + int current_vert = start_vert; + int next_vert = neighbors[neighbor_offsets[current_vert]]; + + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + Vector<int> point_to_vert_map; + spline->set_cyclic(true); + + spline->add_point(verts[current_vert].co, 1.0f, 0.0f); + point_to_vert_map.append(current_vert); + + /* Follow connected edges until we loop back to the start vertex. */ + while (next_vert != start_vert) { + const int last_vert = current_vert; + current_vert = next_vert; + + spline->add_point(verts[current_vert].co, 1.0f, 0.0f); + point_to_vert_map.append(current_vert); + unused_edges[current_vert]--; + unused_edges[last_vert]--; + + const int offset = neighbor_offsets[current_vert]; + const int next_a = neighbors[offset]; + const int next_b = neighbors[offset + 1]; + next_vert = (last_vert == next_a) ? next_b : next_a; + } + + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + point_to_vert_maps.append(std::move(point_to_vert_map)); + } + + curve->attributes.reallocate(curve->splines().size()); + return {std::move(curve), std::move(point_to_vert_maps)}; +} + +/** + * Get a separate array of the indices for edges in a selection (a boolean attribute). + * This helps to make the above algorithm simpler by removing the need to check for selection + * in many places. + */ +static Vector<std::pair<int, int>> get_selected_edges(GeoNodeExecParams params, + const MeshComponent &component) +{ + const Mesh &mesh = *component.get_for_read(); + const std::string selection_name = params.extract_input<std::string>("Selection"); + if (!selection_name.empty() && !component.attribute_exists(selection_name)) { + params.error_message_add(NodeWarningType::Error, + TIP_("No attribute with name \"") + selection_name + "\""); + } + GVArray_Typed<bool> selection = component.attribute_get_for_read<bool>( + selection_name, ATTR_DOMAIN_EDGE, true); + + Vector<std::pair<int, int>> selected_edges; + for (const int i : IndexRange(mesh.totedge)) { + if (selection[i]) { + selected_edges.append({mesh.medge[i].v1, mesh.medge[i].v2}); + } + } + + return selected_edges; +} + +static void geo_node_mesh_to_curve_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_mesh()) { + params.set_output("Curve", GeometrySet()); + return; + } + + const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>(); + const Mesh &mesh = *component.get_for_read(); + Span<MVert> verts = Span{mesh.mvert, mesh.totvert}; + Vector<std::pair<int, int>> selected_edges = get_selected_edges(params, component); + if (selected_edges.size() == 0) { + params.set_output("Curve", GeometrySet()); + return; + } + + CurveFromEdgesOutput output = mesh_to_curve(verts, selected_edges); + copy_attributes_to_points(*output.curve, component, output.point_to_vert_maps); + + params.set_output("Curve", GeometrySet::create_with_curve(output.curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_mesh_to_curve() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_mesh_to_curve_in, geo_node_mesh_to_curve_out); + ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_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 index 54ecb20dae7..167812d5656 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -14,20 +14,15 @@ * 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 "BKE_volume.h" - #include "BLI_math_matrix.h" #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_object_info_in[] = { - {SOCK_OBJECT, N_("Object")}, + {SOCK_OBJECT, N_("Object"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, PROP_NONE, SOCK_HIDE_LABEL}, {-1, ""}, }; @@ -52,9 +47,7 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.get_input<Object *>("Object"); float3 location = {0, 0, 0}; float3 rotation = {0, 0, 0}; @@ -78,14 +71,15 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) if (object != self_object) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(*object); if (transform_space_relative) { - instances.add_instance(object, transform); + instances.add_instance(handle, transform); } else { float unit_transform[4][4]; unit_m4(unit_transform); - instances.add_instance(object, unit_transform); + instances.add_instance(handle, unit_transform); } } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index 2e3460ee5fe..772bd8a1080 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -14,12 +14,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BLI_float3.hh" #include "BLI_hash.h" #include "BLI_kdtree.h" -#include "BLI_math_vector.h" #include "BLI_rand.hh" -#include "BLI_span.hh" #include "BLI_timeit.hh" #include "DNA_mesh_types.h" @@ -28,10 +25,10 @@ #include "BKE_attribute_math.hh" #include "BKE_bvhutils.h" -#include "BKE_deform.h" #include "BKE_geometry_set_instances.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" #include "BKE_pointcloud.h" #include "UI_interface.h" @@ -44,7 +41,7 @@ using blender::bke::GeometryInstanceGroup; static bNodeSocketTemplate geo_node_point_distribute_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_FLOAT, N_("Distance Min"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_FLOAT, N_("Distance Min"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_DISTANCE}, {SOCK_FLOAT, N_("Density Max"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, {SOCK_STRING, N_("Density Attribute")}, {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, @@ -95,7 +92,7 @@ static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) static void sample_mesh_surface(const Mesh &mesh, const float4x4 &transform, const float base_density, - const FloatReadAttribute *density_factors, + const VArray<float> *density_factors, const int seed, Vector<float3> &r_positions, Vector<float3> &r_bary_coords, @@ -117,9 +114,9 @@ static void sample_mesh_surface(const Mesh &mesh, float looptri_density_factor = 1.0f; if (density_factors != nullptr) { - const float v0_density_factor = std::max(0.0f, (*density_factors)[v0_loop]); - const float v1_density_factor = std::max(0.0f, (*density_factors)[v1_loop]); - const float v2_density_factor = std::max(0.0f, (*density_factors)[v2_loop]); + const float v0_density_factor = std::max(0.0f, density_factors->get(v0_loop)); + const float v1_density_factor = std::max(0.0f, density_factors->get(v1_loop)); + const float v2_density_factor = std::max(0.0f, density_factors->get(v2_loop)); 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); @@ -207,7 +204,7 @@ BLI_NOINLINE static void update_elimination_mask_for_close_points( BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( const Mesh &mesh, - const FloatReadAttribute &density_factors, + const VArray<float> &density_factors, Span<float3> bary_coords, Span<int> looptri_indices, MutableSpan<bool> elimination_mask) @@ -253,99 +250,27 @@ BLI_NOINLINE static void eliminate_points_based_on_mask(Span<bool> elimination_m } } -template<typename T> -BLI_NOINLINE static void interpolate_attribute_point(const Mesh &mesh, - const Span<float3> bary_coords, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totvert); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const float3 &bary_coord = bary_coords[i]; - - 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 T &v0 = data_in[v0_index]; - const T &v1 = data_in[v1_index]; - const T &v2 = data_in[v2_index]; - - const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); - data_out[i] = interpolated_value; - } -} - -template<typename T> -BLI_NOINLINE static void interpolate_attribute_corner(const Mesh &mesh, - const Span<float3> bary_coords, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totloop); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const float3 &bary_coord = bary_coords[i]; - - const int loop_index_0 = looptri.tri[0]; - const int loop_index_1 = looptri.tri[1]; - const int loop_index_2 = looptri.tri[2]; - - const T &v0 = data_in[loop_index_0]; - const T &v1 = data_in[loop_index_1]; - const T &v2 = data_in[loop_index_2]; - - const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); - data_out[i] = interpolated_value; - } -} - -template<typename T> -BLI_NOINLINE static void interpolate_attribute_face(const Mesh &mesh, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totpoly); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : data_out.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const int poly_index = looptri.poly; - data_out[i] = data_in[poly_index]; - } -} - -template<typename T> BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, Span<float3> bary_coords, Span<int> looptri_indices, const AttributeDomain source_domain, - Span<T> source_span, - MutableSpan<T> output_span) + const GVArray &source_data, + GMutableSpan output_data) { switch (source_domain) { case ATTR_DOMAIN_POINT: { - interpolate_attribute_point<T>(mesh, bary_coords, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); break; } case ATTR_DOMAIN_CORNER: { - interpolate_attribute_corner<T>( - mesh, bary_coords, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); break; } case ATTR_DOMAIN_FACE: { - interpolate_attribute_face<T>(mesh, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); break; } default: { @@ -367,13 +292,13 @@ BLI_NOINLINE static void interpolate_existing_attributes( StringRef attribute_name = entry.key; const CustomDataType output_data_type = entry.value.data_type; /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ - OutputAttributePtr attribute_out = component.attribute_try_get_for_output( + OutputAttribute attribute_out = component.attribute_try_get_for_output_only( attribute_name, ATTR_DOMAIN_POINT, output_data_type); if (!attribute_out) { continue; } - fn::GMutableSpan out_span = attribute_out->get_span_for_write_only(); + GMutableSpan out_span = attribute_out.as_span(); int i_instance = 0; for (const GeometryInstanceGroup &set_group : set_groups) { @@ -381,47 +306,41 @@ BLI_NOINLINE static void interpolate_existing_attributes( const MeshComponent &source_component = *set.get_component_for_read<MeshComponent>(); const Mesh &mesh = *source_component.get_for_read(); - /* Use a dummy read without specifying a domain or data type in order to - * get the existing attribute's domain. Interpolation is done manually based - * on the bary coords in #interpolate_attribute. */ - ReadAttributePtr dummy_attribute = source_component.attribute_try_get_for_read( + std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data( attribute_name); - if (!dummy_attribute) { + if (!attribute_info) { i_instance += set_group.transforms.size(); continue; } - const AttributeDomain source_domain = dummy_attribute->domain(); - ReadAttributePtr source_attribute = source_component.attribute_get_for_read( + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = source_component.attribute_get_for_read( attribute_name, source_domain, output_data_type, nullptr); if (!source_attribute) { i_instance += set_group.transforms.size(); continue; } - fn::GSpan source_span = source_attribute->get_span(); + + for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { + const int offset = instance_start_offsets[i_instance]; + Span<float3> bary_coords = bary_coords_array[i_instance]; + Span<int> looptri_indices = looptri_indices_array[i_instance]; + + GMutableSpan instance_span = out_span.slice(offset, bary_coords.size()); + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span); + + i_instance++; + } attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { using T = decltype(dummy); - for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { - const int offset = instance_start_offsets[i_instance]; - Span<float3> bary_coords = bary_coords_array[i_instance]; - Span<int> looptri_indices = looptri_indices_array[i_instance]; - - MutableSpan<T> instance_span = out_span.typed<T>().slice(offset, bary_coords.size()); - interpolate_attribute<T>(mesh, - bary_coords, - looptri_indices, - source_domain, - source_span.typed<T>(), - instance_span); - - i_instance++; - } + GVArray_Span<T> source_span{*source_attribute}; }); } - attribute_out.apply_span_and_save(); + attribute_out.save(); } } @@ -431,16 +350,16 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) { - OutputAttributePtr id_attribute = component.attribute_try_get_for_output( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); - OutputAttributePtr normal_attribute = component.attribute_try_get_for_output( - "normal", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<int> id_attribute = component.attribute_try_get_for_output_only<int>( + "id", ATTR_DOMAIN_POINT); + OutputAttribute_Typed<float3> normal_attribute = + component.attribute_try_get_for_output_only<float3>("normal", ATTR_DOMAIN_POINT); + OutputAttribute_Typed<float3> rotation_attribute = + component.attribute_try_get_for_output_only<float3>("rotation", ATTR_DOMAIN_POINT); - MutableSpan<int> result_ids = id_attribute->get_span_for_write_only<int>(); - MutableSpan<float3> result_normals = normal_attribute->get_span_for_write_only<float3>(); - MutableSpan<float3> result_rotations = rotation_attribute->get_span_for_write_only<float3>(); + MutableSpan<int> result_ids = id_attribute.as_span(); + MutableSpan<float3> result_normals = normal_attribute.as_span(); + MutableSpan<float3> result_rotations = rotation_attribute.as_span(); int i_instance = 0; for (const GeometryInstanceGroup &set_group : sets) { @@ -484,9 +403,9 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> } } - id_attribute.apply_span_and_save(); - normal_attribute.apply_span_and_save(); - rotation_attribute.apply_span_and_save(); + id_attribute.save(); + normal_attribute.save(); + rotation_attribute.save(); } BLI_NOINLINE static void add_remaining_point_attributes( @@ -524,7 +443,7 @@ static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); - const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( + GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); const Mesh &mesh = *component.get_for_read(); for (const float4x4 &transform : set_group.transforms) { @@ -534,7 +453,7 @@ static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, sample_mesh_surface(mesh, transform, density, - &density_factors, + &*density_factors, seed, positions, bary_coords, @@ -593,7 +512,7 @@ static void distribute_points_poisson_disk(Span<GeometryInstanceGroup> set_group const GeometrySet &set = set_group.geometry_set; const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); const Mesh &mesh = *component.get_for_read(); - const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( + const GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { @@ -626,7 +545,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) const GeometryNodePointDistributeMode distribute_method = static_cast<GeometryNodePointDistributeMode>(params.node().custom1); - const int seed = params.get_input<int>("Seed"); + const int seed = params.get_input<int>("Seed") * 5383843; const float density = params.extract_input<float>("Density Max"); const std::string density_attribute_name = params.extract_input<std::string>( "Density Attribute"); @@ -636,7 +555,8 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) return; } - Vector<GeometryInstanceGroup> set_groups = bke::geometry_set_gather_instances(geometry_set); + Vector<GeometryInstanceGroup> set_groups; + geometry_set_gather_instances(geometry_set, set_groups); if (set_groups.is_empty()) { params.set_output("Geometry", GeometrySet()); return; @@ -715,8 +635,8 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) geometry_set_out.get_component_for_write<PointCloudComponent>(); Map<std::string, AttributeKind> attributes; - bke::gather_attribute_info( - attributes, {GEO_COMPONENT_TYPE_MESH}, set_groups, {"position", "normal", "id"}); + bke::geometry_set_gather_instances_attribute_info( + set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes); add_remaining_point_attributes(set_groups, instance_start_offsets, attributes, diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index dbbb73bd36d..e52ab1b2127 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -14,15 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BKE_mesh.h" -#include "BKE_persistent_data_handle.hh" - #include "DNA_collection_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_pointcloud_types.h" #include "BLI_hash.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -31,8 +26,17 @@ static bNodeSocketTemplate geo_node_point_instance_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_OBJECT, N_("Object")}, - {SOCK_COLLECTION, N_("Collection")}, + {SOCK_OBJECT, N_("Object"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, PROP_NONE, SOCK_HIDE_LABEL}, + {SOCK_COLLECTION, + N_("Collection"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_HIDE_LABEL}, {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; @@ -52,6 +56,15 @@ static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C) namespace blender::nodes { +static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( + sizeof(NodeGeometryPointInstance), __func__); + data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; + data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; + node->storage = data; +} + static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); @@ -69,129 +82,133 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } -static void get_instanced_data__object(const GeoNodeExecParams ¶ms, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static Vector<InstanceReference> get_instance_references__object(GeoNodeExecParams ¶ms) { - bke::PersistentObjectHandle object_handle = params.get_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.extract_input<Object *>("Object"); if (object == params.self_object()) { - object = nullptr; + return {}; } if (object != nullptr) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - r_instances_data.fill(instance); + return {*object}; } + return {}; } -static void get_instanced_data__collection( - const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static Vector<InstanceReference> get_instance_references__collection(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; - bke::PersistentCollectionHandle collection_handle = - params.get_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); if (collection == nullptr) { - return; + return {}; } if (BLI_listbase_is_empty(&collection->children) && BLI_listbase_is_empty(&collection->gobject)) { params.error_message_add(NodeWarningType::Info, TIP_("Collection is empty")); - return; + return {}; } - const bool use_whole_collection = (node_storage->flag & - GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0; - if (use_whole_collection) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - r_instances_data.fill(instance); + if (node_storage->flag & GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) { + return {*collection}; } - else { - Vector<InstancedData> possible_instances; - /* Direct child objects are instanced as objects. */ - LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { - Object *object = cob->ob; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - possible_instances.append(instance); - } - /* Direct child collections are instanced as collections. */ - LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - Collection *child_collection = child->collection; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = child_collection; - possible_instances.append(instance); - } - if (!possible_instances.is_empty()) { - const int seed = params.get_input<int>("Seed"); - Array<uint32_t> ids = get_geometry_element_ids_as_uints(component, ATTR_DOMAIN_POINT); - for (const int i : r_instances_data.index_range()) { - const int index = BLI_hash_int_2d(ids[i], seed) % possible_instances.size(); - r_instances_data[i] = possible_instances[index]; - } - } + Vector<InstanceReference> references; + /* Direct child objects are instanced as objects. */ + LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { + references.append(*cob->ob); + } + /* Direct child collections are instanced as collections. */ + LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { + references.append(*child->collection); } + + return references; } -static Array<std::optional<InstancedData>> get_instanced_data(const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - const int amount) +static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; const GeometryNodePointInstanceType type = (GeometryNodePointInstanceType) node_storage->instance_type; - Array<std::optional<InstancedData>> instances_data(amount); switch (type) { case GEO_NODE_POINT_INSTANCE_TYPE_OBJECT: { - get_instanced_data__object(params, instances_data); - break; + return get_instance_references__object(params); } case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { - get_instanced_data__collection(params, component, instances_data); - break; + return get_instance_references__collection(params); } } - return instances_data; + return {}; +} + +/** + * Add the instance references to the component as a separate step from actually creating the + * instances in order to avoid a map lookup for every transform. While this might add some + * unnecessary references if they are not chosen while adding transforms, in the common cases + * there are many more transforms than there are references, so that isn't likely. + */ +static Array<int> add_instance_references(InstancesComponent &instance_component, + Span<InstanceReference> possible_references) +{ + Array<int> possible_handles(possible_references.size()); + for (const int i : possible_references.index_range()) { + possible_handles[i] = instance_component.add_reference(possible_references[i]); + } + return possible_handles; } -static void add_instances_from_geometry_component(InstancesComponent &instances, - const GeometryComponent &src_geometry, - const GeoNodeExecParams ¶ms) +static void add_instances_from_component(InstancesComponent &instances, + const GeometryComponent &src_geometry, + Span<int> possible_handles, + const GeoNodeExecParams ¶ms) { const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_geometry.attribute_domain_size(domain); - Array<std::optional<InstancedData>> instances_data = get_instanced_data( - params, src_geometry, domain_size); - Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> positions = src_geometry.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); - Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> rotations = src_geometry.attribute_get_for_read<float3>( "rotation", domain, {0, 0, 0}); - Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> scales = src_geometry.attribute_get_for_read<float3>( "scale", domain, {1, 1, 1}); - Int32ReadAttribute ids = src_geometry.attribute_get_for_read<int>("id", domain, -1); - - for (const int i : IndexRange(domain_size)) { - if (instances_data[i].has_value()) { - float transform[4][4]; - loc_eul_size_to_mat4(transform, positions[i], rotations[i], scales[i]); - instances.add_instance(*instances_data[i], transform, ids[i]); - } + GVArray_Typed<int> id_attribute = src_geometry.attribute_get_for_read<int>("id", domain, -1); + + /* The initial size of the component might be non-zero if there are two component types. */ + const int start_len = instances.instances_amount(); + instances.resize(start_len + domain_size); + MutableSpan<int> handles = instances.instance_reference_handles().slice(start_len, domain_size); + MutableSpan<float4x4> transforms = instances.instance_transforms().slice(start_len, domain_size); + MutableSpan<int> instance_ids = instances.instance_ids().slice(start_len, domain_size); + + /* Skip all of the randomness handling if there is only a single possible instance + * (anything except for collection mode with "Whole Collection" turned off). */ + if (possible_handles.size() == 1) { + const int handle = possible_handles.first(); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); + } + else { + const int seed = params.get_input<int>("Seed"); + Array<uint32_t> ids = get_geometry_element_ids_as_uints(src_geometry, ATTR_DOMAIN_POINT); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + const int index = BLI_hash_int_2d(ids[i], seed) % possible_handles.size(); + const int handle = possible_handles[index]; + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); } } @@ -204,28 +221,37 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params) * rather than making the entire input geometry set real. */ geometry_set = geometry_set_realize_instances(geometry_set); + const Vector<InstanceReference> possible_references = get_instance_references(params); + if (possible_references.is_empty()) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Array<int> possible_handles = add_instance_references(instances, possible_references); + if (geometry_set.has<MeshComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<MeshComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<MeshComponent>(), + possible_handles, + params); } if (geometry_set.has<PointCloudComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + possible_handles, + params); + } + if (geometry_set.has<CurveComponent>()) { + add_instances_from_component(instances, + *geometry_set.get_component_for_read<CurveComponent>(), + possible_handles, + params); } params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( - sizeof(NodeGeometryPointInstance), __func__); - data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; - data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; - node->storage = data; -} - } // namespace blender::nodes void register_node_type_geo_point_instance() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc index 5eef2fabce0..828d3f50551 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc @@ -14,13 +14,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "BLI_math_rotation.h" #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_point_rotate_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Axis")}, @@ -59,9 +59,43 @@ static void geo_node_point_rotate_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( + sizeof(NodeGeometryRotatePoints), __func__); + + node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; + node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); +} + static void point_rotate__axis_angle__object_space(const int domain_size, - const Float3ReadAttribute &axis, - const FloatReadAttribute &angles, + const VArray<float3> &axis, + const VArray<float> &angles, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -76,8 +110,8 @@ static void point_rotate__axis_angle__object_space(const int domain_size, } static void point_rotate__axis_angle__point_space(const int domain_size, - const Float3ReadAttribute &axis, - const FloatReadAttribute &angles, + const VArray<float3> &axis, + const VArray<float> &angles, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -92,7 +126,7 @@ static void point_rotate__axis_angle__point_space(const int domain_size, } static void point_rotate__euler__object_space(const int domain_size, - const Float3ReadAttribute &eulers, + const VArray<float3> &eulers, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -107,7 +141,7 @@ static void point_rotate__euler__object_space(const int domain_size, } static void point_rotate__euler__point_space(const int domain_size, - const Float3ReadAttribute &eulers, + const VArray<float3> &eulers, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -127,19 +161,19 @@ static void point_rotate_on_component(GeometryComponent &component, const bNode &node = params.node(); const NodeGeometryRotatePoints &storage = *(const NodeGeometryRotatePoints *)node.storage; - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> rotation_attribute = + component.attribute_try_get_for_output<float3>("rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); if (!rotation_attribute) { return; } - MutableSpan<float3> rotations = rotation_attribute->get_span<float3>(); + MutableSpan<float3> rotations = rotation_attribute.as_span(); const int domain_size = rotations.size(); if (storage.type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE) { - Float3ReadAttribute axis = params.get_input_attribute<float3>( + GVArray_Typed<float3> axis = params.get_input_attribute<float3>( "Axis", component, ATTR_DOMAIN_POINT, {0, 0, 1}); - FloatReadAttribute angles = params.get_input_attribute<float>( + GVArray_Typed<float> angles = params.get_input_attribute<float>( "Angle", component, ATTR_DOMAIN_POINT, 0); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -150,7 +184,7 @@ static void point_rotate_on_component(GeometryComponent &component, } } else { - Float3ReadAttribute eulers = params.get_input_attribute<float3>( + GVArray_Typed<float3> eulers = params.get_input_attribute<float3>( "Rotation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -161,7 +195,7 @@ static void point_rotate_on_component(GeometryComponent &component, } } - rotation_attribute.apply_span_and_save(); + rotation_attribute.save(); } static void geo_node_point_rotate_exec(GeoNodeExecParams params) @@ -176,44 +210,13 @@ static void geo_node_point_rotate_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { point_rotate_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + point_rotate_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( - sizeof(NodeGeometryRotatePoints), __func__); - - node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; - node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; - node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; - update_attribute_input_socket_availabilities( - *node, - "Axis", - (GeometryNodeAttributeInputMode)node_storage->input_type_axis, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Angle", - (GeometryNodeAttributeInputMode)node_storage->input_type_angle, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Rotation", - (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); -} - } // namespace blender::nodes void register_node_type_geo_point_rotate() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc index 9df103ff057..265ec9fb776 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc @@ -14,17 +14,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - #include "BKE_colorband.h" #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_point_scale_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Factor")}, {SOCK_VECTOR, N_("Factor"), 1.0f, 1.0f, 1.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {SOCK_FLOAT, N_("Factor"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, {-1, ""}, }; @@ -42,27 +43,64 @@ static void geo_node_point_scale_layout(uiLayout *layout, bContext *UNUSED(C), P namespace blender::nodes { +static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( + sizeof(NodeGeometryPointScale), __func__); + + data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node->storage = data; +} + +static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) { + /* Note that scale doesn't necessarily need to be created with a vector type-- it could also use + * the highest complexity of the existing attribute's type (if it exists) and the data type used + * for the factor. But for it's simpler to simply always use float3, since that is usually + * expected anyway. */ static const float3 scale_default = float3(1.0f); - OutputAttributePtr scale_attribute = component.attribute_try_get_for_output( + OutputAttribute_Typed<float3> scale_attribute = component.attribute_try_get_for_output( "scale", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, &scale_default); if (!scale_attribute) { return; } - ReadAttributePtr attribute = params.get_input_attribute( - "Factor", component, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, nullptr); + + const bNode &node = params.node(); + const NodeGeometryPointScale &node_storage = *(const NodeGeometryPointScale *)node.storage; + const GeometryNodeAttributeInputMode input_type = (GeometryNodeAttributeInputMode) + node_storage.input_type; + const CustomDataType data_type = (input_type == GEO_NODE_ATTRIBUTE_INPUT_FLOAT) ? CD_PROP_FLOAT : + CD_PROP_FLOAT3; + + GVArrayPtr attribute = params.get_input_attribute( + "Factor", component, ATTR_DOMAIN_POINT, data_type, nullptr); if (!attribute) { return; } - Span<float3> data = attribute->get_span<float3>(); - MutableSpan<float3> scale_span = scale_attribute->get_span<float3>(); - for (const int i : scale_span.index_range()) { - scale_span[i] = scale_span[i] * data[i]; + MutableSpan<float3> scale_span = scale_attribute.as_span(); + if (data_type == CD_PROP_FLOAT) { + GVArray_Typed<float> factors{*attribute}; + for (const int i : scale_span.index_range()) { + scale_span[i] = scale_span[i] * factors[i]; + } + } + else if (data_type == CD_PROP_FLOAT3) { + GVArray_Typed<float3> factors{*attribute}; + for (const int i : scale_span.index_range()) { + scale_span[i] = scale_span[i] * factors[i]; + } } - scale_attribute.apply_span_and_save(); + scale_attribute.save(); } static void geo_node_point_scale_exec(GeoNodeExecParams params) @@ -77,27 +115,13 @@ static void geo_node_point_scale_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( - sizeof(NodeGeometryPointScale), __func__); - - data->input_type = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - node->storage = data; -} - -static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; - - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); -} - } // namespace blender::nodes void register_node_type_geo_point_scale() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc index 522dea4aa0e..fc04d1e275f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc @@ -52,33 +52,33 @@ static void copy_data_based_on_mask(Span<T> data, } } -static void copy_attributes_based_on_mask(const GeometryComponent &in_component, - GeometryComponent &result_component, - Span<bool> masks, - const bool invert) +void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, + GeometryComponent &result_component, + Span<bool> masks, + const bool invert) { for (const std::string &name : in_component.attribute_names()) { - ReadAttributePtr attribute = in_component.attribute_try_get_for_read(name); - const CustomDataType data_type = attribute->custom_data_type(); + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name); + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type()); /* Only copy point attributes. Theoretically this could interpolate attributes on other * domains to the point domain, but that would conflict with attributes that are built-in * on other domains, which causes creating the attributes to fail. */ - if (attribute->domain() != ATTR_DOMAIN_POINT) { + if (attribute.domain != ATTR_DOMAIN_POINT) { continue; } - OutputAttributePtr result_attribute = result_component.attribute_try_get_for_output( + OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( name, ATTR_DOMAIN_POINT, data_type); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); - Span<T> span = attribute->get_span<T>(); - MutableSpan<T> out_span = result_attribute->get_span_for_write_only<T>(); + GVArray_Span<T> span{*attribute.varray}; + MutableSpan<T> out_span = result_attribute.as_span<T>(); copy_data_based_on_mask(span, masks, invert, out_span); }); - result_attribute.apply_span_and_save(); + result_attribute.save(); } } @@ -107,9 +107,9 @@ static void separate_points_from_component(const GeometryComponent &in_component return; } - const BooleanReadAttribute mask_attribute = in_component.attribute_get_for_read<bool>( + const GVArray_Typed<bool> mask_attribute = in_component.attribute_get_for_read<bool>( mask_name, ATTR_DOMAIN_POINT, false); - Span<bool> masks = mask_attribute.get_span(); + VArray_Span<bool> masks{mask_attribute}; const int total = masks.count(!invert); if (total == 0) { @@ -118,7 +118,7 @@ static void separate_points_from_component(const GeometryComponent &in_component create_component_points(out_component, total); - copy_attributes_based_on_mask(in_component, out_component, masks, invert); + copy_point_attributes_based_on_mask(in_component, out_component, masks, invert); } static GeometrySet separate_geometry_set(const GeometrySet &set_in, @@ -127,6 +127,10 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, { GeometrySet set_out; for (const GeometryComponent *component : set_in.get_components_for_read()) { + if (component->type() == GEO_COMPONENT_TYPE_CURVE) { + /* Don't support the curve component for now, even though it has a point domain. */ + continue; + } GeometryComponent &out_component = set_out.get_component_for_write(component->type()); separate_points_from_component(*component, out_component, mask_name, invert); } @@ -135,15 +139,27 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, static void geo_node_point_separate_exec(GeoNodeExecParams params) { - const std::string mask_attribute_name = params.extract_input<std::string>("Mask"); - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + bool wait_for_inputs = false; + wait_for_inputs |= params.lazy_require_input("Geometry"); + wait_for_inputs |= params.lazy_require_input("Mask"); + if (wait_for_inputs) { + return; + } + const std::string mask_attribute_name = params.get_input<std::string>("Mask"); + GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); /* TODO: This is not necessary-- the input geometry set can be read only, * but it must be rewritten to handle instance groups. */ geometry_set = geometry_set_realize_instances(geometry_set); - params.set_output("Geometry 1", separate_geometry_set(geometry_set, mask_attribute_name, true)); - params.set_output("Geometry 2", separate_geometry_set(geometry_set, mask_attribute_name, false)); + if (params.lazy_output_is_required("Geometry 1")) { + params.set_output("Geometry 1", + separate_geometry_set(geometry_set, mask_attribute_name, true)); + } + if (params.lazy_output_is_required("Geometry 2")) { + params.set_output("Geometry 2", + separate_geometry_set(geometry_set, mask_attribute_name, false)); + } } } // namespace blender::nodes @@ -155,5 +171,6 @@ void register_node_type_geo_point_separate() geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 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_separate_exec; + ntype.geometry_node_execute_supports_laziness = true; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc index 015f4cd38e7..293f151fe19 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc @@ -14,13 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_geometry_util.hh" - -#include "BKE_colorband.h" - #include "UI_interface.h" #include "UI_resources.h" +#include "node_geometry_util.hh" + static bNodeSocketTemplate geo_node_point_translate_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Translation")}, @@ -44,24 +42,19 @@ namespace blender::nodes { static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) { - OutputAttributePtr position_attribute = component.attribute_try_get_for_output( - "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> position_attribute = + component.attribute_try_get_for_output<float3>("position", ATTR_DOMAIN_POINT, {0, 0, 0}); if (!position_attribute) { return; } - ReadAttributePtr attribute = params.get_input_attribute( - "Translation", component, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, nullptr); - if (!attribute) { - return; - } + GVArray_Typed<float3> attribute = params.get_input_attribute<float3>( + "Translation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); - Span<float3> data = attribute->get_span<float3>(); - MutableSpan<float3> scale_span = position_attribute->get_span<float3>(); - for (const int i : scale_span.index_range()) { - scale_span[i] = scale_span[i] + data[i]; + for (const int i : IndexRange(attribute.size())) { + position_attribute->set(i, position_attribute->get(i) + attribute[i]); } - position_attribute.apply_span_and_save(); + position_attribute.save(); } static void geo_node_point_translate_exec(GeoNodeExecParams params) @@ -76,6 +69,9 @@ static void geo_node_point_translate_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } params.set_output("Geometry", std::move(geometry_set)); } @@ -85,7 +81,7 @@ static void geo_node_point_translate_init(bNodeTree *UNUSED(tree), bNode *node) NodeGeometryPointTranslate *data = (NodeGeometryPointTranslate *)MEM_callocN( sizeof(NodeGeometryPointTranslate), __func__); - data->input_type = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; node->storage = data; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index b79ca0a6197..65306b1c452 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -31,14 +31,14 @@ static bNodeSocketTemplate geo_node_points_to_volume_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_FLOAT, N_("Density"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, - {SOCK_FLOAT, N_("Voxel Size"), 0.3f, 0.0f, 0.0f, 0.0f, 0.01f, FLT_MAX}, + {SOCK_FLOAT, N_("Voxel Size"), 0.3f, 0.0f, 0.0f, 0.0f, 0.01f, FLT_MAX, PROP_DISTANCE}, {SOCK_FLOAT, N_("Voxel Amount"), 64.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, {SOCK_STRING, N_("Radius")}, {SOCK_FLOAT, N_("Radius"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, {-1, ""}, }; -static bNodeSocketTemplate geo_node_point_translate_out[] = { +static bNodeSocketTemplate geo_node_points_to_volume_out[] = { {SOCK_GEOMETRY, N_("Geometry")}, {-1, ""}, }; @@ -55,6 +55,35 @@ static void geo_node_points_to_volume_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( + sizeof(NodeGeometryPointsToVolume), __func__); + data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; + + bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); + bNodeSocketValueString *radius_attribute_socket_value = + (bNodeSocketValueString *)radius_attribute_socket->default_value; + STRNCPY(radius_attribute_socket_value->value, "radius"); +} + +static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(voxel_amount_socket, + data->resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); + nodeSetSocketAvailability( + voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); + + update_attribute_input_socket_availabilities( + *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); +} + #ifdef WITH_OPENVDB namespace { /* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */ @@ -147,13 +176,15 @@ static void gather_point_data_from_component(const GeoNodeExecParams ¶ms, Vector<float3> &r_positions, Vector<float> &r_radii) { - Float3ReadAttribute positions = component.attribute_get_for_read<float3>( + GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); - FloatReadAttribute radii = params.get_input_attribute<float>( + GVArray_Typed<float> radii = params.get_input_attribute<float>( "Radius", component, ATTR_DOMAIN_POINT, 0.0f); - r_positions.extend(positions.get_span()); - r_radii.extend(radii.get_span()); + for (const int i : IndexRange(positions.size())) { + r_positions.append(positions[i]); + r_radii.append(radii[i]); + } } static void convert_to_grid_index_space(const float voxel_size, @@ -184,6 +215,10 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_ gather_point_data_from_component( params, *geometry_set_in.get_component_for_read<PointCloudComponent>(), positions, radii); } + if (geometry_set_in.has<CurveComponent>()) { + gather_point_data_from_component( + params, *geometry_set_in.get_component_for_read<CurveComponent>(), positions, radii); + } const float max_radius = *std::max_element(radii.begin(), radii.end()); const float voxel_size = compute_voxel_size(params, positions, max_radius); @@ -225,35 +260,6 @@ static void geo_node_points_to_volume_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( - sizeof(NodeGeometryPointsToVolume), __func__); - data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; - data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node->storage = data; - - bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); - bNodeSocketValueString *radius_attribute_socket_value = - (bNodeSocketValueString *)radius_attribute_socket->default_value; - STRNCPY(radius_attribute_socket_value->value, "radius"); -} - -static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; - bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); - bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); - nodeSetSocketAvailability(voxel_amount_socket, - data->resolution_mode == - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); - nodeSetSocketAvailability( - voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); - - update_attribute_input_socket_availabilities( - *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); -} - } // namespace blender::nodes void register_node_type_geo_points_to_volume() @@ -262,7 +268,7 @@ void register_node_type_geo_points_to_volume() geo_node_type_base( &ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); - node_type_socket_templates(&ntype, geo_node_points_to_volume_in, geo_node_point_translate_out); + node_type_socket_templates(&ntype, geo_node_points_to_volume_in, geo_node_points_to_volume_out); node_type_storage(&ntype, "NodeGeometryPointsToVolume", node_free_standard_storage, diff --git a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc new file mode 100644 index 00000000000..9bc963eec43 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc @@ -0,0 +1,106 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_task.hh" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_select_by_material_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, + N_("Material"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_HIDE_LABEL}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_select_by_material_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void select_mesh_by_material(const Mesh &mesh, + const Material *material, + const MutableSpan<bool> r_selection) +{ + BLI_assert(mesh.totpoly == r_selection.size()); + Vector<int> material_indices; + for (const int i : IndexRange(mesh.totcol)) { + if (mesh.mat[i] == material) { + material_indices.append(i); + } + } + parallel_for(r_selection.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + r_selection[i] = material_indices.contains(mesh.mpoly[i].mat_nr); + } + }); +} + +static void geo_node_select_by_material_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string selection_name = params.extract_input<std::string>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh != nullptr) { + OutputAttribute_Typed<bool> selection = + mesh_component.attribute_try_get_for_output_only<bool>(selection_name, ATTR_DOMAIN_FACE); + if (selection) { + select_mesh_by_material(*mesh, material, selection.as_span()); + selection.save(); + } + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_select_by_material() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates( + &ntype, geo_node_select_by_material_in, geo_node_select_by_material_out); + ntype.geometry_node_execute = blender::nodes::geo_node_select_by_material_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivide.cc index 06c5586a3ff..a4c6522c57a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivide.cc @@ -14,7 +14,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "MEM_guardedalloc.h" +// #include "MEM_guardedalloc.h" #include "BKE_mesh.h" #include "BKE_subdiv.h" @@ -41,6 +41,7 @@ namespace blender::nodes { static void geo_node_subdivide_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = geometry_set_realize_instances(geometry_set); if (!geometry_set.has_mesh()) { params.set_output("Geometry", geometry_set); @@ -49,7 +50,7 @@ static void geo_node_subdivide_exec(GeoNodeExecParams params) #ifndef WITH_OPENSUBDIV params.error_message_add(NodeWarningType::Error, - TIP_("Disabled, Blender was built without OpenSubdiv")); + TIP_("Disabled, Blender was compiled without OpenSubdiv")); params.set_output("Geometry", std::move(geometry_set)); return; #endif @@ -98,6 +99,7 @@ static void geo_node_subdivide_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_subdivide() diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc index b14a512ce28..20ffcdb516d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -14,8 +14,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "MEM_guardedalloc.h" - #include "BKE_mesh.h" #include "BKE_subdiv.h" #include "BKE_subdiv_mesh.h" @@ -39,17 +37,6 @@ static bNodeSocketTemplate geo_node_subdivision_surface_out[] = { {-1, ""}, }; -static void geo_node_subdivision_surface_layout(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 -} - namespace blender::nodes { static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) { @@ -63,9 +50,8 @@ static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) } #ifndef WITH_OPENSUBDIV - /* Return input geometry if Blender is built without OpenSubdiv. */ - params.set_output("Geometry", std::move(geometry_set)); - return; + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenSubdiv")); #else const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 30); @@ -115,9 +101,11 @@ static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) // BKE_subdiv_stats_print(&subdiv->stats); BKE_subdiv_free(subdiv); - params.set_output("Geometry", std::move(geometry_set)); #endif + + params.set_output("Geometry", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_subdivision_surface() @@ -129,6 +117,5 @@ void register_node_type_geo_subdivision_surface() 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; - ntype.draw_buttons = geo_node_subdivision_surface_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc new file mode 100644 index 00000000000..0aa5c68aaf5 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -0,0 +1,191 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +static bNodeSocketTemplate geo_node_switch_in[] = { + {SOCK_BOOLEAN, N_("Switch")}, + + {SOCK_FLOAT, N_("False"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("True"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_INT, N_("False"), 0, 0, 0, 0, -100000, 100000}, + {SOCK_INT, N_("True"), 0, 0, 0, 0, -100000, 100000}, + {SOCK_BOOLEAN, N_("False")}, + {SOCK_BOOLEAN, N_("True")}, + {SOCK_VECTOR, N_("False"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("True"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_RGBA, N_("False"), 0.8, 0.8, 0.8, 1.0}, + {SOCK_RGBA, N_("True"), 0.8, 0.8, 0.8, 1.0}, + {SOCK_STRING, N_("False")}, + {SOCK_STRING, N_("True")}, + {SOCK_GEOMETRY, N_("False")}, + {SOCK_GEOMETRY, N_("True")}, + {SOCK_OBJECT, N_("False")}, + {SOCK_OBJECT, N_("True")}, + {SOCK_COLLECTION, N_("False")}, + {SOCK_COLLECTION, N_("True")}, + {SOCK_TEXTURE, N_("False")}, + {SOCK_TEXTURE, N_("True")}, + {SOCK_MATERIAL, N_("False")}, + {SOCK_MATERIAL, N_("True")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_switch_out[] = { + {SOCK_FLOAT, N_("Output")}, + {SOCK_INT, N_("Output")}, + {SOCK_BOOLEAN, N_("Output")}, + {SOCK_VECTOR, N_("Output")}, + {SOCK_RGBA, N_("Output")}, + {SOCK_STRING, N_("Output")}, + {SOCK_GEOMETRY, N_("Output")}, + {SOCK_OBJECT, N_("Output")}, + {SOCK_COLLECTION, N_("Output")}, + {SOCK_TEXTURE, N_("Output")}, + {SOCK_MATERIAL, N_("Output")}, + {-1, ""}, +}; + +static void geo_node_switch_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "input_type", 0, "", ICON_NONE); +} + +static void geo_node_switch_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeSwitch *data = (NodeSwitch *)MEM_callocN(sizeof(NodeSwitch), __func__); + data->input_type = SOCK_GEOMETRY; + node->storage = data; +} + +namespace blender::nodes { + +static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeSwitch *node_storage = (NodeSwitch *)node->storage; + int index = 0; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + nodeSetSocketAvailability( + socket, index == 0 || socket->type == (eNodeSocketDatatype)node_storage->input_type); + index++; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + nodeSetSocketAvailability(socket, + socket->type == (eNodeSocketDatatype)node_storage->input_type); + } +} + +template<typename T> +static void output_input(GeoNodeExecParams ¶ms, + const bool input, + const StringRef input_suffix, + const StringRef output_identifier) +{ + const std::string name_a = "False" + input_suffix; + const std::string name_b = "True" + input_suffix; + if (input) { + params.set_input_unused(name_a); + if (params.lazy_require_input(name_b)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_b)); + } + else { + params.set_input_unused(name_b); + if (params.lazy_require_input(name_a)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_a)); + } +} + +static void geo_node_switch_exec(GeoNodeExecParams params) +{ + if (params.lazy_require_input("Switch")) { + return; + } + const NodeSwitch &storage = *(const NodeSwitch *)params.node().storage; + const bool input = params.get_input<bool>("Switch"); + switch ((eNodeSocketDatatype)storage.input_type) { + case SOCK_FLOAT: { + output_input<float>(params, input, "", "Output"); + break; + } + case SOCK_INT: { + output_input<int>(params, input, "_001", "Output_001"); + break; + } + case SOCK_BOOLEAN: { + output_input<bool>(params, input, "_002", "Output_002"); + break; + } + case SOCK_VECTOR: { + output_input<float3>(params, input, "_003", "Output_003"); + break; + } + case SOCK_RGBA: { + output_input<ColorGeometry4f>(params, input, "_004", "Output_004"); + break; + } + case SOCK_STRING: { + output_input<std::string>(params, input, "_005", "Output_005"); + break; + } + case SOCK_GEOMETRY: { + output_input<GeometrySet>(params, input, "_006", "Output_006"); + break; + } + case SOCK_OBJECT: { + output_input<Object *>(params, input, "_007", "Output_007"); + break; + } + case SOCK_COLLECTION: { + output_input<Collection *>(params, input, "_008", "Output_008"); + break; + } + case SOCK_TEXTURE: { + output_input<Tex *>(params, input, "_009", "Output_009"); + break; + } + case SOCK_MATERIAL: { + output_input<Material *>(params, input, "_010", "Output_010"); + break; + } + default: + BLI_assert_unreachable(); + break; + } +} + +} // namespace blender::nodes + +void register_node_type_geo_switch() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SWITCH, "Switch", NODE_CLASS_CONVERTOR, 0); + node_type_socket_templates(&ntype, geo_node_switch_in, geo_node_switch_out); + node_type_init(&ntype, geo_node_switch_init); + node_type_update(&ntype, blender::nodes::geo_node_switch_update); + node_type_storage(&ntype, "NodeSwitch", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_switch_exec; + ntype.geometry_node_execute_supports_laziness = true; + ntype.draw_buttons = geo_node_switch_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index 842a78b839a..cdd2df56b77 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -18,12 +18,14 @@ # include <openvdb/openvdb.h> #endif -#include "BLI_math_matrix.h" +#include "BLI_float4x4.hh" +#include "DNA_mesh_types.h" #include "DNA_pointcloud_types.h" #include "DNA_volume_types.h" #include "BKE_mesh.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DEG_depsgraph_query.h" @@ -81,14 +83,14 @@ void transform_mesh(Mesh *mesh, /* Use only translation if rotation and scale are zero. */ if (use_translate(rotation, scale)) { if (!translation.is_zero()) { - BKE_mesh_translate(mesh, translation, true); + BKE_mesh_translate(mesh, translation, false); } } 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); + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + BKE_mesh_transform(mesh, matrix.values, false); + mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; } } @@ -99,15 +101,15 @@ static void transform_pointcloud(PointCloud *pointcloud, { /* Use only translation if rotation and scale don't apply. */ if (use_translate(rotation, scale)) { - for (int i = 0; i < pointcloud->totpoint; i++) { + for (const int i : IndexRange(pointcloud->totpoint)) { 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]); + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + for (const int i : IndexRange(pointcloud->totpoint)) { + float3 &co = *(float3 *)pointcloud->co[i]; + co = matrix * co; } } } @@ -117,7 +119,7 @@ static void transform_instances(InstancesComponent &instances, const float3 rotation, const float3 scale) { - MutableSpan<float4x4> transforms = instances.transforms(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); /* Use only translation if rotation and scale don't apply. */ if (use_translate(rotation, scale)) { @@ -126,11 +128,9 @@ static void transform_instances(InstancesComponent &instances, } } else { - float mat[4][4]; - - loc_eul_size_to_mat4(mat, translation, rotation, scale); + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); for (float4x4 &transform : transforms) { - mul_m4_m4_pre(transform.ptr(), mat); + transform = matrix * transform; } } } @@ -149,11 +149,10 @@ static void transform_volume(Volume *volume, (scale.z == 0.0f) ? FLT_EPSILON : scale.z, }; - Main *bmain = DEG_get_bmain(params.depsgraph()); + const Main *bmain = DEG_get_bmain(params.depsgraph()); BKE_volume_load(volume, bmain); - float matrix[4][4]; - loc_eul_size_to_mat4(matrix, translation, rotation, limited_scale); + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, limited_scale); openvdb::Mat4s vdb_matrix; memcpy(vdb_matrix.asPointer(), matrix, sizeof(float[4][4])); @@ -161,7 +160,7 @@ static void transform_volume(Volume *volume, const int num_grids = BKE_volume_num_grids(volume); for (const int i : IndexRange(num_grids)) { - VolumeGrid *volume_grid = BKE_volume_grid_get(volume, i); + VolumeGrid *volume_grid = BKE_volume_grid_get_for_write(volume, i); openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); openvdb::math::Transform &grid_transform = grid->transform(); @@ -172,6 +171,20 @@ static void transform_volume(Volume *volume, #endif } +static void transform_curve(CurveEval &curve, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + if (use_translate(rotation, scale)) { + curve.translate(translation); + } + else { + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + curve.transform(matrix); + } +} + static void geo_node_transform_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -183,21 +196,22 @@ static void geo_node_transform_exec(GeoNodeExecParams params) 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); } - if (geometry_set.has_volume()) { Volume *volume = geometry_set.get_volume_for_write(); transform_volume(volume, translation, rotation, scale, params); } + if (geometry_set.has_curve()) { + CurveEval *curve = geometry_set.get_curve_for_write(); + transform_curve(*curve, translation, rotation, scale); + } params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index 0c754ad3b37..03581b28f18 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -14,10 +14,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "DNA_node_types.h" - -#include "RNA_enum_types.h" - #include "UI_interface.h" #include "UI_resources.h" diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index 54dccb613a1..403f4906d07 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -23,6 +23,7 @@ #include "node_geometry_util.hh" #include "BKE_lib_id.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_volume.h" @@ -36,8 +37,8 @@ static bNodeSocketTemplate geo_node_volume_to_mesh_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_STRING, N_("Grid")}, - {SOCK_FLOAT, N_("Voxel Size"), 0.3f, 0.0f, 0.0f, 0.0f, 0.01f, FLT_MAX}, + {SOCK_STRING, N_("Density")}, + {SOCK_FLOAT, N_("Voxel Size"), 0.3f, 0.0f, 0.0f, 0.0f, 0.01f, FLT_MAX, PROP_DISTANCE}, {SOCK_FLOAT, N_("Voxel Amount"), 64.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, {SOCK_FLOAT, N_("Threshold"), 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, {SOCK_FLOAT, N_("Adaptivity"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, @@ -64,7 +65,7 @@ static void geo_node_volume_to_mesh_init(bNodeTree *UNUSED(ntree), bNode *node) sizeof(NodeGeometryVolumeToMesh), __func__); data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID; - bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Grid"); + bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Density"); bNodeSocketValueString *grid_socket_value = (bNodeSocketValueString *)grid_socket->default_value; STRNCPY(grid_socket_value->value, "density"); @@ -117,11 +118,11 @@ static void create_mesh_from_volume(GeometrySet &geometry_set_in, return; } - Main *bmain = DEG_get_bmain(params.depsgraph()); - BKE_volume_load(const_cast<Volume *>(volume), bmain); + const Main *bmain = DEG_get_bmain(params.depsgraph()); + BKE_volume_load(volume, bmain); - const std::string grid_name = params.get_input<std::string>("Grid"); - VolumeGrid *volume_grid = BKE_volume_grid_find(volume, grid_name.c_str()); + const std::string grid_name = params.get_input<std::string>("Density"); + const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, grid_name.c_str()); if (volume_grid == nullptr) { return; } @@ -134,6 +135,7 @@ static void create_mesh_from_volume(GeometrySet &geometry_set_in, if (mesh == nullptr) { return; } + BKE_id_material_eval_ensure_default_slot(&mesh->id); MeshComponent &dst_component = geometry_set_out.get_component_for_write<MeshComponent>(); dst_component.replace(mesh); } diff --git a/source/blender/nodes/intern/derived_node_tree.cc b/source/blender/nodes/intern/derived_node_tree.cc index e4f8a0c5fda..9a3eb574bcd 100644 --- a/source/blender/nodes/intern/derived_node_tree.cc +++ b/source/blender/nodes/intern/derived_node_tree.cc @@ -40,6 +40,7 @@ DTreeContext &DerivedNodeTree::construct_context_recursively(DTreeContext *paren DTreeContext &context = *allocator_.construct<DTreeContext>().release(); context.parent_context_ = parent_context; context.parent_node_ = parent_node; + context.derived_tree_ = this; context.tree_ = &get_tree_ref_from_map(node_tree_refs, btree); used_node_tree_refs_.add(context.tree_); @@ -83,6 +84,16 @@ bool DerivedNodeTree::has_link_cycles() const return false; } +bool DerivedNodeTree::has_undefined_nodes_or_sockets() const +{ + for (const NodeTreeRef *tree_ref : used_node_tree_refs_) { + if (tree_ref->has_undefined_nodes_or_sockets()) { + return true; + } + } + return false; +} + /* Calls the given callback on all nodes in the (possibly nested) derived node tree. */ void DerivedNodeTree::foreach_node(FunctionRef<void(DNode)> callback) const { @@ -167,10 +178,10 @@ DInputSocket DOutputSocket::get_active_corresponding_group_output_socket() const return {}; } -/* Call the given callback for every "real" origin socket. "Real" means that reroutes, muted nodes +/* Call `origin_fn` for every "real" origin socket. "Real" means that reroutes, muted nodes * and node groups are handled by this function. Origin sockets are ones where a node gets its * inputs from. */ -void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) const +void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) const { BLI_assert(*this); for (const OutputSocketRef *linked_socket : socket_ref_->as_input().logically_linked_sockets()) { @@ -180,18 +191,18 @@ void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) co if (linked_node.is_group_input_node()) { if (context_->is_root()) { /* This is a group input in the root node group. */ - callback(linked_dsocket); + origin_fn(linked_dsocket); } else { DInputSocket socket_in_parent_group = linked_dsocket.get_corresponding_group_node_input(); if (socket_in_parent_group->is_logically_linked()) { /* Follow the links coming into the corresponding socket on the parent group node. */ - socket_in_parent_group.foreach_origin_socket(callback); + socket_in_parent_group.foreach_origin_socket(origin_fn); } else { /* The corresponding input on the parent group node is not connected. Therefore, we use * the value of that input socket directly. */ - callback(socket_in_parent_group); + origin_fn(socket_in_parent_group); } } } @@ -200,27 +211,32 @@ void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) co if (socket_in_group) { if (socket_in_group->is_logically_linked()) { /* Follow the links coming into the group output node of the child node group. */ - socket_in_group.foreach_origin_socket(callback); + socket_in_group.foreach_origin_socket(origin_fn); } else { /* The output of the child node group is not connected, so we have to get the value from * that socket. */ - callback(socket_in_group); + origin_fn(socket_in_group); } } } else { /* The normal case: just use the value of a linked output socket. */ - callback(linked_dsocket); + origin_fn(linked_dsocket); } } } -/* Calls the given callback for every "real" target socket. "Real" means that reroutes, muted nodes +/* Calls `target_fn` for every "real" target socket. "Real" means that reroutes, muted nodes * and node groups are handled by this function. Target sockets are on the nodes that use the value - * from this socket. */ -void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> callback) const + * from this socket. The `skipped_fn` function is called for sockets that have been skipped during + * the search for target sockets (e.g. reroutes). */ +void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> target_fn, + FunctionRef<void(DSocket)> skipped_fn) const { + for (const SocketRef *skipped_socket : socket_ref_->logically_linked_skipped_sockets()) { + skipped_fn.call_safe({context_, skipped_socket}); + } for (const InputSocketRef *linked_socket : socket_ref_->as_output().logically_linked_sockets()) { const NodeRef &linked_node = linked_socket->node(); DInputSocket linked_dsocket{context_, linked_socket}; @@ -228,26 +244,30 @@ void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> callba if (linked_node.is_group_output_node()) { if (context_->is_root()) { /* This is a group output in the root node group. */ - callback(linked_dsocket); + target_fn(linked_dsocket); } else { /* Follow the links going out of the group node in the parent node group. */ DOutputSocket socket_in_parent_group = linked_dsocket.get_corresponding_group_node_output(); - socket_in_parent_group.foreach_target_socket(callback); + skipped_fn.call_safe(linked_dsocket); + skipped_fn.call_safe(socket_in_parent_group); + socket_in_parent_group.foreach_target_socket(target_fn, skipped_fn); } } else if (linked_node.is_group_node()) { /* Follow the links within the nested node group. */ Vector<DOutputSocket> sockets_in_group = linked_dsocket.get_corresponding_group_input_sockets(); + skipped_fn.call_safe(linked_dsocket); for (DOutputSocket socket_in_group : sockets_in_group) { - socket_in_group.foreach_target_socket(callback); + skipped_fn.call_safe(socket_in_group); + socket_in_group.foreach_target_socket(target_fn, skipped_fn); } } else { /* The normal case: just use the linked input socket as target. */ - callback(linked_dsocket); + target_fn(linked_dsocket); } } } diff --git a/source/blender/nodes/intern/math_functions.cc b/source/blender/nodes/intern/math_functions.cc index fb34144abf6..aa23777b664 100644 --- a/source/blender/nodes/intern/math_functions.cc +++ b/source/blender/nodes/intern/math_functions.cc @@ -209,6 +209,8 @@ const FloatMathOperationInfo *get_float3_math_operation_info(const int operation RETURN_OPERATION_INFO("Refract", "vector_math_refract"); case NODE_VECTOR_MATH_FACEFORWARD: RETURN_OPERATION_INFO("Faceforward", "vector_math_faceforward"); + case NODE_VECTOR_MATH_MULTIPLY_ADD: + RETURN_OPERATION_INFO("Multiply Add", "vector_math_multiply_add"); } #undef RETURN_OPERATION_INFO diff --git a/source/blender/nodes/intern/node_common.c b/source/blender/nodes/intern/node_common.c index 0b1ab85c059..7fc9f664df0 100644 --- a/source/blender/nodes/intern/node_common.c +++ b/source/blender/nodes/intern/node_common.c @@ -79,12 +79,12 @@ void node_group_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int ma BLI_strncpy(label, (node->id) ? node->id->name + 2 : IFACE_("Missing Data-Block"), maxlen); } -bool node_group_poll_instance(bNode *node, bNodeTree *nodetree) +bool node_group_poll_instance(bNode *node, bNodeTree *nodetree, const char **disabled_hint) { - if (node->typeinfo->poll(node->typeinfo, nodetree)) { + if (node->typeinfo->poll(node->typeinfo, nodetree, disabled_hint)) { bNodeTree *grouptree = (bNodeTree *)node->id; if (grouptree) { - return nodeGroupPoll(nodetree, grouptree); + return nodeGroupPoll(nodetree, grouptree, disabled_hint); } return true; /* without a linked node tree, group node is always ok */ @@ -93,25 +93,27 @@ bool node_group_poll_instance(bNode *node, bNodeTree *nodetree) return false; } -int nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree) +bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_disabled_hint) { bNode *node; - int valid = 1; + bool valid = true; /* unspecified node group, generally allowed * (if anything, should be avoided on operator level) */ if (grouptree == NULL) { - return 1; + return true; } if (nodetree == grouptree) { - return 0; + *r_disabled_hint = "Nesting a node group inside of itself is not allowed"; + return false; } for (node = grouptree->nodes.first; node; node = node->next) { - if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, nodetree)) { - valid = 0; + if (node->typeinfo->poll_instance && + !node->typeinfo->poll_instance(node, nodetree, r_disabled_hint)) { + valid = false; break; } } diff --git a/source/blender/nodes/intern/node_common.h b/source/blender/nodes/intern/node_common.h index 7aad6782640..cdb7b6897b9 100644 --- a/source/blender/nodes/intern/node_common.h +++ b/source/blender/nodes/intern/node_common.h @@ -32,7 +32,9 @@ extern "C" { struct bNodeTree; void node_group_label(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen); -bool node_group_poll_instance(struct bNode *node, struct bNodeTree *nodetree); +bool node_group_poll_instance(struct bNode *node, + struct bNodeTree *nodetree, + const char **r_disabled_hint); void ntree_update_reroute_nodes(struct bNodeTree *ntree); diff --git a/source/blender/nodes/intern/node_exec.c b/source/blender/nodes/intern/node_exec.cc index dd9d0b6796a..18403417af3 100644 --- a/source/blender/nodes/intern/node_exec.c +++ b/source/blender/nodes/intern/node_exec.cc @@ -47,7 +47,7 @@ bNodeStack *node_get_socket_stack(bNodeStack *stack, bNodeSocket *sock) if (stack && sock && sock->stack_index >= 0) { return stack + sock->stack_index; } - return NULL; + return nullptr; } void node_get_stack(bNode *node, bNodeStack *stack, bNodeStack **in, bNodeStack **out) @@ -56,13 +56,13 @@ void node_get_stack(bNode *node, bNodeStack *stack, bNodeStack **in, bNodeStack /* build pointer stack */ if (in) { - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { *(in++) = node_get_socket_stack(stack, sock); } } if (out) { - for (sock = node->outputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { *(out++) = node_get_socket_stack(stack, sock); } } @@ -90,7 +90,7 @@ static void node_init_output_index(bNodeSocket *sock, int *index, ListBase *inte if (internal_links) { bNodeLink *link; /* copy the stack index from internally connected input to skip the node */ - for (link = internal_links->first; link; link = link->next) { + for (link = (bNodeLink *)internal_links->first; link; link = link->next) { if (link->tosock == sock) { sock->stack_index = link->fromsock->stack_index; /* set the link pointer to indicate that this socket @@ -128,7 +128,7 @@ static struct bNodeStack *setup_stack(bNodeStack *stack, { bNodeStack *ns = node_get_socket_stack(stack, sock); if (!ns) { - return NULL; + return nullptr; } /* don't mess with remote socket stacks, these are initialized by other nodes! */ @@ -166,7 +166,7 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, int index; bNode **nodelist; int totnodes, n; - /* XXX texnodes have threading issues with muting, have to disable it there ... */ + /* XXX: texture-nodes have threading issues with muting, have to disable it there. */ /* ensure all sock->link pointers and node levels are correct */ /* Using global main here is likely totally wrong, not sure what to do about that one though... @@ -178,7 +178,7 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, ntreeGetDependencyList(ntree, &nodelist, &totnodes); /* XXX could let callbacks do this for specialized data */ - exec = MEM_callocN(sizeof(bNodeTreeExec), "node tree execution data"); + exec = (bNodeTreeExec *)MEM_callocN(sizeof(bNodeTreeExec), "node tree execution data"); /* backpointer to node tree */ exec->nodetree = ntree; @@ -188,28 +188,29 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, node = nodelist[n]; /* init node socket stack indexes */ - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { node_init_input_index(sock, &index); } if (node->flag & NODE_MUTED || node->type == NODE_REROUTE) { - for (sock = node->outputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { node_init_output_index(sock, &index, &node->internal_links); } } else { - for (sock = node->outputs.first; sock; sock = sock->next) { - node_init_output_index(sock, &index, NULL); + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { + node_init_output_index(sock, &index, nullptr); } } } /* allocated exec data pointers for nodes */ exec->totnodes = totnodes; - exec->nodeexec = MEM_callocN(exec->totnodes * sizeof(bNodeExec), "node execution data"); + exec->nodeexec = (bNodeExec *)MEM_callocN(exec->totnodes * sizeof(bNodeExec), + "node execution data"); /* allocate data pointer for node stack */ exec->stacksize = index; - exec->stack = MEM_callocN(exec->stacksize * sizeof(bNodeStack), "bNodeStack"); + exec->stack = (bNodeStack *)MEM_callocN(exec->stacksize * sizeof(bNodeStack), "bNodeStack"); /* all non-const results are considered inputs */ for (n = 0; n < exec->stacksize; n++) { @@ -222,7 +223,7 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, nodeexec->free_exec_fn = node->typeinfo->free_exec_fn; /* tag inputs */ - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { /* disable the node if an input link is invalid */ if (sock->link && !(sock->link->flag & NODE_LINK_VALID)) { node->need_exec = 0; @@ -235,14 +236,14 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, } /* tag all outputs */ - for (sock = node->outputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { /* ns = */ setup_stack(exec->stack, ntree, node, sock); } nodekey = BKE_node_instance_key(parent_key, ntree, node); - nodeexec->data.preview = context->previews ? - BKE_node_instance_hash_lookup(context->previews, nodekey) : - NULL; + nodeexec->data.preview = context->previews ? (bNodePreview *)BKE_node_instance_hash_lookup( + context->previews, nodekey) : + nullptr; if (node->typeinfo->init_exec_fn) { nodeexec->data.data = node->typeinfo->init_exec_fn(context, node, nodekey); } @@ -284,7 +285,7 @@ bNodeThreadStack *ntreeGetThreadStack(bNodeTreeExec *exec, int thread) ListBase *lb = &exec->threadstack[thread]; bNodeThreadStack *nts; - for (nts = lb->first; nts; nts = nts->next) { + for (nts = (bNodeThreadStack *)lb->first; nts; nts = nts->next) { if (!nts->used) { nts->used = true; break; @@ -292,8 +293,8 @@ bNodeThreadStack *ntreeGetThreadStack(bNodeTreeExec *exec, int thread) } if (!nts) { - nts = MEM_callocN(sizeof(bNodeThreadStack), "bNodeThreadStack"); - nts->stack = MEM_dupallocN(exec->stack); + nts = (bNodeThreadStack *)MEM_callocN(sizeof(bNodeThreadStack), "bNodeThreadStack"); + nts->stack = (bNodeStack *)MEM_dupallocN(exec->stack); nts->used = true; BLI_addtail(lb, nts); } @@ -303,13 +304,13 @@ bNodeThreadStack *ntreeGetThreadStack(bNodeTreeExec *exec, int thread) void ntreeReleaseThreadStack(bNodeThreadStack *nts) { - nts->used = 0; + nts->used = false; } bool ntreeExecThreadNodes(bNodeTreeExec *exec, bNodeThreadStack *nts, void *callerdata, int thread) { - bNodeStack *nsin[MAX_SOCKET] = {NULL}; /* arbitrary... watch this */ - bNodeStack *nsout[MAX_SOCKET] = {NULL}; /* arbitrary... watch this */ + bNodeStack *nsin[MAX_SOCKET] = {nullptr}; /* arbitrary... watch this */ + bNodeStack *nsout[MAX_SOCKET] = {nullptr}; /* arbitrary... watch this */ bNodeExec *nodeexec; bNode *node; int n; diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index a4fb99a988e..188d198e159 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -22,6 +22,7 @@ #include "NOD_geometry_exec.hh" #include "NOD_type_callbacks.hh" +#include "NOD_type_conversions.hh" #include "node_geometry_util.hh" @@ -29,22 +30,22 @@ namespace blender::nodes { void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const { - bNodeTree *btree_cow = node_->btree(); + bNodeTree *btree_cow = provider_->dnode->btree(); BLI_assert(btree_cow != nullptr); if (btree_cow == nullptr) { return; } bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow); - const NodeTreeEvaluationContext context(*self_object_, *modifier_); + const NodeTreeEvaluationContext context(*provider_->self_object, *provider_->modifier); BKE_nodetree_error_message_add( - *btree_original, context, *node_->bnode(), type, std::move(message)); + *btree_original, context, *provider_->dnode->bnode(), type, std::move(message)); } const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available() && socket->name() == name) { return socket->bsocket(); } @@ -53,22 +54,29 @@ const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name return nullptr; } -ReadAttributePtr GeoNodeExecParams::get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const CustomDataType type, - const void *default_value) const +GVArrayPtr GeoNodeExecParams::get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const CustomDataType type, + const void *default_value) const { const bNodeSocket *found_socket = this->find_available_socket(name); BLI_assert(found_socket != nullptr); /* There should always be available socket for the name. */ + const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(type); + const int64_t domain_size = component.attribute_domain_size(domain); + + if (default_value == nullptr) { + default_value = cpp_type->default_value(); + } + if (found_socket == nullptr) { - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); /* Try getting the attribute without the default value. */ - ReadAttributePtr attribute = component.attribute_try_get_for_read(name, domain, type); + GVArrayPtr attribute = component.attribute_try_get_for_read(name, domain, type); if (attribute) { return attribute; } @@ -80,25 +88,30 @@ ReadAttributePtr GeoNodeExecParams::get_input_attribute(const StringRef name, this->error_message_add(NodeWarningType::Error, TIP_("No attribute with name \"") + name + "\""); } - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } + const DataTypeConversions &conversions = get_implicit_type_conversions(); if (found_socket->type == SOCK_FLOAT) { const float value = this->get_input<float>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_FLOAT, type, &value); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized(CPPType::get<float>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } if (found_socket->type == SOCK_VECTOR) { const float3 value = this->get_input<float3>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_FLOAT3, type, &value); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized(CPPType::get<float3>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } if (found_socket->type == SOCK_RGBA) { - const Color4f value = this->get_input<Color4f>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_COLOR, type, &value); + const ColorGeometry4f value = this->get_input<ColorGeometry4f>(found_socket->identifier); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized( + CPPType::get<ColorGeometry4f>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } BLI_assert(false); - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } CustomDataType GeoNodeExecParams::get_input_attribute_data_type( @@ -114,11 +127,11 @@ CustomDataType GeoNodeExecParams::get_input_attribute_data_type( if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); - ReadAttributePtr attribute = component.attribute_try_get_for_read(name); - if (!attribute) { - return default_type; + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(name); + if (info) { + return info->data_type; } - return attribute->custom_data_type(); + return default_type; } if (found_socket->type == SOCK_FLOAT) { return CD_PROP_FLOAT; @@ -157,9 +170,9 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); - ReadAttributePtr attribute = component.attribute_try_get_for_read(name); - if (attribute) { - input_domains.append(attribute->domain()); + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(name); + if (info) { + input_domains.append(info->domain); } } } @@ -171,11 +184,11 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( return default_domain; } -void GeoNodeExecParams::check_extract_input(StringRef identifier, - const CPPType *requested_type) const +void GeoNodeExecParams::check_input_access(StringRef identifier, + const CPPType *requested_type) const { bNodeSocket *found_socket = nullptr; - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -185,39 +198,39 @@ void GeoNodeExecParams::check_extract_input(StringRef identifier, if (found_socket == nullptr) { std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (!input_values_.contains(identifier)) { + else if (!provider_->can_get_input(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); + BLI_assert_unreachable(); } 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); + BLI_assert_unreachable(); } } } -void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const +void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const { bNodeSocket *found_socket = nullptr; - for (const OutputSocketRef *socket : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -227,29 +240,29 @@ void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &va if (found_socket == nullptr) { std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const OutputSocketRef *socket : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (output_values_.contains(identifier)) { + else if (!provider_->can_set_output(identifier)) { std::cout << "The identifier '" << identifier << "' has been set already.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } 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); + BLI_assert_unreachable(); } } } diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 26df3f77738..d00bf636e15 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -35,9 +35,9 @@ #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_node.h" -#include "BKE_persistent_data_handle.hh" #include "DNA_collection_types.h" +#include "DNA_material_types.h" #include "RNA_access.h" #include "RNA_types.h" @@ -52,7 +52,7 @@ struct bNodeSocket *node_add_socket_from_template(struct bNodeTree *ntree, struct bNode *node, struct bNodeSocketTemplate *stemp, - int in_out) + eNodeSocketInOut in_out) { bNodeSocket *sock = nodeAddStaticSocket( ntree, node, in_out, stemp->type, stemp->subtype, stemp->identifier, stemp->name); @@ -102,8 +102,11 @@ struct bNodeSocket *node_add_socket_from_template(struct bNodeTree *ntree, return sock; } -static bNodeSocket *verify_socket_template( - bNodeTree *ntree, bNode *node, int in_out, ListBase *socklist, bNodeSocketTemplate *stemp) +static bNodeSocket *verify_socket_template(bNodeTree *ntree, + bNode *node, + eNodeSocketInOut in_out, + ListBase *socklist, + bNodeSocketTemplate *stemp) { bNodeSocket *sock; @@ -132,7 +135,7 @@ static bNodeSocket *verify_socket_template( static void verify_socket_template_list(bNodeTree *ntree, bNode *node, - int in_out, + eNodeSocketInOut in_out, ListBase *socklist, bNodeSocketTemplate *stemp_first) { @@ -291,6 +294,21 @@ void node_socket_init_default_value(bNodeSocket *sock) sock->default_value = dval; break; + } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *dval = (bNodeSocketValueTexture *)MEM_callocN( + sizeof(bNodeSocketValueTexture), "node socket value texture"); + dval->value = nullptr; + + sock->default_value = dval; + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *dval = (bNodeSocketValueMaterial *)MEM_callocN( + sizeof(bNodeSocketValueMaterial), "node socket value material"); + dval->value = nullptr; + + sock->default_value = dval; break; } } @@ -372,6 +390,20 @@ void node_socket_copy_default_value(bNodeSocket *to, const bNodeSocket *from) id_us_plus(&toval->value->id); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *toval = (bNodeSocketValueTexture *)to->default_value; + bNodeSocketValueTexture *fromval = (bNodeSocketValueTexture *)from->default_value; + *toval = *fromval; + id_us_plus(&toval->value->id); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *toval = (bNodeSocketValueMaterial *)to->default_value; + bNodeSocketValueMaterial *fromval = (bNodeSocketValueMaterial *)from->default_value; + *toval = *fromval; + id_us_plus(&toval->value->id); + break; + } } to->flag |= (from->flag & SOCK_HIDE_VALUE); @@ -613,9 +645,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_cpp_type = []() { return &blender::fn::CPPType::get<blender::Color4f>(); }; + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::ColorGeometry4f>(); }; socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { - *(blender::Color4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; + *(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; return socktype; } @@ -630,63 +662,17 @@ static bNodeSocketType *make_socket_type_string() return socktype; } -class ObjectSocketMultiFunction : public blender::fn::MultiFunction { - private: - Object *object_; - - public: - ObjectSocketMultiFunction(Object *object) : object_(object) - { - static blender::fn::MFSignature signature = create_signature(); - this->set_signature(&signature); - } - - static blender::fn::MFSignature create_signature() - { - blender::fn::MFSignatureBuilder signature{"Object Socket"}; - signature.depends_on_context(); - signature.single_output<blender::bke::PersistentObjectHandle>("Object"); - return signature.build(); - } - - void call(blender::IndexMask mask, - blender::fn::MFParams params, - blender::fn::MFContext context) const override - { - blender::MutableSpan output = - params.uninitialized_single_output<blender::bke::PersistentObjectHandle>(0, "Object"); - - /* Try to get a handle map, so that the object can be converted to a handle. */ - const blender::bke::PersistentDataHandleMap *handle_map = - context.get_global_context<blender::bke::PersistentDataHandleMap>( - "PersistentDataHandleMap"); - - if (handle_map == nullptr) { - /* Return empty handles when there is no handle map. */ - output.fill_indices(mask, blender::bke::PersistentObjectHandle()); - return; - } - - blender::bke::PersistentObjectHandle handle = handle_map->lookup(object_); - for (int64_t i : mask) { - output[i] = handle; - } - } -}; - -MAKE_CPP_TYPE(PersistentObjectHandle, blender::bke::PersistentObjectHandle); -MAKE_CPP_TYPE(PersistentCollectionHandle, blender::bke::PersistentCollectionHandle); +MAKE_CPP_TYPE(Object, Object *) +MAKE_CPP_TYPE(Collection, Collection *) +MAKE_CPP_TYPE(Texture, Tex *) +MAKE_CPP_TYPE(Material, Material *) static bNodeSocketType *make_socket_type_object() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - 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; - builder.construct_generator_fn<ObjectSocketMultiFunction>(object); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Object *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Object **)r_value = ((bNodeSocketValueObject *)socket.default_value)->value; }; return socktype; } @@ -704,9 +690,29 @@ static bNodeSocketType *make_socket_type_geometry() static bNodeSocketType *make_socket_type_collection() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_COLLECTION, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - return &blender::fn::CPPType::get<blender::bke::PersistentCollectionHandle>(); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Collection *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Collection **)r_value = ((bNodeSocketValueCollection *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_texture() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_TEXTURE, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Tex *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Tex **)r_value = ((bNodeSocketValueTexture *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_material() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_MATERIAL, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Material *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Material **)r_value = ((bNodeSocketValueMaterial *)socket.default_value)->value; }; return socktype; } @@ -721,6 +727,7 @@ void register_standard_node_socket_types(void) nodeRegisterSocketType(make_socket_type_float(PROP_FACTOR)); nodeRegisterSocketType(make_socket_type_float(PROP_ANGLE)); nodeRegisterSocketType(make_socket_type_float(PROP_TIME)); + nodeRegisterSocketType(make_socket_type_float(PROP_TIME_ABSOLUTE)); nodeRegisterSocketType(make_socket_type_float(PROP_DISTANCE)); nodeRegisterSocketType(make_socket_type_int(PROP_NONE)); @@ -752,5 +759,9 @@ void register_standard_node_socket_types(void) nodeRegisterSocketType(make_socket_type_collection()); + nodeRegisterSocketType(make_socket_type_texture()); + + nodeRegisterSocketType(make_socket_type_material()); + nodeRegisterSocketType(make_socket_type_virtual()); } diff --git a/source/blender/nodes/intern/node_tree_multi_function.cc b/source/blender/nodes/intern/node_tree_multi_function.cc index b973350becd..7ab6495f733 100644 --- a/source/blender/nodes/intern/node_tree_multi_function.cc +++ b/source/blender/nodes/intern/node_tree_multi_function.cc @@ -15,6 +15,7 @@ */ #include "NOD_node_tree_multi_function.hh" +#include "NOD_type_conversions.hh" #include "FN_multi_function_network_evaluation.hh" @@ -142,118 +143,16 @@ static void insert_nodes(CommonMFNetworkBuilderData &common) }); } -template<typename From, typename To> -static void add_implicit_conversion(DataTypeConversions &conversions) -{ - static fn::CustomMF_Convert<From, 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(DataTypeConversions &conversions, - StringRef name, - ConversionF conversion) -{ - static fn::CustomMF_SI_SO<From, To> function{name, conversion}; - conversions.add(fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>(), function); -} - -static DataTypeConversions create_implicit_conversions() -{ - DataTypeConversions conversions; - add_implicit_conversion<float, float2>(conversions); - add_implicit_conversion<float, float3>(conversions); - add_implicit_conversion<float, int32_t>(conversions); - add_implicit_conversion<float, bool>(conversions); - add_implicit_conversion<float, Color4f>( - conversions, "float to Color4f", [](float a) { return Color4f(a, a, a, 1.0f); }); - - add_implicit_conversion<float2, float3>( - conversions, "float2 to float3", [](float2 a) { return float3(a.x, a.y, 0.0f); }); - add_implicit_conversion<float2, float>( - conversions, "float2 to float", [](float2 a) { return (a.x + a.y) / 2.0f; }); - add_implicit_conversion<float2, int32_t>( - conversions, "float2 to int32_t", [](float2 a) { return (int32_t)((a.x + a.y) / 2.0f); }); - add_implicit_conversion<float2, bool>( - conversions, "float2 to bool", [](float2 a) { return !is_zero_v2(a); }); - add_implicit_conversion<float2, Color4f>( - conversions, "float2 to Color4f", [](float2 a) { return Color4f(a.x, a.y, 0.0f, 1.0f); }); - - add_implicit_conversion<float3, bool>( - conversions, "float3 to boolean", [](float3 a) { return !is_zero_v3(a); }); - add_implicit_conversion<float3, float>( - conversions, "float3 to float", [](float3 a) { return (a.x + a.y + a.z) / 3.0f; }); - add_implicit_conversion<float3, int32_t>( - conversions, "float3 to int32_t", [](float3 a) { return (int)((a.x + a.y + a.z) / 3.0f); }); - add_implicit_conversion<float3, float2>(conversions); - add_implicit_conversion<float3, Color4f>( - conversions, "float3 to Color4f", [](float3 a) { return Color4f(a.x, a.y, a.z, 1.0f); }); - - add_implicit_conversion<int32_t, bool>(conversions); - add_implicit_conversion<int32_t, float>(conversions); - add_implicit_conversion<int32_t, float2>( - conversions, "int32 to float2", [](int32_t a) { return float2((float)a); }); - add_implicit_conversion<int32_t, float3>( - conversions, "int32 to float3", [](int32_t a) { return float3((float)a); }); - add_implicit_conversion<int32_t, Color4f>(conversions, "int32 to Color4f", [](int32_t a) { - return Color4f((float)a, (float)a, (float)a, 1.0f); - }); - - add_implicit_conversion<bool, float>(conversions); - add_implicit_conversion<bool, int32_t>(conversions); - add_implicit_conversion<bool, float2>( - conversions, "boolean to float2", [](bool a) { return (a) ? float2(1.0f) : float2(0.0f); }); - add_implicit_conversion<bool, float3>( - conversions, "boolean to float3", [](bool a) { return (a) ? float3(1.0f) : float3(0.0f); }); - add_implicit_conversion<bool, Color4f>(conversions, "boolean to Color4f", [](bool a) { - return (a) ? Color4f(1.0f, 1.0f, 1.0f, 1.0f) : Color4f(0.0f, 0.0f, 0.0f, 1.0f); - }); - - add_implicit_conversion<Color4f, bool>(conversions, "Color4f to boolean", [](Color4f a) { - return a.r != 0.0f && a.g != 0.0f && a.b != 0.0f; - }); - add_implicit_conversion<Color4f, float>( - conversions, "Color4f to float", [](Color4f a) { return rgb_to_grayscale(a); }); - add_implicit_conversion<Color4f, float2>( - conversions, "Color4f to float2", [](Color4f a) { return float2(a.r, a.g); }); - add_implicit_conversion<Color4f, float3>( - conversions, "Color4f to float3", [](Color4f a) { return float3(a.r, a.g, a.b); }); - - return conversions; -} - -const DataTypeConversions &get_implicit_type_conversions() -{ - 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, fn::MFDataType type) { const fn::MultiFunction *default_fn; if (type.is_single()) { - default_fn = &common.resources.construct<fn::CustomMF_GenericConstant>( + default_fn = &common.scope.construct<fn::CustomMF_GenericConstant>( AT, type.single_type(), type.single_type().default_value()); } else { - default_fn = &common.resources.construct<fn::CustomMF_GenericConstantArray>( + default_fn = &common.scope.construct<fn::CustomMF_GenericConstantArray>( AT, fn::GSpan(type.vector_base_type())); } @@ -320,8 +219,8 @@ static void insert_links_and_unlinked_inputs(CommonMFNetworkBuilderData &common) const fn::MFDataType from_type = from_socket->data_type(); if (from_type != to_type) { - const fn::MultiFunction *conversion_fn = get_implicit_type_conversions().get_conversion( - from_type, to_type); + const fn::MultiFunction *conversion_fn = + get_implicit_type_conversions().get_conversion_multi_function(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)); @@ -348,11 +247,11 @@ static void insert_links_and_unlinked_inputs(CommonMFNetworkBuilderData &common) */ MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, const DerivedNodeTree &tree, - ResourceCollector &resources) + ResourceScope &scope) { MFNetworkTreeMap network_map{tree, network}; - CommonMFNetworkBuilderData common{resources, network, network_map, tree}; + CommonMFNetworkBuilderData common{scope, network, network_map, tree}; insert_nodes(common); insert_links_and_unlinked_inputs(common); @@ -426,7 +325,7 @@ static const fn::MultiFunction &create_function_for_node_that_expands_into_multi const DNode &dnode, fn::MFNetwork &network, MFNetworkTreeMap &network_map, - ResourceCollector &resources) + ResourceScope &scope) { Vector<const fn::MFOutputSocket *> dummy_fn_inputs; for (const InputSocketRef *dsocket : dnode->inputs()) { @@ -451,7 +350,7 @@ static const fn::MultiFunction &create_function_for_node_that_expands_into_multi } } - fn::MFNetworkEvaluator &fn_evaluator = resources.construct<fn::MFNetworkEvaluator>( + fn::MFNetworkEvaluator &fn_evaluator = scope.construct<fn::MFNetworkEvaluator>( __func__, std::move(dummy_fn_inputs), std::move(dummy_fn_outputs)); return fn_evaluator; } @@ -460,16 +359,15 @@ static const fn::MultiFunction &create_function_for_node_that_expands_into_multi * 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) +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, ResourceScope &scope) { /* Build a network that nodes can insert themselves into. However, the individual nodes are not * connected. */ - fn::MFNetwork &network = resources.construct<fn::MFNetwork>(__func__); + fn::MFNetwork &network = scope.construct<fn::MFNetwork>(__func__); MFNetworkTreeMap network_map{tree, network}; MultiFunctionByNode functions_by_node; - CommonMFNetworkBuilderData common{resources, network, network_map, tree}; + CommonMFNetworkBuilderData common{scope, network, network_map, tree}; tree.foreach_node([&](DNode dnode) { const bNodeType *node_type = dnode->typeinfo(); @@ -498,7 +396,7 @@ MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, /* 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); + dnode, network, network_map, scope); functions_by_node.add_new(dnode, &fn); break; } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index e627a7f4497..154ee716153 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include <mutex> + #include "NOD_node_tree_ref.hh" #include "BLI_dot_export.hh" @@ -105,6 +107,7 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) } } + this->create_socket_identifier_maps(); this->create_linked_socket_caches(); for (NodeRef *node : nodes_by_id_) { @@ -163,7 +166,7 @@ void NodeTreeRef::create_linked_socket_caches() { for (InputSocketRef *socket : input_sockets_) { /* Find directly linked socket based on incident links. */ - Vector<SocketRef *> directly_linked_sockets; + Vector<const SocketRef *> directly_linked_sockets; for (LinkRef *link : socket->directly_linked_links_) { directly_linked_sockets.append(link->from_); } @@ -171,9 +174,14 @@ void NodeTreeRef::create_linked_socket_caches() directly_linked_sockets.as_span()); /* Find logically linked sockets. */ - Vector<SocketRef *> logically_linked_sockets; - this->foreach_logical_origin( - *socket, [&](OutputSocketRef &origin) { logically_linked_sockets.append(&origin); }); + Vector<const SocketRef *> logically_linked_sockets; + Vector<const SocketRef *> logically_linked_skipped_sockets; + Vector<const InputSocketRef *> handled_sockets; + socket->foreach_logical_origin( + [&](const OutputSocketRef &origin) { logically_linked_sockets.append(&origin); }, + [&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); }, + false, + handled_sockets); if (logically_linked_sockets == directly_linked_sockets) { socket->logically_linked_sockets_ = socket->directly_linked_sockets_; } @@ -181,11 +189,13 @@ void NodeTreeRef::create_linked_socket_caches() socket->logically_linked_sockets_ = allocator_.construct_array_copy( logically_linked_sockets.as_span()); } + socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy( + logically_linked_skipped_sockets.as_span()); } for (OutputSocketRef *socket : output_sockets_) { /* Find directly linked socket based on incident links. */ - Vector<SocketRef *> directly_linked_sockets; + Vector<const SocketRef *> directly_linked_sockets; for (LinkRef *link : socket->directly_linked_links_) { directly_linked_sockets.append(link->to_); } @@ -193,9 +203,13 @@ void NodeTreeRef::create_linked_socket_caches() directly_linked_sockets.as_span()); /* Find logically linked sockets. */ - Vector<SocketRef *> logically_linked_sockets; - this->foreach_logical_target( - *socket, [&](InputSocketRef &target) { logically_linked_sockets.append(&target); }); + Vector<const SocketRef *> logically_linked_sockets; + Vector<const SocketRef *> logically_linked_skipped_sockets; + Vector<const OutputSocketRef *> handled_sockets; + socket->foreach_logical_target( + [&](const InputSocketRef &target) { logically_linked_sockets.append(&target); }, + [&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); }, + handled_sockets); if (logically_linked_sockets == directly_linked_sockets) { socket->logically_linked_sockets_ = socket->directly_linked_sockets_; } @@ -203,61 +217,187 @@ void NodeTreeRef::create_linked_socket_caches() socket->logically_linked_sockets_ = allocator_.construct_array_copy( logically_linked_sockets.as_span()); } + socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy( + logically_linked_skipped_sockets.as_span()); } } -void NodeTreeRef::foreach_logical_origin(InputSocketRef &socket, - FunctionRef<void(OutputSocketRef &)> callback, - bool only_follow_first_input_link) +void InputSocketRef::foreach_logical_origin(FunctionRef<void(const OutputSocketRef &)> origin_fn, + FunctionRef<void(const SocketRef &)> skipped_fn, + bool only_follow_first_input_link, + Vector<const InputSocketRef *> &handled_sockets) const { - Span<LinkRef *> links_to_check = socket.directly_linked_links_; + /* Protect against loops. */ + if (handled_sockets.contains(this)) { + return; + } + handled_sockets.append(this); + + Span<const LinkRef *> links_to_check = this->directly_linked_links(); if (only_follow_first_input_link) { links_to_check = links_to_check.take_front(1); } - for (LinkRef *link : links_to_check) { + for (const LinkRef *link : links_to_check) { if (link->is_muted()) { continue; } - OutputSocketRef *origin = link->from_; - NodeRef *origin_node = origin->node_; - if (origin_node->is_reroute_node()) { - this->foreach_logical_origin(*origin_node->inputs_[0], callback, false); + const OutputSocketRef &origin = link->from(); + const NodeRef &origin_node = origin.node(); + if (!origin.is_available()) { + /* Non available sockets are ignored. */ } - else if (origin_node->is_muted()) { - for (InternalLinkRef *internal_link : origin_node->internal_links_) { - if (internal_link->to_ == origin) { - this->foreach_logical_origin(*internal_link->from_, callback, true); + else if (origin_node.is_reroute_node()) { + const InputSocketRef &reroute_input = origin_node.input(0); + const OutputSocketRef &reroute_output = origin_node.output(0); + skipped_fn.call_safe(reroute_input); + skipped_fn.call_safe(reroute_output); + reroute_input.foreach_logical_origin(origin_fn, skipped_fn, false, handled_sockets); + } + else if (origin_node.is_muted()) { + for (const InternalLinkRef *internal_link : origin_node.internal_links()) { + if (&internal_link->to() == &origin) { + const InputSocketRef &mute_input = internal_link->from(); + skipped_fn.call_safe(origin); + skipped_fn.call_safe(mute_input); + mute_input.foreach_logical_origin(origin_fn, skipped_fn, true, handled_sockets); break; } } } else { - callback(*origin); + origin_fn(origin); } } } -void NodeTreeRef::foreach_logical_target(OutputSocketRef &socket, - FunctionRef<void(InputSocketRef &)> callback) +void OutputSocketRef::foreach_logical_target( + FunctionRef<void(const InputSocketRef &)> target_fn, + FunctionRef<void(const SocketRef &)> skipped_fn, + Vector<const OutputSocketRef *> &handled_sockets) const { - for (LinkRef *link : socket.directly_linked_links_) { + /* Protect against loops. */ + if (handled_sockets.contains(this)) { + return; + } + handled_sockets.append(this); + + for (const LinkRef *link : this->directly_linked_links()) { if (link->is_muted()) { continue; } - InputSocketRef *target = link->to_; - NodeRef *target_node = target->node_; - if (target_node->is_reroute_node()) { - this->foreach_logical_target(*target_node->outputs_[0], callback); + const InputSocketRef &target = link->to(); + const NodeRef &target_node = target.node(); + if (!target.is_available()) { + /* Non available sockets are ignored. */ } - else if (target_node->is_muted()) { - for (InternalLinkRef *internal_link : target_node->internal_links_) { - if (internal_link->from_ == target) { - this->foreach_logical_target(*internal_link->to_, callback); + else if (target_node.is_reroute_node()) { + const OutputSocketRef &reroute_output = target_node.output(0); + skipped_fn.call_safe(target); + skipped_fn.call_safe(reroute_output); + reroute_output.foreach_logical_target(target_fn, skipped_fn, handled_sockets); + } + else if (target_node.is_muted()) { + skipped_fn.call_safe(target); + for (const InternalLinkRef *internal_link : target_node.internal_links()) { + if (&internal_link->from() == &target) { + /* The internal link only forwards the first incoming link. */ + if (target.is_multi_input_socket()) { + if (target.directly_linked_links()[0] != link) { + continue; + } + } + const OutputSocketRef &mute_output = internal_link->to(); + skipped_fn.call_safe(target); + skipped_fn.call_safe(mute_output); + mute_output.foreach_logical_target(target_fn, skipped_fn, handled_sockets); } } } else { - callback(*target); + target_fn(target); + } + } +} + +namespace { +struct SocketByIdentifierMap { + SocketIndexByIdentifierMap *map = nullptr; + std::unique_ptr<SocketIndexByIdentifierMap> owned_map; +}; +} // namespace + +static std::unique_ptr<SocketIndexByIdentifierMap> create_identifier_map(const ListBase &sockets) +{ + std::unique_ptr<SocketIndexByIdentifierMap> map = std::make_unique<SocketIndexByIdentifierMap>(); + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &sockets, index) { + map->add_new(socket->identifier, index); + } + return map; +} + +/* This function is not threadsafe. */ +static SocketByIdentifierMap get_or_create_identifier_map( + const bNode &node, const ListBase &sockets, const bNodeSocketTemplate *sockets_template) +{ + SocketByIdentifierMap map; + if (sockets_template == nullptr) { + if (BLI_listbase_is_empty(&sockets)) { + static SocketIndexByIdentifierMap empty_map; + map.map = &empty_map; + } + else if (node.type == NODE_REROUTE) { + if (&node.inputs == &sockets) { + static SocketIndexByIdentifierMap reroute_input_map = [] { + SocketIndexByIdentifierMap map; + map.add_new("Input", 0); + return map; + }(); + map.map = &reroute_input_map; + } + else { + static SocketIndexByIdentifierMap reroute_output_map = [] { + SocketIndexByIdentifierMap map; + map.add_new("Output", 0); + return map; + }(); + map.map = &reroute_output_map; + } + } + else { + /* The node has a dynamic amount of sockets. Therefore we need to create a new map. */ + map.owned_map = create_identifier_map(sockets); + map.map = &*map.owned_map; + } + } + else { + /* Cache only one map for nodes that have the same sockets. */ + static Map<const bNodeSocketTemplate *, std::unique_ptr<SocketIndexByIdentifierMap>> maps; + map.map = &*maps.lookup_or_add_cb(sockets_template, + [&]() { return create_identifier_map(sockets); }); + } + return map; +} + +void NodeTreeRef::create_socket_identifier_maps() +{ + /* `get_or_create_identifier_map` is not threadsafe, therefore we have to hold a lock here. */ + static std::mutex mutex; + std::lock_guard lock{mutex}; + + for (NodeRef *node : nodes_by_id_) { + bNode &bnode = *node->bnode_; + SocketByIdentifierMap inputs_map = get_or_create_identifier_map( + bnode, bnode.inputs, bnode.typeinfo->inputs); + SocketByIdentifierMap outputs_map = get_or_create_identifier_map( + bnode, bnode.outputs, bnode.typeinfo->outputs); + node->input_index_by_identifier_ = inputs_map.map; + node->output_index_by_identifier_ = outputs_map.map; + if (inputs_map.owned_map) { + owned_identifier_maps_.append(std::move(inputs_map.owned_map)); + } + if (outputs_map.owned_map) { + owned_identifier_maps_.append(std::move(outputs_map.owned_map)); } } } @@ -304,6 +444,21 @@ bool NodeTreeRef::has_link_cycles() const return false; } +bool NodeTreeRef::has_undefined_nodes_or_sockets() const +{ + for (const NodeRef *node : nodes_by_id_) { + if (node->is_undefined()) { + return true; + } + } + for (const SocketRef *socket : sockets_by_id_) { + if (socket->is_undefined()) { + return true; + } + } + return true; +} + std::string NodeTreeRef::to_dot() const { dot::DirectedGraph digraph; diff --git a/source/blender/nodes/intern/node_util.c b/source/blender/nodes/intern/node_util.c index 00db819721c..1aec280fd2b 100644 --- a/source/blender/nodes/intern/node_util.c +++ b/source/blender/nodes/intern/node_util.c @@ -140,8 +140,8 @@ void node_math_update(bNodeTree *UNUSED(ntree), bNode *node) switch (node->custom1) { case NODE_MATH_WRAP: - node_sock_label(sock2, "Min"); - node_sock_label(sock3, "Max"); + node_sock_label(sock2, "Max"); + node_sock_label(sock3, "Min"); break; case NODE_MATH_MULTIPLY_ADD: node_sock_label(sock2, "Multiplier"); @@ -239,24 +239,21 @@ void node_filter_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int m /** \name Link Insertion * \{ */ -/* test if two sockets are interchangeable */ -static bool node_link_socket_match(bNodeSocket *a, bNodeSocket *b) +static bool node_link_socket_match(const bNodeSocket *a, const bNodeSocket *b) { - /* check if sockets are of the same type */ + /* Check if sockets are of the same type. */ if (a->typeinfo != b->typeinfo) { return false; } - /* tests if alphabetic prefix matches - * this allows for imperfect matches, such as numeric suffixes, - * like Color1/Color2 - */ + /* Test if alphabetic prefix matches, allowing for imperfect matches, such as numeric suffixes + * like Color1/Color2. */ int prefix_len = 0; - char *ca = a->name, *cb = b->name; + const char *ca = a->name, *cb = b->name; for (; *ca != '\0' && *cb != '\0'; ca++, cb++) { - /* end of common prefix? */ + /* End of common prefix? */ if (*ca != *cb) { - /* prefix delimited by non-alphabetic char */ + /* Prefix delimited by non-alphabetic char. */ if (isalpha(*ca) || isalpha(*cb)) { return false; } @@ -267,75 +264,73 @@ static bool node_link_socket_match(bNodeSocket *a, bNodeSocket *b) return prefix_len > 0; } -static int node_count_links(bNodeTree *ntree, bNodeSocket *sock) +static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket) { - bNodeLink *link; int count = 0; - for (link = ntree->links.first; link; link = link->next) { - if (link->fromsock == sock) { - count++; - } - if (link->tosock == sock) { + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (ELEM(socket, link->fromsock, link->tosock)) { count++; } } return count; } -/* Find an eligible socket for linking. */ -static bNodeSocket *node_find_linkable_socket(bNodeTree *ntree, bNode *node, bNodeSocket *cur) +static bNodeSocket *node_find_linkable_socket(bNodeTree *ntree, + bNode *node, + bNodeSocket *to_socket) { - bNodeSocket *first = cur->in_out == SOCK_IN ? node->inputs.first : node->outputs.first; - bNodeSocket *sock; - - /* Iterate over all sockets of the target node, to find one that matches the same socket type. - * The idea behind this is: When a user connects an input to a socket that is - * already linked (and if its not an Multi Input Socket), we try to find a replacement socket for - * the link that we try to overwrite and connect that previous link to the new socket. */ - sock = cur->next ? cur->next : first; /* Wrap around the list end. */ - while (sock != cur) { - if (!nodeSocketIsHidden(sock) && node_link_socket_match(sock, cur)) { - break; + bNodeSocket *first = to_socket->in_out == SOCK_IN ? node->inputs.first : node->outputs.first; + + /* Wrap around the list end. */ + bNodeSocket *socket_iter = to_socket->next ? to_socket->next : first; + while (socket_iter != to_socket) { + if (!nodeSocketIsHidden(socket_iter) && node_link_socket_match(socket_iter, to_socket)) { + const int link_count = node_count_links(ntree, socket_iter); + /* Add one to account for the new link being added. */ + if (link_count + 1 <= nodeSocketLinkLimit(socket_iter)) { + return socket_iter; /* Found a valid free socket we can swap to. */ + } } - sock = sock->next ? sock->next : first; /* Wrap around the list end. */ + socket_iter = socket_iter->next ? socket_iter->next : first; /* Wrap around the list end. */ } - if (!nodeSocketIsHidden(sock) && node_link_socket_match(sock, cur)) { - int link_count = node_count_links(ntree, sock); - /* Take +1 into account since we would add a new link. */ - if (link_count + 1 <= nodeSocketLinkLimit(sock)) { - return sock; /* Found a valid free socket we can swap to. */ - } - } return NULL; } +/** + * The idea behind this is: When a user connects an input to a socket that is + * already linked (and if its not an Multi Input Socket), we try to find a replacement socket for + * the link that we try to overwrite and connect that previous link to the new socket. + */ void node_insert_link_default(bNodeTree *ntree, bNode *node, bNodeLink *link) { - bNodeSocket *sock = link->tosock; - bNodeLink *tlink, *tlink_next; + bNodeSocket *socket = link->tosock; if (node != link->tonode) { return; } - for (tlink = ntree->links.first; tlink; tlink = tlink_next) { - bNodeSocket *new_sock; - tlink_next = tlink->next; + /* If we're not at the link limit of the target socket, we can skip + * trying to move existing links to another socket. */ + const int to_link_limit = nodeSocketLinkLimit(socket); + if (socket->total_inputs + 1 < to_link_limit) { + return; + } - if (sock != tlink->tosock) { - continue; - } + LISTBASE_FOREACH_MUTABLE (bNodeLink *, to_link, &ntree->links) { + if (socket == to_link->tosock) { + bNodeSocket *new_socket = node_find_linkable_socket(ntree, node, socket); + if (new_socket && new_socket != socket) { + /* Attempt to redirect the existing link to the new socket. */ + to_link->tosock = new_socket; + return; + } - new_sock = node_find_linkable_socket(ntree, node, sock); - if (new_sock && new_sock != sock) { - /* redirect existing link */ - tlink->tosock = new_sock; - } - else if (!new_sock) { - /* no possible replacement, remove tlink */ - nodeRemLink(ntree, tlink); - tlink = NULL; + if (new_socket == NULL) { + /* No possible replacement, remove the existing link. */ + nodeRemLink(ntree, to_link); + return; + } } } } @@ -464,6 +459,22 @@ static int node_datatype_priority(eNodeSocketDatatype from, eNodeSocketDatatype return -1; } } + case SOCK_TEXTURE: { + switch (from) { + case SOCK_TEXTURE: + return 1; + default: + return -1; + } + } + case SOCK_MATERIAL: { + switch (from) { + case SOCK_MATERIAL: + return 1; + default: + return -1; + } + } default: return -1; } diff --git a/source/blender/nodes/intern/type_callbacks.cc b/source/blender/nodes/intern/type_callbacks.cc index 26eeaebc6d4..5160432aea5 100644 --- a/source/blender/nodes/intern/type_callbacks.cc +++ b/source/blender/nodes/intern/type_callbacks.cc @@ -64,7 +64,8 @@ void socket_expand_in_mf_network(SocketMFNetworkBuilder &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()); + void *buffer = builder.resource_scope().linear_allocator().allocate(type.size(), + type.alignment()); socket.typeinfo->get_cpp_value(socket, buffer); builder.set_constant_value(type, buffer); } diff --git a/source/blender/nodes/intern/type_conversions.cc b/source/blender/nodes/intern/type_conversions.cc new file mode 100644 index 00000000000..220e5ea9046 --- /dev/null +++ b/source/blender/nodes/intern/type_conversions.cc @@ -0,0 +1,349 @@ +/* + * 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_type_conversions.hh" + +#include "FN_multi_function_builder.hh" + +#include "BLI_color.hh" +#include "BLI_float2.hh" +#include "BLI_float3.hh" + +namespace blender::nodes { + +using fn::GVArrayPtr; +using fn::GVMutableArray; +using fn::GVMutableArrayPtr; +using fn::MFDataType; + +template<typename From, typename To, To (*ConversionF)(const From &)> +static void add_implicit_conversion(DataTypeConversions &conversions) +{ + const CPPType &from_type = CPPType::get<From>(); + const CPPType &to_type = CPPType::get<To>(); + const std::string conversion_name = from_type.name() + " to " + to_type.name(); + + static fn::CustomMF_SI_SO<From, To> multi_function{conversion_name, ConversionF}; + static auto convert_single_to_initialized = [](const void *src, void *dst) { + *(To *)dst = ConversionF(*(const From *)src); + }; + static auto convert_single_to_uninitialized = [](const void *src, void *dst) { + new (dst) To(ConversionF(*(const From *)src)); + }; + conversions.add(fn::MFDataType::ForSingle<From>(), + fn::MFDataType::ForSingle<To>(), + multi_function, + convert_single_to_initialized, + convert_single_to_uninitialized); +} + +static float2 float_to_float2(const float &a) +{ + return float2(a); +} +static float3 float_to_float3(const float &a) +{ + return float3(a); +} +static int32_t float_to_int(const float &a) +{ + return (int32_t)a; +} +static bool float_to_bool(const float &a) +{ + return a > 0.0f; +} +static ColorGeometry4f float_to_color(const float &a) +{ + return ColorGeometry4f(a, a, a, 1.0f); +} + +static float3 float2_to_float3(const float2 &a) +{ + return float3(a.x, a.y, 0.0f); +} +static float float2_to_float(const float2 &a) +{ + return (a.x + a.y) / 2.0f; +} +static int float2_to_int(const float2 &a) +{ + return (int32_t)((a.x + a.y) / 2.0f); +} +static bool float2_to_bool(const float2 &a) +{ + return !is_zero_v2(a); +} +static ColorGeometry4f float2_to_color(const float2 &a) +{ + return ColorGeometry4f(a.x, a.y, 0.0f, 1.0f); +} + +static bool float3_to_bool(const float3 &a) +{ + return !is_zero_v3(a); +} +static float float3_to_float(const float3 &a) +{ + return (a.x + a.y + a.z) / 3.0f; +} +static int float3_to_int(const float3 &a) +{ + return (int)((a.x + a.y + a.z) / 3.0f); +} +static float2 float3_to_float2(const float3 &a) +{ + return float2(a); +} +static ColorGeometry4f float3_to_color(const float3 &a) +{ + return ColorGeometry4f(a.x, a.y, a.z, 1.0f); +} + +static bool int_to_bool(const int32_t &a) +{ + return a > 0; +} +static float int_to_float(const int32_t &a) +{ + return (float)a; +} +static float2 int_to_float2(const int32_t &a) +{ + return float2((float)a); +} +static float3 int_to_float3(const int32_t &a) +{ + return float3((float)a); +} +static ColorGeometry4f int_to_color(const int32_t &a) +{ + return ColorGeometry4f((float)a, (float)a, (float)a, 1.0f); +} + +static float bool_to_float(const bool &a) +{ + return (bool)a; +} +static int32_t bool_to_int(const bool &a) +{ + return (int32_t)a; +} +static float2 bool_to_float2(const bool &a) +{ + return (a) ? float2(1.0f) : float2(0.0f); +} +static float3 bool_to_float3(const bool &a) +{ + return (a) ? float3(1.0f) : float3(0.0f); +} +static ColorGeometry4f bool_to_color(const bool &a) +{ + return (a) ? ColorGeometry4f(1.0f, 1.0f, 1.0f, 1.0f) : ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f); +} + +static bool color_to_bool(const ColorGeometry4f &a) +{ + return rgb_to_grayscale(a) > 0.0f; +} +static float color_to_float(const ColorGeometry4f &a) +{ + return rgb_to_grayscale(a); +} +static int32_t color_to_int(const ColorGeometry4f &a) +{ + return (int)rgb_to_grayscale(a); +} +static float2 color_to_float2(const ColorGeometry4f &a) +{ + return float2(a.r, a.g); +} +static float3 color_to_float3(const ColorGeometry4f &a) +{ + return float3(a.r, a.g, a.b); +} + +static DataTypeConversions create_implicit_conversions() +{ + DataTypeConversions conversions; + + add_implicit_conversion<float, float2, float_to_float2>(conversions); + add_implicit_conversion<float, float3, float_to_float3>(conversions); + add_implicit_conversion<float, int32_t, float_to_int>(conversions); + add_implicit_conversion<float, bool, float_to_bool>(conversions); + add_implicit_conversion<float, ColorGeometry4f, float_to_color>(conversions); + + add_implicit_conversion<float2, float3, float2_to_float3>(conversions); + add_implicit_conversion<float2, float, float2_to_float>(conversions); + add_implicit_conversion<float2, int32_t, float2_to_int>(conversions); + add_implicit_conversion<float2, bool, float2_to_bool>(conversions); + add_implicit_conversion<float2, ColorGeometry4f, float2_to_color>(conversions); + + add_implicit_conversion<float3, bool, float3_to_bool>(conversions); + add_implicit_conversion<float3, float, float3_to_float>(conversions); + add_implicit_conversion<float3, int32_t, float3_to_int>(conversions); + add_implicit_conversion<float3, float2, float3_to_float2>(conversions); + add_implicit_conversion<float3, ColorGeometry4f, float3_to_color>(conversions); + + add_implicit_conversion<int32_t, bool, int_to_bool>(conversions); + add_implicit_conversion<int32_t, float, int_to_float>(conversions); + add_implicit_conversion<int32_t, float2, int_to_float2>(conversions); + add_implicit_conversion<int32_t, float3, int_to_float3>(conversions); + add_implicit_conversion<int32_t, ColorGeometry4f, int_to_color>(conversions); + + add_implicit_conversion<bool, float, bool_to_float>(conversions); + add_implicit_conversion<bool, int32_t, bool_to_int>(conversions); + add_implicit_conversion<bool, float2, bool_to_float2>(conversions); + add_implicit_conversion<bool, float3, bool_to_float3>(conversions); + add_implicit_conversion<bool, ColorGeometry4f, bool_to_color>(conversions); + + add_implicit_conversion<ColorGeometry4f, bool, color_to_bool>(conversions); + add_implicit_conversion<ColorGeometry4f, float, color_to_float>(conversions); + add_implicit_conversion<ColorGeometry4f, int32_t, color_to_int>(conversions); + add_implicit_conversion<ColorGeometry4f, float2, color_to_float2>(conversions); + add_implicit_conversion<ColorGeometry4f, float3, color_to_float3>(conversions); + + return conversions; +} + +const DataTypeConversions &get_implicit_type_conversions() +{ + static const DataTypeConversions conversions = create_implicit_conversions(); + return conversions; +} + +void DataTypeConversions::convert_to_uninitialized(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const +{ + if (from_type == to_type) { + from_type.copy_to_uninitialized(from_value, to_value); + return; + } + + const ConversionFunctions *functions = this->get_conversion_functions( + MFDataType::ForSingle(from_type), MFDataType::ForSingle(to_type)); + BLI_assert(functions != nullptr); + + functions->convert_single_to_uninitialized(from_value, to_value); +} + +class GVArray_For_ConvertedGVArray : public GVArray { + private: + GVArrayPtr varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + + public: + GVArray_For_ConvertedGVArray(GVArrayPtr varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : GVArray(to_type, varray->size()), varray_(std::move(varray)), from_type_(varray_->type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + } + + private: + void get_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } +}; + +class GVMutableArray_For_ConvertedGVMutableArray : public GVMutableArray { + private: + GVMutableArrayPtr varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + ConversionFunctions new_to_old_conversions_; + + public: + GVMutableArray_For_ConvertedGVMutableArray(GVMutableArrayPtr varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : GVMutableArray(to_type, varray->size()), + varray_(std::move(varray)), + from_type_(varray_->type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + new_to_old_conversions_ = *conversions.get_conversion_functions(to_type, from_type_); + } + + private: + void get_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void set_by_move_impl(const int64_t index, void *value) override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + new_to_old_conversions_.convert_single_to_uninitialized(value, buffer); + varray_->set_by_relocate(index, buffer); + } +}; + +fn::GVArrayPtr DataTypeConversions::try_convert(fn::GVArrayPtr varray, + const CPPType &to_type) const +{ + const CPPType &from_type = varray->type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return std::make_unique<GVArray_For_ConvertedGVArray>(std::move(varray), to_type, *this); +} + +fn::GVMutableArrayPtr DataTypeConversions::try_convert(fn::GVMutableArrayPtr varray, + const CPPType &to_type) const +{ + const CPPType &from_type = varray->type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return std::make_unique<GVMutableArray_For_ConvertedGVMutableArray>( + std::move(varray), to_type, *this); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index 3fb4d10979d..5ec982c4e7f 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -184,6 +184,12 @@ static bool shader_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link) return true; } +static bool shader_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER); +} + bNodeTreeType *ntreeType_Shader; void register_node_tree_type_sh(void) @@ -205,6 +211,7 @@ void register_node_tree_type_sh(void) tt->poll = shader_tree_poll; tt->get_from_context = shader_get_from_context; tt->validate_link = shader_validate_link; + tt->valid_socket_type = shader_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_ShaderNodeTree; @@ -557,12 +564,14 @@ static bool ntree_shader_has_displacement(bNodeTree *ntree, /* Non-cycles node is used as an output. */ return false; } + if ((displacement->link != NULL) && !(displacement->link->flag & NODE_LINK_MUTED)) { *r_node = displacement->link->fromnode; *r_socket = displacement->link->fromsock; *r_link = displacement->link; + return true; } - return displacement->link != NULL; + return false; } static void ntree_shader_relink_node_normal(bNodeTree *ntree, @@ -1028,30 +1037,3 @@ void ntreeShaderEndExecTree(bNodeTreeExec *exec) ntree->execdata = NULL; } } - -/* TODO: left over from Blender Internal, could reuse for new texture nodes. */ -bool ntreeShaderExecTree(bNodeTree *ntree, int thread) -{ - ShaderCallData scd; - bNodeThreadStack *nts = NULL; - bNodeTreeExec *exec = ntree->execdata; - int compat; - - /* ensure execdata is only initialized once */ - if (!exec) { - BLI_thread_lock(LOCK_NODES); - if (!ntree->execdata) { - ntree->execdata = ntreeShaderBeginExecTree(ntree); - } - BLI_thread_unlock(LOCK_NODES); - - exec = ntree->execdata; - } - - nts = ntreeGetThreadStack(exec, thread); - compat = ntreeExecThreadNodes(exec, nts, &scd, thread); - ntreeReleaseThreadStack(nts); - - /* if compat is zero, it has been using non-compatible nodes */ - return compat; -} diff --git a/source/blender/nodes/shader/node_shader_util.c b/source/blender/nodes/shader/node_shader_util.c index 1a2405e021f..abc2c7008c7 100644 --- a/source/blender/nodes/shader/node_shader_util.c +++ b/source/blender/nodes/shader/node_shader_util.c @@ -27,14 +27,24 @@ #include "node_exec.h" -bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree, const char **r_disabled_hint) { - return STREQ(ntree->idname, "ShaderNodeTree"); + if (!STREQ(ntree->idname, "ShaderNodeTree")) { + *r_disabled_hint = "Not a shader node tree"; + return false; + } + return true; } -static bool sh_fn_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +static bool sh_fn_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { - return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "GeometryNodeTree"); + if (!STREQ(ntree->idname, "ShaderNodeTree") && !STREQ(ntree->idname, "GeometryNodeTree")) { + *r_disabled_hint = "Not a shader or geometry node tree"; + return false; + } + return true; } void sh_node_type_base( @@ -322,3 +332,17 @@ void node_shader_gpu_tex_mapping(GPUMaterial *mat, } } } + +void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data) +{ + const float *xyz_to_rgb = IMB_colormanagement_get_xyz_to_rgb(); + data->r[0] = xyz_to_rgb[0]; + data->r[1] = xyz_to_rgb[3]; + data->r[2] = xyz_to_rgb[6]; + data->g[0] = xyz_to_rgb[1]; + data->g[1] = xyz_to_rgb[4]; + data->g[2] = xyz_to_rgb[7]; + data->b[0] = xyz_to_rgb[2]; + data->b[1] = xyz_to_rgb[5]; + data->b[2] = xyz_to_rgb[8]; +} diff --git a/source/blender/nodes/shader/node_shader_util.h b/source/blender/nodes/shader/node_shader_util.h index 91454c3c982..dc44f0fa98f 100644 --- a/source/blender/nodes/shader/node_shader_util.h +++ b/source/blender/nodes/shader/node_shader_util.h @@ -80,7 +80,9 @@ extern "C" { #endif -bool sh_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool sh_node_poll_default(struct bNodeType *ntype, + struct bNodeTree *ntree, + const char **r_disabled_hint); void sh_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); void sh_fn_node_type_base( @@ -93,6 +95,11 @@ typedef struct ShaderCallData { int dummy; } ShaderCallData; +typedef struct XYZ_to_RGB /* Transposed #imbuf_xyz_to_rgb, passed as 3x vec3. */ +{ + float r[3], g[3], b[3]; +} XYZ_to_RGB; + void nodestack_get_vec(float *in, short type_in, bNodeStack *ns); void node_gpu_stack_from_data(struct GPUNodeStack *gs, int type, struct bNodeStack *ns); @@ -111,6 +118,7 @@ void node_shader_gpu_tex_mapping(struct GPUMaterial *mat, void ntreeExecGPUNodes(struct bNodeTreeExec *exec, struct GPUMaterial *mat, struct bNode *output_node); +void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data); #ifdef __cplusplus } diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.c b/source/blender/nodes/shader/nodes/node_shader_curves.cc index 42299a193e2..f1d5040a292 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.c +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -47,7 +47,7 @@ static void node_shader_exec_curve_vec(void *UNUSED(data), /* stack order input: vec */ /* stack order output: vec */ nodestack_get_vec(vec, SOCK_VECTOR, in[1]); - BKE_curvemapping_evaluate3F(node->storage, out[0]->vec, vec); + BKE_curvemapping_evaluate3F((CurveMapping *)node->storage, out[0]->vec, vec); } static void node_shader_init_curve_vec(bNodeTree *UNUSED(ntree), bNode *node) @@ -64,7 +64,7 @@ static int gpu_shader_curve_vec(GPUMaterial *mat, float *array, layer; int size; - CurveMapping *cumap = node->storage; + CurveMapping *cumap = (CurveMapping *)node->storage; BKE_curvemapping_table_RGBA(cumap, &array, &size); GPUNodeLink *tex = GPU_color_band(mat, size, array, &layer); @@ -104,17 +104,65 @@ static int gpu_shader_curve_vec(GPUMaterial *mat, GPU_uniform(ext_xyz[2])); } +class CurveVecFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveVecFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Vec"}; + signature.single_input<float>("Fac"); + signature.single_input<blender::float3>("Vector"); + signature.single_output<blender::float3>("Vector"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Fac"); + const blender::VArray<blender::float3> &vec_in = params.readonly_single_input<blender::float3>( + 1, "Vector"); + blender::MutableSpan<blender::float3> vec_out = + params.uninitialized_single_output<blender::float3>(2, "Vector"); + + for (int64_t i : mask) { + BKE_curvemapping_evaluate3F(&cumap_, vec_out[i], vec_in[i]); + if (fac[i] != 1.0f) { + interp_v3_v3v3(vec_out[i], vec_in[i], vec_out[i], fac[i]); + } + } + } +}; + +static void sh_node_curve_vec_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) +{ + bNode &bnode = builder.bnode(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveVecFunction>(*cumap); +} + void register_node_type_sh_curve_vec(void) { static bNodeType ntype; - sh_node_type_base(&ntype, SH_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR, 0); + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR, 0); node_type_socket_templates(&ntype, sh_node_curve_vec_in, sh_node_curve_vec_out); node_type_init(&ntype, node_shader_init_curve_vec); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); - node_type_exec(&ntype, node_initexec_curves, NULL, node_shader_exec_curve_vec); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_vec); node_type_gpu(&ntype, gpu_shader_curve_vec); + ntype.expand_in_mf_network = sh_node_curve_vec_expand_in_mf_network; nodeRegisterType(&ntype); } @@ -145,7 +193,7 @@ static void node_shader_exec_curve_rgb(void *UNUSED(data), /* stack order output: vec */ nodestack_get_vec(&fac, SOCK_FLOAT, in[0]); nodestack_get_vec(vec, SOCK_VECTOR, in[1]); - BKE_curvemapping_evaluateRGBF(node->storage, out[0]->vec, vec); + BKE_curvemapping_evaluateRGBF((CurveMapping *)node->storage, out[0]->vec, vec); if (fac != 1.0f) { interp_v3_v3v3(out[0]->vec, vec, out[0]->vec, fac); } @@ -166,7 +214,7 @@ static int gpu_shader_curve_rgb(GPUMaterial *mat, int size; bool use_opti = true; - CurveMapping *cumap = node->storage; + CurveMapping *cumap = (CurveMapping *)node->storage; BKE_curvemapping_init(cumap); BKE_curvemapping_table_RGBA(cumap, &array, &size); @@ -230,17 +278,65 @@ static int gpu_shader_curve_rgb(GPUMaterial *mat, GPU_uniform(ext_rgba[3])); } +class CurveRGBFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveRGBFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve RGB"}; + signature.single_input<float>("Fac"); + signature.single_input<blender::ColorGeometry4f>("Color"); + signature.single_output<blender::ColorGeometry4f>("Color"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Fac"); + const blender::VArray<blender::ColorGeometry4f> &col_in = + params.readonly_single_input<blender::ColorGeometry4f>(1, "Color"); + blender::MutableSpan<blender::ColorGeometry4f> col_out = + params.uninitialized_single_output<blender::ColorGeometry4f>(2, "Color"); + + for (int64_t i : mask) { + BKE_curvemapping_evaluateRGBF(&cumap_, col_out[i], col_in[i]); + if (fac[i] != 1.0f) { + interp_v3_v3v3(col_out[i], col_in[i], col_out[i], fac[i]); + } + } + } +}; + +static void sh_node_curve_rgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) +{ + bNode &bnode = builder.bnode(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveRGBFunction>(*cumap); +} + void register_node_type_sh_curve_rgb(void) { static bNodeType ntype; - sh_node_type_base(&ntype, SH_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); node_type_socket_templates(&ntype, sh_node_curve_rgb_in, sh_node_curve_rgb_out); node_type_init(&ntype, node_shader_init_curve_rgb); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); - node_type_exec(&ntype, node_initexec_curves, NULL, node_shader_exec_curve_rgb); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_rgb); node_type_gpu(&ntype, gpu_shader_curve_rgb); + ntype.expand_in_mf_network = sh_node_curve_rgb_expand_in_mf_network; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/shader/nodes/node_shader_map_range.cc b/source/blender/nodes/shader/nodes/node_shader_map_range.cc index 3b4ea3d1bdf..ad7abd9d491 100644 --- a/source/blender/nodes/shader/nodes/node_shader_map_range.cc +++ b/source/blender/nodes/shader/nodes/node_shader_map_range.cc @@ -23,6 +23,8 @@ #include "node_shader_util.h" +#include "BLI_math_base_safe.h" + /* **************** Map Range ******************** */ static bNodeSocketTemplate sh_node_map_range_in[] = { {SOCK_FLOAT, N_("Value"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, @@ -88,6 +90,19 @@ static int gpu_shader_map_range(GPUMaterial *mat, return ret; } +static void map_range_signature(blender::fn::MFSignatureBuilder *signature, bool use_steps) +{ + signature->single_input<float>("Value"); + signature->single_input<float>("From Min"); + signature->single_input<float>("From Max"); + signature->single_input<float>("To Min"); + signature->single_input<float>("To Max"); + if (use_steps) { + signature->single_input<float>("Steps"); + } + signature->single_output<float>("Result"); +} + class MapRangeFunction : public blender::fn::MultiFunction { private: bool clamp_; @@ -102,12 +117,7 @@ class MapRangeFunction : public blender::fn::MultiFunction { static blender::fn::MFSignature create_signature() { blender::fn::MFSignatureBuilder signature{"Map Range"}; - signature.single_input<float>("Value"); - signature.single_input<float>("From Min"); - signature.single_input<float>("From Max"); - signature.single_input<float>("To Min"); - signature.single_input<float>("To Max"); - signature.single_output<float>("Result"); + map_range_signature(&signature, false); return signature.build(); } @@ -136,25 +146,163 @@ class MapRangeFunction : public blender::fn::MultiFunction { } }; +class MapRangeSteppedFunction : public blender::fn::MultiFunction { + private: + bool clamp_; + + public: + MapRangeSteppedFunction(bool clamp) : clamp_(clamp) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Map Range Stepped"}; + map_range_signature(&signature, true); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &values = params.readonly_single_input<float>(0, "Value"); + const blender::VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min"); + const blender::VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max"); + const blender::VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min"); + const blender::VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max"); + const blender::VArray<float> &steps = params.readonly_single_input<float>(5, "Steps"); + blender::MutableSpan<float> results = params.uninitialized_single_output<float>(6, "Result"); + + for (int64_t i : mask) { + float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]); + factor = safe_divide(floorf(factor * (steps[i] + 1.0f)), steps[i]); + results[i] = to_min[i] + factor * (to_max[i] - to_min[i]); + } + + if (clamp_) { + for (int64_t i : mask) { + results[i] = (to_min[i] > to_max[i]) ? clamp_f(results[i], to_max[i], to_min[i]) : + clamp_f(results[i], to_min[i], to_max[i]); + } + } + } +}; + +class MapRangeSmoothstepFunction : public blender::fn::MultiFunction { + public: + MapRangeSmoothstepFunction() + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Map Range Smoothstep"}; + map_range_signature(&signature, false); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &values = params.readonly_single_input<float>(0, "Value"); + const blender::VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min"); + const blender::VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max"); + const blender::VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min"); + const blender::VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max"); + blender::MutableSpan<float> results = params.uninitialized_single_output<float>(5, "Result"); + + for (int64_t i : mask) { + float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]); + factor = std::clamp(factor, 0.0f, 1.0f); + factor = (3.0f - 2.0f * factor) * (factor * factor); + results[i] = to_min[i] + factor * (to_max[i] - to_min[i]); + } + } +}; + +class MapRangeSmootherstepFunction : public blender::fn::MultiFunction { + public: + MapRangeSmootherstepFunction() + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Map Range Smoothstep"}; + map_range_signature(&signature, false); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &values = params.readonly_single_input<float>(0, "Value"); + const blender::VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min"); + const blender::VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max"); + const blender::VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min"); + const blender::VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max"); + blender::MutableSpan<float> results = params.uninitialized_single_output<float>(5, "Result"); + + for (int64_t i : mask) { + float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]); + factor = std::clamp(factor, 0.0f, 1.0f); + factor = factor * factor * factor * (factor * (factor * 6.0f - 15.0f) + 10.0f); + results[i] = to_min[i] + factor * (to_max[i] - to_min[i]); + } + } +}; + static void sh_node_map_range_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) { bNode &bnode = builder.bnode(); bool clamp = bnode.custom1 != 0; int interpolation_type = bnode.custom2; - if (interpolation_type == NODE_MAP_RANGE_LINEAR) { - static MapRangeFunction fn_with_clamp{true}; - static MapRangeFunction fn_without_clamp{false}; - - if (clamp) { - builder.set_matching_fn(fn_with_clamp); + switch (interpolation_type) { + case NODE_MAP_RANGE_LINEAR: { + if (clamp) { + static MapRangeFunction fn_with_clamp{true}; + builder.set_matching_fn(fn_with_clamp); + } + else { + static MapRangeFunction fn_without_clamp{false}; + builder.set_matching_fn(fn_without_clamp); + } + break; } - else { - builder.set_matching_fn(fn_without_clamp); + case NODE_MAP_RANGE_STEPPED: { + if (clamp) { + static MapRangeSteppedFunction fn_stepped_with_clamp{true}; + builder.set_matching_fn(fn_stepped_with_clamp); + } + else { + static MapRangeSteppedFunction fn_stepped_without_clamp{false}; + builder.set_matching_fn(fn_stepped_without_clamp); + } + break; } - } - else { - builder.set_not_implemented(); + case NODE_MAP_RANGE_SMOOTHSTEP: { + static MapRangeSmoothstepFunction smoothstep; + builder.set_matching_fn(smoothstep); + break; + } + case NODE_MAP_RANGE_SMOOTHERSTEP: { + static MapRangeSmootherstepFunction smootherstep; + builder.set_matching_fn(smootherstep); + break; + } + default: + builder.set_not_implemented(); + break; } } diff --git a/source/blender/nodes/shader/nodes/node_shader_mixRgb.c b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc index 090c6216224..52f2adb10dd 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mixRgb.c +++ b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc @@ -61,35 +61,60 @@ static void node_shader_exec_mix_rgb(void *UNUSED(data), copy_v3_v3(out[0]->vec, col); } +static const char *gpu_shader_get_name(int mode) +{ + switch (mode) { + case MA_RAMP_BLEND: + return "mix_blend"; + case MA_RAMP_ADD: + return "mix_add"; + case MA_RAMP_MULT: + return "mix_mult"; + case MA_RAMP_SUB: + return "mix_sub"; + case MA_RAMP_SCREEN: + return "mix_screen"; + case MA_RAMP_DIV: + return "mix_div"; + case MA_RAMP_DIFF: + return "mix_diff"; + case MA_RAMP_DARK: + return "mix_dark"; + case MA_RAMP_LIGHT: + return "mix_light"; + case MA_RAMP_OVERLAY: + return "mix_overlay"; + case MA_RAMP_DODGE: + return "mix_dodge"; + case MA_RAMP_BURN: + return "mix_burn"; + case MA_RAMP_HUE: + return "mix_hue"; + case MA_RAMP_SAT: + return "mix_sat"; + case MA_RAMP_VAL: + return "mix_val"; + case MA_RAMP_COLOR: + return "mix_color"; + case MA_RAMP_SOFT: + return "mix_soft"; + case MA_RAMP_LINEAR: + return "mix_linear"; + } + + return nullptr; +} + static int gpu_shader_mix_rgb(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), GPUNodeStack *in, GPUNodeStack *out) { - static const char *names[] = { - "mix_blend", - "mix_add", - "mix_mult", - "mix_sub", - "mix_screen", - "mix_div", - "mix_diff", - "mix_dark", - "mix_light", - "mix_overlay", - "mix_dodge", - "mix_burn", - "mix_hue", - "mix_sat", - "mix_val", - "mix_color", - "mix_soft", - "mix_linear", - }; + const char *name = gpu_shader_get_name(node->custom1); - if (node->custom1 < ARRAY_SIZE(names) && names[node->custom1]) { - int ret = GPU_stack_link(mat, node, names[node->custom1], in, out); + if (name != nullptr) { + int ret = GPU_stack_link(mat, node, name, in, out); if (ret && node->custom2 & SHD_MIXRGB_CLAMP) { const float min[3] = {0.0f, 0.0f, 0.0f}; const float max[3] = {1.0f, 1.0f, 1.0f}; diff --git a/source/blender/nodes/shader/nodes/node_shader_output_aov.c b/source/blender/nodes/shader/nodes/node_shader_output_aov.c index 403b3e6d9d6..7e7e1b703f1 100644 --- a/source/blender/nodes/shader/nodes/node_shader_output_aov.c +++ b/source/blender/nodes/shader/nodes/node_shader_output_aov.c @@ -43,8 +43,9 @@ static int node_shader_gpu_output_aov(GPUMaterial *mat, { GPUNodeLink *outlink; NodeShaderOutputAOV *aov = (NodeShaderOutputAOV *)node->storage; - /* Keep in sync with `renderpass_lib.glsl#render_pass_aov_hash`. */ - unsigned int hash = BLI_hash_string(aov->name) & ~1; + /* Keep in sync with `renderpass_lib.glsl#render_pass_aov_hash` and + * `EEVEE_renderpasses_aov_hash`. */ + unsigned int hash = BLI_hash_string(aov->name) << 1; GPU_stack_link(mat, node, "node_output_aov", in, out, &outlink); GPU_material_add_output_link_aov(mat, outlink, hash); diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc index 8ca4a6bab5f..a7239154633 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc @@ -70,7 +70,7 @@ class SeparateRGBFunction : public blender::fn::MultiFunction { static blender::fn::MFSignature create_signature() { blender::fn::MFSignatureBuilder signature{"Separate RGB"}; - signature.single_input<blender::Color4f>("Color"); + signature.single_input<blender::ColorGeometry4f>("Color"); signature.single_output<float>("R"); signature.single_output<float>("G"); signature.single_output<float>("B"); @@ -81,14 +81,14 @@ class SeparateRGBFunction : public blender::fn::MultiFunction { blender::fn::MFParams params, blender::fn::MFContext UNUSED(context)) const override { - const blender::VArray<blender::Color4f> &colors = - params.readonly_single_input<blender::Color4f>(0, "Color"); + const blender::VArray<blender::ColorGeometry4f> &colors = + params.readonly_single_input<blender::ColorGeometry4f>(0, "Color"); blender::MutableSpan<float> rs = params.uninitialized_single_output<float>(1, "R"); blender::MutableSpan<float> gs = params.uninitialized_single_output<float>(2, "G"); blender::MutableSpan<float> bs = params.uninitialized_single_output<float>(3, "B"); for (int64_t i : mask) { - blender::Color4f color = colors[i]; + blender::ColorGeometry4f color = colors[i]; rs[i] = color.r; gs[i] = color.g; bs[i] = color.b; @@ -155,8 +155,9 @@ static int gpu_shader_combrgb(GPUMaterial *mat, static void sh_node_combrgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, blender::Color4f> fn{ - "Combine RGB", [](float r, float g, float b) { return blender::Color4f(r, g, b, 1.0f); }}; + static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, blender::ColorGeometry4f> fn{ + "Combine RGB", + [](float r, float g, float b) { return blender::ColorGeometry4f(r, g, b, 1.0f); }}; builder.set_matching_fn(fn); } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_sky.c b/source/blender/nodes/shader/nodes/node_shader_tex_sky.c index 9ef05d781bd..5dc11c4df00 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_sky.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_sky.c @@ -60,11 +60,6 @@ typedef struct SkyModelPreetham { float radiance[3]; } SkyModelPreetham; -typedef struct XYZ_to_RGB /* transposed imbuf_xyz_to_rgb, passed as 3x vec3 */ -{ - float r[3], g[3], b[3]; -} XYZ_to_RGB; - static float sky_perez_function(const float *lam, float theta, float gamma) { float ctheta = cosf(theta); @@ -119,20 +114,6 @@ static void sky_precompute_old(SkyModelPreetham *sunsky, const float sun_angles[ sunsky->radiance[2] /= sky_perez_function(sunsky->config_y, 0, theta); } -static void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data) -{ - const float *xyz_to_rgb = IMB_colormangement_get_xyz_to_rgb(); - data->r[0] = xyz_to_rgb[0]; - data->r[1] = xyz_to_rgb[3]; - data->r[2] = xyz_to_rgb[6]; - data->g[0] = xyz_to_rgb[1]; - data->g[1] = xyz_to_rgb[4]; - data->g[2] = xyz_to_rgb[7]; - data->b[0] = xyz_to_rgb[2]; - data->b[1] = xyz_to_rgb[5]; - data->b[2] = xyz_to_rgb[8]; -} - static int node_shader_gpu_tex_sky(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), diff --git a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc index 90e8161c09f..5b2eb300aac 100644 --- a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc @@ -140,7 +140,7 @@ class ColorBandFunction : public blender::fn::MultiFunction { { blender::fn::MFSignatureBuilder signature{"Color Band"}; signature.single_input<float>("Value"); - signature.single_output<blender::Color4f>("Color"); + signature.single_output<blender::ColorGeometry4f>("Color"); signature.single_output<float>("Alpha"); return signature.build(); } @@ -150,12 +150,12 @@ class ColorBandFunction : public blender::fn::MultiFunction { blender::fn::MFContext UNUSED(context)) const override { const blender::VArray<float> &values = params.readonly_single_input<float>(0, "Value"); - blender::MutableSpan<blender::Color4f> colors = - params.uninitialized_single_output<blender::Color4f>(1, "Color"); + blender::MutableSpan<blender::ColorGeometry4f> colors = + params.uninitialized_single_output<blender::ColorGeometry4f>(1, "Color"); blender::MutableSpan<float> alphas = params.uninitialized_single_output<float>(2, "Alpha"); for (int64_t i : mask) { - blender::Color4f color; + blender::ColorGeometry4f color; BKE_colorband_evaluate(&color_band_, values[i], color); colors[i] = color; alphas[i] = color.a; diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc index 3a9822fbc8e..419a11201aa 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc @@ -23,6 +23,8 @@ #include "node_shader_util.h" +#include "NOD_math_functions.hh" + /* **************** VECTOR MATH ******************** */ static bNodeSocketTemplate sh_node_vector_math_in[] = { {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, @@ -92,6 +94,8 @@ static const char *gpu_shader_get_name(int mode) return "vector_math_refract"; case NODE_VECTOR_MATH_FACEFORWARD: return "vector_math_faceforward"; + case NODE_VECTOR_MATH_MULTIPLY_ADD: + return "vector_math_multiply_add"; } return nullptr; @@ -132,8 +136,11 @@ static void node_shader_update_vector_math(bNodeTree *UNUSED(ntree), bNode *node NODE_VECTOR_MATH_ABSOLUTE, NODE_VECTOR_MATH_FRACTION, NODE_VECTOR_MATH_NORMALIZE)); - nodeSetSocketAvailability( - sockC, ELEM(node->custom1, NODE_VECTOR_MATH_WRAP, NODE_VECTOR_MATH_FACEFORWARD)); + nodeSetSocketAvailability(sockC, + ELEM(node->custom1, + NODE_VECTOR_MATH_WRAP, + NODE_VECTOR_MATH_FACEFORWARD, + NODE_VECTOR_MATH_MULTIPLY_ADD)); nodeSetSocketAvailability(sockScale, ELEM(node->custom1, NODE_VECTOR_MATH_SCALE, NODE_VECTOR_MATH_REFRACT)); nodeSetSocketAvailability(sockVector, @@ -152,6 +159,10 @@ static void node_shader_update_vector_math(bNodeTree *UNUSED(ntree), bNode *node node_sock_label_clear(sockC); node_sock_label_clear(sockScale); switch (node->custom1) { + case NODE_VECTOR_MATH_MULTIPLY_ADD: + node_sock_label(sockB, "Multiplier"); + node_sock_label(sockC, "Addend"); + break; case NODE_VECTOR_MATH_FACEFORWARD: node_sock_label(sockB, "Incident"); node_sock_label(sockC, "Reference"); @@ -177,117 +188,79 @@ static const blender::fn::MultiFunction &get_multi_function( { using blender::float3; - const int mode = builder.bnode().custom1; - switch (mode) { - case NODE_VECTOR_MATH_ADD: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Add", [](float3 a, float3 b) { return a + b; }}; - return fn; - } - case NODE_VECTOR_MATH_SUBTRACT: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Subtract", [](float3 a, float3 b) { return a - b; }}; - return fn; - } - case NODE_VECTOR_MATH_MULTIPLY: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Multiply", [](float3 a, float3 b) { return a * b; }}; - return fn; - } - case NODE_VECTOR_MATH_DIVIDE: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Divide", [](float3 a, float3 b) { return float3::safe_divide(a, b); }}; - return fn; - } - case NODE_VECTOR_MATH_CROSS_PRODUCT: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Cross Product", float3::cross_high_precision}; - return fn; - } - case NODE_VECTOR_MATH_PROJECT: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{"Project", float3::project}; - return fn; - } - case NODE_VECTOR_MATH_REFLECT: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{ - "Reflect", [](float3 a, float3 b) { return a.reflected(b); }}; - return fn; - } - case NODE_VECTOR_MATH_DOT_PRODUCT: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float> fn{"Dot Product", float3::dot}; - return fn; - } - case NODE_VECTOR_MATH_DISTANCE: { - static blender::fn::CustomMF_SI_SI_SO<float3, float3, float> fn{"Distance", - float3::distance}; - return fn; - } - case NODE_VECTOR_MATH_LENGTH: { - static blender::fn::CustomMF_SI_SO<float3, float> fn{"Length", - [](float3 a) { return a.length(); }}; - return fn; - } - case NODE_VECTOR_MATH_SCALE: { - static blender::fn::CustomMF_SI_SI_SO<float3, float, float3> fn{ - "Scale", [](float3 a, float factor) { return a * factor; }}; - return fn; - } - case NODE_VECTOR_MATH_NORMALIZE: { - static blender::fn::CustomMF_SI_SO<float3, float3> fn{ - "Normalize", [](float3 a) { return a.normalized(); }}; - return fn; - } - case NODE_VECTOR_MATH_REFRACT: { - static blender::fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> fn{ - "Refract", - [](float3 a, float3 b, float c) { return float3::refract(a, b.normalized(), c); }}; - return fn; - } - case NODE_VECTOR_MATH_FACEFORWARD: { - static blender::fn::CustomMF_SI_SI_SI_SO<float3, float3, float3, float3> fn{ - "Faceforward", float3::faceforward}; - return fn; - } - case NODE_VECTOR_MATH_SNAP: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_FLOOR: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_CEIL: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_MODULO: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_FRACTION: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_ABSOLUTE: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_MINIMUM: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_MAXIMUM: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_WRAP: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_SINE: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_COSINE: { - return builder.get_not_implemented_fn(); - } - case NODE_VECTOR_MATH_TANGENT: { - return builder.get_not_implemented_fn(); - } - default: - BLI_assert_unreachable(); - return builder.get_not_implemented_fn(); - }; + NodeVectorMathOperation operation = NodeVectorMathOperation(builder.bnode().custom1); + + const blender::fn::MultiFunction *multi_fn = nullptr; + + blender::nodes::try_dispatch_float_math_fl3_fl3_to_fl3( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{info.title_case_name, + function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_fl3_fl3_to_fl3( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SI_SO<float3, float3, float3, float3> fn{ + info.title_case_name, function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_fl3_fl_to_fl3( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> fn{ + info.title_case_name, function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_fl3_to_fl( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SO<float3, float3, float> fn{info.title_case_name, + function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_fl_to_fl3( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SO<float3, float, float3> fn{info.title_case_name, + function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_to_fl3( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SO<float3, float3> fn{info.title_case_name, function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + blender::nodes::try_dispatch_float_math_fl3_to_fl( + operation, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SO<float3, float> fn{info.title_case_name, function}; + multi_fn = &fn; + }); + if (multi_fn != nullptr) { + return *multi_fn; + } + + return builder.get_not_implemented_fn(); } static void sh_node_vector_math_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) diff --git a/source/blender/nodes/shader/nodes/node_shader_wavelength.c b/source/blender/nodes/shader/nodes/node_shader_wavelength.c index 6b7e1399328..30f69557020 100644 --- a/source/blender/nodes/shader/nodes/node_shader_wavelength.c +++ b/source/blender/nodes/shader/nodes/node_shader_wavelength.c @@ -30,6 +30,33 @@ static bNodeSocketTemplate sh_node_wavelength_out[] = { {-1, ""}, }; +static int node_shader_gpu_wavelength(GPUMaterial *mat, + bNode *node, + bNodeExecData *UNUSED(execdata), + GPUNodeStack *in, + GPUNodeStack *out) +{ + const int size = CM_TABLE + 1; + float *data = MEM_mallocN(sizeof(float) * size * 4, "cie_xyz texture"); + + wavelength_to_xyz_table(data, size); + + float layer; + GPUNodeLink *ramp_texture = GPU_color_band(mat, size, data, &layer); + XYZ_to_RGB xyz_to_rgb; + get_XYZ_to_RGB_for_gpu(&xyz_to_rgb); + return GPU_stack_link(mat, + node, + "node_wavelength", + in, + out, + ramp_texture, + GPU_constant(&layer), + GPU_uniform(xyz_to_rgb.r), + GPU_uniform(xyz_to_rgb.g), + GPU_uniform(xyz_to_rgb.b)); +} + /* node type definition */ void register_node_type_sh_wavelength(void) { @@ -40,6 +67,7 @@ void register_node_type_sh_wavelength(void) node_type_socket_templates(&ntype, sh_node_wavelength_in, sh_node_wavelength_out); node_type_init(&ntype, NULL); node_type_storage(&ntype, "", NULL, NULL); + node_type_gpu(&ntype, node_shader_gpu_wavelength); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 48027dc847b..2ae722e3cd8 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -152,6 +152,12 @@ static void update(bNodeTree *ntree) } } +static bool texture_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); +} + bNodeTreeType *ntreeType_Texture; void register_node_tree_type_tex(void) @@ -171,6 +177,7 @@ void register_node_tree_type_tex(void) tt->local_sync = local_sync; tt->local_merge = local_merge; tt->get_from_context = texture_get_from_context; + tt->valid_socket_type = texture_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_TextureNodeTree; diff --git a/source/blender/nodes/texture/node_texture_util.c b/source/blender/nodes/texture/node_texture_util.c index 2091a8bf10e..570b10d6e89 100644 --- a/source/blender/nodes/texture/node_texture_util.c +++ b/source/blender/nodes/texture/node_texture_util.c @@ -39,9 +39,15 @@ #include "node_texture_util.h" -bool tex_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool tex_node_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *ntree, + const char **r_disabled_hint) { - return STREQ(ntree->idname, "TextureNodeTree"); + if (!STREQ(ntree->idname, "TextureNodeTree")) { + *r_disabled_hint = "Not a texture node tree"; + return false; + } + return true; } void tex_node_type_base( diff --git a/source/blender/nodes/texture/node_texture_util.h b/source/blender/nodes/texture/node_texture_util.h index 74f27ef3974..8f63a1ad07d 100644 --- a/source/blender/nodes/texture/node_texture_util.h +++ b/source/blender/nodes/texture/node_texture_util.h @@ -106,7 +106,9 @@ typedef struct TexDelegate { int type; } TexDelegate; -bool tex_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool tex_node_poll_default(struct bNodeType *ntype, + struct bNodeTree *ntree, + const char **r_disabled_hint); void tex_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); |