Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc
- This file was copied to source/blender/geometry/intern/mesh_to_curve_convert.cc.
| /* | /* | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License | * modify it under the terms of the GNU General Public License | ||||
| * as published by the Free Software Foundation; either version 2 | * as published by the Free Software Foundation; either version 2 | ||||
| * of the License, or (at your option) any later version. | * of the License, or (at your option) any later version. | ||||
| * | * | ||||
| * This program is distributed in the hope that it will be useful, | * This program is distributed in the hope that it will be useful, | ||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | * | ||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| */ | */ | ||||
| #include "BLI_array.hh" | #include "GEO_mesh_to_curve.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" | #include "node_geometry_util.hh" | ||||
| using blender::Array; | |||||
| namespace blender::nodes { | 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<decl::Geometry>("Mesh"); | b.add_input<decl::Geometry>("Mesh"); | ||||
| b.add_input<decl::String>("Selection"); | b.add_input<decl::String>("Selection"); | ||||
| b.add_output<decl::Geometry>("Curve"); | b.add_output<decl::Geometry>("Curve"); | ||||
| } | } | ||||
| template<typename T> | static void geo_node_legacy_mesh_to_curve_exec(GeoNodeExecParams params) | ||||
| 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>(); | GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); | ||||
| 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()); | geometry_set = bke::geometry_set_realize_instances(geometry_set); | ||||
| curve->add_spline(std::move(spline)); | |||||
| point_to_vert_maps.append(std::move(point_to_vert_map)); | |||||
| } | |||||
| curve->attributes.reallocate(curve->splines().size()); | if (!geometry_set.has_mesh()) { | ||||
| return {std::move(curve), std::move(point_to_vert_maps)}; | params.set_output("Curve", GeometrySet()); | ||||
| return; | |||||
| } | } | ||||
| /** | const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>(); | ||||
| * 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"); | const std::string selection_name = params.extract_input<std::string>("Selection"); | ||||
| if (!selection_name.empty() && !component.attribute_exists(selection_name)) { | if (!selection_name.empty() && !component.attribute_exists(selection_name)) { | ||||
| params.error_message_add(NodeWarningType::Error, | params.error_message_add(NodeWarningType::Error, | ||||
| TIP_("No attribute with name \"") + selection_name + "\""); | TIP_("No attribute with name \"") + selection_name + "\""); | ||||
| } | } | ||||
| GVArray_Typed<bool> selection = component.attribute_get_for_read<bool>( | GVArray_Typed<bool> selection = component.attribute_get_for_read<bool>( | ||||
| selection_name, ATTR_DOMAIN_EDGE, true); | selection_name, ATTR_DOMAIN_EDGE, true); | ||||
| Vector<std::pair<int, int>> selected_edges; | Vector<int64_t> selected_edge_indices; | ||||
| for (const int i : IndexRange(mesh.totedge)) { | for (const int64_t i : IndexRange(component.attribute_domain_size(ATTR_DOMAIN_EDGE))) { | ||||
| if (selection[i]) { | if (selection[i]) { | ||||
| selected_edges.append({mesh.medge[i].v1, mesh.medge[i].v2}); | selected_edge_indices.append(i); | ||||
| } | } | ||||
| } | } | ||||
| return selected_edges; | if (selected_edge_indices.size() == 0) { | ||||
| } | |||||
| 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()); | params.set_output("Curve", GeometrySet()); | ||||
| return; | return; | ||||
| } | } | ||||
| CurveFromEdgesOutput output = mesh_to_curve(verts, selected_edges); | std::unique_ptr<CurveEval> curve = geometry::mesh_to_curve_convert( | ||||
| copy_attributes_to_points(*output.curve, component, output.point_to_vert_maps); | 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 | } // namespace blender::nodes | ||||
| void register_node_type_geo_mesh_to_curve() | void register_node_type_geo_legacy_mesh_to_curve() | ||||
| { | { | ||||
| static bNodeType ntype; | static bNodeType ntype; | ||||
| geo_node_type_base( | geo_node_type_base( | ||||
| &ntype, GEO_NODE_LEGACY_MESH_TO_CURVE, "Mesh to Curve", NODE_CLASS_GEOMETRY, 0); | &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.declare = blender::nodes::geo_node_legacy_mesh_to_curve_declare; | ||||
| ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_curve_exec; | ntype.geometry_node_execute = blender::nodes::geo_node_legacy_mesh_to_curve_exec; | ||||
| nodeRegisterType(&ntype); | nodeRegisterType(&ntype); | ||||
| } | } | ||||