From 17b8da719606abfc9e3076555c626e6fc38dd7c5 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 14 Oct 2021 12:06:48 -0500 Subject: Geometry Nodes: Field version of mesh to curve node This commit adds a fields version of the mesh to curve node, with a field for the input selection. In order to reduce code duplication, it adds the mesh to curve conversion to the new geometry module and calls that implementation from both places. More details on the geometry module can be found here: T86869 Differential Revision: https://developer.blender.org/D12579 --- release/scripts/startup/nodeitems_builtins.py | 1 + source/blender/CMakeLists.txt | 1 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenkernel/intern/node.cc | 1 + source/blender/geometry/CMakeLists.txt | 42 +++ source/blender/geometry/GEO_mesh_to_curve.hh | 35 +++ .../geometry/intern/mesh_to_curve_convert.cc | 283 +++++++++++++++++++++ source/blender/nodes/CMakeLists.txt | 3 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + .../nodes/legacy/node_geo_mesh_to_curve.cc | 273 ++------------------ .../nodes/geometry/nodes/node_geo_mesh_to_curve.cc | 69 +++++ 12 files changed, 457 insertions(+), 254 deletions(-) create mode 100644 source/blender/geometry/CMakeLists.txt create mode 100644 source/blender/geometry/GEO_mesh_to_curve.hh create mode 100644 source/blender/geometry/intern/mesh_to_curve_convert.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index b2faff56656..d4885f7bad8 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -145,6 +145,7 @@ def mesh_node_items(context): yield NodeItem("GeometryNodeEdgeSplit") yield NodeItem("GeometryNodeBoolean") + yield NodeItem("GeometryNodeMeshToCurve") yield NodeItem("GeometryNodeMeshToPoints") yield NodeItem("GeometryNodeMeshSubdivide") yield NodeItem("GeometryNodeTriangulate") diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index fbc0ec440cf..84d31bccc53 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -119,6 +119,7 @@ add_subdirectory(blenloader) add_subdirectory(depsgraph) add_subdirectory(ikplugin) add_subdirectory(simulation) +add_subdirectory(geometry) add_subdirectory(gpu) add_subdirectory(imbuf) add_subdirectory(nodes) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 19215e75d95..9429da9d6a0 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1536,6 +1536,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_SCALE_INSTANCES 1121 #define GEO_NODE_ROTATE_INSTANCES 1122 #define GEO_NODE_EDGE_SPLIT 1123 +#define GEO_NODE_MESH_TO_CURVE 1124 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index cb51249ac27..e526475fa95 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5714,6 +5714,7 @@ static void registerGeometryNodes() register_node_type_geo_legacy_attribute_randomize(); register_node_type_geo_legacy_delete_geometry(); register_node_type_geo_legacy_material_assign(); + register_node_type_geo_legacy_mesh_to_curve(); register_node_type_geo_legacy_points_to_volume(); register_node_type_geo_legacy_select_by_material(); register_node_type_geo_legacy_curve_spline_type(); diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt new file mode 100644 index 00000000000..4e7e0b1ea58 --- /dev/null +++ b/source/blender/geometry/CMakeLists.txt @@ -0,0 +1,42 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# The Original Code is Copyright (C) 2006, Blender Foundation +# All rights reserved. + +set(INC + . + ../blenkernel + ../blenlib + ../blentranslation + ../functions + ../makesdna + ../makesrna + ../../../intern/guardedalloc + ${CMAKE_BINARY_DIR}/source/blender/makesdna/intern +) + +set(SRC + intern/mesh_to_curve_convert.cc + GEO_mesh_to_curve.hh +) + +set(LIB + bf_blenkernel + bf_blenlib +) + +blender_add_lib(bf_geometry "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/geometry/GEO_mesh_to_curve.hh b/source/blender/geometry/GEO_mesh_to_curve.hh new file mode 100644 index 00000000000..66459ab79a9 --- /dev/null +++ b/source/blender/geometry/GEO_mesh_to_curve.hh @@ -0,0 +1,35 @@ +/* + * 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_index_mask.hh" + +#include "BKE_spline.hh" + +#pragma once + +struct Mesh; +class MeshComponent; + +/** \file + * \ingroup geo + */ + +namespace blender::geometry { + +std::unique_ptr mesh_to_curve_convert(const MeshComponent &mesh_component, + const IndexMask selection); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc new file mode 100644 index 00000000000..64b9fa5c01d --- /dev/null +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -0,0 +1,283 @@ +/* + * 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_set.hh" +#include "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" +#include "BKE_geometry_set.hh" +#include "BKE_spline.hh" + +#include "GEO_mesh_to_curve.hh" + +namespace blender::geometry { + +template +static void copy_attribute_to_points(const VArray &source_data, + Span map, + MutableSpan 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> point_to_vert_maps) +{ + MutableSpan splines = curve.splines(); + Set source_attribute_ids = mesh_component.attribute_ids(); + + /* Copy builtin control point attributes. */ + if (source_attribute_ids.contains("tilt")) { + const fn::GVArray_Typed tilt_attribute = mesh_component.attribute_get_for_read( + "tilt", ATTR_DOMAIN_POINT, 0.0f); + threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { + for (const int i : range) { + copy_attribute_to_points( + *tilt_attribute, point_to_vert_maps[i], splines[i]->tilts()); + } + }); + source_attribute_ids.remove_contained("tilt"); + } + if (source_attribute_ids.contains("radius")) { + const fn::GVArray_Typed radius_attribute = mesh_component.attribute_get_for_read( + "radius", ATTR_DOMAIN_POINT, 1.0f); + threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { + for (const int i : range) { + copy_attribute_to_points( + *radius_attribute, point_to_vert_maps[i], splines[i]->radii()); + } + }); + source_attribute_ids.remove_contained("radius"); + } + + for (const bke::AttributeIDRef &attribute_id : source_attribute_ids) { + /* Don't copy attributes that are built-in on meshes but not on curves. */ + if (mesh_component.attribute_is_builtin(attribute_id)) { + continue; + } + + /* Don't copy anonymous attributes with no references anymore. */ + if (attribute_id.is_anonymous()) { + const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); + if (!BKE_anonymous_attribute_id_has_strong_references(&anonymous_id)) { + continue; + } + } + + const fn::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 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( + mesh_attribute->typed(), point_to_vert_maps[i], spline_attribute->typed()); + }); + } + }); + } + + curve.assert_valid_point_attributes(); +} + +struct CurveFromEdgesOutput { + std::unique_ptr curve; + Vector> point_to_vert_maps; +}; + +static CurveFromEdgesOutput edges_to_curve(Span verts, Span> edges) +{ + std::unique_ptr curve = std::make_unique(); + Vector> point_to_vert_maps; + + /* Compute the number of edges connecting to each vertex. */ + Array neighbor_count(verts.size(), 0); + for (const std::pair &edge : edges) { + neighbor_count[edge.first]++; + neighbor_count[edge.second]++; + } + + /* Compute an offset into the array of neighbor edges based on the counts. */ + Array 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 used_slots(verts.size(), 0); + /* Calculate the indices of each vertex's neighboring edges. */ + Array 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 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 spline = std::make_unique(); + Vector 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 spline = std::make_unique(); + Vector 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> get_selected_edges(const Mesh &mesh, const IndexMask selection) +{ + Vector> selected_edges; + for (const int i : selection) { + selected_edges.append({mesh.medge[i].v1, mesh.medge[i].v2}); + } + return selected_edges; +} + +/** + * Convert the mesh into one or many poly splines. Since splines cannot have branches, + * intersections of more than three edges will become breaks in splines. Attributes that + * are not built-in on meshes and not curves are transferred to the result curve. + */ +std::unique_ptr mesh_to_curve_convert(const MeshComponent &mesh_component, + const IndexMask selection) +{ + const Mesh &mesh = *mesh_component.get_for_read(); + Vector> selected_edges = get_selected_edges(*mesh_component.get_for_read(), + selection); + CurveFromEdgesOutput output = edges_to_curve({mesh.mvert, mesh.totvert}, selected_edges); + copy_attributes_to_points(*output.curve, mesh_component, output.point_to_vert_maps); + return std::move(output.curve); +} + +} // namespace blender::geometry diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index f748c89c005..850c5fbffff 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -33,6 +33,7 @@ set(INC ../bmesh ../depsgraph ../functions + ../geometry ../gpu ../imbuf ../makesdna @@ -248,6 +249,7 @@ set(SRC geometry/nodes/node_geo_mesh_primitive_line.cc geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc geometry/nodes/node_geo_mesh_subdivide.cc + geometry/nodes/node_geo_mesh_to_curve.cc geometry/nodes/node_geo_mesh_to_points.cc geometry/nodes/node_geo_object_info.cc geometry/nodes/node_geo_points_to_vertices.cc @@ -446,6 +448,7 @@ set(SRC set(LIB bf_bmesh bf_functions + bf_geometry bf_intern_sky ) diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index d34acd6e9aa..3b78912fbaa 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -34,6 +34,7 @@ void register_node_type_geo_legacy_attribute_proximity(void); void register_node_type_geo_legacy_attribute_randomize(void); void register_node_type_geo_legacy_delete_geometry(void); void register_node_type_geo_legacy_material_assign(void); +void register_node_type_geo_legacy_mesh_to_curve(void); void register_node_type_geo_legacy_points_to_volume(void); void register_node_type_geo_legacy_select_by_material(void); void register_node_type_geo_legacy_curve_spline_type(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index b5640207d04..f3435079563 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -376,6 +376,7 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Subdivide Mesh", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") 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 index 11349dc7d42..7a27e856cef 100644 --- 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 @@ -14,248 +14,31 @@ * 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 "GEO_mesh_to_curve.hh" #include "node_geometry_util.hh" -using blender::Array; - namespace blender::nodes { -static void geo_node_mesh_to_curve_declare(NodeDeclarationBuilder &b) +static void geo_node_legacy_mesh_to_curve_declare(NodeDeclarationBuilder &b) { b.add_input("Mesh"); b.add_input("Selection"); b.add_output("Curve"); } -template -static void copy_attribute_to_points(const VArray &source_data, - Span map, - MutableSpan 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> point_to_vert_maps) -{ - MutableSpan splines = curve.splines(); - Set source_attribute_ids = mesh_component.attribute_ids(); - - /* Copy builtin control point attributes. */ - if (source_attribute_ids.contains("tilt")) { - const GVArray_Typed tilt_attribute = mesh_component.attribute_get_for_read( - "tilt", ATTR_DOMAIN_POINT, 0.0f); - threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { - for (const int i : range) { - copy_attribute_to_points( - *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 radius_attribute = mesh_component.attribute_get_for_read( - "radius", ATTR_DOMAIN_POINT, 1.0f); - threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { - for (const int i : range) { - copy_attribute_to_points( - *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 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( - mesh_attribute->typed(), point_to_vert_maps[i], spline_attribute->typed()); - }); - } - }); - } - - curve.assert_valid_point_attributes(); -} - -struct CurveFromEdgesOutput { - std::unique_ptr curve; - Vector> point_to_vert_maps; -}; - -static CurveFromEdgesOutput mesh_to_curve(Span verts, Span> edges) +static void geo_node_legacy_mesh_to_curve_exec(GeoNodeExecParams params) { - std::unique_ptr curve = std::make_unique(); - Vector> point_to_vert_maps; - - /* Compute the number of edges connecting to each vertex. */ - Array neighbor_count(verts.size(), 0); - for (const std::pair &edge : edges) { - neighbor_count[edge.first]++; - neighbor_count[edge.second]++; - } - - /* Compute an offset into the array of neighbor edges based on the counts. */ - Array 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 used_slots(verts.size(), 0); - /* Calculate the indices of each vertex's neighboring edges. */ - Array 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 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 spline = std::make_unique(); - Vector 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 spline = std::make_unique(); - Vector 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]--; + GeometrySet geometry_set = params.extract_input("Mesh"); - 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; - } + geometry_set = bke::geometry_set_realize_instances(geometry_set); - spline->attributes.reallocate(spline->size()); - curve->add_spline(std::move(spline)); - point_to_vert_maps.append(std::move(point_to_vert_map)); + if (!geometry_set.has_mesh()) { + params.set_output("Curve", GeometrySet()); + return; } - 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> get_selected_edges(GeoNodeExecParams params, - const MeshComponent &component) -{ - const Mesh &mesh = *component.get_for_read(); + const MeshComponent &component = *geometry_set.get_component_for_read(); const std::string selection_name = params.extract_input("Selection"); if (!selection_name.empty() && !component.attribute_exists(selection_name)) { params.error_message_add(NodeWarningType::Error, @@ -264,51 +47,33 @@ static Vector> get_selected_edges(GeoNodeExecParams params, GVArray_Typed selection = component.attribute_get_for_read( selection_name, ATTR_DOMAIN_EDGE, true); - Vector> selected_edges; - for (const int i : IndexRange(mesh.totedge)) { + Vector selected_edge_indices; + for (const int64_t i : IndexRange(component.attribute_domain_size(ATTR_DOMAIN_EDGE))) { if (selection[i]) { - selected_edges.append({mesh.medge[i].v1, mesh.medge[i].v2}); + selected_edge_indices.append(i); } } - return selected_edges; -} - -static void geo_node_mesh_to_curve_exec(GeoNodeExecParams params) -{ - GeometrySet geometry_set = params.extract_input("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(); - const Mesh &mesh = *component.get_for_read(); - Span verts = Span{mesh.mvert, mesh.totvert}; - Vector> selected_edges = get_selected_edges(params, component); - if (selected_edges.size() == 0) { + if (selected_edge_indices.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); + std::unique_ptr curve = geometry::mesh_to_curve_convert( + component, IndexMask(selected_edge_indices)); - params.set_output("Curve", GeometrySet::create_with_curve(output.curve.release())); + params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); } } // namespace blender::nodes -void register_node_type_geo_mesh_to_curve() +void register_node_type_geo_legacy_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; + ntype.declare = blender::nodes::geo_node_legacy_mesh_to_curve_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_mesh_to_curve_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..7bca9ec141b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -0,0 +1,69 @@ +/* + * 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 { + +static void geo_node_legacy_mesh_to_curve_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Mesh"); + b.add_input("Selection").default_value(true).hide_value().supports_field(); + b.add_output("Curve"); +} + +static void geo_node_legacy_mesh_to_curve_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input("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(); + GeometryComponentFieldContext context{component, ATTR_DOMAIN_EDGE}; + fn::FieldEvaluator evaluator{context, component.attribute_domain_size(ATTR_DOMAIN_EDGE)}; + evaluator.add(params.get_input>("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 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 + +void register_node_type_geo_mesh_to_curve() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_mesh_to_curve_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_mesh_to_curve_exec; + nodeRegisterType(&ntype); +} -- cgit v1.2.3