/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_array.hh" #include "BLI_devirtualize_parameters.hh" #include "BLI_set.hh" #include "BLI_task.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_geometry_set.hh" #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_curve_to_mesh.hh" namespace blender::bke { static void mark_edges_sharp(MutableSpan edges) { for (MEdge &edge : edges) { edge.flag |= ME_SHARP; } } static void fill_mesh_topology(const int vert_offset, const int edge_offset, const int poly_offset, const int loop_offset, const int main_point_num, const int profile_point_num, const bool main_cyclic, const bool profile_cyclic, const bool fill_caps, MutableSpan edges, MutableSpan loops, MutableSpan polys) { const int main_segment_num = curves::segments_num(main_point_num, main_cyclic); const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic); if (profile_point_num == 1) { for (const int i : IndexRange(main_point_num - 1)) { MEdge &edge = edges[edge_offset + i]; edge.v1 = vert_offset + i; edge.v2 = vert_offset + i + 1; edge.flag = ME_LOOSEEDGE; } if (main_cyclic && main_segment_num > 1) { MEdge &edge = edges[edge_offset + main_segment_num - 1]; edge.v1 = vert_offset + main_point_num - 1; edge.v2 = vert_offset; edge.flag = ME_LOOSEEDGE; } return; } /* Add the edges running along the length of the curve, starting at each profile vertex. */ const int main_edges_start = edge_offset; for (const int i_profile : IndexRange(profile_point_num)) { const int profile_edge_offset = main_edges_start + i_profile * main_segment_num; for (const int i_ring : IndexRange(main_segment_num)) { const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1; const int ring_vert_offset = vert_offset + profile_point_num * i_ring; const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring; MEdge &edge = edges[profile_edge_offset + i_ring]; edge.v1 = ring_vert_offset + i_profile; edge.v2 = next_ring_vert_offset + i_profile; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } /* Add the edges running along each profile ring. */ const int profile_edges_start = main_edges_start + profile_point_num * main_segment_num; for (const int i_ring : IndexRange(main_point_num)) { const int ring_vert_offset = vert_offset + profile_point_num * i_ring; const int ring_edge_offset = profile_edges_start + i_ring * profile_segment_num; for (const int i_profile : IndexRange(profile_segment_num)) { const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1; MEdge &edge = edges[ring_edge_offset + i_profile]; edge.v1 = ring_vert_offset + i_profile; edge.v2 = ring_vert_offset + i_next_profile; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } /* Calculate poly and corner indices. */ for (const int i_ring : IndexRange(main_segment_num)) { const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1; const int ring_vert_offset = vert_offset + profile_point_num * i_ring; const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring; const int ring_edge_start = profile_edges_start + profile_segment_num * i_ring; const int next_ring_edge_offset = profile_edges_start + profile_segment_num * i_next_ring; const int ring_poly_offset = poly_offset + i_ring * profile_segment_num; const int ring_loop_offset = loop_offset + i_ring * profile_segment_num * 4; for (const int i_profile : IndexRange(profile_segment_num)) { const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1; const int main_edge_start = main_edges_start + main_segment_num * i_profile; const int next_main_edge_start = main_edges_start + main_segment_num * i_next_profile; MPoly &poly = polys[ring_poly_offset + i_profile]; poly.loopstart = ring_segment_loop_offset; poly.totloop = 4; poly.flag = ME_SMOOTH; MLoop &loop_a = loops[ring_segment_loop_offset]; loop_a.v = ring_vert_offset + i_profile; loop_a.e = ring_edge_start + i_profile; MLoop &loop_b = loops[ring_segment_loop_offset + 1]; loop_b.v = ring_vert_offset + i_next_profile; loop_b.e = next_main_edge_start + i_ring; MLoop &loop_c = loops[ring_segment_loop_offset + 2]; loop_c.v = next_ring_vert_offset + i_next_profile; loop_c.e = next_ring_edge_offset + i_profile; MLoop &loop_d = loops[ring_segment_loop_offset + 3]; loop_d.v = next_ring_vert_offset + i_profile; loop_d.e = main_edge_start + i_ring; } } const bool has_caps = fill_caps && !main_cyclic && profile_cyclic; if (has_caps) { const int poly_num = main_segment_num * profile_segment_num; const int cap_loop_offset = loop_offset + poly_num * 4; const int cap_poly_offset = poly_offset + poly_num; MPoly &poly_start = polys[cap_poly_offset]; poly_start.loopstart = cap_loop_offset; poly_start.totloop = profile_segment_num; MPoly &poly_end = polys[cap_poly_offset + 1]; poly_end.loopstart = cap_loop_offset + profile_segment_num; poly_end.totloop = profile_segment_num; const int last_ring_index = main_point_num - 1; const int last_ring_vert_offset = vert_offset + profile_point_num * last_ring_index; const int last_ring_edge_offset = profile_edges_start + profile_segment_num * last_ring_index; for (const int i : IndexRange(profile_segment_num)) { const int i_inv = profile_segment_num - i - 1; MLoop &loop_start = loops[cap_loop_offset + i]; loop_start.v = vert_offset + i_inv; loop_start.e = profile_edges_start + ((i == (profile_segment_num - 1)) ? (profile_segment_num - 1) : (i_inv - 1)); MLoop &loop_end = loops[cap_loop_offset + profile_segment_num + i]; loop_end.v = last_ring_vert_offset + i; loop_end.e = last_ring_edge_offset + i; } mark_edges_sharp(edges.slice(profile_edges_start, profile_segment_num)); mark_edges_sharp(edges.slice(last_ring_edge_offset, profile_segment_num)); } } static void mark_bezier_vector_edges_sharp(const int profile_point_num, const int main_segment_num, const Span control_point_offsets, const Span handle_types_left, const Span handle_types_right, MutableSpan edges) { const int main_edges_start = 0; if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) { mark_edges_sharp(edges.slice(main_edges_start, main_segment_num)); } for (const int i : IndexRange(profile_point_num).drop_front(1)) { if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) { mark_edges_sharp(edges.slice( main_edges_start + main_segment_num * control_point_offsets[i - 1], main_segment_num)); } } } static void fill_mesh_positions(const int main_point_num, const int profile_point_num, const Span main_positions, const Span profile_positions, const Span tangents, const Span normals, const Span radii, MutableSpan mesh_positions) { if (profile_point_num == 1) { for (const int i_ring : IndexRange(main_point_num)) { float4x4 point_matrix = float4x4::from_normalized_axis_data( main_positions[i_ring], normals[i_ring], tangents[i_ring]); if (!radii.is_empty()) { point_matrix.apply_scale(radii[i_ring]); } mesh_positions[i_ring] = point_matrix * profile_positions.first(); } } else { for (const int i_ring : IndexRange(main_point_num)) { float4x4 point_matrix = float4x4::from_normalized_axis_data( main_positions[i_ring], normals[i_ring], tangents[i_ring]); if (!radii.is_empty()) { point_matrix.apply_scale(radii[i_ring]); } const int ring_vert_start = i_ring * profile_point_num; for (const int i_profile : IndexRange(profile_point_num)) { mesh_positions[ring_vert_start + i_profile] = point_matrix * profile_positions[i_profile]; } } } } struct CurvesInfo { const CurvesGeometry &main; const CurvesGeometry &profile; /* Make sure these are spans because they are potentially accessed many times. */ VArraySpan main_cyclic; VArraySpan profile_cyclic; }; static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile) { return {main, profile, main.cyclic(), profile.cyclic()}; } struct ResultOffsets { /** The total number of curve combinations. */ int total; /** Offsets into the result mesh for each combination. */ Array vert; Array edge; Array loop; Array poly; /* The indices of the main and profile curves that form each combination. */ Array main_indices; Array profile_indices; }; static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps) { ResultOffsets result; result.total = info.main.curves_num() * info.profile.curves_num(); result.vert.reinitialize(result.total + 1); result.edge.reinitialize(result.total + 1); result.loop.reinitialize(result.total + 1); result.poly.reinitialize(result.total + 1); result.main_indices.reinitialize(result.total); result.profile_indices.reinitialize(result.total); info.main.ensure_evaluated_offsets(); info.profile.ensure_evaluated_offsets(); int mesh_index = 0; int vert_offset = 0; int edge_offset = 0; int loop_offset = 0; int poly_offset = 0; for (const int i_main : info.main.curves_range()) { const bool main_cyclic = info.main_cyclic[i_main]; const int main_point_num = info.main.evaluated_points_for_curve(i_main).size(); const int main_segment_num = curves::segments_num(main_point_num, main_cyclic); for (const int i_profile : info.profile.curves_range()) { result.vert[mesh_index] = vert_offset; result.edge[mesh_index] = edge_offset; result.loop[mesh_index] = loop_offset; result.poly[mesh_index] = poly_offset; result.main_indices[mesh_index] = i_main; result.profile_indices[mesh_index] = i_profile; const bool profile_cyclic = info.profile_cyclic[i_profile]; const int profile_point_num = info.profile.evaluated_points_for_curve(i_profile).size(); const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic); const bool has_caps = fill_caps && !main_cyclic && profile_cyclic; const int tube_face_num = main_segment_num * profile_segment_num; vert_offset += main_point_num * profile_point_num; /* Add the ring edges, with one ring for every curve vertex, and the edge loops * that run along the length of the curve, starting on the first profile. */ edge_offset += main_point_num * profile_segment_num + main_segment_num * profile_point_num; /* Add two cap N-gons for every ending. */ poly_offset += tube_face_num + (has_caps ? 2 : 0); /* All faces on the tube are quads, and all cap faces are N-gons with an edge for each * profile edge. */ loop_offset += tube_face_num * 4 + (has_caps ? profile_segment_num * 2 : 0); mesh_index++; } } result.vert.last() = vert_offset; result.edge.last() = edge_offset; result.loop.last() = loop_offset; result.poly.last() = poly_offset; return result; } static eAttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_attributes, const AttributeIDRef &attribute_id) { /* Only use a different domain if it is builtin and must only exist on one domain. */ if (!mesh_attributes.is_builtin(attribute_id)) { return ATTR_DOMAIN_POINT; } std::optional meta_data = mesh_attributes.lookup_meta_data(attribute_id); if (!meta_data) { return ATTR_DOMAIN_POINT; } return meta_data->domain; } static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes, const AttributeAccessor &mesh_attributes, const AttributeIDRef &id, const AttributeMetaData &meta_data) { /* The position attribute has special non-generic evaluation. */ if (id.is_named() && id.name() == "position") { return false; } /* Don't propagate built-in curves attributes that are not built-in on meshes. */ if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) { return false; } if (!id.should_be_kept()) { return false; } if (meta_data.data_type == CD_PROP_STRING) { return false; } return true; } static GSpan evaluated_attribute_if_necessary(const GVArray &src, const CurvesGeometry &curves, const std::array &type_counts, Vector &buffer) { if (type_counts[CURVE_TYPE_POLY] == curves.curves_num() && src.is_span()) { return src.get_internal_span(); } buffer.reinitialize(curves.evaluated_points_num() * src.type().size()); GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()}; curves.interpolate_to_evaluated(src.get_internal_span(), eval); return eval; } /** Information at a specific combination of main and profile curves. */ struct CombinationInfo { int i_main; int i_profile; IndexRange main_points; IndexRange profile_points; bool main_cyclic; bool profile_cyclic; int main_segment_num; int profile_segment_num; IndexRange vert_range; IndexRange edge_range; IndexRange poly_range; IndexRange loop_range; }; template static void foreach_curve_combination(const CurvesInfo &info, const ResultOffsets &offsets, const Fn &fn) { threading::parallel_for(IndexRange(offsets.total), 512, [&](IndexRange range) { for (const int i : range) { const int i_main = offsets.main_indices[i]; const int i_profile = offsets.profile_indices[i]; const IndexRange main_points = info.main.evaluated_points_for_curve(i_main); const IndexRange profile_points = info.profile.evaluated_points_for_curve(i_profile); const bool main_cyclic = info.main_cyclic[i_main]; const bool profile_cyclic = info.profile_cyclic[i_profile]; /* Pass all information in a struct to avoid repeating arguments in many lambdas. * The idea is that inlining `fn` will help avoid accessing unnecessary information, * though that may or may not happen in practice. */ fn(CombinationInfo{i_main, i_profile, main_points, profile_points, main_cyclic, profile_cyclic, curves::segments_num(main_points.size(), main_cyclic), curves::segments_num(profile_points.size(), profile_cyclic), offsets_to_range(offsets.vert.as_span(), i), offsets_to_range(offsets.edge.as_span(), i), offsets_to_range(offsets.poly.as_span(), i), offsets_to_range(offsets.loop.as_span(), i)}); } }); } template static void copy_main_point_data_to_mesh_verts(const Span src, const int profile_point_num, MutableSpan dst) { for (const int i_ring : src.index_range()) { const int ring_vert_start = i_ring * profile_point_num; dst.slice(ring_vert_start, profile_point_num).fill(src[i_ring]); } } template static void copy_main_point_data_to_mesh_edges(const Span src, const int profile_point_num, const int main_segment_num, const int profile_segment_num, MutableSpan dst) { const int edges_start = profile_point_num * main_segment_num; for (const int i_ring : src.index_range()) { const int ring_edge_start = edges_start + profile_segment_num * i_ring; dst.slice(ring_edge_start, profile_segment_num).fill(src[i_ring]); } } template static void copy_main_point_data_to_mesh_faces(const Span src, const int main_segment_num, const int profile_segment_num, MutableSpan dst) { for (const int i_ring : IndexRange(main_segment_num)) { const int ring_face_start = profile_segment_num * i_ring; dst.slice(ring_face_start, profile_segment_num).fill(src[i_ring]); } } static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, const ResultOffsets &offsets, const eAttrDomain dst_domain, const GSpan src_all, GMutableSpan dst_all) { attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); const Span src = src_all.typed(); MutableSpan dst = dst_all.typed(); switch (dst_domain) { case ATTR_DOMAIN_POINT: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_main_point_data_to_mesh_verts( src.slice(info.main_points), info.profile_points.size(), dst.slice(info.vert_range)); }); break; case ATTR_DOMAIN_EDGE: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_main_point_data_to_mesh_edges(src.slice(info.main_points), info.profile_points.size(), info.main_segment_num, info.profile_segment_num, dst.slice(info.edge_range)); }); break; case ATTR_DOMAIN_FACE: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_main_point_data_to_mesh_faces(src.slice(info.main_points), info.main_segment_num, info.profile_segment_num, dst.slice(info.poly_range)); }); break; case ATTR_DOMAIN_CORNER: /* Unsupported for now, since there are no builtin attributes to convert into. */ break; default: BLI_assert_unreachable(); break; } }); } template static void copy_profile_point_data_to_mesh_verts(const Span src, const int main_point_num, MutableSpan dst) { for (const int i_ring : IndexRange(main_point_num)) { const int profile_vert_start = i_ring * src.size(); for (const int i_profile : src.index_range()) { dst[profile_vert_start + i_profile] = src[i_profile]; } } } template static void copy_profile_point_data_to_mesh_edges(const Span src, const int main_segment_num, MutableSpan dst) { for (const int i_profile : src.index_range()) { const int profile_edge_offset = i_profile * main_segment_num; dst.slice(profile_edge_offset, main_segment_num).fill(src[i_profile]); } } template static void copy_profile_point_data_to_mesh_faces(const Span src, const int main_segment_num, const int profile_segment_num, MutableSpan dst) { for (const int i_ring : IndexRange(main_segment_num)) { const int profile_face_start = i_ring * profile_segment_num; for (const int i_profile : IndexRange(profile_segment_num)) { dst[profile_face_start + i_profile] = src[i_profile]; } } } static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, const ResultOffsets &offsets, const eAttrDomain dst_domain, const GSpan src_all, GMutableSpan dst_all) { attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); const Span src = src_all.typed(); MutableSpan dst = dst_all.typed(); switch (dst_domain) { case ATTR_DOMAIN_POINT: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_profile_point_data_to_mesh_verts( src.slice(info.profile_points), info.main_points.size(), dst.slice(info.vert_range)); }); break; case ATTR_DOMAIN_EDGE: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_profile_point_data_to_mesh_edges( src.slice(info.profile_points), info.main_segment_num, dst.slice(info.edge_range)); }); break; case ATTR_DOMAIN_FACE: foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { copy_profile_point_data_to_mesh_faces(src.slice(info.profile_points), info.main_segment_num, info.profile_segment_num, dst.slice(info.poly_range)); }); break; case ATTR_DOMAIN_CORNER: /* Unsupported for now, since there are no builtin attributes to convert into. */ break; default: BLI_assert_unreachable(); break; } }); } template static void copy_indices_to_offset_ranges(const VArray &src, const Span curve_indices, const Span mesh_offsets, MutableSpan dst) { /* This unnecessarily instantiates the "is single" case (which should be handled elsewhere if * it's ever used for attributes), but the alternative is duplicating the function for spans and * other virtual arrays. */ devirtualize_varray(src, [&](const auto &src) { threading::parallel_for(curve_indices.index_range(), 512, [&](IndexRange range) { for (const int i : range) { dst.slice(offsets_to_range(mesh_offsets, i)).fill(src[curve_indices[i]]); } }); }); } static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets, const Span curve_indices, const eAttrDomain dst_domain, const GVArray &src, GMutableSpan dst) { Span offsets; switch (dst_domain) { case ATTR_DOMAIN_POINT: offsets = mesh_offsets.vert; break; case ATTR_DOMAIN_EDGE: offsets = mesh_offsets.edge; break; case ATTR_DOMAIN_FACE: offsets = mesh_offsets.poly; break; case ATTR_DOMAIN_CORNER: offsets = mesh_offsets.loop; break; default: BLI_assert_unreachable(); return; } attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); copy_indices_to_offset_ranges(src.typed(), curve_indices, offsets, dst.typed()); }); } Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, const bool fill_caps) { const CurvesInfo curves_info = get_curves_info(main, profile); const ResultOffsets offsets = calculate_result_offsets(curves_info, fill_caps); if (offsets.vert.last() == 0) { return nullptr; } Mesh *mesh = BKE_mesh_new_nomain( offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); mesh->flag |= ME_AUTOSMOOTH; mesh->smoothresh = DEG2RADF(180.0f); MutableSpan positions = mesh->positions_for_write(); MutableSpan edges = mesh->edges_for_write(); MutableSpan polys = mesh->polys_for_write(); MutableSpan loops = mesh->loops_for_write(); foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { fill_mesh_topology(info.vert_range.start(), info.edge_range.start(), info.poly_range.start(), info.loop_range.start(), info.main_points.size(), info.profile_points.size(), info.main_cyclic, info.profile_cyclic, fill_caps, edges, loops, polys); }); const Span main_positions = main.evaluated_positions(); const Span tangents = main.evaluated_tangents(); const Span normals = main.evaluated_normals(); const Span profile_positions = profile.evaluated_positions(); Vector eval_buffer; const AttributeAccessor main_attributes = main.attributes(); const AttributeAccessor profile_attributes = profile.attributes(); Span radii = {}; if (main_attributes.contains("radius")) { radii = evaluated_attribute_if_necessary( main_attributes.lookup_or_default("radius", ATTR_DOMAIN_POINT, 1.0f), main, main.curve_type_counts(), eval_buffer) .typed(); } foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { fill_mesh_positions(info.main_points.size(), info.profile_points.size(), main_positions.slice(info.main_points), profile_positions.slice(info.profile_points), tangents.slice(info.main_points), normals.slice(info.main_points), radii.is_empty() ? radii : radii.slice(info.main_points), positions.slice(info.vert_range)); }); if (profile.curve_type_counts()[CURVE_TYPE_BEZIER] > 0) { const VArray curve_types = profile.curve_types(); const VArraySpan handle_types_left{profile.handle_types_left()}; const VArraySpan handle_types_right{profile.handle_types_right()}; foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) { if (curve_types[info.i_profile] == CURVE_TYPE_BEZIER) { const IndexRange points = profile.points_for_curve(info.i_profile); mark_bezier_vector_edges_sharp(points.size(), info.main_segment_num, profile.bezier_evaluated_offsets_for_curve(info.i_profile), handle_types_left.slice(points), handle_types_right.slice(points), edges.slice(info.edge_range)); } }); } Set main_attributes_set; MutableAttributeAccessor mesh_attributes = mesh->attributes_for_write(); main_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { if (!should_add_attribute_to_mesh(main_attributes, mesh_attributes, id, meta_data)) { return true; } main_attributes_set.add_new(id); const eAttrDomain src_domain = meta_data.domain; const eCustomDataType type = meta_data.data_type; GVArray src = main_attributes.lookup(id, src_domain, type); const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id); GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span( id, dst_domain, type); if (!dst) { return true; } if (src_domain == ATTR_DOMAIN_POINT) { copy_main_point_domain_attribute_to_mesh( curves_info, offsets, dst_domain, evaluated_attribute_if_necessary(src, main, main.curve_type_counts(), eval_buffer), dst.span); } else if (src_domain == ATTR_DOMAIN_CURVE) { copy_curve_domain_attribute_to_mesh( offsets, offsets.main_indices, dst_domain, src, dst.span); } dst.finish(); return true; }); profile_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { if (main_attributes.contains(id)) { return true; } if (!should_add_attribute_to_mesh(profile_attributes, mesh_attributes, id, meta_data)) { return true; } const eAttrDomain src_domain = meta_data.domain; const eCustomDataType type = meta_data.data_type; GVArray src = profile_attributes.lookup(id, src_domain, type); const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id); GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span( id, dst_domain, type); if (!dst) { return true; } if (src_domain == ATTR_DOMAIN_POINT) { copy_profile_point_domain_attribute_to_mesh( curves_info, offsets, dst_domain, evaluated_attribute_if_necessary(src, profile, profile.curve_type_counts(), eval_buffer), dst.span); } else if (src_domain == ATTR_DOMAIN_CURVE) { copy_curve_domain_attribute_to_mesh( offsets, offsets.profile_indices, dst_domain, src, dst.span); } dst.finish(); return true; }); return mesh; } static CurvesGeometry get_curve_single_vert() { CurvesGeometry curves(1, 1); curves.offsets_for_write().last() = 1; curves.positions_for_write().fill(float3(0)); curves.fill_curve_types(CURVE_TYPE_POLY); return curves; } Mesh *curve_to_wire_mesh(const CurvesGeometry &curve) { static const CurvesGeometry vert_curve = get_curve_single_vert(); return curve_to_mesh_sweep(curve, vert_curve, false); } } // namespace blender::bke