diff options
Diffstat (limited to 'source/blender/nodes/geometry')
161 files changed, 19335 insertions, 4921 deletions
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt new file mode 100644 index 00000000000..b4add633b0c --- /dev/null +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -0,0 +1,290 @@ +# ***** 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. +# +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + .. + ../intern + ../../editors/include + ../../blenkernel + ../../blenlib + ../../blentranslation + ../../bmesh + ../../depsgraph + ../../functions + ../../geometry + ../../gpu + ../../imbuf + ../../makesdna + ../../makesrna + ../../render + ../../windowmanager + ../../../../intern/guardedalloc +) + + +set(SRC + nodes/legacy/node_geo_legacy_align_rotation_to_vector.cc + nodes/legacy/node_geo_legacy_attribute_clamp.cc + nodes/legacy/node_geo_legacy_attribute_color_ramp.cc + nodes/legacy/node_geo_legacy_attribute_combine_xyz.cc + nodes/legacy/node_geo_legacy_attribute_compare.cc + nodes/legacy/node_geo_legacy_attribute_convert.cc + nodes/legacy/node_geo_legacy_attribute_curve_map.cc + nodes/legacy/node_geo_legacy_attribute_fill.cc + nodes/legacy/node_geo_legacy_attribute_map_range.cc + nodes/legacy/node_geo_legacy_attribute_math.cc + nodes/legacy/node_geo_legacy_attribute_mix.cc + nodes/legacy/node_geo_legacy_attribute_proximity.cc + nodes/legacy/node_geo_legacy_attribute_randomize.cc + nodes/legacy/node_geo_legacy_attribute_sample_texture.cc + nodes/legacy/node_geo_legacy_attribute_separate_xyz.cc + nodes/legacy/node_geo_legacy_attribute_transfer.cc + nodes/legacy/node_geo_legacy_attribute_vector_math.cc + nodes/legacy/node_geo_legacy_attribute_vector_rotate.cc + nodes/legacy/node_geo_legacy_curve_endpoints.cc + nodes/legacy/node_geo_legacy_curve_reverse.cc + nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc + nodes/legacy/node_geo_legacy_curve_set_handles.cc + nodes/legacy/node_geo_legacy_curve_spline_type.cc + nodes/legacy/node_geo_legacy_curve_subdivide.cc + nodes/legacy/node_geo_legacy_curve_to_points.cc + nodes/legacy/node_geo_legacy_delete_geometry.cc + nodes/legacy/node_geo_legacy_edge_split.cc + nodes/legacy/node_geo_legacy_material_assign.cc + nodes/legacy/node_geo_legacy_mesh_to_curve.cc + nodes/legacy/node_geo_legacy_point_distribute.cc + nodes/legacy/node_geo_legacy_point_instance.cc + nodes/legacy/node_geo_legacy_point_rotate.cc + nodes/legacy/node_geo_legacy_point_scale.cc + nodes/legacy/node_geo_legacy_point_separate.cc + nodes/legacy/node_geo_legacy_point_translate.cc + nodes/legacy/node_geo_legacy_points_to_volume.cc + nodes/legacy/node_geo_legacy_raycast.cc + nodes/legacy/node_geo_legacy_select_by_material.cc + nodes/legacy/node_geo_legacy_subdivision_surface.cc + nodes/legacy/node_geo_legacy_volume_to_mesh.cc + + nodes/node_geo_accumulate_field.cc + nodes/node_geo_attribute_capture.cc + nodes/node_geo_attribute_domain_size.cc + nodes/node_geo_attribute_remove.cc + nodes/node_geo_attribute_statistic.cc + nodes/node_geo_boolean.cc + nodes/node_geo_bounding_box.cc + nodes/node_geo_collection_info.cc + nodes/node_geo_common.cc + nodes/node_geo_convex_hull.cc + nodes/node_geo_curve_endpoint_selection.cc + nodes/node_geo_curve_fill.cc + nodes/node_geo_curve_fillet.cc + nodes/node_geo_curve_handle_type_selection.cc + nodes/node_geo_curve_length.cc + nodes/node_geo_curve_primitive_arc.cc + nodes/node_geo_curve_primitive_bezier_segment.cc + nodes/node_geo_curve_primitive_circle.cc + nodes/node_geo_curve_primitive_line.cc + nodes/node_geo_curve_primitive_quadratic_bezier.cc + nodes/node_geo_curve_primitive_quadrilateral.cc + nodes/node_geo_curve_primitive_spiral.cc + nodes/node_geo_curve_primitive_star.cc + nodes/node_geo_curve_resample.cc + nodes/node_geo_curve_reverse.cc + nodes/node_geo_curve_sample.cc + nodes/node_geo_curve_set_handles.cc + nodes/node_geo_curve_spline_parameter.cc + nodes/node_geo_curve_spline_type.cc + nodes/node_geo_curve_subdivide.cc + nodes/node_geo_curve_to_mesh.cc + nodes/node_geo_curve_to_points.cc + nodes/node_geo_curve_trim.cc + nodes/node_geo_delete_geometry.cc + nodes/node_geo_distribute_points_on_faces.cc + nodes/node_geo_dual_mesh.cc + nodes/node_geo_edge_split.cc + nodes/node_geo_extrude_mesh.cc + nodes/node_geo_field_at_index.cc + nodes/node_geo_flip_faces.cc + nodes/node_geo_geometry_to_instance.cc + nodes/node_geo_image_texture.cc + nodes/node_geo_input_curve_handles.cc + nodes/node_geo_input_curve_tilt.cc + nodes/node_geo_input_id.cc + nodes/node_geo_input_index.cc + nodes/node_geo_input_material.cc + nodes/node_geo_input_material_index.cc + nodes/node_geo_input_mesh_edge_angle.cc + nodes/node_geo_input_mesh_edge_neighbors.cc + nodes/node_geo_input_mesh_edge_vertices.cc + nodes/node_geo_input_mesh_face_area.cc + nodes/node_geo_input_mesh_face_neighbors.cc + nodes/node_geo_input_mesh_island.cc + nodes/node_geo_input_mesh_vertex_neighbors.cc + nodes/node_geo_input_normal.cc + nodes/node_geo_input_position.cc + nodes/node_geo_input_radius.cc + nodes/node_geo_input_scene_time.cc + nodes/node_geo_input_shade_smooth.cc + nodes/node_geo_input_spline_cyclic.cc + nodes/node_geo_input_spline_length.cc + nodes/node_geo_input_spline_resolution.cc + nodes/node_geo_input_tangent.cc + nodes/node_geo_instance_on_points.cc + nodes/node_geo_instances_to_points.cc + nodes/node_geo_is_viewport.cc + nodes/node_geo_join_geometry.cc + nodes/node_geo_material_replace.cc + nodes/node_geo_material_selection.cc + nodes/node_geo_merge_by_distance.cc + nodes/node_geo_mesh_primitive_circle.cc + nodes/node_geo_mesh_primitive_cone.cc + nodes/node_geo_mesh_primitive_cube.cc + nodes/node_geo_mesh_primitive_cylinder.cc + nodes/node_geo_mesh_primitive_grid.cc + nodes/node_geo_mesh_primitive_ico_sphere.cc + nodes/node_geo_mesh_primitive_line.cc + nodes/node_geo_mesh_primitive_uv_sphere.cc + nodes/node_geo_mesh_subdivide.cc + nodes/node_geo_mesh_to_curve.cc + nodes/node_geo_mesh_to_points.cc + nodes/node_geo_object_info.cc + nodes/node_geo_points_to_vertices.cc + nodes/node_geo_points_to_volume.cc + nodes/node_geo_proximity.cc + nodes/node_geo_raycast.cc + nodes/node_geo_realize_instances.cc + nodes/node_geo_rotate_instances.cc + nodes/node_geo_scale_elements.cc + nodes/node_geo_scale_instances.cc + nodes/node_geo_separate_components.cc + nodes/node_geo_separate_geometry.cc + nodes/node_geo_set_curve_handles.cc + nodes/node_geo_set_curve_radius.cc + nodes/node_geo_set_curve_tilt.cc + nodes/node_geo_set_id.cc + nodes/node_geo_set_material.cc + nodes/node_geo_set_material_index.cc + nodes/node_geo_set_point_radius.cc + nodes/node_geo_set_position.cc + nodes/node_geo_set_shade_smooth.cc + nodes/node_geo_set_spline_cyclic.cc + nodes/node_geo_set_spline_resolution.cc + nodes/node_geo_string_join.cc + nodes/node_geo_string_to_curves.cc + nodes/node_geo_subdivision_surface.cc + nodes/node_geo_switch.cc + nodes/node_geo_transfer_attribute.cc + nodes/node_geo_transform.cc + nodes/node_geo_translate_instances.cc + nodes/node_geo_triangulate.cc + nodes/node_geo_viewer.cc + nodes/node_geo_volume_to_mesh.cc + + node_geometry_exec.cc + node_geometry_tree.cc + node_geometry_util.cc + + node_geometry_util.hh +) + +set(LIB + bf_bmesh + bf_functions + bf_geometry +) + +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 + ) + list(APPEND INC_SYS + ${PYTHON_INCLUDE_DIRS} + ) + list(APPEND LIB + ${PYTHON_LINKFLAGS} + ${PYTHON_LIBRARIES} + ) + add_definitions(-DWITH_PYTHON) +endif() + +if(WITH_INTERNATIONAL) + add_definitions(-DWITH_INTERNATIONAL) +endif() + +if(WITH_TBB) + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() +endif() + +if(WITH_IMAGE_OPENEXR) + add_definitions(-DWITH_OPENEXR) +endif() + +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} + ) + add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS}) +endif() + +blender_add_lib(bf_nodes_geometry "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +if(WITH_UNITY_BUILD) + set_target_properties(bf_nodes_geometry PROPERTIES UNITY_BUILD ON) + set_target_properties(bf_nodes_geometry PROPERTIES UNITY_BUILD_BATCH_SIZE 10) +endif() diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index d6b23c38ee4..a6dec71ed06 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -32,6 +32,8 @@ #include "RNA_access.h" +#include "UI_resources.h" + #include "node_common.h" bNodeTreeType *ntreeType_Geometry; @@ -84,15 +86,16 @@ 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) +static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a, + eNodeSocketDatatype type_b) { /* 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)) { + if (ELEM(type_a, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) && + ELEM(type_b, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) { return true; } - return (link->tosock->type == link->fromsock->type); + return type_a == type_b; } static bool geometry_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype), @@ -109,17 +112,18 @@ static bool geometry_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype SOCK_GEOMETRY, SOCK_COLLECTION, SOCK_TEXTURE, + SOCK_IMAGE, SOCK_MATERIAL); } -void register_node_tree_type_geo(void) +void register_node_tree_type_geo() { bNodeTreeType *tt = ntreeType_Geometry = static_cast<bNodeTreeType *>( MEM_callocN(sizeof(bNodeTreeType), "geometry node tree type")); tt->type = NTREE_GEOMETRY; strcpy(tt->idname, "GeometryNodeTree"); strcpy(tt->ui_name, N_("Geometry Node Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = ICON_NODETREE; strcpy(tt->ui_description, N_("Geometry nodes")); tt->rna_ext.srna = &RNA_GeometryNodeTree; tt->update = geometry_node_tree_update; diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index 46e9d36c09c..ceb9a7e1467 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -24,18 +24,14 @@ #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" +#include "NOD_socket_search_link.hh" + namespace blender::nodes { using bke::GeometryInstanceGroup; -/** - * Update the availability of a group of input sockets with the same name, - * used for switching between attribute inputs or single values. - * - * \param mode: Controls which socket of the group to make available. - * \param name_is_available: If false, make all sockets with this name unavailable. - */ -void update_attribute_input_socket_availabilities(bNode &node, +void update_attribute_input_socket_availabilities(bNodeTree &ntree, + bNode &node, const StringRef name, const GeometryNodeAttributeInputMode mode, const bool name_is_available) @@ -50,11 +46,36 @@ void update_attribute_input_socket_availabilities(bNode &node, (socket->type == SOCK_INT && mode_ == GEO_NODE_ATTRIBUTE_INPUT_INTEGER) || (socket->type == SOCK_VECTOR && mode_ == GEO_NODE_ATTRIBUTE_INPUT_VECTOR) || (socket->type == SOCK_RGBA && mode_ == GEO_NODE_ATTRIBUTE_INPUT_COLOR)); - nodeSetSocketAvailability(socket, socket_is_available); + nodeSetSocketAvailability(&ntree, socket, socket_is_available); } } } +std::optional<CustomDataType> node_data_type_to_custom_data_type(const eNodeSocketDatatype type) +{ + switch (type) { + case SOCK_FLOAT: + return CD_PROP_FLOAT; + case SOCK_VECTOR: + return CD_PROP_FLOAT3; + case SOCK_RGBA: + return CD_PROP_COLOR; + case SOCK_BOOLEAN: + return CD_PROP_BOOL; + case SOCK_INT: + return CD_PROP_INT32; + case SOCK_STRING: + return CD_PROP_STRING; + default: + return {}; + } +} + +std::optional<CustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket) +{ + return node_data_type_to_custom_data_type(static_cast<eNodeSocketDatatype>(socket.type)); +} + } // namespace blender::nodes bool geo_node_poll_default(bNodeType *UNUSED(ntype), @@ -62,16 +83,16 @@ bool geo_node_poll_default(bNodeType *UNUSED(ntype), const char **r_disabled_hint) { if (!STREQ(ntree->idname, "GeometryNodeTree")) { - *r_disabled_hint = "Not a geometry node tree"; + *r_disabled_hint = TIP_("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) +void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass) { - node_type_base(ntype, type, name, nclass, flag); + node_type_base(ntype, type, name, nclass); ntype->poll = geo_node_poll_default; - ntype->update_internal_links = node_update_internal_links_default; ntype->insert_link = node_insert_link_default; + ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node; } diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 015ac0de002..dddc3527124 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -18,7 +18,7 @@ #include <string.h> -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BLI_utildefines.h" #include "MEM_guardedalloc.h" @@ -32,41 +32,59 @@ #include "NOD_geometry.h" #include "NOD_geometry_exec.hh" #include "NOD_socket_declarations.hh" +#include "NOD_socket_declarations_geometry.hh" #include "node_util.h" -void geo_node_type_base( - struct bNodeType *ntype, int type, const char *name, short nclass, short flag); +void geo_node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass); 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, +/** + * Update the availability of a group of input sockets with the same name, + * used for switching between attribute inputs or single values. + * + * \param mode: Controls which socket of the group to make available. + * \param name_is_available: If false, make all sockets with this name unavailable. + */ +void update_attribute_input_socket_availabilities(bNodeTree &ntree, + bNode &node, const StringRef name, - const GeometryNodeAttributeInputMode mode, - const bool name_is_available = true); + GeometryNodeAttributeInputMode mode, + bool name_is_available = true); Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, - const AttributeDomain domain); + AttributeDomain domain); -void transform_mesh(Mesh *mesh, +void transform_mesh(Mesh &mesh, const float3 translation, const float3 rotation, const float3 scale); -Mesh *create_line_mesh(const float3 start, const float3 delta, const int count); +void transform_geometry_set(GeometrySet &geometry, + const float4x4 &transform, + const Depsgraph &depsgraph); -Mesh *create_grid_mesh(const int verts_x, - const int verts_y, - const float size_x, - const float size_y); +Mesh *create_line_mesh(const float3 start, const float3 delta, int count); -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); +Mesh *create_grid_mesh(int verts_x, int verts_y, float size_x, float size_y); + +struct ConeAttributeOutputs { + StrongAnonymousAttributeID top_id; + StrongAnonymousAttributeID bottom_id; + StrongAnonymousAttributeID side_id; +}; + +Mesh *create_cylinder_or_cone_mesh(float radius_top, + float radius_bottom, + float depth, + int circle_segments, + int side_segments, + int fill_segments, + GeometryNodeMeshCircleFillType fill_type, + ConeAttributeOutputs &attribute_outputs); Mesh *create_cuboid_mesh(float3 size, int verts_x, int verts_y, int verts_z); @@ -76,7 +94,18 @@ Mesh *create_cuboid_mesh(float3 size, int verts_x, int verts_y, int verts_z); void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, GeometryComponent &result_component, Span<bool> masks, - const bool invert); + bool invert); +/** + * Returns the parts of the geometry that are on the selection for the given domain. If the domain + * is not applicable for the component, e.g. face domain for point cloud, nothing happens to that + * component. If no component can work with the domain, then `error_message` is set to true. + */ +void separate_geometry(GeometrySet &geometry_set, + AttributeDomain domain, + GeometryNodeDeleteGeometryMode mode, + const Field<bool> &selection_field, + bool invert, + bool &r_is_error); struct CurveToPointsResults { int result_size; @@ -100,4 +129,7 @@ void curve_create_default_rotation_attribute(Span<float3> tangents, Span<float3> normals, MutableSpan<float3> rotations); +std::optional<CustomDataType> node_data_type_to_custom_data_type(eNodeSocketDatatype type); +std::optional<CustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket); + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_align_rotation_to_vector.cc index d0bb906e8af..1d064586238 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_align_rotation_to_vector.cc @@ -22,27 +22,25 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_align_rotation_to_vector_cc { -static void geo_node_align_rotation_to_vector_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Factor"); - b.add_input<decl::Float>("Factor", "Factor_001") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Factor")); + b.add_input<decl::Float>(N_("Factor"), "Factor_001") .default_value(1.0f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR); - b.add_input<decl::String>("Vector"); - b.add_input<decl::Vector>("Vector", "Vector_001") + b.add_input<decl::String>(N_("Vector")); + b.add_input<decl::Vector>(N_("Vector"), "Vector_001") .default_value({0.0, 0.0, 1.0}) .subtype(PROP_ANGLE); - b.add_output<decl::Geometry>("Geometry"); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_align_rotation_to_vector_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "axis", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); uiLayoutSetPropSep(layout, true); @@ -53,10 +51,10 @@ static void geo_node_align_rotation_to_vector_layout(uiLayout *layout, uiItemR(col, ptr, "input_type_vector", 0, IFACE_("Vector"), ICON_NONE); } -static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); + NodeGeometryAlignRotationToVector *node_storage = MEM_cnew<NodeGeometryAlignRotationToVector>( + __func__); node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; @@ -65,14 +63,14 @@ static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNo node->storage = node_storage; } -static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) node->storage; update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + *ntree, *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); update_attribute_input_socket_availabilities( - *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); + *ntree, *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); } static void align_rotations_auto_pivot(const VArray<float3> &vectors, @@ -92,14 +90,14 @@ static void align_rotations_auto_pivot(const VArray<float3> &vectors, 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); + const float3 new_axis = math::normalize(vector); + float3 rotation_axis = math::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)); + rotation_axis = math::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)); + rotation_axis = math::cross_high_precision(old_axis, float3(0, 1, 0)); } } @@ -179,9 +177,9 @@ static void align_rotations_on_component(GeometryComponent &component, return; } - GVArray_Typed<float> factors = params.get_input_attribute<float>( + VArray<float> factors = params.get_input_attribute<float>( "Factor", component, ATTR_DOMAIN_POINT, 1.0f); - GVArray_Typed<float3> vectors = params.get_input_attribute<float3>( + VArray<float3> vectors = params.get_input_attribute<float3>( "Vector", component, ATTR_DOMAIN_POINT, {0, 0, 1}); float3 local_main_axis{0, 0, 0}; @@ -199,11 +197,11 @@ static void align_rotations_on_component(GeometryComponent &component, rotations.save(); } -static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { align_rotations_on_component(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -219,25 +217,26 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_align_rotation_to_vector_cc void register_node_type_geo_align_rotation_to_vector() { + namespace file_ns = blender::nodes::node_geo_legacy_align_rotation_to_vector_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, "Align Rotation to Vector", - NODE_CLASS_GEOMETRY, - 0); - node_type_init(&ntype, blender::nodes::geo_node_align_rotation_to_vector_init); - node_type_update(&ntype, blender::nodes::geo_node_align_rotation_to_vector_update); + NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage(&ntype, "NodeGeometryAlignRotationToVector", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_align_rotation_to_vector_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_align_rotation_to_vector_exec; - ntype.draw_buttons = blender::nodes::geo_node_align_rotation_to_vector_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_clamp.cc index 2e931a2da98..cac2a90a76c 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_clamp.cc @@ -20,40 +20,39 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_clamp_cc { -static void geo_node_attribute_clamp_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::String>("Result"); - b.add_input<decl::Vector>("Min"); - b.add_input<decl::Vector>("Max").default_value({1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>("Min", "Min_001"); - b.add_input<decl::Float>("Max", "Max_001").default_value(1.0f); - b.add_input<decl::Int>("Min", "Min_002").min(-100000).max(100000); - b.add_input<decl::Int>("Max", "Max_002").default_value(100).min(-100000).max(100000); - b.add_input<decl::Color>("Min", "Min_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); - b.add_input<decl::Color>("Max", "Max_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::String>(N_("Result")); + b.add_input<decl::Vector>(N_("Min")); + b.add_input<decl::Vector>(N_("Max")).default_value({1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Min"), "Min_001"); + b.add_input<decl::Float>(N_("Max"), "Max_001").default_value(1.0f); + b.add_input<decl::Int>(N_("Min"), "Min_002").min(-100000).max(100000); + b.add_input<decl::Int>(N_("Max"), "Max_002").default_value(100).min(-100000).max(100000); + b.add_input<decl::Color>(N_("Min"), "Min_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); + b.add_input<decl::Color>(N_("Max"), "Max_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_clamp_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_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) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeClamp *data = (NodeAttributeClamp *)MEM_callocN(sizeof(NodeAttributeClamp), - __func__); + NodeAttributeClamp *data = MEM_cnew<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) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 3); bNodeSocket *sock_max_vector = sock_min_vector->next; @@ -66,14 +65,14 @@ static void geo_node_attribute_clamp_update(bNodeTree *UNUSED(ntree), bNode *nod 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); + nodeSetSocketAvailability(ntree, sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, sock_max_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, sock_min_color, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, sock_max_color, data_type == CD_PROP_COLOR); } template<typename T> T clamp_value(const T val, const T min, const T max); @@ -156,7 +155,7 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam 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( + GVArray attribute_input = component.attribute_try_get_for_read( attribute_name, domain, data_type); OutputAttribute attribute_result = component.attribute_try_get_for_output_only( @@ -185,7 +184,7 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam } } MutableSpan<float3> results = attribute_result.as_span<float3>(); - clamp_attribute<float3>(attribute_input->typed<float3>(), results, min, max); + clamp_attribute<float3>(attribute_input.typed<float3>(), results, min, max); break; } case CD_PROP_FLOAT: { @@ -193,10 +192,10 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam 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); + clamp_attribute<float>(attribute_input.typed<float>(), results, max, min); } else { - clamp_attribute<float>(attribute_input->typed<float>(), results, min, max); + clamp_attribute<float>(attribute_input.typed<float>(), results, min, max); } break; } @@ -205,10 +204,10 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam 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); + clamp_attribute<int>(attribute_input.typed<int>(), results, max, min); } else { - clamp_attribute<int>(attribute_input->typed<int>(), results, min, max); + clamp_attribute<int>(attribute_input.typed<int>(), results, min, max); } break; } @@ -231,7 +230,7 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam } MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); clamp_attribute<ColorGeometry4f>( - attribute_input->typed<ColorGeometry4f>(), results, min, max); + attribute_input.typed<ColorGeometry4f>(), results, min, max); break; } default: { @@ -243,11 +242,11 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam attribute_result.save(); } -static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { clamp_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -262,19 +261,21 @@ static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_clamp_cc void register_node_type_geo_attribute_clamp() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_clamp_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_clamp_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_clamp_update); - ntype.declare = blender::nodes::geo_node_attribute_clamp_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_clamp_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_clamp_layout; + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeAttributeClamp", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_color_ramp.cc index aa054af3acd..ec57422a531 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_color_ramp.cc @@ -23,27 +23,24 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_color_ramp_cc { -static void geo_node_attribute_color_ramp_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_color_ramp_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiTemplateColorRamp(layout, ptr, "color_ramp", false); } -static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( - sizeof(NodeAttributeColorRamp), __func__); + NodeAttributeColorRamp *node_storage = MEM_cnew<NodeAttributeColorRamp>(__func__); BKE_colorband_init(&node_storage->color_ramp, true); node->storage = node_storage; } @@ -85,7 +82,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon return; } - GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( + VArray<float> attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, 0.0f); MutableSpan<ColorGeometry4f> results = attribute_result.as_span(); @@ -100,11 +97,11 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon attribute_result.save(); } -static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); @@ -119,23 +116,22 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_color_ramp_cc void register_node_type_geo_attribute_color_ramp() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_color_ramp_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, - GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, - "Attribute Color Ramp", - NODE_CLASS_ATTRIBUTE, - 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, "Attribute Color Ramp", NODE_CLASS_ATTRIBUTE); node_type_storage( &ntype, "NodeAttributeColorRamp", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_attribute_color_ramp_init); + node_type_init(&ntype, file_ns::node_init); node_type_size_preset(&ntype, NODE_SIZE_LARGE); - ntype.declare = blender::nodes::geo_node_attribute_color_ramp_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_color_ramp_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_color_ramp_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_combine_xyz.cc index 569d5a824ca..403b9446f75 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_combine_xyz.cc @@ -19,24 +19,22 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_combine_xyz_cc { -static void geo_node_attribute_combine_xyz_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("X"); - b.add_input<decl::Float>("X", "X_001"); - b.add_input<decl::String>("Y"); - b.add_input<decl::Float>("Y", "Y_001"); - b.add_input<decl::String>("Z"); - b.add_input<decl::Float>("Z", "Z_001"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("X")); + b.add_input<decl::Float>(N_("X"), "X_001"); + b.add_input<decl::String>(N_("Y")); + b.add_input<decl::Float>(N_("Y"), "Y_001"); + b.add_input<decl::String>(N_("Z")); + b.add_input<decl::Float>(N_("Z"), "Z_001"); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_combine_xyz_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -46,10 +44,9 @@ static void geo_node_attribute_combine_xyz_layout(uiLayout *layout, uiItemR(col, ptr, "input_type_z", 0, IFACE_("Z"), ICON_NONE); } -static void geo_node_attribute_combine_xyz_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeCombineXYZ *data = (NodeAttributeCombineXYZ *)MEM_callocN( - sizeof(NodeAttributeCombineXYZ), __func__); + NodeAttributeCombineXYZ *data = MEM_cnew<NodeAttributeCombineXYZ>(__func__); data->input_type_x = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; data->input_type_y = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; @@ -57,15 +54,15 @@ static void geo_node_attribute_combine_xyz_init(bNodeTree *UNUSED(tree), bNode * node->storage = data; } -static void geo_node_attribute_combine_xyz_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeCombineXYZ *node_storage = (NodeAttributeCombineXYZ *)node->storage; update_attribute_input_socket_availabilities( - *node, "X", (GeometryNodeAttributeInputMode)node_storage->input_type_x); + *ntree, *node, "X", (GeometryNodeAttributeInputMode)node_storage->input_type_x); update_attribute_input_socket_availabilities( - *node, "Y", (GeometryNodeAttributeInputMode)node_storage->input_type_y); + *ntree, *node, "Y", (GeometryNodeAttributeInputMode)node_storage->input_type_y); update_attribute_input_socket_availabilities( - *node, "Z", (GeometryNodeAttributeInputMode)node_storage->input_type_z); + *ntree, *node, "Z", (GeometryNodeAttributeInputMode)node_storage->input_type_z); } static AttributeDomain get_result_domain(const GeometryComponent &component, @@ -95,11 +92,11 @@ static void combine_attributes(GeometryComponent &component, const GeoNodeExecPa if (!attribute_result) { return; } - GVArray_Typed<float> attribute_x = params.get_input_attribute<float>( + VArray<float> attribute_x = params.get_input_attribute<float>( "X", component, result_domain, 0.0f); - GVArray_Typed<float> attribute_y = params.get_input_attribute<float>( + VArray<float> attribute_y = params.get_input_attribute<float>( "Y", component, result_domain, 0.0f); - GVArray_Typed<float> attribute_z = params.get_input_attribute<float>( + VArray<float> attribute_z = params.get_input_attribute<float>( "Z", component, result_domain, 0.0f); for (const int i : IndexRange(attribute_result->size())) { @@ -111,11 +108,11 @@ static void combine_attributes(GeometryComponent &component, const GeoNodeExecPa attribute_result.save(); } -static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { combine_attributes(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -130,24 +127,25 @@ static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_combine_xyz_cc void register_node_type_geo_attribute_combine_xyz() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_combine_xyz_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, "Attribute Combine XYZ", - NODE_CLASS_ATTRIBUTE, - 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_combine_xyz_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_combine_xyz_update); + NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeAttributeCombineXYZ", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_attribute_combine_xyz_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_combine_xyz_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_combine_xyz_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_compare.cc index 0b9708dae14..6cec73d76a2 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_compare.cc @@ -21,27 +21,25 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_compare_cc { -static void geo_node_attribute_compare_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("A"); - b.add_input<decl::Float>("A", "A_001"); - b.add_input<decl::Vector>("A", "A_002"); - b.add_input<decl::Color>("A", "A_003").default_value({0.5, 0.5, 0.5, 1.0}); - b.add_input<decl::String>("B"); - b.add_input<decl::Float>("B", "B_001"); - b.add_input<decl::Vector>("B", "B_002"); - b.add_input<decl::Color>("B", "B_003").default_value({0.5, 0.5, 0.5, 1.0}); - b.add_input<decl::Float>("Threshold").default_value(0.01f).min(0.0f); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("A")); + b.add_input<decl::Float>(N_("A"), "A_001"); + b.add_input<decl::Vector>(N_("A"), "A_002"); + b.add_input<decl::Color>(N_("A"), "A_003").default_value({0.5, 0.5, 0.5, 1.0}); + b.add_input<decl::String>(N_("B")); + b.add_input<decl::Float>(N_("B"), "B_001"); + b.add_input<decl::Vector>(N_("B"), "B_002"); + b.add_input<decl::Color>(N_("B"), "B_003").default_value({0.5, 0.5, 0.5, 1.0}); + b.add_input<decl::Float>(N_("Threshold")).default_value(0.01f).min(0.0f); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_compare_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); uiLayoutSetPropSep(layout, true); @@ -50,11 +48,10 @@ static void geo_node_attribute_compare_layout(uiLayout *layout, uiItemR(layout, ptr, "input_type_b", 0, IFACE_("B"), ICON_NONE); } -static void geo_node_attribute_compare_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeCompare *data = (NodeAttributeCompare *)MEM_callocN(sizeof(NodeAttributeCompare), - __func__); - data->operation = NODE_FLOAT_COMPARE_GREATER_THAN; + NodeAttributeCompare *data = MEM_cnew<NodeAttributeCompare>(__func__); + data->operation = NODE_COMPARE_GREATER_THAN; data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; node->storage = data; @@ -62,24 +59,24 @@ static void geo_node_attribute_compare_init(bNodeTree *UNUSED(tree), bNode *node static bool operation_tests_equality(const NodeAttributeCompare &node_storage) { - return ELEM(node_storage.operation, NODE_FLOAT_COMPARE_EQUAL, NODE_FLOAT_COMPARE_NOT_EQUAL); + return ELEM(node_storage.operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL); } -static void geo_node_attribute_compare_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node->storage; update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + *ntree, *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); update_attribute_input_socket_availabilities( - *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); + *ntree, *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); bNodeSocket *socket_threshold = (bNodeSocket *)BLI_findlink(&node->inputs, 9); - nodeSetSocketAvailability(socket_threshold, operation_tests_equality(*node_storage)); + nodeSetSocketAvailability(ntree, socket_threshold, operation_tests_equality(*node_storage)); } static void do_math_operation(const VArray<float> &input_a, const VArray<float> &input_b, - const FloatCompareOperation operation, + const NodeCompareOperation operation, MutableSpan<bool> span_result) { const int size = input_a.size(); @@ -243,7 +240,7 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx { const bNode &node = params.node(); NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node.storage; - const FloatCompareOperation operation = static_cast<FloatCompareOperation>( + const NodeCompareOperation operation = static_cast<NodeCompareOperation>( node_storage->operation); const std::string result_name = params.get_input<std::string>("Result"); @@ -257,9 +254,9 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx const CustomDataType input_data_type = get_data_type(component, params, *node_storage); - GVArrayPtr attribute_a = params.get_input_attribute( + GVArray attribute_a = params.get_input_attribute( "A", component, result_domain, input_data_type, nullptr); - GVArrayPtr attribute_b = params.get_input_attribute( + GVArray attribute_b = params.get_input_attribute( "B", component, result_domain, input_data_type, nullptr); if (!attribute_a || !attribute_b) { @@ -273,60 +270,60 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx * conversions and float comparison. In other words, the comparison is not element-wise. */ if (operation_tests_equality(*node_storage)) { const float threshold = params.get_input<float>("Threshold"); - if (operation == NODE_FLOAT_COMPARE_EQUAL) { + if (operation == NODE_COMPARE_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { do_equal_operation_float( - attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); + 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->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); + 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->typed<ColorGeometry4f>(), - attribute_b->typed<ColorGeometry4f>(), + 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->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); + attribute_a.typed<bool>(), attribute_b.typed<bool>(), threshold, result_span); } } - else if (operation == NODE_FLOAT_COMPARE_NOT_EQUAL) { + else if (operation == NODE_COMPARE_NOT_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { do_not_equal_operation_float( - attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); + 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->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); + 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->typed<ColorGeometry4f>(), - attribute_b->typed<ColorGeometry4f>(), + 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->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); + attribute_a.typed<bool>(), attribute_b.typed<bool>(), threshold, result_span); } } } else { do_math_operation( - attribute_a->typed<float>(), attribute_b->typed<float>(), operation, result_span); + attribute_a.typed<float>(), attribute_b.typed<float>(), operation, result_span); } attribute_result.save(); } -static void geo_node_attribute_compare_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { attribute_compare_calc(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -341,20 +338,22 @@ static void geo_node_attribute_compare_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_compare_cc void register_node_type_geo_attribute_compare() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_compare_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); - ntype.declare = blender::nodes::geo_node_attribute_compare_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_compare_layout; - node_type_update(&ntype, blender::nodes::geo_node_attribute_compare_update); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeAttributeCompare", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_attribute_compare_init); + node_type_init(&ntype, file_ns::node_init); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_convert.cc index a2382aa9d25..2b13f57e990 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_convert.cc @@ -19,19 +19,17 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_convert_cc { -static void geo_node_attribute_convert_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_convert_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -39,10 +37,9 @@ static void geo_node_attribute_convert_layout(uiLayout *layout, uiItemR(layout, ptr, "data_type", 0, IFACE_("Type"), ICON_NONE); } -static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeConvert *data = (NodeAttributeConvert *)MEM_callocN(sizeof(NodeAttributeConvert), - __func__); + NodeAttributeConvert *data = MEM_cnew<NodeAttributeConvert>(__func__); data->data_type = CD_AUTO_FROM_NAME; data->domain = ATTR_DOMAIN_AUTO; @@ -104,7 +101,7 @@ static void attribute_convert_calc(GeometryComponent &component, return; } - GVArrayPtr source_attribute = component.attribute_try_get_for_read( + GVArray source_attribute = component.attribute_try_get_for_read( source_name, result_domain, result_type); if (!source_attribute) { params.error_message_add(NodeWarningType::Error, @@ -118,7 +115,7 @@ static void attribute_convert_calc(GeometryComponent &component, return; } - GVArray_GSpan source_span{*source_attribute}; + GVArray_GSpan source_span{source_attribute}; GMutableSpan result_span = result_attribute.as_span(); BLI_assert(source_span.size() == result_span.size()); @@ -130,11 +127,11 @@ static void attribute_convert_calc(GeometryComponent &component, result_attribute.save(); } -static void geo_node_attribute_convert_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); const std::string result_name = params.extract_input<std::string>("Result"); const std::string source_name = params.extract_input<std::string>("Attribute"); @@ -143,7 +140,7 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params) const AttributeDomain domain = static_cast<AttributeDomain>(node_storage.domain); if (result_name.empty()) { - params.set_output("Geometry", geometry_set); + params.set_default_remaining_outputs(); return; } @@ -175,18 +172,20 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_convert_cc void register_node_type_geo_attribute_convert() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_convert_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE, 0); - ntype.declare = blender::nodes::geo_node_attribute_convert_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_convert_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_convert_layout; - node_type_init(&ntype, blender::nodes::geo_node_attribute_convert_init); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CONVERT, "Attribute Convert", NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeAttributeConvert", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_curve_map.cc index b9621b4ae92..8e1e763f1ad 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_curve_map.cc @@ -24,19 +24,17 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_curve_map_cc { -static void geo_node_attribute_curve_map_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_curve_map_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); bNode *node = (bNode *)ptr->data; @@ -54,7 +52,7 @@ static void geo_node_attribute_curve_map_layout(uiLayout *layout, } } -static void geo_node_attribute_curve_map_free_storage(bNode *node) +static void node_free_storage(bNode *node) { if (node->storage) { NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; @@ -64,9 +62,9 @@ static void geo_node_attribute_curve_map_free_storage(bNode *node) } } -static void geo_node_attribute_curve_map_copy_storage(bNodeTree *UNUSED(dest_ntree), - bNode *dest_node, - const bNode *src_node) +static void node_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; @@ -75,10 +73,9 @@ static void geo_node_attribute_curve_map_copy_storage(bNodeTree *UNUSED(dest_ntr dest_data->curve_rgb = BKE_curvemapping_copy(src_data->curve_rgb); } -static void geo_node_attribute_curve_map_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)MEM_callocN(sizeof(NodeAttributeCurveMap), - __func__); + NodeAttributeCurveMap *data = MEM_cnew<NodeAttributeCurveMap>(__func__); data->data_type = CD_PROP_FLOAT; data->curve_vec = BKE_curvemapping_add(4, -1.0f, -1.0f, 1.0f, 1.0f); @@ -87,7 +84,7 @@ static void geo_node_attribute_curve_map_init(bNodeTree *UNUSED(ntree), bNode *n node->storage = data; } -static void geo_node_attribute_curve_map_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *UNUSED(ntree), bNode *node) { /* Set the active curve when data type is changed. */ NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; @@ -136,10 +133,10 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon 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>( + VArray<float> attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, float(0.0f)); MutableSpan<float> results = attribute_result.as_span<float>(); - threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(attribute_in.index_range(), 512, [&](IndexRange range) { for (const int i : range) { results[i] = BKE_curvemapping_evaluateF(cumap, 3, attribute_in[i]); } @@ -148,10 +145,10 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon } case CD_PROP_FLOAT3: { const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; - GVArray_Typed<float3> attribute_in = component.attribute_get_for_read<float3>( + VArray<float3> attribute_in = component.attribute_get_for_read<float3>( input_name, result_domain, float3(0.0f)); MutableSpan<float3> results = attribute_result.as_span<float3>(); - threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(attribute_in.index_range(), 512, [&](IndexRange range) { for (const int i : range) { BKE_curvemapping_evaluate3F(cumap, results[i], attribute_in[i]); } @@ -160,11 +157,10 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon } 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)); + VArray<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>(); - threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(attribute_in.index_range(), 512, [&](IndexRange range) { for (const int i : range) { BKE_curvemapping_evaluateRGBF(cumap, results[i], attribute_in[i]); } @@ -180,7 +176,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon attribute_result.save(); } -static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const bNode &bnode = params.node(); NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)bnode.storage; @@ -189,7 +185,7 @@ static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); @@ -204,23 +200,23 @@ static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_curve_map_cc void register_node_type_geo_attribute_curve_map() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_curve_map_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); - node_type_update(&ntype, blender::nodes::geo_node_attribute_curve_map_update); - node_type_init(&ntype, blender::nodes::geo_node_attribute_curve_map_init); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE); + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_init); node_type_size_preset(&ntype, NODE_SIZE_LARGE); - node_type_storage(&ntype, - "NodeAttributeCurveMap", - blender::nodes::geo_node_attribute_curve_map_free_storage, - blender::nodes::geo_node_attribute_curve_map_copy_storage); - ntype.declare = blender::nodes::geo_node_attribute_curve_map_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_curve_map_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_curve_map_layout; + node_type_storage( + &ntype, "NodeAttributeCurveMap", file_ns::node_free_storage, file_ns::node_copy_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_fill.cc index 3c50ae5c837..a32e3b7412f 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_fill.cc @@ -19,21 +19,21 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_fill_cc { -static void geo_node_attribute_fill_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::Vector>("Value", "Value"); - b.add_input<decl::Float>("Value", "Value_001"); - b.add_input<decl::Color>("Value", "Value_002"); - b.add_input<decl::Bool>("Value", "Value_003"); - b.add_input<decl::Int>("Value", "Value_004"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")).is_attribute_name(); + b.add_input<decl::Vector>(N_("Value"), "Value"); + b.add_input<decl::Float>(N_("Value"), "Value_001"); + b.add_input<decl::Color>(N_("Value"), "Value_002"); + b.add_input<decl::Bool>(N_("Value"), "Value_003"); + b.add_input<decl::Int>(N_("Value"), "Value_004"); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_fill_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -41,13 +41,13 @@ static void geo_node_attribute_fill_layout(uiLayout *layout, bContext *UNUSED(C) uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); } -static void geo_node_attribute_fill_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { node->custom1 = CD_PROP_FLOAT; node->custom2 = ATTR_DOMAIN_AUTO; } -static void geo_node_attribute_fill_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *socket_value_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *socket_value_float = socket_value_vector->next; @@ -57,11 +57,11 @@ static void geo_node_attribute_fill_update(bNodeTree *UNUSED(ntree), bNode *node const CustomDataType data_type = static_cast<CustomDataType>(node->custom1); - nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR); - nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL); - nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, socket_value_int32, data_type == CD_PROP_INT32); } static AttributeDomain get_result_domain(const GeometryComponent &component, const StringRef name) @@ -127,11 +127,11 @@ static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams attribute.save(); } -static void geo_node_attribute_fill_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { fill_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -146,18 +146,20 @@ static void geo_node_attribute_fill_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_fill_cc void register_node_type_geo_attribute_fill() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_fill_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_fill_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_fill_update); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_fill_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_fill_layout; - ntype.declare = blender::nodes::geo_node_attribute_fill_declare; + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_map_range.cc index 0ea3bbe1e45..398af087499 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_map_range.cc @@ -22,25 +22,25 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_map_range_cc { -static void geo_node_attribute_map_range_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::String>("Result"); - b.add_input<decl::Float>("From Min"); - b.add_input<decl::Float>("From Max").default_value(1.0f); - b.add_input<decl::Float>("To Min"); - b.add_input<decl::Float>("To Max").default_value(1.0f); - b.add_input<decl::Float>("Steps").default_value(4.0f); - b.add_input<decl::Vector>("From Min", "From Min_001"); - b.add_input<decl::Vector>("From Max", "From Max_001").default_value({1.0f, 1.0f, 1.0f}); - b.add_input<decl::Vector>("To Min", "To Min_001"); - b.add_input<decl::Vector>("To Max", "To Max_001").default_value({1.0f, 1.0f, 1.0f}); - b.add_input<decl::Vector>("Steps", "Steps_001").default_value({4.0f, 4.0f, 4.0f}); - b.add_input<decl::Bool>("Clamp"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::String>(N_("Result")); + b.add_input<decl::Float>(N_("From Min")); + b.add_input<decl::Float>(N_("From Max")).default_value(1.0f); + b.add_input<decl::Float>(N_("To Min")); + b.add_input<decl::Float>(N_("To Max")).default_value(1.0f); + b.add_input<decl::Float>(N_("Steps")).default_value(4.0f); + b.add_input<decl::Vector>(N_("From Min"), "From Min_001"); + b.add_input<decl::Vector>(N_("From Max"), "From Max_001").default_value({1.0f, 1.0f, 1.0f}); + b.add_input<decl::Vector>(N_("To Min"), "To Min_001"); + b.add_input<decl::Vector>(N_("To Max"), "To Max_001").default_value({1.0f, 1.0f, 1.0f}); + b.add_input<decl::Vector>(N_("Steps"), "Steps_001").default_value({4.0f, 4.0f, 4.0f}); + b.add_input<decl::Bool>(N_("Clamp")); + b.add_output<decl::Geometry>(N_("Geometry")); } static void fn_attribute_map_range_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) @@ -49,16 +49,15 @@ static void fn_attribute_map_range_layout(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "interpolation_type", 0, "", ICON_NONE); } -static void geo_node_attribute_map_range_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAttributeMapRange *data = (NodeAttributeMapRange *)MEM_callocN(sizeof(NodeAttributeMapRange), - __func__); + NodeAttributeMapRange *data = MEM_cnew<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) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeMapRange &node_storage = *(NodeAttributeMapRange *)node->storage; @@ -78,23 +77,26 @@ static void geo_node_attribute_map_range_update(bNodeTree *UNUSED(ntree), bNode const CustomDataType data_type = static_cast<CustomDataType>(node_storage.data_type); - nodeSetSocketAvailability(sock_clamp, + nodeSetSocketAvailability(ntree, + 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, + nodeSetSocketAvailability(ntree, sock_from_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_from_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_to_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_to_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, + 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, + nodeSetSocketAvailability(ntree, sock_from_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_from_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_to_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_to_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, + sock_steps_vector, data_type == CD_PROP_FLOAT3 && node_storage.interpolation_type == NODE_MAP_RANGE_STEPPED); } @@ -362,7 +364,7 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP 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); + GVArray attribute_input = component.attribute_try_get_for_read(input_name, domain, data_type); if (!attribute_input) { params.error_message_add(NodeWarningType::Error, @@ -381,12 +383,12 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP switch (data_type) { case CD_PROP_FLOAT: { - map_range_float(attribute_input->typed<float>(), attribute_result.as_span<float>(), params); + 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); + attribute_input.typed<float3>(), attribute_result.as_span<float3>(), params); break; } default: @@ -396,7 +398,7 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP attribute_result.save(); } -static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -413,20 +415,22 @@ static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_map_range_cc void register_node_type_geo_attribute_map_range() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_map_range_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_map_range_exec; - node_type_init(&ntype, blender::nodes::geo_node_attribute_map_range_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_map_range_update); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, "Attribute Map Range", NODE_CLASS_ATTRIBUTE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeAttributeMapRange", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_attribute_map_range_declare; - ntype.draw_buttons = blender::nodes::fn_attribute_map_range_layout; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::fn_attribute_map_range_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_math.cc index efa09215b45..3257d2d7358 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_math.cc @@ -25,19 +25,19 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_math_cc { -static void geo_node_attribute_math_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("A"); - b.add_input<decl::Float>("A", "A_001"); - b.add_input<decl::String>("B"); - b.add_input<decl::Float>("B", "B_001"); - b.add_input<decl::String>("C"); - b.add_input<decl::Float>("C", "C_001"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("A")); + b.add_input<decl::Float>(N_("A"), "A_001"); + b.add_input<decl::String>(N_("B")); + b.add_input<decl::Float>(N_("B"), "B_001"); + b.add_input<decl::String>(N_("C")); + b.add_input<decl::Float>(N_("C"), "C_001"); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } static bool operation_use_input_c(const NodeMathOperation operation) @@ -100,7 +100,7 @@ static bool operation_use_input_b(const NodeMathOperation operation) return false; } -static void geo_node_attribute_math_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; NodeAttributeMath *node_storage = (NodeAttributeMath *)node->storage; @@ -119,9 +119,9 @@ static void geo_node_attribute_math_layout(uiLayout *layout, bContext *UNUSED(C) } } -static void geo_node_attribute_math_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeMath *data = (NodeAttributeMath *)MEM_callocN(sizeof(NodeAttributeMath), __func__); + NodeAttributeMath *data = MEM_cnew<NodeAttributeMath>(__func__); data->operation = NODE_MATH_ADD; data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; @@ -130,7 +130,10 @@ static void geo_node_attribute_math_init(bNodeTree *UNUSED(tree), bNode *node) node->storage = data; } -static void geo_node_math_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen) +static void geo_node_math_label(const bNodeTree *UNUSED(ntree), + const bNode *node, + char *label, + int maxlen) { NodeAttributeMath &node_storage = *(NodeAttributeMath *)node->storage; const char *name; @@ -141,19 +144,21 @@ static void geo_node_math_label(bNodeTree *UNUSED(ntree), bNode *node, char *lab BLI_strncpy(label, IFACE_(name), maxlen); } -static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeMath &node_storage = *(NodeAttributeMath *)node->storage; NodeMathOperation operation = static_cast<NodeMathOperation>(node_storage.operation); update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage.input_type_a); + *ntree, *node, "A", (GeometryNodeAttributeInputMode)node_storage.input_type_a); update_attribute_input_socket_availabilities( + *ntree, *node, "B", (GeometryNodeAttributeInputMode)node_storage.input_type_b, operation_use_input_b(operation)); update_attribute_input_socket_availabilities( + *ntree, *node, "C", (GeometryNodeAttributeInputMode)node_storage.input_type_c, @@ -250,7 +255,7 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP return; } - GVArray_Typed<float> attribute_a = params.get_input_attribute<float>( + VArray<float> attribute_a = params.get_input_attribute<float>( "A", component, result_domain, 0.0f); MutableSpan<float> result_span = attribute_result.as_span(); @@ -258,10 +263,10 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP /* 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)) { - GVArray_Typed<float> attribute_b = params.get_input_attribute<float>( + VArray<float> attribute_b = params.get_input_attribute<float>( "B", component, result_domain, 0.0f); if (operation_use_input_c(operation)) { - GVArray_Typed<float> attribute_c = params.get_input_attribute<float>( + VArray<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); } @@ -276,11 +281,11 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP attribute_result.save(); } -static void geo_node_attribute_math_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { attribute_math_calc(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -295,20 +300,22 @@ static void geo_node_attribute_math_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_math_cc void register_node_type_geo_attribute_math() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_math_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE, 0); - ntype.declare = blender::nodes::geo_node_attribute_math_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_math_layout; - node_type_label(&ntype, blender::nodes::geo_node_math_label); - node_type_update(&ntype, blender::nodes::geo_node_attribute_math_update); - node_type_init(&ntype, blender::nodes::geo_node_attribute_math_init); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MATH, "Attribute Math", NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.labelfunc = file_ns::geo_node_math_label; + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeAttributeMath", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_mix.cc index 74e05cb997d..c0c30898584 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_mix.cc @@ -25,30 +25,30 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_mix_cc { -static void geo_node_mix_attribute_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Factor"); - b.add_input<decl::Float>("Factor", "Factor_001") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Factor")); + b.add_input<decl::Float>(N_("Factor"), "Factor_001") .default_value(0.5f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR); - b.add_input<decl::String>("A"); - b.add_input<decl::Float>("A", "A_001"); - b.add_input<decl::Vector>("A", "A_002"); - b.add_input<decl::Color>("A", "A_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); - b.add_input<decl::String>("B"); - b.add_input<decl::Float>("B", "B_001"); - b.add_input<decl::Vector>("B", "B_002"); - b.add_input<decl::Color>("B", "B_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::String>(N_("A")); + b.add_input<decl::Float>(N_("A"), "A_001"); + b.add_input<decl::Vector>(N_("A"), "A_002"); + b.add_input<decl::Color>(N_("A"), "A_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); + b.add_input<decl::String>(N_("B")); + b.add_input<decl::Float>(N_("B"), "B_001"); + b.add_input<decl::Vector>(N_("B"), "B_002"); + b.add_input<decl::Color>(N_("B"), "B_003").default_value({0.5f, 0.5f, 0.5f, 1.0f}); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_mix_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -59,10 +59,9 @@ static void geo_node_attribute_mix_layout(uiLayout *layout, bContext *UNUSED(C), uiItemR(col, ptr, "input_type_b", 0, IFACE_("B"), ICON_NONE); } -static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), - "attribute mix node"); + NodeAttributeMix *data = MEM_cnew<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; @@ -70,15 +69,15 @@ static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) node->storage = data; } -static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + *ntree, *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + *ntree, *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); update_attribute_input_socket_availabilities( - *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); + *ntree, *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); } static void do_mix_operation_float(const int blend_mode, @@ -144,25 +143,28 @@ static void do_mix_operation(const CustomDataType result_type, GVMutableArray &attribute_result) { if (result_type == CD_PROP_FLOAT) { + VMutableArray<float> result = attribute_result.typed<float>(); do_mix_operation_float(blend_mode, attribute_factor, attribute_a.typed<float>(), attribute_b.typed<float>(), - attribute_result.typed<float>()); + result); } else if (result_type == CD_PROP_FLOAT3) { + VMutableArray<float3> result = attribute_result.typed<float3>(); do_mix_operation_float3(blend_mode, attribute_factor, attribute_a.typed<float3>(), attribute_b.typed<float3>(), - attribute_result.typed<float3>()); + result); } else if (result_type == CD_PROP_COLOR) { + VMutableArray<ColorGeometry4f> result = attribute_result.typed<ColorGeometry4f>(); do_mix_operation_color4f(blend_mode, attribute_factor, attribute_a.typed<ColorGeometry4f>(), attribute_b.typed<ColorGeometry4f>(), - attribute_result.typed<ColorGeometry4f>()); + result); } } @@ -203,27 +205,27 @@ static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecPa return; } - GVArray_Typed<float> attribute_factor = params.get_input_attribute<float>( + VArray<float> attribute_factor = params.get_input_attribute<float>( "Factor", component, result_domain, 0.5f); - GVArrayPtr attribute_a = params.get_input_attribute( + GVArray attribute_a = params.get_input_attribute( "A", component, result_domain, result_type, nullptr); - GVArrayPtr attribute_b = params.get_input_attribute( + GVArray attribute_b = params.get_input_attribute( "B", component, result_domain, result_type, nullptr); do_mix_operation(result_type, node_storage->blend_type, attribute_factor, - *attribute_a, - *attribute_b, - *attribute_result); + attribute_a, + attribute_b, + attribute_result.varray()); attribute_result.save(); } -static void geo_node_attribute_mix_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { attribute_mix_calc(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -238,19 +240,20 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_mix_cc void register_node_type_geo_attribute_mix() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_mix_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_mix_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_mix_update); - ntype.declare = blender::nodes::geo_node_mix_attribute_declare; - ntype.draw_buttons = blender::nodes::geo_node_attribute_mix_layout; + geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeAttributeMix", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_mix_exec; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_proximity.cc index 6120118f611..20f500b1bd8 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_proximity.cc @@ -26,28 +26,26 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_proximity_cc { -static void geo_node_attribute_proximity_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Geometry>("Target"); - b.add_input<decl::String>("Distance"); - b.add_input<decl::String>("Position"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Geometry>(N_("Target")); + b.add_input<decl::String>(N_("Distance")); + b.add_input<decl::String>(N_("Position")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_proximity_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "target_geometry_element", 0, "", ICON_NONE); } -static void geo_attribute_proximity_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryAttributeProximity *node_storage = (NodeGeometryAttributeProximity *)MEM_callocN( - sizeof(NodeGeometryAttributeProximity), __func__); + NodeGeometryAttributeProximity *node_storage = MEM_cnew<NodeGeometryAttributeProximity>( + __func__); node_storage->target_geometry_element = GEO_NODE_PROXIMITY_TARGET_FACES; node->storage = node_storage; @@ -83,7 +81,7 @@ static void calculate_mesh_proximity(const VArray<float3> &positions, for (int i : range) { /* Use the distance to the last found point as upper bound to speedup the bvh lookup. */ - nearest.dist_sq = float3::distance_squared(nearest.co, positions[i]); + nearest.dist_sq = math::distance_squared(float3(nearest.co), positions[i]); BLI_bvhtree_find_nearest( bvh_data.tree, positions[i], &nearest, bvh_data.nearest_callback, &bvh_data); @@ -153,7 +151,7 @@ static void attribute_calc_proximity(GeometryComponent &component, if (!position_attribute || (!distance_attribute && !location_attribute)) { return; } - GVArray_Typed<float3> positions{*position_attribute.varray}; + VArray<float3> positions = position_attribute.varray.typed<float3>(); const NodeGeometryAttributeProximity &storage = *(const NodeGeometryAttributeProximity *)params.node().storage; @@ -203,16 +201,16 @@ static void attribute_calc_proximity(GeometryComponent &component, } } -static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet geometry_set_target = params.extract_input<GeometrySet>("Target"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); /* This isn't required. This node should be rewritten to handle instances * for the target geometry set. However, the generic BVH API complicates this. */ - geometry_set_target = geometry_set_realize_instances(geometry_set_target); + geometry_set_target = geometry::realize_instances_legacy(geometry_set_target); if (geometry_set.has<MeshComponent>()) { attribute_calc_proximity( @@ -230,22 +228,24 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_proximity_cc void register_node_type_geo_legacy_attribute_proximity() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_proximity_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_attribute_proximity_init); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, "Attribute Proximity", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryAttributeProximity", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_attribute_proximity_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_proximity_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_proximity_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_randomize.cc index 2e6ba456725..92a946b225b 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_randomize.cc @@ -25,39 +25,70 @@ namespace blender::nodes { -static void geo_node_legacy_attribute_randomize_declare(NodeDeclarationBuilder &b) +Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, + const AttributeDomain domain) +{ + const int domain_size = component.attribute_domain_size(domain); + + /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ + GVArray 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.type(); + BLI_assert(cpp_type.is_hashable()); + GVArray_GSpan items{hash_attribute}; + threading::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. */ + RandomNumberGenerator rng(0); + for (const int i : hashes.index_range()) { + hashes[i] = rng.get_uint32(); + } + } + + return hashes; +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_legacy_attribute_randomize_cc { + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute"); - b.add_input<decl::Vector>("Min"); - b.add_input<decl::Vector>("Max").default_value({1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>("Min", "Min_001"); - b.add_input<decl::Float>("Max", "Max_001").default_value(1.0f); - b.add_input<decl::Int>("Min", "Min_002").min(-100000).max(100000); - b.add_input<decl::Int>("Max", "Max_002").default_value(100).min(-100000).max(100000); - b.add_input<decl::Int>("Seed").min(-10000).max(10000); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")); + b.add_input<decl::Vector>(N_("Min")); + b.add_input<decl::Vector>(N_("Max")).default_value({1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Min"), "Min_001"); + b.add_input<decl::Float>(N_("Max"), "Max_001").default_value(1.0f); + b.add_input<decl::Int>(N_("Min"), "Min_002").min(-100000).max(100000); + b.add_input<decl::Int>(N_("Max"), "Max_002").default_value(100).min(-100000).max(100000); + b.add_input<decl::Int>(N_("Seed")).min(-10000).max(10000); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_legacy_attribute_random_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_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_legacy_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( - sizeof(NodeAttributeRandomize), __func__); + NodeAttributeRandomize *data = MEM_cnew<NodeAttributeRandomize>(__func__); data->data_type = CD_PROP_FLOAT; data->domain = ATTR_DOMAIN_POINT; data->operation = GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE; node->storage = data; } -static void geo_node_legacy_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *sock_max_vector = sock_min_vector->next; @@ -68,12 +99,12 @@ static void geo_node_legacy_attribute_randomize_update(bNodeTree *UNUSED(ntree), const NodeAttributeRandomize &storage = *(const NodeAttributeRandomize *)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(ntree, sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, sock_max_int, data_type == CD_PROP_INT32); } template<typename T> @@ -174,36 +205,6 @@ static void randomize_attribute_bool(MutableSpan<bool> span, }); } -Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, - const AttributeDomain domain) -{ - const int domain_size = component.attribute_domain_size(domain); - - /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ - 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->type(); - BLI_assert(cpp_type.is_hashable()); - GVArray_GSpan items{*hash_attribute}; - threading::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. */ - RandomNumberGenerator rng(0); - for (const int i : hashes.index_range()) { - hashes[i] = rng.get_uint32(); - } - } - - return hashes; -} - static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, const StringRef name) @@ -280,12 +281,12 @@ static void randomize_attribute_on_component(GeometryComponent &component, attribute.save(); } -static void geo_node_legacy_random_attribute_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); const std::string attribute_name = params.get_input<std::string>("Attribute"); if (attribute_name.empty()) { - params.set_output("Geometry", geometry_set); + params.set_default_remaining_outputs(); return; } const int seed = params.get_input<int>("Seed"); @@ -294,7 +295,7 @@ static void geo_node_legacy_random_attribute_exec(GeoNodeExecParams params) const GeometryNodeAttributeRandomizeMode operation = static_cast<GeometryNodeAttributeRandomizeMode>(storage.operation); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { randomize_attribute_on_component(geometry_set.get_component_for_write<MeshComponent>(), @@ -324,20 +325,22 @@ static void geo_node_legacy_random_attribute_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_randomize_cc void register_node_type_geo_legacy_attribute_randomize() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_randomize_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_init); - node_type_update(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_update); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); - ntype.declare = blender::nodes::geo_node_legacy_attribute_randomize_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_legacy_random_attribute_exec; - ntype.draw_buttons = blender::nodes::geo_node_legacy_attribute_random_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeAttributeRandomize", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_sample_texture.cc index 52f97475941..ae034d152be 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_sample_texture.cc @@ -28,15 +28,15 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_sample_texture_cc { -static void geo_node_attribute_sample_texture_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Texture>("Texture").hide_label(); - b.add_input<decl::String>("Mapping"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Texture>(N_("Texture")).hide_label(); + b.add_input<decl::String>(N_("Mapping")); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } static AttributeDomain get_result_domain(const GeometryComponent &component, @@ -82,7 +82,7 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec return; } - GVArray_Typed<float3> mapping_attribute = component.attribute_get_for_read<float3>( + VArray<float3> mapping_attribute = component.attribute_get_for_read<float3>( mapping_name, result_domain, {0, 0, 0}); MutableSpan<ColorGeometry4f> colors = attribute_out.as_span(); @@ -100,11 +100,11 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec attribute_out.save(); } -static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -119,19 +119,20 @@ static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_sample_texture_cc void register_node_type_geo_sample_texture() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_sample_texture_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, "Attribute Sample Texture", - NODE_CLASS_ATTRIBUTE, - 0); + NODE_CLASS_ATTRIBUTE); node_type_size_preset(&ntype, NODE_SIZE_LARGE); - ntype.declare = blender::nodes::geo_node_attribute_sample_texture_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_sample_texture_exec; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_separate_xyz.cc index de0090406c6..960ec540556 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_separate_xyz.cc @@ -19,41 +19,38 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_separate_xyz_cc { -static void geo_node_attribute_separate_xyz_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Vector"); - b.add_input<decl::Vector>("Vector", "Vector_001"); - b.add_input<decl::String>("Result X"); - b.add_input<decl::String>("Result Y"); - b.add_input<decl::String>("Result Z"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Vector")); + b.add_input<decl::Vector>(N_("Vector"), "Vector_001"); + b.add_input<decl::String>(N_("Result X")); + b.add_input<decl::String>(N_("Result Y")); + b.add_input<decl::String>(N_("Result Z")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_separate_xyz_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "input_type", 0, IFACE_("Type"), ICON_NONE); } -static void geo_node_attribute_separate_xyz_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeSeparateXYZ *data = (NodeAttributeSeparateXYZ *)MEM_callocN( - sizeof(NodeAttributeSeparateXYZ), __func__); + NodeAttributeSeparateXYZ *data = MEM_cnew<NodeAttributeSeparateXYZ>(__func__); data->input_type = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; node->storage = data; } -static void geo_node_attribute_separate_xyz_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeAttributeSeparateXYZ *node_storage = (NodeAttributeSeparateXYZ *)node->storage; update_attribute_input_socket_availabilities( - *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type); + *ntree, *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type); } static void extract_input(const int index, const Span<float3> &input, MutableSpan<float> result) @@ -106,9 +103,9 @@ static void separate_attribute(GeometryComponent &component, const GeoNodeExecPa const AttributeDomain result_domain = get_result_domain( component, params, result_name_x, result_name_y, result_name_z); - GVArray_Typed<float3> attribute_input = params.get_input_attribute<float3>( + VArray<float3> attribute_input = params.get_input_attribute<float3>( "Vector", component, result_domain, {0, 0, 0}); - VArray_Span<float3> input_span{*attribute_input}; + VArray_Span<float3> input_span{attribute_input}; OutputAttribute_Typed<float> attribute_result_x = component.attribute_try_get_for_output_only<float>(result_name_x, result_domain); @@ -132,11 +129,11 @@ static void separate_attribute(GeometryComponent &component, const GeoNodeExecPa } } -static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { separate_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -151,23 +148,24 @@ static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_separate_xyz_cc void register_node_type_geo_attribute_separate_xyz() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_separate_xyz_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, "Attribute Separate XYZ", - NODE_CLASS_ATTRIBUTE, - 0); - ntype.declare = blender::nodes::geo_node_attribute_separate_xyz_declare; - node_type_init(&ntype, blender::nodes::geo_node_attribute_separate_xyz_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_separate_xyz_update); + NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeAttributeSeparateXYZ", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_separate_xyz_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_separate_xyz_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_transfer.cc index f187ee39b94..a85a7c56cb9 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_transfer.cc @@ -29,20 +29,18 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_transfer_cc { -static void geo_node_attribute_transfer_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Geometry>("Source Geometry"); - b.add_input<decl::String>("Source"); - b.add_input<decl::String>("Destination"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Geometry>(N_("Source Geometry")); + b.add_input<decl::String>(N_("Source")); + b.add_input<decl::String>(N_("Destination")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_transfer_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -50,10 +48,9 @@ static void geo_node_attribute_transfer_layout(uiLayout *layout, uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); } -static void geo_node_attribute_transfer_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryAttributeTransfer *data = (NodeGeometryAttributeTransfer *)MEM_callocN( - sizeof(NodeGeometryAttributeTransfer), __func__); + NodeGeometryAttributeTransfer *data = MEM_cnew<NodeGeometryAttributeTransfer>(__func__); data->domain = ATTR_DOMAIN_AUTO; node->storage = data; } @@ -232,7 +229,7 @@ static void get_closest_mesh_corners(const Mesh &mesh, 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); + const float distance_sq = math::distance_squared(position, float3(mvert.co)); if (distance_sq < min_distance_sq) { min_distance_sq = distance_sq; closest_loop_index = loop_index; @@ -284,7 +281,8 @@ static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_ Array<float3> positions(tot_samples); get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); - bke::mesh_surface_sample::MeshAttributeInterpolator interp(mesh, positions, looptri_indices); + bke::mesh_surface_sample::MeshAttributeInterpolator interp( + mesh, IndexMask(tot_samples), positions, looptri_indices); interp.sample_attribute( src_attribute, dst_attribute, bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED); @@ -404,15 +402,15 @@ static void transfer_attribute_nearest(const GeometrySet &src_geometry, data_type); for (const int i : IndexRange(tot_samples)) { if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { - /* Point-cloud point is closer. */ + /* Point cloud point is closer. */ const int index = pointcloud_indices[i]; - pointcloud_src_attribute.varray->get(index, buffer); + 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); + mesh_src_attribute.varray.get(index, buffer); dst_attribute->set_by_relocate(i, buffer); } } @@ -423,7 +421,7 @@ static void transfer_attribute_nearest(const GeometrySet &src_geometry, src_name, data_type); for (const int i : IndexRange(tot_samples)) { const int index = pointcloud_indices[i]; - src_attribute.varray->get(index, buffer); + src_attribute.varray.get(index, buffer); dst_attribute->set_by_relocate(i, buffer); } } @@ -433,7 +431,7 @@ static void transfer_attribute_nearest(const GeometrySet &src_geometry, data_type); for (const int i : IndexRange(tot_samples)) { const int index = mesh_indices[i]; - src_attribute.varray->get(index, buffer); + src_attribute.varray.get(index, buffer); dst_attribute->set_by_relocate(i, buffer); } } @@ -459,16 +457,16 @@ static void transfer_attribute(const GeoNodeExecParams ¶ms, 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>( + VArray<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: { + case GEO_NODE_LEGACY_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: { + case GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER_NEAREST: { transfer_attribute_nearest( src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); break; @@ -476,7 +474,7 @@ static void transfer_attribute(const GeoNodeExecParams ¶ms, } } -static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet dst_geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet src_geometry_set = params.extract_input<GeometrySet>("Source Geometry"); @@ -484,12 +482,12 @@ static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) 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); + params.set_default_remaining_outputs(); return; } - dst_geometry_set = bke::geometry_set_realize_instances(dst_geometry_set); - src_geometry_set = bke::geometry_set_realize_instances(src_geometry_set); + dst_geometry_set = geometry::realize_instances_legacy(dst_geometry_set); + src_geometry_set = geometry::realize_instances_legacy(src_geometry_set); if (dst_geometry_set.has<MeshComponent>()) { transfer_attribute(params, @@ -509,21 +507,23 @@ static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) params.set_output("Geometry", dst_geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_transfer_cc -void register_node_type_geo_attribute_transfer() +void register_node_type_geo_legacy_attribute_transfer() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_transfer_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryAttributeTransfer", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_attribute_transfer_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_transfer_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_transfer_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_vector_math.cc index 59903050f88..5b3c3c05a6a 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_vector_math.cc @@ -26,21 +26,21 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_vector_math_cc { -static void geo_node_attribute_vector_math_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("A"); - b.add_input<decl::Vector>("A", "A_001"); - b.add_input<decl::String>("B"); - b.add_input<decl::Vector>("B", "B_001"); - b.add_input<decl::Float>("B", "B_002"); - b.add_input<decl::String>("C"); - b.add_input<decl::Vector>("C", "C_001"); - b.add_input<decl::Float>("C", "C_002"); - b.add_input<decl::String>("Result"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("A")); + b.add_input<decl::Vector>(N_("A"), "A_001"); + b.add_input<decl::String>(N_("B")); + b.add_input<decl::Vector>(N_("B"), "B_001"); + b.add_input<decl::Float>(N_("B"), "B_002"); + b.add_input<decl::String>(N_("C")); + b.add_input<decl::Vector>(N_("C"), "C_001"); + b.add_input<decl::Float>(N_("C"), "C_002"); + b.add_input<decl::String>(N_("Result")); + b.add_output<decl::Geometry>(N_("Geometry")); } static bool operation_use_input_b(const NodeVectorMathOperation operation) @@ -66,9 +66,7 @@ static bool operation_use_input_c(const NodeVectorMathOperation operation) NODE_VECTOR_MATH_MULTIPLY_ADD); } -static void geo_node_attribute_vector_math_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; const NodeAttributeVectorMath &node_storage = *(NodeAttributeVectorMath *)node->storage; @@ -103,10 +101,9 @@ static CustomDataType operation_get_read_type_c(const NodeVectorMathOperation op return CD_PROP_FLOAT3; } -static void geo_node_attribute_vector_math_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeAttributeVectorMath *data = (NodeAttributeVectorMath *)MEM_callocN( - sizeof(NodeAttributeVectorMath), __func__); + NodeAttributeVectorMath *data = MEM_cnew<NodeAttributeVectorMath>(__func__); data->operation = NODE_VECTOR_MATH_ADD; data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; @@ -152,8 +149,8 @@ static CustomDataType operation_get_result_type(const NodeVectorMathOperation op return CD_PROP_FLOAT3; } -static void geo_node_vector_math_label(bNodeTree *UNUSED(ntree), - bNode *node, +static void geo_node_vector_math_label(const bNodeTree *UNUSED(ntree), + const bNode *node, char *label, int maxlen) { @@ -166,19 +163,21 @@ static void geo_node_vector_math_label(bNodeTree *UNUSED(ntree), BLI_snprintf(label, maxlen, IFACE_("Vector %s"), IFACE_(name)); } -static void geo_node_attribute_vector_math_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { const NodeAttributeVectorMath *node_storage = (NodeAttributeVectorMath *)node->storage; const NodeVectorMathOperation operation = (const NodeVectorMathOperation)node_storage->operation; update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + *ntree, *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); update_attribute_input_socket_availabilities( + *ntree, *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b, operation_use_input_b(operation)); update_attribute_input_socket_availabilities( + *ntree, *node, "C", (GeometryNodeAttributeInputMode)node_storage->input_type_c, @@ -187,7 +186,7 @@ static void geo_node_attribute_vector_math_update(bNodeTree *UNUSED(ntree), bNod static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, const VArray<float3> &input_b, - VMutableArray<float3> &result, + const VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -218,7 +217,7 @@ static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, 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 VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -251,7 +250,7 @@ static void do_math_operation_fl3_fl3_fl3_to_fl3(const VArray<float3> &input_a, 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 VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -283,7 +282,7 @@ static void do_math_operation_fl3_fl3_fl_to_fl3(const VArray<float3> &input_a, static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, const VArray<float3> &input_b, - VMutableArray<float> &result, + const VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -313,7 +312,7 @@ static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, const VArray<float> &input_b, - VMutableArray<float3> &result, + const VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -342,7 +341,7 @@ static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, } static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, - VMutableArray<float3> &result, + const VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -369,7 +368,7 @@ static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, } static void do_math_operation_fl3_to_fl(const VArray<float3> &input_a, - VMutableArray<float> &result, + const VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); @@ -437,13 +436,13 @@ static void attribute_vector_math_calc(GeometryComponent &component, const AttributeDomain result_domain = get_result_domain( component, params, operation, result_name); - GVArrayPtr attribute_a = params.get_input_attribute( + GVArray attribute_a = params.get_input_attribute( "A", component, result_domain, read_type_a, nullptr); if (!attribute_a) { return; } - GVArrayPtr attribute_b; - GVArrayPtr attribute_c; + GVArray attribute_b; + GVArray attribute_c; if (use_input_b) { attribute_b = params.get_input_attribute("B", component, result_domain, read_type_b, nullptr); if (!attribute_b) { @@ -476,26 +475,26 @@ 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->typed<float3>(), - attribute_b->typed<float3>(), - attribute_result->typed<float3>(), + do_math_operation_fl3_fl3_to_fl3(attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_result.varray().typed<float3>(), operation); break; case NODE_VECTOR_MATH_DOT_PRODUCT: case NODE_VECTOR_MATH_DISTANCE: - do_math_operation_fl3_fl3_to_fl(attribute_a->typed<float3>(), - attribute_b->typed<float3>(), - attribute_result->typed<float>(), + do_math_operation_fl3_fl3_to_fl(attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_result.varray().typed<float>(), operation); break; case NODE_VECTOR_MATH_LENGTH: do_math_operation_fl3_to_fl( - attribute_a->typed<float3>(), attribute_result->typed<float>(), operation); + attribute_a.typed<float3>(), attribute_result.varray().typed<float>(), operation); break; case NODE_VECTOR_MATH_SCALE: - do_math_operation_fl3_fl_to_fl3(attribute_a->typed<float3>(), - attribute_b->typed<float>(), - attribute_result->typed<float3>(), + do_math_operation_fl3_fl_to_fl3(attribute_a.typed<float3>(), + attribute_b.typed<float>(), + attribute_result.varray().typed<float3>(), operation); break; case NODE_VECTOR_MATH_NORMALIZE: @@ -507,33 +506,33 @@ static void attribute_vector_math_calc(GeometryComponent &component, case NODE_VECTOR_MATH_COSINE: case NODE_VECTOR_MATH_TANGENT: do_math_operation_fl3_to_fl3( - attribute_a->typed<float3>(), attribute_result->typed<float3>(), operation); + attribute_a.typed<float3>(), attribute_result.varray().typed<float3>(), operation); break; case NODE_VECTOR_MATH_WRAP: case NODE_VECTOR_MATH_FACEFORWARD: 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>(), + do_math_operation_fl3_fl3_fl3_to_fl3(attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_c.typed<float3>(), + attribute_result.varray().typed<float3>(), operation); break; case NODE_VECTOR_MATH_REFRACT: - do_math_operation_fl3_fl3_fl_to_fl3(attribute_a->typed<float3>(), - attribute_b->typed<float3>(), - attribute_c->typed<float>(), - attribute_result->typed<float3>(), + do_math_operation_fl3_fl3_fl_to_fl3(attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_c.typed<float>(), + attribute_result.varray().typed<float3>(), operation); break; } attribute_result.save(); } -static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { attribute_vector_math_calc(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -549,23 +548,24 @@ static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_vector_math_cc void register_node_type_geo_attribute_vector_math() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_vector_math_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, "Attribute Vector Math", - NODE_CLASS_ATTRIBUTE, - 0); - ntype.declare = blender::nodes::geo_node_attribute_vector_math_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_math_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_vector_math_layout; - node_type_label(&ntype, blender::nodes::geo_node_vector_math_label); - node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_math_update); - node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_math_init); + NODE_CLASS_ATTRIBUTE); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.labelfunc = file_ns::geo_node_vector_math_label; + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeAttributeVectorMath", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_vector_rotate.cc index 0c515fa63fb..3738c4ad14d 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_attribute_vector_rotate.cc @@ -21,30 +21,28 @@ #include "UI_interface.h" #include "UI_resources.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_attribute_vector_rotate_cc { -static void geo_node_attribute_vector_rotate_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Vector"); - b.add_input<decl::Vector>("Vector", "Vector_001").min(0.0f).max(1.0f).hide_value(); - b.add_input<decl::String>("Center"); - b.add_input<decl::Vector>("Center", "Center_001").subtype(PROP_XYZ); - b.add_input<decl::String>("Axis"); - b.add_input<decl::Vector>("Axis", "Axis_001").min(-1.0f).max(1.0f).subtype(PROP_XYZ); - b.add_input<decl::String>("Angle"); - b.add_input<decl::Float>("Angle", "Angle_001").subtype(PROP_ANGLE); - b.add_input<decl::String>("Rotation"); - b.add_input<decl::Vector>("Rotation", "Rotation_001").subtype(PROP_EULER); - b.add_input<decl::Bool>("Invert"); - b.add_input<decl::String>("Result"); - - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Vector")); + b.add_input<decl::Vector>(N_("Vector"), "Vector_001").min(0.0f).max(1.0f).hide_value(); + b.add_input<decl::String>(N_("Center")); + b.add_input<decl::Vector>(N_("Center"), "Center_001").subtype(PROP_XYZ); + b.add_input<decl::String>(N_("Axis")); + b.add_input<decl::Vector>(N_("Axis"), "Axis_001").min(-1.0f).max(1.0f).subtype(PROP_XYZ); + b.add_input<decl::String>(N_("Angle")); + b.add_input<decl::Float>(N_("Angle"), "Angle_001").subtype(PROP_ANGLE); + b.add_input<decl::String>(N_("Rotation")); + b.add_input<decl::Vector>(N_("Rotation"), "Rotation_001").subtype(PROP_EULER); + b.add_input<decl::Bool>(N_("Invert")); + b.add_input<decl::String>(N_("Result")); + + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; const NodeAttributeVectorRotate &node_storage = *(NodeAttributeVectorRotate *)node->storage; @@ -70,27 +68,30 @@ static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, } } -static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *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); + *ntree, *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); update_attribute_input_socket_availabilities( - *node, "Center", (GeometryNodeAttributeInputMode)node_storage->input_type_center); + *ntree, *node, "Center", (GeometryNodeAttributeInputMode)node_storage->input_type_center); update_attribute_input_socket_availabilities( + *ntree, *node, "Axis", (GeometryNodeAttributeInputMode)node_storage->input_type_axis, (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS)); update_attribute_input_socket_availabilities( + *ntree, *node, "Angle", (GeometryNodeAttributeInputMode)node_storage->input_type_angle, (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); update_attribute_input_socket_availabilities( + *ntree, *node, "Rotation", (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, @@ -109,10 +110,9 @@ static float3 vector_rotate_around_axis(const float3 vector, return result + center; } -static void geo_node_attribute_vector_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)MEM_callocN( - sizeof(NodeAttributeVectorRotate), __func__); + NodeAttributeVectorRotate *node_storage = MEM_cnew<NodeAttributeVectorRotate>(__func__); node_storage->mode = GEO_NODE_VECTOR_ROTATE_TYPE_AXIS; node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; @@ -220,12 +220,12 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon 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( + GVArray 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( + GVArray attribute_center = params.get_input_attribute( "Center", component, result_domain, CD_PROP_FLOAT3, nullptr); if (!attribute_center) { return; @@ -238,21 +238,21 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon } if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { - GVArrayPtr attribute_rotation = params.get_input_attribute( + GVArray 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>(), + 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( + GVArray attribute_angle = params.get_input_attribute( "Angle", component, result_domain, CD_PROP_FLOAT, nullptr); if (!attribute_angle) { return; @@ -260,40 +260,40 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon switch (mode) { case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS: { - GVArrayPtr attribute_axis = params.get_input_attribute( + GVArray 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>(), + 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>(), + 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_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>(), + 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_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>(), + 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_angle.typed<float>(), attribute_result.as_span<float3>(), invert); @@ -306,11 +306,11 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon attribute_result.save(); } -static void geo_node_attribute_vector_rotate_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); @@ -325,24 +325,25 @@ static void geo_node_attribute_vector_rotate_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_attribute_vector_rotate_cc void register_node_type_geo_attribute_vector_rotate() { + namespace file_ns = blender::nodes::node_geo_legacy_attribute_vector_rotate_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE, "Attribute Vector Rotate", - NODE_CLASS_ATTRIBUTE, - 0); - 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_CLASS_ATTRIBUTE); + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_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 = blender::nodes::geo_node_attribute_vector_rotate_layout; - ntype.declare = blender::nodes::geo_node_attribute_vector_rotate_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc index 65d22eca39c..51564d8d200 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_endpoints.cc @@ -25,13 +25,13 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_curve_endpoints_cc { -static void geo_node_curve_endpoints_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_output<decl::Geometry>("Start Points"); - b.add_output<decl::Geometry>("End Points"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Geometry>(N_("Start Points")); + b.add_output<decl::Geometry>(N_("End Points")); } /** @@ -61,7 +61,7 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, if (meta_data.domain != ATTR_DOMAIN_CURVE) { return true; } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + GVArray spline_attribute = curve_component.attribute_get_for_read( attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); OutputAttribute result_attribute = points.attribute_try_get_for_output_only( @@ -70,7 +70,7 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, /* Only copy the attributes of splines in the offsets. */ for (const int i : offsets.index_range()) { - spline_attribute->get(offsets[i], result[i]); + spline_attribute.get(offsets[i], result[i]); } result_attribute.save(); @@ -124,36 +124,35 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines, end_data.tilts[i] = spline.tilts().last(); /* Copy the point attribute data over. */ - for (const auto &item : start_data.point_attributes.items()) { + for (const auto item : start_data.point_attributes.items()) { const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; BLI_assert(spline.attributes.get_for_read(attribute_id)); GSpan spline_span = *spline.attributes.get_for_read(attribute_id); - blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]); + spline_span.type().copy_assign(spline_span[0], point_span[i]); } - for (const auto &item : end_data.point_attributes.items()) { + for (const auto item : end_data.point_attributes.items()) { const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; BLI_assert(spline.attributes.get_for_read(attribute_id)); GSpan spline_span = *spline.attributes.get_for_read(attribute_id); - blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]); + spline_span.type().copy_assign(spline_span[spline.size() - 1], point_span[i]); } } }); } -static void geo_node_curve_endpoints_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { - params.set_output("Start Points", GeometrySet()); - params.set_output("End Points", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -168,8 +167,7 @@ static void geo_node_curve_endpoints_exec(GeoNodeExecParams params) const int total_size = offsets.size(); if (total_size == 0) { - params.set_output("Start Points", GeometrySet()); - params.set_output("End Points", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -206,16 +204,18 @@ static void geo_node_curve_endpoints_exec(GeoNodeExecParams params) params.set_output("End Points", std::move(end_result)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_endpoints_cc -void register_node_type_geo_curve_endpoints() +void register_node_type_geo_legacy_curve_endpoints() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_endpoints_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_endpoints_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_endpoints_exec; + &ntype, GEO_NODE_LEGACY_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc index d1c81333c30..844baa53962 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_reverse.cc @@ -20,19 +20,19 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_curve_reverse_cc { -static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Geometry>(N_("Curve")); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_reverse_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Curve", geometry_set); return; @@ -44,7 +44,7 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) MutableSpan<SplinePtr> splines = curve.splines(); const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + VArray<bool> selection = curve_component.attribute_get_for_read( selection_name, ATTR_DOMAIN_CURVE, true); threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { @@ -58,14 +58,15 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) params.set_output("Curve", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_reverse_cc void register_node_type_geo_legacy_curve_reverse() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_reverse_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_reverse_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; + geo_node_type_base(&ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc index dfcae2e65b0..780756bcbca 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_select_by_handle_type.cc @@ -23,27 +23,24 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_curve_select_by_handle_type_cc { -static void geo_node_select_by_handle_type_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_curve_select_by_handle_type_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); } -static void geo_node_curve_select_by_handle_type_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveSelectHandles *data = (NodeGeometryCurveSelectHandles *)MEM_callocN( - sizeof(NodeGeometryCurveSelectHandles), __func__); + NodeGeometryCurveSelectHandles *data = MEM_cnew<NodeGeometryCurveSelectHandles>(__func__); data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; @@ -94,7 +91,7 @@ static void select_curve_by_handle_type(const CurveEval &curve, }); } -static void geo_node_select_by_handle_type_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const NodeGeometryCurveSelectHandles *storage = (const NodeGeometryCurveSelectHandles *)params.node().storage; @@ -103,7 +100,7 @@ static void geo_node_select_by_handle_type_exec(GeoNodeExecParams params) const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage->mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); const CurveEval *curve = curve_component.get_for_read(); @@ -121,25 +118,24 @@ static void geo_node_select_by_handle_type_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_select_by_handle_type_cc -void register_node_type_geo_select_by_handle_type() +void register_node_type_geo_legacy_select_by_handle_type() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_select_by_handle_type_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, - GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, - "Select by Handle Type", - NODE_CLASS_GEOMETRY, - 0); - ntype.declare = blender::nodes::geo_node_select_by_handle_type_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_select_by_handle_type_exec; - node_type_init(&ntype, blender::nodes::geo_node_curve_select_by_handle_type_init); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, "Select by Handle Type", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryCurveSelectHandles", node_free_standard_storage, node_copy_standard_storage); - ntype.draw_buttons = blender::nodes::geo_node_curve_select_by_handle_type_layout; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc index 31c13134f79..a82b917e817 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_set_handles.cc @@ -21,27 +21,24 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_curve_set_handles_cc { -static void geo_node_curve_set_handles_decalre(NodeDeclarationBuilder &b) +static void node_decalre(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Geometry>(N_("Curve")); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_set_handles_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); } -static void geo_node_curve_set_handles_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveSetHandles *data = (NodeGeometryCurveSetHandles *)MEM_callocN( - sizeof(NodeGeometryCurveSetHandles), __func__); + NodeGeometryCurveSetHandles *data = MEM_cnew<NodeGeometryCurveSetHandles>(__func__); data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; @@ -64,7 +61,7 @@ static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHan return BezierSpline::HandleType::Auto; } -static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const NodeGeometryCurveSetHandles *node_storage = (NodeGeometryCurveSetHandles *)params.node().storage; @@ -72,7 +69,7 @@ static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)node_storage->mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Curve", geometry_set); return; @@ -84,7 +81,7 @@ static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) MutableSpan<SplinePtr> splines = curve.splines(); const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + VArray<bool> selection = curve_component.attribute_get_for_read( selection_name, ATTR_DOMAIN_POINT, true); const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); @@ -124,21 +121,23 @@ static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) params.set_output("Curve", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_set_handles_cc -void register_node_type_geo_curve_set_handles() +void register_node_type_geo_legacy_curve_set_handles() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_set_handles_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; - node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); + &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_decalre; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryCurveSetHandles", node_free_standard_storage, node_copy_standard_storage); - ntype.draw_buttons = blender::nodes::geo_node_curve_set_handles_layout; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc index 0ef107fd8a4..6fd82e6a1bb 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_spline_type.cc @@ -23,26 +23,23 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_curve_spline_type_cc { -static void geo_node_curve_spline_type_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Geometry>(N_("Curve")); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_spline_type_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "spline_type", 0, "", ICON_NONE); } -static void geo_node_curve_spline_type_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveSplineType *data = (NodeGeometryCurveSplineType *)MEM_callocN( - sizeof(NodeGeometryCurveSplineType), __func__); + NodeGeometryCurveSplineType *data = MEM_cnew<NodeGeometryCurveSplineType>(__func__); data->spline_type = GEO_NODE_SPLINE_TYPE_POLY; node->storage = data; @@ -238,14 +235,14 @@ static SplinePtr convert_to_nurbs(const Spline &input) return {}; } -static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const NodeGeometryCurveSplineType *storage = (const NodeGeometryCurveSplineType *)params.node().storage; const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Curve", geometry_set); return; @@ -255,7 +252,7 @@ static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) const CurveEval &curve = *curve_component->get_for_read(); const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component->attribute_get_for_read( + VArray<bool> selection = curve_component->attribute_get_for_read( selection_name, ATTR_DOMAIN_CURVE, true); std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); @@ -282,21 +279,23 @@ static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_spline_type_cc -void register_node_type_geo_curve_spline_type() +void register_node_type_geo_legacy_curve_spline_type() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_spline_type_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_spline_type_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec; - node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init); + &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryCurveSplineType", node_free_standard_storage, node_copy_standard_storage); - ntype.draw_buttons = blender::nodes::geo_node_curve_spline_type_layout; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc index 0522f2b8981..4621a1656aa 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_subdivide.cc @@ -25,42 +25,37 @@ #include "node_geometry_util.hh" -using blender::fn::GVArray_For_GSpan; -using blender::fn::GVArray_For_Span; -using blender::fn::GVArray_Typed; +namespace blender::nodes::node_geo_legacy_curve_subdivide_cc { -namespace blender::nodes { - -static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Cuts"); - b.add_input<decl::Int>("Cuts", "Cuts_001").default_value(1).min(0).max(1000); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Cuts")); + b.add_input<decl::Int>(N_("Cuts"), "Cuts_001").default_value(1).min(0).max(1000); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); } -static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( - sizeof(NodeGeometryCurveSubdivide), __func__); + NodeGeometryCurveSubdivide *data = MEM_cnew<NodeGeometryCurveSubdivide>(__func__); data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; node->storage = data; } -static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; update_attribute_input_socket_availabilities( - *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); + *ntree, *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); } static Array<int> get_subdivided_offsets(const Spline &spline, @@ -308,8 +303,12 @@ static SplinePtr subdivide_spline(const Spline &spline, const VArray<int> &cuts, const int spline_offset) { - /* Since we expect to access each value many times, it should be worth it to make sure the - * attribute is a real span (especially considering the note below). Using the offset at each + if (spline.size() <= 1) { + return spline.copy(); + } + + /* Since we expect to access each value many times, it should be worth it to make sure count + * of cuts is a real span (especially considering the note below). Using the offset at each * point facilitates subdividing in parallel later. */ Array<int> offsets = get_subdivided_offsets(spline, cuts, spline_offset); const int result_size = offsets.last() + int(!spline.is_cyclic()); @@ -347,11 +346,11 @@ static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, return output_curve; } -static void geo_node_subdivide_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Geometry", geometry_set); @@ -359,34 +358,35 @@ static void geo_node_subdivide_exec(GeoNodeExecParams params) } const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); - GVArray_Typed<int> cuts = params.get_input_attribute<int>( - "Cuts", component, ATTR_DOMAIN_POINT, 0); - if (cuts->is_single() && cuts->get_internal_single() < 1) { + VArray<int> cuts = params.get_input_attribute<int>("Cuts", component, ATTR_DOMAIN_POINT, 0); + if (cuts.is_single() && cuts.get_internal_single() < 1) { params.set_output("Geometry", geometry_set); return; } - std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), cuts); params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_subdivide_cc -void register_node_type_geo_curve_subdivide() +void register_node_type_geo_legacy_curve_subdivide() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_subdivide_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; - ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; + &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; node_type_storage(&ntype, "NodeGeometryCurveSubdivide", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); - ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc index 0c435d69991..1e6b7f92a77 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_curve_to_points.cc @@ -28,29 +28,80 @@ namespace blender::nodes { -static void geo_node_curve_to_points_declare(NodeDeclarationBuilder &b) +static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points, + const AttributeIDRef &attribute_id, + const CustomDataType data_type) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Count").default_value(10).min(2).max(100000); - b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); + points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); + WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); + BLI_assert(attribute); + return attribute.varray.get_internal_span(); } -static void geo_node_curve_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +template<typename T> +static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points, + const AttributeIDRef &attribute_id) +{ + GMutableSpan attribute = create_attribute_and_retrieve_span( + points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); + return attribute.typed<T>(); +} + +CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponent &points, + const CurveEval &curve) +{ + CurveToPointsResults attributes; + + attributes.result_size = points.attribute_domain_size(ATTR_DOMAIN_POINT); + + attributes.positions = create_attribute_and_retrieve_span<float3>(points, "position"); + attributes.radii = create_attribute_and_retrieve_span<float>(points, "radius"); + attributes.tilts = create_attribute_and_retrieve_span<float>(points, "tilt"); + + /* Because of the invariants of the curve component, we use the attributes of the + * first spline as a representative for the attribute meta data all splines. */ + curve.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + attributes.point_attributes.add_new( + attribute_id, + create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type)); + return true; + }, + ATTR_DOMAIN_POINT); + + attributes.tangents = create_attribute_and_retrieve_span<float3>(points, "tangent"); + attributes.normals = create_attribute_and_retrieve_span<float3>(points, "normal"); + attributes.rotations = create_attribute_and_retrieve_span<float3>(points, "rotation"); + + return attributes; +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_legacy_curve_to_points_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Int>(N_("Count")).default_value(10).min(2).max(100000); + b.add_input<decl::Float>(N_("Length")).default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } -static void geo_node_curve_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveToPoints *data = (NodeGeometryCurveToPoints *)MEM_callocN( - sizeof(NodeGeometryCurveToPoints), __func__); + NodeGeometryCurveToPoints *data = MEM_cnew<NodeGeometryCurveToPoints>(__func__); data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT; node->storage = data; } -static void geo_node_curve_to_points_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)node->storage; const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; @@ -58,8 +109,8 @@ static void geo_node_curve_to_points_update(bNodeTree *UNUSED(ntree), bNode *nod bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *length_socket = count_socket->next; - nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); - nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); + nodeSetSocketAvailability(ntree, count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); + nodeSetSocketAvailability(ntree, length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); } /** @@ -114,54 +165,6 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, return {0}; } -static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points, - const AttributeIDRef &attribute_id, - const CustomDataType data_type) -{ - points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); - WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); - BLI_assert(attribute); - return attribute.varray->get_internal_span(); -} - -template<typename T> -static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points, - const AttributeIDRef &attribute_id) -{ - GMutableSpan attribute = create_attribute_and_retrieve_span( - points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); - return attribute.typed<T>(); -} - -CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponent &points, - const CurveEval &curve) -{ - CurveToPointsResults attributes; - - attributes.result_size = points.attribute_domain_size(ATTR_DOMAIN_POINT); - - attributes.positions = create_attribute_and_retrieve_span<float3>(points, "position"); - attributes.radii = create_attribute_and_retrieve_span<float>(points, "radius"); - attributes.tilts = create_attribute_and_retrieve_span<float>(points, "tilt"); - - /* Because of the invariants of the curve component, we use the attributes of the - * first spline as a representative for the attribute meta data all splines. */ - curve.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - attributes.point_attributes.add_new( - attribute_id, - create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type)); - return true; - }, - ATTR_DOMAIN_POINT); - - attributes.tangents = create_attribute_and_retrieve_span<float3>(points, "tangent"); - attributes.normals = create_attribute_and_retrieve_span<float3>(points, "normal"); - attributes.rotations = create_attribute_and_retrieve_span<float3>(points, "rotation"); - - return attributes; -} - /** * TODO: For non-poly splines, this has double copies that could be avoided as part * of a general look at optimizing uses of #Spline::interpolate_to_evaluated. @@ -177,10 +180,10 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines, const int size = offsets[i + 1] - offsets[i]; data.positions.slice(offset, size).copy_from(spline.evaluated_positions()); - spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size)); - spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size)); + spline.interpolate_to_evaluated(spline.radii()).materialize(data.radii.slice(offset, size)); + spline.interpolate_to_evaluated(spline.tilts()).materialize(data.tilts.slice(offset, size)); - for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + for (const Map<AttributeIDRef, GMutableSpan>::Item item : data.point_attributes.items()) { const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; @@ -188,7 +191,7 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines, GSpan spline_span = *spline.attributes.get_for_read(attribute_id); spline.interpolate_to_evaluated(spline_span) - ->materialize(point_span.slice(offset, size).data()); + .materialize(point_span.slice(offset, size).data()); } data.tangents.slice(offset, size).copy_from(spline.evaluated_tangents()); @@ -223,14 +226,14 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines, uniform_samples, data.tilts.slice(offset, size)); - for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) { + for (const Map<AttributeIDRef, GMutableSpan>::Item item : data.point_attributes.items()) { const AttributeIDRef attribute_id = item.key; GMutableSpan point_span = item.value; BLI_assert(spline.attributes.get_for_read(attribute_id)); GSpan spline_span = *spline.attributes.get_for_read(attribute_id); - spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span), + spline.sample_with_index_factors(spline.interpolate_to_evaluated(spline_span), uniform_samples, point_span.slice(offset, size)); } @@ -238,13 +241,13 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines, spline.sample_with_index_factors<float3>( spline.evaluated_tangents(), uniform_samples, data.tangents.slice(offset, size)); for (float3 &tangent : data.tangents) { - tangent.normalize(); + tangent = math::normalize(tangent); } spline.sample_with_index_factors<float3>( spline.evaluated_normals(), uniform_samples, data.normals.slice(offset, size)); for (float3 &normals : data.normals) { - normals.normalize(); + normals = math::normalize(normals); } } }); @@ -263,20 +266,20 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, if (meta_data.domain != ATTR_DOMAIN_CURVE) { return true; } - GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + GVArray spline_attribute = curve_component.attribute_get_for_read( attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type); - const CPPType &type = spline_attribute->type(); + const CPPType &type = spline_attribute.type(); OutputAttribute result_attribute = points.attribute_try_get_for_output_only( attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type); GMutableSpan result = result_attribute.as_span(); - for (const int i : IndexRange(spline_attribute->size())) { + for (const int i : spline_attribute.index_range()) { const int offset = offsets[i]; const int size = offsets[i + 1] - offsets[i]; if (size != 0) { BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - spline_attribute->get(i, buffer); + spline_attribute.get(i, buffer); type.fill_assign_n(buffer, result[offset], size); } } @@ -286,25 +289,13 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component, }); } -void curve_create_default_rotation_attribute(Span<float3> tangents, - Span<float3> normals, - MutableSpan<float3> rotations) -{ - threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) { - for (const int i : range) { - rotations[i] = - float4x4::from_normalized_axis_data({0, 0, 0}, normals[i], tangents[i]).to_euler(); - } - }); -} - -static void geo_node_curve_to_points_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)params.node().storage; const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Geometry", GeometrySet()); @@ -352,21 +343,23 @@ static void geo_node_curve_to_points_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(result)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_curve_to_points_cc -void register_node_type_geo_curve_to_points() +void register_node_type_geo_legacy_curve_to_points() { + namespace file_ns = blender::nodes::node_geo_legacy_curve_to_points_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_to_points_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_points_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_to_points_layout; + &ntype, GEO_NODE_LEGACY_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_to_points_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_to_points_update); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc index 1e2f652cd78..f7fd12d775a 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_delete_geometry.cc @@ -26,31 +26,16 @@ #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); +namespace blender::nodes::node_geo_legacy_delete_geometry_cc { -namespace blender::nodes { +using blender::bke::CustomDataAttributes; -static void geo_node_delete_geometry_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Selection"); - b.add_input<decl::Bool>("Invert"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Selection")); + b.add_input<decl::Bool>(N_("Invert")); + b.add_output<decl::Geometry>(N_("Geometry")); } template<typename T> static void copy_data(Span<T> data, MutableSpan<T> r_data, IndexMask mask) @@ -60,6 +45,78 @@ template<typename T> static void copy_data(Span<T> data, MutableSpan<T> r_data, } } +static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map) +{ + BLI_assert(src_mesh.totvert == vertex_map.size()); + for (const int i_src : vertex_map.index_range()) { + const int i_dst = vertex_map[i_src]; + if (i_dst == -1) { + continue; + } + + const MVert &v_src = src_mesh.mvert[i_src]; + MVert &v_dst = dst_mesh.mvert[i_dst]; + + v_dst = v_src; + CustomData_copy_data(&src_mesh.vdata, &dst_mesh.vdata, i_src, i_dst, 1); + } +} + +static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map) +{ + BLI_assert(src_mesh.totvert == vertex_map.size()); + BLI_assert(src_mesh.totedge == edge_map.size()); + for (const int i_src : IndexRange(src_mesh.totedge)) { + const int i_dst = edge_map[i_src]; + if (ELEM(i_dst, -1, -2)) { + continue; + } + + const MEdge &e_src = src_mesh.medge[i_src]; + MEdge &e_dst = dst_mesh.medge[i_dst]; + + CustomData_copy_data(&src_mesh.edata, &dst_mesh.edata, i_src, i_dst, 1); + e_dst = e_src; + e_dst.v1 = vertex_map[e_src.v1]; + e_dst.v2 = vertex_map[e_src.v2]; + } +} + +static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map, + Span<int> masked_poly_indices, + Span<int> new_loop_starts) +{ + for (const int i_dst : masked_poly_indices.index_range()) { + const int i_src = masked_poly_indices[i_dst]; + + const MPoly &mp_src = src_mesh.mpoly[i_src]; + MPoly &mp_dst = dst_mesh.mpoly[i_dst]; + const int i_ml_src = mp_src.loopstart; + const int i_ml_dst = new_loop_starts[i_dst]; + + CustomData_copy_data(&src_mesh.pdata, &dst_mesh.pdata, i_src, i_dst, 1); + CustomData_copy_data(&src_mesh.ldata, &dst_mesh.ldata, i_ml_src, i_ml_dst, mp_src.totloop); + + const MLoop *ml_src = src_mesh.mloop + i_ml_src; + MLoop *ml_dst = dst_mesh.mloop + i_ml_dst; + + mp_dst = mp_src; + mp_dst.loopstart = i_ml_dst; + for (int i : IndexRange(mp_src.totloop)) { + ml_dst[i].v = vertex_map[ml_src[i].v]; + ml_dst[i].e = edge_map[ml_src[i].e]; + } + } +} + static void spline_copy_builtin_attributes(const Spline &spline, Spline &r_spline, const IndexMask mask) @@ -137,7 +194,7 @@ static std::unique_ptr<CurveEval> curve_delete(const CurveEval &input_curve, 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); + VArray<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()); @@ -151,7 +208,7 @@ static std::unique_ptr<CurveEval> curve_delete(const CurveEval &input_curve, 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); + VArray<bool> selection = spline.attributes.get_for_read<bool>(name, false); indices_to_copy.clear(); for (const int i_point : IndexRange(spline.size())) { @@ -202,7 +259,7 @@ static void delete_point_cloud_selection(const PointCloudComponent &in_component const StringRef selection_name, const bool invert) { - const GVArray_Typed<bool> selection_attribute = in_component.attribute_get_for_read<bool>( + const VArray<bool> selection_attribute = in_component.attribute_get_for_read<bool>( selection_name, ATTR_DOMAIN_POINT, false); VArray_Span<bool> selection{selection_attribute}; @@ -590,7 +647,7 @@ static void delete_mesh_selection(MeshComponent &component, 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>( + VArray<bool> selection = component.attribute_get_for_read<bool>( selection_name, selection_domain, false); /* Check if there is anything to delete. */ @@ -627,10 +684,10 @@ static void delete_mesh_selection(MeshComponent &component, component.replace(mesh_out); } -static void geo_node_delete_geometry_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); const bool invert = params.extract_input<bool>("Invert"); const std::string selection_name = params.extract_input<std::string>("Selection"); @@ -662,16 +719,18 @@ static void geo_node_delete_geometry_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(out_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_delete_geometry_cc -void register_node_type_geo_delete_geometry() +void register_node_type_geo_legacy_delete_geometry() { + namespace file_ns = blender::nodes::node_geo_legacy_delete_geometry_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY); - ntype.declare = blender::nodes::geo_node_delete_geometry_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_edge_split.cc index 2ea6516996d..e628edb7e17 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_edge_split.cc @@ -22,26 +22,26 @@ extern "C" { Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd); } -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_edge_split_cc { -static void geo_node_edge_split_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Bool>("Edge Angle").default_value(true); - b.add_input<decl::Float>("Angle") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Edge Angle")).default_value(true); + b.add_input<decl::Float>(N_("Angle")) .default_value(DEG2RADF(30.0f)) .min(0.0f) .max(DEG2RADF(180.0f)) .subtype(PROP_ANGLE); - b.add_input<decl::Bool>("Sharp Edges"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Bool>(N_("Sharp Edges")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_edge_split_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_mesh()) { params.set_output("Geometry", std::move(geometry_set)); @@ -76,14 +76,16 @@ static void geo_node_edge_split_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_edge_split_cc -void register_node_type_geo_edge_split() +void register_node_type_geo_legacy_edge_split() { + namespace file_ns = blender::nodes::node_geo_legacy_edge_split_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_LEGACY_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_edge_split_exec; - ntype.declare = blender::nodes::geo_node_edge_split_declare; + geo_node_type_base(&ntype, GEO_NODE_LEGACY_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_material_assign.cc index 7d3481c1067..8fd6b1e299f 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_material_assign.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_material_assign.cc @@ -24,14 +24,14 @@ #include "BKE_material.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_material_assign_cc { -static void geo_node_legacy_material_assign_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(true); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Material>(N_("Material")).hide_label(true); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Geometry")); } static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) @@ -59,20 +59,20 @@ static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, } } -static void geo_node_legacy_material_assign_exec(GeoNodeExecParams params) +static void node_geo_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); + geometry_set = geometry::realize_instances_legacy(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>( + VArray<bool> face_mask = mesh_component.attribute_get_for_read<bool>( mask_name, ATTR_DOMAIN_FACE, true); assign_material_to_faces(*mesh, face_mask, material); } @@ -81,15 +81,17 @@ static void geo_node_legacy_material_assign_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_material_assign_cc void register_node_type_geo_legacy_material_assign() { + namespace file_ns = blender::nodes::node_geo_legacy_material_assign_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_legacy_material_assign_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_legacy_material_assign_exec; + &ntype, GEO_NODE_LEGACY_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc new file mode 100644 index 00000000000..d026fff003f --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_mesh_to_curve.cc @@ -0,0 +1,80 @@ +/* + * 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 "GEO_mesh_to_curve.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_legacy_mesh_to_curve_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + + geometry_set = geometry::realize_instances_legacy(geometry_set); + + if (!geometry_set.has_mesh()) { + params.set_default_remaining_outputs(); + return; + } + + const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>(); + 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 + "\""); + } + VArray<bool> selection = component.attribute_get_for_read<bool>( + selection_name, ATTR_DOMAIN_EDGE, true); + + Vector<int64_t> selected_edge_indices; + for (const int64_t i : IndexRange(component.attribute_domain_size(ATTR_DOMAIN_EDGE))) { + if (selection[i]) { + selected_edge_indices.append(i); + } + } + + if (selected_edge_indices.size() == 0) { + params.set_default_remaining_outputs(); + return; + } + + std::unique_ptr<CurveEval> curve = geometry::mesh_to_curve_convert( + component, IndexMask(selected_edge_indices)); + + params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); +} + +} // namespace blender::nodes::node_geo_legacy_mesh_to_curve_cc + +void register_node_type_geo_legacy_mesh_to_curve() +{ + namespace file_ns = blender::nodes::node_geo_legacy_mesh_to_curve_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_distribute.cc index f95b0da86ed..29eff373d15 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_distribute.cc @@ -36,36 +36,35 @@ #include "node_geometry_util.hh" -using blender::bke::GeometryInstanceGroup; +namespace blender::nodes::node_geo_legacy_point_distribute_cc { -namespace blender::nodes { +using blender::bke::GeometryInstanceGroup; -static void geo_node_point_distribute_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Distance Min").min(0.0f).max(100000.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Density Max") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Float>(N_("Distance Min")).min(0.0f).max(100000.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Density Max")) .default_value(1.0f) .min(0.0f) .max(100000.0f) .subtype(PROP_NONE); - b.add_input<decl::String>("Density Attribute"); - b.add_input<decl::Int>("Seed").min(-10000).max(10000); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::String>(N_("Density Attribute")); + b.add_input<decl::Int>(N_("Seed")).min(-10000).max(10000); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_point_distribute_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); } -static void node_point_distribute_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_point_distribute_update(bNodeTree *ntree, bNode *node) { bNodeSocket *sock_min_dist = (bNodeSocket *)BLI_findlink(&node->inputs, 1); - nodeSetSocketAvailability(sock_min_dist, ELEM(node->custom1, GEO_NODE_POINT_DISTRIBUTE_POISSON)); + nodeSetSocketAvailability( + ntree, sock_min_dist, ELEM(node->custom1, GEO_NODE_POINT_DISTRIBUTE_POISSON)); } /** @@ -106,9 +105,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->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)); + 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]); 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); @@ -252,18 +251,26 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, { switch (source_domain) { case ATTR_DOMAIN_POINT: { - bke::mesh_surface_sample::sample_point_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); + bke::mesh_surface_sample::sample_point_attribute(mesh, + looptri_indices, + bary_coords, + source_data, + IndexMask(output_data.size()), + output_data); break; } case ATTR_DOMAIN_CORNER: { - bke::mesh_surface_sample::sample_corner_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); + bke::mesh_surface_sample::sample_corner_attribute(mesh, + looptri_indices, + bary_coords, + source_data, + IndexMask(output_data.size()), + output_data); break; } case ATTR_DOMAIN_FACE: { bke::mesh_surface_sample::sample_face_attribute( - mesh, looptri_indices, source_data, output_data); + mesh, looptri_indices, source_data, IndexMask(output_data.size()), output_data); break; } default: { @@ -307,21 +314,21 @@ BLI_NOINLINE static void interpolate_existing_attributes( } const AttributeDomain source_domain = attribute_info->domain; - GVArrayPtr source_attribute = source_component.attribute_get_for_read( + GVArray source_attribute = source_component.attribute_get_for_read( attribute_id, source_domain, output_data_type, nullptr); if (!source_attribute) { i_instance += set_group.transforms.size(); continue; } - for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { + for ([[maybe_unused]] const int 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); + mesh, bary_coords, looptri_indices, source_domain, source_attribute, instance_span); i_instance++; } @@ -329,7 +336,7 @@ BLI_NOINLINE static void interpolate_existing_attributes( attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { using T = decltype(dummy); - GVArray_Span<T> source_span{*source_attribute}; + VArray_Span source_span{source_attribute.typed<T>()}; }); } @@ -437,7 +444,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>(); - GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( + VArray<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) { @@ -447,7 +454,7 @@ static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, sample_mesh_surface(mesh, transform, density, - &*density_factors, + &density_factors, seed, positions, bary_coords, @@ -506,10 +513,10 @@ 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 GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( + const VArray<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()) { + for ([[maybe_unused]] const int i_set_instance : set_group.transforms.index_range()) { Vector<float3> &positions = positions_all[i_instance]; Vector<float3> &bary_coords = bary_coords_all[i_instance]; Vector<int> &looptri_indices = looptri_indices_all[i_instance]; @@ -532,7 +539,7 @@ static void distribute_points_poisson_disk(Span<GeometryInstanceGroup> set_group } } -static void geo_node_point_distribute_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -545,14 +552,14 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) "Density Attribute"); if (density <= 0.0f) { - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } Vector<GeometryInstanceGroup> set_groups; geometry_set_gather_instances(geometry_set, set_groups); if (set_groups.is_empty()) { - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -566,7 +573,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) if (set_groups.is_empty()) { params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -615,6 +622,11 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) final_points_len += positions.size(); } + if (final_points_len == 0) { + params.set_default_remaining_outputs(); + return; + } + PointCloud *pointcloud = BKE_pointcloud_new_nomain(final_points_len); for (const int instance_index : positions_all.index_range()) { const int offset = instance_start_offsets[instance_index]; @@ -641,17 +653,19 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_distribute_cc void register_node_type_geo_point_distribute() { + namespace file_ns = blender::nodes::node_geo_legacy_point_distribute_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); - node_type_update(&ntype, blender::nodes::node_point_distribute_update); - ntype.declare = blender::nodes::geo_node_point_distribute_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_distribute_layout; + &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY); + node_type_update(&ntype, file_ns::node_point_distribute_update); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_instance.cc index fb45c22ced4..faf0b1a5fe7 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_instance.cc @@ -24,19 +24,19 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_point_instance_cc { -static void geo_node_point_instance_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Object>("Object").hide_label(); - b.add_input<decl::Collection>("Collection").hide_label(); - b.add_input<decl::Geometry>("Instance Geometry"); - b.add_input<decl::Int>("Seed").min(-10000).max(10000); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Object>(N_("Object")).hide_label(); + b.add_input<decl::Collection>(N_("Collection")).hide_label(); + b.add_input<decl::Geometry>(N_("Instance Geometry")); + b.add_input<decl::Int>(N_("Seed")).min(-10000).max(10000); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "instance_type", 0, "", ICON_NONE); if (RNA_enum_get(ptr, "instance_type") == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) { @@ -44,16 +44,15 @@ static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C) } } -static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( - sizeof(NodeGeometryPointInstance), __func__); + NodeGeometryPointInstance *data = MEM_cnew<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) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); bNodeSocket *collection_socket = object_socket->next; @@ -65,12 +64,15 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) const bool use_whole_collection = (node_storage->flag & GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0; - nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT); - nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION); - nodeSetSocketAvailability(instance_geometry_socket, - type == GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY); + nodeSetSocketAvailability(ntree, object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT); nodeSetSocketAvailability( - seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); + ntree, collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION); + nodeSetSocketAvailability( + ntree, instance_geometry_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_GEOMETRY); + nodeSetSocketAvailability(ntree, + seed_socket, + type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && + !use_whole_collection); } static Vector<InstanceReference> get_instance_references__object(GeoNodeExecParams ¶ms) @@ -170,21 +172,25 @@ static void add_instances_from_component(InstancesComponent &instances, const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_geometry.attribute_domain_size(domain); + if (domain_size == 0) { + return; + } - GVArray_Typed<float3> positions = src_geometry.attribute_get_for_read<float3>( + VArray<float3> positions = src_geometry.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); - GVArray_Typed<float3> rotations = src_geometry.attribute_get_for_read<float3>( + VArray<float3> rotations = src_geometry.attribute_get_for_read<float3>( "rotation", domain, {0, 0, 0}); - GVArray_Typed<float3> scales = src_geometry.attribute_get_for_read<float3>( - "scale", domain, {1, 1, 1}); - GVArray_Typed<int> id_attribute = src_geometry.attribute_get_for_read<int>("id", domain, -1); + VArray<float3> scales = src_geometry.attribute_get_for_read<float3>("scale", domain, {1, 1, 1}); + VArray<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); + OutputAttribute_Typed<int> instance_id_attribute = + instances.attribute_try_get_for_output_only<int>("id", ATTR_DOMAIN_INSTANCE); + MutableSpan<int> instance_ids = instance_id_attribute.as_span(); /* Skip all of the randomness handling if there is only a single possible instance * (anything except for collection mode with "Whole Collection" turned off). */ @@ -211,16 +217,18 @@ static void add_instances_from_component(InstancesComponent &instances, } }); } + + instance_id_attribute.save(); } -static void geo_node_point_instance_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet geometry_set_out; /* TODO: This node should be able to instance on the input instances component * rather than making the entire input geometry set real. */ - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); const Vector<InstanceReference> possible_references = get_instance_references(params); if (possible_references.is_empty()) { @@ -253,20 +261,22 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_instance_cc void register_node_type_geo_point_instance() { + namespace file_ns = blender::nodes::node_geo_legacy_point_instance_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_point_instance_init); + &ntype, GEO_NODE_LEGACY_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeGeometryPointInstance", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_point_instance_declare; - ntype.draw_buttons = blender::nodes::geo_node_point_instance_layout; - node_type_update(&ntype, blender::nodes::geo_node_point_instance_update); - ntype.geometry_node_execute = blender::nodes::geo_node_point_instance_exec; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_rotate.cc index 60c82360007..ad87ec5541b 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_rotate.cc @@ -21,21 +21,23 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_point_rotate_cc { -static void geo_node_point_rotate_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Axis"); - b.add_input<decl::Vector>("Axis", "Axis_001").default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ); - b.add_input<decl::String>("Angle"); - b.add_input<decl::Float>("Angle", "Angle_001").subtype(PROP_ANGLE); - b.add_input<decl::String>("Rotation"); - b.add_input<decl::Vector>("Rotation", "Rotation_001").subtype(PROP_EULER); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Axis")); + b.add_input<decl::Vector>(N_("Axis"), "Axis_001") + .default_value({0.0, 0.0, 1.0}) + .subtype(PROP_XYZ); + b.add_input<decl::String>(N_("Angle")); + b.add_input<decl::Float>(N_("Angle"), "Angle_001").subtype(PROP_ANGLE); + b.add_input<decl::String>(N_("Rotation")); + b.add_input<decl::Vector>(N_("Rotation"), "Rotation_001").subtype(PROP_EULER); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_point_rotate_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { NodeGeometryRotatePoints *storage = (NodeGeometryRotatePoints *)((bNode *)ptr->data)->storage; @@ -55,10 +57,9 @@ static void geo_node_point_rotate_layout(uiLayout *layout, bContext *UNUSED(C), } } -static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( - sizeof(NodeGeometryRotatePoints), __func__); + NodeGeometryRotatePoints *node_storage = MEM_cnew<NodeGeometryRotatePoints>(__func__); node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; @@ -69,20 +70,23 @@ static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) node->storage = node_storage; } -static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; update_attribute_input_socket_availabilities( + *ntree, *node, "Axis", (GeometryNodeAttributeInputMode)node_storage->input_type_axis, node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); update_attribute_input_socket_availabilities( + *ntree, *node, "Angle", (GeometryNodeAttributeInputMode)node_storage->input_type_angle, node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); update_attribute_input_socket_availabilities( + *ntree, *node, "Rotation", (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, @@ -167,9 +171,9 @@ static void point_rotate_on_component(GeometryComponent &component, const int domain_size = rotations.size(); if (storage.type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE) { - GVArray_Typed<float3> axis = params.get_input_attribute<float3>( + VArray<float3> axis = params.get_input_attribute<float3>( "Axis", component, ATTR_DOMAIN_POINT, {0, 0, 1}); - GVArray_Typed<float> angles = params.get_input_attribute<float>( + VArray<float> angles = params.get_input_attribute<float>( "Angle", component, ATTR_DOMAIN_POINT, 0); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -180,7 +184,7 @@ static void point_rotate_on_component(GeometryComponent &component, } } else { - GVArray_Typed<float3> eulers = params.get_input_attribute<float3>( + VArray<float3> eulers = params.get_input_attribute<float3>( "Rotation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -194,11 +198,11 @@ static void point_rotate_on_component(GeometryComponent &component, rotation_attribute.save(); } -static void geo_node_point_rotate_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { point_rotate_on_component(geometry_set.get_component_for_write<MeshComponent>(), params); @@ -213,19 +217,21 @@ static void geo_node_point_rotate_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_rotate_cc void register_node_type_geo_point_rotate() { + namespace file_ns = blender::nodes::node_geo_legacy_point_rotate_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_point_rotate_init); - node_type_update(&ntype, blender::nodes::geo_node_point_rotate_update); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_ROTATE, "Point Rotate", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryRotatePoints", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_point_rotate_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_rotate_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_rotate_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_scale.cc index 99adce149e9..69e69a24e29 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_scale.cc @@ -21,41 +21,40 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_point_scale_cc { -static void geo_node_point_scale_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Factor"); - b.add_input<decl::Vector>("Factor", "Factor_001") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Factor")); + b.add_input<decl::Vector>(N_("Factor"), "Factor_001") .default_value({1.0f, 1.0f, 1.0f}) .subtype(PROP_XYZ); - b.add_input<decl::Float>("Factor", "Factor_002").default_value(1.0f).min(0.0f); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Float>(N_("Factor"), "Factor_002").default_value(1.0f).min(0.0f); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_point_scale_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "input_type", 0, IFACE_("Type"), ICON_NONE); } -static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( - sizeof(NodeGeometryPointScale), __func__); + NodeGeometryPointScale *data = MEM_cnew<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) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); + *ntree, *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); } static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) @@ -78,7 +77,7 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co const CustomDataType data_type = (input_type == GEO_NODE_ATTRIBUTE_INPUT_FLOAT) ? CD_PROP_FLOAT : CD_PROP_FLOAT3; - GVArrayPtr attribute = params.get_input_attribute( + GVArray attribute = params.get_input_attribute( "Factor", component, ATTR_DOMAIN_POINT, data_type, nullptr); if (!attribute) { return; @@ -86,13 +85,13 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co MutableSpan<float3> scale_span = scale_attribute.as_span(); if (data_type == CD_PROP_FLOAT) { - GVArray_Typed<float> factors{*attribute}; + VArray<float> factors = attribute.typed<float>(); 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}; + VArray<float3> factors = attribute.typed<float3>(); for (const int i : scale_span.index_range()) { scale_span[i] = scale_span[i] * factors[i]; } @@ -101,11 +100,11 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co scale_attribute.save(); } -static void geo_node_point_scale_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); @@ -120,20 +119,22 @@ static void geo_node_point_scale_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_scale_cc void register_node_type_geo_point_scale() { + namespace file_ns = blender::nodes::node_geo_legacy_point_scale_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_POINT_SCALE, "Point Scale", NODE_CLASS_GEOMETRY); - ntype.declare = blender::nodes::geo_node_point_scale_declare; - node_type_init(&ntype, blender::nodes::geo_node_point_scale_init); - node_type_update(&ntype, blender::nodes::geo_node_point_scale_update); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryPointScale", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_point_scale_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_scale_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_separate.cc index 48b6676c1dd..b9760587706 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_separate.cc @@ -25,14 +25,6 @@ namespace blender::nodes { -static void geo_node_point_instance_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Mask"); - b.add_output<decl::Geometry>("Geometry 1"); - b.add_output<decl::Geometry>("Geometry 2"); -} - template<typename T> static void copy_data_based_on_mask(Span<T> data, Span<bool> masks, @@ -55,7 +47,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, { for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) { ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); - const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type()); + 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 @@ -69,7 +61,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); - GVArray_Span<T> span{*attribute.varray}; + VArray_Span span{attribute.varray.typed<T>()}; MutableSpan<T> out_span = result_attribute.as_span<T>(); copy_data_based_on_mask(span, masks, invert, out_span); }); @@ -78,6 +70,18 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component, } } +} // namespace blender::nodes + +namespace blender::nodes::node_geo_legacy_point_separate_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Mask")); + b.add_output<decl::Geometry>(N_("Geometry 1")); + b.add_output<decl::Geometry>(N_("Geometry 2")); +} + static void create_component_points(GeometryComponent &component, const int total) { switch (component.type()) { @@ -103,7 +107,7 @@ static void separate_points_from_component(const GeometryComponent &in_component return; } - const GVArray_Typed<bool> mask_attribute = in_component.attribute_get_for_read<bool>( + const VArray<bool> mask_attribute = in_component.attribute_get_for_read<bool>( mask_name, ATTR_DOMAIN_POINT, false); VArray_Span<bool> masks{mask_attribute}; @@ -133,7 +137,7 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, return set_out; } -static void geo_node_point_separate_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { bool wait_for_inputs = false; wait_for_inputs |= params.lazy_require_input("Geometry"); @@ -146,7 +150,7 @@ static void geo_node_point_separate_exec(GeoNodeExecParams params) /* 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); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (params.lazy_output_is_required("Geometry 1")) { params.set_output("Geometry 1", @@ -158,16 +162,18 @@ static void geo_node_point_separate_exec(GeoNodeExecParams params) } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_separate_cc void register_node_type_geo_point_separate() { + namespace file_ns = blender::nodes::node_geo_legacy_point_separate_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_point_instance_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec; + &ntype, GEO_NODE_LEGACY_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; ntype.geometry_node_execute_supports_laziness = true; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_translate.cc index f2fce45c57b..385c3d9f22d 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_point_translate.cc @@ -19,17 +19,17 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_point_translate_cc { -static void geo_node_point_translate_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Translation"); - b.add_input<decl::Vector>("Translation", "Translation_001").subtype(PROP_TRANSLATION); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Translation")); + b.add_input<decl::Vector>(N_("Translation"), "Translation_001").subtype(PROP_TRANSLATION); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_point_translate_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -43,21 +43,21 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co if (!position_attribute) { return; } - GVArray_Typed<float3> attribute = params.get_input_attribute<float3>( + VArray<float3> attribute = params.get_input_attribute<float3>( "Translation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); - for (const int i : IndexRange(attribute.size())) { + for (const int i : attribute.index_range()) { position_attribute->set(i, position_attribute->get(i) + attribute[i]); } position_attribute.save(); } -static void geo_node_point_translate_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); @@ -72,39 +72,40 @@ static void geo_node_point_translate_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_point_translate_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryPointTranslate *data = (NodeGeometryPointTranslate *)MEM_callocN( - sizeof(NodeGeometryPointTranslate), __func__); + NodeGeometryPointTranslate *data = MEM_cnew<NodeGeometryPointTranslate>(__func__); data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; node->storage = data; } -static void geo_node_point_translate_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; update_attribute_input_socket_availabilities( - *node, "Translation", (GeometryNodeAttributeInputMode)node_storage.input_type); + *ntree, *node, "Translation", (GeometryNodeAttributeInputMode)node_storage.input_type); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_point_translate_cc void register_node_type_geo_point_translate() { + namespace file_ns = blender::nodes::node_geo_legacy_point_translate_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_point_translate_init); - node_type_update(&ntype, blender::nodes::geo_node_point_translate_update); + &ntype, GEO_NODE_LEGACY_POINT_TRANSLATE, "Point Translate", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage(&ntype, "NodeGeometryPointTranslate", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_point_translate_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_translate_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_translate_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_points_to_volume.cc index d920c8de9f0..f54ffc53a6e 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_points_to_volume.cc @@ -28,22 +28,20 @@ #include "UI_interface.h" #include "UI_resources.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_points_to_volume_cc { -static void geo_node_points_to_volume_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Density").default_value(1.0f).min(0.0f); - b.add_input<decl::Float>("Voxel Size").default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Voxel Amount").default_value(64.0f).min(0.0f); - b.add_input<decl::String>("Radius"); - b.add_input<decl::Float>("Radius", "Radius_001").default_value(0.5f).min(0.0f); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Float>(N_("Density")).default_value(1.0f).min(0.0f); + b.add_input<decl::Float>(N_("Voxel Size")).default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Voxel Amount")).default_value(64.0f).min(0.0f); + b.add_input<decl::String>(N_("Radius")); + b.add_input<decl::Float>(N_("Radius"), "Radius_001").default_value(0.5f).min(0.0f); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_points_to_volume_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -51,10 +49,9 @@ static void geo_node_points_to_volume_layout(uiLayout *layout, uiItemR(layout, ptr, "input_type_radius", 0, IFACE_("Radius"), ICON_NONE); } -static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( - sizeof(NodeGeometryPointsToVolume), __func__); + NodeGeometryPointsToVolume *data = MEM_cnew<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; @@ -65,19 +62,22 @@ static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node STRNCPY(radius_attribute_socket_value->value, "radius"); } -static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *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, + nodeSetSocketAvailability(ntree, + 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); + nodeSetSocketAvailability(ntree, + 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); + *ntree, *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); } #ifdef WITH_OPENVDB @@ -161,7 +161,7 @@ static float compute_voxel_size(const GeoNodeExecParams ¶ms, } /* The voxel size adapts to the final size of the volume. */ - const float diagonal = float3::distance(min, max); + const float diagonal = math::distance(min, max); const float extended_diagonal = diagonal + 2.0f * radius; const float voxel_size = extended_diagonal / voxel_amount; return voxel_size; @@ -172,12 +172,12 @@ static void gather_point_data_from_component(const GeoNodeExecParams ¶ms, Vector<float3> &r_positions, Vector<float> &r_radii) { - GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( + VArray<float3> positions = component.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); - GVArray_Typed<float> radii = params.get_input_attribute<float>( + VArray<float> radii = params.get_input_attribute<float>( "Radius", component, ATTR_DOMAIN_POINT, 0.0f); - for (const int i : IndexRange(positions.size())) { + for (const int i : positions.index_range()) { r_positions.append(positions[i]); r_radii.append(radii[i]); } @@ -241,13 +241,13 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_ } #endif -static void geo_node_points_to_volume_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Geometry"); GeometrySet geometry_set_out; /* TODO: Read-only access to instances should be supported here, for now they are made real. */ - geometry_set_in = geometry_set_realize_instances(geometry_set_in); + geometry_set_in = geometry::realize_instances_legacy(geometry_set_in); #ifdef WITH_OPENVDB initialize_volume_component_from_points(geometry_set_in, geometry_set_out, params); @@ -256,23 +256,25 @@ static void geo_node_points_to_volume_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_points_to_volume_cc -void register_node_type_geo_points_to_volume() +void register_node_type_geo_legacy_points_to_volume() { + namespace file_ns = blender::nodes::node_geo_legacy_points_to_volume_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY); node_type_storage(&ntype, "NodeGeometryPointsToVolume", node_free_standard_storage, node_copy_standard_storage); node_type_size(&ntype, 170, 120, 700); - node_type_init(&ntype, blender::nodes::geo_node_points_to_volume_init); - node_type_update(&ntype, blender::nodes::geo_node_points_to_volume_update); - ntype.declare = blender::nodes::geo_node_points_to_volume_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_points_to_volume_exec; - ntype.draw_buttons = blender::nodes::geo_node_points_to_volume_layout; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_raycast.cc index 401a478f04c..cfae88e0625 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_raycast.cc @@ -24,30 +24,30 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_raycast_cc { -static void geo_node_raycast_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Geometry>("Target Geometry"); - b.add_input<decl::String>("Ray Direction"); - b.add_input<decl::Vector>("Ray Direction", "Ray Direction_001") + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Geometry>(N_("Target Geometry")); + b.add_input<decl::String>(N_("Ray Direction")); + b.add_input<decl::Vector>(N_("Ray Direction"), "Ray Direction_001") .default_value({0.0f, 0.0f, 1.0f}); - b.add_input<decl::String>("Ray Length"); - b.add_input<decl::Float>("Ray Length", "Ray Length_001") + b.add_input<decl::String>(N_("Ray Length")); + b.add_input<decl::Float>(N_("Ray Length"), "Ray Length_001") .default_value(100.0f) .min(0.0f) .subtype(PROP_DISTANCE); - b.add_input<decl::String>("Target Attribute"); - b.add_input<decl::String>("Is Hit"); - b.add_input<decl::String>("Hit Position"); - b.add_input<decl::String>("Hit Normal"); - b.add_input<decl::String>("Hit Distance"); - b.add_input<decl::String>("Hit Attribute"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::String>(N_("Target Attribute")); + b.add_input<decl::String>(N_("Is Hit")); + b.add_input<decl::String>(N_("Hit Position")); + b.add_input<decl::String>(N_("Hit Normal")); + b.add_input<decl::String>(N_("Hit Distance")); + b.add_input<decl::String>(N_("Hit Attribute")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -56,24 +56,27 @@ static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), Point uiItemR(layout, ptr, "input_type_ray_length", 0, IFACE_("Ray Length"), ICON_NONE); } -static void geo_node_raycast_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryRaycast *data = (NodeGeometryRaycast *)MEM_callocN(sizeof(NodeGeometryRaycast), - __func__); + NodeGeometryRaycast *data = MEM_cnew<NodeGeometryRaycast>(__func__); data->input_type_ray_direction = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; data->input_type_ray_length = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; node->storage = data; } -static void geo_node_raycast_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { NodeGeometryRaycast *node_storage = (NodeGeometryRaycast *)node->storage; update_attribute_input_socket_availabilities( + *ntree, *node, "Ray Direction", (GeometryNodeAttributeInputMode)node_storage->input_type_ray_direction); update_attribute_input_socket_availabilities( - *node, "Ray Length", (GeometryNodeAttributeInputMode)node_storage->input_type_ray_length); + *ntree, + *node, + "Ray Length", + (GeometryNodeAttributeInputMode)node_storage->input_type_ray_length); } static void raycast_to_mesh(const Mesh &mesh, @@ -104,7 +107,7 @@ static void raycast_to_mesh(const Mesh &mesh, for (const int i : ray_origins.index_range()) { const float ray_length = ray_lengths[i]; const float3 ray_origin = ray_origins[i]; - const float3 ray_direction = ray_directions[i].normalized(); + const float3 ray_direction = math::normalize(ray_directions[i]); BVHTreeRayHit hit; hit.index = -1; @@ -197,11 +200,11 @@ static void raycast_from_points(const GeoNodeExecParams ¶ms, (GeometryNodeRaycastMapMode)storage.mapping); const AttributeDomain result_domain = ATTR_DOMAIN_POINT; - GVArray_Typed<float3> ray_origins = dst_component.attribute_get_for_read<float3>( + VArray<float3> ray_origins = dst_component.attribute_get_for_read<float3>( "position", result_domain, {0, 0, 0}); - GVArray_Typed<float3> ray_directions = params.get_input_attribute<float3>( + VArray<float3> ray_directions = params.get_input_attribute<float3>( "Ray Direction", dst_component, result_domain, {0, 0, 0}); - GVArray_Typed<float> ray_lengths = params.get_input_attribute<float>( + VArray<float> ray_lengths = params.get_input_attribute<float>( "Ray Length", dst_component, result_domain, 0); OutputAttribute_Typed<bool> hit_attribute = @@ -218,10 +221,10 @@ static void raycast_from_points(const GeoNodeExecParams ¶ms, Array<int> hit_indices; Array<float3> hit_positions_internal; if (!hit_attribute_names.is_empty()) { - hit_indices.reinitialize(ray_origins->size()); + hit_indices.reinitialize(ray_origins.size()); if (!hit_position_attribute) { - hit_positions_internal.reinitialize(ray_origins->size()); + hit_positions_internal.reinitialize(ray_origins.size()); } } const MutableSpan<bool> is_hit = hit_attribute ? hit_attribute.as_span() : MutableSpan<bool>(); @@ -250,7 +253,8 @@ static void raycast_from_points(const GeoNodeExecParams ¶ms, hit_distance_attribute.save(); /* Custom interpolated attributes */ - bke::mesh_surface_sample::MeshAttributeInterpolator interp(src_mesh, hit_positions, hit_indices); + bke::mesh_surface_sample::MeshAttributeInterpolator interp( + src_mesh, IndexMask(ray_origins.size()), hit_positions, hit_indices); for (const int i : hit_attribute_names.index_range()) { const std::optional<AttributeMetaData> meta_data = src_mesh_component->attribute_get_meta_data( hit_attribute_names[i]); @@ -267,7 +271,7 @@ static void raycast_from_points(const GeoNodeExecParams ¶ms, } } -static void geo_node_raycast_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet target_geometry_set = params.extract_input<GeometrySet>("Target Geometry"); @@ -280,8 +284,8 @@ static void geo_node_raycast_exec(GeoNodeExecParams params) const Array<std::string> hit_names = {params.extract_input<std::string>("Target Attribute")}; const Array<std::string> hit_output_names = {params.extract_input<std::string>("Hit Attribute")}; - geometry_set = bke::geometry_set_realize_instances(geometry_set); - target_geometry_set = bke::geometry_set_realize_instances(target_geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); + target_geometry_set = geometry::realize_instances_legacy(target_geometry_set); static const Array<GeometryComponentType> types = { GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; @@ -302,20 +306,22 @@ static void geo_node_raycast_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_raycast_cc -void register_node_type_geo_raycast() +void register_node_type_geo_legacy_raycast() { + namespace file_ns = blender::nodes::node_geo_legacy_raycast_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_LEGACY_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY); node_type_size_preset(&ntype, NODE_SIZE_LARGE); - node_type_init(&ntype, blender::nodes::geo_node_raycast_init); - node_type_update(&ntype, blender::nodes::geo_node_raycast_update); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_raycast_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_raycast_exec; - ntype.draw_buttons = blender::nodes::geo_node_raycast_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_select_by_material.cc index eabdd2bcd5a..59ac697b658 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_select_by_material.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_select_by_material.cc @@ -26,14 +26,14 @@ #include "BKE_material.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_select_by_material_cc { -static void geo_node_legacy_select_by_material_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Material>(N_("Material")).hide_label(); + b.add_input<decl::String>(N_("Selection")); + b.add_output<decl::Geometry>(N_("Geometry")); } static void select_mesh_by_material(const Mesh &mesh, @@ -54,13 +54,13 @@ static void select_mesh_by_material(const Mesh &mesh, }); } -static void geo_node_legacy_select_by_material_exec(GeoNodeExecParams params) +static void node_geo_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); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (geometry_set.has<MeshComponent>()) { MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); @@ -78,15 +78,17 @@ static void geo_node_legacy_select_by_material_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_select_by_material_cc void register_node_type_geo_legacy_select_by_material() { + namespace file_ns = blender::nodes::node_geo_legacy_select_by_material_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_legacy_select_by_material_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_legacy_select_by_material_exec; + &ntype, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, "Select by Material", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_subdivision_surface.cc index 07d3f89bdb7..7c5553cb5e4 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_subdivision_surface.cc @@ -23,19 +23,17 @@ #include "UI_resources.h" #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_legacy_subdivision_surface_cc { -static void geo_node_subdivision_surface_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Level").default_value(1).min(0).max(6); - b.add_input<decl::Bool>("Use Creases"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Int>(N_("Level")).default_value(1).min(0).max(6); + b.add_input<decl::Bool>(N_("Use Creases")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_subdivision_surface_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { #ifdef WITH_OPENSUBDIV uiLayoutSetPropSep(layout, true); @@ -47,20 +45,19 @@ static void geo_node_subdivision_surface_layout(uiLayout *layout, #endif } -static void geo_node_subdivision_surface_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometrySubdivisionSurface *data = (NodeGeometrySubdivisionSurface *)MEM_callocN( - sizeof(NodeGeometrySubdivisionSurface), __func__); + NodeGeometrySubdivisionSurface *data = MEM_cnew<NodeGeometrySubdivisionSurface>(__func__); data->uv_smooth = SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES; data->boundary_smooth = SUBSURF_BOUNDARY_SMOOTH_ALL; node->storage = data; } -static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set = geometry::realize_instances_legacy(geometry_set); if (!geometry_set.has_mesh()) { params.set_output("Geometry", geometry_set); @@ -126,18 +123,20 @@ static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_legacy_subdivision_surface_cc -void register_node_type_geo_subdivision_surface() +void register_node_type_geo_legacy_subdivision_surface() { + namespace file_ns = blender::nodes::node_geo_legacy_subdivision_surface_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_subdivision_surface_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_subdivision_surface_exec; - ntype.draw_buttons = blender::nodes::geo_node_subdivision_surface_layout; - node_type_init(&ntype, blender::nodes::geo_node_subdivision_surface_init); + &ntype, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_init(&ntype, file_ns::node_init); node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); node_type_storage(&ntype, "NodeGeometrySubdivisionSurface", diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_volume_to_mesh.cc new file mode 100644 index 00000000000..42fbc49ed4b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_legacy_volume_to_mesh.cc @@ -0,0 +1,176 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DEG_depsgraph_query.h" +#ifdef WITH_OPENVDB +# include <openvdb/tools/GridTransformer.h> +# include <openvdb/tools/VolumeToMesh.h> +#endif + +#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" +#include "BKE_volume_to_mesh.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes::node_geo_legacy_volume_to_mesh_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Density")); + b.add_input<decl::Float>(N_("Voxel Size")).default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Voxel Amount")).default_value(64.0f).min(0.0f); + b.add_input<decl::Float>(N_("Threshold")).default_value(0.1f).min(0.0f); + b.add_input<decl::Float>(N_("Adaptivity")).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "resolution_mode", 0, IFACE_("Resolution"), ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryVolumeToMesh *data = MEM_cnew<NodeGeometryVolumeToMesh>(__func__); + data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID; + + bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Density"); + bNodeSocketValueString *grid_socket_value = (bNodeSocketValueString *)grid_socket->default_value; + STRNCPY(grid_socket_value->value, "density"); + + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)node->storage; + + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(ntree, + voxel_amount_socket, + data->resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT); + nodeSetSocketAvailability(ntree, + voxel_size_socket, + data->resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE); +} + +#ifdef WITH_OPENVDB + +static void create_mesh_from_volume(GeometrySet &geometry_set_in, + GeometrySet &geometry_set_out, + GeoNodeExecParams ¶ms) +{ + if (!geometry_set_in.has<VolumeComponent>()) { + return; + } + + const NodeGeometryVolumeToMesh &storage = + *(const NodeGeometryVolumeToMesh *)params.node().storage; + + bke::VolumeToMeshResolution resolution; + resolution.mode = (VolumeToMeshResolutionMode)storage.resolution_mode; + if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT) { + resolution.settings.voxel_amount = params.get_input<float>("Voxel Amount"); + if (resolution.settings.voxel_amount <= 0.0f) { + return; + } + } + else if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE) { + resolution.settings.voxel_size = params.get_input<float>("Voxel Size"); + if (resolution.settings.voxel_size <= 0.0f) { + return; + } + } + + const VolumeComponent *component = geometry_set_in.get_component_for_read<VolumeComponent>(); + const Volume *volume = component->get_for_read(); + if (volume == nullptr) { + return; + } + + const Main *bmain = DEG_get_bmain(params.depsgraph()); + BKE_volume_load(volume, bmain); + + 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; + } + + float threshold = params.get_input<float>("Threshold"); + float adaptivity = params.get_input<float>("Adaptivity"); + + const openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + Mesh *mesh = bke::volume_to_mesh(*grid, resolution, threshold, adaptivity); + 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); +} + +#endif /* WITH_OPENVDB */ + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_out; + +#ifdef WITH_OPENVDB + create_mesh_from_volume(geometry_set_in, geometry_set_out, params); +#else + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenVDB")); +#endif + + params.set_output("Geometry", geometry_set_out); +} + +} // namespace blender::nodes::node_geo_legacy_volume_to_mesh_cc + +void register_node_type_geo_legacy_volume_to_mesh() +{ + namespace file_ns = blender::nodes::node_geo_legacy_volume_to_mesh_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_VOLUME_TO_MESH, "Volume to Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + node_type_storage( + &ntype, "NodeGeometryVolumeToMesh", node_free_standard_storage, node_copy_standard_storage); + node_type_size(&ntype, 170, 120, 700); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc deleted file mode 100644 index 11349dc7d42..00000000000 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc +++ /dev/null @@ -1,314 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#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; - -namespace blender::nodes { - -static void geo_node_mesh_to_curve_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Mesh"); - b.add_input<decl::String>("Selection"); - b.add_output<decl::Geometry>("Curve"); -} - -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<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids(); - - /* Copy builtin control point attributes. */ - if (source_attribute_ids.contains("tilt")) { - const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>( - "tilt", ATTR_DOMAIN_POINT, 0.0f); - threading::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_ids.remove_contained("tilt"); - } - if (source_attribute_ids.contains("radius")) { - const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>( - "radius", ATTR_DOMAIN_POINT, 1.0f); - threading::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_ids.remove_contained("radius"); - } - - /* Don't copy other builtin control point attributes. */ - source_attribute_ids.remove("position"); - - /* Copy dynamic control point attributes. */ - for (const AttributeIDRef &attribute_id : source_attribute_ids) { - const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id, - 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()); - - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - /* Create attribute on the spline points. */ - splines[i]->attributes.create(attribute_id, data_type); - std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write( - attribute_id); - 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_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_to_curve_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_exec; - nodeRegisterType(&ntype); -} diff --git a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc new file mode 100644 index 00000000000..6c2e72cf14f --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc @@ -0,0 +1,430 @@ +/* + * 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_attribute_math.hh" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes::node_geo_accumulate_field_cc { + +NODE_STORAGE_FUNCS(NodeAccumulateField) + +static void node_declare(NodeDeclarationBuilder &b) +{ + std::string value_in_description = "The values to be accumulated"; + std::string leading_out_description = + "The running total of values in the corresponding group, starting at the first value"; + std::string trailing_out_description = + "The running total of values in the corresponding group, starting at zero"; + std::string total_out_description = "The total of all of the values in the corresponding group"; + + b.add_input<decl::Vector>(N_("Value"), "Value Vector") + .default_value({1.0f, 1.0f, 1.0f}) + .supports_field() + .description(N_(value_in_description)); + b.add_input<decl::Float>(N_("Value"), "Value Float") + .default_value(1.0f) + .supports_field() + .description(N_(value_in_description)); + b.add_input<decl::Int>(N_("Value"), "Value Int") + .default_value(1) + .supports_field() + .description(N_(value_in_description)); + b.add_input<decl::Int>(N_("Group Index")) + .supports_field() + .description( + N_("An index used to group values together for multiple separate accumulations")); + + b.add_output<decl::Vector>(N_("Leading"), "Leading Vector") + .field_source() + .description(N_(leading_out_description)); + b.add_output<decl::Float>(N_("Leading"), "Leading Float") + .field_source() + .description(N_(leading_out_description)); + b.add_output<decl::Int>(N_("Leading"), "Leading Int") + .field_source() + .description(N_(leading_out_description)); + + b.add_output<decl::Vector>(N_("Trailing"), "Trailing Vector") + .field_source() + .description(N_(trailing_out_description)); + b.add_output<decl::Float>(N_("Trailing"), "Trailing Float") + .field_source() + .description(N_(trailing_out_description)); + b.add_output<decl::Int>(N_("Trailing"), "Trailing Int") + .field_source() + .description(N_(trailing_out_description)); + + b.add_output<decl::Vector>(N_("Total"), "Total Vector") + .field_source() + .description(N_(total_out_description)); + b.add_output<decl::Float>(N_("Total"), "Total Float") + .field_source() + .description(N_(total_out_description)); + b.add_output<decl::Int>(N_("Total"), "Total Int") + .field_source() + .description(N_(total_out_description)); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeAccumulateField *data = MEM_cnew<NodeAccumulateField>(__func__); + data->data_type = CD_PROP_FLOAT; + data->domain = ATTR_DOMAIN_POINT; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeAccumulateField &storage = node_storage(*node); + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *sock_in_vector = (bNodeSocket *)node->inputs.first; + bNodeSocket *sock_in_float = sock_in_vector->next; + bNodeSocket *sock_in_int = sock_in_float->next; + + bNodeSocket *sock_out_vector = (bNodeSocket *)node->outputs.first; + bNodeSocket *sock_out_float = sock_out_vector->next; + bNodeSocket *sock_out_int = sock_out_float->next; + + bNodeSocket *sock_out_first_vector = sock_out_int->next; + bNodeSocket *sock_out_first_float = sock_out_first_vector->next; + bNodeSocket *sock_out_first_int = sock_out_first_float->next; + bNodeSocket *sock_out_total_vector = sock_out_first_int->next; + bNodeSocket *sock_out_total_float = sock_out_total_vector->next; + bNodeSocket *sock_out_total_int = sock_out_total_float->next; + + nodeSetSocketAvailability(ntree, sock_in_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_in_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_in_int, data_type == CD_PROP_INT32); + + nodeSetSocketAvailability(ntree, sock_out_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_out_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_out_int, data_type == CD_PROP_INT32); + + nodeSetSocketAvailability(ntree, sock_out_first_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_out_first_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_out_first_int, data_type == CD_PROP_INT32); + + nodeSetSocketAvailability(ntree, sock_out_total_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_out_total_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_out_total_int, data_type == CD_PROP_INT32); +} + +enum class AccumulationMode { Leading = 0, Trailing = 1 }; + +static std::optional<CustomDataType> node_type_from_other_socket(const bNodeSocket &socket) +{ + switch (socket.type) { + case SOCK_FLOAT: + return CD_PROP_FLOAT; + case SOCK_BOOLEAN: + case SOCK_INT: + return CD_PROP_INT32; + case SOCK_VECTOR: + case SOCK_RGBA: + return CD_PROP_FLOAT3; + default: + return {}; + } +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const std::optional<CustomDataType> type = node_type_from_other_socket(params.other_socket()); + if (!type) { + return; + } + if (params.in_out() == SOCK_OUT) { + params.add_item( + IFACE_("Leading"), + [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAccumulateField"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Leading"); + }, + 0); + params.add_item( + IFACE_("Trailing"), + [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAccumulateField"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Trailing"); + }, + -1); + params.add_item( + IFACE_("Total"), + [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAccumulateField"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Total"); + }, + -2); + } + else { + params.add_item( + IFACE_("Value"), + [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAccumulateField"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Value"); + }, + 0); + + params.add_item( + IFACE_("Group Index"), + [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAccumulateField"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Group Index"); + }, + -1); + } +} + +template<typename T> class AccumulateFieldInput final : public GeometryFieldInput { + private: + Field<T> input_; + Field<int> group_index_; + AttributeDomain source_domain_; + AccumulationMode accumulation_mode_; + + public: + AccumulateFieldInput(const AttributeDomain source_domain, + Field<T> input, + Field<int> group_index, + AccumulationMode accumulation_mode) + : GeometryFieldInput(CPPType::get<T>(), "Accumulation"), + input_(input), + group_index_(group_index), + source_domain_(source_domain), + accumulation_mode_(accumulation_mode) + { + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + const GeometryComponentFieldContext field_context{component, source_domain_}; + const int domain_size = component.attribute_domain_size(field_context.domain()); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(input_); + evaluator.add(group_index_); + evaluator.evaluate(); + const VArray<T> &values = evaluator.get_evaluated<T>(0); + const VArray<int> &group_indices = evaluator.get_evaluated<int>(1); + + Array<T> accumulations_out(domain_size); + + if (group_indices.is_single()) { + T accumulation = T(); + if (accumulation_mode_ == AccumulationMode::Leading) { + for (const int i : values.index_range()) { + accumulation = values[i] + accumulation; + accumulations_out[i] = accumulation; + } + } + else { + for (const int i : values.index_range()) { + accumulations_out[i] = accumulation; + accumulation = values[i] + accumulation; + } + } + } + else { + Map<int, T> accumulations; + if (accumulation_mode_ == AccumulationMode::Leading) { + for (const int i : values.index_range()) { + T &accumulation_value = accumulations.lookup_or_add_default(group_indices[i]); + accumulation_value += values[i]; + accumulations_out[i] = accumulation_value; + } + } + else { + for (const int i : values.index_range()) { + T &accumulation_value = accumulations.lookup_or_add_default(group_indices[i]); + accumulations_out[i] = accumulation_value; + accumulation_value += values[i]; + } + } + } + + return component.attribute_try_adapt_domain<T>( + VArray<T>::ForContainer(std::move(accumulations_out)), source_domain_, domain); + } + + uint64_t hash() const override + { + return get_default_hash_4(input_, group_index_, source_domain_, accumulation_mode_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const AccumulateFieldInput *other_accumulate = dynamic_cast<const AccumulateFieldInput *>( + &other)) { + return input_ == other_accumulate->input_ && + group_index_ == other_accumulate->group_index_ && + source_domain_ == other_accumulate->source_domain_ && + accumulation_mode_ == other_accumulate->accumulation_mode_; + } + return false; + } +}; + +template<typename T> class TotalFieldInput final : public GeometryFieldInput { + private: + Field<T> input_; + Field<int> group_index_; + AttributeDomain source_domain_; + + public: + TotalFieldInput(const AttributeDomain source_domain, Field<T> input, Field<int> group_index) + : GeometryFieldInput(CPPType::get<T>(), "Total Value"), + input_(input), + group_index_(group_index), + source_domain_(source_domain) + { + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + const GeometryComponentFieldContext field_context{component, source_domain_}; + const int domain_size = component.attribute_domain_size(field_context.domain()); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(input_); + evaluator.add(group_index_); + evaluator.evaluate(); + const VArray<T> &values = evaluator.get_evaluated<T>(0); + const VArray<int> &group_indices = evaluator.get_evaluated<int>(1); + + if (group_indices.is_single()) { + T accumulation = T(); + for (const int i : values.index_range()) { + accumulation = values[i] + accumulation; + } + return VArray<T>::ForSingle(accumulation, domain_size); + } + + Array<T> accumulations_out(domain_size); + Map<int, T> accumulations; + for (const int i : values.index_range()) { + T &value = accumulations.lookup_or_add_default(group_indices[i]); + value = value + values[i]; + } + for (const int i : values.index_range()) { + accumulations_out[i] = accumulations.lookup(group_indices[i]); + } + + return component.attribute_try_adapt_domain<T>( + VArray<T>::ForContainer(std::move(accumulations_out)), source_domain_, domain); + } + + uint64_t hash() const override + { + return get_default_hash_3(input_, group_index_, source_domain_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const TotalFieldInput *other_field = dynamic_cast<const TotalFieldInput *>(&other)) { + return input_ == other_field->input_ && group_index_ == other_field->group_index_ && + source_domain_ == other_field->source_domain_; + } + return false; + } +}; + +template<typename T> std::string identifier_suffix() +{ + if constexpr (std::is_same_v<T, int>) { + return "Int"; + } + if constexpr (std::is_same_v<T, float>) { + return "Float"; + } + if constexpr (std::is_same_v<T, float3>) { + return "Vector"; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeAccumulateField &storage = node_storage(params.node()); + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const AttributeDomain source_domain = static_cast<AttributeDomain>(storage.domain); + + Field<int> group_index_field = params.extract_input<Field<int>>("Group Index"); + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + if constexpr (std::is_same_v<T, int> || std::is_same_v<T, float> || + std::is_same_v<T, float3>) { + const std::string suffix = " " + identifier_suffix<T>(); + Field<T> input_field = params.extract_input<Field<T>>("Value" + suffix); + if (params.output_is_required("Leading" + suffix)) { + params.set_output( + "Leading" + suffix, + Field<T>{std::make_shared<AccumulateFieldInput<T>>( + source_domain, input_field, group_index_field, AccumulationMode::Leading)}); + } + if (params.output_is_required("Trailing" + suffix)) { + params.set_output( + "Trailing" + suffix, + Field<T>{std::make_shared<AccumulateFieldInput<T>>( + source_domain, input_field, group_index_field, AccumulationMode::Trailing)}); + } + if (params.output_is_required("Total" + suffix)) { + params.set_output("Total" + suffix, + Field<T>{std::make_shared<TotalFieldInput<T>>( + source_domain, input_field, group_index_field)}); + } + } + }); +} +} // namespace blender::nodes::node_geo_accumulate_field_cc + +void register_node_type_geo_accumulate_field() +{ + namespace file_ns = blender::nodes::node_geo_accumulate_field_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ACCUMULATE_FIELD, "Accumulate Field", NODE_CLASS_CONVERTER); + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + node_type_storage( + &ntype, "NodeAccumulateField", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index 43fb00a482c..840dfd2fbd3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -19,51 +19,51 @@ #include "BKE_attribute_math.hh" +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_attribute_capture_cc { + +NODE_STORAGE_FUNCS(NodeGeometryAttributeCapture) -static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Value").supports_field(); - b.add_input<decl::Float>("Value", "Value_001").supports_field(); - b.add_input<decl::Color>("Value", "Value_002").supports_field(); - b.add_input<decl::Bool>("Value", "Value_003").supports_field(); - b.add_input<decl::Int>("Value", "Value_004").supports_field(); - - b.add_output<decl::Geometry>("Geometry"); - b.add_output<decl::Vector>("Attribute").field_source(); - b.add_output<decl::Float>("Attribute", "Attribute_001").field_source(); - b.add_output<decl::Color>("Attribute", "Attribute_002").field_source(); - b.add_output<decl::Bool>("Attribute", "Attribute_003").field_source(); - b.add_output<decl::Int>("Attribute", "Attribute_004").field_source(); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Vector>(N_("Value")).supports_field(); + b.add_input<decl::Float>(N_("Value"), "Value_001").supports_field(); + b.add_input<decl::Color>(N_("Value"), "Value_002").supports_field(); + b.add_input<decl::Bool>(N_("Value"), "Value_003").supports_field(); + b.add_input<decl::Int>(N_("Value"), "Value_004").supports_field(); + + b.add_output<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Vector>(N_("Attribute")).field_source(); + b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").field_source(); + b.add_output<decl::Color>(N_("Attribute"), "Attribute_002").field_source(); + b.add_output<decl::Bool>(N_("Attribute"), "Attribute_003").field_source(); + b.add_output<decl::Int>(N_("Attribute"), "Attribute_004").field_source(); } -static void geo_node_attribute_capture_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); } -static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN( - sizeof(NodeGeometryAttributeCapture), __func__); + NodeGeometryAttributeCapture *data = MEM_cnew<NodeGeometryAttributeCapture>(__func__); data->data_type = CD_PROP_FLOAT; data->domain = ATTR_DOMAIN_POINT; node->storage = data; } -static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) - node->storage; + const NodeGeometryAttributeCapture &storage = node_storage(*node); const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); bNodeSocket *socket_value_geometry = (bNodeSocket *)node->inputs.first; @@ -73,11 +73,11 @@ static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *n bNodeSocket *socket_value_boolean = socket_value_color4f->next; bNodeSocket *socket_value_int32 = socket_value_boolean->next; - nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR); - nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL); - nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, socket_value_int32, data_type == CD_PROP_INT32); bNodeSocket *out_socket_value_geometry = (bNodeSocket *)node->outputs.first; bNodeSocket *out_socket_value_vector = out_socket_value_geometry->next; @@ -86,11 +86,38 @@ static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *n bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next; bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next; - nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR); - nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL); - nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, out_socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, out_socket_value_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, out_socket_value_int32, data_type == CD_PROP_INT32); +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + search_link_ops_for_declarations(params, declaration.outputs().take_front(1)); + + const bNodeType &node_type = params.node_type(); + const std::optional<CustomDataType> type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + if (params.in_out() == SOCK_OUT) { + params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Attribute"); + }); + } + else { + params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Value"); + }); + } + } } static void try_capture_field_on_geometry(GeometryComponent &component, @@ -113,13 +140,11 @@ static void try_capture_field_on_geometry(GeometryComponent &component, output_attribute.save(); } -static void geo_node_attribute_capture_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - const bNode &node = params.node(); - const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) - node.storage; + const NodeGeometryAttributeCapture &storage = node_storage(params.node()); const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); @@ -144,20 +169,33 @@ static void geo_node_attribute_capture_exec(GeoNodeExecParams params) break; } - WeakAnonymousAttributeID anonymous_id{"Attribute Capture"}; + WeakAnonymousAttributeID anonymous_id{"Attribute"}; const CPPType &type = field.cpp_type(); - static const Array<GeometryComponentType> types = { - GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; - for (const GeometryComponentType type : types) { - if (geometry_set.has(type)) { - GeometryComponent &component = geometry_set.get_component_for_write(type); + /* Run on the instances component separately to only affect the top level of instances. */ + if (domain == ATTR_DOMAIN_INSTANCE) { + if (geometry_set.has_instances()) { + GeometryComponent &component = geometry_set.get_component_for_write( + GEO_COMPONENT_TYPE_INSTANCES); try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); } } + else { + static const Array<GeometryComponentType> types = { + GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + for (const GeometryComponentType type : types) { + if (geometry_set.has(type)) { + GeometryComponent &component = geometry_set.get_component_for_write(type); + try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); + } + } + }); + } - GField output_field{ - std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)}; + GField output_field{std::make_shared<bke::AnonymousAttributeFieldInput>( + std::move(anonymous_id), type, params.attribute_producer_name())}; switch (data_type) { case CD_PROP_FLOAT: { @@ -187,22 +225,25 @@ static void geo_node_attribute_capture_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_attribute_capture_cc void register_node_type_geo_attribute_capture() { + namespace file_ns = blender::nodes::node_geo_attribute_capture_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_CAPTURE_ATTRIBUTE, "Capture Attribute", NODE_CLASS_ATTRIBUTE); node_type_storage(&ntype, "NodeGeometryAttributeCapture", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update); - ntype.declare = blender::nodes::geo_node_attribute_capture_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc new file mode 100644 index 00000000000..609ef39eb3f --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc @@ -0,0 +1,154 @@ +/* + * 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" + +namespace blender::nodes::node_geo_attribute_domain_size_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_output<decl::Int>("Point Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_MESH; + }); + b.add_output<decl::Int>("Edge Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_MESH; + }); + b.add_output<decl::Int>("Face Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_MESH; + }); + b.add_output<decl::Int>("Face Corner Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_MESH; + }); + b.add_output<decl::Int>("Spline Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_CURVE; + }); + b.add_output<decl::Int>("Instance Count").make_available([](bNode &node) { + node.custom1 = GEO_COMPONENT_TYPE_INSTANCES; + }); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "component", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = GEO_COMPONENT_TYPE_MESH; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + bNodeSocket *point_socket = (bNodeSocket *)node->outputs.first; + bNodeSocket *edge_socket = point_socket->next; + bNodeSocket *face_socket = edge_socket->next; + bNodeSocket *face_corner_socket = face_socket->next; + bNodeSocket *spline_socket = face_corner_socket->next; + bNodeSocket *instances_socket = spline_socket->next; + + nodeSetSocketAvailability(ntree, + point_socket, + ELEM(node->custom1, + GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_POINT_CLOUD)); + nodeSetSocketAvailability(ntree, edge_socket, node->custom1 == GEO_COMPONENT_TYPE_MESH); + nodeSetSocketAvailability(ntree, face_socket, node->custom1 == GEO_COMPONENT_TYPE_MESH); + nodeSetSocketAvailability(ntree, face_corner_socket, node->custom1 == GEO_COMPONENT_TYPE_MESH); + nodeSetSocketAvailability(ntree, spline_socket, node->custom1 == GEO_COMPONENT_TYPE_CURVE); + nodeSetSocketAvailability( + ntree, instances_socket, node->custom1 == GEO_COMPONENT_TYPE_INSTANCES); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometryComponentType component = (GeometryComponentType)params.node().custom1; + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + switch (component) { + case GEO_COMPONENT_TYPE_MESH: { + if (geometry_set.has_mesh()) { + const MeshComponent *component = geometry_set.get_component_for_read<MeshComponent>(); + params.set_output("Point Count", component->attribute_domain_size(ATTR_DOMAIN_POINT)); + params.set_output("Edge Count", component->attribute_domain_size(ATTR_DOMAIN_EDGE)); + params.set_output("Face Count", component->attribute_domain_size(ATTR_DOMAIN_FACE)); + params.set_output("Face Corner Count", + component->attribute_domain_size(ATTR_DOMAIN_CORNER)); + } + else { + params.set_default_remaining_outputs(); + } + break; + } + case GEO_COMPONENT_TYPE_CURVE: { + if (geometry_set.has_curve()) { + const CurveComponent *component = geometry_set.get_component_for_read<CurveComponent>(); + params.set_output("Point Count", component->attribute_domain_size(ATTR_DOMAIN_POINT)); + params.set_output("Spline Count", component->attribute_domain_size(ATTR_DOMAIN_CURVE)); + } + else { + params.set_default_remaining_outputs(); + } + break; + } + case GEO_COMPONENT_TYPE_POINT_CLOUD: { + if (geometry_set.has_pointcloud()) { + const PointCloudComponent *component = + geometry_set.get_component_for_read<PointCloudComponent>(); + params.set_output("Point Count", component->attribute_domain_size(ATTR_DOMAIN_POINT)); + } + else { + params.set_default_remaining_outputs(); + } + break; + } + case GEO_COMPONENT_TYPE_INSTANCES: { + if (geometry_set.has_instances()) { + const InstancesComponent *component = + geometry_set.get_component_for_read<InstancesComponent>(); + params.set_output("Instance Count", + component->attribute_domain_size(ATTR_DOMAIN_INSTANCE)); + } + else { + params.set_default_remaining_outputs(); + } + break; + } + default: + BLI_assert_unreachable(); + } +} + +} // namespace blender::nodes::node_geo_attribute_domain_size_cc + +void register_node_type_geo_attribute_domain_size() +{ + namespace file_ns = blender::nodes::node_geo_attribute_domain_size_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, "Domain Size", NODE_CLASS_ATTRIBUTE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + node_type_init(&ntype, file_ns::node_init); + ntype.updatefunc = file_ns::node_update; + + nodeRegisterType(&ntype); +} 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 f93ef6f1db3..8ed50b2cc75 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -16,13 +16,13 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_attribute_remove_cc { -static void geo_node_attribute_remove_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Attribute").multi_input(); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::String>(N_("Attribute")).multi_input(); + b.add_output<decl::Geometry>(N_("Geometry")); } static void remove_attribute(GeometryComponent &component, @@ -42,7 +42,7 @@ static void remove_attribute(GeometryComponent &component, } } -static void geo_node_attribute_remove_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); Vector<std::string> attribute_names = params.extract_multi_input<std::string>("Attribute"); @@ -59,18 +59,23 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) remove_attribute( geometry_set.get_component_for_write<CurveComponent>(), params, attribute_names); } + if (geometry_set.has<InstancesComponent>()) { + remove_attribute( + geometry_set.get_component_for_write<InstancesComponent>(), params, attribute_names); + } params.set_output("Geometry", geometry_set); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_attribute_remove_cc void register_node_type_geo_attribute_remove() { + namespace file_ns = blender::nodes::node_geo_attribute_remove_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_REMOVE, "Attribute Remove", NODE_CLASS_ATTRIBUTE, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_remove_exec; - ntype.declare = blender::nodes::geo_node_attribute_remove_declare; + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_REMOVE, "Attribute Remove", NODE_CLASS_ATTRIBUTE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index 1b7d2fe28a1..7df032b150b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -22,53 +22,55 @@ #include "BLI_math_base_safe.h" +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_attribute_statistic_cc { -static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Attribute").hide_value().supports_field(); - b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value().supports_field(); - - b.add_output<decl::Float>("Mean"); - b.add_output<decl::Float>("Median"); - b.add_output<decl::Float>("Sum"); - b.add_output<decl::Float>("Min"); - b.add_output<decl::Float>("Max"); - b.add_output<decl::Float>("Range"); - b.add_output<decl::Float>("Standard Deviation"); - b.add_output<decl::Float>("Variance"); - - b.add_output<decl::Vector>("Mean", "Mean_001"); - b.add_output<decl::Vector>("Median", "Median_001"); - b.add_output<decl::Vector>("Sum", "Sum_001"); - b.add_output<decl::Vector>("Min", "Min_001"); - b.add_output<decl::Vector>("Max", "Max_001"); - b.add_output<decl::Vector>("Range", "Range_001"); - b.add_output<decl::Vector>("Standard Deviation", "Standard Deviation_001"); - b.add_output<decl::Vector>("Variance", "Variance_001"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Float>(N_("Attribute")).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); + + b.add_output<decl::Float>(N_("Mean")); + b.add_output<decl::Float>(N_("Median")); + b.add_output<decl::Float>(N_("Sum")); + b.add_output<decl::Float>(N_("Min")); + b.add_output<decl::Float>(N_("Max")); + b.add_output<decl::Float>(N_("Range")); + b.add_output<decl::Float>(N_("Standard Deviation")); + b.add_output<decl::Float>(N_("Variance")); + + b.add_output<decl::Vector>(N_("Mean"), "Mean_001"); + b.add_output<decl::Vector>(N_("Median"), "Median_001"); + b.add_output<decl::Vector>(N_("Sum"), "Sum_001"); + b.add_output<decl::Vector>(N_("Min"), "Min_001"); + b.add_output<decl::Vector>(N_("Max"), "Max_001"); + b.add_output<decl::Vector>(N_("Range"), "Range_001"); + b.add_output<decl::Vector>(N_("Standard Deviation"), "Standard Deviation_001"); + b.add_output<decl::Vector>(N_("Variance"), "Variance_001"); } -static void geo_node_attribute_statistic_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); } -static void geo_node_attribute_statistic_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { node->custom1 = CD_PROP_FLOAT; node->custom2 = ATTR_DOMAIN_POINT; } -static void geo_node_attribute_statistic_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first; - bNodeSocket *socket_float_attr = socket_geo->next; + bNodeSocket *socket_selection = socket_geo->next; + bNodeSocket *socket_float_attr = socket_selection->next; bNodeSocket *socket_float3_attr = socket_float_attr->next; bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first; @@ -91,25 +93,70 @@ static void geo_node_attribute_statistic_update(bNodeTree *UNUSED(ntree), bNode const CustomDataType data_type = static_cast<CustomDataType>(node->custom1); - nodeSetSocketAvailability(socket_float_attr, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_mean, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_median, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_sum, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_min, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_max, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_range, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_std, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(socket_float_variance, data_type == CD_PROP_FLOAT); - - nodeSetSocketAvailability(socket_float3_attr, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_mean, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_median, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_sum, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_min, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_max, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_range, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_std, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(socket_vector_variance, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_float_attr, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_mean, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_median, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_sum, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_min, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_max, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_range, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_std, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_float_variance, data_type == CD_PROP_FLOAT); + + nodeSetSocketAvailability(ntree, socket_float3_attr, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_mean, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_median, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_sum, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_min, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_max, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_range, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_std, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_vector_variance, data_type == CD_PROP_FLOAT3); +} + +static std::optional<CustomDataType> node_type_from_other_socket(const bNodeSocket &socket) +{ + switch (socket.type) { + case SOCK_FLOAT: + case SOCK_BOOLEAN: + case SOCK_INT: + return CD_PROP_FLOAT; + case SOCK_VECTOR: + case SOCK_RGBA: + return CD_PROP_FLOAT3; + default: + return {}; + } +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const bNodeType &node_type = params.node_type(); + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_front(2)); + + const std::optional<CustomDataType> type = node_type_from_other_socket(params.other_socket()); + if (!type) { + return; + } + + if (params.in_out() == SOCK_IN) { + params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + node.custom1 = *type; + params.update_and_connect_available_socket(node, "Attribute"); + }); + } + else { + for (const StringRefNull name : + {"Mean", "Median", "Sum", "Min", "Max", "Range", "Standard Deviation", "Variance"}) { + params.add_item(IFACE_(name.c_str()), [node_type, name, type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + node.custom1 = *type; + params.update_and_connect_available_socket(node, name); + }); + } + } } template<typename T> static T compute_sum(const Span<T> data) @@ -146,65 +193,40 @@ static float median_of_sorted_span(const Span<float> data) } return median; } -static void set_empty(CustomDataType data_type, GeoNodeExecParams ¶ms) -{ - if (data_type == CD_PROP_FLOAT) { - params.set_output("Mean", 0.0f); - params.set_output("Median", 0.0f); - params.set_output("Sum", 0.0f); - params.set_output("Min", 0.0f); - params.set_output("Max", 0.0f); - params.set_output("Range", 0.0f); - params.set_output("Standard Deviation", 0.0f); - params.set_output("Variance", 0.0f); - } - else if (data_type == CD_PROP_FLOAT3) { - params.set_output("Mean_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Median_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Sum_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Min_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Max_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Range_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Standard Deviation_001", float3{0.0f, 0.0f, 0.0f}); - params.set_output("Variance_001", float3{0.0f, 0.0f, 0.0f}); - } -} -static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); - const bNode &node = params.node(); const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); - - int64_t total_size = 0; Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); - for (const GeometryComponent *component : components) { - if (component->attribute_domain_supported(domain)) { - total_size += component->attribute_domain_size(domain); - } - } - if (total_size == 0) { - set_empty(data_type, params); - return; - } + const Field<bool> selection_field = params.get_input<Field<bool>>("Selection"); switch (data_type) { case CD_PROP_FLOAT: { const Field<float> input_field = params.get_input<Field<float>>("Attribute"); - Array<float> data = Array<float>(total_size); - int offset = 0; + Vector<float> data; for (const GeometryComponent *component : components) { if (component->attribute_domain_supported(domain)) { GeometryComponentFieldContext field_context{*component, domain}; const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; - MutableSpan<float> component_result = data.as_mutable_span().slice(offset, domain_size); - data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.add(input_field); + data_evaluator.set_selection(selection_field); data_evaluator.evaluate(); - offset += domain_size; + const VArray<float> &component_data = data_evaluator.get_evaluated<float>(0); + const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask(); + + const int next_data_index = data.size(); + data.resize(next_data_index + selection.size()); + MutableSpan<float> selected_data = data.as_mutable_span().slice(next_data_index, + selection.size()); + for (const int i : selection.index_range()) { + selected_data[i] = component_data[selection[i]]; + } } } @@ -225,7 +247,7 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) const bool variance_required = params.output_is_required("Standard Deviation") || params.output_is_required("Variance"); - if (total_size != 0) { + if (data.size() != 0) { if (sort_required) { std::sort(data.begin(), data.end()); median = median_of_sorted_span(data); @@ -236,7 +258,7 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) } if (sum_required || variance_required) { sum = compute_sum<float>(data); - mean = sum / total_size; + mean = sum / data.size(); if (variance_required) { variance = compute_variance(data, mean); @@ -263,18 +285,26 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) } case CD_PROP_FLOAT3: { const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001"); - - Array<float3> data = Array<float3>(total_size); - int offset = 0; + Vector<float3> data; for (const GeometryComponent *component : components) { if (component->attribute_domain_supported(domain)) { GeometryComponentFieldContext field_context{*component, domain}; const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; - MutableSpan<float3> component_result = data.as_mutable_span().slice(offset, domain_size); - data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.add(input_field); + data_evaluator.set_selection(selection_field); data_evaluator.evaluate(); - offset += domain_size; + const VArray<float3> &component_data = data_evaluator.get_evaluated<float3>(0); + const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask(); + + const int next_data_index = data.size(); + data.resize(data.size() + selection.size()); + MutableSpan<float3> selected_data = data.as_mutable_span().slice(next_data_index, + selection.size()); + for (const int i : selection.index_range()) { + selected_data[i] = component_data[selection[i]]; + } } } @@ -299,9 +329,9 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) Array<float> data_y; Array<float> data_z; if (sort_required || variance_required) { - data_x.reinitialize(total_size); - data_y.reinitialize(total_size); - data_z.reinitialize(total_size); + data_x.reinitialize(data.size()); + data_y.reinitialize(data.size()); + data_z.reinitialize(data.size()); for (const int i : data.index_range()) { data_x[i] = data[i].x; data_y[i] = data[i].y; @@ -309,7 +339,7 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) } } - if (total_size != 0) { + if (data.size() != 0) { if (sort_required) { std::sort(data_x.begin(), data_x.end()); std::sort(data_y.begin(), data_y.end()); @@ -326,7 +356,7 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) } if (sum_required || variance_required) { sum = compute_sum(data.as_span()); - mean = sum / total_size; + mean = sum / data.size(); if (variance_required) { const float x_variance = compute_variance(data_x, mean.x); @@ -360,19 +390,22 @@ static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_attribute_statistic_cc void register_node_type_geo_attribute_statistic() { + namespace file_ns = blender::nodes::node_geo_attribute_statistic_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE, 0); - - ntype.declare = blender::nodes::geo_node_attribute_statistic_declare; - node_type_init(&ntype, blender::nodes::geo_node_attribute_statistic_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_statistic_update); - ntype.geometry_node_execute = blender::nodes::geo_node_attribute_statistic_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_statistic_layout; + &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE); + + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; 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 21b425c0ed4..a9158e0ef7a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -23,23 +23,25 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_boolean_cc { -static void geo_node_boolean_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry 1"); - b.add_input<decl::Geometry>("Geometry 2").multi_input(); - b.add_input<decl::Bool>("Self Intersection"); - b.add_input<decl::Bool>("Hole Tolerant"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Mesh 1")) + .only_realized_data() + .supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Geometry>(N_("Mesh 2")).multi_input().supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Self Intersection")); + b.add_input<decl::Bool>(N_("Hole Tolerant")); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } -static void geo_node_boolean_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)node->custom1; @@ -49,24 +51,24 @@ static void geo_node_boolean_update(bNodeTree *UNUSED(ntree), bNode *node) 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")); + nodeSetSocketAvailability(ntree, geometry_1_socket, false); + nodeSetSocketAvailability(ntree, geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Mesh")); 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")); + nodeSetSocketAvailability(ntree, geometry_1_socket, true); + nodeSetSocketAvailability(ntree, geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Mesh 2")); break; } } -static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE; } -static void geo_node_boolean_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; const bool use_self = params.get_input<bool>("Self Intersection"); @@ -82,12 +84,7 @@ static void geo_node_boolean_exec(GeoNodeExecParams params) GeometrySet set_a; if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) { - set_a = params.extract_input<GeometrySet>("Geometry 1"); - if (set_a.has_instances()) { - params.error_message_add( - NodeWarningType::Info, - TIP_("Instances are not supported for the first geometry input, and will not be used")); - } + set_a = params.extract_input<GeometrySet>("Mesh 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. */ @@ -101,7 +98,7 @@ static void geo_node_boolean_exec(GeoNodeExecParams params) /* 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"); + Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Mesh 2"); for (const GeometrySet &geometry_set : geometry_sets) { bke::geometry_set_gather_instances(geometry_set, set_groups); } @@ -119,20 +116,22 @@ static void geo_node_boolean_exec(GeoNodeExecParams params) Mesh *result = blender::meshintersect::direct_mesh_boolean( meshes, transforms, float4x4::identity(), {}, use_self, hole_tolerant, operation); - params.set_output("Geometry", GeometrySet::create_with_mesh(result)); + params.set_output("Mesh", GeometrySet::create_with_mesh(result)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_boolean_cc void register_node_type_geo_boolean() { + namespace file_ns = blender::nodes::node_geo_boolean_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_boolean_declare; - ntype.draw_buttons = blender::nodes::geo_node_boolean_layout; - ntype.updatefunc = blender::nodes::geo_node_boolean_update; - node_type_init(&ntype, blender::nodes::geo_node_boolean_init); - ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; + geo_node_type_base(&ntype, GEO_NODE_MESH_BOOLEAN, "Mesh Boolean", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + ntype.updatefunc = file_ns::node_update; + node_type_init(&ntype, file_ns::node_init); + ntype.geometry_node_execute = file_ns::node_geo_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 index fdc6b12095c..465bd72b57a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc @@ -14,160 +14,80 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BKE_spline.hh" -#include "BKE_volume.h" - #include "node_geometry_util.hh" -namespace blender::nodes { - -static void geo_node_bounding_box_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Geometry"); - b.add_output<decl::Geometry>("Bounding Box"); - b.add_output<decl::Vector>("Min"); - b.add_output<decl::Vector>("Max"); -} - -using bke::GeometryInstanceGroup; +namespace blender::nodes::node_geo_bounding_box_cc { -static void compute_min_max_from_position_and_transform(const GeometryComponent &component, - Span<float4x4> transforms, - float3 &r_min, - float3 &r_max) +static void node_declare(NodeDeclarationBuilder &b) { - 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); - } - } - } + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Geometry>(N_("Bounding Box")); + b.add_output<decl::Vector>(N_("Min")); + b.add_output<decl::Vector>(N_("Max")); } -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) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + /* Compute the min and max of all realized geometry for the two + * vector outputs, which are only meant to consider real 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); - } - + 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_cuboid_mesh(scale, 2, 2, 2); - transform_mesh(mesh, center, float3(0), float3(1)); - params.set_output("Bounding Box", GeometrySet::create_with_mesh(mesh)); params.set_output("Min", min); params.set_output("Max", max); } + + /* Generate the bounding box meshes inside each unique geometry set (including individually for + * every instance). Because geometry components are reference counted anyway, we can just + * repurpose the original geometry sets for the output. */ + if (params.output_is_required("Bounding Box")) { + geometry_set.modify_geometry_sets([&](GeometrySet &sub_geometry) { + float3 sub_min = float3(FLT_MAX); + float3 sub_max = float3(-FLT_MAX); + + /* Reuse the min and max calculation if this is the main "real" geometry set. */ + if (&sub_geometry == &geometry_set) { + sub_min = min; + sub_max = max; + } + else { + sub_geometry.compute_boundbox_without_instances(&sub_min, &sub_max); + } + + if (sub_min == float3(FLT_MAX)) { + sub_geometry.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + } + else { + const float3 scale = sub_max - sub_min; + const float3 center = sub_min + scale / 2.0f; + Mesh *mesh = create_cuboid_mesh(scale, 2, 2, 2); + transform_mesh(*mesh, center, float3(0), float3(1)); + sub_geometry.replace_mesh(mesh); + sub_geometry.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + } + }); + + params.set_output("Bounding Box", std::move(geometry_set)); + } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_bounding_box_cc void register_node_type_geo_bounding_box() { + namespace file_ns = blender::nodes::node_geo_bounding_box_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_BOUNDING_BOX, "Bounding Box", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_bounding_box_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_bounding_box_exec; + geo_node_type_base(&ntype, GEO_NODE_BOUNDING_BOX, "Bounding Box", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 d03221703f0..43816b8d8dc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -25,53 +25,67 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +#include <algorithm> -static void geo_node_collection_info_declare(NodeDeclarationBuilder &b) +namespace blender::nodes::node_geo_collection_info_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCollectionInfo) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Collection>("Collection").hide_label(); - b.add_input<decl::Bool>("Separate Children") - .description("Output each child of the collection as a separate instance"); - b.add_input<decl::Bool>("Reset Children") + b.add_input<decl::Collection>(N_("Collection")).hide_label(); + b.add_input<decl::Bool>(N_("Separate Children")) .description( - "Reset the transforms of every child instance in the output. Only used when Separate " - "Children is enabled"); - b.add_output<decl::Geometry>("Geometry"); + N_("Output each child of the collection as a separate instance, sorted alphabetically")); + b.add_input<decl::Bool>(N_("Reset Children")) + .description( + N_("Reset the transforms of every child instance in the output. Only used when Separate " + "Children is enabled")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_collection_info_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "transform_space", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( - sizeof(NodeGeometryCollectionInfo), __func__); + NodeGeometryCollectionInfo *data = MEM_cnew<NodeGeometryCollectionInfo>(__func__); data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; node->storage = data; } -static void geo_node_collection_info_exec(GeoNodeExecParams params) +struct InstanceListEntry { + int handle; + char *name; + float4x4 transform; +}; + +static void node_geo_exec(GeoNodeExecParams params) { Collection *collection = params.get_input<Collection *>("Collection"); - GeometrySet geometry_set_out; - if (collection == nullptr) { - params.set_output("Geometry", geometry_set_out); + params.set_default_remaining_outputs(); + return; + } + const Object *self_object = params.self_object(); + const bool is_recursive = BKE_collection_has_object_recursive_instanced(collection, + (Object *)self_object); + if (is_recursive) { + params.error_message_add(NodeWarningType::Error, "Collection contains current object"); + params.set_default_remaining_outputs(); return; } - const bNode &bnode = params.node(); - NodeGeometryCollectionInfo *node_storage = (NodeGeometryCollectionInfo *)bnode.storage; - const bool use_relative_transform = (node_storage->transform_space == + const NodeGeometryCollectionInfo &storage = node_storage(params.node()); + const bool use_relative_transform = (storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); + GeometrySet geometry_set_out; InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); - const Object *self_object = params.self_object(); - const bool separate_children = params.get_input<bool>("Separate Children"); if (separate_children) { const bool reset_children = params.get_input<bool>("Reset Children"); @@ -85,6 +99,8 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) } instances.reserve(children_collections.size() + children_objects.size()); + Vector<InstanceListEntry> entries; + entries.reserve(children_collections.size() + children_objects.size()); for (Collection *child_collection : children_collections) { float4x4 transform = float4x4::identity(); @@ -98,7 +114,7 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) } } const int handle = instances.add_reference(*child_collection); - instances.add_instance(handle, transform); + entries.append({handle, &(child_collection->id.name[2]), transform}); } for (Object *child_object : children_objects) { const int handle = instances.add_reference(*child_object); @@ -112,7 +128,16 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) } mul_m4_m4_post(transform.values, child_object->obmat); } - instances.add_instance(handle, transform); + entries.append({handle, &(child_object->id.name[2]), transform}); + } + + std::sort(entries.begin(), + entries.end(), + [](const InstanceListEntry &a, const InstanceListEntry &b) { + return BLI_strcasecmp_natural(a.name, b.name) <= 0; + }); + for (const InstanceListEntry &entry : entries) { + instances.add_instance(entry.handle, entry.transform); } } else { @@ -129,20 +154,22 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) params.set_output("Geometry", geometry_set_out); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_collection_info_cc void register_node_type_geo_collection_info() { + namespace file_ns = blender::nodes::node_geo_collection_info_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_COLLECTION_INFO, "Collection Info", NODE_CLASS_INPUT, 0); - ntype.declare = blender::nodes::geo_node_collection_info_declare; - node_type_init(&ntype, blender::nodes::geo_node_collection_info_node_init); + geo_node_type_base(&ntype, GEO_NODE_COLLECTION_INFO, "Collection Info", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_node_init); node_type_storage(&ntype, "NodeGeometryCollectionInfo", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_collection_info_exec; - ntype.draw_buttons = blender::nodes::geo_node_collection_info_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index e2bb7e9f939..093b4450657 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -22,23 +22,21 @@ #include "node_common.h" #include "node_geometry_util.hh" -void register_node_type_geo_group(void) +void register_node_type_geo_group() { static bNodeType ntype; - node_type_base_custom(&ntype, "GeometryNodeGroup", "Group", NODE_CLASS_GROUP, 0); + node_type_base_custom(&ntype, "GeometryNodeGroup", "Group", NODE_CLASS_GROUP); ntype.type = NODE_GROUP; ntype.poll = geo_node_poll_default; ntype.poll_instance = node_group_poll_instance; ntype.insert_link = node_insert_link_default; - ntype.update_internal_links = node_update_internal_links_default; ntype.rna_ext.srna = RNA_struct_find("GeometryNodeGroup"); BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); - node_type_socket_templates(&ntype, nullptr, nullptr); node_type_size(&ntype, 140, 60, 400); - node_type_label(&ntype, node_group_label); + ntype.labelfunc = node_group_label; node_type_group_update(&ntype, node_group_update); nodeRegisterType(&ntype); @@ -53,7 +51,4 @@ void register_node_type_geo_custom_group(bNodeType *ntype) 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 index 4377d32210d..11bb8a61df5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -28,12 +28,12 @@ # include "RBI_hull_api.h" #endif -namespace blender::nodes { +namespace blender::nodes::node_geo_convex_hull_cc { -static void geo_node_convex_hull_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_output<decl::Geometry>("Convex Hull"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Geometry>(N_("Convex Hull")); } using bke::GeometryInstanceGroup; @@ -169,10 +169,10 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) span_count++; const PointCloudComponent *component = geometry_set.get_component_for_read<PointCloudComponent>(); - GVArray_Typed<float3> varray = component->attribute_get_for_read<float3>( + VArray<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(); + total_size += varray.size(); + positions_span = varray.get_internal_span(); } if (geometry_set.has_curve()) { @@ -200,18 +200,18 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) 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>( + VArray<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())); + 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>( + VArray<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())); + varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); offset += varray.size(); } @@ -235,16 +235,16 @@ static void read_positions(const GeometryComponent &component, Span<float4x4> transforms, Vector<float3> *r_coords) { - GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( + VArray<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()); + r_coords->reserve(r_coords->size() + positions->size() * transforms.size()); for (const float4x4 &transform : transforms) { - for (const int i : positions.index_range()) { + for (const int i : positions->index_range()) { const float3 position = positions[i]; const float3 transformed_position = transform * position; r_coords->append(transformed_position); @@ -296,7 +296,7 @@ static Mesh *convex_hull_from_instances(const GeometrySet &geometry_set) #endif /* WITH_BULLET */ -static void geo_node_convex_hull_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -312,18 +312,20 @@ static void geo_node_convex_hull_exec(GeoNodeExecParams params) #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without Bullet")); - params.set_output("Convex Hull", geometry_set); + params.set_default_remaining_outputs(); #endif /* WITH_BULLET */ } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_convex_hull_cc void register_node_type_geo_convex_hull() { + namespace file_ns = blender::nodes::node_geo_convex_hull_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CONVEX_HULL, "Convex Hull", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_convex_hull_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_convex_hull_exec; + geo_node_type_base(&ntype, GEO_NODE_CONVEX_HULL, "Convex Hull", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc new file mode 100644 index 00000000000..666100ffd7f --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc @@ -0,0 +1,147 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_endpoint_selection_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Start Size")) + .min(0) + .default_value(1) + .supports_field() + .description(N_("The amount of points to select from the start of each spline")); + b.add_input<decl::Int>(N_("End Size")) + .min(0) + .default_value(1) + .supports_field() + .description(N_("The amount of points to select from the end of each spline")); + b.add_output<decl::Bool>(N_("Selection")) + .field_source() + .description( + N_("The selection from the start and end of the splines based on the input sizes")); +} + +static void select_by_spline(const int start, const int end, MutableSpan<bool> r_selection) +{ + const int size = r_selection.size(); + const int start_use = std::min(start, size); + const int end_use = std::min(end, size); + + r_selection.slice(0, start_use).fill(true); + r_selection.slice(size - end_use, end_use).fill(true); +} + +class EndpointFieldInput final : public GeometryFieldInput { + Field<int> start_size_; + Field<int> end_size_; + + public: + EndpointFieldInput(Field<int> start_size, Field<int> end_size) + : GeometryFieldInput(CPPType::get<bool>(), "Endpoint Selection node"), + start_size_(start_size), + end_size_(end_size) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_CURVE || domain != ATTR_DOMAIN_POINT) { + return nullptr; + } + + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + + Array<int> control_point_offsets = curve->control_point_offsets(); + + if (curve == nullptr || control_point_offsets.last() == 0) { + return nullptr; + } + + GeometryComponentFieldContext size_context{curve_component, ATTR_DOMAIN_CURVE}; + fn::FieldEvaluator evaluator{size_context, curve->splines().size()}; + evaluator.add(start_size_); + evaluator.add(end_size_); + evaluator.evaluate(); + const VArray<int> &start_size = evaluator.get_evaluated<int>(0); + const VArray<int> &end_size = evaluator.get_evaluated<int>(1); + + const int point_size = control_point_offsets.last(); + Array<bool> selection(point_size, false); + int current_point = 0; + MutableSpan<bool> selection_span = selection.as_mutable_span(); + for (int i : IndexRange(curve->splines().size())) { + const SplinePtr &spline = curve->splines()[i]; + if (start_size[i] <= 0 && end_size[i] <= 0) { + selection_span.slice(current_point, spline->size()).fill(false); + } + else { + int start_use = std::max(start_size[i], 0); + int end_use = std::max(end_size[i], 0); + select_by_spline(start_use, end_use, selection_span.slice(current_point, spline->size())); + } + current_point += spline->size(); + } + return VArray<bool>::ForContainer(std::move(selection)); + }; + + uint64_t hash() const override + { + return get_default_hash_2(start_size_, end_size_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const EndpointFieldInput *other_endpoint = dynamic_cast<const EndpointFieldInput *>( + &other)) { + return start_size_ == other_endpoint->start_size_ && end_size_ == other_endpoint->end_size_; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> start_size = params.extract_input<Field<int>>("Start Size"); + Field<int> end_size = params.extract_input<Field<int>>("End Size"); + Field<bool> selection_field{std::make_shared<EndpointFieldInput>(start_size, end_size)}; + params.set_output("Selection", std::move(selection_field)); +} +} // namespace blender::nodes::node_geo_curve_endpoint_selection_cc + +void register_node_type_geo_curve_endpoint_selection() +{ + namespace file_ns = blender::nodes::node_geo_curve_endpoint_selection_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_CURVE_ENDPOINT_SELECTION, "Endpoint Selection", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc index c30741cf786..929d9046f98 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -16,7 +16,7 @@ #include "BLI_array.hh" #include "BLI_delaunay_2d.h" -#include "BLI_double2.hh" +#include "BLI_math_vec_types.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -31,23 +31,24 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_fill_cc { -static void geo_node_curve_fill_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurveFill) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_output<decl::Geometry>("Mesh"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_curve_fill_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_fill_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryCurveFill *data = (NodeGeometryCurveFill *)MEM_callocN(sizeof(NodeGeometryCurveFill), - __func__); + NodeGeometryCurveFill *data = MEM_cnew<NodeGeometryCurveFill>(__func__); data->mode = GEO_NODE_CURVE_FILL_MODE_TRIANGULATED; node->storage = data; @@ -147,11 +148,11 @@ static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCu geometry_set.replace_curve(nullptr); } -static void geo_node_curve_fill_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; + const NodeGeometryCurveFill &storage = node_storage(params.node()); const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; geometry_set.modify_geometry_sets( @@ -160,19 +161,21 @@ static void geo_node_curve_fill_exec(GeoNodeExecParams params) params.set_output("Mesh", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_fill_cc void register_node_type_geo_curve_fill() { + namespace file_ns = blender::nodes::node_geo_curve_fill_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_FILL, "Curve Fill", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_FILL_CURVE, "Fill Curve", NODE_CLASS_GEOMETRY); - node_type_init(&ntype, blender::nodes::geo_node_curve_fill_init); + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeGeometryCurveFill", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_curve_fill_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_fill_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_fill_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index 67ce20efd9d..68b609f8045 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -25,30 +25,37 @@ #include "BKE_spline.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_fillet_cc { -static void geo_node_curve_fillet_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurveFillet) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Int>("Count").default_value(1).min(1).max(1000); - b.add_input<decl::Float>("Radius") + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Int>(N_("Count")) + .default_value(1) + .min(1) + .max(1000) + .supports_field() + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_FILLET_POLY; }); + b.add_input<decl::Float>(N_("Radius")) .min(0.0f) .max(FLT_MAX) .subtype(PropertySubType::PROP_DISTANCE) - .default_value(0.25f); - b.add_input<decl::Bool>("Limit Radius"); - b.add_output<decl::Geometry>("Curve"); + .default_value(0.25f) + .supports_field(); + b.add_input<decl::Bool>(N_("Limit Radius")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_fillet_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_fillet_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveFillet *data = (NodeGeometryCurveFillet *)MEM_callocN( - sizeof(NodeGeometryCurveFillet), __func__); + NodeGeometryCurveFillet *data = MEM_cnew<NodeGeometryCurveFillet>(__func__); data->mode = GEO_NODE_CURVE_FILLET_BEZIER; node->storage = data; @@ -58,10 +65,10 @@ struct FilletParam { GeometryNodeCurveFilletMode mode; /* Number of points to be added. */ - const VArray<int> *counts; + VArray<int> counts; /* Radii for fillet arc at all vertices. */ - const VArray<float> *radii; + VArray<float> radii; /* Whether or not fillets are allowed to overlap. */ bool limit_radius; @@ -75,14 +82,14 @@ struct FilletData { Array<int> counts; }; -static void geo_node_curve_fillet_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)node->storage; - const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; + const NodeGeometryCurveFillet &storage = node_storage(*node); + const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)storage.mode; bNodeSocket *poly_socket = ((bNodeSocket *)node->inputs.first)->next; - nodeSetSocketAvailability(poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY); + nodeSetSocketAvailability(ntree, poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY); } /* Function to get the center of a fillet. */ @@ -115,9 +122,9 @@ static Array<float3> calculate_directions(const Span<float3> positions) Array<float3> directions(size); for (const int i : IndexRange(size - 1)) { - directions[i] = (positions[i + 1] - positions[i]).normalized(); + directions[i] = math::normalize(positions[i + 1] - positions[i]); } - directions[size - 1] = (positions[0] - positions[size - 1]).normalized(); + directions[size - 1] = math::normalize(positions[0] - positions[size - 1]); return directions; } @@ -128,9 +135,9 @@ static Array<float3> calculate_axes(const Span<float3> directions) const int size = directions.size(); Array<float3> axes(size); - axes[0] = float3::cross(-directions[size - 1], directions[0]).normalized(); + axes[0] = math::normalize(math::cross(-directions[size - 1], directions[0])); for (const int i : IndexRange(1, size - 1)) { - axes[i] = float3::cross(-directions[i - 1], directions[i]).normalized(); + axes[i] = math::normalize(math::cross(-directions[i - 1], directions[i])); } return axes; @@ -159,7 +166,7 @@ static Array<int> calculate_counts(const FilletParam &fillet_param, Array<int> counts(size, 1); if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) { for (const int i : IndexRange(size)) { - counts[i] = (*fillet_param.counts)[spline_offset + i]; + counts[i] = fillet_param.counts[spline_offset + i]; } } if (!cyclic) { @@ -177,12 +184,12 @@ static Array<float> calculate_radii(const FilletParam &fillet_param, Array<float> radii(size, 0.0f); if (fillet_param.limit_radius) { for (const int i : IndexRange(size)) { - radii[i] = std::max((*fillet_param.radii)[spline_offset + i], 0.0f); + radii[i] = std::max(fillet_param.radii[spline_offset + i], 0.0f); } } else { for (const int i : IndexRange(size)) { - radii[i] = (*fillet_param.radii)[spline_offset + i]; + radii[i] = fillet_param.radii[spline_offset + i]; } } @@ -241,8 +248,8 @@ static void limit_radii(FilletData &fd, const bool cyclic) if (cyclic) { /* Calculate lengths between adjacent control points. */ - const float len_prev = float3::distance(positions[0], positions[size - 1]); - const float len_next = float3::distance(positions[0], positions[1]); + const float len_prev = math::distance(positions[0], positions[size - 1]); + const float len_next = math::distance(positions[0], positions[1]); /* Calculate tangent lengths of fillets in control points. */ const float tan_len = radii[0] * tan(angles[0] / 2.0f); @@ -264,16 +271,16 @@ static void limit_radii(FilletData &fd, const bool cyclic) } /* Initialize max_radii to largest possible radii. */ - float prev_dist = float3::distance(positions[1], positions[0]); + float prev_dist = math::distance(positions[1], positions[0]); for (const int i : IndexRange(1, size - 2)) { - const float temp_dist = float3::distance(positions[i], positions[i + 1]); + const float temp_dist = math::distance(positions[i], positions[i + 1]); max_radii[i] = std::min(prev_dist, temp_dist) / tan(angles[i] / 2.0f); prev_dist = temp_dist; } /* Max radii calculations for each index. */ for (const int i : IndexRange(start, fillet_count - 1)) { - const float len_next = float3::distance(positions[i], positions[i + 1]); + const float len_next = math::distance(positions[i], positions[i + 1]); const float tan_len = radii[i] * tan(angles[i] / 2.0f); const float tan_len_next = radii[i + 1] * tan(angles[i + 1] / 2.0f); @@ -331,14 +338,17 @@ static void copy_common_attributes_by_mapping(const Spline &src, copy_attribute_by_mapping(src.radii(), dst.radii(), mapping); copy_attribute_by_mapping(src.tilts(), dst.tilts(), mapping); - dst.attributes.reallocate(1); src.attributes.foreach_attribute( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); if (dst.attributes.create(attribute_id, meta_data.data_type)) { std::optional<GMutableSpan> dst_attribute = dst.attributes.get_for_write(attribute_id); if (dst_attribute) { - src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data()); + attribute_math::convert_to_static_type(dst_attribute->type(), [&](auto dummy) { + using T = decltype(dummy); + copy_attribute_by_mapping( + src_attribute->typed<T>(), dst_attribute->typed<T>(), mapping); + }); return true; } } @@ -405,7 +415,8 @@ static void update_bezier_positions(const FilletData &fd, const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src); /* Calculate the vector of the radius formed by the first vertex. */ float3 radius_vec = dst_spline.positions()[i_dst] - center; - const float radius = radius_vec.normalize_and_get_length(); + float radius; + radius_vec = math::normalize_and_get_length(radius_vec, radius); dst_spline.handle_types_right().slice(1, count - 2).fill(BezierSpline::HandleType::Align); dst_spline.handle_types_left().slice(1, count - 2).fill(BezierSpline::HandleType::Align); @@ -589,13 +600,13 @@ static void calculate_curve_fillet(GeometrySet &geometry_set, field_evaluator.evaluate(); - fillet_param.radii = &field_evaluator.get_evaluated<float>(0); - if (fillet_param.radii->is_single() && fillet_param.radii->get_internal_single() < 0.0f) { + fillet_param.radii = field_evaluator.get_evaluated<float>(0); + if (fillet_param.radii.is_single() && fillet_param.radii.get_internal_single() < 0.0f) { return; } if (mode == GEO_NODE_CURVE_FILLET_POLY) { - fillet_param.counts = &field_evaluator.get_evaluated<int>(1); + fillet_param.counts = field_evaluator.get_evaluated<int>(1); } fillet_param.limit_radius = limit_radius; @@ -606,12 +617,12 @@ static void calculate_curve_fillet(GeometrySet &geometry_set, geometry_set.replace_curve(output_curve.release()); } -static void geo_node_fillet_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage; - const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; + const NodeGeometryCurveFillet &storage = node_storage(params.node()); + const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)storage.mode; Field<float> radius_field = params.extract_input<Field<float>>("Radius"); const bool limit_radius = params.extract_input<bool>("Limit Radius"); @@ -628,19 +639,21 @@ static void geo_node_fillet_exec(GeoNodeExecParams params) params.set_output("Curve", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_fillet_cc void register_node_type_geo_curve_fillet() { + namespace file_ns = blender::nodes::node_geo_curve_fillet_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_FILLET, "Curve Fillet", NODE_CLASS_GEOMETRY, 0); - ntype.draw_buttons = blender::nodes::geo_node_curve_fillet_layout; + geo_node_type_base(&ntype, GEO_NODE_FILLET_CURVE, "Fillet Curve", NODE_CLASS_GEOMETRY); + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeGeometryCurveFillet", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_curve_fillet_declare; - node_type_init(&ntype, blender::nodes::geo_node_curve_fillet_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_fillet_update); - ntype.geometry_node_execute = blender::nodes::geo_node_fillet_exec; + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc new file mode 100644 index 00000000000..e4e87e519f7 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_handle_type_selection.cc @@ -0,0 +1,169 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_handle_type_selection_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCurveSelectHandles) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Bool>(N_("Selection")).field_source(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSelectHandles *data = MEM_cnew<NodeGeometryCurveSelectHandles>(__func__); + + data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + node->storage = data; +} + +static BezierSpline::HandleType handle_type_from_input_type(const GeometryNodeCurveHandleType type) +{ + switch (type) { + case GEO_NODE_CURVE_HANDLE_AUTO: + return BezierSpline::HandleType::Auto; + case GEO_NODE_CURVE_HANDLE_ALIGN: + return BezierSpline::HandleType::Align; + case GEO_NODE_CURVE_HANDLE_FREE: + return BezierSpline::HandleType::Free; + case GEO_NODE_CURVE_HANDLE_VECTOR: + return BezierSpline::HandleType::Vector; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static void select_by_handle_type(const CurveEval &curve, + const BezierSpline::HandleType type, + const GeometryNodeCurveHandleMode mode, + const MutableSpan<bool> r_selection) +{ + int offset = 0; + for (const SplinePtr &spline : curve.splines()) { + if (spline->type() != Spline::Type::Bezier) { + r_selection.slice(offset, spline->size()).fill(false); + offset += spline->size(); + } + else { + BezierSpline *b = static_cast<BezierSpline *>(spline.get()); + for (int i : IndexRange(b->size())) { + r_selection[offset++] = (mode & GEO_NODE_CURVE_HANDLE_LEFT && + b->handle_types_left()[i] == type) || + (mode & GEO_NODE_CURVE_HANDLE_RIGHT && + b->handle_types_right()[i] == type); + } + } + } +} + +class HandleTypeFieldInput final : public GeometryFieldInput { + BezierSpline::HandleType type_; + GeometryNodeCurveHandleMode mode_; + + public: + HandleTypeFieldInput(BezierSpline::HandleType type, GeometryNodeCurveHandleMode mode) + : GeometryFieldInput(CPPType::get<bool>(), "Handle Type Selection node"), + type_(type), + mode_(mode) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + if (component.type() != GEO_COMPONENT_TYPE_CURVE) { + return {}; + } + + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve == nullptr) { + return {}; + } + + if (domain == ATTR_DOMAIN_POINT) { + Array<bool> selection(mask.min_array_size()); + select_by_handle_type(*curve, type_, mode_, selection); + return VArray<bool>::ForContainer(std::move(selection)); + } + return {}; + } + + uint64_t hash() const override + { + return get_default_hash_2((int)mode_, (int)type_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const HandleTypeFieldInput *>(&other) != nullptr; + if (const HandleTypeFieldInput *other_handle_selection = + dynamic_cast<const HandleTypeFieldInput *>(&other)) { + return mode_ == other_handle_selection->mode_ && type_ == other_handle_selection->type_; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSelectHandles &storage = node_storage(params.node()); + const BezierSpline::HandleType handle_type = handle_type_from_input_type( + (GeometryNodeCurveHandleType)storage.handle_type); + const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode; + + Field<bool> selection_field{std::make_shared<HandleTypeFieldInput>(handle_type, mode)}; + params.set_output("Selection", std::move(selection_field)); +} + +} // namespace blender::nodes::node_geo_curve_handle_type_selection_cc + +void register_node_type_geo_curve_handle_type_selection() +{ + namespace file_ns = blender::nodes::node_geo_curve_handle_type_selection_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_CURVE_HANDLE_TYPE_SELECTION, "Handle Type Selection", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_storage(&ntype, + "NodeGeometryCurveSelectHandles", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = file_ns::node_layout; + + 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 index ac7df35bb72..21ae88a6852 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc @@ -17,19 +17,19 @@ #include "BKE_spline.hh" #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_length_cc { -static void geo_node_curve_length_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_output<decl::Float>("Length"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_output<decl::Float>(N_("Length")); } -static void geo_node_curve_length_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); if (!curve_set.has_curve()) { - params.set_output("Length", 0.0f); + params.set_default_remaining_outputs(); return; } const CurveEval &curve = *curve_set.get_curve_for_read(); @@ -40,14 +40,16 @@ static void geo_node_curve_length_exec(GeoNodeExecParams params) params.set_output("Length", length); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_length_cc void register_node_type_geo_curve_length() { + namespace file_ns = blender::nodes::node_geo_curve_length_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_LENGTH, "Curve Length", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_length_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_length_exec; + geo_node_type_base(&ntype, GEO_NODE_CURVE_LENGTH, "Curve Length", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc deleted file mode 100644 index 90853387ec7..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc +++ /dev/null @@ -1,206 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "BLI_task.hh" - -#include "BKE_spline.hh" - -#include "node_geometry_util.hh" - -namespace blender::nodes { - -static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b) -{ - b.add_output<decl::Float>("Factor").field_source(); -} - -/** - * A basic interpolation from the point domain to the spline domain would be useless, since the - * average parameter for each spline would just be 0.5, or close to it. Instead, the parameter for - * each spline is the portion of the total length at the start of the spline. - */ -static Array<float> curve_parameter_spline_domain(const CurveEval &curve, const IndexMask mask) -{ - Span<SplinePtr> splines = curve.splines(); - float length = 0.0f; - Array<float> parameters(splines.size()); - for (const int i : splines.index_range()) { - parameters[i] = length; - length += splines[i]->length(); - } - const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; - mask.foreach_index([&](const int64_t i) { parameters[i] *= total_length_inverse; }); - - return parameters; -} - -/** - * The parameter at each control point is the factor at the corresponding evaluated point. - */ -static void calculate_bezier_parameters(const BezierSpline &spline, MutableSpan<float> parameters) -{ - Span<int> offsets = spline.control_point_offsets(); - Span<float> lengths = spline.evaluated_lengths(); - const float total_length = spline.length(); - const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; - - for (const int i : IndexRange(1, spline.size() - 1)) { - parameters[i] = lengths[offsets[i] - 1] * total_length_inverse; - } -} - -/** - * The parameter for poly splines is simply the evaluated lengths divided by the total length. - */ -static void calculate_poly_parameters(const PolySpline &spline, MutableSpan<float> parameters) -{ - Span<float> lengths = spline.evaluated_lengths(); - const float total_length = spline.length(); - const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; - - for (const int i : IndexRange(1, spline.size() - 1)) { - parameters[i] = lengths[i - 1] * total_length_inverse; - } -} - -/** - * Since NURBS control points do not necessarily coincide with the evaluated curve's path, and - * each control point doesn't correspond well to a specific evaluated point, the parameter at - * each point is not well defined. So instead, treat the control points as if they were a poly - * spline. - */ -static void calculate_nurbs_parameters(const NURBSpline &spline, MutableSpan<float> parameters) -{ - Span<float3> positions = spline.positions(); - Array<float> control_point_lengths(spline.size()); - - float length = 0.0f; - for (const int i : IndexRange(positions.size() - 1)) { - parameters[i] = length; - length += float3::distance(positions[i], positions[i + 1]); - } - - const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; - for (float ¶meter : parameters) { - parameter *= total_length_inverse; - } -} - -static Array<float> curve_parameter_point_domain(const CurveEval &curve) -{ - Span<SplinePtr> splines = curve.splines(); - Array<int> offsets = curve.control_point_offsets(); - const int total_size = offsets.last(); - Array<float> parameters(total_size); - - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - const Spline &spline = *splines[i]; - MutableSpan spline_factors{parameters.as_mutable_span().slice(offsets[i], spline.size())}; - spline_factors.first() = 0.0f; - switch (splines[i]->type()) { - case Spline::Type::Bezier: { - calculate_bezier_parameters(static_cast<const BezierSpline &>(spline), spline_factors); - break; - } - case Spline::Type::Poly: { - calculate_poly_parameters(static_cast<const PolySpline &>(spline), spline_factors); - break; - } - case Spline::Type::NURBS: { - calculate_nurbs_parameters(static_cast<const NURBSpline &>(spline), spline_factors); - break; - } - } - } - }); - return parameters; -} - -static const GVArray *construct_curve_parameter_gvarray(const CurveEval &curve, - const IndexMask mask, - const AttributeDomain domain, - ResourceScope &scope) -{ - if (domain == ATTR_DOMAIN_POINT) { - Array<float> parameters = curve_parameter_point_domain(curve); - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); - } - - if (domain == ATTR_DOMAIN_CURVE) { - Array<float> parameters = curve_parameter_spline_domain(curve, mask); - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); - } - - return nullptr; -} - -class CurveParameterFieldInput final : public fn::FieldInput { - public: - CurveParameterFieldInput() : fn::FieldInput(CPPType::get<float>(), "Curve Parameter") - { - } - - const GVArray *get_varray_for_context(const fn::FieldContext &context, - IndexMask mask, - ResourceScope &scope) const final - { - if (const GeometryComponentFieldContext *geometry_context = - dynamic_cast<const GeometryComponentFieldContext *>(&context)) { - - const GeometryComponent &component = geometry_context->geometry_component(); - const AttributeDomain domain = geometry_context->domain(); - - if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - const CurveEval *curve = curve_component.get_for_read(); - if (curve) { - return construct_curve_parameter_gvarray(*curve, mask, domain, scope); - } - } - } - return nullptr; - } - - uint64_t hash() const override - { - /* Some random constant hash. */ - return 29837456298; - } - - bool is_equal_to(const fn::FieldNode &other) const override - { - return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr; - } -}; - -static void geo_node_curve_parameter_exec(GeoNodeExecParams params) -{ - Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()}; - params.set_output("Factor", std::move(parameter_field)); -} - -} // namespace blender::nodes - -void register_node_type_geo_curve_parameter() -{ - static bNodeType ntype; - - geo_node_type_base(&ntype, GEO_NODE_CURVE_PARAMETER, "Curve Parameter", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_curve_parameter_exec; - ntype.declare = blender::nodes::geo_node_curve_parameter_declare; - nodeRegisterType(&ntype); -} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc new file mode 100644 index 00000000000..3f6298168a2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc @@ -0,0 +1,391 @@ +/* + * 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 "BLI_math_base_safe.h" +#include "UI_interface.h" +#include "UI_resources.h" +#include "node_geometry_util.hh" +#include <numeric> + +namespace blender::nodes::node_geo_curve_primitive_arc_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveArc) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Resolution")) + .default_value(16) + .min(2) + .max(256) + .subtype(PROP_UNSIGNED) + .description(N_("The number of points on the arc")); + b.add_input<decl::Vector>(N_("Start")) + .default_value({-1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the first control point")); + b.add_input<decl::Vector>(N_("Middle")) + .default_value({0.0f, 2.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the middle control point")); + b.add_input<decl::Vector>(N_("End")) + .default_value({1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the last control point")); + b.add_input<decl::Float>(N_("Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance of the points from the origin")); + b.add_input<decl::Float>(N_("Start Angle")) + .default_value(0.0f) + .subtype(PROP_ANGLE) + .description(N_("Starting angle of the arc")); + b.add_input<decl::Float>(N_("Sweep Angle")) + .default_value(1.75f * M_PI) + .min(-2 * M_PI) + .max(2 * M_PI) + .subtype(PROP_ANGLE) + .description(N_("Length of the arc")); + b.add_input<decl::Float>(N_("Offset Angle")) + .default_value(0.0f) + .subtype(PROP_ANGLE) + .description(N_("Offset angle of the arc")); + b.add_input<decl::Bool>(N_("Connect Center")) + .default_value(false) + .description(N_("Connect the arc at the center")); + b.add_input<decl::Bool>(N_("Invert Arc")) + .default_value(false) + .description(N_("Invert and draw opposite arc")); + + b.add_output<decl::Geometry>(N_("Curve")); + b.add_output<decl::Vector>(N_("Center")) + .description(N_("The center of the circle described by the three points")) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); + b.add_output<decl::Vector>(N_("Normal")) + .description(N_("The normal direction of the plane described by the three points, pointing " + "towards the positive Z axis")) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); + b.add_output<decl::Float>(N_("Radius")) + .description(N_("The radius of the circle described by the three points")) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurvePrimitiveArc *data = MEM_cnew<NodeGeometryCurvePrimitiveArc>(__func__); + + data->mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryCurvePrimitiveArc &storage = node_storage(*node); + const GeometryNodeCurvePrimitiveArcMode mode = (GeometryNodeCurvePrimitiveArcMode)storage.mode; + + bNodeSocket *start_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *middle_socket = start_socket->next; + bNodeSocket *end_socket = middle_socket->next; + + bNodeSocket *radius_socket = end_socket->next; + bNodeSocket *start_angle_socket = radius_socket->next; + bNodeSocket *sweep_angle_socket = start_angle_socket->next; + + bNodeSocket *offset_angle_socket = sweep_angle_socket->next; + + bNodeSocket *center_out_socket = ((bNodeSocket *)node->outputs.first)->next; + bNodeSocket *normal_out_socket = center_out_socket->next; + bNodeSocket *radius_out_socket = normal_out_socket->next; + + const bool radius_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS); + const bool points_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS); + + nodeSetSocketAvailability(ntree, start_socket, points_mode); + nodeSetSocketAvailability(ntree, middle_socket, points_mode); + nodeSetSocketAvailability(ntree, end_socket, points_mode); + + nodeSetSocketAvailability(ntree, radius_socket, radius_mode); + nodeSetSocketAvailability(ntree, start_angle_socket, radius_mode); + nodeSetSocketAvailability(ntree, sweep_angle_socket, radius_mode); + + nodeSetSocketAvailability(ntree, offset_angle_socket, points_mode); + + nodeSetSocketAvailability(ntree, center_out_socket, points_mode); + nodeSetSocketAvailability(ntree, normal_out_socket, points_mode); + nodeSetSocketAvailability(ntree, radius_out_socket, points_mode); +} + +static float3 rotate_vector_around_axis(const float3 vector, const float3 axis, const float angle) +{ + float3 result = vector; + float mat[3][3]; + axis_angle_to_mat3(mat, axis, angle); + mul_m3_v3(mat, result); + return result; +} + +static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3) +{ + const float3 a = math::normalize(p2 - p1); + const float3 b = math::normalize(p3 - p1); + return (ELEM(a, b, b * -1.0f)); +} + +static std::unique_ptr<CurveEval> create_arc_curve_from_points(const int resolution, + const float3 a, + const float3 b, + const float3 c, + float angle_offset, + const bool connect_center, + const bool invert_arc, + float3 &r_center, + float3 &r_normal, + float &r_radius) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + + if (connect_center) { + spline->resize(resolution + 1); + } + else { + spline->resize(resolution); + } + + const int stepcount = resolution - 1; + const int centerpoint = resolution; + MutableSpan<float3> positions = spline->positions(); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); + + const bool is_colinear = colinear_f3_f3_f3(a, b, c); + + float3 center; + float3 normal; + float radius; + const float3 mid_ac = math::midpoint(a, c); + normal_tri_v3(normal, a, c, b); + + if (is_colinear || a == c || a == b || b == c || resolution == 2) { + /* If colinear, generate a point line between points. */ + float3 p1, p2; + + /* Find the two points that are furthest away from each other. */ + const float ab = math::distance_squared(a, b); + const float ac = math::distance_squared(a, c); + const float bc = math::distance_squared(b, c); + if (ab > ac && ab > bc) { + p1 = a; + p2 = b; + } + else if (bc > ab && bc > ac) { + p1 = b; + p2 = c; + } + else { + p1 = a; + p2 = c; + } + + const float step = 1.0f / stepcount; + for (const int i : IndexRange(resolution)) { + const float factor = step * i; + positions[i] = math::interpolate(p1, p2, factor); + } + center = mid_ac; + radius = 0.0f; + } + else { + /* Midpoints of `A->B` and `B->C`. */ + const float3 mid_ab = math::midpoint(a, b); + const float3 mid_bc = math::midpoint(c, b); + + /* Normalized vectors of `A->B` and `B->C`. */ + const float3 nba = math::normalize(b - a); + const float3 ncb = math::normalize(c - b); + + /* Normal of plane of main 2 segments A->B and `B->C`. */ + const float3 nabc = math::normalize(math::cross(nba, ncb)); + + /* Determine center point from the intersection of 3 planes. */ + float plane_1[4], plane_2[4], plane_3[4]; + plane_from_point_normal_v3(plane_1, mid_ab, nabc); + plane_from_point_normal_v3(plane_2, mid_ab, nba); + plane_from_point_normal_v3(plane_3, mid_bc, ncb); + + /* If the 3 planes do not intersect at one point, just return empty geometry. */ + if (!isect_plane_plane_plane_v3(plane_1, plane_2, plane_3, center)) { + r_center = mid_ac; + r_normal = normal; + r_radius = 0.0f; + return nullptr; + } + + /* Radial vectors. */ + const float3 rad_a = math::normalize(a - center); + const float3 rad_b = math::normalize(b - center); + const float3 rad_c = math::normalize(c - center); + + /* Calculate angles. */ + radius = math::distance(center, b); + float angle_ab = angle_signed_on_axis_v3v3_v3(rad_a, rad_b, normal) + 2.0f * M_PI; + float angle_ac = angle_signed_on_axis_v3v3_v3(rad_a, rad_c, normal) + 2.0f * M_PI; + float angle = (angle_ac > angle_ab) ? angle_ac : angle_ab; + angle -= 2.0f * M_PI; + if (invert_arc) { + angle = -(2.0f * M_PI - angle); + } + + /* Create arc. */ + const float step = angle / stepcount; + for (const int i : IndexRange(resolution)) { + const float factor = step * i + angle_offset; + float3 out = rotate_vector_around_axis(rad_a, -normal, factor); + positions[i] = out * radius + center; + } + } + + if (connect_center) { + spline->set_cyclic(true); + positions[centerpoint] = center; + } + + /* Ensure normal is relative to Z-up. */ + if (math::dot(float3(0, 0, 1), normal) < 0) { + normal = -normal; + } + + curve->add_spline(std::move(spline)); + curve->attributes.reallocate(curve->splines().size()); + r_center = center; + r_radius = radius; + r_normal = normal; + return curve; +} + +static std::unique_ptr<CurveEval> create_arc_curve_from_radius(const int resolution, + const float radius, + const float start_angle, + const float sweep_angle, + const bool connect_center, + const bool invert_arc) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + + if (connect_center) { + spline->resize(resolution + 1); + } + else { + spline->resize(resolution); + } + + const int stepcount = resolution - 1; + const int centerpoint = resolution; + MutableSpan<float3> positions = spline->positions(); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); + + const float sweep = (invert_arc) ? -(2.0f * M_PI - sweep_angle) : sweep_angle; + + const float theta_step = sweep / float(stepcount); + for (const int i : IndexRange(resolution)) { + const float theta = theta_step * i + start_angle; + const float x = radius * cos(theta); + const float y = radius * sin(theta); + positions[i] = float3(x, y, 0.0f); + } + + if (connect_center) { + spline->set_cyclic(true); + positions[centerpoint] = float3(0.0f, 0.0f, 0.0f); + } + + curve->add_spline(std::move(spline)); + curve->attributes.reallocate(curve->splines().size()); + return curve; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurvePrimitiveArc &storage = node_storage(params.node()); + + const GeometryNodeCurvePrimitiveArcMode mode = (GeometryNodeCurvePrimitiveArcMode)storage.mode; + + switch (mode) { + case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS: { + std::unique_ptr<CurveEval> curve; + float3 r_center, r_normal; + float r_radius; + curve = create_arc_curve_from_points(std::max(params.extract_input<int>("Resolution"), 2), + params.extract_input<float3>("Start"), + params.extract_input<float3>("Middle"), + params.extract_input<float3>("End"), + params.extract_input<float>("Offset Angle"), + params.extract_input<bool>("Connect Center"), + params.extract_input<bool>("Invert Arc"), + r_center, + r_normal, + r_radius); + params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); + params.set_output("Center", r_center); + params.set_output("Normal", r_normal); + params.set_output("Radius", r_radius); + break; + } + case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS: { + std::unique_ptr<CurveEval> curve; + curve = create_arc_curve_from_radius(std::max(params.extract_input<int>("Resolution"), 2), + params.extract_input<float>("Radius"), + params.extract_input<float>("Start Angle"), + params.extract_input<float>("Sweep Angle"), + params.extract_input<bool>("Connect Center"), + params.extract_input<bool>("Invert Arc")); + + params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); + break; + } + } +} + +} // namespace blender::nodes::node_geo_curve_primitive_arc_cc + +void register_node_type_geo_curve_primitive_arc() +{ + namespace file_ns = blender::nodes::node_geo_curve_primitive_arc_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_ARC, "Arc", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + node_type_storage(&ntype, + "NodeGeometryCurvePrimitiveArc", + node_free_standard_storage, + node_copy_standard_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc index 313473e3442..7d84ddf9917 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_bezier_segment.cc @@ -21,31 +21,49 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc { -static void geo_node_curve_primitive_bezier_segment_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveBezierSegment) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Resolution").default_value(16).min(1).max(256).subtype(PROP_UNSIGNED); - b.add_input<decl::Vector>("Start").default_value({-1.0f, 0.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Start Handle") + b.add_input<decl::Int>(N_("Resolution")) + .default_value(16) + .min(1) + .max(256) + .subtype(PROP_UNSIGNED) + .description(N_("The number of evaluated points on the curve")); + b.add_input<decl::Vector>(N_("Start")) + .default_value({-1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the start control point of the curve")); + b.add_input<decl::Vector>(N_("Start Handle")) .default_value({-0.5f, 0.5f, 0.0f}) - .subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("End Handle").subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("End").default_value({1.0f, 0.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_output<decl::Geometry>("Curve"); + .subtype(PROP_TRANSLATION) + .description( + N_("Position of the start handle used to define the shape of the curve. In Offset mode, " + "relative to Start point")); + b.add_input<decl::Vector>(N_("End Handle")) + .subtype(PROP_TRANSLATION) + .description( + N_("Position of the end handle used to define the shape of the curve. In Offset mode, " + "relative to End point")); + b.add_input<decl::Vector>(N_("End")) + .default_value({1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the end control point of the curve")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_primitive_bezier_segment_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_primitive_bezier_segment_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurvePrimitiveBezierSegment *data = (NodeGeometryCurvePrimitiveBezierSegment *) - MEM_callocN(sizeof(NodeGeometryCurvePrimitiveBezierSegment), __func__); + NodeGeometryCurvePrimitiveBezierSegment *data = + MEM_cnew<NodeGeometryCurvePrimitiveBezierSegment>(__func__); data->mode = GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION; node->storage = data; @@ -61,53 +79,37 @@ static std::unique_ptr<CurveEval> create_bezier_segment_curve( { std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>(); + spline->set_resolution(resolution); + + spline->resize(2); + MutableSpan<float3> positions = spline->positions(); + spline->handle_types_left().fill(BezierSpline::HandleType::Align); + spline->handle_types_right().fill(BezierSpline::HandleType::Align); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); + + positions.first() = start; + positions.last() = end; if (mode == GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT_POSITION) { - spline->add_point(start, - BezierSpline::HandleType::Align, - 2.0f * start - start_handle_right, - BezierSpline::HandleType::Align, - start_handle_right, - 1.0f, - 0.0f); - spline->add_point(end, - BezierSpline::HandleType::Align, - end_handle_left, - BezierSpline::HandleType::Align, - 2.0f * end - end_handle_left, - 1.0f, - 0.0f); + spline->set_handle_position_right(0, start_handle_right); + spline->set_handle_position_left(1, end_handle_left); } else { - spline->add_point(start, - BezierSpline::HandleType::Align, - start - start_handle_right, - BezierSpline::HandleType::Align, - start + start_handle_right, - 1.0f, - 0.0f); - spline->add_point(end, - BezierSpline::HandleType::Align, - end + end_handle_left, - BezierSpline::HandleType::Align, - end - end_handle_left, - 1.0f, - 0.0f); + spline->set_handle_position_right(0, start + start_handle_right); + spline->set_handle_position_left(1, end + end_handle_left); } - spline->set_resolution(resolution); - spline->attributes.reallocate(spline->size()); curve->add_spline(std::move(spline)); - curve->attributes.reallocate(curve->splines().size()); + curve->attributes.reallocate(1); return curve; } -static void geo_node_curve_primitive_bezier_segment_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const NodeGeometryCurvePrimitiveBezierSegment *node_storage = - (NodeGeometryCurvePrimitiveBezierSegment *)params.node().storage; + const NodeGeometryCurvePrimitiveBezierSegment &storage = node_storage(params.node()); const GeometryNodeCurvePrimitiveBezierSegmentMode mode = - (const GeometryNodeCurvePrimitiveBezierSegmentMode)node_storage->mode; + (const GeometryNodeCurvePrimitiveBezierSegmentMode)storage.mode; std::unique_ptr<CurveEval> curve = create_bezier_segment_curve( params.extract_input<float3>("Start"), @@ -119,20 +121,22 @@ static void geo_node_curve_primitive_bezier_segment_exec(GeoNodeExecParams param params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_bezier_segment_cc void register_node_type_geo_curve_primitive_bezier_segment() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_bezier_segment_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, "Bezier Segment", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_curve_primitive_bezier_segment_init); + &ntype, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, "Bezier Segment", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryCurvePrimitiveBezierSegment", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_curve_primitive_bezier_segment_declare; - ntype.draw_buttons = blender::nodes::geo_node_curve_primitive_bezier_segment_layout; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_bezier_segment_exec; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc index f5eb83ea4fd..7b5d1a1dc80 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc @@ -21,43 +21,64 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_circle_cc { -static void geo_node_curve_primitive_circle_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveCircle) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Resolution").default_value(32).min(3).max(512); - b.add_input<decl::Vector>("Point 1") + b.add_input<decl::Int>(N_("Resolution")) + .default_value(32) + .min(3) + .max(512) + .description(N_("Number of points on the circle")); + b.add_input<decl::Vector>(N_("Point 1")) .default_value({-1.0f, 0.0f, 0.0f}) - .subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Point 2").default_value({0.0f, 1.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Point 3").default_value({1.0f, 0.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Curve"); - b.add_output<decl::Vector>("Center"); + .subtype(PROP_TRANSLATION) + .description( + N_("One of the three points on the circle. The point order determines the circle's " + "direction")); + b.add_input<decl::Vector>(N_("Point 2")) + .default_value({0.0f, 1.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description( + N_("One of the three points on the circle. The point order determines the circle's " + "direction")); + b.add_input<decl::Vector>(N_("Point 3")) + .default_value({1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description( + N_("One of the three points on the circle. The point order determines the circle's " + "direction")); + b.add_input<decl::Float>(N_("Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance of the points from the origin")); + b.add_output<decl::Geometry>(N_("Curve")); + b.add_output<decl::Vector>(N_("Center")).make_available([](bNode &node) { + node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS; + }); } -static void geo_node_curve_primitive_circle_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_primitive_circle_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurvePrimitiveCircle *data = (NodeGeometryCurvePrimitiveCircle *)MEM_callocN( - sizeof(NodeGeometryCurvePrimitiveCircle), __func__); + NodeGeometryCurvePrimitiveCircle *data = MEM_cnew<NodeGeometryCurvePrimitiveCircle>(__func__); data->mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS; node->storage = data; } -static void geo_node_curve_primitive_circle_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryCurvePrimitiveCircle *node_storage = (NodeGeometryCurvePrimitiveCircle *) - node->storage; - const GeometryNodeCurvePrimitiveCircleMode mode = (const GeometryNodeCurvePrimitiveCircleMode) - node_storage->mode; + const NodeGeometryCurvePrimitiveCircle &storage = node_storage(*node); + const GeometryNodeCurvePrimitiveCircleMode mode = (GeometryNodeCurvePrimitiveCircleMode) + storage.mode; bNodeSocket *start_socket = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *middle_socket = start_socket->next; @@ -66,18 +87,23 @@ static void geo_node_curve_primitive_circle_update(bNodeTree *UNUSED(ntree), bNo bNodeSocket *center_socket = ((bNodeSocket *)node->outputs.first)->next; - nodeSetSocketAvailability(start_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); - nodeSetSocketAvailability(middle_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); - nodeSetSocketAvailability(end_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); - nodeSetSocketAvailability(center_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); - nodeSetSocketAvailability(radius_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS); + nodeSetSocketAvailability( + ntree, start_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); + nodeSetSocketAvailability( + ntree, middle_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); + nodeSetSocketAvailability( + ntree, end_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); + nodeSetSocketAvailability( + ntree, center_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS); + nodeSetSocketAvailability( + ntree, radius_socket, mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS); } static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3) { - const float3 a = (p2 - p1).normalized(); - const float3 b = (p3 - p1).normalized(); - return (a == b || a == b * -1.0f); + const float3 a = math::normalize(p2 - p1); + const float3 b = math::normalize(p3 - p1); + return (ELEM(a, b, b * -1.0f)); } static std::unique_ptr<CurveEval> create_point_circle_curve( @@ -96,18 +122,18 @@ static std::unique_ptr<CurveEval> create_point_circle_curve( float3 center; /* Midpoints of `P1->P2` and `P2->P3`. */ - const float3 q1 = float3::interpolate(p1, p2, 0.5f); - const float3 q2 = float3::interpolate(p2, p3, 0.5f); + const float3 q1 = math::interpolate(p1, p2, 0.5f); + const float3 q2 = math::interpolate(p2, p3, 0.5f); /* Normal Vectors of `P1->P2` and `P2->P3` */ - const float3 v1 = (p2 - p1).normalized(); - const float3 v2 = (p3 - p2).normalized(); + const float3 v1 = math::normalize(p2 - p1); + const float3 v2 = math::normalize(p3 - p2); /* Normal of plane of main 2 segments P1->P2 and `P2->P3`. */ - const float3 v3 = float3::cross(v1, v2).normalized(); + const float3 v3 = math::normalize(math::cross(v1, v2)); /* Normal of plane of first perpendicular bisector and `P1->P2`. */ - const float3 v4 = float3::cross(v3, v1).normalized(); + const float3 v4 = math::normalize(math::cross(v3, v1)); /* Determine Center-point from the intersection of 3 planes. */ float plane_1[4], plane_2[4], plane_3[4]; @@ -122,7 +148,7 @@ static std::unique_ptr<CurveEval> create_point_circle_curve( } /* Get the radius from the center-point to p1. */ - const float r = float3::distance(p1, center); + const float r = math::distance(p1, center); const float theta_step = ((2 * M_PI) / (float)resolution); for (const int i : IndexRange(resolution)) { @@ -132,7 +158,7 @@ static std::unique_ptr<CurveEval> create_point_circle_curve( */ const float theta = theta_step * i; - positions[i] = center + r * cos(theta) * v1 + r * sin(theta) * v4; + positions[i] = center + r * sin(theta) * v1 + r * cos(theta) * v4; } spline->radii().fill(1.0f); @@ -169,13 +195,11 @@ static std::unique_ptr<CurveEval> create_radius_circle_curve(const int resolutio return curve; } -static void geo_node_curve_primitive_circle_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const NodeGeometryCurvePrimitiveCircle *node_storage = - (NodeGeometryCurvePrimitiveCircle *)params.node().storage; - + const NodeGeometryCurvePrimitiveCircle &storage = node_storage(params.node()); const GeometryNodeCurvePrimitiveCircleMode mode = (GeometryNodeCurvePrimitiveCircleMode) - node_storage->mode; + storage.mode; std::unique_ptr<CurveEval> curve; if (mode == GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS) { @@ -196,26 +220,27 @@ static void geo_node_curve_primitive_circle_exec(GeoNodeExecParams params) params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } else { - params.set_output("Curve", GeometrySet()); + params.set_default_remaining_outputs(); } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_circle_cc void register_node_type_geo_curve_primitive_circle() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_circle_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, "Curve Circle", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, "Curve Circle", NODE_CLASS_GEOMETRY); - node_type_init(&ntype, blender::nodes::geo_node_curve_primitive_circle_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_primitive_circle_update); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage(&ntype, "NodeGeometryCurvePrimitiveCircle", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_curve_primitive_circle_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_circle_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_primitive_circle_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc index a3d2ada612f..d35fa0a2fdc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_line.cc @@ -21,48 +21,57 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_line_cc { -static void geo_node_curve_primitive_line_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveLine) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Start").subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("End").default_value({0.0f, 0.0f, 1.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Direction").default_value({0.0f, 0.0f, 1.0f}); - b.add_input<decl::Float>("Length").default_value(1.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Vector>(N_("Start")) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the first control point")); + b.add_input<decl::Vector>(N_("End")) + .default_value({0.0f, 0.0f, 1.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the second control point")); + b.add_input<decl::Vector>(N_("Direction")) + .default_value({0.0f, 0.0f, 1.0f}) + .description( + N_("Direction the line is going in. The length of this vector does not matter")); + b.add_input<decl::Float>(N_("Length")) + .default_value(1.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance between the two points")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_primitive_line_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_primitive_line_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurvePrimitiveLine *data = (NodeGeometryCurvePrimitiveLine *)MEM_callocN( - sizeof(NodeGeometryCurvePrimitiveLine), __func__); + NodeGeometryCurvePrimitiveLine *data = MEM_cnew<NodeGeometryCurvePrimitiveLine>(__func__); data->mode = GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS; node->storage = data; } -static void geo_node_curve_primitive_line_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryCurvePrimitiveLine *node_storage = (NodeGeometryCurvePrimitiveLine *) - node->storage; - const GeometryNodeCurvePrimitiveLineMode mode = (const GeometryNodeCurvePrimitiveLineMode) - node_storage->mode; + const NodeGeometryCurvePrimitiveLine &storage = node_storage(*node); + const GeometryNodeCurvePrimitiveLineMode mode = (GeometryNodeCurvePrimitiveLineMode)storage.mode; bNodeSocket *p2_socket = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *direction_socket = p2_socket->next; bNodeSocket *length_socket = direction_socket->next; - nodeSetSocketAvailability(p2_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS); - nodeSetSocketAvailability(direction_socket, - mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION); - nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION); + nodeSetSocketAvailability(ntree, p2_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS); + nodeSetSocketAvailability( + ntree, direction_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION); + nodeSetSocketAvailability( + ntree, length_socket, mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_DIRECTION); } static std::unique_ptr<CurveEval> create_point_line_curve(const float3 start, const float3 end) @@ -91,7 +100,7 @@ static std::unique_ptr<CurveEval> create_direction_line_curve(const float3 start spline->resize(2); MutableSpan<float3> positions = spline->positions(); positions[0] = start; - positions[1] = direction.normalized() * length + start; + positions[1] = math::normalize(direction) * length + start; spline->radii().fill(1.0f); spline->tilts().fill(0.0f); @@ -100,13 +109,10 @@ static std::unique_ptr<CurveEval> create_direction_line_curve(const float3 start return curve; } -static void geo_node_curve_primitive_line_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - - const NodeGeometryCurvePrimitiveLine *node_storage = - (NodeGeometryCurvePrimitiveLine *)params.node().storage; - - GeometryNodeCurvePrimitiveLineMode mode = (GeometryNodeCurvePrimitiveLineMode)node_storage->mode; + const NodeGeometryCurvePrimitiveLine &storage = node_storage(params.node()); + const GeometryNodeCurvePrimitiveLineMode mode = (GeometryNodeCurvePrimitiveLineMode)storage.mode; std::unique_ptr<CurveEval> curve; if (mode == GEO_NODE_CURVE_PRIMITIVE_LINE_MODE_POINTS) { @@ -122,20 +128,22 @@ static void geo_node_curve_primitive_line_exec(GeoNodeExecParams params) params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_line_cc void register_node_type_geo_curve_primitive_line() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_line_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_LINE, "Curve Line", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_curve_primitive_line_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_primitive_line_update); + geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_LINE, "Curve Line", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage(&ntype, "NodeGeometryCurvePrimitiveLine", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_curve_primitive_line_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_line_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_primitive_line_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc index a54fd971ac4..885d92a111b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadratic_bezier.cc @@ -17,15 +17,29 @@ #include "BKE_spline.hh" #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc { -static void geo_node_curve_primitive_quadratic_bezier_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Resolution").default_value(16).min(3).max(256).subtype(PROP_UNSIGNED); - b.add_input<decl::Vector>("Start").default_value({-1.0f, 0.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Middle").default_value({0.0f, 2.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("End").default_value({1.0f, 0.0f, 0.0f}).subtype(PROP_TRANSLATION); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Int>(N_("Resolution")) + .default_value(16) + .min(3) + .max(256) + .subtype(PROP_UNSIGNED) + .description(N_("The number of edges on the curve")); + b.add_input<decl::Vector>(N_("Start")) + .default_value({-1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the first control point")); + b.add_input<decl::Vector>(N_("Middle")) + .default_value({0.0f, 2.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the middle control point")); + b.add_input<decl::Vector>(N_("End")) + .default_value({1.0f, 0.0f, 0.0f}) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the last control point")); + b.add_output<decl::Geometry>(N_("Curve")); } static std::unique_ptr<CurveEval> create_quadratic_bezier_curve(const float3 p1, @@ -36,21 +50,25 @@ static std::unique_ptr<CurveEval> create_quadratic_bezier_curve(const float3 p1, std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->resize(resolution + 1); + MutableSpan<float3> positions = spline->positions(); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); + const float step = 1.0f / resolution; - for (int i : IndexRange(resolution + 1)) { + for (const int i : IndexRange(resolution + 1)) { const float factor = step * i; - const float3 q1 = float3::interpolate(p1, p2, factor); - const float3 q2 = float3::interpolate(p2, p3, factor); - const float3 out = float3::interpolate(q1, q2, factor); - spline->add_point(out, 1.0f, 0.0f); + const float3 q1 = math::interpolate(p1, p2, factor); + const float3 q2 = math::interpolate(p2, p3, factor); + positions[i] = math::interpolate(q1, q2, factor); } - spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); curve->attributes.reallocate(curve->splines().size()); return curve; } -static void geo_node_curve_primitive_quadratic_bezier_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { std::unique_ptr<CurveEval> curve = create_quadratic_bezier_curve( params.extract_input<float3>("Start"), @@ -60,17 +78,16 @@ static void geo_node_curve_primitive_quadratic_bezier_exec(GeoNodeExecParams par params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc void register_node_type_geo_curve_primitive_quadratic_bezier() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_quadratic_bezier_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, - GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER, - "Quadratic Bezier", - NODE_CLASS_GEOMETRY, - 0); - ntype.declare = blender::nodes::geo_node_curve_primitive_quadratic_bezier_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_quadratic_bezier_exec; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER, "Quadratic Bezier", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc index 07ddaa8f61e..8ec42cb1c45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc @@ -17,46 +17,88 @@ #include "BKE_spline.hh" #include "UI_interface.h" #include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc { -static void geo_node_curve_primitive_quadrilateral_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveQuad) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>("Width").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Height").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Bottom Width").default_value(4.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Top Width").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Offset").default_value(1.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Bottom Height").default_value(3.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Top Height").default_value(1.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 1").default_value({-1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 2").default_value({1.0f, -1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 3").default_value({1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Point 4").default_value({-1.0f, 1.0f, 0.0f}).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Float>(N_("Width")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("The X axis size of the shape")); + b.add_input<decl::Float>(N_("Height")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("The Y axis size of the shape")); + b.add_input<decl::Float>(N_("Bottom Width")) + .default_value(4.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("The X axis size of the shape")); + b.add_input<decl::Float>(N_("Top Width")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("The X axis size of the shape")); + b.add_input<decl::Float>(N_("Offset")) + .default_value(1.0f) + .subtype(PROP_DISTANCE) + .description( + N_("For Parallelogram, the relative X difference between the top and bottom edges. For " + "Trapezoid, the amount to move the top edge in the positive X axis")); + b.add_input<decl::Float>(N_("Bottom Height")) + .default_value(3.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("The distance between the bottom point and the X axis")); + b.add_input<decl::Float>(N_("Top Height")) + .default_value(1.0f) + .subtype(PROP_DISTANCE) + .description(N_("The distance between the top point and the X axis")); + b.add_input<decl::Vector>(N_("Point 1")) + .default_value({-1.0f, -1.0f, 0.0f}) + .subtype(PROP_DISTANCE) + .description(N_("The exact location of the point to use")); + b.add_input<decl::Vector>(N_("Point 2")) + .default_value({1.0f, -1.0f, 0.0f}) + .subtype(PROP_DISTANCE) + .description(N_("The exact location of the point to use")); + b.add_input<decl::Vector>(N_("Point 3")) + .default_value({1.0f, 1.0f, 0.0f}) + .subtype(PROP_DISTANCE) + .description(N_("The exact location of the point to use")); + b.add_input<decl::Vector>(N_("Point 4")) + .default_value({-1.0f, 1.0f, 0.0f}) + .subtype(PROP_DISTANCE) + .description(N_("The exact location of the point to use")); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_primitive_quadrilateral_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } -static void geo_node_curve_primitive_quadrilateral_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurvePrimitiveQuad *data = (NodeGeometryCurvePrimitiveQuad *)MEM_callocN( - sizeof(NodeGeometryCurvePrimitiveQuad), __func__); + NodeGeometryCurvePrimitiveQuad *data = MEM_cnew<NodeGeometryCurvePrimitiveQuad>(__func__); data->mode = GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE; node->storage = data; } -static void geo_node_curve_primitive_quadrilateral_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - NodeGeometryCurvePrimitiveQuad &node_storage = *(NodeGeometryCurvePrimitiveQuad *)node->storage; + const NodeGeometryCurvePrimitiveQuad &storage = node_storage(*node); GeometryNodeCurvePrimitiveQuadMode mode = static_cast<GeometryNodeCurvePrimitiveQuadMode>( - node_storage.mode); + storage.mode); bNodeSocket *width = ((bNodeSocket *)node->inputs.first); bNodeSocket *height = width->next; @@ -70,35 +112,61 @@ static void geo_node_curve_primitive_quadrilateral_update(bNodeTree *UNUSED(ntre bNodeSocket *p3 = p2->next; bNodeSocket *p4 = p3->next; - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - nodeSetSocketAvailability(sock, false); - } + Vector<bNodeSocket *> available_sockets; if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE) { - nodeSetSocketAvailability(width, true); - nodeSetSocketAvailability(height, true); + available_sockets.extend({width, height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM) { - nodeSetSocketAvailability(width, true); - nodeSetSocketAvailability(height, true); - nodeSetSocketAvailability(offset, true); + available_sockets.extend({width, height, offset}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID) { - nodeSetSocketAvailability(bottom, true); - nodeSetSocketAvailability(top, true); - nodeSetSocketAvailability(offset, true); - nodeSetSocketAvailability(height, true); + available_sockets.extend({bottom, top, offset, height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_KITE) { - nodeSetSocketAvailability(width, true); - nodeSetSocketAvailability(bottom_height, true); - nodeSetSocketAvailability(top_height, true); + available_sockets.extend({width, bottom_height, top_height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS) { - nodeSetSocketAvailability(p1, true); - nodeSetSocketAvailability(p2, true); - nodeSetSocketAvailability(p3, true); - nodeSetSocketAvailability(p4, true); + available_sockets.extend({p1, p2, p3, p4}); + } + + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + nodeSetSocketAvailability(ntree, sock, available_sockets.contains(sock)); + } +} + +class SocketSearchOp { + public: + std::string socket_name; + GeometryNodeCurvePrimitiveQuadMode quad_mode; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("GeometryNodeCurvePrimitiveQuadrilateral"); + node_storage(node).mode = quad_mode; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + if (params.in_out() == SOCK_OUT) { + search_link_ops_for_declarations(params, declaration.outputs()); + } + else if (params.node_tree().typeinfo->validate_link( + static_cast<eNodeSocketDatatype>(params.other_socket().type), SOCK_FLOAT)) { + params.add_item(IFACE_("Width"), + SocketSearchOp{"Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE}); + params.add_item(IFACE_("Height"), + SocketSearchOp{"Height", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE}); + params.add_item(IFACE_("Bottom Width"), + SocketSearchOp{"Bottom Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID}); + params.add_item(IFACE_("Top Width"), + SocketSearchOp{"Top Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID}); + params.add_item(IFACE_("Offset"), + SocketSearchOp{"Offset", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM}); + params.add_item(IFACE_("Point 1"), + SocketSearchOp{"Point 1", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS}); } } @@ -157,12 +225,10 @@ static void create_kite_curve(MutableSpan<float3> positions, positions[3] = float3(-width / 2.0f, 0, 0); } -static void geo_node_curve_primitive_quadrilateral_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const NodeGeometryCurvePrimitiveQuad &node_storage = - *(NodeGeometryCurvePrimitiveQuad *)(params.node()).storage; - const GeometryNodeCurvePrimitiveQuadMode mode = static_cast<GeometryNodeCurvePrimitiveQuadMode>( - node_storage.mode); + const NodeGeometryCurvePrimitiveQuad &storage = node_storage(params.node()); + const GeometryNodeCurvePrimitiveQuadMode mode = (GeometryNodeCurvePrimitiveQuadMode)storage.mode; std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); @@ -206,7 +272,7 @@ static void geo_node_curve_primitive_quadrilateral_exec(GeoNodeExecParams params params.extract_input<float3>("Point 4")); break; default: - params.set_output("Curve", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -215,21 +281,24 @@ static void geo_node_curve_primitive_quadrilateral_exec(GeoNodeExecParams params params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_quadrilateral_cc void register_node_type_geo_curve_primitive_quadrilateral() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_quadrilateral_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, "Quadrilateral", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_primitive_quadrilateral_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_quadrilateral_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_primitive_quadrilateral_layout; - node_type_update(&ntype, blender::nodes::geo_node_curve_primitive_quadrilateral_update); - node_type_init(&ntype, blender::nodes::geo_node_curve_primitive_quadrilateral_init); + &ntype, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, "Quadrilateral", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_init); node_type_storage(&ntype, "NodeGeometryCurvePrimitiveQuad", node_free_standard_storage, node_copy_standard_storage); + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc index 0803d43e5c3..6aba65b5638 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc @@ -18,17 +18,35 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_spiral_cc { -static void geo_node_curve_primitive_spiral_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Resolution").default_value(32).min(1).max(1024).subtype(PROP_UNSIGNED); - b.add_input<decl::Float>("Rotations").default_value(2.0f).min(0.0f); - b.add_input<decl::Float>("Start Radius").default_value(1.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("End Radius").default_value(2.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Height").default_value(2.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Bool>("Reverse"); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Int>(N_("Resolution")) + .default_value(32) + .min(1) + .max(1024) + .subtype(PROP_UNSIGNED) + .description(N_("Number of points in one rotation of the spiral")); + b.add_input<decl::Float>(N_("Rotations")) + .default_value(2.0f) + .min(0.0f) + .description(N_("Number of times the spiral makes a full rotation")); + b.add_input<decl::Float>(N_("Start Radius")) + .default_value(1.0f) + .subtype(PROP_DISTANCE) + .description(N_("Horizontal Distance from the Z axis at the start of the spiral")); + b.add_input<decl::Float>(N_("End Radius")) + .default_value(2.0f) + .subtype(PROP_DISTANCE) + .description(N_("Horizontal Distance from the Z axis at the end of the spiral")); + b.add_input<decl::Float>(N_("Height")) + .default_value(2.0f) + .subtype(PROP_DISTANCE) + .description(N_("The height perpendicular to the base of the spiral")); + b.add_input<decl::Bool>(N_("Reverse")) + .description(N_("Switch the direction from clockwise to counterclockwise")); + b.add_output<decl::Geometry>(N_("Curve")); } static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, @@ -43,39 +61,35 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, const int totalpoints = std::max(int(resolution * rotations), 1); const float delta_radius = (end_radius - start_radius) / (float)totalpoints; - float radius = start_radius; const float delta_height = height / (float)totalpoints; - const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints; - float theta = 0.0f; + const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints * + (direction ? 1.0f : -1.0f); + + spline->resize(totalpoints + 1); + MutableSpan<float3> positions = spline->positions(); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); for (const int i : IndexRange(totalpoints + 1)) { + const float theta = i * delta_theta; + const float radius = start_radius + i * delta_radius; const float x = radius * cos(theta); const float y = radius * sin(theta); const float z = delta_height * i; - spline->add_point(float3(x, y, z), 1.0f, 0.0f); - - radius += delta_radius; - - if (direction) { - theta += delta_theta; - } - else { - theta -= delta_theta; - } + positions[i] = {x, y, z}; } - spline->attributes.reallocate(spline->size()); curve->add_spline(std::move(spline)); curve->attributes.reallocate(curve->splines().size()); return curve; } -static void geo_node_curve_primitive_spiral_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const float rotations = std::max(params.extract_input<float>("Rotations"), 0.0f); if (rotations == 0.0f) { - params.set_output("Curve", GeometrySet()); + params.set_default_remaining_outputs(); return; } @@ -89,14 +103,16 @@ static void geo_node_curve_primitive_spiral_exec(GeoNodeExecParams params) params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_primitive_spiral_cc void register_node_type_geo_curve_primitive_spiral() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_spiral_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, "Spiral", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_primitive_spiral_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_spiral_exec; + geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, "Spiral", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc index 6261146562d..14517a79037 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc @@ -18,15 +18,33 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_primitive_star_cc { -static void geo_node_curve_primitive_star_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Points").default_value(8).min(3).max(256).subtype(PROP_UNSIGNED); - b.add_input<decl::Float>("Inner Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Outer Radius").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Twist").subtype(PROP_ANGLE); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Int>(N_("Points")) + .default_value(8) + .min(3) + .max(256) + .subtype(PROP_UNSIGNED) + .description(N_("Number of points on each of the circles")); + b.add_input<decl::Float>(N_("Inner Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the inner circle; can be larger than outer radius")); + b.add_input<decl::Float>(N_("Outer Radius")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the outer circle; can be smaller than inner radius")); + b.add_input<decl::Float>(N_("Twist")) + .subtype(PROP_ANGLE) + .description(N_("The counterclockwise rotation of the inner set of points")); + b.add_output<decl::Geometry>(N_("Curve")); + b.add_output<decl::Bool>(N_("Outer Points")) + .field_source() + .description(N_("An attribute field with a selection of the outer points")); } static std::unique_ptr<CurveEval> create_star_curve(const float inner_radius, @@ -36,41 +54,69 @@ static std::unique_ptr<CurveEval> create_star_curve(const float inner_radius, { std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->set_cyclic(true); + + spline->resize(points * 2); + MutableSpan<float3> positions = spline->positions(); + spline->radii().fill(1.0f); + spline->tilts().fill(0.0f); const float theta_step = (2.0f * M_PI) / float(points); - for (int i : IndexRange(points)) { + for (const int i : IndexRange(points)) { const float x = outer_radius * cos(theta_step * i); const float y = outer_radius * sin(theta_step * i); - spline->add_point(float3(x, y, 0.0f), 1.0f, 0.0f); + positions[i * 2] = {x, y, 0.0f}; const float inner_x = inner_radius * cos(theta_step * i + theta_step * 0.5f + twist); const float inner_y = inner_radius * sin(theta_step * i + theta_step * 0.5f + twist); - spline->add_point(float3(inner_x, inner_y, 0.0f), 1.0f, 0.0f); + positions[i * 2 + 1] = {inner_x, inner_y, 0.0f}; } - spline->set_cyclic(true); - spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); curve->attributes.reallocate(curve->splines().size()); + return curve; } -static void geo_node_curve_primitive_star_exec(GeoNodeExecParams params) +static void create_selection_output(CurveComponent &component, + StrongAnonymousAttributeID &r_attribute) +{ + OutputAttribute_Typed<bool> attribute = component.attribute_try_get_for_output_only<bool>( + r_attribute.get(), ATTR_DOMAIN_POINT); + MutableSpan<bool> selection = attribute.as_span(); + for (int i : selection.index_range()) { + selection[i] = i % 2 == 0; + } + attribute.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) { std::unique_ptr<CurveEval> curve = create_star_curve( std::max(params.extract_input<float>("Inner Radius"), 0.0f), std::max(params.extract_input<float>("Outer Radius"), 0.0f), params.extract_input<float>("Twist"), std::max(params.extract_input<int>("Points"), 3)); - params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); -} + GeometrySet output = GeometrySet::create_with_curve(curve.release()); -} // namespace blender::nodes + if (params.output_is_required("Outer Points")) { + StrongAnonymousAttributeID attribute_output("Outer Points"); + create_selection_output(output.get_component_for_write<CurveComponent>(), attribute_output); + params.set_output("Outer Points", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_output), params.attribute_producer_name())); + } + params.set_output("Curve", std::move(output)); +} +} // namespace blender::nodes::node_geo_curve_primitive_star_cc void register_node_type_geo_curve_primitive_star() { + namespace file_ns = blender::nodes::node_geo_curve_primitive_star_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_STAR, "Star", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_primitive_star_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_primitive_star_exec; + geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_STAR, "Star", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 index b8f62460069..8494b4868e8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -26,50 +26,53 @@ #include "node_geometry_util.hh" -using blender::fn::GVArray_For_GSpan; -using blender::fn::GVArray_For_Span; -using blender::fn::GVArray_Typed; +namespace blender::nodes::node_geo_curve_resample_cc { -namespace blender::nodes { +NODE_STORAGE_FUNCS(NodeGeometryCurveResample) -static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Count").default_value(10).min(1).max(100000); - b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Int>(N_("Count")).default_value(10).min(1).max(100000).supports_field(); + b.add_input<decl::Float>(N_("Length")) + .default_value(0.1f) + .min(0.001f) + .supports_field() + .subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } -static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveResample *data = (NodeGeometryCurveResample *)MEM_callocN( - sizeof(NodeGeometryCurveResample), __func__); + NodeGeometryCurveResample *data = MEM_cnew<NodeGeometryCurveResample>(__func__); data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT; node->storage = data; } -static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)node->storage; - const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; + const NodeGeometryCurveResample &storage = node_storage(*node); + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; - bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next->next; bNodeSocket *length_socket = count_socket->next; - nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); - nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); + nodeSetSocketAvailability(ntree, count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); + nodeSetSocketAvailability(ntree, length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); } struct SampleModeParam { GeometryNodeCurveResampleMode mode; - std::optional<float> length; - std::optional<int> count; + std::optional<Field<float>> length; + std::optional<Field<int>> count; + Field<bool> selection; }; static SplinePtr resample_spline(const Spline &src, const int count) @@ -78,8 +81,11 @@ static SplinePtr resample_spline(const Spline &src, const int count) Spline::copy_base_settings(src, *dst); if (src.evaluated_edges_size() < 1 || count == 1) { - dst->add_point(src.positions().first(), src.tilts().first(), src.radii().first()); - dst->attributes.reallocate(1); + dst->resize(1); + dst->positions().first() = src.positions().first(); + dst->radii().first() = src.radii().first(); + dst->tilts().first() = src.tilts().first(); + src.attributes.foreach_attribute( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); @@ -118,7 +124,7 @@ static SplinePtr resample_spline(const Spline &src, const int count) std::optional<GMutableSpan> output_attribute = dst->attributes.get_for_write( attribute_id); if (output_attribute) { - src.sample_with_index_factors(*src.interpolate_to_evaluated(*input_attribute), + src.sample_with_index_factors(src.interpolate_to_evaluated(*input_attribute), uniform_samples, *output_attribute); return true; @@ -141,8 +147,8 @@ static SplinePtr resample_spline_evaluated(const Spline &src) dst->positions().copy_from(src.evaluated_positions()); dst->positions().copy_from(src.evaluated_positions()); - src.interpolate_to_evaluated(src.radii())->materialize(dst->radii()); - src.interpolate_to_evaluated(src.tilts())->materialize(dst->tilts()); + src.interpolate_to_evaluated(src.radii()).materialize(dst->radii()); + src.interpolate_to_evaluated(src.tilts()).materialize(dst->tilts()); src.attributes.foreach_attribute( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { @@ -150,7 +156,7 @@ static SplinePtr resample_spline_evaluated(const Spline &src) if (dst->attributes.create(attribute_id, meta_data.data_type)) { std::optional<GMutableSpan> dst_attribute = dst->attributes.get_for_write(attribute_id); if (dst_attribute) { - src.interpolate_to_evaluated(*src_attribute)->materialize(dst_attribute->data()); + src.interpolate_to_evaluated(*src_attribute).materialize(dst_attribute->data()); return true; } } @@ -163,42 +169,80 @@ static SplinePtr resample_spline_evaluated(const Spline &src) return dst; } -static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, +static std::unique_ptr<CurveEval> resample_curve(const CurveComponent *component, const SampleModeParam &mode_param) { - Span<SplinePtr> input_splines = input_curve.splines(); + const CurveEval *input_curve = component->get_for_read(); + GeometryComponentFieldContext field_context{*component, ATTR_DOMAIN_CURVE}; + const int domain_size = component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + Span<SplinePtr> input_splines = input_curve->splines(); std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); output_curve->resize(input_splines.size()); MutableSpan<SplinePtr> output_splines = output_curve->splines(); if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(*mode_param.count); + evaluator.add(mode_param.selection); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); + const VArray<bool> &selections = evaluator.get_evaluated<bool>(1); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { BLI_assert(mode_param.count); - output_splines[i] = resample_spline(*input_splines[i], *mode_param.count); + if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { + output_splines[i] = resample_spline(*input_splines[i], std::max(cuts[i], 1)); + } + else { + output_splines[i] = input_splines[i]->copy(); + } } }); } else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(*mode_param.length); + evaluator.add(mode_param.selection); + evaluator.evaluate(); + const VArray<float> &lengths = evaluator.get_evaluated<float>(0); + const VArray<bool> &selections = evaluator.get_evaluated<bool>(1); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - const float length = input_splines[i]->length(); - const int count = std::max(int(length / *mode_param.length) + 1, 1); - output_splines[i] = resample_spline(*input_splines[i], count); + if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { + /* Don't allow asymptotic count increase for low resolution values. */ + const float divide_length = std::max(lengths[i], 0.0001f); + const float spline_length = input_splines[i]->length(); + const int count = std::max(int(spline_length / divide_length) + 1, 1); + output_splines[i] = resample_spline(*input_splines[i], count); + } + else { + output_splines[i] = input_splines[i]->copy(); + } } }); } else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_EVALUATED) { + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(mode_param.selection); + evaluator.evaluate(); + const VArray<bool> &selections = evaluator.get_evaluated<bool>(0); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - output_splines[i] = resample_spline_evaluated(*input_splines[i]); + if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { + output_splines[i] = resample_spline_evaluated(*input_splines[i]); + } + else { + output_splines[i] = input_splines[i]->copy(); + } } }); } - - output_curve->attributes = input_curve.attributes; - + output_curve->attributes = input_curve->attributes; return output_curve; } @@ -209,54 +253,57 @@ static void geometry_set_curve_resample(GeometrySet &geometry_set, return; } - const CurveEval &input_curve = *geometry_set.get_curve_for_read(); - std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + std::unique_ptr<CurveEval> output_curve = resample_curve( + geometry_set.get_component_for_read<CurveComponent>(), mode_param); geometry_set.replace_curve(output_curve.release()); } -static void geo_node_resample_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; - const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; + const NodeGeometryCurveResample &storage = node_storage(params.node()); + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; SampleModeParam mode_param; mode_param.mode = mode; + mode_param.selection = params.extract_input<Field<bool>>("Selection"); + if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { - const int count = params.extract_input<int>("Count"); + Field<int> count = params.extract_input<Field<int>>("Count"); if (count < 1) { - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } mode_param.count.emplace(count); } else if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { - /* Don't allow asymptotic count increase for low resolution values. */ - const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + Field<float> resolution = params.extract_input<Field<float>>("Length"); mode_param.length.emplace(resolution); } geometry_set.modify_geometry_sets( [&](GeometrySet &geometry_set) { geometry_set_curve_resample(geometry_set, mode_param); }); - params.set_output("Geometry", std::move(geometry_set)); + params.set_output("Curve", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_resample_cc void register_node_type_geo_curve_resample() { + namespace file_ns = blender::nodes::node_geo_curve_resample_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_RESAMPLE, "Resample Curve", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_resample_declare; - ntype.draw_buttons = blender::nodes::geo_node_curve_resample_layout; + geo_node_type_base(&ntype, GEO_NODE_RESAMPLE_CURVE, "Resample Curve", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeGeometryCurveResample", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_resample_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_resample_update); - ntype.geometry_node_execute = blender::nodes::geo_node_resample_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc index b644faabedb..38974fafce7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -20,16 +20,16 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_reverse_cc { -static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_reverse_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); @@ -38,7 +38,7 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) return; } - Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<bool> selection_field = params.get_input<Field<bool>>("Selection"); CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); @@ -60,13 +60,15 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) params.set_output("Curve", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_reverse_cc void register_node_type_geo_curve_reverse() { + namespace file_ns = blender::nodes::node_geo_curve_reverse_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_reverse_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; + geo_node_type_base(&ntype, GEO_NODE_REVERSE_CURVE, "Reverse Curve", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index 1266f525861..56fbc50f033 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -23,42 +23,53 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_sample_cc { -static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field(); - b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE).supports_field(); +NODE_STORAGE_FUNCS(NodeGeometryCurveSample) - b.add_output<decl::Vector>("Position").dependent_field(); - b.add_output<decl::Vector>("Tangent").dependent_field(); - b.add_output<decl::Vector>("Normal").dependent_field(); +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")) + .only_realized_data() + .supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Float>(N_("Factor")) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .supports_field() + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }); + b.add_input<decl::Float>(N_("Length")) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field() + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }); + b.add_output<decl::Vector>(N_("Position")).dependent_field(); + b.add_output<decl::Vector>(N_("Tangent")).dependent_field(); + b.add_output<decl::Vector>(N_("Normal")).dependent_field(); } -static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_sample_type_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_type_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveSample *data = (NodeGeometryCurveSample *)MEM_callocN( - sizeof(NodeGeometryCurveSample), __func__); + NodeGeometryCurveSample *data = MEM_cnew<NodeGeometryCurveSample>(__func__); data->mode = GEO_NODE_CURVE_SAMPLE_LENGTH; node->storage = data; } -static void geo_node_curve_sample_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)node->storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const NodeGeometryCurveSample &storage = node_storage(*node); + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; bNodeSocket *factor = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *length = factor->next; - nodeSetSocketAvailability(factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); - nodeSetSocketAvailability(length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(ntree, factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(ntree, length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); } template<typename T> static T sample_with_lookup(const Spline::LookupResult lookup, Span<T> data) @@ -174,7 +185,7 @@ class SampleCurveFunction : public fn::MultiFunction { for (const int i : mask) { const Spline::LookupResult &lookup = lookups[i]; const Span<float3> evaluated_tangents = splines[spline_indices[i]]->evaluated_tangents(); - sampled_tangents[i] = sample_with_lookup(lookup, evaluated_tangents).normalized(); + sampled_tangents[i] = math::normalize(sample_with_lookup(lookup, evaluated_tangents)); } } @@ -182,7 +193,7 @@ class SampleCurveFunction : public fn::MultiFunction { for (const int i : mask) { const Spline::LookupResult &lookup = lookups[i]; const Span<float3> evaluated_normals = splines[spline_indices[i]]->evaluated_normals(); - sampled_normals[i] = sample_with_lookup(lookup, evaluated_normals).normalized(); + sampled_normals[i] = math::normalize(sample_with_lookup(lookup, evaluated_normals)); } } } @@ -198,8 +209,8 @@ class SampleCurveFunction : public fn::MultiFunction { static Field<float> get_length_input_field(const GeoNodeExecParams ¶ms, const float curve_total_length) { - const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const NodeGeometryCurveSample &storage = node_storage(params.node()); + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { /* Just make sure the length is in bounds of the curve. */ @@ -227,34 +238,32 @@ static Field<float> get_length_input_field(const GeoNodeExecParams ¶ms, return Field<float>(std::move(process_op), 0); } -static void geo_node_curve_sample_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - auto return_default = [&]() { - params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); - params.set_output("Tangent", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); - params.set_output("Normal", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); - }; - const CurveComponent *component = geometry_set.get_component_for_read<CurveComponent>(); if (component == nullptr) { - return return_default(); + params.set_default_remaining_outputs(); + return; } const CurveEval *curve = component->get_for_read(); if (curve == nullptr) { - return return_default(); + params.set_default_remaining_outputs(); + return; } if (curve->splines().is_empty()) { - return return_default(); + params.set_default_remaining_outputs(); + return; } Array<float> spline_lengths = curve->accumulated_spline_lengths(); const float total_length = spline_lengths.last(); if (total_length == 0.0f) { - return return_default(); + params.set_default_remaining_outputs(); + return; } Field<float> length_field = get_length_input_field(params, total_length); @@ -269,20 +278,22 @@ static void geo_node_curve_sample_exec(GeoNodeExecParams params) params.set_output("Normal", Field<float3>(sample_op, 2)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_sample_cc void register_node_type_geo_curve_sample() { + namespace file_ns = blender::nodes::node_geo_curve_sample_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_SAMPLE, "Curve Sample", NODE_CLASS_GEOMETRY, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_curve_sample_exec; - ntype.declare = blender::nodes::geo_node_curve_sample_declare; - node_type_init(&ntype, blender::nodes::geo_node_curve_sample_type_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_sample_update); + geo_node_type_base(&ntype, GEO_NODE_SAMPLE_CURVE, "Sample Curve", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_type_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage); - ntype.draw_buttons = blender::nodes::geo_node_curve_sample_layout; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc new file mode 100644 index 00000000000..74bdce4cef3 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc @@ -0,0 +1,149 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_set_handles_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCurveSetHandles) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSetHandles *data = MEM_cnew<NodeGeometryCurveSetHandles>(__func__); + + data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + node->storage = data; +} + +static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type) +{ + switch (type) { + case GEO_NODE_CURVE_HANDLE_AUTO: + return BezierSpline::HandleType::Auto; + case GEO_NODE_CURVE_HANDLE_ALIGN: + return BezierSpline::HandleType::Align; + case GEO_NODE_CURVE_HANDLE_FREE: + return BezierSpline::HandleType::Free; + case GEO_NODE_CURVE_HANDLE_VECTOR: + return BezierSpline::HandleType::Vector; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSetHandles &storage = node_storage(params.node()); + const GeometryNodeCurveHandleType type = (GeometryNodeCurveHandleType)storage.handle_type; + const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + bool has_bezier_spline = false; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the handles data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + GeometryComponentFieldContext field_context{curve_component, ATTR_DOMAIN_POINT}; + const int domain_size = curve_component.attribute_domain_size(ATTR_DOMAIN_POINT); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); + int point_index = 0; + + for (SplinePtr &spline : splines) { + if (spline->type() != Spline::Type::Bezier) { + point_index += spline->positions().size(); + continue; + } + + has_bezier_spline = true; + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); + if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { + /* In this case the automatically calculated handle types need to be "baked", because + * they're possibly changing from a type that is calculated automatically to a type that + * is positioned manually. */ + bezier_spline.ensure_auto_handles(); + } + + for (int i_point : IndexRange(bezier_spline.size())) { + if (selection[point_index]) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + bezier_spline.handle_types_left()[i_point] = new_handle_type; + } + if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { + bezier_spline.handle_types_right()[i_point] = new_handle_type; + } + } + point_index++; + } + bezier_spline.mark_cache_invalid(); + } + }); + if (!has_bezier_spline) { + params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve")); + } + params.set_output("Curve", std::move(geometry_set)); +} +} // namespace blender::nodes::node_geo_curve_set_handles_cc + +void register_node_type_geo_curve_set_handles() +{ + namespace file_ns = blender::nodes::node_geo_curve_set_handles_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_storage(&ntype, + "NodeGeometryCurveSetHandles", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = file_ns::node_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc new file mode 100644 index 00000000000..257a5b8df00 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_parameter.cc @@ -0,0 +1,326 @@ +/* + * 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_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_spline_parameter_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Factor")) + .field_source() + .description( + N_("For points, the portion of the spline's total length at the control point. For " + "Splines, the factor of that spline within the entire curve")); + b.add_output<decl::Float>(N_("Length")) + .field_source() + .description( + N_("For points, the distance along the control point's spline, For splines, the " + "distance along the entire curve")); + b.add_output<decl::Int>(N_("Index")) + .field_source() + .description(N_("Each control point's index on its spline")); +} + +/** + * A basic interpolation from the point domain to the spline domain would be useless, since the + * average parameter for each spline would just be 0.5, or close to it. Instead, the parameter for + * each spline is the portion of the total length at the start of the spline. + */ +static Array<float> curve_length_spline_domain(const CurveEval &curve, + const IndexMask UNUSED(mask)) +{ + Span<SplinePtr> splines = curve.splines(); + float length = 0.0f; + Array<float> lengths(splines.size()); + for (const int i : splines.index_range()) { + lengths[i] = length; + length += splines[i]->length(); + } + return lengths; +} + +/** + * The parameter at each control point is the factor at the corresponding evaluated point. + */ +static void calculate_bezier_lengths(const BezierSpline &spline, MutableSpan<float> lengths) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float> lengths_eval = spline.evaluated_lengths(); + for (const int i : IndexRange(1, spline.size() - 1)) { + lengths[i] = lengths_eval[offsets[i] - 1]; + } +} + +/** + * The parameter for poly splines is simply the evaluated lengths divided by the total length. + */ +static void calculate_poly_length(const PolySpline &spline, MutableSpan<float> lengths) +{ + Span<float> lengths_eval = spline.evaluated_lengths(); + if (spline.is_cyclic()) { + lengths.drop_front(1).copy_from(lengths_eval.drop_back(1)); + } + else { + lengths.drop_front(1).copy_from(lengths_eval); + } +} + +/** + * Since NURBS control points do not necessarily coincide with the evaluated curve's path, and + * each control point doesn't correspond well to a specific evaluated point, the parameter at + * each point is not well defined. So instead, treat the control points as if they were a poly + * spline. + */ +static void calculate_nurbs_lengths(const NURBSpline &spline, MutableSpan<float> lengths) +{ + Span<float3> positions = spline.positions(); + Array<float> control_point_lengths(spline.size()); + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + lengths[i] = length; + length += math::distance(positions[i], positions[i + 1]); + } + lengths.last() = length; +} + +static Array<float> curve_length_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float> lengths(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_factors{lengths.as_mutable_span().slice(offsets[i], spline.size())}; + spline_factors.first() = 0.0f; + switch (splines[i]->type()) { + case Spline::Type::Bezier: { + calculate_bezier_lengths(static_cast<const BezierSpline &>(spline), spline_factors); + break; + } + case Spline::Type::Poly: { + calculate_poly_length(static_cast<const PolySpline &>(spline), spline_factors); + break; + } + case Spline::Type::NURBS: { + calculate_nurbs_lengths(static_cast<const NURBSpline &>(spline), spline_factors); + break; + } + } + } + }); + return lengths; +} + +static VArray<float> construct_curve_parameter_varray(const CurveEval &curve, + const IndexMask mask, + const AttributeDomain domain) +{ + if (domain == ATTR_DOMAIN_POINT) { + Span<SplinePtr> splines = curve.splines(); + Array<float> values = curve_length_point_domain(curve); + + const Array<int> offsets = curve.control_point_offsets(); + for (const int i_spline : curve.splines().index_range()) { + const Spline &spline = *splines[i_spline]; + const float spline_length = spline.length(); + const float spline_length_inv = spline_length == 0.0f ? 0.0f : 1.0f / spline_length; + for (const int i : IndexRange(spline.size())) { + values[offsets[i_spline] + i] *= spline_length_inv; + } + } + return VArray<float>::ForContainer(std::move(values)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float> values = curve.accumulated_spline_lengths(); + const float total_length_inv = values.last() == 0.0f ? 0.0f : 1.0f / values.last(); + for (const int i : mask) { + values[i] *= total_length_inv; + } + return VArray<float>::ForContainer(std::move(values)); + } + return {}; +} + +static VArray<float> construct_curve_length_varray(const CurveEval &curve, + const IndexMask mask, + const AttributeDomain domain) +{ + if (domain == ATTR_DOMAIN_POINT) { + Array<float> lengths = curve_length_point_domain(curve); + return VArray<float>::ForContainer(std::move(lengths)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + if (curve.splines().size() == 1) { + Array<float> lengths(1, 0.0f); + return VArray<float>::ForContainer(std::move(lengths)); + } + + Array<float> lengths = curve_length_spline_domain(curve, mask); + return VArray<float>::ForContainer(std::move(lengths)); + } + + return {}; +} + +static VArray<int> construct_index_on_spline_varray(const CurveEval &curve, + const IndexMask UNUSED(mask), + const AttributeDomain domain) +{ + if (domain == ATTR_DOMAIN_POINT) { + Array<int> output(curve.total_control_point_size()); + int output_index = 0; + for (int spline_index : curve.splines().index_range()) { + for (int point_index : IndexRange(curve.splines()[spline_index]->size())) { + output[output_index++] = point_index; + } + } + return VArray<int>::ForContainer(std::move(output)); + } + return {}; +} + +class CurveParameterFieldInput final : public GeometryFieldInput { + public: + CurveParameterFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Curve Parameter node") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve) { + return construct_curve_parameter_varray(*curve, mask, domain); + } + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 29837456298; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr; + } +}; + +class CurveLengthFieldInput final : public GeometryFieldInput { + public: + CurveLengthFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Curve Length node") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve) { + return construct_curve_length_varray(*curve, mask, domain); + } + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 345634563454; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const CurveLengthFieldInput *>(&other) != nullptr; + } +}; + +class IndexOnSplineFieldInput final : public GeometryFieldInput { + public: + IndexOnSplineFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Spline Index") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve) { + return construct_index_on_spline_varray(*curve, mask, domain); + } + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 4536246522; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const IndexOnSplineFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()}; + Field<float> length_field{std::make_shared<CurveLengthFieldInput>()}; + Field<int> index_on_spline_field{std::make_shared<IndexOnSplineFieldInput>()}; + params.set_output("Factor", std::move(parameter_field)); + params.set_output("Length", std::move(length_field)); + params.set_output("Index", std::move(index_on_spline_field)); +} + +} // namespace blender::nodes::node_geo_curve_spline_parameter_cc + +void register_node_type_geo_curve_spline_parameter() +{ + namespace file_ns = blender::nodes::node_geo_curve_spline_parameter_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_SPLINE_PARAMETER, "Spline Parameter", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc new file mode 100644 index 00000000000..b91ddd7bc7a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -0,0 +1,434 @@ +/* + * 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 "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_spline_type_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCurveSplineType) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "spline_type", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSplineType *data = MEM_cnew<NodeGeometryCurveSplineType>(__func__); + + data->spline_type = GEO_NODE_SPLINE_TYPE_POLY; + node->storage = data; +} + +template<class T> +static void scale_input_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> r_output) +{ + for (const int i : IndexRange(r_output.size())) { + r_output[i] = input[i * scale + offset]; + } +} + +template<class T> +static void scale_output_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> &r_output) +{ + for (const int i : IndexRange(input.size())) { + r_output[i * scale + offset] = input[i]; + } +} + +template<class T> +static void nurbs_to_bezier_assign(const Span<T> input, + const MutableSpan<T> r_output, + const NURBSpline::KnotsMode knotsMode) +{ + const int input_size = input.size(); + const int output_size = r_output.size(); + + switch (knotsMode) { + case NURBSpline::KnotsMode::Bezier: + scale_input_assign<T>(input, 3, 1, r_output); + break; + case NURBSpline::KnotsMode::Normal: + for (const int i : IndexRange(output_size)) { + r_output[i] = input[(i + 1) % input_size]; + } + break; + case NURBSpline::KnotsMode::EndPoint: + for (const int i : IndexRange(1, output_size - 2)) { + r_output[i] = input[i + 1]; + } + r_output.first() = input.first(); + r_output.last() = input.last(); + break; + } +} + +template<typename CopyFn> +static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) +{ + input_spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id); + if (!dst) { + BLI_assert_unreachable(); + return false; + } + + copy_fn(*src, *dst); + + return true; + }, + ATTR_DOMAIN_POINT); +} + +static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_positions, + const NURBSpline::KnotsMode knots_mode) +{ + const int nurbs_positions_size = nurbs_positions.size(); + Vector<float3> handle_positions; + if (knots_mode == NURBSpline::KnotsMode::Bezier) { + for (const int i : IndexRange(nurbs_positions_size)) { + if (i % 3 == 1) { + continue; + } + handle_positions.append(nurbs_positions[i]); + } + if (nurbs_positions_size % 3 == 1) { + handle_positions.pop_last(); + } + else if (nurbs_positions_size % 3 == 2) { + const int last_index = nurbs_positions_size - 1; + handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); + } + } + else { + const bool is_periodic = knots_mode == NURBSpline::KnotsMode::Normal; + if (is_periodic) { + handle_positions.append(nurbs_positions[1] + + ((nurbs_positions[0] - nurbs_positions[1]) / 3)); + } + else { + handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]); + handle_positions.append(nurbs_positions[1]); + } + const int segments_size = nurbs_positions_size - 1; + const bool ignore_interior_segment = segments_size == 3 && is_periodic == false; + if (ignore_interior_segment == false) { + const float mid_offset = (float)(segments_size - 1) / 2.0f; + for (const int i : IndexRange(1, segments_size - 2)) { + const int divisor = is_periodic ? + 3 : + std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f)); + const float3 &p1 = nurbs_positions[i]; + const float3 &p2 = nurbs_positions[i + 1]; + const float3 displacement = (p2 - p1) / divisor; + const int num_handles_on_segment = divisor < 3 ? 1 : 2; + for (int j : IndexRange(1, num_handles_on_segment)) { + handle_positions.append(p1 + (displacement * j)); + } + } + } + const int last_index = nurbs_positions_size - 1; + if (is_periodic) { + handle_positions.append( + nurbs_positions[last_index - 1] + + ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3)); + } + else { + handle_positions.append(nurbs_positions[last_index - 1]); + handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); + } + } + return handle_positions; +} + +static Array<float3> create_nurbs_to_bezier_positions(const Span<float3> nurbs_positions, + const Span<float3> handle_positions, + const NURBSpline::KnotsMode knots_mode) +{ + if (knots_mode == NURBSpline::KnotsMode::Bezier) { + /* Every third NURBS position (starting from index 1) should be converted to Bezier position */ + const int scale = 3; + const int offset = 1; + Array<float3> bezier_positions((nurbs_positions.size() + offset) / scale); + scale_input_assign(nurbs_positions, scale, offset, bezier_positions.as_mutable_span()); + return bezier_positions; + } + + Array<float3> bezier_positions(handle_positions.size() / 2); + for (const int i : IndexRange(bezier_positions.size())) { + bezier_positions[i] = math::interpolate( + handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f); + } + return bezier_positions; +} + +static SplinePtr convert_to_poly_spline(const Spline &input) +{ + std::unique_ptr<PolySpline> output = std::make_unique<PolySpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr poly_to_nurbs(const Spline &input) +{ + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + Spline::copy_base_settings(input, *output); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes = input.attributes; + return output; +} + +static SplinePtr bezier_to_nurbs(const Spline &input) +{ + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(input); + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.size() * 3); + + scale_output_assign(bezier_spline.handle_positions_left(), 3, 0, output->positions()); + scale_output_assign(input.radii(), 3, 0, output->radii()); + scale_output_assign(input.tilts(), 3, 0, output->tilts()); + + scale_output_assign(bezier_spline.positions(), 3, 1, output->positions()); + scale_output_assign(input.radii(), 3, 1, output->radii()); + scale_output_assign(input.tilts(), 3, 1, output->tilts()); + + scale_output_assign(bezier_spline.handle_positions_right(), 3, 2, output->positions()); + scale_output_assign(input.radii(), 3, 2, output->radii()); + scale_output_assign(input.tilts(), 3, 2, output->tilts()); + + Spline::copy_base_settings(input, *output); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + output->set_cyclic(input.is_cyclic()); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_output_assign<T>(src.typed<T>(), 3, 0, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 2, dst.typed<T>()); + }); + }); + return output; +} + +static SplinePtr poly_to_bezier(const Spline &input) +{ + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(input.size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Vector); + output->handle_types_right().fill(BezierSpline::HandleType::Vector); + output->set_resolution(12); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr nurbs_to_bezier(const Spline &input) +{ + const NURBSpline &nurbs_spline = static_cast<const NURBSpline &>(input); + Span<float3> nurbs_positions; + Vector<float3> nurbs_positions_vector; + NURBSpline::KnotsMode knots_mode; + if (nurbs_spline.is_cyclic()) { + nurbs_positions_vector = nurbs_spline.positions(); + nurbs_positions_vector.append(nurbs_spline.positions()[0]); + nurbs_positions_vector.append(nurbs_spline.positions()[1]); + nurbs_positions = nurbs_positions_vector; + knots_mode = NURBSpline::KnotsMode::Normal; + } + else { + nurbs_positions = nurbs_spline.positions(); + knots_mode = nurbs_spline.knots_mode; + } + const Vector<float3> handle_positions = create_nurbs_to_bezier_handles(nurbs_positions, + knots_mode); + BLI_assert(handle_positions.size() % 2 == 0); + const Array<float3> bezier_positions = create_nurbs_to_bezier_positions( + nurbs_positions, handle_positions.as_span(), knots_mode); + BLI_assert(handle_positions.size() == bezier_positions.size() * 2); + + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(bezier_positions.size()); + output->positions().copy_from(bezier_positions); + nurbs_to_bezier_assign(nurbs_spline.radii(), output->radii(), knots_mode); + nurbs_to_bezier_assign(nurbs_spline.tilts(), output->tilts(), knots_mode); + scale_input_assign(handle_positions.as_span(), 2, 0, output->handle_positions_left()); + scale_input_assign(handle_positions.as_span(), 2, 1, output->handle_positions_right()); + output->handle_types_left().fill(BezierSpline::HandleType::Align); + output->handle_types_right().fill(BezierSpline::HandleType::Align); + output->set_resolution(nurbs_spline.resolution()); + Spline::copy_base_settings(nurbs_spline, *output); + output->attributes.reallocate(output->size()); + copy_attributes(nurbs_spline, *output, [knots_mode](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + nurbs_to_bezier_assign(src.typed<T>(), dst.typed<T>(), knots_mode); + }); + }); + return output; +} + +static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params) +{ + switch (input.type()) { + case Spline::Type::Bezier: + return input.copy(); + case Spline::Type::Poly: + return poly_to_bezier(input); + case Spline::Type::NURBS: + if (input.size() < 4) { + params.error_message_add( + NodeWarningType::Info, + TIP_("NURBS must have minimum of 4 points for Bezier Conversion")); + return input.copy(); + } + return nurbs_to_bezier(input); + } + BLI_assert_unreachable(); + return {}; +} + +static SplinePtr convert_to_nurbs(const Spline &input) +{ + switch (input.type()) { + case Spline::Type::NURBS: + return input.copy(); + case Spline::Type::Bezier: + return bezier_to_nurbs(input); + case Spline::Type::Poly: + return poly_to_nurbs(input); + } + BLI_assert_unreachable(); + return {}; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSplineType &storage = node_storage(params.node()); + const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage.spline_type; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } + + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component->get_for_read(); + GeometryComponentFieldContext field_context{*curve_component, ATTR_DOMAIN_CURVE}; + const int domain_size = curve_component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); + new_curve->resize(curve.splines().size()); + + threading::parallel_for(curve.splines().index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->splines()[i] = convert_to_poly_spline(*curve.splines()[i]); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->splines()[i] = convert_to_bezier(*curve.splines()[i], params); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->splines()[i] = convert_to_nurbs(*curve.splines()[i]); + break; + } + } + else { + new_curve->splines()[i] = curve.splines()[i]->copy(); + } + } + }); + new_curve->attributes = curve.attributes; + geometry_set.replace_curve(new_curve.release()); + }); + + params.set_output("Curve", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_curve_spline_type_cc + +void register_node_type_geo_curve_spline_type() +{ + namespace file_ns = blender::nodes::node_geo_curve_spline_type_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_storage(&ntype, + "NodeGeometryCurveSplineType", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = file_ns::node_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc new file mode 100644 index 00000000000..ae282017e0c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -0,0 +1,366 @@ +/* + * 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_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" + +namespace blender::nodes::node_geo_curve_subdivide_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Int>(N_("Cuts")).default_value(1).min(0).max(1000).supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static Array<int> get_subdivided_offsets(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + Array<int> offsets(spline.segments_size() + 1); + int offset = 0; + for (const int i : IndexRange(spline.segments_size())) { + offsets[i] = offset; + offset = offset + std::max(cuts[spline_offset + i], 0) + 1; + } + offsets.last() = offset; + return offsets; +} + +template<typename T> +static void subdivide_attribute(Span<T> src, + const Span<int> offsets, + const bool is_cyclic, + MutableSpan<T> dst) +{ + const int src_size = src.size(); + threading::parallel_for(IndexRange(src_size - 1), 1024, [&](IndexRange range) { + for (const int i : range) { + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src[i]; + const float factor_delta = cuts == 0 ? 1.0f : 1.0f / cuts; + for (const int cut : IndexRange(cuts)) { + const float factor = cut * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); + } + } + }); + + if (is_cyclic) { + const int i = src_size - 1; + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src.last(); + const float factor_delta = cuts == 0 ? 1.0f : 1.0f / cuts; + for (const int cut : IndexRange(cuts)) { + const float factor = cut * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); + } + } + else { + dst.last() = src.last(); + } +} + +/** + * In order to generate a Bezier spline with the same shape as the input spline, apply the + * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the + * previous result point's right handle and the left handle at the end of the segment. + * + * \note Non-vector segments in the result spline are given free handles. This could possibly be + * improved with another pass that sets handles to aligned where possible, but currently that does + * not provide much benefit for the increased complexity. + */ +static void subdivide_bezier_segment(const BezierSpline &src, + const int index, + const int offset, + const int result_size, + Span<float3> src_positions, + Span<float3> src_handles_left, + Span<float3> src_handles_right, + MutableSpan<float3> dst_positions, + MutableSpan<float3> dst_handles_left, + MutableSpan<float3> dst_handles_right, + MutableSpan<BezierSpline::HandleType> dst_type_left, + MutableSpan<BezierSpline::HandleType> dst_type_right) +{ + const bool is_last_cyclic_segment = index == (src.size() - 1); + const int next_index = is_last_cyclic_segment ? 0 : index + 1; + + /* The first point in the segment is always copied. */ + dst_positions[offset] = src_positions[index]; + + if (src.segment_is_vector(index)) { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Vector; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector); + + const float factor_delta = 1.0f / result_size; + for (const int cut : IndexRange(result_size)) { + const float factor = cut * factor_delta; + dst_positions[offset + cut] = attribute_math::mix2( + factor, src_positions[index], src_positions[next_index]); + } + } + else { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Free; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free); + + const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size; + + /* Create a Bezier segment to update iteratively for every subdivision + * and references to the meaningful values for ease of use. */ + BezierSpline temp; + temp.resize(2); + float3 &segment_start = temp.positions().first(); + float3 &segment_end = temp.positions().last(); + float3 &handle_prev = temp.handle_positions_right().first(); + float3 &handle_next = temp.handle_positions_left().last(); + segment_start = src_positions[index]; + segment_end = src_positions[next_index]; + handle_prev = src_handles_right[index]; + handle_next = src_handles_left[next_index]; + + for (const int cut : IndexRange(result_size - 1)) { + const float parameter = 1.0f / (result_size - cut); + const BezierSpline::InsertResult insert = temp.calculate_segment_insertion(0, 1, parameter); + + /* Copy relevant temporary data to the result. */ + dst_handles_right[offset + cut] = insert.handle_prev; + dst_handles_left[offset + cut + 1] = insert.left_handle; + dst_positions[offset + cut + 1] = insert.position; + + /* Update the segment to prepare it for the next subdivision. */ + segment_start = insert.position; + handle_prev = insert.right_handle; + handle_next = insert.handle_next; + } + + /* Copy the handles for the last segment from the temporary spline. */ + dst_handles_right[offset + result_size - 1] = handle_prev; + dst_handles_left[i_segment_last] = handle_next; + } +} + +static void subdivide_bezier_spline(const BezierSpline &src, + const Span<int> offsets, + BezierSpline &dst) +{ + Span<float3> src_positions = src.positions(); + Span<float3> src_handles_left = src.handle_positions_left(); + Span<float3> src_handles_right = src.handle_positions_right(); + MutableSpan<float3> dst_positions = dst.positions(); + MutableSpan<float3> dst_handles_left = dst.handle_positions_left(); + MutableSpan<float3> dst_handles_right = dst.handle_positions_right(); + MutableSpan<BezierSpline::HandleType> dst_type_left = dst.handle_types_left(); + MutableSpan<BezierSpline::HandleType> dst_type_right = dst.handle_types_right(); + + threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { + for (const int i : range) { + subdivide_bezier_segment(src, + i, + offsets[i], + offsets[i + 1] - offsets[i], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + }); + + if (src.is_cyclic()) { + const int i_last = src.size() - 1; + subdivide_bezier_segment(src, + i_last, + offsets[i_last], + offsets.last() - offsets[i_last], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + else { + dst_positions.last() = src_positions.last(); + } +} + +static void subdivide_builtin_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + subdivide_attribute<float>(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); + subdivide_attribute<float>(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); + switch (src_spline.type()) { + case Spline::Type::Poly: { + const PolySpline &src = static_cast<const PolySpline &>(src_spline); + PolySpline &dst = static_cast<PolySpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + break; + } + case Spline::Type::Bezier: { + const BezierSpline &src = static_cast<const BezierSpline &>(src_spline); + BezierSpline &dst = static_cast<BezierSpline &>(dst_spline); + subdivide_bezier_spline(src, offsets, dst); + dst.mark_cache_invalid(); + break; + } + case Spline::Type::NURBS: { + const NURBSpline &src = static_cast<const NURBSpline &>(src_spline); + NURBSpline &dst = static_cast<NURBSpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + subdivide_attribute<float>(src.weights(), offsets, is_cyclic, dst.weights()); + break; + } + } +} + +static void subdivide_dynamic_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + src_spline.attributes.foreach_attribute( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + + if (!dst_spline.attributes.create(attribute_id, 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> dst = dst_spline.attributes.get_for_write(attribute_id); + BLI_assert(dst); + + attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute<T>(src->typed<T>(), offsets, is_cyclic, dst->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr subdivide_spline(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + if (spline.size() <= 1) { + return spline.copy(); + } + + /* Since we expect to access each value many times, it should be worth it to make sure count + * of cuts is a real span (especially considering the note below). Using the offset at each + * point facilitates subdividing in parallel later. */ + Array<int> offsets = get_subdivided_offsets(spline, cuts, spline_offset); + const int result_size = offsets.last() + int(!spline.is_cyclic()); + SplinePtr new_spline = spline.copy_only_settings(); + new_spline->resize(result_size); + subdivide_builtin_attributes(spline, offsets, *new_spline); + subdivide_dynamic_attributes(spline, offsets, *new_spline); + return new_spline; +} + +/** + * \note Passing the virtual array for the entire spline is possibly quite inefficient here when + * the attribute was on the point domain and stored separately for each spline already, and it + * prevents some other optimizations like skipping splines with a single attribute value of < 1. + * However, it allows the node to access builtin attribute easily, so it the makes most sense this + * way until the attribute API is refactored. + */ +static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, + const VArray<int> &cuts) +{ + const Array<int> control_point_offsets = input_curve.control_point_offsets(); + const Span<SplinePtr> input_splines = input_curve.splines(); + + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + output_curve->resize(input_splines.size()); + output_curve->attributes = input_curve.attributes; + MutableSpan<SplinePtr> output_splines = output_curve->splines(); + + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); + } + }); + + return output_curve; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<int> cuts_field = params.extract_input<Field<int>>("Cuts"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } + + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(cuts_field); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); + + if (cuts.is_single() && cuts.get_internal_single() < 1) { + return; + } + + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), cuts); + geometry_set.replace_curve(output_curve.release()); + }); + params.set_output("Curve", geometry_set); +} + +} // namespace blender::nodes::node_geo_curve_subdivide_cc + +void register_node_type_geo_curve_subdivide() +{ + namespace file_ns = blender::nodes::node_geo_curve_subdivide_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SUBDIVIDE_CURVE, "Subdivide Curve", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 index f7cef9bbf63..ef4fc51d1b3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -23,71 +23,65 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_curve_to_mesh_cc { -static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Geometry>("Profile Curve"); - b.add_output<decl::Geometry>("Mesh"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Geometry>(N_("Profile Curve")) + .only_realized_data() + .supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Fill Caps")) + .description( + N_("If the profile spline is cyclic, fill the ends of the generated mesh with N-gons")); + b.add_output<decl::Geometry>(N_("Mesh")); } static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set, - const GeoNodeExecParams ¶ms) + const bool fill_caps) { - if (!geometry_set.has_curve()) { - if (!geometry_set.is_empty()) { - params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in curve input")); - } - return; - } - + const CurveEval *curve = geometry_set.get_curve_for_read(); const CurveEval *profile_curve = profile_set.get_curve_for_read(); if (profile_curve == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(*geometry_set.get_curve_for_read()); + Mesh *mesh = bke::curve_to_wire_mesh(*curve); geometry_set.replace_mesh(mesh); } else { - Mesh *mesh = bke::curve_to_mesh_sweep(*geometry_set.get_curve_for_read(), *profile_curve); + Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps); geometry_set.replace_mesh(mesh); } } -static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); + const bool fill_caps = params.extract_input<bool>("Fill Caps"); - if (profile_set.has_instances()) { - params.error_message_add(NodeWarningType::Error, - TIP_("Instances are not supported in the profile input")); - params.set_output("Mesh", GeometrySet()); - return; - } - - if (!profile_set.has_curve() && !profile_set.is_empty()) { - params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in the profile input")); - } - + bool has_curve = false; curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_to_mesh(geometry_set, profile_set, params); + if (geometry_set.has_curve()) { + has_curve = true; + geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps); + } + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); }); params.set_output("Mesh", std::move(curve_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_to_mesh_cc void register_node_type_geo_curve_to_mesh() { + namespace file_ns = blender::nodes::node_geo_curve_to_mesh_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_to_mesh_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_mesh_exec; + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc new file mode 100644 index 00000000000..19efd4b7508 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -0,0 +1,414 @@ +/* + * 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_pointcloud.h" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { +void curve_create_default_rotation_attribute(Span<float3> tangents, + Span<float3> normals, + MutableSpan<float3> rotations) +{ + threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) { + for (const int i : range) { + rotations[i] = + float4x4::from_normalized_axis_data({0, 0, 0}, normals[i], tangents[i]).to_euler(); + } + }); +} +} // namespace blender::nodes + +namespace blender::nodes::node_geo_curve_to_points_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCurveToPoints) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Int>(N_("Count")) + .default_value(10) + .min(2) + .max(100000) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT; }); + b.add_input<decl::Float>(N_("Length")) + .default_value(0.1f) + .min(0.001f) + .subtype(PROP_DISTANCE) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_LENGTH; }); + b.add_output<decl::Geometry>(N_("Points")); + b.add_output<decl::Vector>(N_("Tangent")).field_source(); + b.add_output<decl::Vector>(N_("Normal")).field_source(); + b.add_output<decl::Vector>(N_("Rotation")).field_source(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveToPoints *data = MEM_cnew<NodeGeometryCurveToPoints>(__func__); + + data->mode = GEO_NODE_CURVE_RESAMPLE_COUNT; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryCurveToPoints &storage = node_storage(*node); + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; + + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length_socket = count_socket->next; + + nodeSetSocketAvailability(ntree, count_socket, mode == GEO_NODE_CURVE_RESAMPLE_COUNT); + nodeSetSocketAvailability(ntree, length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); +} + +static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, + const GeometryNodeCurveResampleMode mode, + const CurveEval &curve, + const Span<SplinePtr> splines) +{ + const int size = curve.splines().size(); + switch (mode) { + case GEO_NODE_CURVE_RESAMPLE_COUNT: { + const int count = params.get_input<int>("Count"); + if (count < 1) { + return {0}; + } + Array<int> offsets(size + 1); + int offset = 0; + for (const int i : IndexRange(size)) { + offsets[i] = offset; + if (splines[i]->evaluated_points_size() > 0) { + offset += count; + } + } + offsets.last() = offset; + return offsets; + } + case GEO_NODE_CURVE_RESAMPLE_LENGTH: { + /* Don't allow asymptotic count increase for low resolution values. */ + const float resolution = std::max(params.get_input<float>("Length"), 0.0001f); + Array<int> offsets(size + 1); + int offset = 0; + for (const int i : IndexRange(size)) { + offsets[i] = offset; + if (splines[i]->evaluated_points_size() > 0) { + offset += splines[i]->length() / resolution + 1; + } + } + offsets.last() = offset; + return offsets; + } + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: { + return curve.evaluated_point_offsets(); + } + } + BLI_assert_unreachable(); + return {0}; +} + +/** + * \note: Relies on the fact that all attributes on point clouds are stored contiguously. + */ +static GMutableSpan ensure_point_attribute(PointCloudComponent &points, + const AttributeIDRef &attribute_id, + const CustomDataType data_type) +{ + points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); + WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); + BLI_assert(attribute); + return attribute.varray.get_internal_span(); +} + +template<typename T> +static MutableSpan<T> ensure_point_attribute(PointCloudComponent &points, + const AttributeIDRef &attribute_id) +{ + GMutableSpan attribute = ensure_point_attribute( + points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); + return attribute.typed<T>(); +} + +namespace { +struct AnonymousAttributeIDs { + StrongAnonymousAttributeID tangent_id; + StrongAnonymousAttributeID normal_id; + StrongAnonymousAttributeID rotation_id; +}; + +struct ResultAttributes { + MutableSpan<float3> positions; + MutableSpan<float> radii; + + Map<AttributeIDRef, GMutableSpan> point_attributes; + + MutableSpan<float3> tangents; + MutableSpan<float3> normals; + MutableSpan<float3> rotations; +}; +} // namespace + +static ResultAttributes create_attributes_for_transfer(PointCloudComponent &points, + const CurveEval &curve, + const AnonymousAttributeIDs &attributes) +{ + ResultAttributes outputs; + + outputs.positions = ensure_point_attribute<float3>(points, "position"); + outputs.radii = ensure_point_attribute<float>(points, "radius"); + + if (attributes.tangent_id) { + outputs.tangents = ensure_point_attribute<float3>(points, attributes.tangent_id.get()); + } + if (attributes.normal_id) { + outputs.normals = ensure_point_attribute<float3>(points, attributes.normal_id.get()); + } + if (attributes.rotation_id) { + outputs.rotations = ensure_point_attribute<float3>(points, attributes.rotation_id.get()); + } + + /* Because of the invariants of the curve component, we use the attributes of the first spline + * as a representative for the attribute meta data all splines. Attributes from the spline domain + * are handled separately. */ + curve.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (id.should_be_kept()) { + outputs.point_attributes.add_new( + id, ensure_point_attribute(points, id, meta_data.data_type)); + } + return true; + }, + ATTR_DOMAIN_POINT); + + return outputs; +} + +/** + * TODO: For non-poly splines, this has double copies that could be avoided as part + * of a general look at optimizing uses of #Spline::interpolate_to_evaluated. + */ +static void copy_evaluated_point_attributes(const Span<SplinePtr> splines, + const Span<int> offsets, + ResultAttributes &data) +{ + threading::parallel_for(splines.index_range(), 64, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + + data.positions.slice(offset, size).copy_from(spline.evaluated_positions()); + spline.interpolate_to_evaluated(spline.radii()).materialize(data.radii.slice(offset, size)); + + for (const Map<AttributeIDRef, GMutableSpan>::Item item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; + const GMutableSpan dst = item.value; + + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); + + spline.interpolate_to_evaluated(spline_span).materialize(dst.slice(offset, size).data()); + } + + if (!data.tangents.is_empty()) { + data.tangents.slice(offset, size).copy_from(spline.evaluated_tangents()); + } + if (!data.normals.is_empty()) { + data.normals.slice(offset, size).copy_from(spline.evaluated_normals()); + } + } + }); +} + +static void copy_uniform_sample_point_attributes(const Span<SplinePtr> splines, + const Span<int> offsets, + ResultAttributes &data) +{ + threading::parallel_for(splines.index_range(), 64, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + if (size == 0) { + continue; + } + + const Array<float> uniform_samples = spline.sample_uniform_index_factors(size); + + spline.sample_with_index_factors<float3>( + spline.evaluated_positions(), uniform_samples, data.positions.slice(offset, size)); + spline.sample_with_index_factors<float>(spline.interpolate_to_evaluated(spline.radii()), + uniform_samples, + data.radii.slice(offset, size)); + + for (const Map<AttributeIDRef, GMutableSpan>::Item item : data.point_attributes.items()) { + const AttributeIDRef attribute_id = item.key; + const GMutableSpan dst = item.value; + + BLI_assert(spline.attributes.get_for_read(attribute_id)); + GSpan spline_span = *spline.attributes.get_for_read(attribute_id); + + spline.sample_with_index_factors(spline.interpolate_to_evaluated(spline_span), + uniform_samples, + dst.slice(offset, size)); + } + + if (!data.tangents.is_empty()) { + spline.sample_with_index_factors<float3>( + spline.evaluated_tangents(), uniform_samples, data.tangents.slice(offset, size)); + for (float3 &tangent : data.tangents) { + tangent = math::normalize(tangent); + } + } + + if (!data.normals.is_empty()) { + spline.sample_with_index_factors<float3>( + spline.evaluated_normals(), uniform_samples, data.normals.slice(offset, size)); + for (float3 &normals : data.normals) { + normals = math::normalize(normals); + } + } + } + }); +} + +static void copy_spline_domain_attributes(const CurveEval &curve, + const Span<int> offsets, + PointCloudComponent &points) +{ + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + const GSpan curve_attribute = *curve.attributes.get_for_read(attribute_id); + const CPPType &type = curve_attribute.type(); + const GMutableSpan dst = ensure_point_attribute(points, attribute_id, meta_data.data_type); + + for (const int i : curve.splines().index_range()) { + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + type.fill_assign_n(curve_attribute[i], dst[offset], size); + } + + return true; + }, + ATTR_DOMAIN_CURVE); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveToPoints &storage = node_storage(params.node()); + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + AnonymousAttributeIDs attribute_outputs; + attribute_outputs.tangent_id = StrongAnonymousAttributeID("Tangent"); + attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal"); + attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + const CurveEval &curve = *geometry_set.get_curve_for_read(); + const Span<SplinePtr> splines = curve.splines(); + curve.assert_valid_point_attributes(); + + const Array<int> offsets = calculate_spline_point_offsets(params, mode, curve, splines); + const int total_size = offsets.last(); + if (total_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + geometry_set.replace_pointcloud(BKE_pointcloud_new_nomain(total_size)); + PointCloudComponent &points = geometry_set.get_component_for_write<PointCloudComponent>(); + ResultAttributes point_attributes = create_attributes_for_transfer( + points, curve, attribute_outputs); + + switch (mode) { + case GEO_NODE_CURVE_RESAMPLE_COUNT: + case GEO_NODE_CURVE_RESAMPLE_LENGTH: + copy_uniform_sample_point_attributes(splines, offsets, point_attributes); + break; + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: + copy_evaluated_point_attributes(splines, offsets, point_attributes); + break; + } + + copy_spline_domain_attributes(curve, offsets, points); + + if (!point_attributes.rotations.is_empty()) { + curve_create_default_rotation_attribute( + point_attributes.tangents, point_attributes.normals, point_attributes.rotations); + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES, GEO_COMPONENT_TYPE_POINT_CLOUD}); + }); + + params.set_output("Points", std::move(geometry_set)); + if (attribute_outputs.tangent_id) { + params.set_output( + "Tangent", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.tangent_id), + params.attribute_producer_name())); + } + if (attribute_outputs.normal_id) { + params.set_output( + "Normal", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id), + params.attribute_producer_name())); + } + if (attribute_outputs.rotation_id) { + params.set_output( + "Rotation", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id), + params.attribute_producer_name())); + } +} + +} // namespace blender::nodes::node_geo_curve_to_points_cc + +void register_node_type_geo_curve_to_points() +{ + namespace file_ns = blender::nodes::node_geo_curve_to_points_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_storage( + &ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 97043980899..359863d39e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -20,50 +20,105 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" +namespace blender::nodes::node_geo_curve_trim_cc { + using blender::attribute_math::mix2; -namespace blender::nodes { +NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) -static void geo_node_curve_trim_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Float>("Start").min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Float>("End").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Float>("Start", "Start_001").min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("End", "End_001").min(0.0f).default_value(1.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Curve"); + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Float>(N_("Start")) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }) + .supports_field(); + b.add_input<decl::Float>(N_("End")) + .min(0.0f) + .max(1.0f) + .default_value(1.0f) + .subtype(PROP_FACTOR) + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }) + .supports_field(); + b.add_input<decl::Float>(N_("Start"), "Start_001") + .min(0.0f) + .subtype(PROP_DISTANCE) + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }) + .supports_field(); + b.add_input<decl::Float>(N_("End"), "End_001") + .min(0.0f) + .default_value(1.0f) + .subtype(PROP_DISTANCE) + .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }) + .supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); } -static void geo_node_curve_trim_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_curve_trim_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryCurveTrim *data = (NodeGeometryCurveTrim *)MEM_callocN(sizeof(NodeGeometryCurveTrim), - __func__); + NodeGeometryCurveTrim *data = MEM_cnew<NodeGeometryCurveTrim>(__func__); data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR; node->storage = data; } -static void geo_node_curve_trim_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)node->storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const NodeGeometryCurveTrim &storage = node_storage(*node); + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; bNodeSocket *start_fac = ((bNodeSocket *)node->inputs.first)->next; bNodeSocket *end_fac = start_fac->next; bNodeSocket *start_len = end_fac->next; bNodeSocket *end_len = start_len->next; - nodeSetSocketAvailability(start_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); - nodeSetSocketAvailability(end_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); - nodeSetSocketAvailability(start_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); - nodeSetSocketAvailability(end_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(ntree, start_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(ntree, end_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(ntree, start_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); + nodeSetSocketAvailability(ntree, end_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +class SocketSearchOp { + public: + StringRef socket_name; + GeometryNodeCurveSampleMode mode; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("GeometryNodeTrimCurve"); + node_storage(node).mode = mode; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + + search_link_ops_for_declarations(params, declaration.outputs()); + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + + if (params.in_out() == SOCK_IN) { + if (params.node_tree().typeinfo->validate_link( + static_cast<eNodeSocketDatatype>(params.other_socket().type), SOCK_FLOAT)) { + params.add_item(IFACE_("Start (Factor)"), + SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_FACTOR}); + params.add_item(IFACE_("End (Factor)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_FACTOR}); + params.add_item(IFACE_("Start (Length)"), + SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_LENGTH}); + params.add_item(IFACE_("End (Length)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_LENGTH}); + } + } } struct TrimLocation { @@ -123,19 +178,19 @@ static void linear_trim_to_output_data(const TrimLocation &start, /* Look up the control points to the left and right of factor, and get the factor between them. */ static TrimLocation lookup_control_point_position(const Spline::LookupResult &lookup, - Span<int> control_point_offsets) + const BezierSpline &spline) { - const int *left_offset = std::lower_bound( - control_point_offsets.begin(), control_point_offsets.end(), lookup.evaluated_index); - const int index = left_offset - control_point_offsets.begin(); - const int left = control_point_offsets[index] > lookup.evaluated_index ? index - 1 : index; - const int right = left + 1; - - const float factor = std::clamp( - (lookup.evaluated_index + lookup.factor - control_point_offsets[left]) / - (control_point_offsets[right] - control_point_offsets[left]), - 0.0f, - 1.0f); + Span<int> offsets = spline.control_point_offsets(); + + const int *offset = std::lower_bound(offsets.begin(), offsets.end(), lookup.evaluated_index); + const int index = offset - offsets.begin(); + + const int left = offsets[index] > lookup.evaluated_index ? index - 1 : index; + const int right = left == (spline.size() - 1) ? 0 : left + 1; + + const float offset_in_segment = lookup.evaluated_index + lookup.factor - offsets[left]; + const int segment_eval_size = offsets[left + 1] - offsets[left]; + const float factor = std::clamp(offset_in_segment / segment_eval_size, 0.0f, 1.0f); return {left, right, factor}; } @@ -204,9 +259,9 @@ static PolySpline trim_nurbs_spline(const Spline &spline, attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { using T = decltype(dummy); - GVArray_Typed<T> eval_data = spline.interpolate_to_evaluated<T>(src->typed<T>()); + VArray<T> eval_data = spline.interpolate_to_evaluated<T>(src->typed<T>()); linear_trim_to_output_data<T>( - start, end, eval_data->get_internal_span(), dst->typed<T>()); + start, end, eval_data.get_internal_span(), dst->typed<T>()); }); return true; }, @@ -215,13 +270,13 @@ static PolySpline trim_nurbs_spline(const Spline &spline, linear_trim_to_output_data<float3>( start, end, spline.evaluated_positions(), new_spline.positions()); - GVArray_Typed<float> evaluated_radii = spline.interpolate_to_evaluated(spline.radii()); + VArray<float> evaluated_radii = spline.interpolate_to_evaluated(spline.radii()); linear_trim_to_output_data<float>( - start, end, evaluated_radii->get_internal_span(), new_spline.radii()); + start, end, evaluated_radii.get_internal_span(), new_spline.radii()); - GVArray_Typed<float> evaluated_tilts = spline.interpolate_to_evaluated(spline.tilts()); + VArray<float> evaluated_tilts = spline.interpolate_to_evaluated(spline.tilts()); linear_trim_to_output_data<float>( - start, end, evaluated_tilts->get_internal_span(), new_spline.tilts()); + start, end, evaluated_tilts.get_internal_span(), new_spline.tilts()); return new_spline; } @@ -235,10 +290,11 @@ static void trim_bezier_spline(Spline &spline, const Spline::LookupResult &end_lookup) { BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline); - Span<int> control_offsets = bezier_spline.control_point_offsets(); - const TrimLocation start = lookup_control_point_position(start_lookup, control_offsets); - TrimLocation end = lookup_control_point_position(end_lookup, control_offsets); + const TrimLocation start = lookup_control_point_position(start_lookup, bezier_spline); + TrimLocation end = lookup_control_point_position(end_lookup, bezier_spline); + + const Span<int> control_offsets = bezier_spline.control_point_offsets(); /* The number of control points in the resulting spline. */ const int size = end.right_index - start.left_index + 1; @@ -320,97 +376,245 @@ static void trim_bezier_spline(Spline &spline, bezier_spline.resize(size); } +static void trim_spline(SplinePtr &spline, + const Spline::LookupResult start, + const Spline::LookupResult end) +{ + switch (spline->type()) { + case Spline::Type::Bezier: + trim_bezier_spline(*spline, start, end); + break; + case Spline::Type::Poly: + trim_poly_spline(*spline, start, end); + break; + case Spline::Type::NURBS: + spline = std::make_unique<PolySpline>(trim_nurbs_spline(*spline, start, end)); + break; + } + spline->mark_cache_invalid(); +} + +template<typename T> +static void to_single_point_data(const TrimLocation &trim, MutableSpan<T> data) +{ + data.first() = mix2<T>(trim.factor, data[trim.left_index], data[trim.right_index]); +} +template<typename T> +static void to_single_point_data(const TrimLocation &trim, Span<T> src, MutableSpan<T> dst) +{ + dst.first() = mix2<T>(trim.factor, src[trim.left_index], src[trim.right_index]); +} + +static void to_single_point_bezier(Spline &spline, const Spline::LookupResult &lookup) +{ + BezierSpline &bezier = static_cast<BezierSpline &>(spline); + + const TrimLocation trim = lookup_control_point_position(lookup, bezier); + + const BezierSpline::InsertResult new_point = bezier.calculate_segment_insertion( + trim.left_index, trim.right_index, trim.factor); + bezier.positions().first() = new_point.position; + bezier.handle_types_left().first() = BezierSpline::HandleType::Free; + bezier.handle_types_right().first() = BezierSpline::HandleType::Free; + bezier.handle_positions_left().first() = new_point.left_handle; + bezier.handle_positions_right().first() = new_point.right_handle; + + to_single_point_data<float>(trim, bezier.radii()); + to_single_point_data<float>(trim, bezier.tilts()); + spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> data = spline.attributes.get_for_write(attribute_id); + attribute_math::convert_to_static_type(data->type(), [&](auto dummy) { + using T = decltype(dummy); + to_single_point_data<T>(trim, data->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + spline.resize(1); +} + +static void to_single_point_poly(Spline &spline, const Spline::LookupResult &lookup) +{ + const TrimLocation trim{lookup.evaluated_index, lookup.next_evaluated_index, lookup.factor}; + + to_single_point_data<float3>(trim, spline.positions()); + to_single_point_data<float>(trim, spline.radii()); + to_single_point_data<float>(trim, spline.tilts()); + spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { + std::optional<GMutableSpan> data = spline.attributes.get_for_write(attribute_id); + attribute_math::convert_to_static_type(data->type(), [&](auto dummy) { + using T = decltype(dummy); + to_single_point_data<T>(trim, data->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + spline.resize(1); +} + +static PolySpline to_single_point_nurbs(const Spline &spline, const Spline::LookupResult &lookup) +{ + /* Since this outputs a poly spline, the evaluated indices are the control point indices. */ + const TrimLocation trim{lookup.evaluated_index, lookup.next_evaluated_index, lookup.factor}; + + /* Create poly spline and copy trimmed data to it. */ + PolySpline new_spline; + new_spline.resize(1); + + spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + new_spline.attributes.create(attribute_id, meta_data.data_type); + std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id); + std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id); + attribute_math::convert_to_static_type(src->type(), [&](auto dummy) { + using T = decltype(dummy); + VArray<T> eval_data = spline.interpolate_to_evaluated<T>(src->typed<T>()); + to_single_point_data<T>(trim, eval_data.get_internal_span(), dst->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + to_single_point_data<float3>(trim, spline.evaluated_positions(), new_spline.positions()); + + VArray<float> evaluated_radii = spline.interpolate_to_evaluated(spline.radii()); + to_single_point_data<float>(trim, evaluated_radii.get_internal_span(), new_spline.radii()); + + VArray<float> evaluated_tilts = spline.interpolate_to_evaluated(spline.tilts()); + to_single_point_data<float>(trim, evaluated_tilts.get_internal_span(), new_spline.tilts()); + + return new_spline; +} + +static void to_single_point_spline(SplinePtr &spline, const Spline::LookupResult &lookup) +{ + switch (spline->type()) { + case Spline::Type::Bezier: + to_single_point_bezier(*spline, lookup); + break; + case Spline::Type::Poly: + to_single_point_poly(*spline, lookup); + break; + case Spline::Type::NURBS: + spline = std::make_unique<PolySpline>(to_single_point_nurbs(*spline, lookup)); + break; + } +} + static void geometry_set_curve_trim(GeometrySet &geometry_set, const GeometryNodeCurveSampleMode mode, - const float start, - const float end) + Field<float> &start_field, + Field<float> &end_field) { if (!geometry_set.has_curve()) { return; } + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(start_field); + evaluator.add(end_field); + evaluator.evaluate(); + const blender::VArray<float> &starts = evaluator.get_evaluated<float>(0); + const blender::VArray<float> &ends = evaluator.get_evaluated<float>(1); + CurveEval &curve = *geometry_set.get_curve_for_write(); MutableSpan<SplinePtr> splines = curve.splines(); threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - Spline &spline = *splines[i]; + SplinePtr &spline = splines[i]; - /* Currently this node doesn't support cyclic splines, it could in the future though. */ - if (spline.is_cyclic()) { + /* Currently trimming cyclic splines is not supported. It could be in the future though. */ + if (spline->is_cyclic()) { continue; } - /* Return a spline with one point instead of implicitly - * reversing the spline or switching the parameters. */ - if (end < start) { - spline.resize(1); + if (spline->evaluated_edges_size() == 0) { continue; } - const Spline::LookupResult start_lookup = - (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) ? - spline.lookup_evaluated_length(std::clamp(start, 0.0f, spline.length())) : - spline.lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f)); - const Spline::LookupResult end_lookup = - (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) ? - spline.lookup_evaluated_length(std::clamp(end, 0.0f, spline.length())) : - spline.lookup_evaluated_factor(std::clamp(end, 0.0f, 1.0f)); - - switch (spline.type()) { - case Spline::Type::Bezier: - trim_bezier_spline(spline, start_lookup, end_lookup); - break; - case Spline::Type::Poly: - trim_poly_spline(spline, start_lookup, end_lookup); - break; - case Spline::Type::NURBS: - splines[i] = std::make_unique<PolySpline>( - trim_nurbs_spline(spline, start_lookup, end_lookup)); - break; + const float length = spline->length(); + if (length == 0.0f) { + continue; + } + + const float start = starts[i]; + const float end = ends[i]; + + /* When the start and end samples are reversed, instead of implicitly reversing the spline + * or switching the parameters, create a single point spline with the end sample point. */ + if (end <= start) { + if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + to_single_point_spline(spline, + spline->lookup_evaluated_length(std::clamp(start, 0.0f, length))); + } + else { + to_single_point_spline(spline, + spline->lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f))); + } + continue; + } + + if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + trim_spline(spline, + spline->lookup_evaluated_length(std::clamp(start, 0.0f, length)), + spline->lookup_evaluated_length(std::clamp(end, 0.0f, length))); + } + else { + trim_spline(spline, + spline->lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f)), + spline->lookup_evaluated_factor(std::clamp(end, 0.0f, 1.0f))); } - splines[i]->mark_cache_invalid(); } }); } -static void geo_node_curve_trim_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + const NodeGeometryCurveTrim &storage = node_storage(params.node()); + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { - const float start = params.extract_input<float>("Start"); - const float end = params.extract_input<float>("End"); + Field<float> start_field = params.extract_input<Field<float>>("Start"); + Field<float> end_field = params.extract_input<Field<float>>("End"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start, end); + geometry_set_curve_trim(geometry_set, mode, start_field, end_field); }); } else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { - const float start = params.extract_input<float>("Start_001"); - const float end = params.extract_input<float>("End_001"); + Field<float> start_field = params.extract_input<Field<float>>("Start_001"); + Field<float> end_field = params.extract_input<Field<float>>("End_001"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start, end); + geometry_set_curve_trim(geometry_set, mode, start_field, end_field); }); } params.set_output("Curve", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_curve_trim_cc void register_node_type_geo_curve_trim() { + namespace file_ns = blender::nodes::node_geo_curve_trim_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_TRIM, "Curve Trim", NODE_CLASS_GEOMETRY, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_curve_trim_exec; - ntype.draw_buttons = blender::nodes::geo_node_curve_trim_layout; - ntype.declare = blender::nodes::geo_node_curve_trim_declare; + geo_node_type_base(&ntype, GEO_NODE_TRIM_CURVE, "Trim Curve", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; node_type_storage( &ntype, "NodeGeometryCurveTrim", node_free_standard_storage, node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_trim_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_trim_update); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; 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..8b762abd29b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -0,0 +1,1399 @@ +/* + * 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 "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" + +namespace blender::nodes::node_geo_delete_geometry_cc { + +using blender::bke::CustomDataAttributes; + +template<typename T> +static void copy_data_based_on_mask(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]]; + } +} + +template<typename T> +static void copy_data_based_on_map(Span<T> src, MutableSpan<T> dst, Span<int> index_map) +{ + for (const int i_src : index_map.index_range()) { + const int i_dst = index_map[i_src]; + if (i_dst != -1) { + dst[i_dst] = src[i_src]; + } + } +} + +/** Utility function for making an IndexMask from a boolean selection. The indices vector should + * live at least as long as the returned IndexMask. + */ +static IndexMask index_mask_indices(Span<bool> mask, const bool invert, Vector<int64_t> &indices) +{ + for (const int i : mask.index_range()) { + if (mask[i] != invert) { + indices.append(i); + } + } + return IndexMask(indices); +} + +/** + * Copies the attributes with a domain in `domains` to `result_component`. + */ +static void copy_attributes(const Map<AttributeIDRef, AttributeKind> &attributes, + const GeometryComponent &in_component, + GeometryComponent &result_component, + const Span<AttributeDomain> domains) +{ + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + if (!attribute) { + continue; + } + + /* Only copy if it is on a domain we want. */ + if (!domains.contains(attribute.domain)) { + continue; + } + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray.type()); + + OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( + attribute_id, attribute.domain, data_type); + + if (!result_attribute) { + continue; + } + + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + VArray_Span<T> span{attribute.varray.typed<T>()}; + MutableSpan<T> out_span = result_attribute.as_span<T>(); + out_span.copy_from(span); + }); + result_attribute.save(); + } +} + +/** + * For each attribute with a domain in `domains` it copies the parts of that attribute which lie in + * the mask to `result_component`. + */ +static void copy_attributes_based_on_mask(const Map<AttributeIDRef, AttributeKind> &attributes, + const GeometryComponent &in_component, + GeometryComponent &result_component, + const AttributeDomain domain, + const IndexMask mask) +{ + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + if (!attribute) { + continue; + } + + /* Only copy if it is on a domain we want. */ + if (domain != attribute.domain) { + continue; + } + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray.type()); + + OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( + attribute_id, attribute.domain, data_type); + + if (!result_attribute) { + continue; + } + + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + VArray_Span<T> span{attribute.varray.typed<T>()}; + MutableSpan<T> out_span = result_attribute.as_span<T>(); + copy_data_based_on_mask(span, out_span, mask); + }); + result_attribute.save(); + } +} + +static void copy_attributes_based_on_map(const Map<AttributeIDRef, AttributeKind> &attributes, + const GeometryComponent &in_component, + GeometryComponent &result_component, + const AttributeDomain domain, + const Span<int> index_map) +{ + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + if (!attribute) { + continue; + } + + /* Only copy if it is on a domain we want. */ + if (domain != attribute.domain) { + continue; + } + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray.type()); + + OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( + attribute_id, attribute.domain, data_type); + + if (!result_attribute) { + continue; + } + + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + VArray_Span<T> span{attribute.varray.typed<T>()}; + MutableSpan<T> out_span = result_attribute.as_span<T>(); + copy_data_based_on_map(span, out_span, index_map); + }); + result_attribute.save(); + } +} + +static void copy_face_corner_attributes(const Map<AttributeIDRef, AttributeKind> &attributes, + const GeometryComponent &in_component, + GeometryComponent &out_component, + const int num_selected_loops, + const Span<int> selected_poly_indices, + const Mesh &mesh_in) +{ + Vector<int64_t> indices; + indices.reserve(num_selected_loops); + for (const int src_poly_index : selected_poly_indices) { + const MPoly &src_poly = mesh_in.mpoly[src_poly_index]; + const int src_loop_start = src_poly.loopstart; + const int tot_loop = src_poly.totloop; + for (const int i : IndexRange(tot_loop)) { + indices.append_unchecked(src_loop_start + i); + } + } + copy_attributes_based_on_mask( + attributes, in_component, out_component, ATTR_DOMAIN_CORNER, IndexMask(indices)); +} + +static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map) +{ + BLI_assert(src_mesh.totvert == vertex_map.size()); + for (const int i_src : vertex_map.index_range()) { + const int i_dst = vertex_map[i_src]; + if (i_dst == -1) { + continue; + } + + const MVert &v_src = src_mesh.mvert[i_src]; + MVert &v_dst = dst_mesh.mvert[i_dst]; + + v_dst = v_src; + } +} + +static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> edge_map) +{ + BLI_assert(src_mesh.totedge == edge_map.size()); + for (const int i_src : IndexRange(src_mesh.totedge)) { + const int i_dst = edge_map[i_src]; + if (ELEM(i_dst, -1, -2)) { + continue; + } + + const MEdge &e_src = src_mesh.medge[i_src]; + MEdge &e_dst = dst_mesh.medge[i_dst]; + + e_dst = e_src; + e_dst.v1 = e_src.v1; + e_dst.v2 = e_src.v2; + } +} + +static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map) +{ + BLI_assert(src_mesh.totvert == vertex_map.size()); + BLI_assert(src_mesh.totedge == edge_map.size()); + for (const int i_src : IndexRange(src_mesh.totedge)) { + const int i_dst = edge_map[i_src]; + if (i_dst == -1) { + continue; + } + + const MEdge &e_src = src_mesh.medge[i_src]; + MEdge &e_dst = dst_mesh.medge[i_dst]; + + e_dst = e_src; + e_dst.v1 = vertex_map[e_src.v1]; + e_dst.v2 = vertex_map[e_src.v2]; + } +} + +/* Faces and edges changed but vertices are the same. */ +static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> edge_map, + Span<int> masked_poly_indices, + Span<int> new_loop_starts) +{ + for (const int i_dst : masked_poly_indices.index_range()) { + const int i_src = masked_poly_indices[i_dst]; + + const MPoly &mp_src = src_mesh.mpoly[i_src]; + MPoly &mp_dst = dst_mesh.mpoly[i_dst]; + const int i_ml_src = mp_src.loopstart; + const int i_ml_dst = new_loop_starts[i_dst]; + + const MLoop *ml_src = src_mesh.mloop + i_ml_src; + MLoop *ml_dst = dst_mesh.mloop + i_ml_dst; + + mp_dst = mp_src; + mp_dst.loopstart = i_ml_dst; + for (int i : IndexRange(mp_src.totloop)) { + ml_dst[i].v = ml_src[i].v; + ml_dst[i].e = edge_map[ml_src[i].e]; + } + } +} + +/* Only faces changed. */ +static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> masked_poly_indices, + Span<int> new_loop_starts) +{ + for (const int i_dst : masked_poly_indices.index_range()) { + const int i_src = masked_poly_indices[i_dst]; + + const MPoly &mp_src = src_mesh.mpoly[i_src]; + MPoly &mp_dst = dst_mesh.mpoly[i_dst]; + const int i_ml_src = mp_src.loopstart; + const int i_ml_dst = new_loop_starts[i_dst]; + + const MLoop *ml_src = src_mesh.mloop + i_ml_src; + MLoop *ml_dst = dst_mesh.mloop + i_ml_dst; + + mp_dst = mp_src; + mp_dst.loopstart = i_ml_dst; + for (int i : IndexRange(mp_src.totloop)) { + ml_dst[i].v = ml_src[i].v; + ml_dst[i].e = ml_src[i].e; + } + } +} + +static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map, + Span<int> masked_poly_indices, + Span<int> new_loop_starts) +{ + for (const int i_dst : masked_poly_indices.index_range()) { + const int i_src = masked_poly_indices[i_dst]; + + const MPoly &mp_src = src_mesh.mpoly[i_src]; + MPoly &mp_dst = dst_mesh.mpoly[i_dst]; + const int i_ml_src = mp_src.loopstart; + const int i_ml_dst = new_loop_starts[i_dst]; + + const MLoop *ml_src = src_mesh.mloop + i_ml_src; + MLoop *ml_dst = dst_mesh.mloop + i_ml_dst; + + mp_dst = mp_src; + mp_dst.loopstart = i_ml_dst; + for (int i : IndexRange(mp_src.totloop)) { + ml_dst[i].v = vertex_map[ml_src[i].v]; + ml_dst[i].e = edge_map[ml_src[i].e]; + } + } +} + +static void spline_copy_builtin_attributes(const Spline &spline, + Spline &r_spline, + const IndexMask mask) +{ + copy_data_based_on_mask(spline.positions(), r_spline.positions(), mask); + copy_data_based_on_mask(spline.radii(), r_spline.radii(), mask); + copy_data_based_on_mask(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_based_on_mask(src.handle_positions_left(), dst.handle_positions_left(), mask); + copy_data_based_on_mask(src.handle_positions_right(), dst.handle_positions_right(), mask); + copy_data_based_on_mask(src.handle_types_left(), dst.handle_types_left(), mask); + copy_data_based_on_mask(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_based_on_mask(src.weights(), dst.weights(), mask); + break; + } + } +} + +static void copy_dynamic_attributes(const CustomDataAttributes &src, + CustomDataAttributes &dst, + const IndexMask mask) +{ + src.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src_attribute = src.get_for_read(attribute_id); + BLI_assert(src_attribute); + + if (!dst.create(attribute_id, 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(attribute_id); + BLI_assert(new_attribute); + + attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) { + using T = decltype(dummy); + copy_data_based_on_mask(src_attribute->typed<T>(), new_attribute->typed<T>(), mask); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +/** + * Deletes points in the spline. Those not in the mask are deleted. The spline is not split into + * multiple newer splines. + */ +static SplinePtr spline_delete(const Spline &spline, const IndexMask mask) +{ + SplinePtr new_spline = spline.copy_only_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_separate(const CurveEval &input_curve, + const Span<bool> selection, + const AttributeDomain selection_domain, + 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 (selection_domain == ATTR_DOMAIN_CURVE) { + /* Operates on each of the splines as a whole, i.e. not on the points in the splines + * themselves. */ + for (const int i : selection.index_range()) { + if (selection[i] != invert) { + output_curve->add_spline(input_splines[i]->copy()); + copied_splines.append(i); + } + } + } + else { + /* Operates on the points in the splines themselves. */ + + /* Reuse index vector for each spline. */ + Vector<int64_t> indices_to_copy; + + int selection_index = 0; + for (const int i : input_splines.index_range()) { + const Spline &spline = *input_splines[i]; + + indices_to_copy.clear(); + for (const int i_point : IndexRange(spline.size())) { + if (selection[selection_index] != invert) { + /* Append i_point instead of selection_index because we need indices local to the spline + * for copying. */ + indices_to_copy.append(i_point); + } + selection_index++; + } + + /* 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 separate_curve_selection(GeometrySet &geometry_set, + const Field<bool> &selection_field, + const AttributeDomain selection_domain, + const bool invert) +{ + const CurveComponent &src_component = *geometry_set.get_component_for_read<CurveComponent>(); + GeometryComponentFieldContext field_context{src_component, selection_domain}; + + fn::FieldEvaluator selection_evaluator{field_context, + src_component.attribute_domain_size(selection_domain)}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray_Span<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + std::unique_ptr<CurveEval> r_curve = curve_separate( + *src_component.get_for_read(), selection, selection_domain, invert); + if (r_curve) { + geometry_set.replace_curve(r_curve.release()); + } + else { + geometry_set.replace_curve(nullptr); + } +} + +static void separate_point_cloud_selection(GeometrySet &geometry_set, + const Field<bool> &selection_field, + const bool invert) +{ + const PointCloudComponent &src_points = + *geometry_set.get_component_for_read<PointCloudComponent>(); + GeometryComponentFieldContext field_context{src_points, ATTR_DOMAIN_POINT}; + + fn::FieldEvaluator selection_evaluator{field_context, + src_points.attribute_domain_size(ATTR_DOMAIN_POINT)}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray_Span<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + Vector<int64_t> indices; + const IndexMask mask = index_mask_indices(selection, invert, indices); + const int total = mask.size(); + PointCloud *pointcloud = BKE_pointcloud_new_nomain(total); + + if (total == 0) { + geometry_set.replace_pointcloud(pointcloud); + return; + } + + PointCloudComponent dst_points; + dst_points.replace(pointcloud, GeometryOwnershipType::Editable); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + + copy_attributes_based_on_mask(attributes, src_points, dst_points, ATTR_DOMAIN_POINT, mask); + geometry_set.replace_pointcloud(pointcloud); +} + +static void separate_instance_selection(GeometrySet &geometry_set, + const Field<bool> &selection_field, + const bool invert) +{ + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + GeometryComponentFieldContext field_context{instances, ATTR_DOMAIN_INSTANCE}; + + const int domain_size = instances.attribute_domain_size(ATTR_DOMAIN_INSTANCE); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(selection_field); + evaluator.evaluate(); + const VArray_Span<bool> &selection = evaluator.get_evaluated<bool>(0); + + Vector<int64_t> indices; + const IndexMask mask = index_mask_indices(selection, invert, indices); + + if (mask.is_empty()) { + geometry_set.remove<InstancesComponent>(); + return; + } + + instances.remove_instances(mask); +} + +static void compute_selected_vertices_from_vertex_selection(const Span<bool> vertex_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + int *r_num_selected_vertices) +{ + BLI_assert(vertex_selection.size() == r_vertex_map.size()); + + int 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 Span<bool> vertex_selection, + const bool invert, + MutableSpan<int> r_edge_map, + int *r_num_selected_edges) +{ + BLI_assert(mesh.totedge == r_edge_map.size()); + + int 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 Span<bool> vertex_selection, + const bool invert, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + BLI_assert(mesh.totvert == vertex_selection.size()); + + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + int 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 Span<bool> edge_selection, + const bool invert, + MutableSpan<int> r_vertex_map, + MutableSpan<int> r_edge_map, + int *r_num_selected_vertices, + int *r_num_selected_edges) +{ + BLI_assert(mesh.totedge == edge_selection.size()); + + int num_selected_edges = 0; + int 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 edge if it is in `edge_selection`. + */ +static void compute_selected_edges_from_edge_selection(const Mesh &mesh, + const Span<bool> edge_selection, + const bool invert, + MutableSpan<int> r_edge_map, + int *r_num_selected_edges) +{ + BLI_assert(mesh.totedge == edge_selection.size()); + + int num_selected_edges = 0; + for (const int i : IndexRange(mesh.totedge)) { + if (edge_selection[i] != invert) { + r_edge_map[i] = num_selected_edges; + num_selected_edges++; + } + else { + r_edge_map[i] = -1; + } + } + + *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 Span<bool> edge_selection, + const bool invert, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + int 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 edge and polygon if all its vertices are in `vertex_selection`. + */ +static void compute_selected_mesh_data_from_vertex_selection_edge_face( + const Mesh &mesh, + const Span<bool> vertex_selection, + const bool invert, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + + 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 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 Span<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, + int *r_num_selected_vertices, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *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`. The polygons are kept if all edges are in + * the selection. + */ +static void compute_selected_mesh_data_from_edge_selection_edge_face( + const Mesh &mesh, + const Span<bool> edge_selection, + const bool invert, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + compute_selected_edges_from_edge_selection( + mesh, edge_selection, invert, r_edge_map, 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 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 Span<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, + int *r_num_selected_vertices, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *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`. + */ +static void compute_selected_polygons_from_poly_selection(const Mesh &mesh, + const Span<bool> poly_selection, + const bool invert, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + BLI_assert(mesh.totpoly == poly_selection.size()); + + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + int num_selected_loops = 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; + } + } + *r_num_selected_polys = r_selected_poly_indices.size(); + *r_num_selected_loops = num_selected_loops; +} +/** + * Checks for every polygon if it is in `poly_selection`. If it is, the edges + * belonging to that polygon are kept as well. + */ +static void compute_selected_mesh_data_from_poly_selection_edge_face( + const Mesh &mesh, + const Span<bool> poly_selection, + const bool invert, + MutableSpan<int> r_edge_map, + Vector<int> &r_selected_poly_indices, + Vector<int> &r_loop_starts, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *r_num_selected_loops) +{ + BLI_assert(mesh.totpoly == poly_selection.size()); + BLI_assert(mesh.totedge == r_edge_map.size()); + r_edge_map.fill(-1); + + r_selected_poly_indices.reserve(mesh.totpoly); + r_loop_starts.reserve(mesh.totloop); + + int num_selected_loops = 0; + int 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_edge_map[loop.e] == -1) { + r_edge_map[loop.e] = num_selected_edges; + num_selected_edges++; + } + } + } + } + *r_num_selected_edges = num_selected_edges; + *r_num_selected_polys = r_selected_poly_indices.size(); + *r_num_selected_loops = 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 Span<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, + int *r_num_selected_vertices, + int *r_num_selected_edges, + int *r_num_selected_polys, + int *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); + + int num_selected_loops = 0; + int num_selected_vertices = 0; + int 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; +} + +/** + * Keep the parts of the mesh that are in the selection. + */ +static void do_mesh_separation(GeometrySet &geometry_set, + const MeshComponent &in_component, + const VArray_Span<bool> &selection, + const bool invert, + const AttributeDomain domain, + const GeometryNodeDeleteGeometryMode mode) +{ + /* Needed in all cases. */ + Vector<int> selected_poly_indices; + Vector<int> new_loop_starts; + int num_selected_polys = 0; + int num_selected_loops = 0; + + const Mesh &mesh_in = *in_component.get_for_read(); + Mesh *mesh_out; + MeshComponent out_component; + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes); + + switch (mode) { + case GEO_NODE_DELETE_GEOMETRY_MODE_ALL: { + Array<int> vertex_map(mesh_in.totvert); + int num_selected_vertices = 0; + + Array<int> edge_map(mesh_in.totedge); + int num_selected_edges = 0; + + /* Fill all the maps based on the selection. */ + switch (domain) { + case ATTR_DOMAIN_POINT: + compute_selected_mesh_data_from_vertex_selection(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); + break; + case ATTR_DOMAIN_EDGE: + compute_selected_mesh_data_from_edge_selection(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); + break; + case ATTR_DOMAIN_FACE: + compute_selected_mesh_data_from_poly_selection(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); + break; + default: + BLI_assert_unreachable(); + break; + } + mesh_out = BKE_mesh_new_nomain_from_template(&mesh_in, + num_selected_vertices, + num_selected_edges, + 0, + num_selected_loops, + num_selected_polys); + out_component.replace(mesh_out, GeometryOwnershipType::Editable); + + /* Copy the selected parts of the mesh over to the new mesh. */ + copy_masked_vertices_to_new_mesh(mesh_in, *mesh_out, vertex_map); + copy_masked_edges_to_new_mesh(mesh_in, *mesh_out, vertex_map, edge_map); + copy_masked_polys_to_new_mesh( + mesh_in, *mesh_out, vertex_map, edge_map, selected_poly_indices, new_loop_starts); + + /* Copy attributes. */ + copy_attributes_based_on_map( + attributes, in_component, out_component, ATTR_DOMAIN_POINT, vertex_map); + copy_attributes_based_on_map( + attributes, in_component, out_component, ATTR_DOMAIN_EDGE, edge_map); + copy_attributes_based_on_mask(attributes, + in_component, + out_component, + ATTR_DOMAIN_FACE, + IndexMask(Vector<int64_t>(selected_poly_indices.as_span()))); + copy_face_corner_attributes(attributes, + in_component, + out_component, + num_selected_loops, + selected_poly_indices, + mesh_in); + break; + } + case GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE: { + Array<int> edge_map(mesh_in.totedge); + int num_selected_edges = 0; + + /* Fill all the maps based on the selection. */ + switch (domain) { + case ATTR_DOMAIN_POINT: + compute_selected_mesh_data_from_vertex_selection_edge_face(mesh_in, + selection, + invert, + edge_map, + selected_poly_indices, + new_loop_starts, + &num_selected_edges, + &num_selected_polys, + &num_selected_loops); + break; + case ATTR_DOMAIN_EDGE: + compute_selected_mesh_data_from_edge_selection_edge_face(mesh_in, + selection, + invert, + edge_map, + selected_poly_indices, + new_loop_starts, + &num_selected_edges, + &num_selected_polys, + &num_selected_loops); + break; + case ATTR_DOMAIN_FACE: + compute_selected_mesh_data_from_poly_selection_edge_face(mesh_in, + selection, + invert, + edge_map, + selected_poly_indices, + new_loop_starts, + &num_selected_edges, + &num_selected_polys, + &num_selected_loops); + break; + default: + BLI_assert_unreachable(); + break; + } + mesh_out = BKE_mesh_new_nomain_from_template(&mesh_in, + mesh_in.totvert, + num_selected_edges, + 0, + num_selected_loops, + num_selected_polys); + out_component.replace(mesh_out, GeometryOwnershipType::Editable); + + /* Copy the selected parts of the mesh over to the new mesh. */ + memcpy(mesh_out->mvert, mesh_in.mvert, mesh_in.totvert * sizeof(MVert)); + copy_masked_edges_to_new_mesh(mesh_in, *mesh_out, edge_map); + copy_masked_polys_to_new_mesh( + mesh_in, *mesh_out, edge_map, selected_poly_indices, new_loop_starts); + + /* Copy attributes. */ + copy_attributes(attributes, in_component, out_component, {ATTR_DOMAIN_POINT}); + copy_attributes_based_on_map( + attributes, in_component, out_component, ATTR_DOMAIN_EDGE, edge_map); + copy_attributes_based_on_mask(attributes, + in_component, + out_component, + ATTR_DOMAIN_FACE, + IndexMask(Vector<int64_t>(selected_poly_indices.as_span()))); + copy_face_corner_attributes(attributes, + in_component, + out_component, + num_selected_loops, + selected_poly_indices, + mesh_in); + break; + } + case GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE: { + /* Fill all the maps based on the selection. */ + switch (domain) { + case ATTR_DOMAIN_POINT: + compute_selected_polygons_from_vertex_selection(mesh_in, + selection, + invert, + selected_poly_indices, + new_loop_starts, + &num_selected_polys, + &num_selected_loops); + break; + case ATTR_DOMAIN_EDGE: + compute_selected_polygons_from_edge_selection(mesh_in, + selection, + invert, + selected_poly_indices, + new_loop_starts, + &num_selected_polys, + &num_selected_loops); + break; + case ATTR_DOMAIN_FACE: + compute_selected_polygons_from_poly_selection(mesh_in, + selection, + invert, + selected_poly_indices, + new_loop_starts, + &num_selected_polys, + &num_selected_loops); + break; + default: + BLI_assert_unreachable(); + break; + } + mesh_out = BKE_mesh_new_nomain_from_template( + &mesh_in, mesh_in.totvert, mesh_in.totedge, 0, num_selected_loops, num_selected_polys); + out_component.replace(mesh_out, GeometryOwnershipType::Editable); + + /* Copy the selected parts of the mesh over to the new mesh. */ + memcpy(mesh_out->mvert, mesh_in.mvert, mesh_in.totvert * sizeof(MVert)); + memcpy(mesh_out->medge, mesh_in.medge, mesh_in.totedge * sizeof(MEdge)); + copy_masked_polys_to_new_mesh(mesh_in, *mesh_out, selected_poly_indices, new_loop_starts); + + /* Copy attributes. */ + copy_attributes( + attributes, in_component, out_component, {ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE}); + copy_attributes_based_on_mask(attributes, + in_component, + out_component, + ATTR_DOMAIN_FACE, + IndexMask(Vector<int64_t>(selected_poly_indices.as_span()))); + copy_face_corner_attributes(attributes, + in_component, + out_component, + num_selected_loops, + selected_poly_indices, + mesh_in); + break; + } + } + + BKE_mesh_calc_edges_loose(mesh_out); + /* Tag to recalculate normals later. */ + BKE_mesh_normals_tag_dirty(mesh_out); + geometry_set.replace_mesh(mesh_out); +} + +static void separate_mesh_selection(GeometrySet &geometry_set, + const Field<bool> &selection_field, + const AttributeDomain selection_domain, + const GeometryNodeDeleteGeometryMode mode, + const bool invert) +{ + const MeshComponent &src_component = *geometry_set.get_component_for_read<MeshComponent>(); + GeometryComponentFieldContext field_context{src_component, selection_domain}; + + fn::FieldEvaluator selection_evaluator{field_context, + src_component.attribute_domain_size(selection_domain)}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray_Span<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + /* 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; + } + + do_mesh_separation(geometry_set, src_component, selection, invert, selection_domain, mode); +} + +} // namespace blender::nodes::node_geo_delete_geometry_cc + +namespace blender::nodes { + +void separate_geometry(GeometrySet &geometry_set, + const AttributeDomain domain, + const GeometryNodeDeleteGeometryMode mode, + const Field<bool> &selection_field, + const bool invert, + bool &r_is_error) +{ + namespace file_ns = blender::nodes::node_geo_delete_geometry_cc; + + bool some_valid_domain = false; + if (geometry_set.has_pointcloud()) { + if (domain == ATTR_DOMAIN_POINT) { + file_ns::separate_point_cloud_selection(geometry_set, selection_field, invert); + some_valid_domain = true; + } + } + if (geometry_set.has_mesh()) { + if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER)) { + file_ns::separate_mesh_selection(geometry_set, selection_field, domain, mode, invert); + some_valid_domain = true; + } + } + if (geometry_set.has_curve()) { + if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + file_ns::separate_curve_selection(geometry_set, selection_field, domain, invert); + some_valid_domain = true; + } + } + if (geometry_set.has_instances()) { + if (domain == ATTR_DOMAIN_INSTANCE) { + file_ns::separate_instance_selection(geometry_set, selection_field, invert); + some_valid_domain = true; + } + } + r_is_error = !some_valid_domain && geometry_set.has_realized_data(); +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_delete_geometry_cc { + +NODE_STORAGE_FUNCS(NodeGeometryDeleteGeometry) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Selection")) + .default_value(true) + .hide_value() + .supports_field() + .description(N_("The parts of the geometry to be deleted")); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + const bNode *node = static_cast<bNode *>(ptr->data); + const NodeGeometryDeleteGeometry &storage = node_storage(*node); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + /* Only show the mode when it is relevant. */ + if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE)) { + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); + } +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryDeleteGeometry *data = MEM_cnew<NodeGeometryDeleteGeometry>(__func__); + data->domain = ATTR_DOMAIN_POINT; + data->mode = GEO_NODE_DELETE_GEOMETRY_MODE_ALL; + + node->storage = data; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + const NodeGeometryDeleteGeometry &storage = node_storage(params.node()); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + const GeometryNodeDeleteGeometryMode mode = (GeometryNodeDeleteGeometryMode)storage.mode; + + if (domain == ATTR_DOMAIN_INSTANCE) { + bool is_error; + separate_geometry(geometry_set, domain, mode, selection_field, true, is_error); + } + else { + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + bool is_error; + /* Invert here because we want to keep the things not in the selection. */ + separate_geometry(geometry_set, domain, mode, selection_field, true, is_error); + }); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_delete_geometry_cc + +void register_node_type_geo_delete_geometry() +{ + namespace file_ns = blender::nodes::node_geo_delete_geometry_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY); + + node_type_storage(&ntype, + "NodeGeometryDeleteGeometry", + node_free_standard_storage, + node_copy_standard_storage); + + node_type_init(&ntype, file_ns::node_init); + + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index 1a4c5d84dbf..d17657bfa3a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -36,51 +36,48 @@ #include "node_geometry_util.hh" -using blender::bke::GeometryInstanceGroup; +namespace blender::nodes::node_geo_distribute_points_on_faces_cc { -namespace blender::nodes { - -static void geo_node_point_distribute_points_on_faces_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Distance Min").min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Density Max").default_value(10.0f).min(0.0f); - b.add_input<decl::Float>("Density").default_value(10.0f).supports_field(); - b.add_input<decl::Float>("Density Factor") + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Distance Min")).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Density Max")).default_value(10.0f).min(0.0f); + b.add_input<decl::Float>(N_("Density")).default_value(10.0f).min(0.0f).supports_field(); + b.add_input<decl::Float>(N_("Density Factor")) .default_value(1.0f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) .supports_field(); - b.add_input<decl::Int>("Seed"); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); + b.add_input<decl::Int>(N_("Seed")); - b.add_output<decl::Geometry>("Points"); - b.add_output<decl::Vector>("Normal").field_source(); - b.add_output<decl::Vector>("Rotation").subtype(PROP_EULER).field_source(); - b.add_output<decl::Int>("Stable ID").field_source(); + b.add_output<decl::Geometry>(N_("Points")); + b.add_output<decl::Vector>(N_("Normal")).field_source(); + b.add_output<decl::Vector>(N_("Rotation")).subtype(PROP_EULER).field_source(); } -static void geo_node_point_distribute_points_on_faces_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); } -static void node_point_distribute_points_on_faces_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_point_distribute_points_on_faces_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock_distance_min = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_distance_min = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *sock_density_max = (bNodeSocket *)sock_distance_min->next; bNodeSocket *sock_density = sock_density_max->next; bNodeSocket *sock_density_factor = sock_density->next; - nodeSetSocketAvailability(sock_distance_min, - node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); - nodeSetSocketAvailability(sock_density_max, + nodeSetSocketAvailability(ntree, + sock_distance_min, node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); - nodeSetSocketAvailability(sock_density, - node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); - nodeSetSocketAvailability(sock_density_factor, + nodeSetSocketAvailability( + ntree, sock_density_max, node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability( + ntree, sock_density, node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + nodeSetSocketAvailability(ntree, + sock_density_factor, node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); } @@ -255,18 +252,26 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, { switch (source_domain) { case ATTR_DOMAIN_POINT: { - bke::mesh_surface_sample::sample_point_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); + bke::mesh_surface_sample::sample_point_attribute(mesh, + looptri_indices, + bary_coords, + source_data, + IndexMask(output_data.size()), + output_data); break; } case ATTR_DOMAIN_CORNER: { - bke::mesh_surface_sample::sample_corner_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); + bke::mesh_surface_sample::sample_corner_attribute(mesh, + looptri_indices, + bary_coords, + source_data, + IndexMask(output_data.size()), + output_data); break; } case ATTR_DOMAIN_FACE: { bke::mesh_surface_sample::sample_face_attribute( - mesh, looptri_indices, source_data, output_data); + mesh, looptri_indices, source_data, IndexMask(output_data.size()), output_data); break; } default: { @@ -288,6 +293,12 @@ BLI_NOINLINE static void propagate_existing_attributes( for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const CustomDataType output_data_type = entry.value.data_type; + + ReadAttributeLookup source_attribute = mesh_component.attribute_try_get_for_read(attribute_id); + if (!source_attribute) { + continue; + } + /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ OutputAttribute attribute_out = point_component.attribute_try_get_for_output_only( attribute_id, ATTR_DOMAIN_POINT, output_data_type); @@ -296,23 +307,12 @@ BLI_NOINLINE static void propagate_existing_attributes( } GMutableSpan out_span = attribute_out.as_span(); - - std::optional<AttributeMetaData> attribute_info = point_component.attribute_get_meta_data( - attribute_id); - if (!attribute_info) { - continue; - } - - const AttributeDomain source_domain = attribute_info->domain; - GVArrayPtr source_attribute = mesh_component.attribute_get_for_read( - attribute_id, source_domain, output_data_type, nullptr); - if (!source_attribute) { - continue; - } - - interpolate_attribute( - mesh, bary_coords, looptri_indices, source_domain, *source_attribute, out_span); - + interpolate_attribute(mesh, + bary_coords, + looptri_indices, + source_attribute.domain, + source_attribute.varray, + out_span); attribute_out.save(); } } @@ -321,7 +321,6 @@ namespace { struct AttributeOutputs { StrongAnonymousAttributeID normal_id; StrongAnonymousAttributeID rotation_id; - StrongAnonymousAttributeID stable_id_id; }; } // namespace @@ -331,28 +330,25 @@ BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_com const Span<int> looptri_indices, const AttributeOutputs &attribute_outputs) { - std::optional<OutputAttribute_Typed<int>> id_attribute; - std::optional<OutputAttribute_Typed<float3>> normal_attribute; - std::optional<OutputAttribute_Typed<float3>> rotation_attribute; + OutputAttribute_Typed<int> id_attribute = point_component.attribute_try_get_for_output_only<int>( + "id", ATTR_DOMAIN_POINT); + MutableSpan<int> ids = id_attribute.as_span(); + + OutputAttribute_Typed<float3> normal_attribute; + OutputAttribute_Typed<float3> rotation_attribute; - MutableSpan<int> ids; MutableSpan<float3> normals; MutableSpan<float3> rotations; - if (attribute_outputs.stable_id_id) { - id_attribute.emplace(point_component.attribute_try_get_for_output_only<int>( - attribute_outputs.stable_id_id.get(), ATTR_DOMAIN_POINT)); - ids = id_attribute->as_span(); - } if (attribute_outputs.normal_id) { - normal_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( - attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT)); - normals = normal_attribute->as_span(); + normal_attribute = point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT); + normals = normal_attribute.as_span(); } if (attribute_outputs.rotation_id) { - rotation_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( - attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT)); - rotations = rotation_attribute->as_span(); + rotation_attribute = point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT); + rotations = rotation_attribute.as_span(); } const Mesh &mesh = *mesh_component.get_for_read(); @@ -371,9 +367,8 @@ BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_com const float3 v1_pos = float3(mesh.mvert[v1_index].co); const float3 v2_pos = float3(mesh.mvert[v2_index].co); - if (!ids.is_empty()) { - ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); - } + ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); + float3 normal; if (!normals.is_empty() || !rotations.is_empty()) { normal_tri_v3(normal, v0_pos, v1_pos, v2_pos); @@ -386,14 +381,13 @@ BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_com } } - if (id_attribute) { - id_attribute->save(); - } + id_attribute.save(); + if (normal_attribute) { - normal_attribute->save(); + normal_attribute.save(); } if (rotation_attribute) { - rotation_attribute->save(); + rotation_attribute.save(); } } @@ -405,16 +399,12 @@ static Array<float> calc_full_density_factors_with_selection(const MeshComponent GeometryComponentFieldContext field_context{component, attribute_domain}; const int domain_size = component.attribute_domain_size(attribute_domain); - fn::FieldEvaluator selection_evaluator{field_context, domain_size}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection_mask = selection_evaluator.get_evaluated_as_mask(0); - Array<float> densities(domain_size, 0.0f); - fn::FieldEvaluator density_evaluator{field_context, &selection_mask}; - density_evaluator.add_with_destination(density_field, densities.as_mutable_span()); - density_evaluator.evaluate(); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(density_field, densities.as_mutable_span()); + evaluator.evaluate(); return densities; } @@ -504,6 +494,10 @@ static void point_distribution_calculate(GeometrySet &geometry_set, } } + if (positions.is_empty()) { + return; + } + PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); memcpy(pointcloud->co, positions.data(), sizeof(float3) * positions.size()); uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); @@ -526,9 +520,9 @@ static void point_distribution_calculate(GeometrySet &geometry_set, mesh_component, point_component, bary_coords, looptri_indices, attribute_outputs); } -static void geo_node_point_distribute_points_on_faces_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); const GeometryNodeDistributePointsOnFacesMode method = static_cast<GeometryNodeDistributePointsOnFacesMode>(params.node().custom1); @@ -538,13 +532,10 @@ static void geo_node_point_distribute_points_on_faces_exec(GeoNodeExecParams par AttributeOutputs attribute_outputs; if (params.output_is_required("Normal")) { - attribute_outputs.normal_id = StrongAnonymousAttributeID("normal"); + attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal"); } if (params.output_is_required("Rotation")) { - attribute_outputs.rotation_id = StrongAnonymousAttributeID("rotation"); - } - if (params.output_is_required("Stable ID")) { - attribute_outputs.stable_id_id = StrongAnonymousAttributeID("stable id"); + attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation"); } geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { @@ -560,35 +551,33 @@ static void geo_node_point_distribute_points_on_faces_exec(GeoNodeExecParams par if (attribute_outputs.normal_id) { params.set_output( "Normal", - AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id))); + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id), + params.attribute_producer_name())); } if (attribute_outputs.rotation_id) { params.set_output( "Rotation", - AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id))); - } - if (attribute_outputs.stable_id_id) { - params.set_output( - "Stable ID", - AnonymousAttributeFieldInput::Create<int>(std::move(attribute_outputs.stable_id_id))); + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id), + params.attribute_producer_name())); } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_distribute_points_on_faces_cc void register_node_type_geo_distribute_points_on_faces() { + namespace file_ns = blender::nodes::node_geo_distribute_points_on_faces_cc; + static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, "Distribute Points on Faces", - NODE_CLASS_GEOMETRY, - 0); - node_type_update(&ntype, blender::nodes::node_point_distribute_points_on_faces_update); + NODE_CLASS_GEOMETRY); + node_type_update(&ntype, file_ns::node_point_distribute_points_on_faces_update); node_type_size(&ntype, 170, 100, 320); - ntype.declare = blender::nodes::geo_node_point_distribute_points_on_faces_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_points_on_faces_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_distribute_points_on_faces_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc new file mode 100644 index 00000000000..f6be6c1e7fb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -0,0 +1,931 @@ +/* + * 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_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_dual_mesh_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Mesh").supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>("Keep Boundaries") + .default_value(false) + .description( + "Keep non-manifold boundaries of the input mesh in place by avoiding the dual " + "transformation there"); + b.add_output<decl::Geometry>("Dual Mesh"); +} + +enum class EdgeType : int8_t { + Loose = 0, /* No polygons connected to it. */ + Boundary = 1, /* An edge connected to exactly one polygon. */ + Normal = 2, /* A normal edge (connected to two polygons). */ + NonManifold = 3, /* An edge connected to more than two polygons. */ +}; + +static EdgeType get_edge_type_with_added_neighbor(EdgeType old_type) +{ + switch (old_type) { + case EdgeType::Loose: + return EdgeType::Boundary; + case EdgeType::Boundary: + return EdgeType::Normal; + case EdgeType::Normal: + case EdgeType::NonManifold: + return EdgeType::NonManifold; + } + BLI_assert_unreachable(); + return EdgeType::Loose; +} + +enum class VertexType : int8_t { + Loose = 0, /* Either no edges connected or only loose edges connected. */ + Normal = 1, /* A normal vertex. */ + Boundary = 2, /* A vertex on a boundary edge. */ + NonManifold = 3, /* A vertex on a non-manifold edge. */ +}; + +static VertexType get_vertex_type_with_added_neighbor(VertexType old_type) +{ + switch (old_type) { + case VertexType::Loose: + return VertexType::Normal; + case VertexType::Normal: + return VertexType::Boundary; + case VertexType::Boundary: + case VertexType::NonManifold: + return VertexType::NonManifold; + } + BLI_assert_unreachable(); + return VertexType::Loose; +} + +/* Copy only where vertex_types is 'normal'. If keep boundaries is selected, also copy from + * boundary vertices. */ +template<typename T> +static void copy_data_based_on_vertex_types(Span<T> data, + MutableSpan<T> r_data, + const Span<VertexType> vertex_types, + const bool keep_boundaries) +{ + if (keep_boundaries) { + int out_i = 0; + for (const int i : data.index_range()) { + if (ELEM(vertex_types[i], VertexType::Normal, VertexType::Boundary)) { + r_data[out_i] = data[i]; + out_i++; + } + } + } + else { + int out_i = 0; + for (const int i : data.index_range()) { + if (vertex_types[i] == VertexType::Normal) { + r_data[out_i] = data[i]; + out_i++; + } + } + } +} + +template<typename T> +static void copy_data_based_on_pairs(Span<T> data, + MutableSpan<T> r_data, + const Span<std::pair<int, int>> new_to_old_map) +{ + for (const std::pair<int, int> &pair : new_to_old_map) { + r_data[pair.first] = data[pair.second]; + } +} + +/* Copy using the map. */ +template<typename T> +static void copy_data_based_on_new_to_old_map(Span<T> data, + MutableSpan<T> r_data, + const Span<int> new_to_old_map) +{ + for (const int i : r_data.index_range()) { + const int old_i = new_to_old_map[i]; + r_data[i] = data[old_i]; + } +} + +/** + * Transfers the attributes from the original mesh to the new mesh using the following logic: + * - If the attribute was on the face domain it is now on the point domain, and this is true + * for all faces, so we can just copy these. + * - If the attribute was on the vertex domain there are three cases: + * - It was a 'bad' vertex so it is not in the dual mesh, and we can just ignore it + * - It was a normal vertex so it has a corresponding face in the dual mesh to which we can + * transfer. + * - It was a boundary vertex so it has a corresponding face, if keep_boundaries is true. + * Otherwise we can just ignore it. + * - If the attribute was on the edge domain we lookup for the new edges which edge it originated + * from using `new_to_old_edges_map`. We have to do it in this reverse order, because there can + * be more edges in the new mesh if keep boundaries is on. + * - We do the same thing for face corners as we do for edges. + * + * Some of the vertices (on the boundary) in the dual mesh don't come from faces, but from edges or + * vertices. For these the `boundary_vertex_to_relevant_face_map` is used, which maps them to the + * closest face. + */ +static void transfer_attributes( + const Map<AttributeIDRef, AttributeKind> &attributes, + const Span<VertexType> vertex_types, + const bool keep_boundaries, + const Span<int> new_to_old_edges_map, + const Span<int> new_to_old_face_corners_map, + const Span<std::pair<int, int>> boundary_vertex_to_relevant_face_map, + const GeometryComponent &src_component, + GeometryComponent &dst_component) +{ + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + if (!src_attribute) { + continue; + } + + AttributeDomain out_domain; + if (src_attribute.domain == ATTR_DOMAIN_FACE) { + out_domain = ATTR_DOMAIN_POINT; + } + else if (src_attribute.domain == ATTR_DOMAIN_POINT) { + out_domain = ATTR_DOMAIN_FACE; + } + else { + /* Edges and Face Corners. */ + out_domain = src_attribute.domain; + } + const CustomDataType data_type = bke::cpp_type_to_custom_data_type( + src_attribute.varray.type()); + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + attribute_id, out_domain, data_type); + + if (!dst_attribute) { + continue; + } + + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + VArray_Span<T> span{src_attribute.varray.typed<T>()}; + MutableSpan<T> dst_span = dst_attribute.as_span<T>(); + if (src_attribute.domain == ATTR_DOMAIN_FACE) { + dst_span.take_front(span.size()).copy_from(span); + if (keep_boundaries) { + copy_data_based_on_pairs(span, dst_span, boundary_vertex_to_relevant_face_map); + } + } + else if (src_attribute.domain == ATTR_DOMAIN_POINT) { + copy_data_based_on_vertex_types(span, dst_span, vertex_types, keep_boundaries); + } + else if (src_attribute.domain == ATTR_DOMAIN_EDGE) { + copy_data_based_on_new_to_old_map(span, dst_span, new_to_old_edges_map); + } + else { + copy_data_based_on_new_to_old_map(span, dst_span, new_to_old_face_corners_map); + } + }); + dst_attribute.save(); + } +} + +/** + * Calculates the boundaries of the mesh. Boundary polygons are not computed since we don't need + * them later on. We use the following definitions: + * - An edge is on a boundary if it is connected to only one polygon. + * - A vertex is on a boundary if it is on an edge on a boundary. + */ +static void calc_boundaries(const Mesh &mesh, + MutableSpan<VertexType> r_vertex_types, + MutableSpan<EdgeType> r_edge_types) +{ + BLI_assert(r_vertex_types.size() == mesh.totvert); + BLI_assert(r_edge_types.size() == mesh.totedge); + r_vertex_types.fill(VertexType::Loose); + r_edge_types.fill(EdgeType::Loose); + + /* Add up the number of polys connected to each edge. */ + for (const int i : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[i]; + for (const MLoop &loop : Span<MLoop>(&mesh.mloop[poly.loopstart], poly.totloop)) { + r_edge_types[loop.e] = get_edge_type_with_added_neighbor(r_edge_types[loop.e]); + } + } + + /* Update vertices. */ + for (const int i : IndexRange(mesh.totedge)) { + const EdgeType edge_type = r_edge_types[i]; + if (edge_type == EdgeType::Loose) { + continue; + } + const MEdge &edge = mesh.medge[i]; + if (edge_type == EdgeType::Boundary) { + r_vertex_types[edge.v1] = get_vertex_type_with_added_neighbor(r_vertex_types[edge.v1]); + r_vertex_types[edge.v2] = get_vertex_type_with_added_neighbor(r_vertex_types[edge.v2]); + } + else if (edge_type >= EdgeType::NonManifold) { + r_vertex_types[edge.v1] = VertexType::NonManifold; + r_vertex_types[edge.v2] = VertexType::NonManifold; + } + } + + /* Normal verts are on a normal edge, and not on boundary edges or non-manifold edges. */ + for (const int i : IndexRange(mesh.totedge)) { + const EdgeType edge_type = r_edge_types[i]; + if (edge_type == EdgeType::Normal) { + const MEdge &edge = mesh.medge[i]; + if (r_vertex_types[edge.v1] == VertexType::Loose) { + r_vertex_types[edge.v1] = VertexType::Normal; + } + if (r_vertex_types[edge.v2] == VertexType::Loose) { + r_vertex_types[edge.v2] = VertexType::Normal; + } + } + } +} + +/** + * Stores the indices of the polygons connected to each vertex. + */ +static void create_vertex_poly_map(const Mesh &mesh, + MutableSpan<Vector<int>> r_vertex_poly_indices) +{ + for (const int i : IndexRange(mesh.totpoly)) { + const MPoly &poly = mesh.mpoly[i]; + for (const MLoop &loop : Span<MLoop>(&mesh.mloop[poly.loopstart], poly.totloop)) { + r_vertex_poly_indices[loop.v].append(i); + } + } +} + +/** + * Sorts the polygons connected to the given vertex based on polygon adjacency. The ordering is + * so such that the normals point in the same way as the original mesh. If the vertex is a + * boundary vertex, the first and last polygon have a boundary edge connected to the vertex. The + * `r_shared_edges` array at index i is set to the index of the shared edge between the i-th and + * `(i+1)-th` sorted polygon. Similarly the `r_sorted_corners` array at index i is set to the + * corner in the i-th sorted polygon. + * + * How the faces are sorted (see diagrams below): + * (For this explanation we'll assume all faces are oriented clockwise) + * (The vertex whose connected polygons we need to sort is "v0") + * + * \code{.unparsed} + * Normal case: Boundary Vertex case: + * v1 ----- v2 ----- v3 | | | + * | f3 | f0 | v2 ---- v4 --------- v3--- + * | | | | / ,-' | + * v8 ----- v0 ----- v4 | f0 / f1 ,-' | + * | f2 | f1 | | / ,-' | + * | | | | / ,-' | + * v7 ----- v6 ----- v5 | / ,-' f2 | + * | /,-' | + * v0 ------------------ v1--- + * \endcode + * + * - First we get the two corners of each face that have an edge which contains v0. A corner is + * simply a vertex followed by an edge. In this case for the face "f0" for example, we'd end up + * with the corners (v: v4, e: v4<->v0) and (v: v0, e: v0<->v2). Note that if the face was + * oriented counter-clockwise we'd end up with the corners (v: v0, e: v0<->v4) and (v: v2, e: + * v0<->v2) instead. + * - Then we need to choose one polygon as our first. If "v0" is not on a boundary we can just + * choose any polygon. If it is on a boundary some more care needs to be taken. Here we need to + * pick a polygon which lies on the boundary (in the diagram either f0 or f2). To choose between + * the two we need the next step. + * - In the normal case we use this polygon to set `shared_edge_i` which indicates the index of the + * shared edge between this polygon and the next one. There are two possible choices: v0<->v4 and + * v2<->v0. To choose we look at the corners. Since the edge v0<->v2 lies on the corner which has + * v0, we set `shared_edge_i` to the other edge (v0<->v4), such that the next face will be "f1" + * which is the next face in clockwise order. + * - In the boundary vertex case, we do something similar, but we are also forced to choose the + * edge which is not on the boundary. If this doesn't line up with orientation of the polygon, we + * know we'll need to choose the other boundary polygon as our first polygon. If the orientations + * don't line up there as well, it means that the mesh normals are not consistent, and we just + * have to force an orientation for ourselves. (Imagine if f0 is oriented counter-clockwise and + * f2 is oriented clockwise for example) + * - Next comes a loop where we look at the other faces and find the one which has the shared + * edge. Then we set the next shared edge to the other edge on the polygon connected to "v0", and + * continue. Because of the way we've chosen the first shared edge the order of the faces will + * have the same orientation as that of the first polygon. + * (In this case we'd have f0 -> f1 -> f2 -> f3 which also goes around clockwise). + * - Every time we determine a shared edge, we can also add a corner to `r_sorted_corners`. This + * will simply be the corner which doesn't contain the shared edge. + * - Finally if we are in the normal case we also need to add the last "shared edge" to close the + * loop. + */ +static void sort_vertex_polys(const Mesh &mesh, + const int vertex_index, + const bool boundary_vertex, + const Span<EdgeType> edge_types, + MutableSpan<int> connected_polygons, + MutableSpan<int> r_shared_edges, + MutableSpan<int> r_sorted_corners) +{ + if (connected_polygons.size() <= 2 && (!boundary_vertex || connected_polygons.size() == 0)) { + return; + } + + /* For each polygon store the two corners whose edge contains the vertex. */ + Array<std::pair<int, int>> poly_vertex_corners(connected_polygons.size()); + for (const int i : connected_polygons.index_range()) { + const MPoly &poly = mesh.mpoly[connected_polygons[i]]; + bool first_edge_done = false; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + if (mesh.medge[loop.e].v1 == vertex_index || mesh.medge[loop.e].v2 == vertex_index) { + if (!first_edge_done) { + poly_vertex_corners[i].first = loop_index; + first_edge_done = true; + } + else { + poly_vertex_corners[i].second = loop_index; + break; + } + } + } + } + + int shared_edge_i = -1; + /* Determine first polygon and orientation. For now the orientation of the whole loop depends + * on the one polygon we chose as first. It's probably not worth it to check every polygon in + * the loop to determine the 'average' orientation. */ + if (boundary_vertex) { + /* Our first polygon needs to be one which has a boundary edge. */ + for (const int i : connected_polygons.index_range()) { + const MLoop &first_loop = mesh.mloop[poly_vertex_corners[i].first]; + const MLoop &second_loop = mesh.mloop[poly_vertex_corners[i].second]; + if (edge_types[first_loop.e] == EdgeType::Boundary && first_loop.v == vertex_index) { + shared_edge_i = second_loop.e; + r_sorted_corners[0] = poly_vertex_corners[i].first; + std::swap(connected_polygons[i], connected_polygons[0]); + std::swap(poly_vertex_corners[i], poly_vertex_corners[0]); + break; + } + if (edge_types[second_loop.e] == EdgeType::Boundary && second_loop.v == vertex_index) { + shared_edge_i = first_loop.e; + r_sorted_corners[0] = poly_vertex_corners[i].second; + std::swap(connected_polygons[i], connected_polygons[0]); + std::swap(poly_vertex_corners[i], poly_vertex_corners[0]); + break; + } + } + if (shared_edge_i == -1) { + /* The rotation is inconsistent between the two polygons on the boundary. Just choose one + * of the polygon's orientation. */ + for (const int i : connected_polygons.index_range()) { + const MLoop &first_loop = mesh.mloop[poly_vertex_corners[i].first]; + const MLoop &second_loop = mesh.mloop[poly_vertex_corners[i].second]; + if (edge_types[first_loop.e] == EdgeType::Boundary) { + shared_edge_i = second_loop.e; + r_sorted_corners[0] = poly_vertex_corners[i].first; + std::swap(connected_polygons[i], connected_polygons[0]); + std::swap(poly_vertex_corners[i], poly_vertex_corners[0]); + break; + } + if (edge_types[second_loop.e] == EdgeType::Boundary) { + shared_edge_i = first_loop.e; + r_sorted_corners[0] = poly_vertex_corners[i].second; + std::swap(connected_polygons[i], connected_polygons[0]); + std::swap(poly_vertex_corners[i], poly_vertex_corners[0]); + break; + } + } + } + } + else { + /* Any polygon can be the first. Just need to check the orientation. */ + const MLoop &first_loop = mesh.mloop[poly_vertex_corners[0].first]; + const MLoop &second_loop = mesh.mloop[poly_vertex_corners[0].second]; + if (first_loop.v == vertex_index) { + shared_edge_i = second_loop.e; + r_sorted_corners[0] = poly_vertex_corners[0].first; + } + else { + r_sorted_corners[0] = poly_vertex_corners[0].second; + shared_edge_i = first_loop.e; + } + } + BLI_assert(shared_edge_i != -1); + + for (const int i : IndexRange(connected_polygons.size() - 1)) { + r_shared_edges[i] = shared_edge_i; + + /* Look at the other polys to see if it has this shared edge. */ + int j = i + 1; + for (; j < connected_polygons.size(); ++j) { + const MLoop &first_loop = mesh.mloop[poly_vertex_corners[j].first]; + const MLoop &second_loop = mesh.mloop[poly_vertex_corners[j].second]; + if (first_loop.e == shared_edge_i) { + r_sorted_corners[i + 1] = poly_vertex_corners[j].first; + shared_edge_i = second_loop.e; + break; + } + if (second_loop.e == shared_edge_i) { + r_sorted_corners[i + 1] = poly_vertex_corners[j].second; + shared_edge_i = first_loop.e; + break; + } + } + + BLI_assert(j != connected_polygons.size()); + + std::swap(connected_polygons[i + 1], connected_polygons[j]); + std::swap(poly_vertex_corners[i + 1], poly_vertex_corners[j]); + } + + if (!boundary_vertex) { + /* Shared edge between first and last polygon. */ + r_shared_edges.last() = shared_edge_i; + } +} + +/** + * Get the edge on the poly that contains the given vertex and is a boundary edge. + */ +static void boundary_edge_on_poly(const MPoly &poly, + const Mesh &mesh, + const int vertex_index, + const Span<EdgeType> edge_types, + int &r_edge) +{ + for (const MLoop &loop : Span<MLoop>(&mesh.mloop[poly.loopstart], poly.totloop)) { + if (edge_types[loop.e] == EdgeType::Boundary) { + const MEdge &edge = mesh.medge[loop.e]; + if (edge.v1 == vertex_index || edge.v2 == vertex_index) { + r_edge = loop.e; + return; + } + } + } +} + +/** + * Get the two edges on the poly that contain the given vertex and are boundary edges. The + * orientation of the poly is taken into account. + */ +static void boundary_edges_on_poly(const MPoly &poly, + const Mesh &mesh, + const int vertex_index, + const Span<EdgeType> edge_types, + int &r_edge1, + int &r_edge2) +{ + bool edge1_done = false; + /* This is set to true if the order in which we encounter the two edges is inconsistent with the + * orientation of the polygon. */ + bool needs_swap = false; + for (const MLoop &loop : Span<MLoop>(&mesh.mloop[poly.loopstart], poly.totloop)) { + if (edge_types[loop.e] == EdgeType::Boundary) { + const MEdge &edge = mesh.medge[loop.e]; + if (edge.v1 == vertex_index || edge.v2 == vertex_index) { + if (edge1_done) { + if (needs_swap) { + r_edge2 = r_edge1; + r_edge1 = loop.e; + } + else { + r_edge2 = loop.e; + } + return; + } + r_edge1 = loop.e; + edge1_done = true; + if (loop.v == vertex_index) { + needs_swap = true; + } + } + } + } +} + +static void add_edge(const Mesh &mesh, + const int old_edge_i, + const int v1, + const int v2, + Vector<int> &new_to_old_edges_map, + Vector<MEdge> &new_edges, + Vector<int> &loop_edges) +{ + MEdge new_edge = MEdge(mesh.medge[old_edge_i]); + new_edge.v1 = v1; + new_edge.v2 = v2; + const int new_edge_i = new_edges.size(); + new_to_old_edges_map.append(old_edge_i); + new_edges.append(new_edge); + loop_edges.append(new_edge_i); +} + +/* Returns true if the vertex is connected only to the two polygons and is not on the boundary. */ +static bool vertex_needs_dissolving(const int vertex, + const int first_poly_index, + const int second_poly_index, + const Span<VertexType> vertex_types, + const Span<Vector<int>> vertex_poly_indices) +{ + /* Order is guaranteed to be the same because 2poly verts that are not on the boundary are + * ignored in `sort_vertex_polys`. */ + return (vertex_types[vertex] != VertexType::Boundary && + vertex_poly_indices[vertex].size() == 2 && + vertex_poly_indices[vertex][0] == first_poly_index && + vertex_poly_indices[vertex][1] == second_poly_index); +} + +/** + * Finds 'normal' vertices which are connected to only two polygons and marks them to not be + * used in the data-structures derived from the mesh. For each pair of polygons which has such a + * vertex, an edge is created for the dual mesh between the centers of those two polygons. All + * edges in the input mesh which contain such a vertex are marked as 'done' to prevent duplicate + * edges being created. (See T94144) + */ +static void dissolve_redundant_verts(const Mesh &mesh, + const Span<Vector<int>> vertex_poly_indices, + MutableSpan<VertexType> vertex_types, + MutableSpan<int> old_to_new_edges_map, + Vector<MEdge> &new_edges, + Vector<int> &new_to_old_edges_map) +{ + for (const int vert_i : IndexRange(mesh.totvert)) { + if (vertex_poly_indices[vert_i].size() != 2 || vertex_types[vert_i] != VertexType::Normal) { + continue; + } + const int first_poly_index = vertex_poly_indices[vert_i][0]; + const int second_poly_index = vertex_poly_indices[vert_i][1]; + const int new_edge_index = new_edges.size(); + bool edge_created = false; + const MPoly &poly = mesh.mpoly[first_poly_index]; + for (const MLoop &loop : Span<MLoop>(&mesh.mloop[poly.loopstart], poly.totloop)) { + const MEdge &edge = mesh.medge[loop.e]; + const int v1 = edge.v1; + const int v2 = edge.v2; + bool mark_edge = false; + if (vertex_needs_dissolving( + v1, first_poly_index, second_poly_index, vertex_types, vertex_poly_indices)) { + /* This vertex is now 'removed' and should be ignored elsewhere. */ + vertex_types[v1] = VertexType::Loose; + mark_edge = true; + } + if (vertex_needs_dissolving( + v2, first_poly_index, second_poly_index, vertex_types, vertex_poly_indices)) { + /* This vertex is now 'removed' and should be ignored elsewhere. */ + vertex_types[v2] = VertexType::Loose; + mark_edge = true; + } + if (mark_edge) { + if (!edge_created) { + MEdge new_edge = MEdge(edge); + /* The vertex indices in the dual mesh are the polygon indices of the input mesh. */ + new_edge.v1 = first_poly_index; + new_edge.v2 = second_poly_index; + new_to_old_edges_map.append(loop.e); + new_edges.append(new_edge); + edge_created = true; + } + old_to_new_edges_map[loop.e] = new_edge_index; + } + } + } +} + +/** + * Calculate the barycentric dual of a mesh. The dual is only "dual" in terms of connectivity, + * i.e. applying the function twice will give the same vertices, edges, and faces, but not the + * same positions. When the option "Keep Boundaries" is selected the connectivity is no + * longer dual. + * + * For the dual mesh of a manifold input mesh: + * - The vertices are at the centers of the faces of the input mesh. + * - The edges connect the two vertices created from the two faces next to the edge in the input + * mesh. + * - The faces are at the vertices of the input mesh. + * + * Some special cases are needed for boundaries and non-manifold geometry. + */ +static void calc_dual_mesh(GeometrySet &geometry_set, + const MeshComponent &in_component, + const bool keep_boundaries) +{ + const Mesh &mesh_in = *in_component.get_for_read(); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes); + + Array<VertexType> vertex_types(mesh_in.totvert); + Array<EdgeType> edge_types(mesh_in.totedge); + calc_boundaries(mesh_in, vertex_types, edge_types); + /* Stores the indices of the polygons connected to the vertex. Because the polygons are looped + * over in order of their indices, the polygon's indices will be sorted in ascending order. + (This can change once they are sorted using `sort_vertex_polys`). */ + Array<Vector<int>> vertex_poly_indices(mesh_in.totvert); + Array<Array<int>> vertex_shared_edges(mesh_in.totvert); + Array<Array<int>> vertex_corners(mesh_in.totvert); + create_vertex_poly_map(mesh_in, vertex_poly_indices); + threading::parallel_for(vertex_poly_indices.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold || + (!keep_boundaries && vertex_types[i] == VertexType::Boundary)) { + /* Bad vertex that we can't work with. */ + continue; + } + MutableSpan<int> loop_indices = vertex_poly_indices[i]; + Array<int> sorted_corners(loop_indices.size()); + if (vertex_types[i] == VertexType::Normal) { + Array<int> shared_edges(loop_indices.size()); + sort_vertex_polys( + mesh_in, i, false, edge_types, loop_indices, shared_edges, sorted_corners); + vertex_shared_edges[i] = shared_edges; + } + else { + Array<int> shared_edges(loop_indices.size() - 1); + sort_vertex_polys( + mesh_in, i, true, edge_types, loop_indices, shared_edges, sorted_corners); + vertex_shared_edges[i] = shared_edges; + } + vertex_corners[i] = sorted_corners; + } + }); + + Vector<float3> vertex_positions(mesh_in.totpoly); + for (const int i : IndexRange(mesh_in.totpoly)) { + const MPoly poly = mesh_in.mpoly[i]; + BKE_mesh_calc_poly_center( + &poly, &mesh_in.mloop[poly.loopstart], mesh_in.mvert, vertex_positions[i]); + } + + Array<int> boundary_edge_midpoint_index; + if (keep_boundaries) { + /* Only initialize when we actually need it. */ + boundary_edge_midpoint_index.reinitialize(mesh_in.totedge); + /* We need to add vertices at the centers of boundary edges. */ + for (const int i : IndexRange(mesh_in.totedge)) { + if (edge_types[i] == EdgeType::Boundary) { + float3 mid; + const MEdge &edge = mesh_in.medge[i]; + mid_v3_v3v3(mid, mesh_in.mvert[edge.v1].co, mesh_in.mvert[edge.v2].co); + boundary_edge_midpoint_index[i] = vertex_positions.size(); + vertex_positions.append(mid); + } + } + } + + Vector<int> loop_lengths; + Vector<int> loops; + Vector<int> loop_edges; + Vector<MEdge> new_edges; + /* These are used to transfer attributes. */ + Vector<int> new_to_old_face_corners_map; + Vector<int> new_to_old_edges_map; + /* Stores the index of the vertex in the dual and the face it should get the attribute from. */ + Vector<std::pair<int, int>> boundary_vertex_to_relevant_face_map; + /* Since each edge in the dual (except the ones created with keep boundaries) comes from + * exactly one edge in the original, we can use this array to keep track of whether it still + * needs to be created or not. If it's not -1 it gives the index in `new_edges` of the dual + * edge. The edges coming from preserving the boundaries only get added once anyway, so we + * don't need a hash-map for that. */ + Array<int> old_to_new_edges_map(mesh_in.totedge); + old_to_new_edges_map.fill(-1); + + /* This is necessary to prevent duplicate edges from being created, but will likely not do + * anything for most meshes. */ + dissolve_redundant_verts(mesh_in, + vertex_poly_indices, + vertex_types, + old_to_new_edges_map, + new_edges, + new_to_old_edges_map); + + for (const int i : IndexRange(mesh_in.totvert)) { + if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold || + (!keep_boundaries && vertex_types[i] == VertexType::Boundary)) { + /* Bad vertex that we can't work with. */ + continue; + } + + Vector<int> loop_indices = vertex_poly_indices[i]; + Span<int> shared_edges = vertex_shared_edges[i]; + Span<int> sorted_corners = vertex_corners[i]; + if (vertex_types[i] == VertexType::Normal) { + if (loop_indices.size() <= 2) { + /* We can't make a polygon from 2 vertices. */ + continue; + } + + /* Add edges in the loop. */ + for (const int i : shared_edges.index_range()) { + const int old_edge_i = shared_edges[i]; + if (old_to_new_edges_map[old_edge_i] == -1) { + /* This edge has not been created yet. */ + MEdge new_edge = MEdge(mesh_in.medge[old_edge_i]); + new_edge.v1 = loop_indices[i]; + new_edge.v2 = loop_indices[(i + 1) % loop_indices.size()]; + new_to_old_edges_map.append(old_edge_i); + old_to_new_edges_map[old_edge_i] = new_edges.size(); + new_edges.append(new_edge); + } + loop_edges.append(old_to_new_edges_map[old_edge_i]); + } + + new_to_old_face_corners_map.extend(sorted_corners); + } + else { + /** + * The code handles boundary vertices like the vertex marked "V" in the diagram below. + * The first thing that happens is ordering the faces f1,f2 and f3 (stored in + * loop_indices), together with their shared edges e3 and e4 (which get stored in + * shared_edges). The ordering could end up being clockwise or counterclockwise, for this + * we'll assume that the ordering f1->f2->f3 is chosen. After that we add the edges in + * between the polygons, in this case the edges f1--f2, and f2--f3. Now we need to merge + * these with the boundary edges e1 and e2. To do this we create an edge from f3 to the + * midpoint of e2 (computed in a previous step), from this midpoint to V, from V to the + * midpoint of e1 and from the midpoint of e1 to f1. + * + * \code{.unparsed} + * | | | | | | + * v2 ---- v3 --------- v4--- v2 ---- v3 -------- v4--- + * | f3 / ,-' | | / ,-'| + * | / f2 ,-' | | / ,-' | + * e2 | /e3 ,-' e4 | ====> M1-f3-/--f2-.,-' | + * | / ,-' | ====> | / ,-'\ | + * | / ,-' f1 | | / ,-' f1 | + * | /,-' | | /,-' | | + * V-------------------- v5--- V------------M2----- v5--- + * \endcode + */ + + /* Add the edges in between the polys. */ + for (const int i : shared_edges.index_range()) { + const int old_edge_i = shared_edges[i]; + if (old_to_new_edges_map[old_edge_i] == -1) { + /* This edge has not been created yet. */ + MEdge new_edge = MEdge(mesh_in.medge[old_edge_i]); + new_edge.v1 = loop_indices[i]; + new_edge.v2 = loop_indices[i + 1]; + new_to_old_edges_map.append(old_edge_i); + old_to_new_edges_map[old_edge_i] = new_edges.size(); + new_edges.append(new_edge); + } + loop_edges.append(old_to_new_edges_map[old_edge_i]); + } + + new_to_old_face_corners_map.extend(sorted_corners); + + /* Add the vertex and the midpoints of the two boundary edges to the loop. */ + + /* Get the boundary edges. */ + int edge1; + int edge2; + if (loop_indices.size() >= 2) { + /* The first boundary edge is at the end of the chain of polygons. */ + boundary_edge_on_poly(mesh_in.mpoly[loop_indices.last()], mesh_in, i, edge_types, edge1); + boundary_edge_on_poly(mesh_in.mpoly[loop_indices.first()], mesh_in, i, edge_types, edge2); + } + else { + /* If there is only one polygon both edges are in that polygon. */ + boundary_edges_on_poly( + mesh_in.mpoly[loop_indices[0]], mesh_in, i, edge_types, edge1, edge2); + } + + const int last_face_center = loop_indices.last(); + loop_indices.append(boundary_edge_midpoint_index[edge1]); + new_to_old_face_corners_map.append(sorted_corners.last()); + const int first_midpoint = loop_indices.last(); + if (old_to_new_edges_map[edge1] == -1) { + add_edge(mesh_in, + edge1, + last_face_center, + first_midpoint, + new_to_old_edges_map, + new_edges, + loop_edges); + old_to_new_edges_map[edge1] = new_edges.size() - 1; + boundary_vertex_to_relevant_face_map.append(std::pair(first_midpoint, last_face_center)); + } + else { + loop_edges.append(old_to_new_edges_map[edge1]); + } + loop_indices.append(vertex_positions.size()); + /* This is sort of arbitrary, but interpolating would be a lot harder to do. */ + new_to_old_face_corners_map.append(sorted_corners.first()); + boundary_vertex_to_relevant_face_map.append( + std::pair(loop_indices.last(), last_face_center)); + vertex_positions.append(mesh_in.mvert[i].co); + const int boundary_vertex = loop_indices.last(); + add_edge(mesh_in, + edge1, + first_midpoint, + boundary_vertex, + new_to_old_edges_map, + new_edges, + loop_edges); + + loop_indices.append(boundary_edge_midpoint_index[edge2]); + new_to_old_face_corners_map.append(sorted_corners.first()); + const int second_midpoint = loop_indices.last(); + add_edge(mesh_in, + edge2, + boundary_vertex, + second_midpoint, + new_to_old_edges_map, + new_edges, + loop_edges); + + if (old_to_new_edges_map[edge2] == -1) { + const int first_face_center = loop_indices.first(); + add_edge(mesh_in, + edge2, + second_midpoint, + first_face_center, + new_to_old_edges_map, + new_edges, + loop_edges); + old_to_new_edges_map[edge2] = new_edges.size() - 1; + boundary_vertex_to_relevant_face_map.append(std::pair(second_midpoint, first_face_center)); + } + else { + loop_edges.append(old_to_new_edges_map[edge2]); + } + } + + loop_lengths.append(loop_indices.size()); + for (const int j : loop_indices) { + loops.append(j); + } + } + Mesh *mesh_out = BKE_mesh_new_nomain( + vertex_positions.size(), new_edges.size(), 0, loops.size(), loop_lengths.size()); + MeshComponent out_component; + out_component.replace(mesh_out, GeometryOwnershipType::Editable); + transfer_attributes(attributes, + vertex_types, + keep_boundaries, + new_to_old_edges_map, + new_to_old_face_corners_map, + boundary_vertex_to_relevant_face_map, + in_component, + out_component); + + int loop_start = 0; + for (const int i : IndexRange(mesh_out->totpoly)) { + mesh_out->mpoly[i].loopstart = loop_start; + mesh_out->mpoly[i].totloop = loop_lengths[i]; + loop_start += loop_lengths[i]; + } + for (const int i : IndexRange(mesh_out->totloop)) { + mesh_out->mloop[i].v = loops[i]; + mesh_out->mloop[i].e = loop_edges[i]; + } + for (const int i : IndexRange(mesh_out->totvert)) { + copy_v3_v3(mesh_out->mvert[i].co, vertex_positions[i]); + } + memcpy(mesh_out->medge, new_edges.data(), sizeof(MEdge) * new_edges.size()); + BKE_mesh_normals_tag_dirty(mesh_out); + geometry_set.replace_mesh(mesh_out); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + const bool keep_boundaries = params.extract_input<bool>("Keep Boundaries"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_mesh()) { + const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>(); + calc_dual_mesh(geometry_set, component, keep_boundaries); + } + }); + params.set_output("Dual Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_dual_mesh_cc + +void register_node_type_geo_dual_mesh() +{ + namespace file_ns = blender::nodes::node_geo_dual_mesh_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_DUAL_MESH, "Dual Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 new file mode 100644 index 00000000000..9376789cf2c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -0,0 +1,97 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_edge_split_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Mesh")); +} + +static Mesh *mesh_edge_split(const Mesh &mesh, const IndexMask selection) +{ + BMeshCreateParams bmesh_create_params{}; + bmesh_create_params.use_toolflags = true; + const BMAllocTemplate allocsize = {0, 0, 0, 0}; + BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params); + + BMeshFromMeshParams bmesh_from_mesh_params{}; + BM_mesh_bm_from_me(bm, &mesh, &bmesh_from_mesh_params); + + BM_mesh_elem_table_ensure(bm, BM_EDGE); + for (const int i : selection) { + BMEdge *edge = BM_edge_at_index(bm, i); + BM_elem_flag_enable(edge, BM_ELEM_TAG); + } + + BM_mesh_edgesplit(bm, false, true, false); + + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, &mesh); + BM_mesh_free(bm); + + BKE_mesh_normals_tag_dirty(result); + + return result; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_mesh()) { + return; + } + + const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE}; + const int domain_size = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE); + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + geometry_set.replace_mesh(mesh_edge_split(*mesh_component.get_for_read(), selection)); + }); + + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_edge_split_cc + +void register_node_type_geo_edge_split() +{ + namespace file_ns = blender::nodes::node_geo_edge_split_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SPLIT_EDGES, "Split Edges", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc new file mode 100644 index 00000000000..1d1c5bd2285 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -0,0 +1,1365 @@ +/* + * 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_disjoint_set.hh" +#include "BLI_task.hh" +#include "BLI_vector_set.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_extrude_mesh_cc { + +NODE_STORAGE_FUNCS(NodeGeometryExtrudeMesh) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Mesh").supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Vector>(N_("Offset")).subtype(PROP_TRANSLATION).implicit_field().hide_value(); + b.add_input<decl::Float>(N_("Offset Scale")).default_value(1.0f).min(0.0f).supports_field(); + b.add_input<decl::Bool>(N_("Individual")).default_value(true); + b.add_output<decl::Geometry>("Mesh"); + b.add_output<decl::Bool>(N_("Top")).field_source(); + b.add_output<decl::Bool>(N_("Side")).field_source(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryExtrudeMesh *data = MEM_cnew<NodeGeometryExtrudeMesh>(__func__); + data->mode = GEO_NODE_EXTRUDE_MESH_FACES; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryExtrudeMesh &storage = node_storage(*node); + const GeometryNodeExtrudeMeshMode mode = static_cast<GeometryNodeExtrudeMeshMode>(storage.mode); + + bNodeSocket *individual_socket = (bNodeSocket *)node->inputs.last; + + nodeSetSocketAvailability(ntree, individual_socket, mode == GEO_NODE_EXTRUDE_MESH_FACES); +} + +struct AttributeOutputs { + StrongAnonymousAttributeID top_id; + StrongAnonymousAttributeID side_id; +}; + +static void save_selection_as_attribute(MeshComponent &component, + const AnonymousAttributeID *id, + const AttributeDomain domain, + const IndexMask selection) +{ + BLI_assert(!component.attribute_exists(id)); + + OutputAttribute_Typed<bool> attribute = component.attribute_try_get_for_output_only<bool>( + id, domain); + /* Rely on the new attribute being zeroed by default. */ + BLI_assert(!attribute.as_span().as_span().contains(true)); + + if (selection.is_range()) { + attribute.as_span().slice(selection.as_range()).fill(true); + } + else { + attribute.as_span().fill_indices(selection, true); + } + + attribute.save(); +} + +static MutableSpan<MVert> mesh_verts(Mesh &mesh) +{ + return {mesh.mvert, mesh.totvert}; +} +static MutableSpan<MEdge> mesh_edges(Mesh &mesh) +{ + return {mesh.medge, mesh.totedge}; +} +static Span<MPoly> mesh_polys(const Mesh &mesh) +{ + return {mesh.mpoly, mesh.totpoly}; +} +static MutableSpan<MPoly> mesh_polys(Mesh &mesh) +{ + return {mesh.mpoly, mesh.totpoly}; +} +static Span<MLoop> mesh_loops(const Mesh &mesh) +{ + return {mesh.mloop, mesh.totloop}; +} +static MutableSpan<MLoop> mesh_loops(Mesh &mesh) +{ + return {mesh.mloop, mesh.totloop}; +} + +/** + * \note: Some areas in this file rely on the new sections of attributes from #CustomData_realloc + * to be zeroed. + */ +static void expand_mesh(Mesh &mesh, + const int vert_expand, + const int edge_expand, + const int poly_expand, + const int loop_expand) +{ + if (vert_expand != 0) { + CustomData_duplicate_referenced_layers(&mesh.vdata, mesh.totvert); + mesh.totvert += vert_expand; + CustomData_realloc(&mesh.vdata, mesh.totvert); + } + else { + /* Even when the number of vertices is not changed, the mesh can still be deformed. */ + CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert); + } + if (edge_expand != 0) { + CustomData_duplicate_referenced_layers(&mesh.edata, mesh.totedge); + mesh.totedge += edge_expand; + CustomData_realloc(&mesh.edata, mesh.totedge); + } + if (poly_expand != 0) { + CustomData_duplicate_referenced_layers(&mesh.pdata, mesh.totpoly); + mesh.totpoly += poly_expand; + CustomData_realloc(&mesh.pdata, mesh.totpoly); + } + if (loop_expand != 0) { + CustomData_duplicate_referenced_layers(&mesh.ldata, mesh.totloop); + mesh.totloop += loop_expand; + CustomData_realloc(&mesh.ldata, mesh.totloop); + } + BKE_mesh_update_customdata_pointers(&mesh, false); +} + +static MEdge new_edge(const int v1, const int v2) +{ + MEdge edge; + edge.v1 = v1; + edge.v2 = v2; + edge.flag = (ME_EDGEDRAW | ME_EDGERENDER); + return edge; +} + +static MEdge new_loose_edge(const int v1, const int v2) +{ + MEdge edge; + edge.v1 = v1; + edge.v2 = v2; + edge.flag = ME_LOOSEEDGE; + return edge; +} + +static MPoly new_poly(const int loopstart, const int totloop) +{ + MPoly poly; + poly.loopstart = loopstart; + poly.totloop = totloop; + poly.flag = 0; + return poly; +} + +template<typename T> void copy_with_indices(MutableSpan<T> dst, Span<T> src, Span<int> indices) +{ + BLI_assert(dst.size() == indices.size()); + for (const int i : dst.index_range()) { + dst[i] = src[indices[i]]; + } +} + +template<typename T> void copy_with_mask(MutableSpan<T> dst, Span<T> src, IndexMask mask) +{ + BLI_assert(dst.size() == mask.size()); + threading::parallel_for(mask.index_range(), 512, [&](const IndexRange range) { + for (const int i : range) { + dst[i] = src[mask[i]]; + } + }); +} + +/** + * \param get_mix_indices_fn: Returns a Span of indices of the source points to mix for every + * result point. + */ +template<typename T, typename GetMixIndicesFn> +void copy_with_mixing(MutableSpan<T> dst, Span<T> src, GetMixIndicesFn get_mix_indices_fn) +{ + threading::parallel_for(dst.index_range(), 512, [&](const IndexRange range) { + attribute_math::DefaultPropatationMixer<T> mixer{dst.slice(range)}; + for (const int i_dst : IndexRange(range.size())) { + for (const int i_src : get_mix_indices_fn(range[i_dst])) { + mixer.mix_in(i_dst, src[i_src]); + } + } + mixer.finalize(); + }); +} + +static Array<Vector<int>> create_vert_to_edge_map(const int vert_size, + Span<MEdge> edges, + const int vert_offset = 0) +{ + Array<Vector<int>> vert_to_edge_map(vert_size); + for (const int i : edges.index_range()) { + vert_to_edge_map[edges[i].v1 - vert_offset].append(i); + vert_to_edge_map[edges[i].v2 - vert_offset].append(i); + } + return vert_to_edge_map; +} + +static void extrude_mesh_vertices(MeshComponent &component, + const Field<bool> &selection_field, + const Field<float3> &offset_field, + const AttributeOutputs &attribute_outputs) +{ + Mesh &mesh = *component.get_for_write(); + const int orig_vert_size = mesh.totvert; + const int orig_edge_size = mesh.totedge; + + GeometryComponentFieldContext context{component, ATTR_DOMAIN_POINT}; + FieldEvaluator evaluator{context, mesh.totvert}; + evaluator.add(offset_field); + evaluator.set_selection(selection_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> offsets = evaluator.get_evaluated<float3>(0); + + /* This allows parallelizing attribute mixing for new edges. */ + Array<Vector<int>> vert_to_edge_map = create_vert_to_edge_map(orig_vert_size, mesh_edges(mesh)); + + expand_mesh(mesh, selection.size(), selection.size(), 0, 0); + + const IndexRange new_vert_range{orig_vert_size, selection.size()}; + const IndexRange new_edge_range{orig_edge_size, selection.size()}; + + MutableSpan<MVert> new_verts = mesh_verts(mesh).slice(new_vert_range); + MutableSpan<MEdge> new_edges = mesh_edges(mesh).slice(new_edge_range); + + for (const int i_selection : selection.index_range()) { + new_edges[i_selection] = new_loose_edge(selection[i_selection], new_vert_range[i_selection]); + } + + component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + if (!ELEM(meta_data.domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE)) { + return true; + } + OutputAttribute attribute = component.attribute_try_get_for_output( + id, meta_data.domain, meta_data.data_type); + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan<T> data = attribute.as_span().typed<T>(); + switch (attribute.domain()) { + case ATTR_DOMAIN_POINT: { + /* New vertices copy the attribute values from their source vertex. */ + copy_with_mask(data.slice(new_vert_range), data.as_span(), selection); + break; + } + case ATTR_DOMAIN_EDGE: { + /* New edge values are mixed from of all the edges connected to the source vertex. */ + copy_with_mixing(data.slice(new_edge_range), data.as_span(), [&](const int i) { + return vert_to_edge_map[selection[i]].as_span(); + }); + break; + } + default: + BLI_assert_unreachable(); + } + }); + + attribute.save(); + return true; + }); + + devirtualize_varray(offsets, [&](const auto offsets) { + threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + const float3 offset = offsets[selection[i]]; + add_v3_v3(new_verts[i].co, offset); + } + }); + }); + + if (attribute_outputs.top_id) { + save_selection_as_attribute( + component, attribute_outputs.top_id.get(), ATTR_DOMAIN_POINT, new_vert_range); + } + if (attribute_outputs.side_id) { + save_selection_as_attribute( + component, attribute_outputs.side_id.get(), ATTR_DOMAIN_EDGE, new_edge_range); + } + + BKE_mesh_runtime_clear_cache(&mesh); + BKE_mesh_normals_tag_dirty(&mesh); +} + +static Array<Vector<int, 2>> mesh_calculate_polys_of_edge(const Mesh &mesh) +{ + Span<MPoly> polys = mesh_polys(mesh); + Span<MLoop> loops = mesh_loops(mesh); + Array<Vector<int, 2>> polys_of_edge(mesh.totedge); + + for (const int i_poly : polys.index_range()) { + const MPoly &poly = polys[i_poly]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + polys_of_edge[loop.e].append(i_poly); + } + } + + return polys_of_edge; +} + +static void fill_quad_consistent_direction(Span<MLoop> other_poly_loops, + MutableSpan<MLoop> new_loops, + const int vert_connected_to_poly_1, + const int vert_connected_to_poly_2, + const int vert_across_from_poly_1, + const int vert_across_from_poly_2, + const int edge_connected_to_poly, + const int connecting_edge_1, + const int edge_across_from_poly, + const int connecting_edge_2) +{ + /* Find the loop on the polygon connected to the new quad that uses the duplicate edge. */ + bool start_with_connecting_edge = true; + for (const MLoop &loop : other_poly_loops) { + if (loop.e == edge_connected_to_poly) { + start_with_connecting_edge = loop.v == vert_connected_to_poly_1; + break; + } + } + if (start_with_connecting_edge) { + new_loops[0].v = vert_connected_to_poly_1; + new_loops[0].e = connecting_edge_1; + new_loops[1].v = vert_across_from_poly_1; + new_loops[1].e = edge_across_from_poly; + new_loops[2].v = vert_across_from_poly_2; + new_loops[2].e = connecting_edge_2; + new_loops[3].v = vert_connected_to_poly_2; + new_loops[3].e = edge_connected_to_poly; + } + else { + new_loops[0].v = vert_connected_to_poly_1; + new_loops[0].e = edge_connected_to_poly; + new_loops[1].v = vert_connected_to_poly_2; + new_loops[1].e = connecting_edge_2; + new_loops[2].v = vert_across_from_poly_2; + new_loops[2].e = edge_across_from_poly; + new_loops[3].v = vert_across_from_poly_1; + new_loops[3].e = connecting_edge_1; + } +} + +template<typename T> +static VectorSet<int> vert_indices_from_edges(const Mesh &mesh, const Span<T> edge_indices) +{ + static_assert(is_same_any_v<T, int, int64_t>); + + VectorSet<int> vert_indices; + vert_indices.reserve(edge_indices.size()); + for (const T i_edge : edge_indices) { + const MEdge &edge = mesh.medge[i_edge]; + vert_indices.add(edge.v1); + vert_indices.add(edge.v2); + } + return vert_indices; +} + +static void extrude_mesh_edges(MeshComponent &component, + const Field<bool> &selection_field, + const Field<float3> &offset_field, + const AttributeOutputs &attribute_outputs) +{ + Mesh &mesh = *component.get_for_write(); + const int orig_vert_size = mesh.totvert; + Span<MEdge> orig_edges = mesh_edges(mesh); + Span<MPoly> orig_polys = mesh_polys(mesh); + const int orig_loop_size = mesh.totloop; + + GeometryComponentFieldContext edge_context{component, ATTR_DOMAIN_EDGE}; + FieldEvaluator edge_evaluator{edge_context, mesh.totedge}; + edge_evaluator.set_selection(selection_field); + edge_evaluator.add(offset_field); + edge_evaluator.evaluate(); + const IndexMask edge_selection = edge_evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &edge_offsets = edge_evaluator.get_evaluated<float3>(0); + if (edge_selection.is_empty()) { + return; + } + + const Array<Vector<int, 2>> edge_to_poly_map = mesh_calculate_polys_of_edge(mesh); + + /* Find the offsets on the vertex domain for translation. This must be done before the mesh's + * custom data layers are reallocated, in case the virtual array references on of them. */ + Array<float3> vert_offsets; + if (!edge_offsets.is_single()) { + vert_offsets.reinitialize(orig_vert_size); + attribute_math::DefaultPropatationMixer<float3> mixer(vert_offsets); + for (const int i_edge : edge_selection) { + const MEdge &edge = orig_edges[i_edge]; + const float3 offset = edge_offsets[i_edge]; + mixer.mix_in(edge.v1, offset); + mixer.mix_in(edge.v2, offset); + } + mixer.finalize(); + } + + const VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, edge_selection.indices()); + + const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()}; + /* The extruded edges connect the original and duplicate edges. */ + const IndexRange connect_edge_range{orig_edges.size(), new_vert_range.size()}; + /* The duplicate edges are extruded copies of the selected edges. */ + const IndexRange duplicate_edge_range = connect_edge_range.after(edge_selection.size()); + /* There is a new polygon for every selected edge. */ + const IndexRange new_poly_range{orig_polys.size(), edge_selection.size()}; + /* Every new polygon is a quad with four corners. */ + const IndexRange new_loop_range{orig_loop_size, new_poly_range.size() * 4}; + + expand_mesh(mesh, + new_vert_range.size(), + connect_edge_range.size() + duplicate_edge_range.size(), + new_poly_range.size(), + new_loop_range.size()); + + MutableSpan<MVert> new_verts = mesh_verts(mesh).slice(new_vert_range); + MutableSpan<MEdge> connect_edges = mesh_edges(mesh).slice(connect_edge_range); + MutableSpan<MEdge> duplicate_edges = mesh_edges(mesh).slice(duplicate_edge_range); + MutableSpan<MPoly> polys = mesh_polys(mesh); + MutableSpan<MPoly> new_polys = polys.slice(new_poly_range); + MutableSpan<MLoop> loops = mesh_loops(mesh); + MutableSpan<MLoop> new_loops = loops.slice(new_loop_range); + + for (const int i : connect_edges.index_range()) { + connect_edges[i] = new_edge(new_vert_indices[i], new_vert_range[i]); + } + + for (const int i : duplicate_edges.index_range()) { + const MEdge &orig_edge = mesh.medge[edge_selection[i]]; + const int i_new_vert_1 = new_vert_indices.index_of(orig_edge.v1); + const int i_new_vert_2 = new_vert_indices.index_of(orig_edge.v2); + duplicate_edges[i] = new_edge(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]); + } + + for (const int i : new_polys.index_range()) { + new_polys[i] = new_poly(new_loop_range[i * 4], 4); + } + + for (const int i : edge_selection.index_range()) { + const int orig_edge_index = edge_selection[i]; + + const MEdge &duplicate_edge = duplicate_edges[i]; + const int new_vert_1 = duplicate_edge.v1; + const int new_vert_2 = duplicate_edge.v2; + const int extrude_index_1 = new_vert_1 - orig_vert_size; + const int extrude_index_2 = new_vert_2 - orig_vert_size; + + Span<int> connected_polys = edge_to_poly_map[orig_edge_index]; + + /* When there was a single polygon connected to the new polygon, we can use the old one to keep + * the face direction consistent. When there is more than one connected edge, the new face + * direction is totally arbitrary and the only goal for the behavior is to be deterministic. */ + Span<MLoop> connected_poly_loops = {}; + if (connected_polys.size() == 1) { + const MPoly &connected_poly = polys[connected_polys.first()]; + connected_poly_loops = loops.slice(connected_poly.loopstart, connected_poly.totloop); + } + fill_quad_consistent_direction(connected_poly_loops, + new_loops.slice(4 * i, 4), + new_vert_indices[extrude_index_1], + new_vert_indices[extrude_index_2], + new_vert_1, + new_vert_2, + orig_edge_index, + connect_edge_range[extrude_index_1], + duplicate_edge_range[i], + connect_edge_range[extrude_index_2]); + } + + /* Create a map of indices in the extruded vertices array to all of the indices of edges + * in the duplicate edges array that connect to that vertex. This can be used to simplify the + * mixing of attribute data for the connecting edges. */ + const Array<Vector<int>> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( + new_vert_range.size(), duplicate_edges, orig_vert_size); + + component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + OutputAttribute attribute = component.attribute_try_get_for_output( + id, meta_data.domain, meta_data.data_type); + if (!attribute) { + return true; /* Impossible to write the "normal" attribute. */ + } + + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan<T> data = attribute.as_span().typed<T>(); + switch (attribute.domain()) { + case ATTR_DOMAIN_POINT: { + /* New vertices copy the attribute values from their source vertex. */ + copy_with_indices(data.slice(new_vert_range), data.as_span(), new_vert_indices); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Edges parallel to original edges copy the edge attributes from the original edges. */ + MutableSpan<T> duplicate_data = data.slice(duplicate_edge_range); + copy_with_mask(duplicate_data, data.as_span(), edge_selection); + + /* Edges connected to original vertices mix values of selected connected edges. */ + MutableSpan<T> connect_data = data.slice(connect_edge_range); + copy_with_mixing(connect_data, duplicate_data.as_span(), [&](const int i_new_vert) { + return new_vert_to_duplicate_edge_map[i_new_vert].as_span(); + }); + break; + } + case ATTR_DOMAIN_FACE: { + /* Attribute values for new faces are a mix of the values of faces connected to the its + * original edge. */ + copy_with_mixing(data.slice(new_poly_range), data.as_span(), [&](const int i) { + return edge_to_poly_map[edge_selection[i]].as_span(); + }); + + break; + } + case ATTR_DOMAIN_CORNER: { + /* New corners get the average value of all adjacent corners on original faces connected + * to the original edge of their face. */ + MutableSpan<T> new_data = data.slice(new_loop_range); + threading::parallel_for(edge_selection.index_range(), 256, [&](const IndexRange range) { + for (const int i_edge_selection : range) { + const int orig_edge_index = edge_selection[i_edge_selection]; + + Span<int> connected_polys = edge_to_poly_map[orig_edge_index]; + if (connected_polys.is_empty()) { + /* If there are no connected polygons, there is no corner data to + * interpolate. */ + new_data.slice(4 * i_edge_selection, 4).fill(T()); + continue; + } + + /* Both corners on each vertical edge of the side polygon get the same value, + * so there are only two unique values to mix. */ + Array<T> side_poly_corner_data(2); + attribute_math::DefaultPropatationMixer<T> mixer{side_poly_corner_data}; + + const MEdge &duplicate_edge = duplicate_edges[i_edge_selection]; + const int new_vert_1 = duplicate_edge.v1; + const int new_vert_2 = duplicate_edge.v2; + const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size]; + const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size]; + + /* Average the corner data from the corners that share a vertex from the + * polygons that share an edge with the extruded edge. */ + for (const int i_connected_poly : connected_polys.index_range()) { + const MPoly &connected_poly = polys[connected_polys[i_connected_poly]]; + for (const int i_loop : + IndexRange(connected_poly.loopstart, connected_poly.totloop)) { + const MLoop &loop = loops[i_loop]; + if (loop.v == orig_vert_1) { + mixer.mix_in(0, data[i_loop]); + } + if (loop.v == orig_vert_2) { + mixer.mix_in(1, data[i_loop]); + } + } + } + + mixer.finalize(); + + /* Instead of replicating the order in #fill_quad_consistent_direction here, it's + * simpler (though probably slower) to just match the corner data based on the vertex + * indices. */ + for (const int i : IndexRange(4 * i_edge_selection, 4)) { + if (ELEM(new_loops[i].v, new_vert_1, orig_vert_1)) { + new_data[i] = side_poly_corner_data.first(); + } + else if (ELEM(new_loops[i].v, new_vert_2, orig_vert_2)) { + new_data[i] = side_poly_corner_data.last(); + } + } + } + }); + break; + } + default: + BLI_assert_unreachable(); + } + }); + + attribute.save(); + return true; + }); + + if (edge_offsets.is_single()) { + const float3 offset = edge_offsets.get_internal_single(); + threading::parallel_for(new_verts.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + add_v3_v3(new_verts[i].co, offset); + } + }); + } + else { + threading::parallel_for(new_verts.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + add_v3_v3(new_verts[i].co, vert_offsets[new_vert_indices[i]]); + } + }); + } + + if (attribute_outputs.top_id) { + save_selection_as_attribute( + component, attribute_outputs.top_id.get(), ATTR_DOMAIN_EDGE, duplicate_edge_range); + } + if (attribute_outputs.side_id) { + save_selection_as_attribute( + component, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, new_poly_range); + } + + BKE_mesh_runtime_clear_cache(&mesh); + BKE_mesh_normals_tag_dirty(&mesh); +} + +/** + * Edges connected to one selected face are on the boundary of a region and will be duplicated into + * a "side face". Edges inside a region will be duplicated to leave any original faces unchanged. + */ +static void extrude_mesh_face_regions(MeshComponent &component, + const Field<bool> &selection_field, + const Field<float3> &offset_field, + const AttributeOutputs &attribute_outputs) +{ + Mesh &mesh = *component.get_for_write(); + const int orig_vert_size = mesh.totvert; + Span<MEdge> orig_edges = mesh_edges(mesh); + Span<MPoly> orig_polys = mesh_polys(mesh); + Span<MLoop> orig_loops = mesh_loops(mesh); + + GeometryComponentFieldContext poly_context{component, ATTR_DOMAIN_FACE}; + FieldEvaluator poly_evaluator{poly_context, mesh.totpoly}; + poly_evaluator.set_selection(selection_field); + poly_evaluator.add(offset_field); + poly_evaluator.evaluate(); + const IndexMask poly_selection = poly_evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &poly_offsets = poly_evaluator.get_evaluated<float3>(0); + if (poly_selection.is_empty()) { + return; + } + + Array<bool> poly_selection_array(orig_polys.size(), false); + for (const int i_poly : poly_selection) { + poly_selection_array[i_poly] = true; + } + + /* Mix the offsets from the face domain to the vertex domain. Evaluate on the face domain above + * in order to be consistent with the selection, and to use the face normals rather than vertex + * normals as an offset, for example. */ + Array<float3> vert_offsets; + if (!poly_offsets.is_single()) { + vert_offsets.reinitialize(orig_vert_size); + attribute_math::DefaultPropatationMixer<float3> mixer(vert_offsets); + for (const int i_poly : poly_selection) { + const MPoly &poly = orig_polys[i_poly]; + const float3 offset = poly_offsets[i_poly]; + for (const MLoop &loop : orig_loops.slice(poly.loopstart, poly.totloop)) { + mixer.mix_in(loop.v, offset); + } + } + mixer.finalize(); + } + + /* All of the faces (selected and deselected) connected to each edge. */ + const Array<Vector<int, 2>> edge_to_poly_map = mesh_calculate_polys_of_edge(mesh); + + /* All vertices that are connected to the selected polygons. + * Start the size at one vert per poly to reduce unnecessary reallocation. */ + VectorSet<int> all_selected_verts; + all_selected_verts.reserve(orig_polys.size()); + for (const int i_poly : poly_selection) { + const MPoly &poly = orig_polys[i_poly]; + for (const MLoop &loop : orig_loops.slice(poly.loopstart, poly.totloop)) { + all_selected_verts.add(loop.v); + } + } + + /* Edges inside of an extruded region that are also attached to deselected edges. They must be + * duplicated in order to leave the old edge attached to the unchanged deselected faces. */ + VectorSet<int> new_inner_edge_indices; + /* Edges inside of an extruded region. Their vertices should be translated + * with the offset, but the edges themselves should not be duplicated. */ + Vector<int> inner_edge_indices; + /* The extruded face corresponding to each boundary edge (and each boundary face). */ + Vector<int> edge_extruded_face_indices; + /* Edges on the outside of selected regions, either because there are no + * other connected faces, or because all of the other faces aren't selected. */ + VectorSet<int> boundary_edge_indices; + for (const int i_edge : orig_edges.index_range()) { + Span<int> polys = edge_to_poly_map[i_edge]; + + int i_selected_poly = -1; + int deselected_poly_count = 0; + int selected_poly_count = 0; + for (const int i_other_poly : polys) { + if (poly_selection_array[i_other_poly]) { + selected_poly_count++; + i_selected_poly = i_other_poly; + } + else { + deselected_poly_count++; + } + } + + if (selected_poly_count == 1) { + /* If there is only one selected polygon connected to the edge, + * the edge should be extruded to form a "side face". */ + boundary_edge_indices.add_new(i_edge); + edge_extruded_face_indices.append(i_selected_poly); + } + else if (selected_poly_count > 1) { + /* The edge is inside an extruded region of faces. */ + if (deselected_poly_count > 0) { + /* Add edges that are also connected to deselected edges to a separate list. */ + new_inner_edge_indices.add_new(i_edge); + } + else { + /* Otherwise, just keep track of edges inside the region so that + * we can reattach them to duplicated vertices if necessary. */ + inner_edge_indices.append(i_edge); + } + } + } + + VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices.as_span()); + /* Before adding the rest of the new vertices from the new inner edges, store the number + * of new vertices from the boundary edges, since this is the number of connecting edges. */ + const int extruded_vert_size = new_vert_indices.size(); + + /* The vertices attached to duplicate inner edges also have to be duplicated. */ + for (const int i_edge : new_inner_edge_indices) { + const MEdge &edge = mesh.medge[i_edge]; + new_vert_indices.add(edge.v1); + new_vert_indices.add(edge.v2); + } + + /* New vertices forming the duplicated boundary edges and the ends of the new inner edges. */ + const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()}; + /* One edge connects each selected vertex to a new vertex on the extruded polygons. */ + const IndexRange connect_edge_range{orig_edges.size(), extruded_vert_size}; + /* Each selected edge is duplicated to form a single edge on the extrusion. */ + const IndexRange boundary_edge_range = connect_edge_range.after(boundary_edge_indices.size()); + /* Duplicated edges inside regions that were connected to deselected faces. */ + const IndexRange new_inner_edge_range = boundary_edge_range.after(new_inner_edge_indices.size()); + /* Each edge selected for extrusion is extruded into a single face. */ + const IndexRange side_poly_range{orig_polys.size(), boundary_edge_indices.size()}; + /* The loops that form the new side faces. */ + const IndexRange side_loop_range{orig_loops.size(), side_poly_range.size() * 4}; + + expand_mesh(mesh, + new_vert_range.size(), + connect_edge_range.size() + boundary_edge_range.size() + new_inner_edge_range.size(), + side_poly_range.size(), + side_loop_range.size()); + + MutableSpan<MEdge> edges = mesh_edges(mesh); + MutableSpan<MEdge> connect_edges = edges.slice(connect_edge_range); + MutableSpan<MEdge> boundary_edges = edges.slice(boundary_edge_range); + MutableSpan<MEdge> new_inner_edges = edges.slice(new_inner_edge_range); + MutableSpan<MPoly> polys = mesh_polys(mesh); + MutableSpan<MPoly> new_polys = polys.slice(side_poly_range); + MutableSpan<MLoop> loops = mesh_loops(mesh); + MutableSpan<MLoop> new_loops = loops.slice(side_loop_range); + + /* Initialize the edges that form the sides of the extrusion. */ + for (const int i : connect_edges.index_range()) { + connect_edges[i] = new_edge(new_vert_indices[i], new_vert_range[i]); + } + + /* Initialize the edges that form the top of the extrusion. */ + for (const int i : boundary_edges.index_range()) { + const MEdge &orig_edge = edges[boundary_edge_indices[i]]; + const int i_new_vert_1 = new_vert_indices.index_of(orig_edge.v1); + const int i_new_vert_2 = new_vert_indices.index_of(orig_edge.v2); + boundary_edges[i] = new_edge(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]); + } + + /* Initialize the new edges inside of extrude regions. */ + for (const int i : new_inner_edge_indices.index_range()) { + const MEdge &orig_edge = edges[new_inner_edge_indices[i]]; + const int i_new_vert_1 = new_vert_indices.index_of(orig_edge.v1); + const int i_new_vert_2 = new_vert_indices.index_of(orig_edge.v2); + new_inner_edges[i] = new_edge(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]); + } + + /* Initialize the new side polygons. */ + for (const int i : new_polys.index_range()) { + new_polys[i] = new_poly(side_loop_range[i * 4], 4); + } + + /* Connect original edges inside face regions to any new vertices, if necessary. */ + for (const int i : inner_edge_indices) { + MEdge &edge = edges[i]; + const int i_new_vert_1 = new_vert_indices.index_of_try(edge.v1); + const int i_new_vert_2 = new_vert_indices.index_of_try(edge.v2); + if (i_new_vert_1 != -1) { + edge.v1 = new_vert_range[i_new_vert_1]; + } + if (i_new_vert_2 != -1) { + edge.v2 = new_vert_range[i_new_vert_2]; + } + } + + /* Connect the selected faces to the extruded or duplicated edges and the new vertices. */ + for (const int i_poly : poly_selection) { + const MPoly &poly = polys[i_poly]; + for (MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + const int i_new_vert = new_vert_indices.index_of_try(loop.v); + if (i_new_vert != -1) { + loop.v = new_vert_range[i_new_vert]; + } + const int i_boundary_edge = boundary_edge_indices.index_of_try(loop.e); + if (i_boundary_edge != -1) { + loop.e = boundary_edge_range[i_boundary_edge]; + /* Skip the next check, an edge cannot be both a boundary edge and an inner edge. */ + continue; + } + const int i_new_inner_edge = new_inner_edge_indices.index_of_try(loop.e); + if (i_new_inner_edge != -1) { + loop.e = new_inner_edge_range[i_new_inner_edge]; + } + } + } + + /* Create the faces on the sides of extruded regions. */ + for (const int i : boundary_edge_indices.index_range()) { + const MEdge &boundary_edge = boundary_edges[i]; + const int new_vert_1 = boundary_edge.v1; + const int new_vert_2 = boundary_edge.v2; + const int extrude_index_1 = new_vert_1 - orig_vert_size; + const int extrude_index_2 = new_vert_2 - orig_vert_size; + + const MPoly &extrude_poly = polys[edge_extruded_face_indices[i]]; + + fill_quad_consistent_direction(loops.slice(extrude_poly.loopstart, extrude_poly.totloop), + new_loops.slice(4 * i, 4), + new_vert_1, + new_vert_2, + new_vert_indices[extrude_index_1], + new_vert_indices[extrude_index_2], + boundary_edge_range[i], + connect_edge_range[extrude_index_1], + boundary_edge_indices[i], + connect_edge_range[extrude_index_2]); + } + + /* Create a map of indices in the extruded vertices array to all of the indices of edges + * in the duplicate edges array that connect to that vertex. This can be used to simplify the + * mixing of attribute data for the connecting edges. */ + const Array<Vector<int>> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( + new_vert_range.size(), boundary_edges, orig_vert_size); + + component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + OutputAttribute attribute = component.attribute_try_get_for_output( + id, meta_data.domain, meta_data.data_type); + if (!attribute) { + return true; /* Impossible to write the "normal" attribute. */ + } + + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan<T> data = attribute.as_span().typed<T>(); + switch (attribute.domain()) { + case ATTR_DOMAIN_POINT: { + /* New vertices copy the attributes from their original vertices. */ + copy_with_indices(data.slice(new_vert_range), data.as_span(), new_vert_indices); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Edges parallel to original edges copy the edge attributes from the original edges. */ + MutableSpan<T> boundary_data = data.slice(boundary_edge_range); + copy_with_indices(boundary_data, data.as_span(), boundary_edge_indices); + + /* Edges inside of face regions also just duplicate their source data. */ + MutableSpan<T> new_inner_data = data.slice(new_inner_edge_range); + copy_with_indices(new_inner_data, data.as_span(), new_inner_edge_indices); + + /* Edges connected to original vertices mix values of selected connected edges. */ + MutableSpan<T> connect_data = data.slice(connect_edge_range); + copy_with_mixing(connect_data, boundary_data.as_span(), [&](const int i) { + return new_vert_to_duplicate_edge_map[i].as_span(); + }); + break; + } + case ATTR_DOMAIN_FACE: { + /* New faces on the side of extrusions get the values from the corresponding selected + * face. */ + copy_with_indices( + data.slice(side_poly_range), data.as_span(), edge_extruded_face_indices); + break; + } + case ATTR_DOMAIN_CORNER: { + /* New corners get the values from the corresponding corner on the extruded face. */ + MutableSpan<T> new_data = data.slice(side_loop_range); + threading::parallel_for( + boundary_edge_indices.index_range(), 256, [&](const IndexRange range) { + for (const int i_boundary_edge : range) { + const MPoly &poly = polys[edge_extruded_face_indices[i_boundary_edge]]; + + const MEdge &boundary_edge = boundary_edges[i_boundary_edge]; + const int new_vert_1 = boundary_edge.v1; + const int new_vert_2 = boundary_edge.v2; + const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size]; + const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size]; + + /* Retrieve the data for the first two sides of the quad from the extruded + * polygon, which we generally expect to have just a small amount of sides. This + * loop could be eliminated by adding a cache of connected loops (which would + * also simplify some of the other code to find the correct loops on the extruded + * face). */ + T data_1; + T data_2; + for (const int i_loop : IndexRange(poly.loopstart, poly.totloop)) { + if (loops[i_loop].v == new_vert_1) { + data_1 = data[i_loop]; + } + if (loops[i_loop].v == new_vert_2) { + data_2 = data[i_loop]; + } + } + + /* Instead of replicating the order in #fill_quad_consistent_direction here, it's + * simpler (though probably slower) to just match the corner data based on the + * vertex indices. */ + for (const int i : IndexRange(4 * i_boundary_edge, 4)) { + if (ELEM(new_loops[i].v, new_vert_1, orig_vert_1)) { + new_data[i] = data_1; + } + else if (ELEM(new_loops[i].v, new_vert_2, orig_vert_2)) { + new_data[i] = data_2; + } + } + } + }); + break; + } + default: + BLI_assert_unreachable(); + } + }); + + attribute.save(); + return true; + }); + + /* Translate vertices based on the offset. If the vertex is used by a selected edge, it will + * have been duplicated and only the new vertex should use the offset. Otherwise the vertex might + * still need an offset, but it was reused on the inside of a region of extruded faces. */ + if (poly_offsets.is_single()) { + const float3 offset = poly_offsets.get_internal_single(); + threading::parallel_for( + IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) { + for (const int i_orig : all_selected_verts.as_span().slice(range)) { + const int i_new = new_vert_indices.index_of_try(i_orig); + MVert &vert = mesh_verts(mesh)[(i_new == -1) ? i_orig : new_vert_range[i_new]]; + add_v3_v3(vert.co, offset); + } + }); + } + else { + threading::parallel_for( + IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) { + for (const int i_orig : all_selected_verts.as_span().slice(range)) { + const int i_new = new_vert_indices.index_of_try(i_orig); + const float3 offset = vert_offsets[i_orig]; + MVert &vert = mesh_verts(mesh)[(i_new == -1) ? i_orig : new_vert_range[i_new]]; + add_v3_v3(vert.co, offset); + } + }); + } + + if (attribute_outputs.top_id) { + save_selection_as_attribute( + component, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, poly_selection); + } + if (attribute_outputs.side_id) { + save_selection_as_attribute( + component, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_poly_range); + } + + BKE_mesh_runtime_clear_cache(&mesh); + BKE_mesh_normals_tag_dirty(&mesh); +} + +/* Get the range into an array of extruded corners, edges, or vertices for a particular polygon. */ +static IndexRange selected_corner_range(Span<int> offsets, const int index) +{ + const int offset = offsets[index]; + const int next_offset = offsets[index + 1]; + return IndexRange(offset, next_offset - offset); +} + +static void extrude_individual_mesh_faces(MeshComponent &component, + const Field<bool> &selection_field, + const Field<float3> &offset_field, + const AttributeOutputs &attribute_outputs) +{ + Mesh &mesh = *component.get_for_write(); + const int orig_vert_size = mesh.totvert; + const int orig_edge_size = mesh.totedge; + Span<MPoly> orig_polys = mesh_polys(mesh); + Span<MLoop> orig_loops = mesh_loops(mesh); + + /* Use a mesh for the result of the evaluation because the mesh is reallocated before + * the vertices are moved, and the evaluated result might reference an attribute. */ + Array<float3> poly_offset(orig_polys.size()); + GeometryComponentFieldContext poly_context{component, ATTR_DOMAIN_FACE}; + FieldEvaluator poly_evaluator{poly_context, mesh.totpoly}; + poly_evaluator.set_selection(selection_field); + poly_evaluator.add_with_destination(offset_field, poly_offset.as_mutable_span()); + poly_evaluator.evaluate(); + const IndexMask poly_selection = poly_evaluator.get_evaluated_selection_as_mask(); + + /* Build an array of offsets into the new data for each polygon. This is used to facilitate + * parallelism later on by avoiding the need to keep track of an offset when iterating through + * all polygons. */ + int extrude_corner_size = 0; + Array<int> index_offsets(poly_selection.size() + 1); + for (const int i_selection : poly_selection.index_range()) { + const MPoly &poly = orig_polys[poly_selection[i_selection]]; + index_offsets[i_selection] = extrude_corner_size; + extrude_corner_size += poly.totloop; + } + index_offsets.last() = extrude_corner_size; + + const IndexRange new_vert_range{orig_vert_size, extrude_corner_size}; + /* One edge connects each selected vertex to a new vertex on the extruded polygons. */ + const IndexRange connect_edge_range{orig_edge_size, extrude_corner_size}; + /* Each selected edge is duplicated to form a single edge on the extrusion. */ + const IndexRange duplicate_edge_range = connect_edge_range.after(extrude_corner_size); + /* Each edge selected for extrusion is extruded into a single face. */ + const IndexRange side_poly_range{orig_polys.size(), duplicate_edge_range.size()}; + const IndexRange side_loop_range{orig_loops.size(), side_poly_range.size() * 4}; + + expand_mesh(mesh, + new_vert_range.size(), + connect_edge_range.size() + duplicate_edge_range.size(), + side_poly_range.size(), + side_loop_range.size()); + + MutableSpan<MVert> new_verts = mesh_verts(mesh).slice(new_vert_range); + MutableSpan<MEdge> edges{mesh.medge, mesh.totedge}; + MutableSpan<MEdge> connect_edges = edges.slice(connect_edge_range); + MutableSpan<MEdge> duplicate_edges = edges.slice(duplicate_edge_range); + MutableSpan<MPoly> polys{mesh.mpoly, mesh.totpoly}; + MutableSpan<MPoly> new_polys = polys.slice(side_poly_range); + MutableSpan<MLoop> loops{mesh.mloop, mesh.totloop}; + + /* For every selected polygon, build the faces that form the sides of the extrusion. Filling some + * of this data like the new edges or polygons could be easily split into separate loops, which + * may or may not be faster, and would involve more duplication. */ + threading::parallel_for(poly_selection.index_range(), 256, [&](const IndexRange range) { + for (const int i_selection : range) { + const IndexRange poly_corner_range = selected_corner_range(index_offsets, i_selection); + + const MPoly &poly = polys[poly_selection[i_selection]]; + Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); + + for (const int i : IndexRange(poly.totloop)) { + const int i_next = (i == poly.totloop - 1) ? 0 : i + 1; + const MLoop &orig_loop = poly_loops[i]; + const MLoop &orig_loop_next = poly_loops[i_next]; + + const int i_extrude = poly_corner_range[i]; + const int i_extrude_next = poly_corner_range[i_next]; + + const int i_duplicate_edge = duplicate_edge_range[i_extrude]; + const int new_vert = new_vert_range[i_extrude]; + const int new_vert_next = new_vert_range[i_extrude_next]; + + const int orig_edge = orig_loop.e; + + const int orig_vert = orig_loop.v; + const int orig_vert_next = orig_loop_next.v; + + duplicate_edges[i_extrude] = new_edge(new_vert, new_vert_next); + + new_polys[i_extrude] = new_poly(side_loop_range[i_extrude * 4], 4); + + MutableSpan<MLoop> side_loops = loops.slice(side_loop_range[i_extrude * 4], 4); + side_loops[0].v = new_vert_next; + side_loops[0].e = i_duplicate_edge; + side_loops[1].v = new_vert; + side_loops[1].e = connect_edge_range[i_extrude]; + side_loops[2].v = orig_vert; + side_loops[2].e = orig_edge; + side_loops[3].v = orig_vert_next; + side_loops[3].e = connect_edge_range[i_extrude_next]; + + connect_edges[i_extrude] = new_edge(orig_vert, new_vert); + } + } + }); + + component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + OutputAttribute attribute = component.attribute_try_get_for_output( + id, meta_data.domain, meta_data.data_type); + if (!attribute) { + return true; /* Impossible to write the "normal" attribute. */ + } + + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan<T> data = attribute.as_span().typed<T>(); + switch (attribute.domain()) { + case ATTR_DOMAIN_POINT: { + /* New vertices copy the attributes from their original vertices. */ + MutableSpan<T> new_data = data.slice(new_vert_range); + + threading::parallel_for(poly_selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i_selection : range) { + const MPoly &poly = polys[poly_selection[i_selection]]; + Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); + + const int corner_offset = index_offsets[i_selection]; + for (const int i : poly_loops.index_range()) { + const int orig_index = poly_loops[i].v; + new_data[corner_offset + i] = data[orig_index]; + } + } + }); + break; + } + case ATTR_DOMAIN_EDGE: { + MutableSpan<T> duplicate_data = data.slice(duplicate_edge_range); + MutableSpan<T> connect_data = data.slice(connect_edge_range); + + threading::parallel_for(poly_selection.index_range(), 512, [&](const IndexRange range) { + for (const int i_selection : range) { + const MPoly &poly = polys[poly_selection[i_selection]]; + Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); + + const IndexRange poly_corner_range = selected_corner_range(index_offsets, + i_selection); + + /* The data for the duplicate edge is simply a copy of the original edge's data. */ + for (const int i : poly_loops.index_range()) { + const int orig_index = poly_loops[i].e; + duplicate_data[poly_corner_range[i]] = data[orig_index]; + } + + /* For the extruded edges, mix the data from the two neighboring original edges of + * the extruded polygon. */ + for (const int i : poly_loops.index_range()) { + const int i_loop_prev = (i == 0) ? poly.totloop - 1 : i - 1; + const int orig_index = poly_loops[i].e; + const int orig_index_prev = poly_loops[i_loop_prev].e; + if constexpr (std::is_same_v<T, bool>) { + /* Propagate selections with "or" instead of "at least half". */ + connect_data[poly_corner_range[i]] = data[orig_index] || data[orig_index_prev]; + } + else { + connect_data[poly_corner_range[i]] = attribute_math::mix2( + 0.5f, data[orig_index], data[orig_index_prev]); + } + } + } + }); + break; + } + case ATTR_DOMAIN_FACE: { + /* Each side face gets the values from the corresponding new face. */ + MutableSpan<T> new_data = data.slice(side_poly_range); + threading::parallel_for(poly_selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i_selection : range) { + const int poly_index = poly_selection[i_selection]; + const IndexRange poly_corner_range = selected_corner_range(index_offsets, + i_selection); + new_data.slice(poly_corner_range).fill(data[poly_index]); + } + }); + break; + } + case ATTR_DOMAIN_CORNER: { + /* Each corner on a side face gets its value from the matching corner on an extruded + * face. */ + MutableSpan<T> new_data = data.slice(side_loop_range); + threading::parallel_for(poly_selection.index_range(), 256, [&](const IndexRange range) { + for (const int i_selection : range) { + const MPoly &poly = polys[poly_selection[i_selection]]; + Span<T> poly_loop_data = data.slice(poly.loopstart, poly.totloop); + const IndexRange poly_corner_range = selected_corner_range(index_offsets, + i_selection); + + for (const int i : IndexRange(poly.totloop)) { + const int i_next = (i == poly.totloop - 1) ? 0 : i + 1; + const int i_extrude = poly_corner_range[i]; + + MutableSpan<T> side_loop_data = new_data.slice(i_extrude * 4, 4); + + /* The two corners on each side of the side polygon get the data from the matching + * corners of the extruded polygon. This order depends on the loop filling the loop + * indices. */ + side_loop_data[0] = poly_loop_data[i_next]; + side_loop_data[1] = poly_loop_data[i]; + side_loop_data[2] = poly_loop_data[i]; + side_loop_data[3] = poly_loop_data[i_next]; + } + } + }); + break; + } + default: + BLI_assert_unreachable(); + } + }); + + attribute.save(); + return true; + }); + + /* Offset the new vertices. */ + threading::parallel_for(poly_selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i_selection : range) { + const IndexRange poly_corner_range = selected_corner_range(index_offsets, i_selection); + for (MVert &vert : new_verts.slice(poly_corner_range)) { + add_v3_v3(vert.co, poly_offset[poly_selection[i_selection]]); + } + } + }); + + /* Finally update each extruded polygon's loops to point to the new edges and vertices. + * This must be done last, because they were used to find original indices for attribute + * interpolation before. Alternatively an original index array could be built for each domain. */ + threading::parallel_for(poly_selection.index_range(), 256, [&](const IndexRange range) { + for (const int i_selection : range) { + const IndexRange poly_corner_range = selected_corner_range(index_offsets, i_selection); + + const MPoly &poly = polys[poly_selection[i_selection]]; + MutableSpan<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); + + for (const int i : IndexRange(poly.totloop)) { + MLoop &loop = poly_loops[i]; + loop.v = new_vert_range[poly_corner_range[i]]; + loop.e = duplicate_edge_range[poly_corner_range[i]]; + } + } + }); + + if (attribute_outputs.top_id) { + save_selection_as_attribute( + component, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, poly_selection); + } + if (attribute_outputs.side_id) { + save_selection_as_attribute( + component, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_poly_range); + } + + BKE_mesh_runtime_clear_cache(&mesh); + BKE_mesh_normals_tag_dirty(&mesh); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + Field<bool> selection = params.extract_input<Field<bool>>("Selection"); + Field<float3> offset_field = params.extract_input<Field<float3>>("Offset"); + Field<float> scale_field = params.extract_input<Field<float>>("Offset Scale"); + const NodeGeometryExtrudeMesh &storage = node_storage(params.node()); + GeometryNodeExtrudeMeshMode mode = static_cast<GeometryNodeExtrudeMeshMode>(storage.mode); + + /* Create a combined field from the offset and the scale so the field evaluator + * can take care of the multiplication and to simplify each extrude function. */ + static fn::CustomMF_SI_SI_SO<float3, float, float3> multiply_fn{ + "Scale", [](const float3 &offset, const float scale) { return offset * scale; }}; + std::shared_ptr<FieldOperation> multiply_op = std::make_shared<FieldOperation>( + FieldOperation(multiply_fn, {std::move(offset_field), std::move(scale_field)})); + const Field<float3> final_offset{std::move(multiply_op)}; + + AttributeOutputs attribute_outputs; + if (params.output_is_required("Top")) { + attribute_outputs.top_id = StrongAnonymousAttributeID("Top"); + } + if (params.output_is_required("Side")) { + attribute_outputs.side_id = StrongAnonymousAttributeID("Side"); + } + + const bool extrude_individual = mode == GEO_NODE_EXTRUDE_MESH_FACES && + params.extract_input<bool>("Individual"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_mesh()) { + MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>(); + switch (mode) { + case GEO_NODE_EXTRUDE_MESH_VERTICES: + extrude_mesh_vertices(component, selection, final_offset, attribute_outputs); + break; + case GEO_NODE_EXTRUDE_MESH_EDGES: + extrude_mesh_edges(component, selection, final_offset, attribute_outputs); + break; + case GEO_NODE_EXTRUDE_MESH_FACES: { + if (extrude_individual) { + extrude_individual_mesh_faces(component, selection, final_offset, attribute_outputs); + } + else { + extrude_mesh_face_regions(component, selection, final_offset, attribute_outputs); + } + break; + } + } + + BLI_assert(BKE_mesh_is_valid(component.get_for_write())); + } + }); + + params.set_output("Mesh", std::move(geometry_set)); + if (attribute_outputs.top_id) { + params.set_output("Top", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.top_id), params.attribute_producer_name())); + } + if (attribute_outputs.side_id) { + params.set_output("Side", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.side_id), params.attribute_producer_name())); + } +} + +} // namespace blender::nodes::node_geo_extrude_mesh_cc + +void register_node_type_geo_extrude_mesh() +{ + namespace file_ns = blender::nodes::node_geo_extrude_mesh_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_EXTRUDE_MESH, "Extrude Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_storage( + &ntype, "NodeGeometryExtrudeMesh", node_free_standard_storage, node_copy_standard_storage); + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc new file mode 100644 index 00000000000..9512323834c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc @@ -0,0 +1,193 @@ +/* + * 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 "BKE_attribute_math.hh" + +#include "BLI_task.hh" + +namespace blender::nodes::node_geo_field_at_index_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Index")).min(0).supports_field(); + + b.add_input<decl::Float>(N_("Value"), "Value_Float").supports_field(); + b.add_input<decl::Int>(N_("Value"), "Value_Int").supports_field(); + b.add_input<decl::Vector>(N_("Value"), "Value_Vector").supports_field(); + b.add_input<decl::Color>(N_("Value"), "Value_Color").supports_field(); + b.add_input<decl::Bool>(N_("Value"), "Value_Bool").supports_field(); + + b.add_output<decl::Float>(N_("Value"), "Value_Float").field_source(); + b.add_output<decl::Int>(N_("Value"), "Value_Int").field_source(); + b.add_output<decl::Vector>(N_("Value"), "Value_Vector").field_source(); + b.add_output<decl::Color>(N_("Value"), "Value_Color").field_source(); + b.add_output<decl::Bool>(N_("Value"), "Value_Bool").field_source(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = ATTR_DOMAIN_POINT; + node->custom2 = CD_PROP_FLOAT; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const CustomDataType data_type = static_cast<CustomDataType>(node->custom2); + + bNodeSocket *sock_index = static_cast<bNodeSocket *>(node->inputs.first); + bNodeSocket *sock_in_float = sock_index->next; + bNodeSocket *sock_in_int = sock_in_float->next; + bNodeSocket *sock_in_vector = sock_in_int->next; + bNodeSocket *sock_in_color = sock_in_vector->next; + bNodeSocket *sock_in_bool = sock_in_color->next; + + bNodeSocket *sock_out_float = static_cast<bNodeSocket *>(node->outputs.first); + bNodeSocket *sock_out_int = sock_out_float->next; + bNodeSocket *sock_out_vector = sock_out_int->next; + bNodeSocket *sock_out_color = sock_out_vector->next; + bNodeSocket *sock_out_bool = sock_out_color->next; + + nodeSetSocketAvailability(ntree, sock_in_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_in_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, sock_in_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_in_color, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, sock_in_bool, data_type == CD_PROP_BOOL); + + nodeSetSocketAvailability(ntree, sock_out_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, sock_out_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(ntree, sock_out_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, sock_out_color, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, sock_out_bool, data_type == CD_PROP_BOOL); +} + +class FieldAtIndex final : public GeometryFieldInput { + private: + Field<int> index_field_; + GField value_field_; + AttributeDomain value_field_domain_; + + public: + FieldAtIndex(Field<int> index_field, GField value_field, AttributeDomain value_field_domain) + : GeometryFieldInput(value_field.cpp_type(), "Field at Index"), + index_field_(std::move(index_field)), + value_field_(std::move(value_field)), + value_field_domain_(value_field_domain) + { + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + const GeometryComponentFieldContext value_field_context{component, value_field_domain_}; + FieldEvaluator value_evaluator{value_field_context, + component.attribute_domain_size(value_field_domain_)}; + value_evaluator.add(value_field_); + value_evaluator.evaluate(); + const GVArray &values = value_evaluator.get_evaluated(0); + + const GeometryComponentFieldContext index_field_context{component, domain}; + FieldEvaluator index_evaluator{index_field_context, &mask}; + index_evaluator.add(index_field_); + index_evaluator.evaluate(); + const VArray<int> &indices = index_evaluator.get_evaluated<int>(0); + + GVArray output_array; + attribute_math::convert_to_static_type(*type_, [&](auto dummy) { + using T = decltype(dummy); + Array<T> dst_array(mask.min_array_size()); + VArray<T> src_values = values.typed<T>(); + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + for (const int i : mask.slice(range)) { + const int index = indices[i]; + if (index >= 0 && index < src_values.size()) { + dst_array[i] = src_values[index]; + } + else { + dst_array[i] = {}; + } + } + }); + output_array = VArray<T>::ForContainer(std::move(dst_array)); + }); + + return output_array; + } +}; + +static StringRefNull identifier_suffix(CustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_BOOL: + return "Bool"; + case CD_PROP_FLOAT: + return "Float"; + case CD_PROP_INT32: + return "Int"; + case CD_PROP_COLOR: + return "Color"; + case CD_PROP_FLOAT3: + return "Vector"; + default: + BLI_assert_unreachable(); + return ""; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const bNode &node = params.node(); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom1); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom2); + + Field<int> index_field = params.extract_input<Field<int>>("Index"); + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + static const std::string identifier = "Value_" + identifier_suffix(data_type); + Field<T> value_field = params.extract_input<Field<T>>(identifier); + Field<T> output_field{ + std::make_shared<FieldAtIndex>(std::move(index_field), std::move(value_field), domain)}; + params.set_output(identifier, std::move(output_field)); + }); +} + +} // namespace blender::nodes::node_geo_field_at_index_cc + +void register_node_type_geo_field_at_index() +{ + namespace file_ns = blender::nodes::node_geo_field_at_index_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_FIELD_AT_INDEX, "Field at Index", NODE_CLASS_CONVERTER); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + ntype.initfunc = file_ns::node_init; + ntype.updatefunc = file_ns::node_update; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc new file mode 100644 index 00000000000..41970d75dfe --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc @@ -0,0 +1,114 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_math.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_flip_faces_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Mesh")); +} + +static void mesh_flip_faces(MeshComponent &component, const Field<bool> &selection_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { + return; + } + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(selection_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + + Mesh *mesh = component.get_for_write(); + + mesh->mloop = (MLoop *)CustomData_duplicate_referenced_layer( + &mesh->ldata, CD_MLOOP, mesh->totloop); + Span<MPoly> polys{mesh->mpoly, mesh->totpoly}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + + for (const int i : selection.index_range()) { + const MPoly &poly = polys[selection[i]]; + int start = poly.loopstart; + for (const int j : IndexRange(poly.totloop / 2)) { + const int index1 = start + j + 1; + const int index2 = start + poly.totloop - j - 1; + std::swap(loops[index1].v, loops[index2].v); + std::swap(loops[index1 - 1].e, loops[index2].e); + } + } + + component.attribute_foreach( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (meta_data.domain == ATTR_DOMAIN_CORNER) { + OutputAttribute attribute = component.attribute_try_get_for_output( + attribute_id, ATTR_DOMAIN_CORNER, meta_data.data_type, nullptr); + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan<T> dst_span = attribute.as_span<T>(); + for (const int j : selection.index_range()) { + const MPoly &poly = polys[selection[j]]; + dst_span.slice(poly.loopstart + 1, poly.totloop - 1).reverse(); + } + }); + attribute.save(); + } + return true; + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_mesh()) { + return; + } + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + mesh_flip_faces(mesh_component, selection_field); + }); + + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_flip_faces_cc + +void register_node_type_geo_flip_faces() +{ + namespace file_ns = blender::nodes::node_geo_flip_faces_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_FLIP_FACES, "Flip Faces", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc new file mode 100644 index 00000000000..f65af5b6737 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc @@ -0,0 +1,55 @@ +/* + * 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" + +namespace blender::nodes::node_geo_geometry_to_instance_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).multi_input(); + b.add_output<decl::Geometry>(N_("Instances")); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Vector<GeometrySet> geometries = params.extract_multi_input<GeometrySet>("Geometry"); + GeometrySet instances_geometry; + InstancesComponent &instances_component = + instances_geometry.get_component_for_write<InstancesComponent>(); + for (GeometrySet &geometry : geometries) { + geometry.ensure_owns_direct_data(); + const int handle = instances_component.add_reference(std::move(geometry)); + instances_component.add_instance(handle, float4x4::identity()); + } + params.set_output("Instances", std::move(instances_geometry)); +} + +} // namespace blender::nodes::node_geo_geometry_to_instance_cc + +void register_node_type_geo_geometry_to_instance() +{ + namespace file_ns = blender::nodes::node_geo_geometry_to_instance_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_GEOMETRY_TO_INSTANCE, "Geometry to Instance", NODE_CLASS_GEOMETRY); + node_type_size(&ntype, 160, 100, 300); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc new file mode 100644 index 00000000000..624a8b6b0f6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc @@ -0,0 +1,428 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2005 Blender Foundation. + * All rights reserved. + */ + +#include "node_geometry_util.hh" + +#include "BKE_image.h" + +#include "BLI_math_vec_types.hh" +#include "BLI_threads.h" +#include "BLI_timeit.hh" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes::node_geo_image_texture_cc { + +NODE_STORAGE_FUNCS(NodeGeometryImageTexture) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Image>(N_("Image")).hide_label(); + b.add_input<decl::Vector>(N_("Vector")) + .implicit_field() + .description(("Texture coordinates from 0 to 1")); + b.add_input<decl::Int>(N_("Frame")).min(0).max(MAXFRAMEF); + b.add_output<decl::Color>(N_("Color")).no_muted_links().dependent_field(); + b.add_output<decl::Float>(N_("Alpha")).no_muted_links().dependent_field(); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "interpolation", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + uiItemR(layout, ptr, "extension", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryImageTexture *tex = MEM_cnew<NodeGeometryImageTexture>(__func__); + node->storage = tex; +} + +class ImageFieldsFunction : public fn::MultiFunction { + private: + const int interpolation_; + const int extension_; + Image &image_; + ImageUser image_user_; + void *image_lock_; + ImBuf *image_buffer_; + + public: + ImageFieldsFunction(const int interpolation, + const int extension, + Image &image, + ImageUser image_user) + : interpolation_(interpolation), + extension_(extension), + image_(image), + image_user_(image_user) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + + image_buffer_ = BKE_image_acquire_ibuf(&image_, &image_user_, &image_lock_); + if (image_buffer_ == nullptr) { + throw std::runtime_error("cannot acquire image buffer"); + } + + if (image_buffer_->rect_float == nullptr) { + BLI_thread_lock(LOCK_IMAGE); + if (!image_buffer_->rect_float) { + IMB_float_from_rect(image_buffer_); + } + BLI_thread_unlock(LOCK_IMAGE); + } + + if (image_buffer_->rect_float == nullptr) { + BKE_image_release_ibuf(&image_, image_buffer_, image_lock_); + throw std::runtime_error("cannot get float buffer"); + } + } + + ~ImageFieldsFunction() override + { + BKE_image_release_ibuf(&image_, image_buffer_, image_lock_); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"ImageFunction"}; + signature.single_input<float3>("Vector"); + signature.single_output<ColorGeometry4f>("Color"); + signature.single_output<float>("Alpha"); + return signature.build(); + } + + static int wrap_periodic(int x, const int width) + { + x %= width; + if (x < 0) { + x += width; + } + return x; + } + + static int wrap_clamp(const int x, const int width) + { + return std::clamp(x, 0, width - 1); + } + + static float4 image_pixel_lookup(const ImBuf *ibuf, const int px, const int py) + { + if (px < 0 || py < 0 || px >= ibuf->x || py >= ibuf->y) { + return float4(0.0f, 0.0f, 0.0f, 0.0f); + } + return ((const float4 *)ibuf->rect_float)[px + py * ibuf->x]; + } + + static float frac(const float x, int *ix) + { + const int i = (int)x - ((x < 0.0f) ? 1 : 0); + *ix = i; + return x - (float)i; + } + + static float4 image_cubic_texture_lookup(const ImBuf *ibuf, + const float px, + const float py, + const int extension) + { + const int width = ibuf->x; + const int height = ibuf->y; + int pix, piy, nix, niy; + const float tx = frac(px * (float)width - 0.5f, &pix); + const float ty = frac(py * (float)height - 0.5f, &piy); + int ppix, ppiy, nnix, nniy; + + switch (extension) { + case SHD_IMAGE_EXTENSION_REPEAT: { + pix = wrap_periodic(pix, width); + piy = wrap_periodic(piy, height); + ppix = wrap_periodic(pix - 1, width); + ppiy = wrap_periodic(piy - 1, height); + nix = wrap_periodic(pix + 1, width); + niy = wrap_periodic(piy + 1, height); + nnix = wrap_periodic(pix + 2, width); + nniy = wrap_periodic(piy + 2, height); + break; + } + case SHD_IMAGE_EXTENSION_CLIP: { + ppix = pix - 1; + ppiy = piy - 1; + nix = pix + 1; + niy = piy + 1; + nnix = pix + 2; + nniy = piy + 2; + break; + } + case SHD_IMAGE_EXTENSION_EXTEND: { + ppix = wrap_clamp(pix - 1, width); + ppiy = wrap_clamp(piy - 1, height); + nix = wrap_clamp(pix + 1, width); + niy = wrap_clamp(piy + 1, height); + nnix = wrap_clamp(pix + 2, width); + nniy = wrap_clamp(piy + 2, height); + pix = wrap_clamp(pix, width); + piy = wrap_clamp(piy, height); + break; + } + default: + return float4(0.0f, 0.0f, 0.0f, 0.0f); + } + + const int xc[4] = {ppix, pix, nix, nnix}; + const int yc[4] = {ppiy, piy, niy, nniy}; + float u[4], v[4]; + + u[0] = (((-1.0f / 6.0f) * tx + 0.5f) * tx - 0.5f) * tx + (1.0f / 6.0f); + u[1] = ((0.5f * tx - 1.0f) * tx) * tx + (2.0f / 3.0f); + u[2] = ((-0.5f * tx + 0.5f) * tx + 0.5f) * tx + (1.0f / 6.0f); + u[3] = (1.0f / 6.0f) * tx * tx * tx; + + v[0] = (((-1.0f / 6.0f) * ty + 0.5f) * ty - 0.5f) * ty + (1.0f / 6.0f); + v[1] = ((0.5f * ty - 1.0f) * ty) * ty + (2.0f / 3.0f); + v[2] = ((-0.5f * ty + 0.5f) * ty + 0.5f) * ty + (1.0f / 6.0f); + v[3] = (1.0f / 6.0f) * ty * ty * ty; + + return (v[0] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[0])) + + u[1] * (image_pixel_lookup(ibuf, xc[1], yc[0])) + + u[2] * (image_pixel_lookup(ibuf, xc[2], yc[0])) + + u[3] * (image_pixel_lookup(ibuf, xc[3], yc[0])))) + + (v[1] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[1])) + + u[1] * (image_pixel_lookup(ibuf, xc[1], yc[1])) + + u[2] * (image_pixel_lookup(ibuf, xc[2], yc[1])) + + u[3] * (image_pixel_lookup(ibuf, xc[3], yc[1])))) + + (v[2] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[2])) + + u[1] * (image_pixel_lookup(ibuf, xc[1], yc[2])) + + u[2] * (image_pixel_lookup(ibuf, xc[2], yc[2])) + + u[3] * (image_pixel_lookup(ibuf, xc[3], yc[2])))) + + (v[3] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[3])) + + u[1] * (image_pixel_lookup(ibuf, xc[1], yc[3])) + + u[2] * (image_pixel_lookup(ibuf, xc[2], yc[3])) + + u[3] * (image_pixel_lookup(ibuf, xc[3], yc[3])))); + } + + static float4 image_linear_texture_lookup(const ImBuf *ibuf, + const float px, + const float py, + const int extension) + { + const int width = ibuf->x; + const int height = ibuf->y; + int pix, piy, nix, niy; + const float nfx = frac(px * (float)width - 0.5f, &pix); + const float nfy = frac(py * (float)height - 0.5f, &piy); + + switch (extension) { + case SHD_IMAGE_EXTENSION_CLIP: { + nix = pix + 1; + niy = piy + 1; + break; + } + case SHD_IMAGE_EXTENSION_EXTEND: { + nix = wrap_clamp(pix + 1, width); + niy = wrap_clamp(piy + 1, height); + pix = wrap_clamp(pix, width); + piy = wrap_clamp(piy, height); + break; + } + default: + case SHD_IMAGE_EXTENSION_REPEAT: + pix = wrap_periodic(pix, width); + piy = wrap_periodic(piy, height); + nix = wrap_periodic(pix + 1, width); + niy = wrap_periodic(piy + 1, height); + break; + } + + const float ptx = 1.0f - nfx; + const float pty = 1.0f - nfy; + + return image_pixel_lookup(ibuf, pix, piy) * ptx * pty + + image_pixel_lookup(ibuf, nix, piy) * nfx * pty + + image_pixel_lookup(ibuf, pix, niy) * ptx * nfy + + image_pixel_lookup(ibuf, nix, niy) * nfx * nfy; + } + + static float4 image_closest_texture_lookup(const ImBuf *ibuf, + const float px, + const float py, + const int extension) + { + const int width = ibuf->x; + const int height = ibuf->y; + int ix, iy; + const float tx = frac(px * (float)width - 0.5f, &ix); + const float ty = frac(py * (float)height - 0.5f, &iy); + + switch (extension) { + case SHD_IMAGE_EXTENSION_REPEAT: { + ix = wrap_periodic(ix, width); + iy = wrap_periodic(iy, height); + return image_pixel_lookup(ibuf, ix, iy); + } + case SHD_IMAGE_EXTENSION_CLIP: { + if (tx < 0.0f || ty < 0.0f || tx > 1.0f || ty > 1.0f) { + return float4(0.0f, 0.0f, 0.0f, 0.0f); + } + if (ix < 0 || iy < 0 || ix > width || iy > height) { + return float4(0.0f, 0.0f, 0.0f, 0.0f); + } + ATTR_FALLTHROUGH; + } + case SHD_IMAGE_EXTENSION_EXTEND: { + ix = wrap_clamp(ix, width); + iy = wrap_clamp(iy, height); + return image_pixel_lookup(ibuf, ix, iy); + } + default: + return float4(0.0f, 0.0f, 0.0f, 0.0f); + } + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &vectors = params.readonly_single_input<float3>(0, "Vector"); + MutableSpan<ColorGeometry4f> r_color = params.uninitialized_single_output<ColorGeometry4f>( + 1, "Color"); + MutableSpan<float> r_alpha = params.uninitialized_single_output_if_required<float>(2, "Alpha"); + + MutableSpan<float4> color_data{(float4 *)r_color.data(), r_color.size()}; + + /* Sample image texture. */ + switch (interpolation_) { + case SHD_INTERP_LINEAR: + for (const int64_t i : mask) { + const float3 p = vectors[i]; + color_data[i] = image_linear_texture_lookup(image_buffer_, p.x, p.y, extension_); + } + break; + case SHD_INTERP_CLOSEST: + for (const int64_t i : mask) { + const float3 p = vectors[i]; + color_data[i] = image_closest_texture_lookup(image_buffer_, p.x, p.y, extension_); + } + break; + case SHD_INTERP_CUBIC: + case SHD_INTERP_SMART: + for (const int64_t i : mask) { + const float3 p = vectors[i]; + color_data[i] = image_cubic_texture_lookup(image_buffer_, p.x, p.y, extension_); + } + break; + } + + int alpha_mode = image_.alpha_mode; + if (IMB_colormanagement_space_name_is_data(image_.colorspace_settings.name)) { + alpha_mode = IMA_ALPHA_CHANNEL_PACKED; + } + + switch (alpha_mode) { + case IMA_ALPHA_STRAIGHT: { + /* #ColorGeometry expects premultiplied alpha, so convert from straight to that. */ + for (int64_t i : mask) { + straight_to_premul_v4(color_data[i]); + } + break; + } + case IMA_ALPHA_PREMUL: { + /* Alpha is premultiplied already, nothing to do. */ + break; + } + case IMA_ALPHA_CHANNEL_PACKED: { + /* Color and alpha channels shouldn't interact with each other, nothing to do. */ + break; + } + case IMA_ALPHA_IGNORE: { + /* The image should be treated as being opaque. */ + for (int64_t i : mask) { + color_data[i].w = 1.0f; + } + break; + } + } + + if (!r_alpha.is_empty()) { + for (int64_t i : mask) { + r_alpha[i] = r_color[i].a; + } + } + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Image *image = params.get_input<Image *>("Image"); + if (image == nullptr) { + params.set_default_remaining_outputs(); + return; + } + + const NodeGeometryImageTexture &storage = node_storage(params.node()); + + ImageUser image_user; + BKE_imageuser_default(&image_user); + image_user.cycl = false; + image_user.frames = INT_MAX; + image_user.sfra = 1; + image_user.framenr = BKE_image_is_animated(image) ? params.get_input<int>("Frame") : 0; + + std::unique_ptr<ImageFieldsFunction> image_fn; + try { + image_fn = std::make_unique<ImageFieldsFunction>( + storage.interpolation, storage.extension, *image, image_user); + } + catch (const std::runtime_error &) { + params.set_default_remaining_outputs(); + return; + } + + Field<float3> vector_field = params.extract_input<Field<float3>>("Vector"); + + auto image_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(image_fn), {std::move(vector_field)})); + + params.set_output("Color", Field<ColorGeometry4f>(image_op, 0)); + params.set_output("Alpha", Field<float>(image_op, 1)); +} + +} // namespace blender::nodes::node_geo_image_texture_cc + +void register_node_type_geo_image_texture() +{ + namespace file_ns = blender::nodes::node_geo_image_texture_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_IMAGE_TEXTURE, "Image Texture", NODE_CLASS_TEXTURE); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + node_type_init(&ntype, file_ns::node_init); + node_type_storage( + &ntype, "NodeGeometryImageTexture", node_free_standard_storage, node_copy_standard_storage); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc new file mode 100644 index 00000000000..b1144b58c37 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc @@ -0,0 +1,128 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_curve_handles_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Bool>(N_("Relative")) + .default_value(false) + .supports_field() + .description(N_("Output the handle positions relative to the corresponding control point " + "instead of in the local space of the geometry")); + b.add_output<decl::Vector>(N_("Left")).field_source(); + b.add_output<decl::Vector>(N_("Right")).field_source(); +} + +class HandlePositionFieldInput final : public GeometryFieldInput { + Field<bool> relative_; + bool left_; + + public: + HandlePositionFieldInput(Field<bool> relative, bool left) + : GeometryFieldInput(CPPType::get<float3>(), "Handle"), relative_(relative), left_(left) + { + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final + { + if (component.type() != GEO_COMPONENT_TYPE_CURVE) { + return {}; + } + + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator(field_context, &mask); + evaluator.add(relative_); + evaluator.evaluate(); + const VArray<bool> &relative = evaluator.get_evaluated<bool>(0); + + VArray<float3> positions = component.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + + StringRef side = left_ ? "handle_left" : "handle_right"; + VArray<float3> handles = component.attribute_get_for_read<float3>( + side, ATTR_DOMAIN_POINT, {0, 0, 0}); + + if (relative.is_single()) { + if (relative.get_internal_single()) { + Array<float3> output(positions.size()); + for (const int i : positions.index_range()) { + output[i] = handles[i] - positions[i]; + } + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); + } + return component.attribute_try_adapt_domain<float3>(handles, ATTR_DOMAIN_POINT, domain); + } + + Array<float3> output(positions.size()); + for (const int i : positions.index_range()) { + if (relative[i]) { + output[i] = handles[i] - positions[i]; + } + else { + output[i] = handles[i]; + } + } + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); + } + + uint64_t hash() const override + { + return get_default_hash_2(relative_, left_); + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const HandlePositionFieldInput *other_handle = + dynamic_cast<const HandlePositionFieldInput *>(&other)) { + return relative_ == other_handle->relative_ && left_ == other_handle->left_; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<bool> relative = params.extract_input<Field<bool>>("Relative"); + Field<float3> left_field{std::make_shared<HandlePositionFieldInput>(relative, true)}; + Field<float3> right_field{std::make_shared<HandlePositionFieldInput>(relative, false)}; + + params.set_output("Left", std::move(left_field)); + params.set_output("Right", std::move(right_field)); +} + +} // namespace blender::nodes::node_geo_input_curve_handles_cc + +void register_node_type_geo_input_curve_handles() +{ + namespace file_ns = blender::nodes::node_geo_input_curve_handles_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_INPUT_CURVE_HANDLES, "Curve Handle Positions", NODE_CLASS_INPUT); + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_curve_tilt.cc b/source/blender/nodes/geometry/nodes/node_geo_input_curve_tilt.cc new file mode 100644 index 00000000000..61b4b6bb9e9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_curve_tilt.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_curve_tilt_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Tilt")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<float> tilt_field = AttributeFieldInput::Create<float>("tilt"); + params.set_output("Tilt", std::move(tilt_field)); +} + +} // namespace blender::nodes::node_geo_input_curve_tilt_cc + +void register_node_type_geo_input_curve_tilt() +{ + namespace file_ns = blender::nodes::node_geo_input_curve_tilt_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_CURVE_TILT, "Curve Tilt", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_id.cc b/source/blender/nodes/geometry/nodes/node_geo_input_id.cc new file mode 100644 index 00000000000..3fe0588a46d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_id.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_id_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("ID")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> position_field{std::make_shared<bke::IDAttributeFieldInput>()}; + params.set_output("ID", std::move(position_field)); +} + +} // namespace blender::nodes::node_geo_input_id_cc + +void register_node_type_geo_input_id() +{ + namespace file_ns = blender::nodes::node_geo_input_id_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_ID, "ID", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc index 7fcbaf429dd..98c2c9d58f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc @@ -16,27 +16,29 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_input_index_cc { -static void geo_node_input_index_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Int>("Index").field_source(); + b.add_output<decl::Int>(N_("Index")).field_source(); } -static void geo_node_input_index_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Field<int> index_field{std::make_shared<fn::IndexFieldInput>()}; params.set_output("Index", std::move(index_field)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_input_index_cc void register_node_type_geo_input_index() { + namespace file_ns = blender::nodes::node_geo_input_index_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec; - ntype.declare = blender::nodes::geo_node_input_index_declare; + geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_material.cc b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc index 8e805bd1359..a1c905fccaa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_material.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc @@ -19,33 +19,35 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_input_material_cc { -static void geo_node_input_material_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Material>("Material"); + b.add_output<decl::Material>(N_("Material")); } -static void geo_node_input_material_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "material", 0, "", ICON_NONE); } -static void geo_node_input_material_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Material *material = (Material *)params.node().id; params.set_output("Material", material); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_input_material_cc void register_node_type_geo_input_material() { + namespace file_ns = blender::nodes::node_geo_input_material_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL, "Material", NODE_CLASS_INPUT, 0); - ntype.draw_buttons = blender::nodes::geo_node_input_material_layout; - ntype.declare = blender::nodes::geo_node_input_material_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_input_material_exec; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL, "Material", NODE_CLASS_INPUT); + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_material_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_material_index.cc new file mode 100644 index 00000000000..fca29feb73c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_material_index.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_material_index_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Material Index")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> material_index_field = AttributeFieldInput::Create<int>("material_index"); + params.set_output("Material Index", std::move(material_index_field)); +} + +} // namespace blender::nodes::node_geo_input_material_index_cc + +void register_node_type_geo_input_material_index() +{ + namespace file_ns = blender::nodes::node_geo_input_material_index_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL_INDEX, "Material Index", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc new file mode 100644 index 00000000000..4b6ed7b77b7 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc @@ -0,0 +1,222 @@ +/* + * 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 "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_edge_angle_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Unsigned Angle")) + .field_source() + .description( + "The shortest angle in radians between two faces where they meet at an edge. Flat edges " + "and Non-manifold edges have an angle of zero. Computing this value is faster than the " + "signed angle"); + b.add_output<decl::Float>(N_("Signed Angle")) + .field_source() + .description( + "The signed angle in radians between two faces where they meet at an edge. Flat edges " + "and Non-manifold edges have an angle of zero. Concave angles are positive and convex " + "angles are negative. Computing this value is slower than the unsigned angle"); +} + +struct EdgeMapEntry { + int face_count; + int face_index_1; + int face_index_2; +}; + +static Array<EdgeMapEntry> create_edge_map(const Span<MPoly> polys, + const Span<MLoop> loops, + const int total_edges) +{ + Array<EdgeMapEntry> edge_map(total_edges, {0, 0, 0}); + + for (const int i_poly : polys.index_range()) { + const MPoly &mpoly = polys[i_poly]; + for (const MLoop &loop : loops.slice(mpoly.loopstart, mpoly.totloop)) { + EdgeMapEntry &entry = edge_map[loop.e]; + if (entry.face_count == 0) { + entry.face_index_1 = i_poly; + } + else if (entry.face_count == 1) { + entry.face_index_2 = i_poly; + } + entry.face_count++; + } + } + return edge_map; +} + +class AngleFieldInput final : public GeometryFieldInput { + public: + AngleFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Unsigned Angle Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + Span<MPoly> polys{mesh->mpoly, mesh->totpoly}; + Span<MLoop> loops{mesh->mloop, mesh->totloop}; + Array<EdgeMapEntry> edge_map = create_edge_map(polys, loops, mesh->totedge); + + auto angle_fn = [edge_map, polys, loops, mesh](const int i) -> float { + if (edge_map[i].face_count != 2) { + return 0.0f; + } + const MPoly &mpoly_1 = polys[edge_map[i].face_index_1]; + const MPoly &mpoly_2 = polys[edge_map[i].face_index_2]; + float3 normal_1, normal_2; + BKE_mesh_calc_poly_normal(&mpoly_1, &loops[mpoly_1.loopstart], mesh->mvert, normal_1); + BKE_mesh_calc_poly_normal(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, normal_2); + return angle_normalized_v3v3(normal_1, normal_2); + }; + + VArray<float> angles = VArray<float>::ForFunc(mesh->totedge, angle_fn); + return component.attribute_try_adapt_domain<float>( + std::move(angles), ATTR_DOMAIN_EDGE, domain); + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 32426725235; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const AngleFieldInput *>(&other) != nullptr; + } +}; + +class SignedAngleFieldInput final : public GeometryFieldInput { + public: + SignedAngleFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Signed Angle Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + Span<MPoly> polys{mesh->mpoly, mesh->totpoly}; + Span<MLoop> loops{mesh->mloop, mesh->totloop}; + Array<EdgeMapEntry> edge_map = create_edge_map(polys, loops, mesh->totedge); + + auto angle_fn = [edge_map, polys, loops, mesh](const int i) -> float { + if (edge_map[i].face_count != 2) { + return 0.0f; + } + const MPoly &mpoly_1 = polys[edge_map[i].face_index_1]; + const MPoly &mpoly_2 = polys[edge_map[i].face_index_2]; + + /* Find the normals of the 2 polys. */ + float3 poly_1_normal, poly_2_normal; + BKE_mesh_calc_poly_normal(&mpoly_1, &loops[mpoly_1.loopstart], mesh->mvert, poly_1_normal); + BKE_mesh_calc_poly_normal(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, poly_2_normal); + + /* Find the centerpoint of the axis edge */ + const float3 edge_centerpoint = (float3(mesh->mvert[mesh->medge[i].v1].co) + + float3(mesh->mvert[mesh->medge[i].v2].co)) * + 0.5f; + + /* Get the centerpoint of poly 2 and subtract the edge centerpoint to get a tangent + * normal for poly 2. */ + float3 poly_center_2; + BKE_mesh_calc_poly_center(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, poly_center_2); + const float3 poly_2_tangent = math::normalize(poly_center_2 - edge_centerpoint); + const float concavity = math::dot(poly_1_normal, poly_2_tangent); + + /* Get the unsigned angle between the two polys */ + const float angle = angle_normalized_v3v3(poly_1_normal, poly_2_normal); + + if (angle == 0.0f || angle == 2.0f * M_PI || concavity < 0) { + return angle; + } + return -angle; + }; + + VArray<float> angles = VArray<float>::ForFunc(mesh->totedge, angle_fn); + return component.attribute_try_adapt_domain<float>( + std::move(angles), ATTR_DOMAIN_EDGE, domain); + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 68465416863; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const SignedAngleFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + if (params.output_is_required("Unsigned Angle")) { + Field<float> angle_field{std::make_shared<AngleFieldInput>()}; + params.set_output("Unsigned Angle", std::move(angle_field)); + } + if (params.output_is_required("Signed Angle")) { + Field<float> angle_field{std::make_shared<SignedAngleFieldInput>()}; + params.set_output("Signed Angle", std::move(angle_field)); + } +} + +} // namespace blender::nodes::node_geo_input_mesh_edge_angle_cc + +void register_node_type_geo_input_mesh_edge_angle() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_edge_angle_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MESH_EDGE_ANGLE, "Edge Angle", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc new file mode 100644 index 00000000000..ddeb3ded511 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc @@ -0,0 +1,93 @@ +/* + * 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 "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_edge_neighbors_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Face Count")) + .field_source() + .description(N_("Number of faces that contain the edge")); +} + +class EdgeNeighborCountFieldInput final : public GeometryFieldInput { + public: + EdgeNeighborCountFieldInput() + : GeometryFieldInput(CPPType::get<int>(), "Edge Neighbor Count Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + Array<int> face_count(mesh->totedge, 0); + for (const int i : IndexRange(mesh->totloop)) { + face_count[mesh->mloop[i].e]++; + } + + return mesh_component.attribute_try_adapt_domain<int>( + VArray<int>::ForContainer(std::move(face_count)), ATTR_DOMAIN_EDGE, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 985671075; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const EdgeNeighborCountFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> neighbor_count_field{std::make_shared<EdgeNeighborCountFieldInput>()}; + params.set_output("Face Count", std::move(neighbor_count_field)); +} + +} // namespace blender::nodes::node_geo_input_mesh_edge_neighbors_cc + +void register_node_type_geo_input_mesh_edge_neighbors() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_edge_neighbors_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, "Edge Neighbors", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc new file mode 100644 index 00000000000..f54c92fea7b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc @@ -0,0 +1,186 @@ +/* + * 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 "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_edge_vertices_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Vertex Index 1")) + .field_source() + .description(N_("The index of the first vertex in the edge")); + b.add_output<decl::Int>(N_("Vertex Index 2")) + .field_source() + .description(N_("The index of the second vertex in the edge")); + b.add_output<decl::Vector>(N_("Position 1")) + .field_source() + .description(N_("The position of the first vertex in the edge")); + b.add_output<decl::Vector>(N_("Position 2")) + .field_source() + .description(N_("The position of the second vertex in the edge")); +} + +enum VertexNumber { VERTEX_ONE, VERTEX_TWO }; + +static VArray<int> construct_edge_vertices_gvarray(const MeshComponent &component, + const VertexNumber vertex, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + if (domain == ATTR_DOMAIN_EDGE) { + if (vertex == VERTEX_ONE) { + return VArray<int>::ForFunc(mesh->totedge, + [mesh](const int i) -> int { return mesh->medge[i].v1; }); + } + return VArray<int>::ForFunc(mesh->totedge, + [mesh](const int i) -> int { return mesh->medge[i].v2; }); + } + return {}; +} + +class EdgeVerticesFieldInput final : public GeometryFieldInput { + private: + VertexNumber vertex_; + + public: + EdgeVerticesFieldInput(VertexNumber vertex) + : GeometryFieldInput(CPPType::get<int>(), "Edge Vertices Field"), vertex_(vertex) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_edge_vertices_gvarray(mesh_component, vertex_, domain); + } + return {}; + } + + uint64_t hash() const override + { + return vertex_ == VERTEX_ONE ? 23847562893465 : 92384598734567; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const EdgeVerticesFieldInput *other_field = dynamic_cast<const EdgeVerticesFieldInput *>( + &other)) { + return vertex_ == other_field->vertex_; + } + return false; + } +}; + +static VArray<float3> construct_edge_positions_gvarray(const MeshComponent &component, + const VertexNumber vertex, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + if (vertex == VERTEX_ONE) { + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForFunc( + mesh->totedge, + [mesh](const int i) { return float3(mesh->mvert[mesh->medge[i].v1].co); }), + ATTR_DOMAIN_EDGE, + domain); + } + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForFunc( + mesh->totedge, + [mesh](const int i) { return float3(mesh->mvert[mesh->medge[i].v2].co); }), + ATTR_DOMAIN_EDGE, + domain); +} + +class EdgePositionFieldInput final : public GeometryFieldInput { + private: + VertexNumber vertex_; + + public: + EdgePositionFieldInput(VertexNumber vertex) + : GeometryFieldInput(CPPType::get<float3>(), "Edge Position Field"), vertex_(vertex) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_edge_positions_gvarray(mesh_component, vertex_, domain); + } + return {}; + } + + uint64_t hash() const override + { + return vertex_ == VERTEX_ONE ? 987456978362 : 374587679866; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const EdgePositionFieldInput *other_field = dynamic_cast<const EdgePositionFieldInput *>( + &other)) { + return vertex_ == other_field->vertex_; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> vertex_field_1{std::make_shared<EdgeVerticesFieldInput>(VERTEX_ONE)}; + Field<int> vertex_field_2{std::make_shared<EdgeVerticesFieldInput>(VERTEX_TWO)}; + Field<float3> position_field_1{std::make_shared<EdgePositionFieldInput>(VERTEX_ONE)}; + Field<float3> position_field_2{std::make_shared<EdgePositionFieldInput>(VERTEX_TWO)}; + + params.set_output("Vertex Index 1", std::move(vertex_field_1)); + params.set_output("Vertex Index 2", std::move(vertex_field_2)); + params.set_output("Position 1", std::move(position_field_1)); + params.set_output("Position 2", std::move(position_field_2)); +} + +} // namespace blender::nodes::node_geo_input_mesh_edge_vertices_cc + +void register_node_type_geo_input_mesh_edge_vertices() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_edge_vertices_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MESH_EDGE_VERTICES, "Edge Vertices", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc new file mode 100644 index 00000000000..ef8adff48f1 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc @@ -0,0 +1,96 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_face_area_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Area")) + .field_source() + .description(N_("The surface area of each of the mesh's faces")); +} + +static VArray<float> construct_face_area_gvarray(const MeshComponent &component, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + auto area_fn = [mesh](const int i) -> float { + const MPoly *mp = &mesh->mpoly[i]; + return BKE_mesh_calc_poly_area(mp, &mesh->mloop[mp->loopstart], mesh->mvert); + }; + + return component.attribute_try_adapt_domain<float>( + VArray<float>::ForFunc(mesh->totpoly, area_fn), ATTR_DOMAIN_FACE, domain); +} + +class FaceAreaFieldInput final : public GeometryFieldInput { + public: + FaceAreaFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Face Area Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_face_area_gvarray(mesh_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 1346334523; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const FaceAreaFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + params.set_output("Area", Field<float>(std::make_shared<FaceAreaFieldInput>())); +} + +} // namespace blender::nodes::node_geo_input_mesh_face_area_cc + +void register_node_type_geo_input_mesh_face_area() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_face_area_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MESH_FACE_AREA, "Face Area", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc new file mode 100644 index 00000000000..8d196e5f8dd --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc @@ -0,0 +1,158 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_face_neighbors_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Vertex Count")) + .field_source() + .description(N_("Number of edges or points in the face")); + b.add_output<decl::Int>(N_("Face Count")) + .field_source() + .description(N_("Number of faces which share an edge with the face")); +} + +static VArray<int> construct_neighbor_count_gvarray(const MeshComponent &component, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + Array<int> edge_count(mesh->totedge, 0); + for (const int i : IndexRange(mesh->totloop)) { + edge_count[mesh->mloop[i].e]++; + } + + Array<int> poly_count(mesh->totpoly, 0); + for (const int poly_num : IndexRange(mesh->totpoly)) { + MPoly &poly = mesh->mpoly[poly_num]; + for (const int loop_num : IndexRange(poly.loopstart, poly.totloop)) { + poly_count[poly_num] += edge_count[mesh->mloop[loop_num].e] - 1; + } + } + + return component.attribute_try_adapt_domain<int>( + VArray<int>::ForContainer(std::move(poly_count)), ATTR_DOMAIN_FACE, domain); +} + +class FaceNeighborCountFieldInput final : public GeometryFieldInput { + public: + FaceNeighborCountFieldInput() + : GeometryFieldInput(CPPType::get<int>(), "Face Neighbor Count Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_neighbor_count_gvarray(mesh_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 823543774; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const FaceNeighborCountFieldInput *>(&other) != nullptr; + } +}; + +static VArray<int> construct_vertex_count_gvarray(const MeshComponent &component, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + return component.attribute_try_adapt_domain<int>( + VArray<int>::ForFunc(mesh->totpoly, + [mesh](const int i) -> float { return mesh->mpoly[i].totloop; }), + ATTR_DOMAIN_FACE, + domain); +} + +class FaceVertexCountFieldInput final : public GeometryFieldInput { + public: + FaceVertexCountFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Vertex Count Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_vertex_count_gvarray(mesh_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 236235463634; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const FaceVertexCountFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> vertex_count_field{std::make_shared<FaceVertexCountFieldInput>()}; + Field<int> neighbor_count_field{std::make_shared<FaceNeighborCountFieldInput>()}; + params.set_output("Vertex Count", std::move(vertex_count_field)); + params.set_output("Face Count", std::move(neighbor_count_field)); +} + +} // namespace blender::nodes::node_geo_input_mesh_face_neighbors_cc + +void register_node_type_geo_input_mesh_face_neighbors() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_face_neighbors_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, "Face Neighbors", NODE_CLASS_INPUT); + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc new file mode 100644 index 00000000000..68bb93bbb64 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc @@ -0,0 +1,157 @@ +/* + * 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 "BKE_mesh.h" + +#include "BLI_disjoint_set.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_island_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Island Index")) + .field_source() + .description(N_("Island indices are based on the order of the lowest-numbered vertex " + "contained in each island")); + b.add_output<decl::Int>(N_("Island Count")) + .field_source() + .description(N_("The total number of mesh islands")); +} + +class IslandFieldInput final : public GeometryFieldInput { + public: + IslandFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Island Index") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + DisjointSet islands(mesh->totvert); + for (const int i : IndexRange(mesh->totedge)) { + islands.join(mesh->medge[i].v1, mesh->medge[i].v2); + } + + Array<int> output(mesh->totvert); + VectorSet<int> ordered_roots; + for (const int i : IndexRange(mesh->totvert)) { + const int64_t root = islands.find_root(i); + output[i] = ordered_roots.index_of_or_add(root); + } + + return mesh_component.attribute_try_adapt_domain<int>( + VArray<int>::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 635467354; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const IslandFieldInput *>(&other) != nullptr; + } +}; + +class IslandCountFieldInput final : public GeometryFieldInput { + public: + IslandCountFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Island Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + DisjointSet islands(mesh->totvert); + for (const int i : IndexRange(mesh->totedge)) { + islands.join(mesh->medge[i].v1, mesh->medge[i].v2); + } + + Set<int> island_list; + for (const int i_vert : IndexRange(mesh->totvert)) { + const int64_t root = islands.find_root(i_vert); + island_list.add(root); + } + + return VArray<int>::ForSingle(island_list.size(), + mesh_component.attribute_domain_size(domain)); + } + + uint64_t hash() const override + { + /* Some random hash. */ + return 45634572457; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const IslandCountFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + if (params.output_is_required("Island Index")) { + Field<int> field{std::make_shared<IslandFieldInput>()}; + params.set_output("Island Index", std::move(field)); + } + if (params.output_is_required("Island Count")) { + Field<int> field{std::make_shared<IslandCountFieldInput>()}; + params.set_output("Island Count", std::move(field)); + } +} + +} // namespace blender::nodes::node_geo_input_mesh_island_cc + +void register_node_type_geo_input_mesh_island() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_island_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MESH_ISLAND, "Mesh Island", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_vertex_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_vertex_neighbors.cc new file mode 100644 index 00000000000..7d79164634d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_vertex_neighbors.cc @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_vertex_neighbors_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Vertex Count")) + .field_source() + .description(N_("Vertex count and edge count are equal")); + b.add_output<decl::Int>(N_("Face Count")) + .field_source() + .description(N_("Number of faces that contain the vertex")); +} + +static VArray<int> construct_vertex_count_gvarray(const MeshComponent &component, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + if (domain == ATTR_DOMAIN_POINT) { + Array<int> vertices(mesh->totvert, 0); + for (const int i : IndexRange(mesh->totedge)) { + vertices[mesh->medge[i].v1]++; + vertices[mesh->medge[i].v2]++; + } + return VArray<int>::ForContainer(std::move(vertices)); + } + return {}; +} + +class VertexCountFieldInput final : public GeometryFieldInput { + public: + VertexCountFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Vertex Count Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_vertex_count_gvarray(mesh_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 23574528465; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const VertexCountFieldInput *>(&other) != nullptr; + } +}; + +static VArray<int> construct_face_count_gvarray(const MeshComponent &component, + const AttributeDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + if (domain == ATTR_DOMAIN_POINT) { + Array<int> vertices(mesh->totvert, 0); + for (const int i : IndexRange(mesh->totloop)) { + int vertex = mesh->mloop[i].v; + vertices[vertex]++; + } + return VArray<int>::ForContainer(std::move(vertices)); + } + return {}; +} + +class VertexFaceCountFieldInput final : public GeometryFieldInput { + public: + VertexFaceCountFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Vertex Face Count Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_face_count_gvarray(mesh_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 3462374322; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const VertexFaceCountFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> vertex_field{std::make_shared<VertexCountFieldInput>()}; + Field<int> face_field{std::make_shared<VertexFaceCountFieldInput>()}; + + params.set_output("Vertex Count", std::move(vertex_field)); + params.set_output("Face Count", std::move(face_field)); +} + +} // namespace blender::nodes::node_geo_input_mesh_vertex_neighbors_cc + +void register_node_type_geo_input_mesh_vertex_neighbors() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_vertex_neighbors_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, "Vertex Neighbors", NODE_CLASS_INPUT); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index 5a2495afb9e..120ae0e9bd1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -24,280 +24,29 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_input_normal_cc { -static void geo_node_input_normal_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Normal").field_source(); + b.add_output<decl::Vector>(N_("Normal")).field_source(); } -static GVArrayPtr mesh_face_normals(const Mesh &mesh, - const Span<MVert> verts, - const Span<MPoly> polys, - const Span<MLoop> loops, - const IndexMask mask) +static void node_geo_exec(GeoNodeExecParams params) { - /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ - if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) && - CustomData_has_layer(&mesh.pdata, CD_NORMAL)) { - const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); - - return std::make_unique<fn::GVArray_For_Span<float3>>( - Span<float3>((const float3 *)data, polys.size())); - } - - auto normal_fn = [verts, polys, loops](const int i) -> float3 { - float3 normal; - const MPoly &poly = polys[i]; - BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal); - return normal; - }; - - return std::make_unique< - fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>( - mask.min_array_size(), mask.min_array_size(), normal_fn); -} - -static GVArrayPtr mesh_vertex_normals(const Mesh &mesh, - const Span<MVert> verts, - const Span<MPoly> polys, - const Span<MLoop> loops, - const IndexMask mask) -{ - /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ - if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) && - CustomData_has_layer(&mesh.vdata, CD_NORMAL)) { - const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); - - return std::make_unique<fn::GVArray_For_Span<float3>>( - Span<float3>((const float3 *)data, mesh.totvert)); - } - - /* If the normals are dirty, they must be recalculated for the output of this node's field - * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not - * possible at the moment, so we take ownership of the results. Sadly we must also create a copy - * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy - * calculation of normals on meshes. - * - * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */ - Array<MVert> temp_verts(verts); - Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */ - BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(), - mask.min_array_size(), - loops.data(), - loops.size(), - polys.data(), - polys.size(), - nullptr, - (float(*)[3])normals.data()); - - return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); -} - -static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component, - const Mesh &mesh, - const IndexMask mask, - const AttributeDomain domain, - ResourceScope &scope) -{ - Span<MVert> verts{mesh.mvert, mesh.totvert}; - Span<MEdge> edges{mesh.medge, mesh.totedge}; - Span<MPoly> polys{mesh.mpoly, mesh.totpoly}; - Span<MLoop> loops{mesh.mloop, mesh.totloop}; - - switch (domain) { - case ATTR_DOMAIN_FACE: { - return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask)).get(); - } - case ATTR_DOMAIN_POINT: { - return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask)).get(); - } - case ATTR_DOMAIN_EDGE: { - /* In this case, start with vertex normals and convert to the edge domain, since the - * conversion from edges to vertices is very simple. Use the full mask since the edges - * might use the vertex normal from any index. */ - GVArrayPtr vert_normals = mesh_vertex_normals( - mesh, verts, polys, loops, IndexRange(verts.size())); - Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>(); - Array<float3> edge_normals(mask.min_array_size()); - - /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid - * calculating unnecessary values and to allow normalizing the result much more simply. */ - for (const int i : mask) { - const MEdge &edge = edges[i]; - edge_normals[i] = float3::interpolate( - vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f) - .normalized(); - } - - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>( - std::move(edge_normals)); - } - case ATTR_DOMAIN_CORNER: { - /* The normals on corners are just the mesh's face normals, so start with the face normal - * array and copy the face normal for each of its corners. */ - GVArrayPtr face_normals = mesh_face_normals( - mesh, verts, polys, loops, IndexRange(polys.size())); - - /* In this case using the mesh component's generic domain interpolation is fine, the data - * will still be normalized, since the face normal is just copied to every corner. */ - GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain( - std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); - return scope.add_value(std::move(loop_normals)).get(); - } - default: - return nullptr; - } -} - -static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) -{ - Span<int> offsets = spline.control_point_offsets(); - Span<float3> evaluated_normals = spline.evaluated_normals(); - for (const int i : IndexRange(spline.size())) { - normals[i] = evaluated_normals[offsets[i]]; - } -} - -static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) -{ - normals.copy_from(spline.evaluated_normals()); -} - -/** - * Because NURBS control points are not necessarily on the path, the normal at the control points - * is not well defined, so create a temporary poly spline to find the normals. This requires extra - * copying currently, but may be more efficient in the future if attributes have some form of CoW. - */ -static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) -{ - PolySpline poly_spline; - poly_spline.resize(spline.size()); - poly_spline.positions().copy_from(spline.positions()); - normals.copy_from(poly_spline.evaluated_normals()); -} - -static Array<float3> curve_normal_point_domain(const CurveEval &curve) -{ - Span<SplinePtr> splines = curve.splines(); - Array<int> offsets = curve.control_point_offsets(); - const int total_size = offsets.last(); - Array<float3> normals(total_size); - - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - const Spline &spline = *splines[i]; - MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; - switch (splines[i]->type()) { - case Spline::Type::Bezier: - calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); - break; - case Spline::Type::Poly: - calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); - break; - case Spline::Type::NURBS: - calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); - break; - } - } - }); - return normals; -} - -static const GVArray *construct_curve_normal_gvarray(const CurveComponent &component, - const AttributeDomain domain, - ResourceScope &scope) -{ - const CurveEval *curve = component.get_for_read(); - if (curve == nullptr) { - return nullptr; - } - - if (domain == ATTR_DOMAIN_POINT) { - const Span<SplinePtr> splines = curve->splines(); - - /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. - * This is only possible when there is only one poly spline. */ - if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { - const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); - return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_normals()); - } - - Array<float3> normals = curve_normal_point_domain(*curve); - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); - } - - if (domain == ATTR_DOMAIN_CURVE) { - Array<float3> point_normals = curve_normal_point_domain(*curve); - GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( - std::move(point_normals)); - GVArrayPtr spline_normals = component.attribute_try_adapt_domain( - std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); - return scope.add_value(std::move(spline_normals)).get(); - } - - return nullptr; -} - -class NormalFieldInput final : public fn::FieldInput { - public: - NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal") - { - } - - const GVArray *get_varray_for_context(const fn::FieldContext &context, - IndexMask mask, - ResourceScope &scope) const final - { - if (const GeometryComponentFieldContext *geometry_context = - dynamic_cast<const GeometryComponentFieldContext *>(&context)) { - - const GeometryComponent &component = geometry_context->geometry_component(); - const AttributeDomain domain = geometry_context->domain(); - - if (component.type() == GEO_COMPONENT_TYPE_MESH) { - const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); - const Mesh *mesh = mesh_component.get_for_read(); - if (mesh == nullptr) { - return nullptr; - } - - return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope); - } - if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - return construct_curve_normal_gvarray(curve_component, domain, scope); - } - } - return nullptr; - } - - uint64_t hash() const override - { - /* Some random constant hash. */ - return 669605641; - } - - bool is_equal_to(const fn::FieldNode &other) const override - { - return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; - } -}; - -static void geo_node_input_normal_exec(GeoNodeExecParams params) -{ - Field<float3> normal_field{std::make_shared<NormalFieldInput>()}; + Field<float3> normal_field{std::make_shared<bke::NormalFieldInput>()}; params.set_output("Normal", std::move(normal_field)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_input_normal_cc void register_node_type_geo_input_normal() { + namespace file_ns = blender::nodes::node_geo_input_normal_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec; - ntype.declare = blender::nodes::geo_node_input_normal_declare; + geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc index 44874259e20..beb528d2fd8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc @@ -16,27 +16,29 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_input_position_cc { -static void geo_node_input_position_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Position").field_source(); + b.add_output<decl::Vector>(N_("Position")).field_source(); } -static void geo_node_input_position_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Field<float3> position_field{AttributeFieldInput::Create<float3>("position")}; params.set_output("Position", std::move(position_field)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_input_position_cc void register_node_type_geo_input_position() { + namespace file_ns = blender::nodes::node_geo_input_position_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec; - ntype.declare = blender::nodes::geo_node_input_position_declare; + geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_input_radius.cc new file mode 100644 index 00000000000..c7777da08c6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_radius.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_radius_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Radius")).default_value(1.0f).min(0.0f).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<float> radius_field = AttributeFieldInput::Create<float>("radius"); + params.set_output("Radius", std::move(radius_field)); +} + +} // namespace blender::nodes::node_geo_input_radius_cc + +void register_node_type_geo_input_radius() +{ + namespace file_ns = blender::nodes::node_geo_input_radius_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_RADIUS, "Radius", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_scene_time.cc b/source/blender/nodes/geometry/nodes/node_geo_input_scene_time.cc new file mode 100644 index 00000000000..4ed65e99a1c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_scene_time.cc @@ -0,0 +1,50 @@ +/* + * 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_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_scene_time_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Seconds")); + b.add_output<decl::Float>(N_("Frame")); +} + +static void node_exec(GeoNodeExecParams params) +{ + const Scene *scene = DEG_get_input_scene(params.depsgraph()); + const float scene_ctime = BKE_scene_ctime_get(scene); + const double frame_rate = (((double)scene->r.frs_sec) / (double)scene->r.frs_sec_base); + params.set_output("Seconds", float(scene_ctime / frame_rate)); + params.set_output("Frame", scene_ctime); +} + +} // namespace blender::nodes::node_geo_input_scene_time_cc + +void register_node_type_geo_input_scene_time() +{ + static bNodeType ntype; + namespace file_ns = blender::nodes::node_geo_input_scene_time_cc; + geo_node_type_base(&ntype, GEO_NODE_INPUT_SCENE_TIME, "Scene Time", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc new file mode 100644 index 00000000000..b27ab097223 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_shade_smooth.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_shade_smooth_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Bool>(N_("Smooth")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<bool> shade_smooth_field = AttributeFieldInput::Create<bool>("shade_smooth"); + params.set_output("Smooth", std::move(shade_smooth_field)); +} + +} // namespace blender::nodes::node_geo_input_shade_smooth_cc + +void register_node_type_geo_input_shade_smooth() +{ + namespace file_ns = blender::nodes::node_geo_input_shade_smooth_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_SHADE_SMOOTH, "Is Shade Smooth", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_cyclic.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_cyclic.cc new file mode 100644 index 00000000000..2db00a1ae68 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_cyclic.cc @@ -0,0 +1,44 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_spline_cyclic_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Bool>(N_("Cyclic")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<bool> cyclic_field = AttributeFieldInput::Create<bool>("cyclic"); + params.set_output("Cyclic", std::move(cyclic_field)); +} + +} // namespace blender::nodes::node_geo_input_spline_cyclic_cc + +void register_node_type_geo_input_spline_cyclic() +{ + namespace file_ns = blender::nodes::node_geo_input_spline_cyclic_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_SPLINE_CYCLIC, "Is Spline Cyclic", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc new file mode 100644 index 00000000000..b8c8ce840eb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc @@ -0,0 +1,163 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "BKE_spline.hh" + +namespace blender::nodes::node_geo_input_spline_length_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>(N_("Length")).field_source(); + b.add_output<decl::Int>(N_("Point Count")).field_source(); +} + +/* -------------------------------------------------------------------- + * Spline Length + */ + +static VArray<float> construct_spline_length_gvarray(const CurveComponent &component, + const AttributeDomain domain) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + auto length_fn = [splines](int i) { return splines[i]->length(); }; + + if (domain == ATTR_DOMAIN_CURVE) { + return VArray<float>::ForFunc(splines.size(), length_fn); + } + if (domain == ATTR_DOMAIN_POINT) { + VArray<float> length = VArray<float>::ForFunc(splines.size(), length_fn); + return component.attribute_try_adapt_domain<float>( + std::move(length), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); + } + + return {}; +} + +class SplineLengthFieldInput final : public GeometryFieldInput { + public: + SplineLengthFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Spline Length node") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_spline_length_gvarray(curve_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 3549623580; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const SplineLengthFieldInput *>(&other) != nullptr; + } +}; + +/* -------------------------------------------------------------------- + * Spline Count + */ + +static VArray<int> construct_spline_count_gvarray(const CurveComponent &component, + const AttributeDomain domain) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + auto count_fn = [splines](int i) { return splines[i]->size(); }; + + if (domain == ATTR_DOMAIN_CURVE) { + return VArray<int>::ForFunc(splines.size(), count_fn); + } + if (domain == ATTR_DOMAIN_POINT) { + VArray<int> count = VArray<int>::ForFunc(splines.size(), count_fn); + return component.attribute_try_adapt_domain<int>( + std::move(count), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); + } + + return {}; +} + +class SplineCountFieldInput final : public GeometryFieldInput { + public: + SplineCountFieldInput() : GeometryFieldInput(CPPType::get<int>(), "Spline Point Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_spline_count_gvarray(curve_component, domain); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 456364322625; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const SplineCountFieldInput *>(&other) != nullptr; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<float> spline_length_field{std::make_shared<SplineLengthFieldInput>()}; + Field<int> spline_count_field{std::make_shared<SplineCountFieldInput>()}; + + params.set_output("Length", std::move(spline_length_field)); + params.set_output("Point Count", std::move(spline_count_field)); +} + +} // namespace blender::nodes::node_geo_input_spline_length_cc + +void register_node_type_geo_input_spline_length() +{ + namespace file_ns = blender::nodes::node_geo_input_spline_length_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_SPLINE_LENGTH, "Spline Length", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_resolution.cc new file mode 100644 index 00000000000..d79f2ffd64d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_resolution.cc @@ -0,0 +1,45 @@ +/* + * 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" + +namespace blender::nodes::node_geo_input_spline_resolution_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Int>(N_("Resolution")).field_source(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> resolution_field = AttributeFieldInput::Create<int>("resolution"); + params.set_output("Resolution", std::move(resolution_field)); +} + +} // namespace blender::nodes::node_geo_input_spline_resolution_cc + +void register_node_type_geo_input_spline_resolution() +{ + namespace file_ns = blender::nodes::node_geo_input_spline_resolution_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_INPUT_SPLINE_RESOLUTION, "Spline Resolution", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc index d690642373a..f80fdfbf334 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -20,11 +20,11 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_input_tangent_cc { -static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Tangent").field_source(); + b.add_output<decl::Vector>(N_("Tangent")).field_source(); } static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents) @@ -84,9 +84,8 @@ static Array<float3> curve_tangent_point_domain(const CurveEval &curve) return tangents; } -static const GVArray *construct_curve_tangent_gvarray(const CurveComponent &component, - const AttributeDomain domain, - ResourceScope &scope) +static VArray<float3> construct_curve_tangent_gvarray(const CurveComponent &component, + const AttributeDomain domain) { const CurveEval *curve = component.get_for_read(); if (curve == nullptr) { @@ -100,47 +99,40 @@ static const GVArray *construct_curve_tangent_gvarray(const CurveComponent &comp * This is only possible when there is only one poly spline. */ if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); - return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_tangents()); + return VArray<float3>::ForSpan(spline.evaluated_tangents()); } Array<float3> tangents = curve_tangent_point_domain(*curve); - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(tangents)); + return VArray<float3>::ForContainer(std::move(tangents)); } if (domain == ATTR_DOMAIN_CURVE) { Array<float3> point_tangents = curve_tangent_point_domain(*curve); - GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( - std::move(point_tangents)); - GVArrayPtr spline_tangents = component.attribute_try_adapt_domain( - std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); - return scope.add_value(std::move(spline_tangents)).get(); + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForContainer(std::move(point_tangents)), + ATTR_DOMAIN_POINT, + ATTR_DOMAIN_CURVE); } return nullptr; } -class TangentFieldInput final : public fn::FieldInput { +class TangentFieldInput final : public GeometryFieldInput { public: - TangentFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Tangent") + TangentFieldInput() : GeometryFieldInput(CPPType::get<float3>(), "Tangent node") { + category_ = Category::Generated; } - const GVArray *get_varray_for_context(const fn::FieldContext &context, - IndexMask UNUSED(mask), - ResourceScope &scope) const final + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final { - if (const GeometryComponentFieldContext *geometry_context = - dynamic_cast<const GeometryComponentFieldContext *>(&context)) { - - const GeometryComponent &component = geometry_context->geometry_component(); - const AttributeDomain domain = geometry_context->domain(); - - if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - return construct_curve_tangent_gvarray(curve_component, domain, scope); - } + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_curve_tangent_gvarray(curve_component, domain); } - return nullptr; + return {}; } uint64_t hash() const override @@ -155,20 +147,22 @@ class TangentFieldInput final : public fn::FieldInput { } }; -static void geo_node_input_tangent_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Field<float3> tangent_field{std::make_shared<TangentFieldInput>()}; params.set_output("Tangent", std::move(tangent_field)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_input_tangent_cc void register_node_type_geo_input_tangent() { + namespace file_ns = blender::nodes::node_geo_input_tangent_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_input_tangent_exec; - ntype.declare = blender::nodes::geo_node_input_tangent_declare; + geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc index 8c0c0763be8..71256a7f781 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -22,72 +22,80 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "BKE_attribute_math.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_instance_on_points_cc { -static void geo_node_instance_on_points_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Points").description("Points to instance on"); - b.add_input<decl::Geometry>("Instance").description("Geometry that is instanced on the points"); - b.add_input<decl::Bool>("Pick Instance") + b.add_input<decl::Geometry>(N_("Points")).description(N_("Points to instance on")); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Geometry>(N_("Instance")) + .description(N_("Geometry that is instanced on the points")); + b.add_input<decl::Bool>(N_("Pick Instance")) .supports_field() - .description("Place different instances on different points"); - b.add_input<decl::Int>("Instance Index") + .description(N_("Place different instances on different points")); + b.add_input<decl::Int>(N_("Instance Index")) .implicit_field() - .description( + .description(N_( "Index of the instance that used for each point. This is only used when Pick Instances " - "is on. By default the point index is used"); - b.add_input<decl::Vector>("Rotation") + "is on. By default the point index is used")); + b.add_input<decl::Vector>(N_("Rotation")) .subtype(PROP_EULER) .supports_field() - .description("Rotation of the instances"); - b.add_input<decl::Vector>("Scale") + .description(N_("Rotation of the instances")); + b.add_input<decl::Vector>(N_("Scale")) .default_value({1.0f, 1.0f, 1.0f}) + .subtype(PROP_XYZ) .supports_field() - .description("Scale of the instances"); - b.add_input<decl::Int>("Stable ID") - .supports_field() - .description( - "ID for every instance that is used to identify it over time even when the number of " - "instances changes. Used for example for motion blur"); + .description(N_("Scale of the instances")); - b.add_output<decl::Geometry>("Instances"); + b.add_output<decl::Geometry>(N_("Instances")); } -static void add_instances_from_component(InstancesComponent &dst_component, - const GeometryComponent &src_component, - const GeometrySet &instance, - const GeoNodeExecParams ¶ms) +static void add_instances_from_component( + InstancesComponent &dst_component, + const GeometryComponent &src_component, + const GeometrySet &instance, + const GeoNodeExecParams ¶ms, + const Map<AttributeIDRef, AttributeKind> &attributes_to_propagate) { const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_component.attribute_domain_size(domain); + VArray<bool> pick_instance; + VArray<int> indices; + VArray<float3> rotations; + VArray<float3> scales; + + GeometryComponentFieldContext field_context{src_component, domain}; + const Field<bool> selection_field = params.get_input<Field<bool>>("Selection"); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + /* The evaluator could use the component's stable IDs as a destination directly, but only the + * selected indices should be copied. */ + evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance); + evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices); + evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations); + evaluator.add(params.get_input<Field<float3>>("Scale"), &scales); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + /* The initial size of the component might be non-zero when this function is called for multiple * component types. */ const int start_len = dst_component.instances_amount(); - dst_component.resize(start_len + domain_size); + const int select_len = selection.index_range().size(); + dst_component.resize(start_len + select_len); + MutableSpan<int> dst_handles = dst_component.instance_reference_handles().slice(start_len, - domain_size); + select_len); MutableSpan<float4x4> dst_transforms = dst_component.instance_transforms().slice(start_len, - domain_size); - MutableSpan<int> dst_stable_ids = dst_component.instance_ids().slice(start_len, domain_size); + select_len); - GeometryComponentFieldContext field_context{src_component, domain}; - FieldEvaluator field_evaluator{field_context, domain_size}; - - const VArray<bool> *pick_instance = nullptr; - const VArray<int> *indices = nullptr; - const VArray<float3> *rotations = nullptr; - const VArray<float3> *scales = nullptr; - field_evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance); - field_evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices); - field_evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations); - field_evaluator.add(params.get_input<Field<float3>>("Scale"), &scales); - field_evaluator.add_with_destination(params.get_input<Field<int>>("Stable ID"), dst_stable_ids); - field_evaluator.evaluate(); - - GVArray_Typed<float3> positions = src_component.attribute_get_for_read<float3>( + VArray<float3> positions = src_component.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); const InstancesComponent *src_instances = instance.get_component_for_read<InstancesComponent>(); @@ -96,7 +104,7 @@ static void add_instances_from_component(InstancesComponent &dst_component, Array<int> handle_mapping; /* Only fill #handle_mapping when it may be used below. */ if (src_instances != nullptr && - (!pick_instance->is_single() || pick_instance->get_internal_single())) { + (!pick_instance.is_single() || pick_instance.get_internal_single())) { Span<InstanceReference> src_references = src_instances->references(); handle_mapping.reinitialize(src_references.size()); for (const int src_instance_handle : src_references.index_range()) { @@ -110,23 +118,24 @@ static void add_instances_from_component(InstancesComponent &dst_component, /* Add this reference last, because it is the most likely one to be removed later on. */ const int empty_reference_handle = dst_component.add_reference(InstanceReference()); - threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { - for (const int i : range) { + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange selection_range) { + for (const int range_i : selection_range) { + const int64_t i = selection[range_i]; + /* Compute base transform for every instances. */ - float4x4 &dst_transform = dst_transforms[i]; - dst_transform = float4x4::from_loc_eul_scale( - positions[i], rotations->get(i), scales->get(i)); + float4x4 &dst_transform = dst_transforms[range_i]; + dst_transform = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); /* Reference that will be used by this new instance. */ int dst_handle = empty_reference_handle; - const bool use_individual_instance = pick_instance->get(i); + const bool use_individual_instance = pick_instance[i]; if (use_individual_instance) { if (src_instances != nullptr) { const int src_instances_amount = src_instances->instances_amount(); - const int original_index = indices->get(i); - /* Use #mod_i instead of `%` to get the desirable wrap around behavior where -1 refers to - * the last element. */ + const int original_index = indices[i]; + /* Use #mod_i instead of `%` to get the desirable wrap around behavior where -1 + * refers to the last element. */ const int index = mod_i(original_index, std::max(src_instances_amount, 1)); if (index < src_instances_amount) { /* Get the reference to the source instance. */ @@ -144,12 +153,12 @@ static void add_instances_from_component(InstancesComponent &dst_component, dst_handle = full_instance_handle; } /* Set properties of new instance. */ - dst_handles[i] = dst_handle; + dst_handles[range_i] = dst_handle; } }); - if (pick_instance->is_single()) { - if (pick_instance->get_internal_single()) { + if (pick_instance.is_single()) { + if (pick_instance.get_internal_single()) { if (instance.has_realized_data()) { params.error_message_add( NodeWarningType::Info, @@ -157,9 +166,40 @@ static void add_instances_from_component(InstancesComponent &dst_component, } } } + + bke::CustomDataAttributes &instance_attributes = dst_component.attributes(); + for (const auto item : attributes_to_propagate.items()) { + const AttributeIDRef &attribute_id = item.key; + const AttributeKind attribute_kind = item.value; + + const GVArray src_attribute = src_component.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); + BLI_assert(src_attribute); + std::optional<GMutableSpan> dst_attribute_opt = instance_attributes.get_for_write( + attribute_id); + if (!dst_attribute_opt) { + if (!instance_attributes.create(attribute_id, attribute_kind.data_type)) { + continue; + } + dst_attribute_opt = instance_attributes.get_for_write(attribute_id); + } + BLI_assert(dst_attribute_opt); + const GMutableSpan dst_attribute = dst_attribute_opt->slice(start_len, select_len); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange selection_range) { + attribute_math::convert_to_static_type(attribute_kind.data_type, [&](auto dummy) { + using T = decltype(dummy); + VArray<T> src = src_attribute.typed<T>(); + MutableSpan<T> dst = dst_attribute.typed<T>(); + for (const int range_i : selection_range) { + const int i = selection[range_i]; + dst[range_i] = src[i]; + } + }); + }); + } } -static void geo_node_instance_on_points_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); GeometrySet instance = params.get_input<GeometrySet>("Instance"); @@ -168,40 +208,59 @@ static void geo_node_instance_on_points_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + Map<AttributeIDRef, AttributeKind> attributes_to_propagate; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}, + GEO_COMPONENT_TYPE_INSTANCES, + false, + attributes_to_propagate); + attributes_to_propagate.remove("position"); + if (geometry_set.has<MeshComponent>()) { - add_instances_from_component( - instances, *geometry_set.get_component_for_read<MeshComponent>(), instance, params); - geometry_set.remove(GEO_COMPONENT_TYPE_MESH); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<MeshComponent>(), + instance, + params, + attributes_to_propagate); } if (geometry_set.has<PointCloudComponent>()) { add_instances_from_component(instances, *geometry_set.get_component_for_read<PointCloudComponent>(), instance, - params); - geometry_set.remove(GEO_COMPONENT_TYPE_POINT_CLOUD); + params, + attributes_to_propagate); } if (geometry_set.has<CurveComponent>()) { - add_instances_from_component( - instances, *geometry_set.get_component_for_read<CurveComponent>(), instance, params); - geometry_set.remove(GEO_COMPONENT_TYPE_CURVE); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<CurveComponent>(), + instance, + params, + attributes_to_propagate); } - /* Unused references may have been added above. Remove those now so that other nodes don't - * process them needlessly. */ - instances.remove_unused_references(); + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); }); + /* Unused references may have been added above. Remove those now so that other nodes don't + * process them needlessly. + * This should eventually be moved into the loop above, but currently this is quite tricky + * because it might remove references that the loop still wants to iterate over. */ + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + instances.remove_unused_references(); + params.set_output("Instances", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_instance_on_points_cc void register_node_type_geo_instance_on_points() { + namespace file_ns = blender::nodes::node_geo_instance_on_points_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_INSTANCE_ON_POINTS, "Instance on Points", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_instance_on_points_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_instance_on_points_exec; + &ntype, GEO_NODE_INSTANCE_ON_POINTS, "Instance on Points", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc new file mode 100644 index 00000000000..f9beed956bb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -0,0 +1,137 @@ +/* + * 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_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_pointcloud.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_instances_to_points_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Instances")).only_instances(); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Position")).implicit_field(); + b.add_input<decl::Float>(N_("Radius")) + .default_value(0.05f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + b.add_output<decl::Geometry>(N_("Points")); +} + +template<typename T> +static void copy_attribute_to_points(const VArray<T> &src, + const IndexMask mask, + MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +static void convert_instances_to_points(GeometrySet &geometry_set, + Field<float3> position_field, + Field<float> radius_field, + const Field<bool> selection_field) +{ + const InstancesComponent &instances = *geometry_set.get_component_for_read<InstancesComponent>(); + + GeometryComponentFieldContext field_context{instances, ATTR_DOMAIN_INSTANCE}; + const int domain_size = instances.attribute_domain_size(ATTR_DOMAIN_INSTANCE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(std::move(selection_field)); + evaluator.add(std::move(position_field)); + evaluator.add(std::move(radius_field)); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + if (selection.is_empty()) { + return; + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); + geometry_set.replace_pointcloud(pointcloud); + + PointCloudComponent &points = geometry_set.get_component_for_write<PointCloudComponent>(); + + const VArray<float3> &positions = evaluator.get_evaluated<float3>(0); + copy_attribute_to_points(positions, selection, {(float3 *)pointcloud->co, pointcloud->totpoint}); + const VArray<float> &radii = evaluator.get_evaluated<float>(1); + copy_attribute_to_points(radii, selection, {pointcloud->radius, pointcloud->totpoint}); + + Map<AttributeIDRef, AttributeKind> attributes_to_propagate; + geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_INSTANCES}, + GEO_COMPONENT_TYPE_POINT_CLOUD, + false, + attributes_to_propagate); + /* These two attributes are added by the implicit inputs above. */ + attributes_to_propagate.remove("position"); + attributes_to_propagate.remove("radius"); + + for (const auto item : attributes_to_propagate.items()) { + const AttributeIDRef &attribute_id = item.key; + const AttributeKind attribute_kind = item.value; + + const GVArray src = instances.attribute_get_for_read( + attribute_id, ATTR_DOMAIN_INSTANCE, attribute_kind.data_type); + BLI_assert(src); + OutputAttribute dst = points.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); + BLI_assert(dst); + + attribute_math::convert_to_static_type(attribute_kind.data_type, [&](auto dummy) { + using T = decltype(dummy); + copy_attribute_to_points(src.typed<T>(), selection, dst.as_span().typed<T>()); + }); + dst.save(); + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instances"); + + if (geometry_set.has_instances()) { + convert_instances_to_points(geometry_set, + params.extract_input<Field<float3>>("Position"), + params.extract_input<Field<float>>("Radius"), + params.extract_input<Field<bool>>("Selection")); + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD}); + params.set_output("Points", std::move(geometry_set)); + } + else { + params.set_default_remaining_outputs(); + } +} + +} // namespace blender::nodes::node_geo_instances_to_points_cc + +void register_node_type_geo_instances_to_points() +{ + namespace file_ns = blender::nodes::node_geo_instances_to_points_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_INSTANCES_TO_POINTS, "Instances to Points", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 f8a1c764f61..c97bbad4665 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_is_viewport.cc @@ -18,14 +18,14 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_is_viewport_cc { -static void geo_node_is_viewport_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Bool>("Is Viewport"); + b.add_output<decl::Bool>(N_("Is Viewport")); } -static void geo_node_is_viewport_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const Depsgraph *depsgraph = params.depsgraph(); const eEvaluationMode mode = DEG_get_mode(depsgraph); @@ -34,14 +34,16 @@ static void geo_node_is_viewport_exec(GeoNodeExecParams params) params.set_output("Is Viewport", is_viewport); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_is_viewport_cc void register_node_type_geo_is_viewport() { + namespace file_ns = blender::nodes::node_geo_is_viewport_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_IS_VIEWPORT, "Is Viewport", NODE_CLASS_INPUT, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_is_viewport_exec; - ntype.declare = blender::nodes::geo_node_is_viewport_declare; + geo_node_type_base(&ntype, GEO_NODE_IS_VIEWPORT, "Is Viewport", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 93643298f92..1e521af6b13 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -19,127 +19,21 @@ #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" #include "BKE_spline.hh" +#include "BKE_type_conversions.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "NOD_type_conversions.hh" +#include "GEO_realize_instances.hh" #include "node_geometry_util.hh" -using blender::fn::GVArray_For_GSpan; +namespace blender::nodes::node_geo_join_geometry_cc { -namespace blender::nodes { - -static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Geometry").multi_input(); - b.add_output<decl::Geometry>("Geometry"); -} - -static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components) +static void node_declare(NodeDeclarationBuilder &b) { - int totverts = 0; - int totloops = 0; - int totedges = 0; - int totpolys = 0; - - int64_t cd_dirty_vert = 0; - int64_t cd_dirty_poly = 0; - 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; - totloops += mesh->totloop; - totedges += mesh->totedge; - totpolys += mesh->totpoly; - cd_dirty_vert |= mesh->runtime.cd_dirty_vert; - 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_parameters_for_eval(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; - new_mesh->runtime.cd_dirty_loop = cd_dirty_loop; - - int vert_offset = 0; - int loop_offset = 0; - int edge_offset = 0; - int poly_offset = 0; - for (const MeshComponent *mesh_component : src_components) { - const Mesh *mesh = mesh_component->get_for_read(); - if (mesh == nullptr) { - continue; - } - - 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]; - new_vert = old_vert; - } - - for (const int i : IndexRange(mesh->totedge)) { - const MEdge &old_edge = mesh->medge[i]; - MEdge &new_edge = new_mesh->medge[edge_offset + i]; - new_edge = old_edge; - new_edge.v1 += vert_offset; - new_edge.v2 += vert_offset; - } - for (const int i : IndexRange(mesh->totloop)) { - const MLoop &old_loop = mesh->mloop[i]; - MLoop &new_loop = new_mesh->mloop[loop_offset + i]; - new_loop = old_loop; - new_loop.v += vert_offset; - new_loop.e += edge_offset; - } - for (const int i : IndexRange(mesh->totpoly)) { - const MPoly &old_poly = mesh->mpoly[i]; - MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; - new_poly = old_poly; - new_poly.loopstart += loop_offset; - 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; - loop_offset += mesh->totloop; - edge_offset += mesh->totedge; - poly_offset += mesh->totpoly; - } - - return new_mesh; + b.add_input<decl::Geometry>(N_("Geometry")).multi_input(); + b.add_output<decl::Geometry>(N_("Geometry")); } template<typename Component> @@ -190,10 +84,10 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, if (domain_size == 0) { continue; } - GVArrayPtr read_attribute = component->attribute_get_for_read( + GVArray read_attribute = component->attribute_get_for_read( attribute_id, domain, data_type, nullptr); - GVArray_GSpan src_span{*read_attribute}; + GVArray_GSpan src_span{read_attribute}; const void *src_buffer = src_span.data(); void *dst_buffer = dst_span[offset]; cpp_type->copy_assign_n(src_buffer, dst_buffer, domain_size); @@ -209,7 +103,7 @@ static void join_attributes(Span<const GeometryComponent *> src_components, const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components, ignored_attributes); - for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { + for (const Map<AttributeIDRef, AttributeMetaData>::Item item : info.items()) { const AttributeIDRef attribute_id = item.key; const AttributeMetaData &meta_data = item.value; @@ -225,33 +119,6 @@ static void join_attributes(Span<const GeometryComponent *> src_components, } } -static void join_components(Span<const MeshComponent *> src_components, GeometrySet &result) -{ - Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(src_components); - - MeshComponent &dst_component = result.get_component_for_write<MeshComponent>(); - dst_component.replace(new_mesh); - - /* Don't copy attributes that are stored directly in the mesh data structs. */ - join_attributes(to_base_components(src_components), - dst_component, - {"position", "material_index", "normal", "shade_smooth", "crease"}); -} - -static void join_components(Span<const PointCloudComponent *> src_components, GeometrySet &result) -{ - int totpoints = 0; - for (const PointCloudComponent *pointcloud_component : src_components) { - totpoints += pointcloud_component->attribute_domain_size(ATTR_DOMAIN_POINT); - } - - PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); - PointCloud *pointcloud = BKE_pointcloud_new_nomain(totpoints); - dst_component.replace(pointcloud); - - join_attributes(to_base_components(src_components), dst_component); -} - static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result) { InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); @@ -270,17 +137,16 @@ static void join_components(Span<const InstancesComponent *> src_components, Geo } 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); + dst_component.add_instance(dst_handle, transform); } } + join_attributes(to_base_components(src_components), dst_component, {"position"}); } static void join_components(Span<const VolumeComponent *> src_components, GeometrySet &result) @@ -291,169 +157,6 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet UNUSED_VARS(src_components, dst_component); } -/** - * \note This takes advantage of the fact that creating attributes on joined curves never - * changes a point attribute into a spline attribute; it is always the other way around. - */ -static void ensure_control_point_attribute(const AttributeIDRef &attribute_id, - const CustomDataType data_type, - Span<CurveComponent *> src_components, - CurveEval &result) -{ - MutableSpan<SplinePtr> splines = result.splines(); - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - /* In order to fill point attributes with spline domain attribute values where necessary, keep - * track of the curve each spline came from while iterating over the splines in the result. */ - int src_component_index = 0; - int spline_index_in_component = 0; - const CurveEval *current_curve = src_components[src_component_index]->get_for_read(); - - for (SplinePtr &spline : splines) { - std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id); - - if (attribute) { - if (attribute->type() != type) { - /* In this case, the attribute exists, but it has the wrong type. So create a buffer - * for the converted values, do the conversion, and then replace the attribute. */ - void *converted_buffer = MEM_mallocN_aligned( - spline->size() * type.size(), type.alignment(), __func__); - - const DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); - conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type) - ->materialize(converted_buffer); - - spline->attributes.remove(attribute_id); - spline->attributes.create_by_move(attribute_id, data_type, converted_buffer); - } - } - else { - spline->attributes.create(attribute_id, data_type); - - if (current_curve->attributes.get_for_read(attribute_id)) { - /* In this case the attribute did not exist, but there is a spline domain attribute - * we can retrieve a value from, as a spline to point domain conversion. So fill the - * new attribute with the value for this spline. */ - GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read( - attribute_id, data_type, nullptr); - - BLI_assert(spline->attributes.get_for_read(attribute_id)); - std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id); - - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - current_curve_attribute->get(spline_index_in_component, buffer); - type.fill_assign_n(buffer, new_attribute->data(), new_attribute->size()); - } - } - - /* Move to the next spline and maybe the next input component. */ - spline_index_in_component++; - if (spline != splines.last() && spline_index_in_component >= current_curve->splines().size()) { - src_component_index++; - spline_index_in_component = 0; - - current_curve = src_components[src_component_index]->get_for_read(); - } - } -} - -/** - * Fill data for an attribute on the new curve based on all source curves. - */ -static void ensure_spline_attribute(const AttributeIDRef &attribute_id, - const CustomDataType data_type, - Span<CurveComponent *> src_components, - CurveEval &result) -{ - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - result.attributes.create(attribute_id, data_type); - GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id); - - int offset = 0; - for (const CurveComponent *component : src_components) { - const CurveEval &curve = *component->get_for_read(); - const int size = curve.splines().size(); - if (size == 0) { - continue; - } - GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr); - GVArray_GSpan src_span{*read_attribute}; - - const void *src_buffer = src_span.data(); - type.copy_assign_n(src_buffer, result_attribute[offset], size); - - offset += size; - } -} - -/** - * Special handling for copying spline attributes. This is necessary because we move the splines - * out of the source components instead of copying them, meaning we can no longer access point - * domain attributes on the source components. - * - * \warning Splines have been moved out of the source components at this point, so it - * is important to only read curve-level data (spline domain attributes) from them. - */ -static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info, - Span<CurveComponent *> src_components, - CurveEval &result) -{ - for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) { - const AttributeIDRef attribute_id = item.key; - const AttributeMetaData meta_data = item.value; - - if (meta_data.domain == ATTR_DOMAIN_CURVE) { - ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result); - } - else { - ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result); - } - } -} - -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; - } - - /* Retrieve attribute info before moving the splines out of the input components. */ - const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info( - {(const GeometryComponent **)src_components.data(), src_components.size()}, - {"position", "radius", "tilt", "cyclic", "resolution"}); - - 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)); - } - } - dst_curve->attributes.reallocate(dst_curve->splines().size()); - - join_curve_attributes(info, src_components, *dst_curve); - - dst_component.replace(dst_curve); -} - template<typename Component> static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result) { @@ -472,10 +175,31 @@ static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet result.add(*components[0]); return; } - join_components(components, result); + + GeometrySet instances_geometry_set; + InstancesComponent &instances = + instances_geometry_set.get_component_for_write<InstancesComponent>(); + + if constexpr (is_same_any_v<Component, InstancesComponent, VolumeComponent>) { + join_components(components, result); + } + else { + for (const Component *component : components) { + GeometrySet tmp_geo; + tmp_geo.add(*component); + const int handle = instances.add_reference(InstanceReference{tmp_geo}); + instances.add_instance(handle, float4x4::identity()); + } + + geometry::RealizeInstancesOptions options; + options.keep_original_ids = true; + options.realize_instance_attributes = false; + GeometrySet joined_components = geometry::realize_instances(instances_geometry_set, options); + result.add(joined_components.get_component_for_write<Component>()); + } } -static void geo_node_join_geometry_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Geometry"); @@ -484,18 +208,20 @@ 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); + join_component_type<CurveComponent>(geometry_sets, geometry_set_result); params.set_output("Geometry", std::move(geometry_set_result)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_join_geometry_cc void register_node_type_geo_join_geometry() { + namespace file_ns = blender::nodes::node_geo_join_geometry_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", NODE_CLASS_GEOMETRY, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec; - ntype.declare = blender::nodes::geo_node_join_geometry_declare; + geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc deleted file mode 100644 index 780994996ae..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#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" - -namespace blender::nodes { - -static void geo_node_material_assign_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Material").hide_label(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); - b.add_output<decl::Geometry>("Geometry"); -} - -static void assign_material_to_faces(Mesh &mesh, const IndexMask selection, 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 : selection) { - 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 Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); - - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - - geometry_set.modify_geometry_sets([&](GeometrySet &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) { - GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; - - fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - - assign_material_to_faces(*mesh, selection, 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); - ntype.declare = blender::nodes::geo_node_material_assign_declare; - 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 index a917434fa00..0309121db74 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -24,17 +24,17 @@ #include "BKE_material.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_material_replace_cc { -static void geo_node_material_replace_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Material>("Old"); - b.add_input<decl::Material>("New"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Material>(N_("Old")); + b.add_input<decl::Material>(N_("New")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_material_replace_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Material *old_material = params.extract_input<Material *>("Old"); Material *new_material = params.extract_input<Material *>("New"); @@ -55,15 +55,16 @@ static void geo_node_material_replace_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_material_replace_cc void register_node_type_geo_material_replace() { + namespace file_ns = blender::nodes::node_geo_material_replace_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_MATERIAL_REPLACE, "Material Replace", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_material_replace_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_material_replace_exec; + geo_node_type_base(&ntype, GEO_NODE_REPLACE_MATERIAL, "Replace Material", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc index 337bd88c6e6..0b5f0bf34c5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -26,12 +26,12 @@ #include "BKE_material.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_material_selection_cc { -static void geo_node_material_selection_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Material>("Material").hide_label(true); - b.add_output<decl::Bool>("Selection").field_source(); + b.add_input<decl::Material>(N_("Material")).hide_label(true); + b.add_output<decl::Bool>(N_("Selection")).field_source(); } static void select_mesh_by_material(const Mesh &mesh, @@ -54,78 +54,76 @@ static void select_mesh_by_material(const Mesh &mesh, }); } -class MaterialSelectionFieldInput final : public fn::FieldInput { +class MaterialSelectionFieldInput final : public GeometryFieldInput { Material *material_; public: MaterialSelectionFieldInput(Material *material) - : fn::FieldInput(CPPType::get<bool>(), "Material Selection"), material_(material) + : GeometryFieldInput(CPPType::get<bool>(), "Material Selection node"), material_(material) { + category_ = Category::Generated; } - const GVArray *get_varray_for_context(const fn::FieldContext &context, - IndexMask mask, - ResourceScope &scope) const final + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const final { - if (const GeometryComponentFieldContext *geometry_context = - dynamic_cast<const GeometryComponentFieldContext *>(&context)) { - const GeometryComponent &component = geometry_context->geometry_component(); - const AttributeDomain domain = geometry_context->domain(); - if (component.type() != GEO_COMPONENT_TYPE_MESH) { - return nullptr; - } - const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); - const Mesh *mesh = mesh_component.get_for_read(); - if (mesh == nullptr) { - return nullptr; - } - - if (domain == ATTR_DOMAIN_FACE) { - Array<bool> selection(mask.min_array_size()); - select_mesh_by_material(*mesh, material_, mask, selection); - return &scope.construct<fn::GVArray_For_ArrayContainer<Array<bool>>>(std::move(selection)); - } - - Array<bool> selection(mesh->totpoly); - select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection); - GVArrayPtr face_selection = std::make_unique<fn::GVArray_For_ArrayContainer<Array<bool>>>( - std::move(selection)); - GVArrayPtr final_selection = mesh_component.attribute_try_adapt_domain( - std::move(face_selection), ATTR_DOMAIN_FACE, domain); - return scope.add_value(std::move(final_selection)).get(); + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + if (domain == ATTR_DOMAIN_FACE) { + Array<bool> selection(mask.min_array_size()); + select_mesh_by_material(*mesh, material_, mask, selection); + return VArray<bool>::ForContainer(std::move(selection)); } + Array<bool> selection(mesh->totpoly); + select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection); + return mesh_component.attribute_try_adapt_domain<bool>( + VArray<bool>::ForContainer(std::move(selection)), ATTR_DOMAIN_FACE, domain); + return nullptr; } uint64_t hash() const override { - /* Some random constant hash. */ - return 91619626; + return get_default_hash(material_); } bool is_equal_to(const fn::FieldNode &other) const override { - return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr; + if (const MaterialSelectionFieldInput *other_material_selection = + dynamic_cast<const MaterialSelectionFieldInput *>(&other)) { + return material_ == other_material_selection->material_; + } + return false; } }; -static void geo_node_material_selection_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Material *material = params.extract_input<Material *>("Material"); Field<bool> material_field{std::make_shared<MaterialSelectionFieldInput>(material)}; params.set_output("Selection", std::move(material_field)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_material_selection_cc void register_node_type_geo_material_selection() { + namespace file_ns = blender::nodes::node_geo_material_selection_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_MATERIAL_SELECTION, "Material Selection", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_material_selection_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_material_selection_exec; + &ntype, GEO_NODE_MATERIAL_SELECTION, "Material Selection", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc new file mode 100644 index 00000000000..deb149fd0f0 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc @@ -0,0 +1,108 @@ +/* + * 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 "GEO_mesh_merge_by_distance.hh" +#include "GEO_point_merge_by_distance.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_merge_by_distance_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")) + .supported_type({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_MESH}); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Distance")).default_value(0.1f).min(0.0f).subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static PointCloud *pointcloud_merge_by_distance(const PointCloudComponent &src_points, + const float merge_distance, + const Field<bool> &selection_field) +{ + const int src_size = src_points.attribute_domain_size(ATTR_DOMAIN_POINT); + GeometryComponentFieldContext context{src_points, ATTR_DOMAIN_POINT}; + FieldEvaluator evaluator{context, src_size}; + evaluator.add(selection_field); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + if (selection.is_empty()) { + return nullptr; + } + + return geometry::point_merge_by_distance(src_points, merge_distance, selection); +} + +static std::optional<Mesh *> mesh_merge_by_distance(const MeshComponent &mesh_component, + const float merge_distance, + const Field<bool> &selection_field) +{ + const int src_size = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); + GeometryComponentFieldContext context{mesh_component, ATTR_DOMAIN_POINT}; + FieldEvaluator evaluator{context, src_size}; + evaluator.add(selection_field); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + if (selection.is_empty()) { + return nullptr; + } + + const Mesh &mesh = *mesh_component.get_for_read(); + return geometry::mesh_merge_by_distance_all(mesh, selection, merge_distance); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + const Field<bool> selection = params.extract_input<Field<bool>>("Selection"); + const float merge_distance = params.extract_input<float>("Distance"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_pointcloud()) { + PointCloud *result = pointcloud_merge_by_distance( + *geometry_set.get_component_for_read<PointCloudComponent>(), merge_distance, selection); + geometry_set.replace_pointcloud(result); + } + if (geometry_set.has_mesh()) { + std::optional<Mesh *> result = mesh_merge_by_distance( + *geometry_set.get_component_for_read<MeshComponent>(), merge_distance, selection); + if (result) { + geometry_set.replace_mesh(*result); + } + } + }); + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_merge_by_distance_cc + +void register_node_type_geo_merge_by_distance() +{ + namespace file_ns = blender::nodes::node_geo_merge_by_distance_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MERGE_BY_DISTANCE, "Merge by Distance", NODE_CLASS_GEOMETRY); + + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 9c477c639a2..b3b11b2e0e9 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 @@ -25,28 +25,34 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_primitive_circle_cc { -static void geo_node_mesh_primitive_circle_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryMeshCircle) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices").default_value(32).min(3); - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Int>(N_("Vertices")) + .default_value(32) + .min(3) + .description(N_("Number of vertices on the circle")); + b.add_input<decl::Float>(N_("Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance of the vertices from the origin")); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_mesh_primitive_circle_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); } -static void geo_node_mesh_primitive_circle_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryMeshCircle *node_storage = (NodeGeometryMeshCircle *)MEM_callocN( - sizeof(NodeGeometryMeshCircle), __func__); + NodeGeometryMeshCircle *node_storage = MEM_cnew<NodeGeometryMeshCircle>(__func__); node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NONE; @@ -132,12 +138,6 @@ static Mesh *create_circle_mesh(const float radius, copy_v3_v3(verts.last().co, float3(0)); } - /* Point all vertex normals in the up direction. */ - const short up_normal[3] = {0, 0, SHRT_MAX}; - for (MVert &vert : verts) { - copy_v3_v3_short(vert.no, up_normal); - } - /* Create outer edges. */ const short edge_flag = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? ME_LOOSEEDGE : @@ -192,42 +192,38 @@ static Mesh *create_circle_mesh(const float radius, return mesh; } -static void geo_node_mesh_primitive_circle_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const bNode &node = params.node(); - const NodeGeometryMeshCircle &storage = *(const NodeGeometryMeshCircle *)node.storage; - - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) - storage.fill_type; + const NodeGeometryMeshCircle &storage = node_storage(params.node()); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; const float radius = params.extract_input<float>("Radius"); const int verts_num = params.extract_input<int>("Vertices"); if (verts_num < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } - Mesh *mesh = create_circle_mesh(radius, verts_num, fill_type); - - BLI_assert(BKE_mesh_is_valid(mesh)); + Mesh *mesh = create_circle_mesh(radius, verts_num, fill); - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_circle_cc void register_node_type_geo_mesh_primitive_circle() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_circle_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_MESH_PRIMITIVE_CIRCLE, "Mesh Circle", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_circle_init); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CIRCLE, "Mesh Circle", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); node_type_storage( &ntype, "NodeGeometryMeshCircle", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_circle_exec; - ntype.draw_buttons = blender::nodes::geo_node_mesh_primitive_circle_layout; - ntype.declare = blender::nodes::geo_node_mesh_primitive_circle_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } 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 0d58476fc58..e0923344421 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 @@ -25,529 +25,797 @@ #include "node_geometry_util.hh" +#include <cmath> + namespace blender::nodes { -static void geo_node_mesh_primitive_cone_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Int>("Vertices").default_value(32).min(3); - b.add_input<decl::Float>("Radius Top").min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Radius Bottom").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); -} +struct ConeConfig { + float radius_top; + float radius_bottom; + float height; + int circle_segments; + int side_segments; + int fill_segments; + GeometryNodeMeshCircleFillType fill_type; + + bool top_is_point; + bool bottom_is_point; + /* The cone tip and a triangle fan filling are topologically identical. + * This simplifies the logic in some cases. */ + bool top_has_center_vert; + bool bottom_has_center_vert; + + /* Helpful quantities. */ + int tot_quad_rings; + int tot_edge_rings; + int tot_verts; + int tot_edges; + int tot_corners; + int tot_faces; + + /* Helpful vertex indices. */ + int first_vert; + int first_ring_verts_start; + int last_ring_verts_start; + int last_vert; + + /* Helpful edge indices. */ + int first_ring_edges_start; + int last_ring_edges_start; + int last_fan_edges_start; + int last_edge; + + /* Helpful face indices. */ + int top_faces_start; + int top_faces_len; + int side_faces_start; + int side_faces_len; + int bottom_faces_start; + int bottom_faces_len; + + ConeConfig(float radius_top, + float radius_bottom, + float depth, + int circle_segments, + int side_segments, + int fill_segments, + GeometryNodeMeshCircleFillType fill_type) + : radius_top(radius_top), + radius_bottom(radius_bottom), + height(0.5f * depth), + circle_segments(circle_segments), + side_segments(side_segments), + fill_segments(fill_segments), + fill_type(fill_type) + { + this->top_is_point = this->radius_top == 0.0f; + this->bottom_is_point = this->radius_bottom == 0.0f; + this->top_has_center_vert = this->top_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + this->bottom_has_center_vert = this->bottom_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + + this->tot_quad_rings = this->calculate_total_quad_rings(); + this->tot_edge_rings = this->calculate_total_edge_rings(); + this->tot_verts = this->calculate_total_verts(); + this->tot_edges = this->calculate_total_edges(); + this->tot_corners = this->calculate_total_corners(); + + this->first_vert = 0; + this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert; + this->last_vert = this->tot_verts - 1; + this->last_ring_verts_start = this->last_vert - this->circle_segments; + + this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0; + this->last_ring_edges_start = this->first_ring_edges_start + + this->tot_quad_rings * this->circle_segments * 2; + this->last_fan_edges_start = this->tot_edges - this->circle_segments; + this->last_edge = this->tot_edges - 1; + + this->top_faces_start = 0; + if (!this->top_is_point) { + this->top_faces_len = (fill_segments - 1) * circle_segments; + this->top_faces_len += this->top_has_center_vert ? circle_segments : 0; + this->top_faces_len += this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON ? 1 : 0; + } + else { + this->top_faces_len = 0; + } -static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) -{ - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); -} + this->side_faces_start = this->top_faces_len; + if (this->top_is_point && this->bottom_is_point) { + this->side_faces_len = 0; + } + else { + this->side_faces_len = side_segments * circle_segments; + } -static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( - sizeof(NodeGeometryMeshCone), __func__); + if (!this->bottom_is_point) { + this->bottom_faces_len = (fill_segments - 1) * circle_segments; + this->bottom_faces_len += this->bottom_has_center_vert ? circle_segments : 0; + this->bottom_faces_len += this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON ? 1 : 0; + } + else { + this->bottom_faces_len = 0; + } + this->bottom_faces_start = this->side_faces_start + this->side_faces_len; - node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + this->tot_faces = this->top_faces_len + this->side_faces_len + this->bottom_faces_len; + } - node->storage = node_storage; -} + private: + int calculate_total_quad_rings(); + int calculate_total_edge_rings(); + int calculate_total_verts(); + int calculate_total_edges(); + int calculate_total_corners(); +}; -static int vert_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_quad_rings() { - int vert_total = 0; - if (!top_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + if (top_is_point && bottom_is_point) { + return 0; } - else { - vert_total++; + + int quad_rings = 0; + + if (!top_is_point) { + quad_rings += fill_segments - 1; } + + quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1); + if (!bottom_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } - } - else { - vert_total++; + quad_rings += fill_segments - 1; } - return vert_total; + return quad_rings; } -static int edge_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edge_rings() { if (top_is_point && bottom_is_point) { - return 1; + return 0; } - int edge_total = 0; + int edge_rings = 0; + if (!top_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + edge_rings += fill_segments; } - edge_total += verts_num; + edge_rings += side_segments - 1; if (!bottom_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + edge_rings += fill_segments; } - return edge_total; + return edge_rings; } -static int corner_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_verts() { if (top_is_point && bottom_is_point) { - return 0; + return side_segments + 1; + } + + int vert_total = 0; + + if (top_has_center_vert) { + vert_total++; } - int corner_total = 0; if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + vert_total += circle_segments * fill_segments; } - if (!top_is_point && !bottom_is_point) { - corner_total += verts_num * 4; + vert_total += circle_segments * (side_segments - 1); + + if (!bottom_is_point) { + vert_total += circle_segments * fill_segments; } - else { - corner_total += verts_num * 3; + + if (bottom_has_center_vert) { + vert_total++; } - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + return vert_total; +} + +int ConeConfig::calculate_total_edges() +{ + if (top_is_point && bottom_is_point) { + return side_segments; } - return corner_total; + int edge_total = 0; + if (top_has_center_vert) { + edge_total += circle_segments; + } + + edge_total += circle_segments * (tot_quad_rings * 2 + 1); + + if (bottom_has_center_vert) { + edge_total += circle_segments; + } + + return edge_total; } -static int face_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_corners() { if (top_is_point && bottom_is_point) { return 0; } - int face_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + int corner_total = 0; + + if (top_has_center_vert) { + corner_total += (circle_segments * 3); + } + else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } - face_total += verts_num; + corner_total += tot_quad_rings * (circle_segments * 4); - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + if (bottom_has_center_vert) { + corner_total += (circle_segments * 3); + } + else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } - return face_total; + return corner_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) +static void calculate_cone_vertices(const MutableSpan<MVert> &verts, const ConeConfig &config) { - 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); + Array<float2> circle(config.circle_segments); + const float angle_delta = 2.0f * (M_PI / static_cast<float>(config.circle_segments)); 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; + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle); + circle[i].y = std::sin(angle); 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); - } - } - } + int vert_index = 0; - /* 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); - } + /* Top cone tip or triangle fan center. */ + if (config.top_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, config.height); } - 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); + + /* Top fill including the outer edge of the fill. */ + if (!config.top_is_point) { + const float top_fill_radius_delta = config.radius_top / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float top_fill_radius = top_fill_radius_delta * (i + 1); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * top_fill_radius; + const float y = circle[j].y * top_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, config.height); } } } - /* 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); - } + /* Rings along the side. */ + const float side_radius_delta = (config.radius_bottom - config.radius_top) / + static_cast<float>(config.side_segments); + const float height_delta = 2.0f * config.height / static_cast<float>(config.side_segments); + for (const int i : IndexRange(config.side_segments - 1)) { + const float ring_radius = config.radius_top + (side_radius_delta * (i + 1)); + const float ring_height = config.height - (height_delta * (i + 1)); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * ring_radius; + const float y = circle[j].y * ring_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, ring_height); } - 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); + } + + /* Bottom fill including the outer edge of the fill. */ + if (!config.bottom_is_point) { + const float bottom_fill_radius_delta = config.radius_bottom / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * bottom_fill_radius; + const float y = circle[j].y * bottom_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, -config.height); } } } - uv_attribute.save(); + /* Bottom cone tip or triangle fan center. */ + if (config.bottom_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, -config.height); + } } -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) +static void calculate_cone_edges(const MutableSpan<MEdge> &edges, const ConeConfig &config) { - 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_normals_tag_dirty(mesh); - return mesh; - } - - 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)); - 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); - const float angle_delta = 2.0f * (M_PI / static_cast<float>(verts_num)); - for (const int i : IndexRange(verts_num)) { - const float angle = i * angle_delta; - 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)); + int edge_index = 0; + + /* Edges for top cone tip or triangle fan */ + if (config.top_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.first_vert; + edge.v2 = config.first_ring_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } - 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)); + /* Rings and connecting edges between the rings. */ + for (const int i : IndexRange(config.tot_edge_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + /* Edge rings. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = this_ring_vert_start + ((j + 1) % config.circle_segments); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height)); + if (i == config.tot_edge_rings - 1) { + /* There is one fewer ring of connecting edges. */ + break; } - } - - /* 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; + /* Connecting edges. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = next_ring_vert_start + j; 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; + /* Edges for bottom triangle fan or tip. */ + if (config.bottom_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.last_ring_verts_start + i; + edge.v2 = config.last_vert; 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. */ +static void calculate_cone_faces(const MutableSpan<MLoop> &loops, + const MutableSpan<MPoly> &polys, + const ConeConfig &config) +{ int loop_index = 0; int poly_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + + if (config.top_has_center_vert) { + /* Top cone tip or center triangle fan in the fill. */ + const int top_center_vert = 0; + const int top_fan_edges_start = 0; + + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = verts_num; + poly.totloop = 3; - for (const int i : IndexRange(verts_num)) { - MLoop &loop = loops[loop_index++]; - loop.v = top_verts_start + i; - loop.e = top_edges_start + i; - } + MLoop &loop_a = loops[loop_index++]; + loop_a.v = config.first_ring_verts_start + i; + loop_a.e = config.first_ring_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = config.first_ring_verts_start + ((i + 1) % config.circle_segments); + loop_b.e = top_fan_edges_start + ((i + 1) % config.circle_segments); + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_center_vert; + loop_c.e = top_fan_edges_start + i; + } + } + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; + for (const int i : IndexRange(config.circle_segments)) { + MLoop &loop = loops[loop_index++]; + loop.v = i; + loop.e = i; } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { + } + + /* Quads connect one edge ring to the next one. */ + if (config.tot_quad_rings > 0) { + for (const int i : IndexRange(config.tot_quad_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + + const int this_ring_edges_start = config.first_ring_edges_start + + (i * 2 * config.circle_segments); + const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments); + const int ring_connections_start = this_ring_edges_start + config.circle_segments; + + for (const int j : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 3; + poly.totloop = 4; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = top_edges_start + i; + loop_a.v = this_ring_vert_start + j; + loop_a.e = ring_connections_start + j; 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; + loop_b.v = next_ring_vert_start + j; + loop_b.e = next_ring_edges_start + j; MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_center_vert_index; - loop_c.e = top_fan_edges_start + i; + loop_c.v = next_ring_vert_start + ((j + 1) % config.circle_segments); + loop_c.e = ring_connections_start + ((j + 1) % config.circle_segments); + MLoop &loop_d = loops[loop_index++]; + loop_d.v = this_ring_vert_start + ((j + 1) % config.circle_segments); + loop_d.e = this_ring_edges_start + j; } } } - /* 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)) { + if (config.bottom_has_center_vert) { + /* Bottom cone tip or center triangle fan in the fill. */ + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 4; + poly.totloop = 3; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; + loop_a.v = config.last_ring_verts_start + i; + loop_a.e = config.last_fan_edges_start + i; MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + i; - loop_b.e = bottom_edges_start + i; + loop_b.v = config.last_vert; + loop_b.e = config.last_fan_edges_start + (i + 1) % config.circle_segments; 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; + loop_c.v = config.last_ring_verts_start + (i + 1) % config.circle_segments; + loop_c.e = config.last_ring_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; + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; - 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; - } + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards to reverse surface normal. */ + MLoop &loop = loops[loop_index++]; + loop.v = config.last_vert - i; + loop.e = config.last_edge - ((i + 1) % config.circle_segments); + } + } +} + +static void calculate_selection_outputs(Mesh *mesh, + const ConeConfig &config, + ConeAttributeOutputs &attribute_outputs) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + + /* Populate "Top" selection output. */ + if (attribute_outputs.top_id) { + const bool face = !config.top_is_point && config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.top_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); + MutableSpan<bool> selection = attribute.as_span(); + + if (config.top_is_point) { + selection[config.first_vert] = true; } 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; + selection.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true); + } + attribute.save(); + } - 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; + /* Populate "Bottom" selection output. */ + if (attribute_outputs.bottom_id) { + const bool face = !config.bottom_is_point && + config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.bottom_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); + MutableSpan<bool> selection = attribute.as_span(); + + if (config.bottom_is_point) { + selection[config.last_vert] = true; + } + else { + selection + .slice(config.bottom_faces_start, + face ? config.bottom_faces_len : config.circle_segments) + .fill(true); + } + attribute.save(); + } + + /* Populate "Side" selection output. */ + if (attribute_outputs.side_id) { + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE); + MutableSpan<bool> selection = attribute.as_span(); + + selection.slice(config.side_faces_start, config.side_faces_len).fill(true); + attribute.save(); + } +} + +/** + * If the top is the cone tip or has a fill, it is unwrapped into a circle in the + * lower left quadrant of the UV. + * Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle + * in the lower right quadrant of the UV. + * If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into + * a rectangle that fills the top half of the UV (or the entire UV, if there are no fills). + */ +static void calculate_cone_uvs(Mesh *mesh, const ConeConfig &config) +{ + 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(config.circle_segments); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<float>(config.circle_segments); + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle) * 0.225f; + circle[i].y = std::sin(angle) * 0.225f; + angle += angle_delta; + } + + int loop_index = 0; + + /* Left circle of the UV representing the top fill or top cone tip. */ + if (config.top_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_left(0.25f, 0.25f); + const float radius_factor_delta = 1.0f / (config.top_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int left_circle_segment_count = config.top_is_point ? config.side_segments : + config.fill_segments; + + if (config.top_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = center_left; + } + } + else if (!config.top_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + } + } + /* The rest of the top fill is made out of quad rings. */ + for (const int i : IndexRange(1, left_circle_segment_count - 1)) { + const float inner_radius_factor = i * radius_factor_delta; + const float outer_radius_factor = (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = inner_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; } } } - /* 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; + if (!config.top_is_point && !config.bottom_is_point) { + /* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */ + const float bottom = (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; + const float x_delta = 1.0f / static_cast<float>(config.circle_segments); + const float y_delta = (1.0f - bottom) / static_cast<float>(config.side_segments); - 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; + for (const int i : IndexRange(config.side_segments)) { + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = float2(j * x_delta, i * y_delta + bottom); + uvs[loop_index++] = float2(j * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, i * y_delta + bottom); } } - 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; + /* Right circle of the UV representing the bottom fill or bottom cone tip. */ + if (config.bottom_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_right(0.75f, 0.25f); + const float radius_factor_delta = 1.0f / (config.bottom_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int right_circle_segment_count = config.bottom_is_point ? config.side_segments : + config.fill_segments; + + /* The bottom circle has to be created outside in to match the loop order. */ + for (const int i : IndexRange(right_circle_segment_count - 1)) { + const float outer_radius_factor = 1.0f - i * radius_factor_delta; + const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = outer_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + } + } + + if (config.bottom_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_right; + uvs[loop_index++] = center_right; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_right; + } + } + else if (!config.bottom_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards because of reversed face normal. */ + uvs[loop_index++] = radius_factor_delta * circle[config.circle_segments - 1 - i] + + center_right; } } } - BKE_mesh_normals_tag_dirty(mesh); + uv_attribute.save(); +} + +static Mesh *create_vertex_mesh() +{ + /* Returns a mesh with a single vertex at the origin. */ + Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0, 0); + copy_v3_fl3(mesh->mvert[0].co, 0.0f, 0.0f, 0.0f); + return mesh; +} - calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type); +Mesh *create_cylinder_or_cone_mesh(const float radius_top, + const float radius_bottom, + const float depth, + const int circle_segments, + const int side_segments, + const int fill_segments, + const GeometryNodeMeshCircleFillType fill_type, + ConeAttributeOutputs &attribute_outputs) +{ + const ConeConfig config( + radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type); + + /* Handle the case of a line / single point before everything else to avoid + * the need to check for it later. */ + if (config.top_is_point && config.bottom_is_point) { + if (config.height == 0.0f) { + return create_vertex_mesh(); + } + const float z_delta = -2.0f * config.height / static_cast<float>(config.side_segments); + const float3 start(0.0f, 0.0f, config.height); + const float3 delta(0.0f, 0.0f, z_delta); + return create_line_mesh(start, delta, config.tot_verts); + } + + Mesh *mesh = BKE_mesh_new_nomain( + config.tot_verts, config.tot_edges, 0, config.tot_corners, config.tot_faces); + 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_cone_vertices(verts, config); + calculate_cone_edges(edges, config); + calculate_cone_faces(loops, polys, config); + calculate_cone_uvs(mesh, config); + calculate_selection_outputs(mesh, config, attribute_outputs); return mesh; } -static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) +} // namespace blender::nodes + +namespace blender::nodes::node_geo_mesh_primitive_cone_cc { + +NODE_STORAGE_FUNCS(NodeGeometryMeshCone) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Vertices")) + .default_value(32) + .min(3) + .max(512) + .description(N_("Number of points on the circle at the top and bottom")); + b.add_input<decl::Int>(N_("Side Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("The number of edges running vertically along the side of the cone")); + b.add_input<decl::Int>(N_("Fill Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("Number of concentric rings used to fill the round face")); + b.add_input<decl::Float>(N_("Radius Top")) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the top circle of the cone")); + b.add_input<decl::Float>(N_("Radius Bottom")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the bottom circle of the cone")); + b.add_input<decl::Float>(N_("Depth")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Height of the generated cone")); + b.add_output<decl::Geometry>(N_("Mesh")); + b.add_output<decl::Bool>(N_("Top")).field_source(); + b.add_output<decl::Bool>(N_("Bottom")).field_source(); + b.add_output<decl::Bool>(N_("Side")).field_source(); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryMeshCone *node_storage = MEM_cnew<NodeGeometryMeshCone>(__func__); + + node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + + node->storage = node_storage; +} + +static void node_update(bNodeTree *ntree, bNode *node) { - const bNode &node = params.node(); - const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node.storage; + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = node_storage(*node); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; + const bool has_fill = fill != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(ntree, fill_subdiv_socket, has_fill); +} - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) - storage.fill_type; +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); +} - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryMeshCone &storage = node_storage(params.node()); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; + + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); + return; + } + + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_default_remaining_outputs(); + return; + } + + const bool no_fill = fill == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_default_remaining_outputs(); return; } @@ -555,27 +823,64 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) const float radius_bottom = params.extract_input<float>("Radius Bottom"); const float depth = params.extract_input<float>("Depth"); - Mesh *mesh = create_cylinder_or_cone_mesh( - radius_top, radius_bottom, depth, verts_num, fill_type); + ConeAttributeOutputs attribute_outputs; + if (params.output_is_required("Top")) { + attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection"); + } + if (params.output_is_required("Bottom")) { + attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection"); + } + if (params.output_is_required("Side")) { + attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection"); + } + + Mesh *mesh = create_cylinder_or_cone_mesh(radius_top, + radius_bottom, + depth, + circle_segments, + side_segments, + fill_segments, + fill, + attribute_outputs); /* 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)); + if (attribute_outputs.top_id) { + params.set_output("Top", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.top_id), params.attribute_producer_name())); + } + if (attribute_outputs.bottom_id) { + params.set_output( + "Bottom", + AnonymousAttributeFieldInput::Create<bool>(std::move(attribute_outputs.bottom_id), + params.attribute_producer_name())); + } + if (attribute_outputs.side_id) { + params.set_output("Side", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.side_id), params.attribute_producer_name())); + } + + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_cone_cc void register_node_type_geo_mesh_primitive_cone() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_cone_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cone_init); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryMeshCone", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cone_exec; - ntype.draw_buttons = blender::nodes::geo_node_mesh_primitive_cone_layout; - ntype.declare = blender::nodes::geo_node_mesh_primitive_cone_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } 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 af8ce02b3c1..5b67258a947 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 @@ -24,15 +24,6 @@ namespace blender::nodes { -static void geo_node_mesh_primitive_cube_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Vector>("Size").default_value(float3(1)).min(0.0f).subtype(PROP_TRANSLATION); - b.add_input<decl::Int>("Vertices X").default_value(2).min(2).max(1000); - b.add_input<decl::Int>("Vertices Y").default_value(2).min(2).max(1000); - b.add_input<decl::Int>("Vertices Z").default_value(2).min(2).max(1000); - b.add_output<decl::Geometry>("Geometry"); -} - struct CuboidConfig { float3 size; int verts_x; @@ -86,23 +77,37 @@ static void calculate_vertices(const CuboidConfig &config, MutableSpan<MVert> ve int vert_index = 0; - /* Though looping over all possible coordinates inside the cube only to skip them may be slow, - * the alternative is similar complexity to below in the poly index calculation. If this loop - * becomes a problem in the future it could be optimized, though only after proper performance - * testing. */ for (const int z : IndexRange(config.verts_z)) { - for (const int y : IndexRange(config.verts_y)) { - for (const int x : IndexRange(config.verts_x)) { - /* Only plot vertices on the surface of the cuboid. */ - if (ELEM(z, 0, config.edges_z) || ELEM(x, 0, config.edges_x) || - ELEM(y, 0, config.edges_y)) { - + if (ELEM(z, 0, config.edges_z)) { + /* Fill bottom and top. */ + const float z_pos = z_bottom + z_delta * z; + for (const int y : IndexRange(config.verts_y)) { + const float y_pos = y_front + y_delta * y; + for (const int x : IndexRange(config.verts_x)) { const float x_pos = x_left + x_delta * x; + copy_v3_v3(verts[vert_index++].co, float3(x_pos, y_pos, z_pos)); + } + } + } + else { + for (const int y : IndexRange(config.verts_y)) { + if (ELEM(y, 0, config.edges_y)) { + /* Fill y-sides. */ const float y_pos = y_front + y_delta * y; const float z_pos = z_bottom + z_delta * z; - copy_v3_v3(verts[vert_index].co, float3(x_pos, y_pos, z_pos)); - - vert_index++; + for (const int x : IndexRange(config.verts_x)) { + const float x_pos = x_left + x_delta * x; + copy_v3_v3(verts[vert_index++].co, float3(x_pos, y_pos, z_pos)); + } + } + else { + /* Fill x-sides. */ + const float x_pos = x_left; + const float y_pos = y_front + y_delta * y; + const float z_pos = z_bottom + z_delta * z; + copy_v3_v3(verts[vert_index++].co, float3(x_pos, y_pos, z_pos)); + const float x_pos2 = x_left + x_delta * config.edges_x; + copy_v3_v3(verts[vert_index++].co, float3(x_pos2, y_pos, z_pos)); } } } @@ -150,7 +155,7 @@ static void calculate_polys(const CuboidConfig &config, /* Calculate polys for Bottom faces. */ int vert_1_start = 0; - for (const int UNUSED(y) : IndexRange(config.edges_y)) { + for ([[maybe_unused]] const int y : IndexRange(config.edges_y)) { for (const int x : IndexRange(config.edges_x)) { const int vert_1 = vert_1_start + x; const int vert_2 = vert_1_start + config.verts_x + x; @@ -168,7 +173,7 @@ static void calculate_polys(const CuboidConfig &config, vert_1_start = 0; int vert_2_start = config.verts_x * config.verts_y; - for (const int UNUSED(z) : IndexRange(config.edges_z)) { + for ([[maybe_unused]] const int z : IndexRange(config.edges_z)) { for (const int x : IndexRange(config.edges_x)) { define_quad(polys, loops, @@ -191,7 +196,7 @@ static void calculate_polys(const CuboidConfig &config, (config.verts_x - 2) * (config.verts_y - 2)); vert_2_start = vert_1_start + config.verts_x; - for (const int UNUSED(y) : IndexRange(config.edges_y)) { + for ([[maybe_unused]] const int y : IndexRange(config.edges_y)) { for (const int x : IndexRange(config.edges_x)) { define_quad(polys, loops, @@ -423,6 +428,35 @@ Mesh *create_cuboid_mesh(const float3 size, return mesh; } +} // namespace blender::nodes + +namespace blender::nodes::node_geo_mesh_primitive_cube_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>(N_("Size")) + .default_value(float3(1)) + .min(0.0f) + .subtype(PROP_TRANSLATION) + .description(N_("Side length along each axis")); + b.add_input<decl::Int>(N_("Vertices X")) + .default_value(2) + .min(2) + .max(1000) + .description(N_("Number of vertices for the X side of the shape")); + b.add_input<decl::Int>(N_("Vertices Y")) + .default_value(2) + .min(2) + .max(1000) + .description(N_("Number of vertices for the Y side of the shape")); + b.add_input<decl::Int>(N_("Vertices Z")) + .default_value(2) + .min(2) + .max(1000) + .description(N_("Number of vertices for the Z side of the shape")); + b.add_output<decl::Geometry>(N_("Mesh")); +} + static Mesh *create_cube_mesh(const float3 size, const int verts_x, const int verts_y, @@ -456,19 +490,19 @@ static Mesh *create_cube_mesh(const float3 size, } if (verts_y == 1) { /* XZ plane. */ Mesh *mesh = create_grid_mesh(verts_x, verts_z, size.x, size.z); - transform_mesh(mesh, float3(0), float3(M_PI_2, 0.0f, 0.0f), float3(1)); + transform_mesh(*mesh, float3(0), float3(M_PI_2, 0.0f, 0.0f), float3(1)); return mesh; } /* YZ plane. */ Mesh *mesh = create_grid_mesh(verts_z, verts_y, size.z, size.y); - transform_mesh(mesh, float3(0), float3(0.0f, M_PI_2, 0.0f), float3(1)); + transform_mesh(*mesh, float3(0), float3(0.0f, M_PI_2, 0.0f), float3(1)); return mesh; } return create_cuboid_mesh(size, verts_x, verts_y, verts_z); } -static void geo_node_mesh_primitive_cube_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const float3 size = params.extract_input<float3>("Size"); const int verts_x = params.extract_input<int>("Vertices X"); @@ -476,23 +510,25 @@ static void geo_node_mesh_primitive_cube_exec(GeoNodeExecParams params) const int verts_z = params.extract_input<int>("Vertices Z"); if (verts_x < 1 || verts_y < 1 || verts_z < 1) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 1")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } Mesh *mesh = create_cube_mesh(size, verts_x, verts_y, verts_z); - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_cube_cc void register_node_type_geo_mesh_primitive_cube() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_cube_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CUBE, "Cube", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_primitive_cube_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cube_exec; + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CUBE, "Cube", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } 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 8c4defc3ca3..73f21cf31fa 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 @@ -25,81 +25,155 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_primitive_cylinder_cc { -static void geo_node_mesh_primitive_cylinder_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryMeshCylinder) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices") + b.add_input<decl::Int>(N_("Vertices")) .default_value(32) .min(3) - .max(4096) - .description("The number of vertices around the circumference"); - b.add_input<decl::Float>("Radius") + .max(512) + .description(N_("The number of vertices on the top and bottom circles")); + b.add_input<decl::Int>(N_("Side Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("The number of rectangular segments along each side")); + b.add_input<decl::Int>(N_("Fill Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("The number of concentric rings used to fill the round faces")); + b.add_input<decl::Float>(N_("Radius")) .default_value(1.0f) .min(0.0f) .subtype(PROP_DISTANCE) - .description("The radius of the cylinder"); - b.add_input<decl::Float>("Depth") + .description(N_("The radius of the cylinder")); + b.add_input<decl::Float>(N_("Depth")) .default_value(2.0f) .min(0.0f) .subtype(PROP_DISTANCE) - .description("The height of the cylinder on the Z axis"); - b.add_output<decl::Geometry>("Geometry"); + .description(N_("The height of the cylinder")); + b.add_output<decl::Geometry>(N_("Mesh")); + b.add_output<decl::Bool>(N_("Top")).field_source(); + b.add_output<decl::Bool>(N_("Side")).field_source(); + b.add_output<decl::Bool>(N_("Bottom")).field_source(); } -static void geo_node_mesh_primitive_cylinder_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); } -static void geo_node_mesh_primitive_cylinder_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryMeshCylinder *node_storage = (NodeGeometryMeshCylinder *)MEM_callocN( - sizeof(NodeGeometryMeshCylinder), __func__); + NodeGeometryMeshCylinder *node_storage = MEM_cnew<NodeGeometryMeshCylinder>(__func__); node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; node->storage = node_storage; } -static void geo_node_mesh_primitive_cylinder_exec(GeoNodeExecParams params) +static void node_update(bNodeTree *ntree, bNode *node) { - const bNode &node = params.node(); - const NodeGeometryMeshCylinder &storage = *(const NodeGeometryMeshCylinder *)node.storage; + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCylinder &storage = node_storage(*node); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; + const bool has_fill = fill != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(ntree, fill_subdiv_socket, has_fill); +} - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) - storage.fill_type; +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryMeshCylinder &storage = node_storage(params.node()); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; const float radius = params.extract_input<float>("Radius"); const float depth = params.extract_input<float>("Depth"); - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); + return; + } + + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_default_remaining_outputs(); + return; + } + + const bool no_fill = fill == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_default_remaining_outputs(); return; } + ConeAttributeOutputs attribute_outputs; + if (params.output_is_required("Top")) { + attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection"); + } + if (params.output_is_required("Bottom")) { + attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection"); + } + if (params.output_is_required("Side")) { + attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection"); + } + /* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */ - Mesh *mesh = create_cylinder_or_cone_mesh(radius, radius, depth, verts_num, fill_type); + Mesh *mesh = create_cylinder_or_cone_mesh(radius, + radius, + depth, + circle_segments, + side_segments, + fill_segments, + fill, + attribute_outputs); + + if (attribute_outputs.top_id) { + params.set_output("Top", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.top_id), params.attribute_producer_name())); + } + if (attribute_outputs.bottom_id) { + params.set_output( + "Bottom", + AnonymousAttributeFieldInput::Create<bool>(std::move(attribute_outputs.bottom_id), + params.attribute_producer_name())); + } + if (attribute_outputs.side_id) { + params.set_output("Side", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.side_id), params.attribute_producer_name())); + } - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_cylinder_cc void register_node_type_geo_mesh_primitive_cylinder() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_cylinder_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CYLINDER, "Cylinder", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cylinder_init); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CYLINDER, "Cylinder", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryMeshCylinder", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_mesh_primitive_cylinder_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cylinder_exec; - ntype.draw_buttons = blender::nodes::geo_node_mesh_primitive_cylinder_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index 858ef8648f8..ecb3c785212 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -27,15 +29,6 @@ namespace blender::nodes { -static void geo_node_mesh_primitive_grid_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Float>("Size X").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Size Y").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Int>("Vertices X").default_value(3).min(2).max(1000); - b.add_input<decl::Int>("Vertices Y").default_value(3).min(2).max(1000); - b.add_output<decl::Geometry>("Geometry"); -} - static void calculate_uvs( Mesh *mesh, Span<MVert> verts, Span<MLoop> loops, const float size_x, const float size_y) { @@ -47,11 +40,13 @@ static void calculate_uvs( 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_x * 0.5f) * dx; - uvs[i].y = (co.y + size_y * 0.5f) * dy; - } + threading::parallel_for(loops.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const float3 &co = verts[loops[i].v].co; + uvs[i].x = (co.x + size_x * 0.5f) * dx; + uvs[i].y = (co.y + size_y * 0.5f) * dy; + } + }); uv_attribute.save(); } @@ -79,72 +74,87 @@ Mesh *create_grid_mesh(const int verts_x, const float dy = edges_y == 0 ? 0.0f : size_y / edges_y; const float x_shift = edges_x / 2.0f; const float y_shift = edges_y / 2.0f; - for (const int x_index : IndexRange(verts_x)) { - for (const int y_index : IndexRange(verts_y)) { - const int vert_index = x_index * verts_y + y_index; - verts[vert_index].co[0] = (x_index - x_shift) * dx; - verts[vert_index].co[1] = (y_index - y_shift) * dy; - verts[vert_index].co[2] = 0.0f; + threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_offset = x * verts_y; + threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int vert_index = y_offset + y; + verts[vert_index].co[0] = (x - x_shift) * dx; + verts[vert_index].co[1] = (y - y_shift) * dy; + verts[vert_index].co[2] = 0.0f; + } + }); } - } - } - - /* Point all vertex normals in the up direction. */ - const short up_normal[3] = {0, 0, SHRT_MAX}; - for (MVert &vert : verts) { - copy_v3_v3_short(vert.no, up_normal); + }); } - /* Build the horizontal edges in the X direction. */ const int y_edges_start = 0; + const int x_edges_start = verts_x * edges_y; const short edge_flag = (edges_x == 0 || edges_y == 0) ? ME_LOOSEEDGE : ME_EDGEDRAW | ME_EDGERENDER; - int edge_index = 0; - for (const int x : IndexRange(verts_x)) { - for (const int y : IndexRange(edges_y)) { - const int vert_index = x * verts_y + y; - MEdge &edge = edges[edge_index++]; - edge.v1 = vert_index; - edge.v2 = vert_index + 1; - edge.flag = edge_flag; + + /* Build the horizontal edges in the X direction. */ + threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_vert_offset = x * verts_y; + const int y_edge_offset = y_edges_start + x * edges_y; + threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int vert_index = y_vert_offset + y; + MEdge &edge = edges[y_edge_offset + y]; + edge.v1 = vert_index; + edge.v2 = vert_index + 1; + edge.flag = edge_flag; + } + }); } - } + }); /* Build the vertical edges in the Y direction. */ - const int x_edges_start = edge_index; - for (const int y : IndexRange(verts_y)) { - for (const int x : IndexRange(edges_x)) { - const int vert_index = x * verts_y + y; - MEdge &edge = edges[edge_index++]; - edge.v1 = vert_index; - edge.v2 = vert_index + verts_y; - edge.flag = edge_flag; + threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int x_edge_offset = x_edges_start + y * edges_x; + threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int vert_index = x * verts_y + y; + MEdge &edge = edges[x_edge_offset + x]; + edge.v1 = vert_index; + edge.v2 = vert_index + verts_y; + edge.flag = edge_flag; + } + }); } - } - - int loop_index = 0; - int poly_index = 0; - for (const int x : IndexRange(edges_x)) { - for (const int y : IndexRange(edges_y)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 4; - const int vert_index = x * verts_y + y; - - MLoop &loop_a = loops[loop_index++]; - loop_a.v = vert_index; - loop_a.e = x_edges_start + edges_x * y + x; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = vert_index + verts_y; - loop_b.e = y_edges_start + edges_y * (x + 1) + y; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = vert_index + verts_y + 1; - loop_c.e = x_edges_start + edges_x * (y + 1) + x; - MLoop &loop_d = loops[loop_index++]; - loop_d.v = vert_index + 1; - loop_d.e = y_edges_start + edges_y * x + y; + }); + + threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_offset = x * edges_y; + threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int poly_index = y_offset + y; + const int loop_index = poly_index * 4; + MPoly &poly = polys[poly_index]; + poly.loopstart = loop_index; + poly.totloop = 4; + const int vert_index = x * verts_y + y; + + MLoop &loop_a = loops[loop_index]; + loop_a.v = vert_index; + loop_a.e = x_edges_start + edges_x * y + x; + MLoop &loop_b = loops[loop_index + 1]; + loop_b.v = vert_index + verts_y; + loop_b.e = y_edges_start + edges_y * (x + 1) + y; + MLoop &loop_c = loops[loop_index + 2]; + loop_c.v = vert_index + verts_y + 1; + loop_c.e = x_edges_start + edges_x * (y + 1) + x; + MLoop &loop_d = loops[loop_index + 3]; + loop_d.v = vert_index + 1; + loop_d.e = y_edges_start + edges_y * x + y; + } + }); } - } + }); if (mesh->totpoly != 0) { calculate_uvs(mesh, verts, loops, size_x, size_y); @@ -153,32 +163,62 @@ Mesh *create_grid_mesh(const int verts_x, return mesh; } -static void geo_node_mesh_primitive_grid_exec(GeoNodeExecParams params) +} // namespace blender::nodes + +namespace blender::nodes::node_geo_mesh_primitive_grid_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>(N_("Size X")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Side length of the plane in the X direction")); + b.add_input<decl::Float>(N_("Size Y")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Side length of the plane in the Y direction")); + b.add_input<decl::Int>(N_("Vertices X")) + .default_value(3) + .min(2) + .max(1000) + .description(N_("Number of vertices in the X direction")); + b.add_input<decl::Int>(N_("Vertices Y")) + .default_value(3) + .min(2) + .max(1000) + .description(N_("Number of vertices in the Y direction")); + b.add_output<decl::Geometry>(N_("Mesh")); +} + +static void node_geo_exec(GeoNodeExecParams params) { 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 < 1 || verts_y < 1) { - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } 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)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_grid_cc void register_node_type_geo_mesh_primitive_grid() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_grid_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_GRID, "Grid", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_primitive_grid_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_grid_exec; + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_GRID, "Grid", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_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 5ea7165ac31..28a505c5bb8 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 @@ -24,22 +24,31 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_primitive_ico_sphere_cc { -static void geo_node_mesh_primitive_ico_sphere_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Int>("Subdivisions").default_value(1).min(1).max(7); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Float>(N_("Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance from the generated points to the origin")); + b.add_input<decl::Int>(N_("Subdivisions")) + .default_value(1) + .min(1) + .max(7) + .description(N_("Number of subdivisions on top of the basic icosahedron")); + b.add_output<decl::Geometry>(N_("Mesh")); } static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) { const float4x4 transform = float4x4::identity(); - const BMeshCreateParams bmcp = {true}; + BMeshCreateParams bmesh_create_params{}; + bmesh_create_params.use_toolflags = true; const BMAllocTemplate allocsize = {0, 0, 0, 0}; - BMesh *bm = BM_mesh_create(&allocsize, &bmcp); + BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params); BM_data_layer_add_named(bm, &bm->ldata, CD_MLOOPUV, nullptr); BMO_op_callf(bm, @@ -60,24 +69,26 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) return mesh; } -static void geo_node_mesh_primitive_ico_sphere_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const int subdivisions = std::min(params.extract_input<int>("Subdivisions"), 10); const float radius = params.extract_input<float>("Radius"); Mesh *mesh = create_ico_sphere_mesh(subdivisions, radius); - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_ico_sphere_cc void register_node_type_geo_mesh_primitive_ico_sphere() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_ico_sphere_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, "Ico Sphere", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_primitive_ico_sphere_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_ico_sphere_exec; + &ntype, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, "Ico Sphere", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } 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 031223b5ca6..691267bccb8 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 @@ -23,22 +23,39 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_primitive_line_cc { -static void geo_node_mesh_primitive_line_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryMeshLine) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Count").default_value(10).min(1).max(10000); - b.add_input<decl::Float>("Resolution").default_value(1.0f).min(0.1f).subtype(PROP_DISTANCE); - b.add_input<decl::Vector>("Start Location").subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Offset").default_value({0.0f, 0.0f, 1.0f}).subtype(PROP_TRANSLATION); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Int>(N_("Count")) + .default_value(10) + .min(1) + .max(10000) + .description(N_("Number of vertices on the line")); + b.add_input<decl::Float>(N_("Resolution")) + .default_value(1.0f) + .min(0.1f) + .subtype(PROP_DISTANCE) + .description(N_("Length of each individual edge")); + b.add_input<decl::Vector>(N_("Start Location")) + .subtype(PROP_TRANSLATION) + .description(N_("Position of the first vertex")); + b.add_input<decl::Vector>(N_("Offset")) + .default_value({0.0f, 0.0f, 1.0f}) + .subtype(PROP_TRANSLATION) + .description(N_( + "In offset mode, the distance between each socket on each axis. In end points mode, the " + "position of the final vertex")); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_mesh_primitive_line_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -48,10 +65,9 @@ static void geo_node_mesh_primitive_line_layout(uiLayout *layout, } } -static void geo_node_mesh_primitive_line_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryMeshLine *node_storage = (NodeGeometryMeshLine *)MEM_callocN( - sizeof(NodeGeometryMeshLine), __func__); + NodeGeometryMeshLine *node_storage = MEM_cnew<NodeGeometryMeshLine>(__func__); node_storage->mode = GEO_NODE_MESH_LINE_MODE_OFFSET; node_storage->count_mode = GEO_NODE_MESH_LINE_COUNT_TOTAL; @@ -59,68 +75,74 @@ static void geo_node_mesh_primitive_line_init(bNodeTree *UNUSED(ntree), bNode *n node->storage = node_storage; } -static void geo_node_mesh_primitive_line_update(bNodeTree *UNUSED(tree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { bNodeSocket *count_socket = (bNodeSocket *)node->inputs.first; bNodeSocket *resolution_socket = count_socket->next; bNodeSocket *start_socket = resolution_socket->next; bNodeSocket *end_and_offset_socket = start_socket->next; - const NodeGeometryMeshLine &storage = *(const NodeGeometryMeshLine *)node->storage; - const GeometryNodeMeshLineMode mode = (const GeometryNodeMeshLineMode)storage.mode; - const GeometryNodeMeshLineCountMode count_mode = (const GeometryNodeMeshLineCountMode) + const NodeGeometryMeshLine &storage = node_storage(*node); + const GeometryNodeMeshLineMode mode = (GeometryNodeMeshLineMode)storage.mode; + const GeometryNodeMeshLineCountMode count_mode = (GeometryNodeMeshLineCountMode) storage.count_mode; node_sock_label(end_and_offset_socket, (mode == GEO_NODE_MESH_LINE_MODE_END_POINTS) ? N_("End Location") : N_("Offset")); - nodeSetSocketAvailability(resolution_socket, + nodeSetSocketAvailability(ntree, + resolution_socket, mode == GEO_NODE_MESH_LINE_MODE_END_POINTS && count_mode == GEO_NODE_MESH_LINE_COUNT_RESOLUTION); - nodeSetSocketAvailability(count_socket, + nodeSetSocketAvailability(ntree, + count_socket, mode == GEO_NODE_MESH_LINE_MODE_OFFSET || count_mode == GEO_NODE_MESH_LINE_COUNT_TOTAL); } -static void fill_edge_data(MutableSpan<MEdge> edges) +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - for (const int i : edges.index_range()) { - edges[i].v1 = i; - edges[i].v2 = i + 1; - edges[i].flag |= ME_LOOSEEDGE; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + if (params.in_out() == SOCK_OUT) { + search_link_ops_for_declarations(params, declaration.outputs()); + return; } -} - -Mesh *create_line_mesh(const float3 start, const float3 delta, const int count) -{ - if (count < 1) { - return nullptr; + else if (params.node_tree().typeinfo->validate_link( + static_cast<eNodeSocketDatatype>(params.other_socket().type), SOCK_FLOAT)) { + params.add_item(IFACE_("Count"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_OFFSET; + params.connect_available_socket(node, "Count"); + }); + params.add_item(IFACE_("Resolution"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_OFFSET; + node_storage(node).count_mode = GEO_NODE_MESH_LINE_COUNT_RESOLUTION; + params.connect_available_socket(node, "Resolution"); + }); + params.add_item(IFACE_("Start Location"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + params.connect_available_socket(node, "Start Location"); + }); + params.add_item(IFACE_("Offset"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + params.connect_available_socket(node, "Offset"); + }); + /* The last socket is reused in end points mode. */ + params.add_item(IFACE_("End Location"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_END_POINTS; + params.connect_available_socket(node, "Offset"); + }); } - - 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}; - - short normal[3]; - normal_float_to_short_v3(normal, delta.normalized()); - - for (const int i : verts.index_range()) { - copy_v3_v3(verts[i].co, start + delta * i); - copy_v3_v3_short(verts[i].no, normal); - } - - fill_edge_data(edges); - - return mesh; } -static void geo_node_mesh_primitive_line_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const NodeGeometryMeshLine &storage = *(const NodeGeometryMeshLine *)params.node().storage; - const GeometryNodeMeshLineMode mode = (const GeometryNodeMeshLineMode)storage.mode; - const GeometryNodeMeshLineCountMode count_mode = (const GeometryNodeMeshLineCountMode) + const NodeGeometryMeshLine &storage = node_storage(params.node()); + const GeometryNodeMeshLineMode mode = (GeometryNodeMeshLineMode)storage.mode; + const GeometryNodeMeshLineCountMode count_mode = (GeometryNodeMeshLineCountMode) storage.count_mode; Mesh *mesh = nullptr; @@ -133,8 +155,8 @@ static void geo_node_mesh_primitive_line_exec(GeoNodeExecParams params) if (count_mode == GEO_NODE_MESH_LINE_COUNT_RESOLUTION) { /* Don't allow asymptotic count increase for low resolution values. */ const float resolution = std::max(params.extract_input<float>("Resolution"), 0.0001f); - const int count = total_delta.length() / resolution + 1; - const float3 delta = total_delta.normalized() * resolution; + const int count = math::length(total_delta) / resolution + 1; + const float3 delta = math::normalize(total_delta) * resolution; mesh = create_line_mesh(start, delta, count); } else if (count_mode == GEO_NODE_MESH_LINE_COUNT_TOTAL) { @@ -154,22 +176,58 @@ static void geo_node_mesh_primitive_line_exec(GeoNodeExecParams params) mesh = create_line_mesh(start, delta, count); } - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); +} + +} // namespace blender::nodes::node_geo_mesh_primitive_line_cc + +namespace blender::nodes { + +static void fill_edge_data(MutableSpan<MEdge> edges) +{ + for (const int i : edges.index_range()) { + edges[i].v1 = i; + edges[i].v2 = i + 1; + edges[i].flag |= ME_LOOSEEDGE; + } +} + +Mesh *create_line_mesh(const float3 start, const float3 delta, const int count) +{ + if (count < 1) { + return nullptr; + } + + 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}; + + for (const int i : verts.index_range()) { + copy_v3_v3(verts[i].co, start + delta * i); + } + + fill_edge_data(edges); + + return mesh; } } // namespace blender::nodes void register_node_type_geo_mesh_primitive_line() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_line_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_LINE, "Mesh Line", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_primitive_line_declare; - node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_line_init); - node_type_update(&ntype, blender::nodes::geo_node_mesh_primitive_line_update); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_LINE, "Mesh Line", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryMeshLine", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_line_exec; - ntype.draw_buttons = blender::nodes::geo_node_mesh_primitive_line_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype); } 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 6fd6cdf5747..751cf917f6f 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 @@ -25,14 +25,26 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_primitive_uv_sphere_cc { -static void geo_node_mesh_primitive_uv_shpere_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Segments").default_value(32).min(3).max(1024); - b.add_input<decl::Int>("Rings").default_value(16).min(2).max(1024); - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Int>(N_("Segments")) + .default_value(32) + .min(3) + .max(1024) + .description(N_("Horizontal resolution of the sphere")); + b.add_input<decl::Int>(N_("Rings")) + .default_value(16) + .min(2) + .max(1024) + .description(N_("The number of horizontal rings")); + b.add_input<decl::Float>(N_("Radius")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Distance from the generated points to the origin")); + b.add_output<decl::Geometry>(N_("Mesh")); } static int sphere_vert_total(const int segments, const int rings) @@ -59,7 +71,12 @@ static int sphere_face_total(const int segments, const int rings) return quads + triangles; } +/** + * Also calculate vertex normals here, since the calculation is trivial, and it allows avoiding the + * calculation later, if it's necessary. The vertex normals are just the normalized positions. + */ static void calculate_sphere_vertex_data(MutableSpan<MVert> verts, + MutableSpan<float3> vert_normals, const float radius, const int segments, const int rings) @@ -68,7 +85,7 @@ static void calculate_sphere_vertex_data(MutableSpan<MVert> verts, const float delta_phi = (2.0f * 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)); + vert_normals.first() = float3(0.0f, 0.0f, 1.0f); int vert_index = 1; for (const int ring : IndexRange(1, rings - 1)) { @@ -80,13 +97,13 @@ static void calculate_sphere_vertex_data(MutableSpan<MVert> verts, 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)); + vert_normals[vert_index] = float3(x, y, z); vert_index++; } } 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)); + vert_normals.last() = float3(0.0f, 0.0f, -1.0f); } static void calculate_sphere_edge_indices(MutableSpan<MEdge> edges, @@ -166,7 +183,7 @@ static void calculate_sphere_faces(MutableSpan<MLoop> loops, int ring_vert_index_start = 1; int ring_edge_index_start = segments; - for (const int UNUSED(ring) : IndexRange(1, rings - 2)) { + for ([[maybe_unused]] const int 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; @@ -267,7 +284,9 @@ static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; - calculate_sphere_vertex_data(verts, radius, segments, rings); + MutableSpan vert_normals{(float3 *)BKE_mesh_vertex_normals_for_write(mesh), mesh->totvert}; + calculate_sphere_vertex_data(verts, vert_normals, radius, segments, rings); + BKE_mesh_vertex_normals_clear_dirty(mesh); calculate_sphere_edge_indices(edges, segments, rings); @@ -275,12 +294,10 @@ static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const calculate_sphere_uvs(mesh, segments, rings); - BLI_assert(BKE_mesh_is_valid(mesh)); - return mesh; } -static void geo_node_mesh_primitive_uv_sphere_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { const int segments_num = params.extract_input<int>("Segments"); const int rings_num = params.extract_input<int>("Rings"); @@ -291,25 +308,26 @@ static void geo_node_mesh_primitive_uv_sphere_exec(GeoNodeExecParams params) if (rings_num < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Rings must be at least 3")); } - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); return; } const float radius = params.extract_input<float>("Radius"); Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num); - params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_uv_sphere_cc void register_node_type_geo_mesh_primitive_uv_sphere() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_uv_sphere_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, "UV Sphere", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_primitive_uv_shpere_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_uv_sphere_exec; + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, "UV Sphere", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc index c436f5bd480..6d8a2fac8ad 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc @@ -23,13 +23,13 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_subdivide_cc { -static void geo_node_mesh_subdivide_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Level").default_value(1).min(0).max(6); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Int>(N_("Level")).default_value(1).min(0).max(6); + b.add_output<decl::Geometry>(N_("Mesh")); } static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int level) @@ -72,14 +72,14 @@ static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int lev BKE_subdiv_free(subdiv); } -static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); #ifndef WITH_OPENSUBDIV params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without OpenSubdiv")); - params.set_output("Geometry", std::move(geometry_set)); + params.set_default_remaining_outputs(); return; #endif @@ -87,24 +87,26 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11); if (subdiv_level == 0) { - params.set_output("Geometry", std::move(geometry_set)); + params.set_output("Mesh", std::move(geometry_set)); return; } geometry_set.modify_geometry_sets( [&](GeometrySet &geometry_set) { geometry_set_mesh_subdivide(geometry_set, subdiv_level); }); - params.set_output("Geometry", std::move(geometry_set)); + params.set_output("Mesh", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_subdivide_cc void register_node_type_geo_mesh_subdivide() { + namespace file_ns = blender::nodes::node_geo_mesh_subdivide_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_SUBDIVIDE, "Mesh Subdivide", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_subdivide_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_subdivide_exec; + geo_node_type_base(&ntype, GEO_NODE_SUBDIVIDE_MESH, "Subdivide Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } 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..0f0fb3c230a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -0,0 +1,71 @@ +/* + * 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 "GEO_mesh_to_curve.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_to_curve_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_mesh()) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>(); + GeometryComponentFieldContext context{component, ATTR_DOMAIN_EDGE}; + fn::FieldEvaluator evaluator{context, component.attribute_domain_size(ATTR_DOMAIN_EDGE)}; + evaluator.add(params.get_input<Field<bool>>("Selection")); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + if (selection.size() == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + std::unique_ptr<CurveEval> curve = geometry::mesh_to_curve_convert(component, selection); + geometry_set.replace_curve(curve.release()); + geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES}); + }); + + params.set_output("Curve", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_mesh_to_curve_cc + +void register_node_type_geo_mesh_to_curve() +{ + namespace file_ns = blender::nodes::node_geo_mesh_to_curve_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc index 2f59a3c968b..d0546cd2583 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -26,31 +26,32 @@ using blender::Array; -namespace blender::nodes { +namespace blender::nodes::node_geo_mesh_to_points_cc { -static void geo_node_mesh_to_points_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryMeshToPoints) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Mesh"); - b.add_input<decl::Vector>("Position").implicit_field(); - b.add_input<decl::Float>("Radius") + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Vector>(N_("Position")).implicit_field(); + b.add_input<decl::Float>(N_("Radius")) .default_value(0.05f) .min(0.0f) .subtype(PROP_DISTANCE) .supports_field(); - b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); - b.add_output<decl::Geometry>("Points"); + b.add_output<decl::Geometry>(N_("Points")); } -static void geo_node_mesh_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); } -static void geo_node_mesh_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryMeshToPoints *data = (NodeGeometryMeshToPoints *)MEM_callocN( - sizeof(NodeGeometryMeshToPoints), __func__); - data->mode = GEO_NODE_MESH_TO_POINTS_FACES; + NodeGeometryMeshToPoints *data = MEM_cnew<NodeGeometryMeshToPoints>(__func__); + data->mode = GEO_NODE_MESH_TO_POINTS_VERTICES; node->storage = data; } @@ -81,10 +82,15 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); return; } - fn::FieldEvaluator selection_evaluator{field_context, domain_size}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + /* Evaluating directly into the point cloud doesn't work because we are not using the full + * "min_array_size" array but compressing the selected elements into the final array with no + * gaps. */ + evaluator.add(position_field); + evaluator.add(radius_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); @@ -92,13 +98,6 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, PointCloudComponent &point_component = geometry_set.get_component_for_write<PointCloudComponent>(); - /* Evaluating directly into the point cloud doesn't work because we are not using the full - * "min_array_size" array but compressing the selected elements into the final array with no - * gaps. */ - fn::FieldEvaluator evaluator{field_context, &selection}; - evaluator.add(position_field); - evaluator.add(radius_field); - evaluator.evaluate(); copy_attribute_to_points(evaluator.get_evaluated<float3>(0), selection, {(float3 *)pointcloud->co, pointcloud->totpoint}); @@ -113,14 +112,14 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const CustomDataType data_type = entry.value.data_type; - GVArrayPtr src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); + GVArray src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); OutputAttribute dst = point_component.attribute_try_get_for_output_only( attribute_id, ATTR_DOMAIN_POINT, data_type); if (dst && src) { attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); - GVArray_Typed<T> src_typed{*src}; - copy_attribute_to_points(*src_typed, selection, dst.as_span().typed<T>()); + VArray<T> src_typed = src.typed<T>(); + copy_attribute_to_points(src_typed, selection, dst.as_span().typed<T>()); }); dst.save(); } @@ -129,7 +128,7 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); } -static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); Field<float3> position = params.extract_input<Field<float3>>("Position"); @@ -144,8 +143,7 @@ static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) FieldOperation(max_zero_fn, {std::move(radius)})); Field<float> positive_radius(std::move(max_zero_op), 0); - const NodeGeometryMeshToPoints &storage = - *(const NodeGeometryMeshToPoints *)params.node().storage; + const NodeGeometryMeshToPoints &storage = node_storage(params.node()); const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode; geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { @@ -172,17 +170,19 @@ static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) params.set_output("Points", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_to_points_cc void register_node_type_geo_mesh_to_points() { + namespace file_ns = blender::nodes::node_geo_mesh_to_points_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_TO_POINTS, "Mesh to Points", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_mesh_to_points_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_points_exec; - node_type_init(&ntype, blender::nodes::geo_node_mesh_to_points_init); - ntype.draw_buttons = blender::nodes::geo_node_mesh_to_points_layout; + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_POINTS, "Mesh to Points", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + ntype.draw_buttons = file_ns::node_layout; node_type_storage( &ntype, "NodeGeometryMeshToPoints", node_free_standard_storage, node_copy_standard_storage); 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 389acc40f0f..d32875d2627 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -21,92 +21,106 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_object_info_cc { -static void geo_node_object_info_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryObjectInfo) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Object>("Object").hide_label(); - b.add_output<decl::Vector>("Location"); - b.add_output<decl::Vector>("Rotation"); - b.add_output<decl::Vector>("Scale"); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Object>(N_("Object")).hide_label(); + b.add_input<decl::Bool>(N_("As Instance")) + .description(N_("Output the entire object as single instance. " + "This allows instancing non-geometry object types")); + b.add_output<decl::Vector>(N_("Location")); + b.add_output<decl::Vector>(N_("Rotation")); + b.add_output<decl::Vector>(N_("Scale")); + b.add_output<decl::Geometry>(N_("Geometry")); } -static void geo_node_object_info_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "transform_space", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } -static void geo_node_object_info_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - const bNode &bnode = params.node(); - NodeGeometryObjectInfo *node_storage = (NodeGeometryObjectInfo *)bnode.storage; - const bool transform_space_relative = (node_storage->transform_space == + const NodeGeometryObjectInfo &storage = node_storage(params.node()); + const bool transform_space_relative = (storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); Object *object = params.get_input<Object *>("Object"); - float3 location = {0, 0, 0}; - float3 rotation = {0, 0, 0}; - float3 scale = {0, 0, 0}; - GeometrySet geometry_set; - const Object *self_object = params.self_object(); + if (object == nullptr) { + params.set_default_remaining_outputs(); + return; + } - if (object != nullptr) { - float transform[4][4]; - mul_m4_m4m4(transform, self_object->imat, object->obmat); + const float4x4 &object_matrix = object->obmat; + const float4x4 transform = float4x4(self_object->imat) * object_matrix; - float quaternion[4]; - if (transform_space_relative) { - mat4_decompose(location, quaternion, scale, transform); - } - else { - mat4_decompose(location, quaternion, scale, object->obmat); + if (transform_space_relative) { + params.set_output("Location", transform.translation()); + params.set_output("Rotation", transform.to_euler()); + params.set_output("Scale", transform.scale()); + } + else { + params.set_output("Location", object_matrix.translation()); + params.set_output("Rotation", object_matrix.to_euler()); + params.set_output("Scale", object_matrix.scale()); + } + + if (params.output_is_required("Geometry")) { + if (object == self_object) { + params.error_message_add(NodeWarningType::Error, + TIP_("Geometry cannot be retrieved from the modifier object")); + params.set_default_remaining_outputs(); + return; } - quat_to_eul(rotation, quaternion); - if (object != self_object) { + GeometrySet geometry_set; + if (params.get_input<bool>("As Instance")) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); const int handle = instances.add_reference(*object); - if (transform_space_relative) { instances.add_instance(handle, transform); } else { - float unit_transform[4][4]; - unit_m4(unit_transform); - instances.add_instance(handle, unit_transform); + instances.add_instance(handle, float4x4::identity()); + } + } + else { + geometry_set = bke::object_get_evaluated_geometry_set(*object); + if (transform_space_relative) { + transform_geometry_set(geometry_set, transform, *params.depsgraph()); } } - } - params.set_output("Location", location); - params.set_output("Rotation", rotation); - params.set_output("Scale", scale); - params.set_output("Geometry", geometry_set); + params.set_output("Geometry", geometry_set); + } } -static void geo_node_object_info_node_init(bNodeTree *UNUSED(tree), bNode *node) +static void node_node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeGeometryObjectInfo *data = (NodeGeometryObjectInfo *)MEM_callocN( - sizeof(NodeGeometryObjectInfo), __func__); + NodeGeometryObjectInfo *data = MEM_cnew<NodeGeometryObjectInfo>(__func__); data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; node->storage = data; } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_object_info_cc void register_node_type_geo_object_info() { + namespace file_ns = blender::nodes::node_geo_object_info_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_OBJECT_INFO, "Object Info", NODE_CLASS_INPUT, 0); - node_type_init(&ntype, blender::nodes::geo_node_object_info_node_init); + geo_node_type_base(&ntype, GEO_NODE_OBJECT_INFO, "Object Info", NODE_CLASS_INPUT); + node_type_init(&ntype, file_ns::node_node_init); node_type_storage( &ntype, "NodeGeometryObjectInfo", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_object_info_exec; - ntype.draw_buttons = blender::nodes::geo_node_object_info_layout; - ntype.declare = blender::nodes::geo_node_object_info_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index afd0ced6360..f3da591f684 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -21,15 +21,15 @@ #include "node_geometry_util.hh" -using blender::Array; +namespace blender::nodes::node_geo_points_to_vertices_cc { -namespace blender::nodes { +using blender::Array; -static void geo_node_points_to_vertices_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Points"); - b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); - b.add_output<decl::Geometry>("Mesh"); + b.add_input<decl::Geometry>(N_("Points")).supported_type(GEO_COMPONENT_TYPE_POINT_CLOUD); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>(N_("Mesh")); } template<typename T> @@ -74,15 +74,15 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const CustomDataType data_type = entry.value.data_type; - GVArrayPtr src = point_component->attribute_get_for_read( + GVArray src = point_component->attribute_get_for_read( attribute_id, ATTR_DOMAIN_POINT, data_type); OutputAttribute dst = mesh_component.attribute_try_get_for_output_only( attribute_id, ATTR_DOMAIN_POINT, data_type); if (dst && src) { attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); - GVArray_Typed<T> src_typed{*src}; - VArray_Span<T> src_typed_span{*src_typed}; + VArray<T> src_typed = src.typed<T>(); + VArray_Span<T> src_typed_span{src_typed}; copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed<T>()); }); dst.save(); @@ -92,7 +92,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); } -static void geo_node_points_to_vertices_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); @@ -104,15 +104,17 @@ static void geo_node_points_to_vertices_exec(GeoNodeExecParams params) params.set_output("Mesh", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_points_to_vertices_cc void register_node_type_geo_points_to_vertices() { + namespace file_ns = blender::nodes::node_geo_points_to_vertices_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_POINTS_TO_VERTICES, "Points to Vertices", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_points_to_vertices_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_points_to_vertices_exec; + &ntype, GEO_NODE_POINTS_TO_VERTICES, "Points to Vertices", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } 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 new file mode 100644 index 00000000000..c165bcf8e35 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.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. + */ + +#ifdef WITH_OPENVDB +# include <openvdb/openvdb.h> +# include <openvdb/tools/LevelSetUtil.h> +# include <openvdb/tools/ParticlesToLevelSet.h> +#endif + +#include "node_geometry_util.hh" + +#include "BKE_lib_id.h" +#include "BKE_volume.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes::node_geo_points_to_volume_cc { + +NODE_STORAGE_FUNCS(NodeGeometryPointsToVolume) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Points")); + b.add_input<decl::Float>(N_("Density")).default_value(1.0f).min(0.0f); + b.add_input<decl::Float>(N_("Voxel Size")) + .default_value(0.3f) + .min(0.01f) + .subtype(PROP_DISTANCE) + .make_available([](bNode &node) { + node_storage(node).resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE; + }); + b.add_input<decl::Float>(N_("Voxel Amount")) + .default_value(64.0f) + .min(0.0f) + .make_available([](bNode &node) { + node_storage(node).resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + }); + b.add_input<decl::Float>(N_("Radius")) + .default_value(0.5f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + b.add_output<decl::Geometry>(N_("Volume")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "resolution_mode", 0, IFACE_("Resolution"), ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = MEM_cnew<NodeGeometryPointsToVolume>(__func__); + data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryPointsToVolume &storage = node_storage(*node); + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(ntree, + voxel_amount_socket, + storage.resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); + nodeSetSocketAvailability(ntree, + voxel_size_socket, + storage.resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); +} + +#ifdef WITH_OPENVDB +namespace { +/* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */ +struct ParticleList { + using PosType = openvdb::Vec3R; + + Span<float3> positions; + Span<float> radii; + + size_t size() const + { + return (size_t)positions.size(); + } + + void getPos(size_t n, openvdb::Vec3R &xyz) const + { + xyz = &positions[n].x; + } + + void getPosRad(size_t n, openvdb::Vec3R &xyz, openvdb::Real &radius) const + { + xyz = &positions[n].x; + radius = radii[n]; + } +}; +} // namespace + +static openvdb::FloatGrid::Ptr generate_volume_from_points(const Span<float3> positions, + const Span<float> radii, + const float density) +{ + /* Create a new grid that will be filled. #ParticlesToLevelSet requires the background value to + * be positive. It will be set to zero later on. */ + openvdb::FloatGrid::Ptr new_grid = openvdb::FloatGrid::create(1.0f); + + /* Create a narrow-band level set grid based on the positions and radii. */ + openvdb::tools::ParticlesToLevelSet op{*new_grid}; + /* Don't ignore particles based on their radius. */ + op.setRmin(0.0f); + op.setRmax(FLT_MAX); + ParticleList particles{positions, radii}; + op.rasterizeSpheres(particles); + op.finalize(); + + /* Convert the level set to a fog volume. This also sets the background value to zero. Inside the + * fog there will be a density of 1. */ + openvdb::tools::sdfToFogVolume(*new_grid); + + /* Take the desired density into account. */ + openvdb::tools::foreach (new_grid->beginValueOn(), + [&](const openvdb::FloatGrid::ValueOnIter &iter) { + iter.modifyValue([&](float &value) { value *= density; }); + }); + return new_grid; +} + +static float compute_voxel_size(const GeoNodeExecParams ¶ms, + Span<float3> positions, + const float radius) +{ + const NodeGeometryPointsToVolume &storage = node_storage(params.node()); + + if (storage.resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE) { + return params.get_input<float>("Voxel Size"); + } + + if (positions.is_empty()) { + return 0.0f; + } + + float3 min, max; + INIT_MINMAX(min, max); + minmax_v3v3_v3_array(min, max, (float(*)[3])positions.data(), positions.size()); + + const float voxel_amount = params.get_input<float>("Voxel Amount"); + if (voxel_amount <= 1) { + return 0.0f; + } + + /* The voxel size adapts to the final size of the volume. */ + const float diagonal = math::distance(min, max); + const float extended_diagonal = diagonal + 2.0f * radius; + const float voxel_size = extended_diagonal / voxel_amount; + return voxel_size; +} + +static void gather_point_data_from_component(GeoNodeExecParams ¶ms, + const GeometryComponent &component, + Vector<float3> &r_positions, + Vector<float> &r_radii) +{ + VArray<float3> positions = component.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + + Field<float> radius_field = params.get_input<Field<float>>("Radius"); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + + r_positions.resize(r_positions.size() + domain_size); + positions.materialize(r_positions.as_mutable_span().take_back(domain_size)); + + r_radii.resize(r_radii.size() + domain_size); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add_with_destination(radius_field, r_radii.as_mutable_span().take_back(domain_size)); + evaluator.evaluate(); +} + +static void convert_to_grid_index_space(const float voxel_size, + MutableSpan<float3> positions, + MutableSpan<float> radii) +{ + const float voxel_size_inv = 1.0f / voxel_size; + for (const int i : positions.index_range()) { + positions[i] *= voxel_size_inv; + /* Better align generated grid with source points. */ + positions[i] -= float3(0.5f); + radii[i] *= voxel_size_inv; + } +} + +static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms, + GeometrySet &r_geometry_set) +{ + Vector<float3> positions; + Vector<float> radii; + + if (r_geometry_set.has<MeshComponent>()) { + gather_point_data_from_component( + params, *r_geometry_set.get_component_for_read<MeshComponent>(), positions, radii); + } + if (r_geometry_set.has<PointCloudComponent>()) { + gather_point_data_from_component( + params, *r_geometry_set.get_component_for_read<PointCloudComponent>(), positions, radii); + } + if (r_geometry_set.has<CurveComponent>()) { + gather_point_data_from_component( + params, *r_geometry_set.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); + if (voxel_size == 0.0f || positions.is_empty()) { + return; + } + + Volume *volume = (Volume *)BKE_id_new_nomain(ID_VO, nullptr); + BKE_volume_init_grids(volume); + + const float density = params.get_input<float>("Density"); + convert_to_grid_index_space(voxel_size, positions, radii); + openvdb::FloatGrid::Ptr new_grid = generate_volume_from_points(positions, radii, density); + new_grid->transform().postScale(voxel_size); + BKE_volume_grid_add_vdb(*volume, "density", std::move(new_grid)); + + r_geometry_set.keep_only({GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_INSTANCES}); + r_geometry_set.replace_volume(volume); +} +#endif + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + +#ifdef WITH_OPENVDB + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + initialize_volume_component_from_points(params, geometry_set); + }); + params.set_output("Volume", std::move(geometry_set)); +#else + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenVDB")); + params.set_default_remaining_outputs(); +#endif +} + +} // namespace blender::nodes::node_geo_points_to_volume_cc + +void register_node_type_geo_points_to_volume() +{ + namespace file_ns = blender::nodes::node_geo_points_to_volume_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY); + node_type_storage(&ntype, + "NodeGeometryPointsToVolume", + node_free_standard_storage, + node_copy_standard_storage); + node_type_size(&ntype, 170, 120, 700); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc index 2b1de5fbf95..3f509942f7c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc @@ -27,30 +27,33 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_proximity_cc { -static void geo_node_proximity_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryProximity) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Source Position").implicit_field(); - b.add_input<decl::Geometry>("Target"); - b.add_output<decl::Vector>("Position").dependent_field(); - b.add_output<decl::Float>("Distance").dependent_field(); + b.add_input<decl::Geometry>(N_("Target")) + .only_realized_data() + .supported_type({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD}); + b.add_input<decl::Vector>(N_("Source Position")).implicit_field(); + b.add_output<decl::Vector>(N_("Position")).dependent_field(); + b.add_output<decl::Float>(N_("Distance")).dependent_field(); } -static void geo_node_proximity_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "target_element", 0, "", ICON_NONE); } static void geo_proximity_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryProximity *node_storage = (NodeGeometryProximity *)MEM_callocN( - sizeof(NodeGeometryProximity), __func__); + NodeGeometryProximity *node_storage = MEM_cnew<NodeGeometryProximity>(__func__); node_storage->target_element = GEO_NODE_PROX_TARGET_FACES; node->storage = node_storage; } -static void calculate_mesh_proximity(const VArray<float3> &positions, +static bool calculate_mesh_proximity(const VArray<float3> &positions, const IndexMask mask, const Mesh &mesh, const GeometryNodeProximityTargetType type, @@ -71,7 +74,7 @@ static void calculate_mesh_proximity(const VArray<float3> &positions, } if (bvh_data.tree == nullptr) { - return; + return false; } threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { @@ -82,7 +85,7 @@ static void calculate_mesh_proximity(const VArray<float3> &positions, for (int i : range) { const int index = mask[i]; /* Use the distance to the last found point as upper bound to speedup the bvh lookup. */ - nearest.dist_sq = float3::distance_squared(nearest.co, positions[index]); + nearest.dist_sq = math::distance_squared(float3(nearest.co), positions[index]); BLI_bvhtree_find_nearest( bvh_data.tree, positions[index], &nearest, bvh_data.nearest_callback, &bvh_data); @@ -97,18 +100,19 @@ static void calculate_mesh_proximity(const VArray<float3> &positions, }); free_bvhtree_from_mesh(&bvh_data); + return true; } -static void calculate_pointcloud_proximity(const VArray<float3> &positions, +static bool calculate_pointcloud_proximity(const VArray<float3> &positions, const IndexMask mask, const PointCloud &pointcloud, - const MutableSpan<float> r_distances, - const MutableSpan<float3> r_locations) + MutableSpan<float> r_distances, + MutableSpan<float3> r_locations) { BVHTreeFromPointCloud bvh_data; BKE_bvhtree_from_pointcloud_get(&bvh_data, &pointcloud, 2); if (bvh_data.tree == nullptr) { - return; + return false; } threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { @@ -136,6 +140,7 @@ static void calculate_pointcloud_proximity(const VArray<float3> &positions, }); free_bvhtree_from_pointcloud(&bvh_data); + return true; } class ProximityFunction : public fn::MultiFunction { @@ -172,18 +177,29 @@ class ProximityFunction : public fn::MultiFunction { * comparison per vertex, so it's likely not worth it. */ MutableSpan<float> distances = params.uninitialized_single_output<float>(2, "Distance"); - distances.fill(FLT_MAX); + distances.fill_indices(mask, FLT_MAX); + bool success = false; if (target_.has_mesh()) { - calculate_mesh_proximity( + success |= calculate_mesh_proximity( src_positions, mask, *target_.get_mesh_for_read(), type_, distances, positions); } if (target_.has_pointcloud() && type_ == GEO_NODE_PROX_TARGET_POINTS) { - calculate_pointcloud_proximity( + success |= calculate_pointcloud_proximity( src_positions, mask, *target_.get_pointcloud_for_read(), distances, positions); } + if (!success) { + if (!positions.is_empty()) { + positions.fill_indices(mask, float3(0)); + } + if (!distances.is_empty()) { + distances.fill_indices(mask, 0.0f); + } + return; + } + if (params.single_output_is_required(2, "Distance")) { threading::parallel_for(mask.index_range(), 2048, [&](IndexRange range) { for (const int i : range) { @@ -195,17 +211,17 @@ class ProximityFunction : public fn::MultiFunction { } }; -static void geo_node_proximity_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set_target = params.extract_input<GeometrySet>("Target"); + geometry_set_target.ensure_owns_direct_data(); if (!geometry_set_target.has_mesh() && !geometry_set_target.has_pointcloud()) { - params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); - params.set_output("Distance", fn::make_constant_field<float>({0.0f})); + params.set_default_remaining_outputs(); return; } - const NodeGeometryProximity &storage = *(const NodeGeometryProximity *)params.node().storage; + const NodeGeometryProximity &storage = node_storage(params.node()); Field<float3> position_field = params.extract_input<Field<float3>>("Source Position"); auto proximity_fn = std::make_unique<ProximityFunction>( @@ -218,18 +234,20 @@ static void geo_node_proximity_exec(GeoNodeExecParams params) params.set_output("Distance", Field<float>(proximity_op, 1)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_proximity_cc void register_node_type_geo_proximity() { + namespace file_ns = blender::nodes::node_geo_proximity_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_PROXIMITY, "Geometry Proximity", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_proximity_init); + geo_node_type_base(&ntype, GEO_NODE_PROXIMITY, "Geometry Proximity", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::geo_proximity_init); node_type_storage( &ntype, "NodeGeometryProximity", node_free_standard_storage, node_copy_standard_storage); - ntype.declare = blender::nodes::geo_node_proximity_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_proximity_exec; - ntype.draw_buttons = blender::nodes::geo_node_proximity_layout; + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc new file mode 100644 index 00000000000..c38503f688c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -0,0 +1,462 @@ +/* + * 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 "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_raycast_cc { + +using namespace blender::bke::mesh_surface_sample; + +NODE_STORAGE_FUNCS(NodeGeometryRaycast) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Target Geometry")) + .only_realized_data() + .supported_type(GEO_COMPONENT_TYPE_MESH); + + b.add_input<decl::Vector>(N_("Attribute")).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); + b.add_input<decl::Color>(N_("Attribute"), "Attribute_002").hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Attribute"), "Attribute_003").hide_value().supports_field(); + b.add_input<decl::Int>(N_("Attribute"), "Attribute_004").hide_value().supports_field(); + + b.add_input<decl::Vector>(N_("Source Position")).implicit_field(); + b.add_input<decl::Vector>(N_("Ray Direction")) + .default_value({0.0f, 0.0f, -1.0f}) + .supports_field(); + b.add_input<decl::Float>(N_("Ray Length")) + .default_value(100.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + + b.add_output<decl::Bool>(N_("Is Hit")).dependent_field(); + b.add_output<decl::Vector>(N_("Hit Position")).dependent_field(); + b.add_output<decl::Vector>(N_("Hit Normal")).dependent_field(); + b.add_output<decl::Float>(N_("Hit Distance")).dependent_field(); + + b.add_output<decl::Vector>(N_("Attribute")).dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Color>(N_("Attribute"), "Attribute_002").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Bool>(N_("Attribute"), "Attribute_003").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Int>(N_("Attribute"), "Attribute_004").dependent_field({1, 2, 3, 4, 5, 6}); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryRaycast *data = MEM_cnew<NodeGeometryRaycast>(__func__); + data->mapping = GEO_NODE_RAYCAST_INTERPOLATED; + data->data_type = CD_PROP_FLOAT; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryRaycast &storage = node_storage(*node); + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *socket_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *socket_float = socket_vector->next; + bNodeSocket *socket_color4f = socket_float->next; + bNodeSocket *socket_boolean = socket_color4f->next; + bNodeSocket *socket_int32 = socket_boolean->next; + + nodeSetSocketAvailability(ntree, socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, socket_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_vector = (bNodeSocket *)BLI_findlink(&node->outputs, 4); + bNodeSocket *out_socket_float = out_socket_vector->next; + bNodeSocket *out_socket_color4f = out_socket_float->next; + bNodeSocket *out_socket_boolean = out_socket_color4f->next; + bNodeSocket *out_socket_int32 = out_socket_boolean->next; + + nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, out_socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + search_link_ops_for_declarations(params, declaration.inputs().take_back(3)); + search_link_ops_for_declarations(params, declaration.outputs().take_front(4)); + + const std::optional<CustomDataType> type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + /* The input and output sockets have the same name. */ + params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeRaycast"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Attribute"); + }); + } +} + +static eAttributeMapMode get_map_mode(GeometryNodeRaycastMapMode map_mode) +{ + switch (map_mode) { + case GEO_NODE_RAYCAST_INTERPOLATED: + return eAttributeMapMode::INTERPOLATED; + default: + case GEO_NODE_RAYCAST_NEAREST: + return eAttributeMapMode::NEAREST; + } +} + +static void raycast_to_mesh(IndexMask mask, + const Mesh &mesh, + const VArray<float3> &ray_origins, + const VArray<float3> &ray_directions, + const VArray<float> &ray_lengths, + const MutableSpan<bool> r_hit, + const MutableSpan<int> r_hit_indices, + const MutableSpan<float3> r_hit_positions, + const MutableSpan<float3> r_hit_normals, + const MutableSpan<float> r_hit_distances, + int &hit_count) +{ + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4); + if (tree_data.tree == nullptr) { + free_bvhtree_from_mesh(&tree_data); + return; + } + + for (const int i : mask) { + const float ray_length = ray_lengths[i]; + const float3 ray_origin = ray_origins[i]; + const float3 ray_direction = math::normalize(ray_directions[i]); + + BVHTreeRayHit hit; + hit.index = -1; + hit.dist = ray_length; + if (BLI_bvhtree_ray_cast(tree_data.tree, + ray_origin, + ray_direction, + 0.0f, + &hit, + tree_data.raycast_callback, + &tree_data) != -1) { + hit_count++; + if (!r_hit.is_empty()) { + r_hit[i] = hit.index >= 0; + } + if (!r_hit_indices.is_empty()) { + /* The caller must be able to handle invalid indices anyway, so don't clamp this value. */ + r_hit_indices[i] = hit.index; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = hit.co; + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = hit.no; + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = hit.dist; + } + } + else { + if (!r_hit.is_empty()) { + r_hit[i] = false; + } + if (!r_hit_indices.is_empty()) { + r_hit_indices[i] = -1; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = ray_length; + } + } + } + + /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */ + BLI_assert(tree_data.cached); + free_bvhtree_from_mesh(&tree_data); +} + +class RaycastFunction : public fn::MultiFunction { + private: + GeometrySet target_; + GeometryNodeRaycastMapMode mapping_; + + /** The field for data evaluated on the target geometry. */ + std::optional<GeometryComponentFieldContext> target_context_; + std::unique_ptr<FieldEvaluator> target_evaluator_; + const GVArray *target_data_ = nullptr; + + /* Always evaluate the target domain data on the face corner domain because it contains the most + * information. Eventually this could be exposed as an option or determined automatically from + * the field inputs for better performance. */ + const AttributeDomain domain_ = ATTR_DOMAIN_CORNER; + + fn::MFSignature signature_; + + public: + RaycastFunction(GeometrySet target, GField src_field, GeometryNodeRaycastMapMode mapping) + : target_(std::move(target)), mapping_((GeometryNodeRaycastMapMode)mapping) + { + target_.ensure_owns_direct_data(); + this->evaluate_target_field(std::move(src_field)); + signature_ = create_signature(); + this->set_signature(&signature_); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Geometry Proximity"}; + signature.single_input<float3>("Source Position"); + signature.single_input<float3>("Ray Direction"); + signature.single_input<float>("Ray Length"); + signature.single_output<bool>("Is Hit"); + signature.single_output<float3>("Hit Position"); + signature.single_output<float3>("Hit Normal"); + signature.single_output<float>("Distance"); + if (target_data_) { + signature.single_output("Attribute", target_data_->type()); + } + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + /* Hit positions are always necessary for retrieving the attribute from the target if that + * output is required, so always retrieve a span from the evaluator in that case (it's + * expected that the evaluator is more likely to have a spare buffer that could be used). */ + MutableSpan<float3> hit_positions = + (target_data_) ? params.uninitialized_single_output<float3>(4, "Hit Position") : + params.uninitialized_single_output_if_required<float3>(4, "Hit Position"); + + Array<int> hit_indices; + if (target_data_) { + hit_indices.reinitialize(mask.min_array_size()); + } + + BLI_assert(target_.has_mesh()); + const Mesh &mesh = *target_.get_mesh_for_read(); + + int hit_count = 0; + raycast_to_mesh(mask, + mesh, + params.readonly_single_input<float3>(0, "Source Position"), + params.readonly_single_input<float3>(1, "Ray Direction"), + params.readonly_single_input<float>(2, "Ray Length"), + params.uninitialized_single_output_if_required<bool>(3, "Is Hit"), + hit_indices, + hit_positions, + params.uninitialized_single_output_if_required<float3>(5, "Hit Normal"), + params.uninitialized_single_output_if_required<float>(6, "Distance"), + hit_count); + + if (target_data_) { + IndexMask hit_mask; + Vector<int64_t> hit_mask_indices; + if (hit_count < mask.size()) { + /* Not all rays hit the target. Create a corrected mask to avoid transferring attribute + * data to invalid indices. An alternative would be handling -1 indices in a separate case + * in #MeshAttributeInterpolator, but since it already has an IndexMask in its constructor, + * it's simpler to use that. */ + hit_mask_indices.reserve(hit_count); + for (const int64_t i : mask) { + if (hit_indices[i] != -1) { + hit_mask_indices.append(i); + } + hit_mask = IndexMask(hit_mask_indices); + } + } + else { + hit_mask = mask; + } + + GMutableSpan result = params.uninitialized_single_output_if_required(7, "Attribute"); + if (!result.is_empty()) { + MeshAttributeInterpolator interp(&mesh, hit_mask, hit_positions, hit_indices); + result.type().fill_assign_indices(result.type().default_value(), result.data(), mask); + interp.sample_data(*target_data_, domain_, get_map_mode(mapping_), result); + } + } + } + + private: + void evaluate_target_field(GField src_field) + { + if (!src_field) { + return; + } + const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>(); + target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_}); + const int domain_size = mesh_component.attribute_domain_size(domain_); + target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size); + target_evaluator_->add(std::move(src_field)); + target_evaluator_->evaluate(); + target_data_ = &target_evaluator_->get_evaluated(0); + } +}; + +static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const CustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + if (params.output_is_required("Attribute_001")) { + return params.extract_input<Field<float>>("Attribute_001"); + } + break; + case CD_PROP_FLOAT3: + if (params.output_is_required("Attribute")) { + return params.extract_input<Field<float3>>("Attribute"); + } + break; + case CD_PROP_COLOR: + if (params.output_is_required("Attribute_002")) { + return params.extract_input<Field<ColorGeometry4f>>("Attribute_002"); + } + break; + case CD_PROP_BOOL: + if (params.output_is_required("Attribute_003")) { + return params.extract_input<Field<bool>>("Attribute_003"); + } + break; + case CD_PROP_INT32: + if (params.output_is_required("Attribute_004")) { + return params.extract_input<Field<int>>("Attribute_004"); + } + break; + default: + BLI_assert_unreachable(); + } + return {}; +} + +static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) +{ + switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { + case CD_PROP_FLOAT: { + params.set_output("Attribute_001", Field<float>(field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Attribute", Field<float3>(field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Attribute_002", Field<ColorGeometry4f>(field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Attribute_003", Field<bool>(field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Attribute_004", Field<int>(field)); + break; + } + default: + break; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet target = params.extract_input<GeometrySet>("Target Geometry"); + const NodeGeometryRaycast &storage = node_storage(params.node()); + const GeometryNodeRaycastMapMode mapping = (GeometryNodeRaycastMapMode)storage.mapping; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + if (target.is_empty()) { + params.set_default_remaining_outputs(); + return; + } + + if (!target.has_mesh()) { + params.set_default_remaining_outputs(); + return; + } + + if (target.get_mesh_for_read()->totpoly == 0) { + params.error_message_add(NodeWarningType::Error, TIP_("The target mesh must have faces")); + params.set_default_remaining_outputs(); + return; + } + + GField field = get_input_attribute_field(params, data_type); + const bool do_attribute_transfer = bool(field); + Field<float3> position_field = params.extract_input<Field<float3>>("Source Position"); + Field<float3> direction_field = params.extract_input<Field<float3>>("Ray Direction"); + Field<float> length_field = params.extract_input<Field<float>>("Ray Length"); + + auto fn = std::make_unique<RaycastFunction>(std::move(target), std::move(field), mapping); + auto op = std::make_shared<FieldOperation>(FieldOperation( + std::move(fn), + {std::move(position_field), std::move(direction_field), std::move(length_field)})); + + params.set_output("Is Hit", Field<bool>(op, 0)); + params.set_output("Hit Position", Field<float3>(op, 1)); + params.set_output("Hit Normal", Field<float3>(op, 2)); + params.set_output("Hit Distance", Field<float>(op, 3)); + if (do_attribute_transfer) { + output_attribute_field(params, GField(op, 4)); + } +} + +} // namespace blender::nodes::node_geo_raycast_cc + +void register_node_type_geo_raycast() +{ + namespace file_ns = blender::nodes::node_geo_raycast_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY); + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + node_type_storage( + &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc index 3be79d5ba3b..48b88705ed2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc @@ -16,33 +16,47 @@ #include "node_geometry_util.hh" +#include "GEO_realize_instances.hh" + #include "UI_interface.h" #include "UI_resources.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_realize_instances_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Geometry>(N_("Geometry")); +} -static void geo_node_realize_instances_declare(NodeDeclarationBuilder &b) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - b.add_input<decl::Geometry>("Geometry"); - b.add_output<decl::Geometry>("Geometry"); + uiItemR(layout, ptr, "legacy_behavior", 0, nullptr, ICON_NONE); } -static void geo_node_realize_instances_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { + const bool legacy_behavior = params.node().custom1 & GEO_NODE_REALIZE_INSTANCES_LEGACY_BEHAVIOR; + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry::RealizeInstancesOptions options; + options.keep_original_ids = legacy_behavior; + options.realize_instance_attributes = !legacy_behavior; + geometry_set = geometry::realize_instances(geometry_set, options); params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_realize_instances_cc void register_node_type_geo_realize_instances() { + namespace file_ns = blender::nodes::node_geo_realize_instances_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_REALIZE_INSTANCES, "Realize Instances", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_realize_instances_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_realize_instances_exec; + geo_node_type_base(&ntype, GEO_NODE_REALIZE_INSTANCES, "Realize Instances", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons_ex = file_ns::node_layout; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc new file mode 100644 index 00000000000..7d5c5b77ffd --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc @@ -0,0 +1,119 @@ +/* + * 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_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_rotate_instances_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Instances")).only_instances(); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Rotation")).subtype(PROP_EULER).supports_field(); + b.add_input<decl::Vector>(N_("Pivot Point")).subtype(PROP_TRANSLATION).supports_field(); + b.add_input<decl::Bool>(N_("Local Space")).default_value(true).supports_field(); + b.add_output<decl::Geometry>(N_("Instances")); +} + +static void rotate_instances(GeoNodeExecParams ¶ms, InstancesComponent &instances_component) +{ + GeometryComponentFieldContext field_context{instances_component, ATTR_DOMAIN_INSTANCE}; + const int domain_size = instances_component.instances_amount(); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(params.extract_input<Field<bool>>("Selection")); + evaluator.add(params.extract_input<Field<float3>>("Rotation")); + evaluator.add(params.extract_input<Field<float3>>("Pivot Point")); + evaluator.add(params.extract_input<Field<bool>>("Local Space")); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &rotations = evaluator.get_evaluated<float3>(0); + const VArray<float3> &pivots = evaluator.get_evaluated<float3>(1); + const VArray<bool> &local_spaces = evaluator.get_evaluated<bool>(2); + + MutableSpan<float4x4> instance_transforms = instances_component.instance_transforms(); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i_selection : range) { + const int i = selection[i_selection]; + const float3 pivot = pivots[i]; + const float3 euler = rotations[i]; + float4x4 &instance_transform = instance_transforms[i]; + + float4x4 rotation_matrix; + float3 used_pivot; + + if (local_spaces[i]) { + /* Find rotation axis from the matrix. This should work even if the instance is skewed. */ + const float3 rotation_axis_x = instance_transform.values[0]; + const float3 rotation_axis_y = instance_transform.values[1]; + const float3 rotation_axis_z = instance_transform.values[2]; + + /* Create rotations around the individual axis. This could be optimized to skip some axis + * when the angle is zero. */ + float rotation_x[3][3], rotation_y[3][3], rotation_z[3][3]; + axis_angle_to_mat3(rotation_x, rotation_axis_x, euler.x); + axis_angle_to_mat3(rotation_y, rotation_axis_y, euler.y); + axis_angle_to_mat3(rotation_z, rotation_axis_z, euler.z); + + /* Combine the previously computed rotations into the final rotation matrix. */ + float rotation[3][3]; + mul_m3_series(rotation, rotation_z, rotation_y, rotation_x); + copy_m4_m3(rotation_matrix.values, rotation); + + /* Transform the passed in pivot into the local space of the instance. */ + used_pivot = instance_transform * pivot; + } + else { + used_pivot = pivot; + eul_to_mat4(rotation_matrix.values, euler); + } + /* Move the pivot to the origin so that we can rotate around it. */ + sub_v3_v3(instance_transform.values[3], used_pivot); + /* Perform the actual rotation. */ + mul_m4_m4_pre(instance_transform.values, rotation_matrix.values); + /* Undo the pivot shifting done before. */ + add_v3_v3(instance_transform.values[3], used_pivot); + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instances"); + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + rotate_instances(params, instances); + } + params.set_output("Instances", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_rotate_instances_cc + +void register_node_type_geo_rotate_instances() +{ + namespace file_ns = blender::nodes::node_geo_rotate_instances_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ROTATE_INSTANCES, "Rotate Instances", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc new file mode 100644 index 00000000000..aaa2c156442 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc @@ -0,0 +1,485 @@ +/* + * 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_disjoint_set.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" +#include "BLI_vector_set.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_scale_elements_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Scale"), "Scale").default_value(1.0f).min(0.0f).supports_field(); + b.add_input<decl::Vector>(N_("Center")) + .subtype(PROP_TRANSLATION) + .implicit_field() + .description(N_("Origin of the scaling for each element. If multiple elements are " + "connected, their center is averaged")); + b.add_input<decl::Vector>(N_("Axis")) + .default_value({1.0f, 0.0f, 0.0f}) + .supports_field() + .description(N_("Direction in which to scale the element")); + b.add_output<decl::Geometry>(N_("Geometry")); +}; + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + uiItemR(layout, ptr, "scale_mode", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = ATTR_DOMAIN_FACE; + node->custom2 = GEO_NODE_SCALE_ELEMENTS_UNIFORM; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + bNodeSocket *geometry_socket = static_cast<bNodeSocket *>(node->inputs.first); + bNodeSocket *selection_socket = geometry_socket->next; + bNodeSocket *scale_float_socket = selection_socket->next; + bNodeSocket *center_socket = scale_float_socket->next; + bNodeSocket *axis_socket = center_socket->next; + + const GeometryNodeScaleElementsMode mode = static_cast<GeometryNodeScaleElementsMode>( + node->custom2); + const bool use_single_axis = mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS; + + nodeSetSocketAvailability(ntree, axis_socket, use_single_axis); +} + +struct UniformScaleFields { + Field<bool> selection; + Field<float> scale; + Field<float3> center; +}; + +struct UniformScaleParams { + IndexMask selection; + VArray<float> scales; + VArray<float3> centers; +}; + +struct AxisScaleFields { + Field<bool> selection; + Field<float> scale; + Field<float3> center; + Field<float3> axis; +}; + +struct AxisScaleParams { + IndexMask selection; + VArray<float> scales; + VArray<float3> centers; + VArray<float3> axis_vectors; +}; + +/** + * When multiple elements share the same vertices, they are scaled together. + */ +struct ElementIsland { + /* Either face or edge indices. */ + Vector<int> element_indices; +}; + +static float3 transform_with_uniform_scale(const float3 &position, + const float3 ¢er, + const float scale) +{ + const float3 diff = position - center; + const float3 scaled_diff = scale * diff; + const float3 new_position = center + scaled_diff; + return new_position; +} + +static float4x4 create_single_axis_transform(const float3 ¢er, + const float3 &axis, + const float scale) +{ + /* Scale along x axis. The other axis need to be orthogonal, but their specific value does not + * matter. */ + const float3 x_axis = math::normalize(axis); + float3 y_axis = math::cross(x_axis, float3(0.0f, 0.0f, 1.0f)); + if (math::is_zero(y_axis)) { + y_axis = math::cross(x_axis, float3(0.0f, 1.0f, 0.0f)); + } + y_axis = math::normalize(y_axis); + const float3 z_axis = math::cross(x_axis, y_axis); + + float4x4 transform = float4x4::identity(); + + /* Move scaling center to the origin. */ + sub_v3_v3(transform.values[3], center); + + /* `base_change` and `base_change_inv` are used to rotate space so that scaling along the + * provided axis is the same as scaling along the x axis. */ + float4x4 base_change = float4x4::identity(); + copy_v3_v3(base_change.values[0], x_axis); + copy_v3_v3(base_change.values[1], y_axis); + copy_v3_v3(base_change.values[2], z_axis); + + /* Can invert by transposing, because the matrix is orthonormal. */ + float4x4 base_change_inv = base_change.transposed(); + + float4x4 scale_transform = float4x4::identity(); + scale_transform.values[0][0] = scale; + + transform = base_change * scale_transform * base_change_inv * transform; + + /* Move scaling center back to where it was. */ + add_v3_v3(transform.values[3], center); + + return transform; +} + +using GetVertexIndicesFn = + FunctionRef<void(const Mesh &mesh, int element_index, VectorSet<int> &r_vertex_indices)>; + +static void scale_vertex_islands_uniformly(Mesh &mesh, + const Span<ElementIsland> islands, + const UniformScaleParams ¶ms, + const GetVertexIndicesFn get_vertex_indices) +{ + threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) { + for (const int island_index : range) { + const ElementIsland &island = islands[island_index]; + + float scale = 0.0f; + float3 center = {0.0f, 0.0f, 0.0f}; + + VectorSet<int> vertex_indices; + for (const int poly_index : island.element_indices) { + get_vertex_indices(mesh, poly_index, vertex_indices); + center += params.centers[poly_index]; + scale += params.scales[poly_index]; + } + + /* Divide by number of elements to get the average. */ + const float f = 1.0f / island.element_indices.size(); + scale *= f; + center *= f; + + for (const int vert_index : vertex_indices) { + MVert &vert = mesh.mvert[vert_index]; + const float3 old_position = vert.co; + const float3 new_position = transform_with_uniform_scale(old_position, center, scale); + copy_v3_v3(vert.co, new_position); + } + } + }); + + /* Positions have changed, so the normals will have to be recomputed. */ + BKE_mesh_normals_tag_dirty(&mesh); +} + +static void scale_vertex_islands_on_axis(Mesh &mesh, + const Span<ElementIsland> islands, + const AxisScaleParams ¶ms, + const GetVertexIndicesFn get_vertex_indices) +{ + threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) { + for (const int island_index : range) { + const ElementIsland &island = islands[island_index]; + + float scale = 0.0f; + float3 center = {0.0f, 0.0f, 0.0f}; + float3 axis = {0.0f, 0.0f, 0.0f}; + + VectorSet<int> vertex_indices; + for (const int poly_index : island.element_indices) { + get_vertex_indices(mesh, poly_index, vertex_indices); + center += params.centers[poly_index]; + scale += params.scales[poly_index]; + axis += params.axis_vectors[poly_index]; + } + + /* Divide by number of elements to get the average. */ + const float f = 1.0f / island.element_indices.size(); + scale *= f; + center *= f; + axis *= f; + + if (math::is_zero(axis)) { + axis = float3(1.0f, 0.0f, 0.0f); + } + + const float4x4 transform = create_single_axis_transform(center, axis, scale); + for (const int vert_index : vertex_indices) { + MVert &vert = mesh.mvert[vert_index]; + const float3 old_position = vert.co; + const float3 new_position = transform * old_position; + copy_v3_v3(vert.co, new_position); + } + } + }); + + /* Positions have changed, so the normals will have to be recomputed. */ + BKE_mesh_normals_tag_dirty(&mesh); +} + +static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexMask face_selection) +{ + /* Use the disjoint set data structure to determine which vertices have to be scaled together. */ + DisjointSet disjoint_set(mesh.totvert); + for (const int poly_index : face_selection) { + const MPoly &poly = mesh.mpoly[poly_index]; + const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop}; + for (const int loop_index : IndexRange(poly.totloop - 1)) { + const int v1 = poly_loops[loop_index].v; + const int v2 = poly_loops[loop_index + 1].v; + disjoint_set.join(v1, v2); + } + disjoint_set.join(poly_loops.first().v, poly_loops.last().v); + } + + VectorSet<int> island_ids; + Vector<ElementIsland> islands; + /* There are at most as many islands as there are selected faces. */ + islands.reserve(face_selection.size()); + + /* Gather all of the face indices in each island into separate vectors. */ + for (const int poly_index : face_selection) { + const MPoly &poly = mesh.mpoly[poly_index]; + const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop}; + const int island_id = disjoint_set.find_root(poly_loops[0].v); + const int island_index = island_ids.index_of_or_add(island_id); + if (island_index == islands.size()) { + islands.append_as(); + } + ElementIsland &island = islands[island_index]; + island.element_indices.append(poly_index); + } + + return islands; +} + +static void get_face_vertices(const Mesh &mesh, int face_index, VectorSet<int> &r_vertex_indices) +{ + const MPoly &poly = mesh.mpoly[face_index]; + const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop}; + for (const MLoop &loop : poly_loops) { + r_vertex_indices.add(loop.v); + } +} + +static AxisScaleParams evaluate_axis_scale_fields(FieldEvaluator &evaluator, + const AxisScaleFields &fields) +{ + AxisScaleParams out; + evaluator.set_selection(fields.selection); + evaluator.add(fields.scale, &out.scales); + evaluator.add(fields.center, &out.centers); + evaluator.add(fields.axis, &out.axis_vectors); + evaluator.evaluate(); + out.selection = evaluator.get_evaluated_selection_as_mask(); + return out; +} + +static void scale_faces_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields) +{ + Mesh &mesh = *mesh_component.get_for_write(); + mesh.mvert = static_cast<MVert *>( + CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert)); + + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; + FieldEvaluator evaluator{field_context, mesh.totpoly}; + AxisScaleParams params = evaluate_axis_scale_fields(evaluator, fields); + + Vector<ElementIsland> island = prepare_face_islands(mesh, params.selection); + scale_vertex_islands_on_axis(mesh, island, params, get_face_vertices); +} + +static UniformScaleParams evaluate_uniform_scale_fields(FieldEvaluator &evaluator, + const UniformScaleFields &fields) +{ + UniformScaleParams out; + evaluator.set_selection(fields.selection); + evaluator.add(fields.scale, &out.scales); + evaluator.add(fields.center, &out.centers); + evaluator.evaluate(); + out.selection = evaluator.get_evaluated_selection_as_mask(); + return out; +} + +static void scale_faces_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields) +{ + Mesh &mesh = *mesh_component.get_for_write(); + mesh.mvert = static_cast<MVert *>( + CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert)); + + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; + FieldEvaluator evaluator{field_context, mesh.totpoly}; + UniformScaleParams params = evaluate_uniform_scale_fields(evaluator, fields); + + Vector<ElementIsland> island = prepare_face_islands(mesh, params.selection); + scale_vertex_islands_uniformly(mesh, island, params, get_face_vertices); +} + +static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexMask edge_selection) +{ + /* Use the disjoint set data structure to determine which vertices have to be scaled together. */ + DisjointSet disjoint_set(mesh.totvert); + for (const int edge_index : edge_selection) { + const MEdge &edge = mesh.medge[edge_index]; + disjoint_set.join(edge.v1, edge.v2); + } + + VectorSet<int> island_ids; + Vector<ElementIsland> islands; + /* There are at most as many islands as there are selected edges. */ + islands.reserve(edge_selection.size()); + + /* Gather all of the edge indices in each island into separate vectors. */ + for (const int edge_index : edge_selection) { + const MEdge &edge = mesh.medge[edge_index]; + const int island_id = disjoint_set.find_root(edge.v1); + const int island_index = island_ids.index_of_or_add(island_id); + if (island_index == islands.size()) { + islands.append_as(); + } + ElementIsland &island = islands[island_index]; + island.element_indices.append(edge_index); + } + + return islands; +} + +static void get_edge_vertices(const Mesh &mesh, int edge_index, VectorSet<int> &r_vertex_indices) +{ + const MEdge &edge = mesh.medge[edge_index]; + r_vertex_indices.add(edge.v1); + r_vertex_indices.add(edge.v2); +} + +static void scale_edges_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields) +{ + Mesh &mesh = *mesh_component.get_for_write(); + mesh.mvert = static_cast<MVert *>( + CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert)); + + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE}; + FieldEvaluator evaluator{field_context, mesh.totedge}; + UniformScaleParams params = evaluate_uniform_scale_fields(evaluator, fields); + + Vector<ElementIsland> island = prepare_edge_islands(mesh, params.selection); + scale_vertex_islands_uniformly(mesh, island, params, get_edge_vertices); +} + +static void scale_edges_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields) +{ + Mesh &mesh = *mesh_component.get_for_write(); + mesh.mvert = static_cast<MVert *>( + CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert)); + + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE}; + FieldEvaluator evaluator{field_context, mesh.totedge}; + AxisScaleParams params = evaluate_axis_scale_fields(evaluator, fields); + + Vector<ElementIsland> island = prepare_edge_islands(mesh, params.selection); + scale_vertex_islands_on_axis(mesh, island, params, get_edge_vertices); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const bNode &node = params.node(); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom1); + const GeometryNodeScaleElementsMode scale_mode = static_cast<GeometryNodeScaleElementsMode>( + node.custom2); + + GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); + + Field<bool> selection_field = params.get_input<Field<bool>>("Selection"); + Field<float> scale_field = params.get_input<Field<float>>("Scale"); + Field<float3> center_field = params.get_input<Field<float3>>("Center"); + Field<float3> axis_field; + if (scale_mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS) { + axis_field = params.get_input<Field<float3>>("Axis"); + } + + geometry.modify_geometry_sets([&](GeometrySet &geometry) { + if (!geometry.has_mesh()) { + return; + } + MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>(); + switch (domain) { + case ATTR_DOMAIN_FACE: { + switch (scale_mode) { + case GEO_NODE_SCALE_ELEMENTS_UNIFORM: { + scale_faces_uniformly(mesh_component, {selection_field, scale_field, center_field}); + break; + } + case GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS: { + scale_faces_on_axis(mesh_component, + {selection_field, scale_field, center_field, axis_field}); + break; + } + } + break; + } + case ATTR_DOMAIN_EDGE: { + switch (scale_mode) { + case GEO_NODE_SCALE_ELEMENTS_UNIFORM: { + scale_edges_uniformly(mesh_component, {selection_field, scale_field, center_field}); + break; + } + case GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS: { + scale_edges_on_axis(mesh_component, + {selection_field, scale_field, center_field, axis_field}); + break; + } + } + break; + } + default: + BLI_assert_unreachable(); + break; + } + }); + + params.set_output("Geometry", std::move(geometry)); +} + +} // namespace blender::nodes::node_geo_scale_elements_cc + +void register_node_type_geo_scale_elements() +{ + namespace file_ns = blender::nodes::node_geo_scale_elements_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SCALE_ELEMENTS, "Scale Elements", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + ntype.initfunc = file_ns::node_init; + ntype.updatefunc = file_ns::node_update; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc new file mode 100644 index 00000000000..5bd2028ff41 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc @@ -0,0 +1,98 @@ +/* + * 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_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_scale_instances_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Instances")).only_instances(); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Scale")) + .subtype(PROP_XYZ) + .default_value({1, 1, 1}) + .supports_field(); + b.add_input<decl::Vector>(N_("Center")).subtype(PROP_TRANSLATION).supports_field(); + b.add_input<decl::Bool>(N_("Local Space")).default_value(true).supports_field(); + b.add_output<decl::Geometry>(N_("Instances")); +} + +static void scale_instances(GeoNodeExecParams ¶ms, InstancesComponent &instances_component) +{ + GeometryComponentFieldContext field_context{instances_component, ATTR_DOMAIN_INSTANCE}; + + fn::FieldEvaluator evaluator{field_context, instances_component.instances_amount()}; + evaluator.set_selection(params.extract_input<Field<bool>>("Selection")); + evaluator.add(params.extract_input<Field<float3>>("Scale")); + evaluator.add(params.extract_input<Field<float3>>("Center")); + evaluator.add(params.extract_input<Field<bool>>("Local Space")); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &scales = evaluator.get_evaluated<float3>(0); + const VArray<float3> &pivots = evaluator.get_evaluated<float3>(1); + const VArray<bool> &local_spaces = evaluator.get_evaluated<bool>(2); + + MutableSpan<float4x4> instance_transforms = instances_component.instance_transforms(); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i_selection : range) { + const int i = selection[i_selection]; + const float3 pivot = pivots[i]; + float4x4 &instance_transform = instance_transforms[i]; + + if (local_spaces[i]) { + instance_transform *= float4x4::from_location(pivot); + rescale_m4(instance_transform.values, scales[i]); + instance_transform *= float4x4::from_location(-pivot); + } + else { + const float4x4 original_transform = instance_transform; + instance_transform = float4x4::from_location(pivot); + rescale_m4(instance_transform.values, scales[i]); + instance_transform *= float4x4::from_location(-pivot); + instance_transform *= original_transform; + } + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instances"); + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + scale_instances(params, instances); + } + params.set_output("Instances", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_scale_instances_cc + +void register_node_type_geo_scale_instances() +{ + namespace file_ns = blender::nodes::node_geo_scale_instances_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SCALE_INSTANCES, "Scale Instances", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc index dafd10cee2d..3e34378d3e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc @@ -16,19 +16,19 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_separate_components_cc { -static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_output<decl::Geometry>("Mesh"); - b.add_output<decl::Geometry>("Point Cloud"); - b.add_output<decl::Geometry>("Curve"); - b.add_output<decl::Geometry>("Volume"); - b.add_output<decl::Geometry>("Instances"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_output<decl::Geometry>(N_("Mesh")); + b.add_output<decl::Geometry>(N_("Point Cloud")); + b.add_output<decl::Geometry>(N_("Curve")); + b.add_output<decl::Geometry>(N_("Volume")); + b.add_output<decl::Geometry>(N_("Instances")); } -static void geo_node_separate_components_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -61,15 +61,17 @@ static void geo_node_separate_components_exec(GeoNodeExecParams params) params.set_output("Instances", instances); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_separate_components_cc void register_node_type_geo_separate_components() { + namespace file_ns = blender::nodes::node_geo_separate_components_cc; + static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_SEPARATE_COMPONENTS, "Separate Components", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_join_geometry_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_separate_components_exec; + &ntype, GEO_NODE_SEPARATE_COMPONENTS, "Separate Components", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc new file mode 100644 index 00000000000..fec1ac1363e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc @@ -0,0 +1,117 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_separate_geometry_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySeparateGeometry) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Selection")) + .default_value(true) + .hide_value() + .supports_field() + .description(N_("The parts of the geometry that go into the first output")); + b.add_output<decl::Geometry>(N_("Selection")) + .description(N_("The parts of the geometry in the selection")); + b.add_output<decl::Geometry>(N_("Inverted")) + .description(N_("The parts of the geometry not in the selection")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometrySeparateGeometry *data = MEM_cnew<NodeGeometrySeparateGeometry>(__func__); + data->domain = ATTR_DOMAIN_POINT; + + node->storage = data; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + const NodeGeometrySeparateGeometry &storage = node_storage(params.node()); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + + auto separate_geometry_maybe_recursively = [&](GeometrySet &geometry_set, bool invert) { + bool is_error; + if (domain == ATTR_DOMAIN_INSTANCE) { + /* Only delete top level instances. */ + separate_geometry(geometry_set, + domain, + GEO_NODE_DELETE_GEOMETRY_MODE_ALL, + selection_field, + invert, + is_error); + } + else { + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + separate_geometry(geometry_set, + domain, + GEO_NODE_DELETE_GEOMETRY_MODE_ALL, + selection_field, + invert, + is_error); + }); + } + }; + + GeometrySet second_set(geometry_set); + if (params.output_is_required("Selection")) { + separate_geometry_maybe_recursively(geometry_set, false); + params.set_output("Selection", std::move(geometry_set)); + } + if (params.output_is_required("Inverted")) { + separate_geometry_maybe_recursively(second_set, true); + params.set_output("Inverted", std::move(second_set)); + } +} + +} // namespace blender::nodes::node_geo_separate_geometry_cc + +void register_node_type_geo_separate_geometry() +{ + namespace file_ns = blender::nodes::node_geo_separate_geometry_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SEPARATE_GEOMETRY, "Separate Geometry", NODE_CLASS_GEOMETRY); + + node_type_storage(&ntype, + "NodeGeometrySeparateGeometry", + node_free_standard_storage, + node_copy_standard_storage); + + node_type_init(&ntype, file_ns::node_init); + + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc new file mode 100644 index 00000000000..82d09bbc208 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc @@ -0,0 +1,181 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_set_curve_handles_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySetCurveHandlePositions) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Position")).implicit_field(); + b.add_input<decl::Vector>(N_("Offset")).default_value(float3(0.0f, 0.0f, 0.0f)).supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometrySetCurveHandlePositions *data = MEM_cnew<NodeGeometrySetCurveHandlePositions>( + __func__); + + data->mode = GEO_NODE_CURVE_HANDLE_LEFT; + node->storage = data; +} + +static void set_position_in_component(const GeometryNodeCurveHandleMode mode, + GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float3> &position_field, + const Field<float3> &offset_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add(position_field); + evaluator.add(offset_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + + CurveComponent *curve_component = static_cast<CurveComponent *>(&component); + CurveEval *curve = curve_component->get_for_write(); + + StringRef side = mode & GEO_NODE_CURVE_HANDLE_LEFT ? "handle_left" : "handle_right"; + + int current_point = 0; + int current_mask = 0; + + for (const SplinePtr &spline : curve->splines()) { + if (spline->type() == Spline::Type::Bezier) { + BezierSpline &bezier = static_cast<BezierSpline &>(*spline); + for (int i : bezier.positions().index_range()) { + if (current_mask < selection.size() && selection[current_mask] == current_point) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + if (bezier.handle_types_left()[i] == BezierSpline::HandleType::Vector) { + bezier.ensure_auto_handles(); + bezier.handle_types_left()[i] = BezierSpline::HandleType::Free; + } + else if (bezier.handle_types_left()[i] == BezierSpline::HandleType::Auto) { + bezier.ensure_auto_handles(); + bezier.handle_types_left()[i] = BezierSpline::HandleType::Align; + } + } + else { + if (bezier.handle_types_right()[i] == BezierSpline::HandleType::Vector) { + bezier.ensure_auto_handles(); + bezier.handle_types_right()[i] = BezierSpline::HandleType::Free; + } + else if (bezier.handle_types_right()[i] == BezierSpline::HandleType::Auto) { + bezier.ensure_auto_handles(); + bezier.handle_types_right()[i] = BezierSpline::HandleType::Align; + } + } + current_mask++; + } + current_point++; + } + } + else { + for ([[maybe_unused]] int i : spline->positions().index_range()) { + if (current_mask < selection.size() && selection[current_mask] == current_point) { + current_mask++; + } + current_point++; + } + } + } + + const VArray<float3> &positions_input = evaluator.get_evaluated<float3>(0); + const VArray<float3> &offsets_input = evaluator.get_evaluated<float3>(1); + + OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>( + side, ATTR_DOMAIN_POINT, {0, 0, 0}); + MutableSpan<float3> position_mutable = positions.as_span(); + + for (int i : selection) { + position_mutable[i] = positions_input[i] + offsets_input[i]; + } + + positions.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometrySetCurveHandlePositions &storage = node_storage(params.node()); + const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)storage.mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float3> position_field = params.extract_input<Field<float3>>("Position"); + Field<float3> offset_field = params.extract_input<Field<float3>>("Offset"); + + bool has_bezier = false; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve() && + geometry_set.get_curve_for_read()->has_spline_with_type(Spline::Type::Bezier)) { + has_bezier = true; + set_position_in_component(mode, + geometry_set.get_component_for_write<CurveComponent>(), + selection_field, + position_field, + offset_field); + } + }); + if (!has_bezier) { + params.error_message_add(NodeWarningType::Info, + TIP_("The input geometry does not contain a Bezier spline")); + } + params.set_output("Curve", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_curve_handles_cc + +void register_node_type_geo_set_curve_handles() +{ + namespace file_ns = blender::nodes::node_geo_set_curve_handles_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SET_CURVE_HANDLES, "Set Handle Positions", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + ntype.minwidth = 100.0f; + node_type_init(&ntype, file_ns::node_init); + node_type_storage(&ntype, + "NodeGeometrySetCurveHandlePositions", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc new file mode 100644 index 00000000000..06fe4427520 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc @@ -0,0 +1,82 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_curve_radius_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Radius")) + .min(0.0f) + .default_value(1.0f) + .supports_field() + .subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void set_radius_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float> &radius_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<float> radii = component.attribute_try_get_for_output_only<float>( + "radius", ATTR_DOMAIN_POINT); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(radius_field, radii.varray()); + evaluator.evaluate(); + + radii.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float> radii_field = params.extract_input<Field<float>>("Radius"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + set_radius_in_component( + geometry_set.get_component_for_write<CurveComponent>(), selection_field, radii_field); + } + }); + + params.set_output("Curve", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_curve_radius_cc + +void register_node_type_geo_set_curve_radius() +{ + namespace file_ns = blender::nodes::node_geo_set_curve_radius_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_CURVE_RADIUS, "Set Curve Radius", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc new file mode 100644 index 00000000000..0854d0a4549 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc @@ -0,0 +1,78 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_curve_tilt_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Tilt")).subtype(PROP_ANGLE).supports_field(); + b.add_output<decl::Geometry>(N_("Curve")); +} + +static void set_tilt_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float> &tilt_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<float> tilts = component.attribute_try_get_for_output_only<float>( + "tilt", ATTR_DOMAIN_POINT); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(tilt_field, tilts.varray()); + evaluator.evaluate(); + + tilts.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float> tilt_field = params.extract_input<Field<float>>("Tilt"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + set_tilt_in_component( + geometry_set.get_component_for_write<CurveComponent>(), selection_field, tilt_field); + } + }); + + params.set_output("Curve", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_curve_tilt_cc + +void register_node_type_geo_set_curve_tilt() +{ + namespace file_ns = blender::nodes::node_geo_set_curve_tilt_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_CURVE_TILT, "Set Curve Tilt", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_id.cc b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc new file mode 100644 index 00000000000..110b8206944 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_set_id_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Int>(N_("ID")).implicit_field(); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_id_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<int> &id_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + + /* Since adding the ID attribute can change the result of the field evaluation (the random value + * node uses the index if the ID is unavailable), make sure that it isn't added before evaluating + * the field. However, as an optimization, use a faster code path when it already exists. */ + if (component.attribute_exists("id")) { + OutputAttribute_Typed<int> id_attribute = component.attribute_try_get_for_output_only<int>( + "id", ATTR_DOMAIN_POINT); + evaluator.add_with_destination(id_field, id_attribute.varray()); + evaluator.evaluate(); + id_attribute.save(); + } + else { + evaluator.add(id_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<int> &result_ids = evaluator.get_evaluated<int>(0); + OutputAttribute_Typed<int> id_attribute = component.attribute_try_get_for_output_only<int>( + "id", ATTR_DOMAIN_POINT); + result_ids.materialize(selection, id_attribute.as_span()); + id_attribute.save(); + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<int> id_field = params.extract_input<Field<int>>("ID"); + + for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_INSTANCES, + GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE}) { + if (geometry_set.has(type)) { + set_id_in_component(geometry_set.get_component_for_write(type), selection_field, id_field); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_id_cc + +void register_node_type_geo_set_id() +{ + namespace file_ns = blender::nodes::node_geo_set_id_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_ID, "Set ID", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material.cc new file mode 100644 index 00000000000..ab2c778d6fc --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_material.cc @@ -0,0 +1,134 @@ +/* + * 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 "DNA_pointcloud_types.h" +#include "DNA_volume_types.h" + +#include "BKE_material.h" + +namespace blender::nodes::node_geo_set_material_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")) + .supported_type( + {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_POINT_CLOUD}); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Material>(N_("Material")).hide_label(); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void assign_material_to_faces(Mesh &mesh, const IndexMask selection, Material *material) +{ + if (selection.size() != mesh.totpoly) { + /* If the entire mesh isn't selected, and there is no material slot yet, add an empty + * slot so that the faces that aren't selected can still refer to the default material. */ + BKE_id_material_eval_ensure_default_slot(&mesh.id); + } + + 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 : selection) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + /* Only add the warnings once, even if there are many unique instances. */ + bool point_selection_warning = false; + bool volume_selection_warning = false; + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_mesh()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh &mesh = *mesh_component.get_for_write(); + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; + + fn::FieldEvaluator selection_evaluator{field_context, mesh.totpoly}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + assign_material_to_faces(mesh, selection, material); + } + if (Volume *volume = geometry_set.get_volume_for_write()) { + BKE_id_material_eval_assign(&volume->id, 1, material); + if (selection_field.node().depends_on_input()) { + volume_selection_warning = true; + } + } + if (PointCloud *pointcloud = geometry_set.get_pointcloud_for_write()) { + BKE_id_material_eval_assign(&pointcloud->id, 1, material); + if (selection_field.node().depends_on_input()) { + point_selection_warning = true; + } + } + }); + + if (volume_selection_warning) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Volumes only support a single material; selection input can not be a field")); + } + if (point_selection_warning) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Point clouds only support a single material; selection input can not be a field")); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_material_cc + +void register_node_type_geo_set_material() +{ + namespace file_ns = blender::nodes::node_geo_set_material_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_MATERIAL, "Set Material", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc new file mode 100644 index 00000000000..ca6d78adc80 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc @@ -0,0 +1,77 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_material_index_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Int>(N_("Material Index")).supports_field().min(0); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_material_index_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<int> &index_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<int> indices = component.attribute_try_get_for_output_only<int>( + "material_index", ATTR_DOMAIN_FACE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(index_field, indices.varray()); + evaluator.evaluate(); + indices.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<int> index_field = params.extract_input<Field<int>>("Material Index"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_mesh()) { + set_material_index_in_component( + geometry_set.get_component_for_write<MeshComponent>(), selection_field, index_field); + } + }); + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_material_index_cc + +void register_node_type_geo_set_material_index() +{ + namespace file_ns = blender::nodes::node_geo_set_material_index_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SET_MATERIAL_INDEX, "Set Material Index", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc new file mode 100644 index 00000000000..b7dd091da44 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc @@ -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. + */ + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_set_point_radius_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Points")).supported_type(GEO_COMPONENT_TYPE_POINT_CLOUD); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Radius")) + .default_value(0.05f) + .min(0.0f) + .supports_field() + .subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>(N_("Points")); +} + +static void set_radius_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<float> &radius_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<float> radii = component.attribute_try_get_for_output_only<float>( + "radius", ATTR_DOMAIN_POINT); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(radius_field, radii.varray()); + evaluator.evaluate(); + + radii.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float> radii_field = params.extract_input<Field<float>>("Radius"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_pointcloud()) { + set_radius_in_component(geometry_set.get_component_for_write<PointCloudComponent>(), + selection_field, + radii_field); + } + }); + + params.set_output("Points", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_point_radius_cc + +void register_node_type_geo_set_point_radius() +{ + namespace file_ns = blender::nodes::node_geo_set_point_radius_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_POINT_RADIUS, "Set Point Radius", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc index 8caf961fc04..4a8e4e6eab8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -16,45 +16,126 @@ #include "DEG_depsgraph_query.h" +#include "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_set_position_cc { -static void geo_node_set_position_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Position").implicit_field(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Position")).implicit_field(); + b.add_input<decl::Vector>(N_("Offset")).supports_field().subtype(PROP_TRANSLATION); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_computed_position_and_offset(GeometryComponent &component, + const VArray<float3> &in_positions, + const VArray<float3> &in_offsets, + const AttributeDomain domain, + const IndexMask selection) +{ + + OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>( + "position", domain, {0, 0, 0}); + + const int grain_size = 10000; + + switch (component.type()) { + case GEO_COMPONENT_TYPE_MESH: { + Mesh *mesh = static_cast<MeshComponent &>(component).get_for_write(); + MutableSpan<MVert> mverts{mesh->mvert, mesh->totvert}; + if (in_positions.is_same(positions.varray())) { + devirtualize_varray(in_offsets, [&](const auto in_offsets) { + threading::parallel_for( + selection.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + const float3 offset = in_offsets[i]; + add_v3_v3(mverts[i].co, offset); + } + }); + }); + } + else { + devirtualize_varray2( + in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) { + threading::parallel_for( + selection.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + const float3 new_position = in_positions[i] + in_offsets[i]; + copy_v3_v3(mverts[i].co, new_position); + } + }); + }); + } + break; + } + default: { + MutableSpan<float3> out_positions_span = positions.as_span(); + if (in_positions.is_same(positions.varray())) { + devirtualize_varray(in_offsets, [&](const auto in_offsets) { + threading::parallel_for( + selection.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + out_positions_span[i] += in_offsets[i]; + } + }); + }); + } + else { + devirtualize_varray2( + in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) { + threading::parallel_for( + selection.index_range(), grain_size, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + out_positions_span[i] = in_positions[i] + in_offsets[i]; + } + }); + }); + } + break; + } + } + + positions.save(); } static void set_position_in_component(GeometryComponent &component, const Field<bool> &selection_field, - const Field<float3> &position_field) + const Field<float3> &position_field, + const Field<float3> &offset_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + AttributeDomain domain = component.type() == GEO_COMPONENT_TYPE_INSTANCES ? + ATTR_DOMAIN_INSTANCE : + ATTR_DOMAIN_POINT; + GeometryComponentFieldContext field_context{component, domain}; + const int domain_size = component.attribute_domain_size(domain); if (domain_size == 0) { return; } - fn::FieldEvaluator selection_evaluator{field_context, domain_size}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add(position_field); + evaluator.add(offset_field); + evaluator.evaluate(); - OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>( - "position", ATTR_DOMAIN_POINT, {0, 0, 0}); - fn::FieldEvaluator position_evaluator{field_context, &selection}; - position_evaluator.add_with_destination(position_field, positions.varray()); - position_evaluator.evaluate(); - positions.save(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &positions_input = evaluator.get_evaluated<float3>(0); + const VArray<float3> &offsets_input = evaluator.get_evaluated<float3>(1); + set_computed_position_and_offset(component, positions_input, offsets_input, domain, selection); } -static void geo_node_set_position_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<float3> offset_field = params.extract_input<Field<float3>>("Offset"); Field<float3> position_field = params.extract_input<Field<float3>>("Position"); for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_MESH, @@ -63,21 +144,23 @@ static void geo_node_set_position_exec(GeoNodeExecParams params) GEO_COMPONENT_TYPE_INSTANCES}) { if (geometry.has(type)) { set_position_in_component( - geometry.get_component_for_write(type), selection_field, position_field); + geometry.get_component_for_write(type), selection_field, position_field, offset_field); } } params.set_output("Geometry", std::move(geometry)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_set_position_cc void register_node_type_geo_set_position() { + namespace file_ns = blender::nodes::node_geo_set_position_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec; - ntype.declare = blender::nodes::geo_node_set_position_declare; + geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc new file mode 100644 index 00000000000..d442cd37e81 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc @@ -0,0 +1,77 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_shade_smooth_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Shade Smooth")).supports_field().default_value(true); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_smooth_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<bool> &shade_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<bool> shades = component.attribute_try_get_for_output_only<bool>( + "shade_smooth", ATTR_DOMAIN_FACE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(shade_field, shades.varray()); + evaluator.evaluate(); + + shades.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<bool> shade_field = params.extract_input<Field<bool>>("Shade Smooth"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_mesh()) { + set_smooth_in_component( + geometry_set.get_component_for_write<MeshComponent>(), selection_field, shade_field); + } + }); + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_shade_smooth_cc + +void register_node_type_geo_set_shade_smooth() +{ + namespace file_ns = blender::nodes::node_geo_set_shade_smooth_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_SHADE_SMOOTH, "Set Shade Smooth", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc new file mode 100644 index 00000000000..13230e185a3 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc @@ -0,0 +1,78 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_spline_cyclic_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Cyclic")).supports_field(); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_cyclic_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<bool> &cyclic_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<bool> cyclics = component.attribute_try_get_for_output_only<bool>( + "cyclic", ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(cyclic_field, cyclics.varray()); + evaluator.evaluate(); + + cyclics.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<bool> cyclic_field = params.extract_input<Field<bool>>("Cyclic"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + set_cyclic_in_component( + geometry_set.get_component_for_write<CurveComponent>(), selection_field, cyclic_field); + } + }); + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_spline_cyclic_cc + +void register_node_type_geo_set_spline_cyclic() +{ + namespace file_ns = blender::nodes::node_geo_set_spline_cyclic_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SET_SPLINE_CYCLIC, "Set Spline Cyclic", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc new file mode 100644 index 00000000000..e472e14671c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc @@ -0,0 +1,95 @@ +/* + * 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" + +namespace blender::nodes::node_geo_set_spline_resolution_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Int>(N_("Resolution")).min(1).default_value(12).supports_field(); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void set_resolution_in_component(GeometryComponent &component, + const Field<bool> &selection_field, + const Field<int> &resolution_field) +{ + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); + if (domain_size == 0) { + return; + } + + OutputAttribute_Typed<int> resolutions = component.attribute_try_get_for_output_only<int>( + "resolution", ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(resolution_field, resolutions.varray()); + evaluator.evaluate(); + + resolutions.save(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + Field<int> resolution_field = params.extract_input<Field<int>>("Resolution"); + + bool only_poly = true; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + if (only_poly) { + for (const SplinePtr &spline : geometry_set.get_curve_for_read()->splines()) { + if (ELEM(spline->type(), Spline::Type::Bezier, Spline::Type::NURBS)) { + only_poly = false; + break; + } + } + } + set_resolution_in_component(geometry_set.get_component_for_write<CurveComponent>(), + selection_field, + resolution_field); + } + }); + + if (only_poly) { + params.error_message_add(NodeWarningType::Warning, + TIP_("Input geometry does not contain a Bezier or NURB spline")); + } + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_set_spline_resolution_cc + +void register_node_type_geo_set_spline_resolution() +{ + namespace file_ns = blender::nodes::node_geo_set_spline_resolution_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SET_SPLINE_RESOLUTION, "Set Spline Resolution", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc index 1e4a4d1f68b..176fcf3178a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc @@ -16,16 +16,16 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_string_join_cc { -static void geo_node_string_join_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::String>("Delimiter"); - b.add_input<decl::String>("Strings").multi_input().hide_value(); - b.add_output<decl::String>("String"); -}; + b.add_input<decl::String>(N_("Delimiter")); + b.add_input<decl::String>(N_("Strings")).multi_input().hide_value(); + b.add_output<decl::String>(N_("String")); +} -static void geo_node_string_join_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { Vector<std::string> strings = params.extract_multi_input<std::string>("Strings"); const std::string delim = params.extract_input<std::string>("Delimiter"); @@ -40,14 +40,16 @@ static void geo_node_string_join_exec(GeoNodeExecParams params) params.set_output("String", std::move(output)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_string_join_cc void register_node_type_geo_string_join() { + namespace file_ns = blender::nodes::node_geo_string_join_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "String Join", NODE_CLASS_CONVERTER, 0); - ntype.geometry_node_execute = blender::nodes::geo_node_string_join_exec; - ntype.declare = blender::nodes::geo_node_string_join_declare; + geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "Join Strings", NODE_CLASS_CONVERTER); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc index 5e2f03806c3..10c0d61ccb6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -18,8 +18,8 @@ #include "DNA_vfont_types.h" #include "BKE_curve.h" -#include "BKE_font.h" #include "BKE_spline.hh" +#include "BKE_vfont.h" #include "BLI_hash.h" #include "BLI_string_utf8.h" @@ -30,25 +30,44 @@ #include "node_geometry_util.hh" -namespace blender::nodes { +namespace blender::nodes::node_geo_string_to_curves_cc { -static void geo_node_string_to_curves_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryStringToCurves) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::String>("String"); - b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Character Spacing") + b.add_input<decl::String>(N_("String")); + b.add_input<decl::Float>(N_("Size")).default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Character Spacing")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Word Spacing")) .default_value(1.0f) .min(0.0f) .subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Text Box Height").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Curves"); - b.add_output<decl::String>("Remainder"); + b.add_input<decl::Float>(N_("Line Spacing")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Text Box Width")) + .default_value(0.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>(N_("Text Box Height")) + .default_value(0.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .make_available([](bNode &node) { + node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT; + }); + b.add_output<decl::Geometry>(N_("Curve Instances")); + b.add_output<decl::String>(N_("Remainder")).make_available([](bNode &node) { + node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE; + }); } -static void geo_node_string_to_curves_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr) +static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); @@ -67,10 +86,9 @@ static void geo_node_string_to_curves_layout(uiLayout *layout, struct bContext * uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE); } -static void geo_node_string_to_curves_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryStringToCurves *data = (NodeGeometryStringToCurves *)MEM_callocN( - sizeof(NodeGeometryStringToCurves), __func__); + NodeGeometryStringToCurves *data = MEM_cnew<NodeGeometryStringToCurves>(__func__); data->overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW; data->align_x = GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT; @@ -79,17 +97,19 @@ static void geo_node_string_to_curves_init(bNodeTree *UNUSED(ntree), bNode *node node->id = (ID *)BKE_vfont_builtin_get(); } -static void geo_node_string_to_curves_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - const NodeGeometryStringToCurves *storage = (const NodeGeometryStringToCurves *)node->storage; + const NodeGeometryStringToCurves &storage = node_storage(*node); const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) - storage->overflow; + storage.overflow; bNodeSocket *socket_remainder = ((bNodeSocket *)node->outputs.first)->next; - nodeSetSocketAvailability(socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE); + nodeSetSocketAvailability( + ntree, socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE); bNodeSocket *height_socket = (bNodeSocket *)node->inputs.last; bNodeSocket *width_socket = height_socket->prev; - nodeSetSocketAvailability(height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + nodeSetSocketAvailability( + ntree, height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); node_sock_label(width_socket, overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? N_("Max Width") : N_("Text Box Width")); @@ -117,8 +137,7 @@ static TextLayout get_text_layout(GeoNodeExecParams ¶ms) return {}; } - const NodeGeometryStringToCurves &storage = - *(const NodeGeometryStringToCurves *)params.node().storage; + const NodeGeometryStringToCurves &storage = node_storage(params.node()); const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) storage.overflow; const GeometryNodeStringToCurvesAlignXMode align_x = (GeometryNodeStringToCurvesAlignXMode) @@ -136,7 +155,7 @@ static TextLayout get_text_layout(GeoNodeExecParams ¶ms) params.extract_input<float>("Text Box Height"); VFont *vfont = (VFont *)params.node().id; - Curve cu = {nullptr}; + Curve cu = {{nullptr}}; cu.type = OB_FONT; /* Set defaults */ cu.resolu = 12; @@ -214,7 +233,7 @@ static Map<int, int> create_curve_instances(GeoNodeExecParams ¶ms, if (handles.contains(charcodes[i])) { continue; } - Curve cu = {nullptr}; + Curve cu = {{nullptr}}; cu.type = OB_FONT; cu.resolu = 12; cu.vfont = vfont; @@ -242,18 +261,16 @@ static void add_instances_from_handles(InstancesComponent &instances, instances.resize(positions.size()); MutableSpan<int> handles = instances.instance_reference_handles(); MutableSpan<float4x4> transforms = instances.instance_transforms(); - MutableSpan<int> instance_ids = instances.instance_ids(); threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) { for (const int i : range) { handles[i] = char_handles.lookup(charcodes[i]); transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0}); - instance_ids[i] = i; } }); } -static void geo_node_string_to_curves_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { TextLayout layout = get_text_layout(params); @@ -264,15 +281,16 @@ static void geo_node_string_to_curves_exec(GeoNodeExecParams params) } if (layout.positions.size() == 0) { - params.set_output("Curves", GeometrySet()); + params.set_output("Curve Instances", GeometrySet()); return; } /* Convert UTF-8 encoded string to UTF-32. */ size_t len_bytes; size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); - Array<char32_t> char_codes(len_chars + 1); - BLI_str_utf8_as_utf32(char_codes.data(), layout.text.c_str(), len_chars + 1); + Array<char32_t> char_codes_with_null(len_chars + 1); + BLI_str_utf8_as_utf32(char_codes_with_null.data(), layout.text.c_str(), len_chars + 1); + const Span<char32_t> char_codes = char_codes_with_null.as_span().drop_back(1); /* Create and add instances. */ GeometrySet geometry_set_out; @@ -281,26 +299,27 @@ static void geo_node_string_to_curves_exec(GeoNodeExecParams params) params, layout.final_font_size, char_codes, instances); add_instances_from_handles(instances, char_handles, char_codes, layout.positions); - params.set_output("Curves", std::move(geometry_set_out)); + params.set_output("Curve Instances", std::move(geometry_set_out)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_string_to_curves_cc void register_node_type_geo_string_to_curves() { + namespace file_ns = blender::nodes::node_geo_string_to_curves_cc; + static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_string_to_curves_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_string_to_curves_exec; - node_type_init(&ntype, blender::nodes::geo_node_string_to_curves_init); - node_type_update(&ntype, blender::nodes::geo_node_string_to_curves_update); + geo_node_type_base(&ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_size(&ntype, 190, 120, 700); node_type_storage(&ntype, "NodeGeometryStringToCurves", node_free_standard_storage, node_copy_standard_storage); - ntype.draw_buttons = blender::nodes::geo_node_string_to_curves_layout; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc new file mode 100644 index 00000000000..eb1a5496845 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -0,0 +1,166 @@ +/* + * 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_modifier_types.h" + +#include "BKE_mesh.h" +#include "BKE_subdiv.h" +#include "BKE_subdiv_mesh.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_subdivision_surface_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySubdivisionSurface) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Int>(N_("Level")).default_value(1).min(0).max(6); + b.add_input<decl::Float>(N_("Crease")) + .default_value(0.0f) + .min(0.0f) + .max(1.0f) + .supports_field() + .subtype(PROP_FACTOR); + b.add_output<decl::Geometry>(N_("Mesh")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "uv_smooth", 0, "", ICON_NONE); + uiItemR(layout, ptr, "boundary_smooth", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometrySubdivisionSurface *data = MEM_cnew<NodeGeometrySubdivisionSurface>(__func__); + data->uv_smooth = SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES; + data->boundary_smooth = SUBSURF_BOUNDARY_SMOOTH_ALL; + node->storage = data; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); +#ifndef WITH_OPENSUBDIV + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenSubdiv")); +#else + Field<float> crease_field = params.extract_input<Field<float>>("Crease"); + + const NodeGeometrySubdivisionSurface &storage = node_storage(params.node()); + const int uv_smooth = storage.uv_smooth; + const int boundary_smooth = storage.boundary_smooth; + const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 30); + + /* Only process subdivision if level is greater than 0. */ + if (subdiv_level == 0) { + params.set_output("Mesh", std::move(geometry_set)); + return; + } + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_mesh()) { + return; + } + + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + AttributeDomain domain = ATTR_DOMAIN_EDGE; + GeometryComponentFieldContext field_context{mesh_component, domain}; + const int domain_size = mesh_component.attribute_domain_size(domain); + + if (domain_size == 0) { + return; + } + + FieldEvaluator evaluator(field_context, domain_size); + evaluator.add(crease_field); + evaluator.evaluate(); + const VArray<float> &creases = evaluator.get_evaluated<float>(0); + + OutputAttribute_Typed<float> crease = mesh_component.attribute_try_get_for_output_only<float>( + "crease", domain); + MutableSpan<float> crease_span = crease.as_span(); + for (auto i : creases.index_range()) { + crease_span[i] = std::clamp(creases[i], 0.0f, 1.0f); + } + crease.save(); + + /* Initialize mesh settings. */ + SubdivToMeshSettings mesh_settings; + mesh_settings.resolution = (1 << subdiv_level) + 1; + mesh_settings.use_optimal_display = false; + + /* Initialize subdivision settings. */ + SubdivSettings subdiv_settings; + subdiv_settings.is_simple = false; + subdiv_settings.is_adaptive = false; + subdiv_settings.use_creases = !(creases.is_single() && creases.get_internal_single() == 0.0f); + subdiv_settings.level = subdiv_level; + + subdiv_settings.vtx_boundary_interpolation = + BKE_subdiv_vtx_boundary_interpolation_from_subsurf(boundary_smooth); + subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth( + uv_smooth); + + Mesh *mesh_in = mesh_component.get_for_write(); + + /* Apply subdivision to mesh. */ + Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh_in); + + /* In case of bad topology, skip to input mesh. */ + if (subdiv == nullptr) { + return; + } + + Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh_in); + BKE_mesh_normals_tag_dirty(mesh_out); + + mesh_component.replace(mesh_out); + + BKE_subdiv_free(subdiv); + }); +#endif + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_subdivision_surface_cc + +void register_node_type_geo_subdivision_surface() +{ + namespace file_ns = blender::nodes::node_geo_subdivision_surface_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + node_type_init(&ntype, file_ns::node_init); + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + node_type_storage(&ntype, + "NodeGeometrySubdivisionSurface", + node_free_standard_storage, + node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index ca857c4d2e3..a2f05677310 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -19,149 +19,298 @@ #include "UI_interface.h" #include "UI_resources.h" -namespace blender::nodes { +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" -static void geo_node_switch_declare(NodeDeclarationBuilder &b) +#include "BKE_material.h" + +#include "NOD_socket_search_link.hh" + +#include "FN_multi_function_signature.hh" + +namespace blender::nodes::node_geo_switch_cc { + +NODE_STORAGE_FUNCS(NodeSwitch) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Bool>("Switch"); - - b.add_input<decl::Float>("False"); - b.add_input<decl::Float>("True"); - b.add_input<decl::Int>("False", "False_001").min(-100000).max(100000); - b.add_input<decl::Int>("True", "True_001").min(-100000).max(100000); - b.add_input<decl::Bool>("False", "False_002"); - b.add_input<decl::Bool>("True", "True_002"); - b.add_input<decl::Vector>("False", "False_003"); - b.add_input<decl::Vector>("True", "True_003"); - b.add_input<decl::Color>("False", "False_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::Color>("True", "True_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::String>("False", "False_005"); - b.add_input<decl::String>("True", "True_005"); - b.add_input<decl::Geometry>("False", "False_006"); - b.add_input<decl::Geometry>("True", "True_006"); - b.add_input<decl::Object>("False", "False_007"); - b.add_input<decl::Object>("True", "True_007"); - b.add_input<decl::Collection>("False", "False_008"); - b.add_input<decl::Collection>("True", "True_008"); - b.add_input<decl::Texture>("False", "False_009"); - b.add_input<decl::Texture>("True", "True_009"); - b.add_input<decl::Material>("False", "False_010"); - b.add_input<decl::Material>("True", "True_010"); - - b.add_output<decl::Float>("Output"); - b.add_output<decl::Int>("Output", "Output_001"); - b.add_output<decl::Bool>("Output", "Output_002"); - b.add_output<decl::Vector>("Output", "Output_003"); - b.add_output<decl::Color>("Output", "Output_004"); - b.add_output<decl::String>("Output", "Output_005"); - b.add_output<decl::Geometry>("Output", "Output_006"); - b.add_output<decl::Object>("Output", "Output_007"); - b.add_output<decl::Collection>("Output", "Output_008"); - b.add_output<decl::Texture>("Output", "Output_009"); - b.add_output<decl::Material>("Output", "Output_010"); + b.add_input<decl::Bool>(N_("Switch")).default_value(false).supports_field(); + b.add_input<decl::Bool>(N_("Switch"), "Switch_001").default_value(false); + + b.add_input<decl::Float>(N_("False")).supports_field(); + b.add_input<decl::Float>(N_("True")).supports_field(); + b.add_input<decl::Int>(N_("False"), "False_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Int>(N_("True"), "True_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Bool>(N_("False"), "False_002") + .default_value(false) + .hide_value() + .supports_field(); + b.add_input<decl::Bool>(N_("True"), "True_002") + .default_value(true) + .hide_value() + .supports_field(); + b.add_input<decl::Vector>(N_("False"), "False_003").supports_field(); + b.add_input<decl::Vector>(N_("True"), "True_003").supports_field(); + b.add_input<decl::Color>(N_("False"), "False_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::Color>(N_("True"), "True_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::String>(N_("False"), "False_005").supports_field(); + b.add_input<decl::String>(N_("True"), "True_005").supports_field(); + + b.add_input<decl::Geometry>(N_("False"), "False_006"); + b.add_input<decl::Geometry>(N_("True"), "True_006"); + b.add_input<decl::Object>(N_("False"), "False_007"); + b.add_input<decl::Object>(N_("True"), "True_007"); + b.add_input<decl::Collection>(N_("False"), "False_008"); + b.add_input<decl::Collection>(N_("True"), "True_008"); + b.add_input<decl::Texture>(N_("False"), "False_009"); + b.add_input<decl::Texture>(N_("True"), "True_009"); + b.add_input<decl::Material>(N_("False"), "False_010"); + b.add_input<decl::Material>(N_("True"), "True_010"); + b.add_input<decl::Image>(N_("False"), "False_011"); + b.add_input<decl::Image>(N_("True"), "True_011"); + + b.add_output<decl::Float>(N_("Output")).dependent_field(); + b.add_output<decl::Int>(N_("Output"), "Output_001").dependent_field(); + b.add_output<decl::Bool>(N_("Output"), "Output_002").dependent_field(); + b.add_output<decl::Vector>(N_("Output"), "Output_003").dependent_field(); + b.add_output<decl::Color>(N_("Output"), "Output_004").dependent_field(); + b.add_output<decl::String>(N_("Output"), "Output_005").dependent_field(); + b.add_output<decl::Geometry>(N_("Output"), "Output_006"); + b.add_output<decl::Object>(N_("Output"), "Output_007"); + b.add_output<decl::Collection>(N_("Output"), "Output_008"); + b.add_output<decl::Texture>(N_("Output"), "Output_009"); + b.add_output<decl::Material>(N_("Output"), "Output_010"); + b.add_output<decl::Image>(N_("Output"), "Output_011"); } -static void geo_node_switch_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_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) +static void node_init(bNodeTree *UNUSED(tree), bNode *node) { - NodeSwitch *data = (NodeSwitch *)MEM_callocN(sizeof(NodeSwitch), __func__); + NodeSwitch *data = MEM_cnew<NodeSwitch>(__func__); data->input_type = SOCK_GEOMETRY; node->storage = data; } -static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - NodeSwitch *node_storage = (NodeSwitch *)node->storage; + const NodeSwitch &storage = node_storage(*node); int index = 0; - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - nodeSetSocketAvailability( - socket, index == 0 || socket->type == (eNodeSocketDatatype)node_storage->input_type); - index++; + bNodeSocket *field_switch = (bNodeSocket *)node->inputs.first; + bNodeSocket *non_field_switch = (bNodeSocket *)field_switch->next; + + const bool fields_type = ELEM( + storage.input_type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA, SOCK_STRING); + + nodeSetSocketAvailability(ntree, field_switch, fields_type); + nodeSetSocketAvailability(ntree, non_field_switch, !fields_type); + + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &node->inputs, index) { + if (index <= 1) { + continue; + } + nodeSetSocketAvailability(ntree, socket, socket->type == storage.input_type); } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { - nodeSetSocketAvailability(socket, - socket->type == (eNodeSocketDatatype)node_storage->input_type); + nodeSetSocketAvailability(ntree, socket, socket->type == storage.input_type); } } -template<typename T> -static void output_input(GeoNodeExecParams ¶ms, - const bool input, - const StringRef input_suffix, - const StringRef output_identifier) +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - 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)) { + if (params.in_out() == SOCK_OUT) { + params.add_item(IFACE_("Output"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSwitch"); + node_storage(node).input_type = params.socket.type; + params.update_and_connect_available_socket(node, "Output"); + }); + } + else { + if (params.other_socket().type == SOCK_BOOLEAN) { + params.add_item(IFACE_("Switch"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSwitch"); + params.connect_available_socket(node, "Start"); + }); + } + params.add_item(IFACE_("False"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSwitch"); + node_storage(node).input_type = params.socket.type; + params.update_and_connect_available_socket(node, "False"); + }); + params.add_item(IFACE_("True"), [](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSwitch"); + node_storage(node).input_type = params.socket.type; + params.update_and_connect_available_socket(node, "True"); + }); + } +} + +template<typename T> class SwitchFieldsFunction : public fn::MultiFunction { + public: + SwitchFieldsFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Switch"}; + signature.single_input<bool>("Switch"); + signature.single_input<T>("False"); + signature.single_input<T>("True"); + signature.single_output<T>("Output"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<bool> &switches = params.readonly_single_input<bool>(0, "Switch"); + const VArray<T> &falses = params.readonly_single_input<T>(1, "False"); + const VArray<T> &trues = params.readonly_single_input<T>(2, "True"); + MutableSpan<T> values = params.uninitialized_single_output_if_required<T>(3, "Output"); + for (int64_t i : mask) { + new (&values[i]) T(switches[i] ? trues[i] : falses[i]); + } + } +}; + +template<typename T> void switch_fields(GeoNodeExecParams ¶ms, const StringRef suffix) +{ + if (params.lazy_require_input("Switch")) { + return; + } + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + Field<bool> switches_field = params.get_input<Field<bool>>("Switch"); + if (switches_field.node().depends_on_input()) { + /* The switch has to be incorporated into the field. Both inputs have to be evaluated. */ + const bool require_false = params.lazy_require_input(name_false); + const bool require_true = params.lazy_require_input(name_true); + if (require_false | require_true) { return; } - params.set_output(output_identifier, params.extract_input<T>(name_b)); + + Field<T> falses_field = params.extract_input<Field<T>>(name_false); + Field<T> trues_field = params.extract_input<Field<T>>(name_true); + + auto switch_fn = std::make_unique<SwitchFieldsFunction<T>>(); + auto switch_op = std::make_shared<FieldOperation>(FieldOperation( + std::move(switch_fn), + {std::move(switches_field), std::move(falses_field), std::move(trues_field)})); + + params.set_output(name_output, Field<T>(switch_op, 0)); } else { - params.set_input_unused(name_b); - if (params.lazy_require_input(name_a)) { - return; + /* The switch input is constant, so just evaluate and forward one of the inputs. */ + const bool switch_value = fn::evaluate_constant_field(switches_field); + if (switch_value) { + params.set_input_unused(name_false); + if (params.lazy_require_input(name_true)) { + return; + } + params.set_output(name_output, params.extract_input<Field<T>>(name_true)); + } + else { + params.set_input_unused(name_true); + if (params.lazy_require_input(name_false)) { + return; + } + params.set_output(name_output, params.extract_input<Field<T>>(name_false)); } - params.set_output(output_identifier, params.extract_input<T>(name_a)); } } -static void geo_node_switch_exec(GeoNodeExecParams params) +template<typename T> void switch_no_fields(GeoNodeExecParams ¶ms, const StringRef suffix) { - if (params.lazy_require_input("Switch")) { + if (params.lazy_require_input("Switch_001")) { return; } - const NodeSwitch &storage = *(const NodeSwitch *)params.node().storage; - const bool input = params.get_input<bool>("Switch"); - switch ((eNodeSocketDatatype)storage.input_type) { + bool switch_value = params.get_input<bool>("Switch_001"); + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + if (switch_value) { + params.set_input_unused(name_false); + if (params.lazy_require_input(name_true)) { + return; + } + params.set_output(name_output, params.extract_input<T>(name_true)); + } + else { + params.set_input_unused(name_true); + if (params.lazy_require_input(name_false)) { + return; + } + params.set_output(name_output, params.extract_input<T>(name_false)); + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeSwitch &storage = node_storage(params.node()); + const eNodeSocketDatatype data_type = static_cast<eNodeSocketDatatype>(storage.input_type); + + switch (data_type) { + case SOCK_FLOAT: { - output_input<float>(params, input, "", "Output"); + switch_fields<float>(params, ""); break; } case SOCK_INT: { - output_input<int>(params, input, "_001", "Output_001"); + switch_fields<int>(params, "_001"); break; } case SOCK_BOOLEAN: { - output_input<bool>(params, input, "_002", "Output_002"); + switch_fields<bool>(params, "_002"); break; } case SOCK_VECTOR: { - output_input<float3>(params, input, "_003", "Output_003"); + switch_fields<float3>(params, "_003"); break; } case SOCK_RGBA: { - output_input<ColorGeometry4f>(params, input, "_004", "Output_004"); + switch_fields<ColorGeometry4f>(params, "_004"); break; } case SOCK_STRING: { - output_input<std::string>(params, input, "_005", "Output_005"); + switch_fields<std::string>(params, "_005"); break; } case SOCK_GEOMETRY: { - output_input<GeometrySet>(params, input, "_006", "Output_006"); + switch_no_fields<GeometrySet>(params, "_006"); break; } case SOCK_OBJECT: { - output_input<Object *>(params, input, "_007", "Output_007"); + switch_no_fields<Object *>(params, "_007"); break; } case SOCK_COLLECTION: { - output_input<Collection *>(params, input, "_008", "Output_008"); + switch_no_fields<Collection *>(params, "_008"); break; } case SOCK_TEXTURE: { - output_input<Tex *>(params, input, "_009", "Output_009"); + switch_no_fields<Tex *>(params, "_009"); break; } case SOCK_MATERIAL: { - output_input<Material *>(params, input, "_010", "Output_010"); + switch_no_fields<Material *>(params, "_010"); + break; + } + case SOCK_IMAGE: { + switch_no_fields<Image *>(params, "_011"); break; } default: @@ -170,19 +319,22 @@ static void geo_node_switch_exec(GeoNodeExecParams params) } } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_switch_cc void register_node_type_geo_switch() { + namespace file_ns = blender::nodes::node_geo_switch_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_SWITCH, "Switch", NODE_CLASS_CONVERTER, 0); - ntype.declare = blender::nodes::geo_node_switch_declare; - node_type_init(&ntype, blender::nodes::geo_node_switch_init); - node_type_update(&ntype, blender::nodes::geo_node_switch_update); + geo_node_type_base(&ntype, GEO_NODE_SWITCH, "Switch", NODE_CLASS_CONVERTER); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_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 = file_ns::node_geo_exec; ntype.geometry_node_execute_supports_laziness = true; - ntype.draw_buttons = blender::nodes::geo_node_switch_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc new file mode 100644 index 00000000000..5a8d9ab470d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc @@ -0,0 +1,842 @@ +/* + * 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 "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "FN_generic_array.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_transfer_attribute_cc { + +using namespace blender::bke::mesh_surface_sample; +using blender::fn::GArray; + +NODE_STORAGE_FUNCS(NodeGeometryTransferAttribute) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Source")) + .supported_type({GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}); + + b.add_input<decl::Vector>(N_("Attribute")).hide_value().supports_field(); + b.add_input<decl::Float>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); + b.add_input<decl::Color>(N_("Attribute"), "Attribute_002").hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Attribute"), "Attribute_003").hide_value().supports_field(); + b.add_input<decl::Int>(N_("Attribute"), "Attribute_004").hide_value().supports_field(); + + b.add_input<decl::Vector>(N_("Source Position")) + .implicit_field() + .make_available([](bNode &node) { + node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED; + }); + b.add_input<decl::Int>(N_("Index")).implicit_field().make_available([](bNode &node) { + node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_INDEX; + }); + + b.add_output<decl::Vector>(N_("Attribute")).dependent_field({6, 7}); + b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").dependent_field({6, 7}); + b.add_output<decl::Color>(N_("Attribute"), "Attribute_002").dependent_field({6, 7}); + b.add_output<decl::Bool>(N_("Attribute"), "Attribute_003").dependent_field({6, 7}); + b.add_output<decl::Int>(N_("Attribute"), "Attribute_004").dependent_field({6, 7}); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + const bNode &node = *static_cast<const bNode *>(ptr->data); + const NodeGeometryTransferAttribute &storage = node_storage(node); + const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) + storage.mode; + + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, "", ICON_NONE); + if (mapping != GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED) { + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + } +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryTransferAttribute *data = MEM_cnew<NodeGeometryTransferAttribute>(__func__); + data->data_type = CD_PROP_FLOAT; + data->mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryTransferAttribute &storage = node_storage(*node); + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) + storage.mode; + + bNodeSocket *socket_geometry = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_vector = socket_geometry->next; + bNodeSocket *socket_float = socket_vector->next; + bNodeSocket *socket_color4f = socket_float->next; + bNodeSocket *socket_boolean = socket_color4f->next; + bNodeSocket *socket_int32 = socket_boolean->next; + + bNodeSocket *socket_positions = socket_int32->next; + bNodeSocket *socket_indices = socket_positions->next; + + nodeSetSocketAvailability(ntree, socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, socket_int32, data_type == CD_PROP_INT32); + + nodeSetSocketAvailability(ntree, socket_positions, mapping != GEO_NODE_ATTRIBUTE_TRANSFER_INDEX); + nodeSetSocketAvailability(ntree, socket_indices, mapping == GEO_NODE_ATTRIBUTE_TRANSFER_INDEX); + + bNodeSocket *out_socket_vector = (bNodeSocket *)node->outputs.first; + bNodeSocket *out_socket_float = out_socket_vector->next; + bNodeSocket *out_socket_color4f = out_socket_float->next; + bNodeSocket *out_socket_boolean = out_socket_color4f->next; + bNodeSocket *out_socket_int32 = out_socket_boolean->next; + + nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, out_socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_back(2)); + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + + const std::optional<CustomDataType> type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + /* The input and output sockets have the same name. */ + params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeAttributeTransfer"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Attribute"); + }); + } +} + +static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(positions.size() >= r_indices.size()); + BLI_assert(positions.size() >= r_distances_sq.size()); + BLI_assert(positions.size() >= r_positions.size()); + + for (const int i : mask) { + 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 IndexMask mask, + 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 : mask) { + 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; + if (!r_distances_sq.is_empty()) { + 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 IndexMask mask, + 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, &mesh, BVHTREE_FROM_VERTS, 2); + get_closest_in_bvhtree(tree_data, positions, mask, 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 IndexMask mask, + 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, &mesh, BVHTREE_FROM_EDGES, 2); + get_closest_in_bvhtree(tree_data, positions, mask, 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 IndexMask mask, + 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, &mesh, BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree( + tree_data, positions, mask, 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 IndexMask mask, + 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, mask, looptri_indices, r_distances_sq, r_positions); + + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int i : mask) { + 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 IndexMask mask, + 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, mask, poly_indices, {}, {}); + + for (const int i : mask) { + 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 = math::distance_squared(position, float3(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; + } + } +} + +template<typename T> +void copy_with_indices(const VArray<T> &src, + const IndexMask mask, + const Span<int> indices, + const MutableSpan<T> dst) +{ + if (src.is_empty()) { + return; + } + for (const int i : mask) { + dst[i] = src[indices[i]]; + } +} + +template<typename T> +void copy_with_indices_clamped(const VArray<T> &src, + const IndexMask mask, + const VArray<int> &indices, + const MutableSpan<T> dst) +{ + if (src.is_empty()) { + return; + } + const int max_index = src.size() - 1; + threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { + for (const int i : range) { + const int index = mask[i]; + dst[index] = src[std::clamp(indices[index], 0, max_index)]; + } + }); +} + +template<typename T> +void copy_with_indices_and_comparison(const VArray<T> &src_1, + const VArray<T> &src_2, + const Span<float> distances_1, + const Span<float> distances_2, + const IndexMask mask, + const Span<int> indices_1, + const Span<int> indices_2, + const MutableSpan<T> dst) +{ + if (src_1.is_empty() || src_2.is_empty()) { + return; + } + for (const int i : mask) { + if (distances_1[i] < distances_2[i]) { + dst[i] = src_1[indices_1[i]]; + } + else { + dst[i] = src_2[indices_2[i]]; + } + } +} + +static bool component_is_available(const GeometrySet &geometry, + const GeometryComponentType type, + const AttributeDomain domain) +{ + if (!geometry.has(type)) { + return false; + } + const GeometryComponent &component = *geometry.get_component_for_read(type); + if (component.is_empty()) { + return false; + } + return component.attribute_domain_size(domain) != 0; +} + +/** + * \note Multi-threading for this function is provided by the field evaluator. Since the #call + * function could be called many times, calculate the data from the target geometry once and store + * it for later. + */ +class NearestInterpolatedTransferFunction : public fn::MultiFunction { + GeometrySet target_; + GField src_field_; + + /** + * This function is meant to sample the surface of a mesh rather than take the value from + * individual elements, so use the most complex domain, ensuring no information is lost. In the + * future, it should be possible to use the most complex domain required by the field inputs, to + * simplify sampling and avoid domain conversions. + */ + AttributeDomain domain_ = ATTR_DOMAIN_CORNER; + + fn::MFSignature signature_; + + std::optional<GeometryComponentFieldContext> target_context_; + std::unique_ptr<FieldEvaluator> target_evaluator_; + const GVArray *target_data_; + + public: + NearestInterpolatedTransferFunction(GeometrySet geometry, GField src_field) + : target_(std::move(geometry)), src_field_(std::move(src_field)) + { + target_.ensure_owns_direct_data(); + signature_ = this->create_signature(); + this->set_signature(&signature_); + this->evaluate_target_field(); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Attribute Transfer Nearest Interpolated"}; + signature.single_input<float3>("Position"); + signature.single_output("Attribute", src_field_.cpp_type()); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); + GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Attribute"); + + const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>(); + BLI_assert(mesh_component.has_mesh()); + const Mesh &mesh = *mesh_component.get_for_read(); + BLI_assert(mesh.totpoly > 0); + + /* Find closest points on the mesh surface. */ + Array<int> looptri_indices(mask.min_array_size()); + Array<float3> sampled_positions(mask.min_array_size()); + get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions); + + MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices); + interp.sample_data(*target_data_, domain_, eAttributeMapMode::INTERPOLATED, dst); + } + + private: + void evaluate_target_field() + { + const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>(); + target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_}); + const int domain_size = mesh_component.attribute_domain_size(domain_); + target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size); + target_evaluator_->add(src_field_); + target_evaluator_->evaluate(); + target_data_ = &target_evaluator_->get_evaluated(0); + } +}; + +/** + * \note Multi-threading for this function is provided by the field evaluator. Since the #call + * function could be called many times, calculate the data from the target geometry once and store + * it for later. + */ +class NearestTransferFunction : public fn::MultiFunction { + GeometrySet target_; + GField src_field_; + AttributeDomain domain_; + + fn::MFSignature signature_; + + bool use_mesh_; + bool use_points_; + + /* Store data from the target as a virtual array, since we may only access a few indices. */ + std::optional<GeometryComponentFieldContext> mesh_context_; + std::unique_ptr<FieldEvaluator> mesh_evaluator_; + const GVArray *mesh_data_; + + std::optional<GeometryComponentFieldContext> point_context_; + std::unique_ptr<FieldEvaluator> point_evaluator_; + const GVArray *point_data_; + + public: + NearestTransferFunction(GeometrySet geometry, GField src_field, AttributeDomain domain) + : target_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain) + { + target_.ensure_owns_direct_data(); + signature_ = this->create_signature(); + this->set_signature(&signature_); + + this->use_mesh_ = component_is_available(target_, GEO_COMPONENT_TYPE_MESH, domain_); + this->use_points_ = component_is_available(target_, GEO_COMPONENT_TYPE_POINT_CLOUD, domain_); + + this->evaluate_target_field(); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Attribute Transfer Nearest"}; + signature.single_input<float3>("Position"); + signature.single_output("Attribute", src_field_.cpp_type()); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); + GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Attribute"); + + if (!use_mesh_ && !use_points_) { + dst.type().fill_construct_indices(dst.type().default_value(), dst.data(), mask); + return; + } + + const Mesh *mesh = use_mesh_ ? target_.get_mesh_for_read() : nullptr; + const PointCloud *pointcloud = use_points_ ? target_.get_pointcloud_for_read() : nullptr; + + const int tot_samples = mask.min_array_size(); + + Array<int> point_indices; + Array<float> point_distances; + + /* 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; + + /* If there is a point cloud, find the closest points. */ + if (use_points_) { + point_indices.reinitialize(tot_samples); + if (use_mesh_) { + point_distances.reinitialize(tot_samples); + } + get_closest_pointcloud_points(*pointcloud, positions, mask, point_indices, point_distances); + } + + /* If there is a mesh, find the closest mesh elements. */ + if (use_mesh_) { + mesh_indices.reinitialize(tot_samples); + if (use_points_) { + mesh_distances.reinitialize(tot_samples); + } + switch (domain_) { + case ATTR_DOMAIN_POINT: { + get_closest_mesh_points(*mesh, positions, mask, mesh_indices, mesh_distances, {}); + break; + } + case ATTR_DOMAIN_EDGE: { + get_closest_mesh_edges(*mesh, positions, mask, mesh_indices, mesh_distances, {}); + break; + } + case ATTR_DOMAIN_FACE: { + get_closest_mesh_polygons(*mesh, positions, mask, mesh_indices, mesh_distances, {}); + break; + } + case ATTR_DOMAIN_CORNER: { + get_closest_mesh_corners(*mesh, positions, mask, mesh_indices, mesh_distances, {}); + break; + } + default: { + break; + } + } + } + + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + if (use_mesh_ && use_points_) { + VArray<T> src_mesh = mesh_data_->typed<T>(); + VArray<T> src_point = point_data_->typed<T>(); + copy_with_indices_and_comparison(src_mesh, + src_point, + mesh_distances, + point_distances, + mask, + mesh_indices, + point_indices, + dst.typed<T>()); + } + else if (use_points_) { + VArray<T> src_point = point_data_->typed<T>(); + copy_with_indices(src_point, mask, point_indices, dst.typed<T>()); + } + else if (use_mesh_) { + VArray<T> src_mesh = mesh_data_->typed<T>(); + copy_with_indices(src_mesh, mask, mesh_indices, dst.typed<T>()); + } + }); + } + + private: + void evaluate_target_field() + { + if (use_mesh_) { + const MeshComponent &mesh = *target_.get_component_for_read<MeshComponent>(); + const int domain_size = mesh.attribute_domain_size(domain_); + mesh_context_.emplace(GeometryComponentFieldContext(mesh, domain_)); + mesh_evaluator_ = std::make_unique<FieldEvaluator>(*mesh_context_, domain_size); + mesh_evaluator_->add(src_field_); + mesh_evaluator_->evaluate(); + mesh_data_ = &mesh_evaluator_->get_evaluated(0); + } + + if (use_points_) { + const PointCloudComponent &points = *target_.get_component_for_read<PointCloudComponent>(); + const int domain_size = points.attribute_domain_size(domain_); + point_context_.emplace(GeometryComponentFieldContext(points, domain_)); + point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, domain_size); + point_evaluator_->add(src_field_); + point_evaluator_->evaluate(); + point_data_ = &point_evaluator_->get_evaluated(0); + } + } +}; + +static const GeometryComponent *find_target_component(const GeometrySet &geometry, + const AttributeDomain domain) +{ + /* Choose the other component based on a consistent order, rather than some more complicated + * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */ + static const Array<GeometryComponentType> supported_types = {GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}; + for (const GeometryComponentType src_type : supported_types) { + if (component_is_available(geometry, src_type, domain)) { + return geometry.get_component_for_read(src_type); + } + } + + return nullptr; +} + +/** + * The index-based transfer theoretically does not need realized data when there is only one + * instance geometry set in the target. A future optimization could be removing that limitation + * internally. + */ +class IndexTransferFunction : public fn::MultiFunction { + GeometrySet src_geometry_; + GField src_field_; + AttributeDomain domain_; + + fn::MFSignature signature_; + + std::optional<GeometryComponentFieldContext> geometry_context_; + std::unique_ptr<FieldEvaluator> evaluator_; + const GVArray *src_data_ = nullptr; + + public: + IndexTransferFunction(GeometrySet geometry, GField src_field, const AttributeDomain domain) + : src_geometry_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain) + { + src_geometry_.ensure_owns_direct_data(); + + signature_ = this->create_signature(); + this->set_signature(&signature_); + + this->evaluate_field(); + } + + fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Attribute Transfer Index"}; + signature.single_input<int>("Index"); + signature.single_output("Attribute", src_field_.cpp_type()); + return signature.build(); + } + + void evaluate_field() + { + const GeometryComponent *component = find_target_component(src_geometry_, domain_); + if (component == nullptr) { + return; + } + const int domain_size = component->attribute_domain_size(domain_); + geometry_context_.emplace(GeometryComponentFieldContext(*component, domain_)); + evaluator_ = std::make_unique<FieldEvaluator>(*geometry_context_, domain_size); + evaluator_->add(src_field_); + evaluator_->evaluate(); + src_data_ = &evaluator_->get_evaluated(0); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<int> &indices = params.readonly_single_input<int>(0, "Index"); + GMutableSpan dst = params.uninitialized_single_output(1, "Attribute"); + + const CPPType &type = dst.type(); + if (src_data_ == nullptr) { + type.fill_construct_indices(type.default_value(), dst.data(), mask); + return; + } + + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + copy_with_indices_clamped(src_data_->typed<T>(), mask, indices, dst.typed<T>()); + }); + } +}; + +static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const CustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + return params.extract_input<Field<float>>("Attribute_001"); + case CD_PROP_FLOAT3: + return params.extract_input<Field<float3>>("Attribute"); + case CD_PROP_COLOR: + return params.extract_input<Field<ColorGeometry4f>>("Attribute_002"); + case CD_PROP_BOOL: + return params.extract_input<Field<bool>>("Attribute_003"); + case CD_PROP_INT32: + return params.extract_input<Field<int>>("Attribute_004"); + default: + BLI_assert_unreachable(); + } + return {}; +} + +static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) +{ + switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { + case CD_PROP_FLOAT: { + params.set_output("Attribute_001", Field<float>(field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Attribute", Field<float3>(field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Attribute_002", Field<ColorGeometry4f>(field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Attribute_003", Field<bool>(field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Attribute_004", Field<int>(field)); + break; + } + default: + break; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Source"); + const NodeGeometryTransferAttribute &storage = node_storage(params.node()); + const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) + storage.mode; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); + + GField field = get_input_attribute_field(params, data_type); + + auto return_default = [&]() { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + output_attribute_field(params, fn::make_constant_field<T>(T())); + }); + }; + + GField output_field; + switch (mapping) { + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { + const Mesh *mesh = geometry.get_mesh_for_read(); + if (mesh == nullptr) { + if (!geometry.is_empty()) { + params.error_message_add(NodeWarningType::Error, + TIP_("The target geometry must contain a mesh")); + } + return return_default(); + } + if (mesh->totpoly == 0) { + /* Don't add a warning for empty meshes. */ + if (mesh->totvert != 0) { + params.error_message_add(NodeWarningType::Error, + TIP_("The target mesh must have faces")); + } + return return_default(); + } + auto fn = std::make_unique<NearestInterpolatedTransferFunction>(std::move(geometry), + std::move(field)); + auto op = std::make_shared<FieldOperation>( + FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); + output_field = GField(std::move(op)); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { + if (geometry.has_curve() && !geometry.has_mesh() && !geometry.has_pointcloud()) { + params.error_message_add(NodeWarningType::Error, + TIP_("The target geometry must contain a mesh or a point cloud")); + return return_default(); + } + auto fn = std::make_unique<NearestTransferFunction>( + std::move(geometry), std::move(field), domain); + auto op = std::make_shared<FieldOperation>( + FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); + output_field = GField(std::move(op)); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_INDEX: { + Field<int> indices = params.extract_input<Field<int>>("Index"); + auto fn = std::make_unique<IndexTransferFunction>( + std::move(geometry), std::move(field), domain); + auto op = std::make_shared<FieldOperation>( + FieldOperation(std::move(fn), {std::move(indices)})); + output_field = GField(std::move(op)); + break; + } + } + + output_attribute_field(params, std::move(output_field)); +} + +} // namespace blender::nodes::node_geo_transfer_attribute_cc + +void register_node_type_geo_transfer_attribute() +{ + namespace file_ns = blender::nodes::node_geo_transfer_attribute_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_TRANSFER_ATTRIBUTE, "Transfer Attribute", NODE_CLASS_ATTRIBUTE); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + node_type_storage(&ntype, + "NodeGeometryTransferAttribute", + node_free_standard_storage, + node_copy_standard_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + 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 d5eb067cad0..6187a2eacf9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -25,6 +25,7 @@ #include "DNA_volume_types.h" #include "BKE_mesh.h" +#include "BKE_pointcloud.h" #include "BKE_spline.hh" #include "BKE_volume.h" @@ -34,18 +35,9 @@ namespace blender::nodes { -static void geo_node_transform_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Translation").subtype(PROP_TRANSLATION); - b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER); - b.add_input<decl::Vector>("Scale").default_value({1, 1, 1}).subtype(PROP_XYZ); - b.add_output<decl::Geometry>("Geometry"); -} - static bool use_translate(const float3 rotation, const float3 scale) { - if (compare_ff(rotation.length_squared(), 0.0f, 1e-9f) != 1) { + if (compare_ff(math::length_squared(rotation), 0.0f, 1e-9f) != 1) { return false; } if (compare_ff(scale.x, 1.0f, 1e-9f) != 1 || compare_ff(scale.y, 1.0f, 1e-9f) != 1 || @@ -55,153 +47,187 @@ static bool use_translate(const float3 rotation, const float3 scale) return true; } -void transform_mesh(Mesh *mesh, - const float3 translation, - const float3 rotation, - const float3 scale) +static void translate_mesh(Mesh &mesh, const float3 translation) { - /* Use only translation if rotation and scale are zero. */ - if (use_translate(rotation, scale)) { - if (!translation.is_zero()) { - BKE_mesh_translate(mesh, translation, false); - } - } - else { - const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); - BKE_mesh_transform(mesh, matrix.values, false); - BKE_mesh_normals_tag_dirty(mesh); + if (!math::is_zero(translation)) { + BKE_mesh_translate(&mesh, translation, false); } } -static void transform_pointcloud(PointCloud *pointcloud, - const float3 translation, - const float3 rotation, - const float3 scale) +static void transform_mesh(Mesh &mesh, const float4x4 &transform) { - /* Use only translation if rotation and scale don't apply. */ - if (use_translate(rotation, scale)) { - for (const int i : IndexRange(pointcloud->totpoint)) { - add_v3_v3(pointcloud->co[i], translation); - } + BKE_mesh_transform(&mesh, transform.values, false); + BKE_mesh_normals_tag_dirty(&mesh); +} + +static void translate_pointcloud(PointCloud &pointcloud, const float3 translation) +{ + CustomData_duplicate_referenced_layer(&pointcloud.pdata, CD_PROP_FLOAT3, pointcloud.totpoint); + BKE_pointcloud_update_customdata_pointers(&pointcloud); + for (const int i : IndexRange(pointcloud.totpoint)) { + add_v3_v3(pointcloud.co[i], translation); } - else { - 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; - } +} + +static void transform_pointcloud(PointCloud &pointcloud, const float4x4 &transform) +{ + CustomData_duplicate_referenced_layer(&pointcloud.pdata, CD_PROP_FLOAT3, pointcloud.totpoint); + BKE_pointcloud_update_customdata_pointers(&pointcloud); + for (const int i : IndexRange(pointcloud.totpoint)) { + float3 &co = *(float3 *)pointcloud.co[i]; + co = transform * co; } } -static void transform_instances(InstancesComponent &instances, - const float3 translation, - const float3 rotation, - const float3 scale) +static void translate_instances(InstancesComponent &instances, const float3 translation) { MutableSpan<float4x4> transforms = instances.instance_transforms(); - - /* Use only translation if rotation and scale don't apply. */ - if (use_translate(rotation, scale)) { - for (float4x4 &transform : transforms) { - add_v3_v3(transform.ptr()[3], translation); - } + for (float4x4 &transform : transforms) { + add_v3_v3(transform.ptr()[3], translation); } - else { - const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); - for (float4x4 &transform : transforms) { - transform = matrix * transform; - } +} + +static void transform_instances(InstancesComponent &instances, const float4x4 &transform) +{ + MutableSpan<float4x4> instance_transforms = instances.instance_transforms(); + for (float4x4 &instance_transform : instance_transforms) { + instance_transform = transform * instance_transform; } } -static void transform_volume(Volume *volume, - const float3 translation, - const float3 rotation, - const float3 scale, - GeoNodeExecParams ¶ms) +static void transform_volume(Volume &volume, const float4x4 &transform, const Depsgraph &depsgraph) { #ifdef WITH_OPENVDB /* Scaling an axis to zero is not supported for volumes. */ + const float3 translation = transform.translation(); + const float3 rotation = transform.to_euler(); + const float3 scale = transform.scale(); const float3 limited_scale = { (scale.x == 0.0f) ? FLT_EPSILON : scale.x, (scale.y == 0.0f) ? FLT_EPSILON : scale.y, (scale.z == 0.0f) ? FLT_EPSILON : scale.z, }; + const float4x4 scale_limited_transform = float4x4::from_loc_eul_scale( + translation, rotation, limited_scale); - const Main *bmain = DEG_get_bmain(params.depsgraph()); - BKE_volume_load(volume, bmain); - - const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, limited_scale); + const Main *bmain = DEG_get_bmain(&depsgraph); + BKE_volume_load(&volume, bmain); openvdb::Mat4s vdb_matrix; - memcpy(vdb_matrix.asPointer(), matrix, sizeof(float[4][4])); + memcpy(vdb_matrix.asPointer(), &scale_limited_transform, sizeof(float[4][4])); openvdb::Mat4d vdb_matrix_d{vdb_matrix}; - const int num_grids = BKE_volume_num_grids(volume); + const int num_grids = BKE_volume_num_grids(&volume); for (const int i : IndexRange(num_grids)) { - VolumeGrid *volume_grid = BKE_volume_grid_get_for_write(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::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(&volume, volume_grid, false); openvdb::math::Transform &grid_transform = grid->transform(); grid_transform.postMult(vdb_matrix_d); } #else - UNUSED_VARS(volume, translation, rotation, scale, params); + UNUSED_VARS(volume, transform, depsgraph); #endif } -static void transform_curve(CurveEval &curve, - const float3 translation, - const float3 rotation, - const float3 scale) +static void translate_volume(Volume &volume, const float3 translation, const Depsgraph &depsgraph) { - if (use_translate(rotation, scale)) { - curve.translate(translation); + transform_volume(volume, float4x4::from_location(translation), depsgraph); +} + +static void translate_geometry_set(GeometrySet &geometry, + const float3 translation, + const Depsgraph &depsgraph) +{ + if (CurveEval *curve = geometry.get_curve_for_write()) { + curve->translate(translation); } - else { - const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); - curve.transform(matrix); + if (Mesh *mesh = geometry.get_mesh_for_write()) { + translate_mesh(*mesh, translation); + } + if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { + translate_pointcloud(*pointcloud, translation); + } + if (Volume *volume = geometry.get_volume_for_write()) { + translate_volume(*volume, translation, depsgraph); + } + if (geometry.has_instances()) { + translate_instances(geometry.get_component_for_write<InstancesComponent>(), translation); + } +} + +void transform_geometry_set(GeometrySet &geometry, + const float4x4 &transform, + const Depsgraph &depsgraph) +{ + if (CurveEval *curve = geometry.get_curve_for_write()) { + curve->transform(transform); + } + if (Mesh *mesh = geometry.get_mesh_for_write()) { + transform_mesh(*mesh, transform); + } + if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { + transform_pointcloud(*pointcloud, transform); + } + if (Volume *volume = geometry.get_volume_for_write()) { + transform_volume(*volume, transform, depsgraph); + } + if (geometry.has_instances()) { + transform_instances(geometry.get_component_for_write<InstancesComponent>(), transform); } } -static void geo_node_transform_exec(GeoNodeExecParams params) +void transform_mesh(Mesh &mesh, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + transform_mesh(mesh, matrix); +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_transform_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Vector>(N_("Translation")).subtype(PROP_TRANSLATION); + b.add_input<decl::Vector>(N_("Rotation")).subtype(PROP_EULER); + b.add_input<decl::Vector>(N_("Scale")).default_value({1, 1, 1}).subtype(PROP_XYZ); + b.add_output<decl::Geometry>(N_("Geometry")); +} + +static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); const float3 translation = params.extract_input<float3>("Translation"); const float3 rotation = params.extract_input<float3>("Rotation"); const float3 scale = params.extract_input<float3>("Scale"); - if (geometry_set.has_mesh()) { - Mesh *mesh = geometry_set.get_mesh_for_write(); - transform_mesh(mesh, translation, rotation, scale); - } - if (geometry_set.has_pointcloud()) { - PointCloud *pointcloud = geometry_set.get_pointcloud_for_write(); - transform_pointcloud(pointcloud, translation, rotation, scale); - } - if (geometry_set.has_instances()) { - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - transform_instances(instances, translation, rotation, scale); - } - if (geometry_set.has_volume()) { - Volume *volume = geometry_set.get_volume_for_write(); - transform_volume(volume, translation, rotation, scale, params); + /* Use only translation if rotation and scale don't apply. */ + if (use_translate(rotation, scale)) { + translate_geometry_set(geometry_set, translation, *params.depsgraph()); } - if (geometry_set.has_curve()) { - CurveEval *curve = geometry_set.get_curve_for_write(); - transform_curve(*curve, translation, rotation, scale); + else { + transform_geometry_set(geometry_set, + float4x4::from_loc_eul_scale(translation, rotation, scale), + *params.depsgraph()); } params.set_output("Geometry", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_transform_cc void register_node_type_geo_transform() { + namespace file_ns = blender::nodes::node_geo_transform_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_TRANSFORM, "Transform", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_transform_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_transform_exec; + geo_node_type_base(&ntype, GEO_NODE_TRANSFORM, "Transform", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc new file mode 100644 index 00000000000..91c503ff047 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc @@ -0,0 +1,84 @@ +/* + * 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_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_translate_instances_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Instances")).only_instances(); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Translation")).subtype(PROP_TRANSLATION).supports_field(); + b.add_input<decl::Bool>(N_("Local Space")).default_value(true).supports_field(); + b.add_output<decl::Geometry>(N_("Instances")); +} + +static void translate_instances(GeoNodeExecParams ¶ms, InstancesComponent &instances_component) +{ + GeometryComponentFieldContext field_context{instances_component, ATTR_DOMAIN_INSTANCE}; + + fn::FieldEvaluator evaluator{field_context, instances_component.instances_amount()}; + evaluator.set_selection(params.extract_input<Field<bool>>("Selection")); + evaluator.add(params.extract_input<Field<float3>>("Translation")); + evaluator.add(params.extract_input<Field<bool>>("Local Space")); + evaluator.evaluate(); + + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray<float3> &translations = evaluator.get_evaluated<float3>(0); + const VArray<bool> &local_spaces = evaluator.get_evaluated<bool>(1); + + MutableSpan<float4x4> instance_transforms = instances_component.instance_transforms(); + + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int i_selection : range) { + const int i = selection[i_selection]; + if (local_spaces[i]) { + instance_transforms[i] *= float4x4::from_location(translations[i]); + } + else { + add_v3_v3(instance_transforms[i].values[3], translations[i]); + } + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Instances"); + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + translate_instances(params, instances); + } + params.set_output("Instances", std::move(geometry_set)); +} + +} // namespace blender::nodes::node_geo_translate_instances_cc + +void register_node_type_geo_translate_instances() +{ + namespace file_ns = blender::nodes::node_geo_translate_instances_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_TRANSLATE_INSTANCES, "Translate Instances", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index 7ef0913622c..e78c4d7bc35 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -14,29 +14,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_customdata.h" +#include "BKE_mesh.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "DNA_mesh_types.h" + #include "UI_interface.h" #include "UI_resources.h" #include "node_geometry_util.hh" -extern "C" { -Mesh *triangulate_mesh(Mesh *mesh, - const int quad_method, - const int ngon_method, - const int min_vertices, - const int flag); -} +namespace blender::nodes::node_geo_triangulate_cc { -namespace blender::nodes { - -static void geo_node_triangulate_declare(NodeDeclarationBuilder &b) +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Minimum Vertices").default_value(4).min(4).max(10000); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); + b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); + b.add_input<decl::Int>(N_("Minimum Vertices")).default_value(4).min(4).max(10000); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_triangulate_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "quad_method", 0, "", ICON_NONE); uiItemR(layout, ptr, "ngon_method", 0, "", ICON_NONE); @@ -48,9 +49,35 @@ static void geo_triangulate_init(bNodeTree *UNUSED(ntree), bNode *node) node->custom2 = GEO_NODE_TRIANGULATE_NGON_BEAUTY; } -static void geo_node_triangulate_exec(GeoNodeExecParams params) +static Mesh *triangulate_mesh_selection(const Mesh &mesh, + const int quad_method, + const int ngon_method, + const IndexMask selection, + const int min_vertices) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + CustomData_MeshMasks cd_mask_extra = { + CD_MASK_ORIGINDEX, CD_MASK_ORIGINDEX, 0, CD_MASK_ORIGINDEX}; + BMeshCreateParams create_params{0}; + BMeshFromMeshParams from_mesh_params{true, 1, 1, 1, cd_mask_extra}; + BMesh *bm = BKE_mesh_to_bmesh_ex(&mesh, &create_params, &from_mesh_params); + + /* Tag faces to be triangulated from the selection mask. */ + BM_mesh_elem_table_ensure(bm, BM_FACE); + for (int i_face : selection) { + BM_elem_flag_set(BM_face_at_index(bm, i_face), BM_ELEM_TAG, true); + } + + BM_mesh_triangulate(bm, quad_method, ngon_method, min_vertices, true, NULL, NULL, NULL); + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, &cd_mask_extra, &mesh); + BM_mesh_free(bm); + BKE_mesh_normals_tag_dirty(result); + return result; +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); const int min_vertices = std::max(params.extract_input<int>("Minimum Vertices"), 4); GeometryNodeTriangulateQuads quad_method = static_cast<GeometryNodeTriangulateQuads>( @@ -59,26 +86,38 @@ static void geo_node_triangulate_exec(GeoNodeExecParams params) params.node().custom2); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - /* #triangulate_mesh might modify the input mesh currently. */ - Mesh *mesh_in = geometry_set.get_mesh_for_write(); - if (mesh_in != nullptr) { - Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); - geometry_set.replace_mesh(mesh_out); + if (!geometry_set.has_mesh()) { + return; } + GeometryComponent &component = geometry_set.get_component_for_write<MeshComponent>(); + const Mesh &mesh_in = *geometry_set.get_mesh_for_read(); + + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + GeometryComponentFieldContext context{component, ATTR_DOMAIN_FACE}; + FieldEvaluator evaluator{context, domain_size}; + evaluator.add(selection_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + + Mesh *mesh_out = triangulate_mesh_selection( + mesh_in, quad_method, ngon_method, selection, min_vertices); + geometry_set.replace_mesh(mesh_out); }); - params.set_output("Geometry", std::move(geometry_set)); + params.set_output("Mesh", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_triangulate_cc void register_node_type_geo_triangulate() { + namespace file_ns = blender::nodes::node_geo_triangulate_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_TRIANGULATE, "Triangulate", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_triangulate_declare; - node_type_init(&ntype, blender::nodes::geo_triangulate_init); - ntype.geometry_node_execute = blender::nodes::geo_node_triangulate_exec; - ntype.draw_buttons = blender::nodes::geo_node_triangulate_layout; + geo_node_type_base(&ntype, GEO_NODE_TRIANGULATE, "Triangulate", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; + node_type_init(&ntype, file_ns::geo_triangulate_init); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc index 3331962341f..c717d90f7cc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc @@ -14,20 +14,139 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_context.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "ED_node.h" +#include "ED_spreadsheet.h" + +#include "NOD_socket_search_link.hh" + #include "node_geometry_util.hh" -namespace blender::nodes { -static void geo_node_viewer_declare(NodeDeclarationBuilder &b) +namespace blender::nodes::node_geo_viewer_cc { + +NODE_STORAGE_FUNCS(NodeGeometryViewer) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")); + b.add_input<decl::Float>(N_("Value")).supports_field().hide_value(); + b.add_input<decl::Vector>(N_("Value"), "Value_001").supports_field().hide_value(); + b.add_input<decl::Color>(N_("Value"), "Value_002").supports_field().hide_value(); + b.add_input<decl::Int>(N_("Value"), "Value_003").supports_field().hide_value(); + b.add_input<decl::Bool>(N_("Value"), "Value_004").supports_field().hide_value(); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryViewer *data = MEM_cnew<NodeGeometryViewer>(__func__); + data->data_type = CD_PROP_FLOAT; + + node->storage = data; +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static eNodeSocketDatatype custom_data_type_to_socket_type(const CustomDataType type) +{ + switch (type) { + case CD_PROP_FLOAT: + return SOCK_FLOAT; + case CD_PROP_INT32: + return SOCK_INT; + case CD_PROP_FLOAT3: + return SOCK_VECTOR; + case CD_PROP_BOOL: + return SOCK_BOOLEAN; + case CD_PROP_COLOR: + return SOCK_RGBA; + default: + BLI_assert_unreachable(); + return SOCK_FLOAT; + } +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryViewer &storage = node_storage(*node); + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + const eNodeSocketDatatype socket_type = custom_data_type_to_socket_type(data_type); + + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (socket->type == SOCK_GEOMETRY) { + continue; + } + nodeSetSocketAvailability(ntree, socket, socket->type == socket_type); + } +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - b.add_input<decl::Geometry>("Geometry"); + auto set_active_fn = [](LinkSearchOpParams ¶ms, bNode &viewer_node) { + /* Set this new viewer node active in spreadsheet editors. */ + SpaceNode *snode = CTX_wm_space_node(¶ms.C); + Main *bmain = CTX_data_main(¶ms.C); + ED_node_set_active(bmain, snode, ¶ms.node_tree, &viewer_node, nullptr); + ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, &viewer_node); + }; + + const std::optional<CustomDataType> type = node_socket_to_custom_data_type( + params.other_socket()); + if (params.in_out() == SOCK_OUT) { + /* The viewer node only has inputs. */ + return; + } + if (params.other_socket().type == SOCK_GEOMETRY) { + params.add_item(IFACE_("Geometry"), [set_active_fn](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeViewer"); + params.connect_available_socket(node, "Geometry"); + set_active_fn(params, node); + }); + } + if (type && + ELEM(type, CD_PROP_FLOAT, CD_PROP_BOOL, CD_PROP_INT32, CD_PROP_FLOAT3, CD_PROP_COLOR)) { + params.add_item(IFACE_("Value"), [type, set_active_fn](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeViewer"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Value"); + + /* If the source node has a geometry socket, connect it to the new viewer node as well. */ + LISTBASE_FOREACH (bNodeSocket *, socket, ¶ms.node.outputs) { + if (socket->type == SOCK_GEOMETRY && !(socket->flag & (SOCK_UNAVAIL | SOCK_HIDDEN))) { + nodeAddLink(¶ms.node_tree, + ¶ms.node, + socket, + &node, + static_cast<bNodeSocket *>(node.inputs.first)); + } + } + + set_active_fn(params, node); + }); + } } -} // namespace blender::nodes + +} // namespace blender::nodes::node_geo_viewer_cc void register_node_type_geo_viewer() { + namespace file_ns = blender::nodes::node_geo_viewer_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_VIEWER, "Viewer", NODE_CLASS_OUTPUT, 0); - ntype.declare = blender::nodes::geo_node_viewer_declare; + geo_node_type_base(&ntype, GEO_NODE_VIEWER, "Viewer", NODE_CLASS_OUTPUT); + node_type_storage( + &ntype, "NodeGeometryViewer", node_free_standard_storage, node_copy_standard_storage); + node_type_update(&ntype, file_ns::node_update); + node_type_init(&ntype, file_ns::node_init); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons_ex = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype); } 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 229a35e0007..c7dc73f8a91 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 @@ -35,138 +35,193 @@ #include "UI_interface.h" #include "UI_resources.h" -namespace blender::nodes { +namespace blender::nodes::node_geo_volume_to_mesh_cc { -static void geo_node_volume_to_mesh_declare(NodeDeclarationBuilder &b) +NODE_STORAGE_FUNCS(NodeGeometryVolumeToMesh) + +static void node_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Density"); - b.add_input<decl::Float>("Voxel Size").default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Voxel Amount").default_value(64.0f).min(0.0f); - b.add_input<decl::Float>("Threshold").default_value(0.1f).min(0.0f); - b.add_input<decl::Float>("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_output<decl::Geometry>("Geometry"); + b.add_input<decl::Geometry>(N_("Volume")).supported_type(GEO_COMPONENT_TYPE_VOLUME); + b.add_input<decl::Float>(N_("Voxel Size")) + .default_value(0.3f) + .min(0.01f) + .subtype(PROP_DISTANCE) + .make_available([](bNode &node) { + node_storage(node).resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE; + }); + b.add_input<decl::Float>(N_("Voxel Amount")) + .default_value(64.0f) + .min(0.0f) + .make_available([](bNode &node) { + node_storage(node).resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT; + }); + b.add_input<decl::Float>(N_("Threshold")).default_value(0.1f).min(0.0f); + b.add_input<decl::Float>(N_("Adaptivity")).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Geometry>(N_("Mesh")); } -static void geo_node_volume_to_mesh_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "resolution_mode", 0, IFACE_("Resolution"), ICON_NONE); } -static void geo_node_volume_to_mesh_init(bNodeTree *UNUSED(ntree), bNode *node) +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)MEM_callocN( - sizeof(NodeGeometryVolumeToMesh), __func__); + NodeGeometryVolumeToMesh *data = MEM_cnew<NodeGeometryVolumeToMesh>(__func__); data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID; - - bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Density"); - bNodeSocketValueString *grid_socket_value = (bNodeSocketValueString *)grid_socket->default_value; - STRNCPY(grid_socket_value->value, "density"); - node->storage = data; } -static void geo_node_volume_to_mesh_update(bNodeTree *UNUSED(ntree), bNode *node) +static void node_update(bNodeTree *ntree, bNode *node) { - NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)node->storage; + const NodeGeometryVolumeToMesh &storage = node_storage(*node); 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 == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT); - nodeSetSocketAvailability(voxel_size_socket, - data->resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE); + nodeSetSocketAvailability(ntree, + voxel_amount_socket, + storage.resolution_mode == + VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT); + nodeSetSocketAvailability(ntree, + voxel_size_socket, + storage.resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE); } #ifdef WITH_OPENVDB -static void create_mesh_from_volume(GeometrySet &geometry_set_in, - GeometrySet &geometry_set_out, - GeoNodeExecParams ¶ms) +static bke::VolumeToMeshResolution get_resolution_param(const GeoNodeExecParams ¶ms) { - if (!geometry_set_in.has<VolumeComponent>()) { - return; - } - - const NodeGeometryVolumeToMesh &storage = - *(const NodeGeometryVolumeToMesh *)params.node().storage; + const NodeGeometryVolumeToMesh &storage = node_storage(params.node()); bke::VolumeToMeshResolution resolution; resolution.mode = (VolumeToMeshResolutionMode)storage.resolution_mode; if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT) { - resolution.settings.voxel_amount = params.get_input<float>("Voxel Amount"); - if (resolution.settings.voxel_amount <= 0.0f) { - return; - } + resolution.settings.voxel_amount = std::max(params.get_input<float>("Voxel Amount"), 0.0f); } else if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE) { - resolution.settings.voxel_size = params.get_input<float>("Voxel Size"); - if (resolution.settings.voxel_size <= 0.0f) { - return; - } + resolution.settings.voxel_size = std::max(params.get_input<float>("Voxel Size"), 0.0f); + } + + return resolution; +} + +static Mesh *create_mesh_from_volume_grids(Span<openvdb::GridBase::ConstPtr> grids, + const float threshold, + const float adaptivity, + const bke::VolumeToMeshResolution &resolution) +{ + Array<bke::OpenVDBMeshData> mesh_data(grids.size()); + for (const int i : grids.index_range()) { + mesh_data[i] = bke::volume_to_mesh_data(*grids[i], resolution, threshold, adaptivity); + } + + int vert_offset = 0; + int poly_offset = 0; + int loop_offset = 0; + Array<int> vert_offsets(mesh_data.size()); + Array<int> poly_offsets(mesh_data.size()); + Array<int> loop_offsets(mesh_data.size()); + for (const int i : grids.index_range()) { + const bke::OpenVDBMeshData &data = mesh_data[i]; + vert_offsets[i] = vert_offset; + poly_offsets[i] = poly_offset; + loop_offsets[i] = loop_offset; + vert_offset += data.verts.size(); + poly_offset += (data.tris.size() + data.quads.size()); + loop_offset += (3 * data.tris.size() + 4 * data.quads.size()); } - const VolumeComponent *component = geometry_set_in.get_component_for_read<VolumeComponent>(); - const Volume *volume = component->get_for_read(); + Mesh *mesh = BKE_mesh_new_nomain(vert_offset, 0, 0, loop_offset, poly_offset); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + + for (const int i : grids.index_range()) { + const bke::OpenVDBMeshData &data = mesh_data[i]; + bke::fill_mesh_from_openvdb_data(data.verts, + data.tris, + data.quads, + vert_offsets[i], + poly_offsets[i], + loop_offsets[i], + verts, + polys, + loops); + } + + BKE_mesh_calc_edges(mesh, false, false); + BKE_mesh_normals_tag_dirty(mesh); + + return mesh; +} + +static Mesh *create_mesh_from_volume(GeometrySet &geometry_set, GeoNodeExecParams ¶ms) +{ + const Volume *volume = geometry_set.get_volume_for_read(); if (volume == nullptr) { - return; + return nullptr; } + const bke::VolumeToMeshResolution resolution = get_resolution_param(params); const Main *bmain = DEG_get_bmain(params.depsgraph()); BKE_volume_load(volume, bmain); - 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; + Vector<openvdb::GridBase::ConstPtr> grids; + 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); + grids.append(std::move(grid)); } - float threshold = params.get_input<float>("Threshold"); - float adaptivity = params.get_input<float>("Adaptivity"); - - const openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); - Mesh *mesh = bke::volume_to_mesh(*grid, resolution, threshold, adaptivity); - if (mesh == nullptr) { - return; + if (grids.is_empty()) { + return nullptr; } - BKE_id_material_eval_ensure_default_slot(&mesh->id); - MeshComponent &dst_component = geometry_set_out.get_component_for_write<MeshComponent>(); - dst_component.replace(mesh); + + return create_mesh_from_volume_grids(grids, + params.get_input<float>("Threshold"), + params.get_input<float>("Adaptivity"), + resolution); } #endif /* WITH_OPENVDB */ -static void geo_node_volume_to_mesh_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params) { - GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Geometry"); - GeometrySet geometry_set_out; + GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume"); #ifdef WITH_OPENVDB - create_mesh_from_volume(geometry_set_in, geometry_set_out, params); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + Mesh *mesh = create_mesh_from_volume(geometry_set, params); + geometry_set.replace_mesh(mesh); + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + }); #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without OpenVDB")); #endif - params.set_output("Geometry", geometry_set_out); + params.set_output("Mesh", std::move(geometry_set)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_volume_to_mesh_cc void register_node_type_geo_volume_to_mesh() { + namespace file_ns = blender::nodes::node_geo_volume_to_mesh_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_VOLUME_TO_MESH, "Volume to Mesh", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_volume_to_mesh_declare; + geo_node_type_base(&ntype, GEO_NODE_VOLUME_TO_MESH, "Volume to Mesh", NODE_CLASS_GEOMETRY); + ntype.declare = file_ns::node_declare; node_type_storage( &ntype, "NodeGeometryVolumeToMesh", node_free_standard_storage, node_copy_standard_storage); - node_type_size(&ntype, 200, 120, 700); - node_type_init(&ntype, blender::nodes::geo_node_volume_to_mesh_init); - node_type_update(&ntype, blender::nodes::geo_node_volume_to_mesh_update); - ntype.geometry_node_execute = blender::nodes::geo_node_volume_to_mesh_exec; - ntype.draw_buttons = blender::nodes::geo_node_volume_to_mesh_layout; + node_type_size(&ntype, 170, 120, 700); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; nodeRegisterType(&ntype); } |