diff options
-rw-r--r-- | source/blender/blenkernel/BKE_curve_to_mesh.hh | 8 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_curves.hh | 29 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curve_to_mesh_convert.cc | 1092 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curves_geometry.cc | 42 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_convert.cc | 4 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_vector.hh | 10 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc | 13 |
7 files changed, 669 insertions, 529 deletions
diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh index a49cb6eb7f5..6e657542e0f 100644 --- a/source/blender/blenkernel/BKE_curve_to_mesh.hh +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -2,7 +2,7 @@ #pragma once -struct CurveEval; +struct CurvesGeometry; struct Mesh; /** \file @@ -21,11 +21,13 @@ namespace blender::bke { * changed anyway in a way that affects the normals. So currently this code uses the safer / * simpler solution of deferring normal calculation to the rest of Blender. */ -Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps); +Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, + const CurvesGeometry &profile, + bool fill_caps); /** * Create a loose-edge mesh based on the evaluated path of the curve's splines. * Transfer curve attributes to the mesh. */ -Mesh *curve_to_wire_mesh(const CurveEval &curve); +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 3d912e10fb2..282e2a40bd0 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -328,6 +328,10 @@ class CurvesGeometry : public ::CurvesGeometry { * calculated. That can be ensured with #ensure_evaluated_offsets. */ void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const; + /** + * Evaluate generic data for curve control points to the standard evaluated points of the curves. + */ + void interpolate_to_evaluated(GSpan src, GMutableSpan dst) const; private: /** @@ -446,6 +450,13 @@ bool segment_is_vector(Span<int8_t> handle_types_left, bool last_cylic_segment_is_vector(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right); /** + * Return true if the handle types at the index are free (#BEZIER_HANDLE_FREE) or vector + * (#BEZIER_HANDLE_VECTOR). In these cases, directional continuitity from the previous and next + * evaluated segments is assumed not to be desired. + */ +bool point_is_sharp(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right, int index); + +/** * Calculate offsets into the curve's evaluated points for each control point. While most control * point edges generate the number of edges specified by the resolution, vector segments only * generate one edge. @@ -715,4 +726,22 @@ inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_in /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Bezier Inline Methods + * \{ */ + +namespace curves::bezier { + +inline bool point_is_sharp(const Span<int8_t> handle_types_left, + const Span<int8_t> handle_types_right, + const int index) +{ + return ELEM(handle_types_left[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) || + ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE); +} + +} // namespace curves::bezier + +/** \} */ + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 9b22a4c9726..c48d155f5ce 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -9,65 +9,15 @@ #include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" +#include "BKE_curves.hh" #include "BKE_geometry_set.hh" #include "BKE_material.h" #include "BKE_mesh.h" -#include "BKE_spline.hh" #include "BKE_curve_to_mesh.hh" namespace blender::bke { -/** Information about the creation of one curve spline and profile spline combination. */ -struct ResultInfo { - const Spline &spline; - const Spline &profile; - int vert_offset; - int edge_offset; - int loop_offset; - int poly_offset; - int spline_vert_len; - int spline_edge_len; - int profile_vert_len; - int profile_edge_len; -}; - -static void vert_extrude_to_mesh_data(const Spline &spline, - const float3 profile_vert, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - const int vert_offset, - const int edge_offset) -{ - const int eval_size = spline.evaluated_points_size(); - for (const int i : IndexRange(eval_size - 1)) { - MEdge &edge = r_edges[edge_offset + i]; - edge.v1 = vert_offset + i; - edge.v2 = vert_offset + i + 1; - edge.flag = ME_LOOSEEDGE; - } - - if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { - MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1]; - edge.v1 = vert_offset + eval_size - 1; - edge.v2 = vert_offset; - edge.flag = ME_LOOSEEDGE; - } - - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - VArray<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i : IndexRange(eval_size)) { - float4x4 point_matrix = float4x4::from_normalized_axis_data( - positions[i], normals[i], tangents[i]); - point_matrix.apply_scale(radii[i]); - - MVert &vert = r_verts[vert_offset + i]; - copy_v3_v3(vert.co, point_matrix * profile_vert); - } -} - static void mark_edges_sharp(MutableSpan<MEdge> edges) { for (MEdge &edge : edges) { @@ -75,36 +25,50 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges) } } -static void spline_extrude_to_mesh_data(const ResultInfo &info, - const bool fill_caps, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - MutableSpan<MLoop> r_loops, - MutableSpan<MPoly> r_polys) +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<MEdge> edges, + MutableSpan<MLoop> loops, + MutableSpan<MPoly> polys) { - const Spline &spline = info.spline; - const Spline &profile = info.profile; - if (info.profile_vert_len == 1) { - vert_extrude_to_mesh_data(spline, - profile.evaluated_positions()[0], - r_verts, - r_edges, - info.vert_offset, - info.edge_offset); + const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic); + const int profile_segment_num = curves::curve_segment_size(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 spline_edges_start = info.edge_offset; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; + 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 = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; + 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 = r_edges[profile_edge_offset + i_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; @@ -112,16 +76,15 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } /* Add the edges running along each profile ring. */ - const int profile_edges_start = spline_edges_start + - info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_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 * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; + 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 = r_edges[ring_edge_offset + i_profile]; + 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; @@ -129,368 +92,410 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } /* Calculate poly and corner indices. */ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; + 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 = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; + 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 + info.profile_edge_len * i_ring; - const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * 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 = info.poly_offset + i_ring * info.profile_edge_len; - const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; + 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(info.profile_edge_len)) { + 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 == info.profile_vert_len - 1) ? 0 : i_profile + 1; + const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1; - const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; - const int next_spline_edge_start = spline_edges_start + - info.spline_edge_len * i_next_profile; + 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 = r_polys[ring_poly_offset + i_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 = r_loops[ring_segment_loop_offset]; + 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 = r_loops[ring_segment_loop_offset + 1]; + MLoop &loop_b = loops[ring_segment_loop_offset + 1]; loop_b.v = ring_vert_offset + i_next_profile; - loop_b.e = next_spline_edge_start + i_ring; - MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; + 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 = r_loops[ring_segment_loop_offset + 3]; + MLoop &loop_d = loops[ring_segment_loop_offset + 3]; loop_d.v = next_ring_vert_offset + i_profile; - loop_d.e = spline_edge_start + i_ring; + loop_d.e = main_edge_start + i_ring; } } - const bool has_caps = fill_caps && profile.is_cyclic() && !spline.is_cyclic(); + const bool has_caps = fill_caps && !main_cyclic && profile_cyclic; if (has_caps) { - const int poly_size = info.spline_edge_len * info.profile_edge_len; - const int cap_loop_offset = info.loop_offset + poly_size * 4; - const int cap_poly_offset = info.poly_offset + poly_size; + const int poly_size = main_segment_num * profile_segment_num; + const int cap_loop_offset = loop_offset + poly_size * 4; + const int cap_poly_offset = poly_offset + poly_size; - MPoly &poly_start = r_polys[cap_poly_offset]; + MPoly &poly_start = polys[cap_poly_offset]; poly_start.loopstart = cap_loop_offset; - poly_start.totloop = info.profile_edge_len; - MPoly &poly_end = r_polys[cap_poly_offset + 1]; - poly_end.loopstart = cap_loop_offset + info.profile_edge_len; - poly_end.totloop = info.profile_edge_len; - - const int last_ring_index = info.spline_vert_len - 1; - const int last_ring_vert_offset = info.vert_offset + info.profile_vert_len * last_ring_index; - const int last_ring_edge_offset = profile_edges_start + - info.profile_edge_len * last_ring_index; - - for (const int i : IndexRange(info.profile_edge_len)) { - const int i_inv = info.profile_edge_len - i - 1; - MLoop &loop_start = r_loops[cap_loop_offset + i]; - loop_start.v = info.vert_offset + i_inv; - loop_start.e = profile_edges_start + ((i == (info.profile_edge_len - 1)) ? - (info.profile_edge_len - 1) : - (i_inv - 1)); - MLoop &loop_end = r_loops[cap_loop_offset + info.profile_edge_len + i]; + 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(r_edges.slice(profile_edges_start, info.profile_edge_len)); - mark_edges_sharp(r_edges.slice(last_ring_edge_offset, info.profile_edge_len)); + mark_edges_sharp(edges.slice(profile_edges_start, profile_segment_num)); + mark_edges_sharp(edges.slice(last_ring_edge_offset, profile_segment_num)); } +} - /* Calculate the positions of each profile ring profile along the spline. */ - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile.evaluated_positions(); - - VArray<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i_ring : IndexRange(info.spline_vert_len)) { - float4x4 point_matrix = float4x4::from_normalized_axis_data( - positions[i_ring], normals[i_ring], tangents[i_ring]); - point_matrix.apply_scale(radii[i_ring]); - - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - MVert &vert = r_verts[ring_vert_start + i_profile]; - copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); - } +static void mark_bezier_vector_edges_sharp(const int profile_point_num, + const int main_segment_num, + const Span<int> control_point_offsets, + const Span<int8_t> handle_types_left, + const Span<int8_t> handle_types_right, + MutableSpan<MEdge> 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)); } - /* Mark edge loops from sharp vector control points sharp. */ - if (profile.type() == CURVE_TYPE_BEZIER) { - const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); - Span<int> control_point_offsets = bezier_spline.control_point_offsets(); - for (const int i : IndexRange(bezier_spline.size())) { - if (bezier_spline.point_is_sharp(i)) { - mark_edges_sharp( - r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], - info.spline_edge_len)); - } + 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 inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) +static void fill_mesh_positions(const int main_point_num, + const int profile_point_num, + const Span<float3> main_positions, + const Span<float3> profile_positions, + const Span<float3> tangents, + const Span<float3> normals, + const Span<float> radii, + MutableSpan<MVert> mesh_positions) { - return curve.evaluated_points_size() * profile.evaluated_points_size(); -} + 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]); + } -static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) -{ - /* 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. */ - return curve.evaluated_points_size() * profile.evaluated_edges_size() + - curve.evaluated_edges_size() * profile.evaluated_points_size(); -} + MVert &vert = mesh_positions[i_ring]; + copy_v3_v3(vert.co, 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]); + } -static inline int spline_extrude_loop_size(const Spline &curve, - const Spline &profile, - const bool fill_caps) -{ - const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; - const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); - const int caps = has_caps ? profile.evaluated_edges_size() * 2 : 0; - return tube + caps; + const int ring_vert_start = i_ring * profile_point_num; + for (const int i_profile : IndexRange(profile_point_num)) { + MVert &vert = mesh_positions[ring_vert_start + i_profile]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + } } -static inline int spline_extrude_poly_size(const Spline &curve, - const Spline &profile, - const bool fill_caps) +struct CurvesInfo { + const CurvesGeometry &main; + const CurvesGeometry &profile; + + /* Make sure these are spans because they are potentially accessed many times. */ + VArray_Span<bool> main_cyclic; + VArray_Span<bool> profile_cyclic; + + /* TODO: Remove once these are cached on #CurvesGeometry. */ + std::array<int, CURVE_TYPES_NUM> main_type_counts; + std::array<int, CURVE_TYPES_NUM> profile_type_counts; +}; +static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile) { - const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size(); - const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic(); - const int caps = has_caps ? 2 : 0; - return tube + caps; + return {main, + profile, + main.cyclic(), + profile.cyclic(), + main.count_curve_types(), + profile.count_curve_types()}; } struct ResultOffsets { + /** The total number of curve combinations. */ + int total; + + /** Offsets into the result mesh for each combination. */ Array<int> vert; Array<int> edge; Array<int> loop; Array<int> poly; + + /* The indices of the main and profile curves that form each combination. */ + Array<int> main_indices; + Array<int> profile_indices; }; -static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, - Span<SplinePtr> curves, - const bool fill_caps) +static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps) { - const int total = profiles.size() * curves.size(); - Array<int> vert(total + 1); - Array<int> edge(total + 1); - Array<int> loop(total + 1); - Array<int> poly(total + 1); + 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_spline : curves.index_range()) { - for (const int i_profile : profiles.index_range()) { - vert[mesh_index] = vert_offset; - edge[mesh_index] = edge_offset; - loop[mesh_index] = loop_offset; - poly[mesh_index] = poly_offset; - vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); - edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); - loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile], fill_caps); - poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile], fill_caps); + 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::curve_segment_size(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::curve_segment_size(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++; } } - vert.last() = vert_offset; - edge.last() = edge_offset; - loop.last() = loop_offset; - poly.last() = poly_offset; - return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; + result.vert.last() = vert_offset; + result.edge.last() = edge_offset; + result.loop.last() = loop_offset; + result.poly.last() = poly_offset; + + return result; } -static AttributeDomain get_result_attribute_domain(const MeshComponent &component, - const AttributeIDRef &attribute_id) +static AttributeDomain get_attribute_domain_for_mesh(const MeshComponent &mesh, + const AttributeIDRef &attribute_id) { /* Only use a different domain if it is builtin and must only exist on one domain. */ - if (!component.attribute_is_builtin(attribute_id)) { + if (!mesh.attribute_is_builtin(attribute_id)) { return ATTR_DOMAIN_POINT; } - std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); + std::optional<AttributeMetaData> meta_data = mesh.attribute_get_meta_data(attribute_id); if (!meta_data) { - /* This function has to return something in this case, but it shouldn't be used, - * so return an output that will assert later if the code attempts to handle it. */ - return ATTR_DOMAIN_AUTO; + return ATTR_DOMAIN_POINT; } return meta_data->domain; } -/** - * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling - * `as_span()` for every single profile and curve spline combination, and for readability. - */ -struct ResultAttributeData { - GMutableSpan data; - AttributeDomain domain; -}; - -static std::optional<ResultAttributeData> create_attribute_and_get_span( - MeshComponent &component, - const AttributeIDRef &attribute_id, - AttributeMetaData meta_data, - Vector<OutputAttribute> &r_attributes) +static bool should_add_attribute_to_mesh(const CurveComponent &curve_component, + const MeshComponent &mesh_component, + const AttributeIDRef &id) { - const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); - OutputAttribute attribute = component.attribute_try_get_for_output_only( - attribute_id, domain, meta_data.data_type); - if (!attribute) { - return std::nullopt; + /* 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_component.attribute_is_builtin(id) && !mesh_component.attribute_is_builtin(id)) { + return false; } + if (!id.should_be_kept()) { + return false; + } + return true; +} - GMutableSpan span = attribute.as_span(); - r_attributes.append(std::move(attribute)); - return std::make_optional<ResultAttributeData>({span, domain}); +static GSpan evaluated_attribute_if_necessary(const GVArray &src, + const CurvesGeometry &curves, + const Span<int> type_counts, + Vector<std::byte> &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; } -/** - * Store the references to the attribute data from the curve and profile inputs. Here we rely on - * the invariants of the storage of curve attributes, that the order will be consistent between - * splines, and all splines will have the same attributes. - */ -struct ResultAttributes { - /** - * Result attributes on the mesh corresponding to each attribute on the curve input, in the same - * order. The data is optional only in case the attribute does not exist on the mesh for some - * reason, like "shade_smooth" when the result has no faces. - */ - Vector<std::optional<ResultAttributeData>> curve_point_attributes; - Vector<std::optional<ResultAttributeData>> curve_spline_attributes; - - /** - * Result attributes corresponding the attributes on the profile input, in the same order. The - * attributes are optional in case the attribute names correspond to a names used by the curve - * input, in which case the curve input attributes take precedence. - */ - Vector<std::optional<ResultAttributeData>> profile_point_attributes; - Vector<std::optional<ResultAttributeData>> profile_spline_attributes; - - /** - * Because some builtin attributes are not stored contiguously, and the curve inputs might have - * attributes with those names, it's necessary to keep OutputAttributes around to give access to - * the result data in a contiguous array. - */ - Vector<OutputAttribute> attributes; +/** 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; }; -static ResultAttributes create_result_attributes(const CurveEval &curve, - const CurveEval &profile, - MeshComponent &mesh_component) +template<typename Fn> +static void foreach_curve_combination(const CurvesInfo &info, + const ResultOffsets &offsets, + const Fn &fn) { - Set<AttributeIDRef> curve_attributes; - - /* In order to prefer attributes on the main curve input when there are name collisions, first - * check the attributes on the curve, then add attributes on the profile that are not also on the - * main curve input. */ - ResultAttributes result; - curve.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_POINT); - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_CURVE); - profile.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_point_attributes.append({}); - } - else { - result.profile_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_POINT); - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_spline_attributes.append({}); - } - else { - result.profile_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_CURVE); - - return result; + 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::curve_segment_size(main_points.size(), main_cyclic), + curves::curve_segment_size(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<typename T> -static void copy_curve_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) +static void copy_main_point_data_to_mesh_verts(const Span<T> src, + const int profile_point_num, + MutableSpan<T> dst) { - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); + 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<typename T> -static void copy_curve_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) +static void copy_main_point_data_to_mesh_edges(const Span<T> src, + const int profile_point_num, + const int main_segment_num, + const int profile_segment_num, + MutableSpan<T> dst) { - const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; - dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); + 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<typename T> -static void copy_curve_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) +static void copy_main_point_data_to_mesh_faces(const Span<T> src, + const int main_segment_num, + const int profile_segment_num, + MutableSpan<T> dst) { - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; - dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); + 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_curve_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) +static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, + const ResultOffsets &offsets, + const AttributeDomain dst_domain, + const GSpan src_all, + GMutableSpan dst_all) { - GVArray interpolated_gvarray = info.spline.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray.get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); - switch (dst.domain) { + const Span<T> src = src_all.typed<T>(); + MutableSpan<T> dst = dst_all.typed<T>(); + switch (dst_domain) { case ATTR_DOMAIN_POINT: - copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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: - copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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: - copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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. */ @@ -504,12 +509,12 @@ static void copy_curve_point_attribute_to_mesh(const GSpan src, template<typename T> static void copy_profile_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, + const int main_point_num, MutableSpan<T> dst) { - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { + 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]; } } @@ -517,46 +522,59 @@ static void copy_profile_point_data_to_mesh_verts(const Span<T> src, template<typename T> static void copy_profile_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, + const int main_segment_num, MutableSpan<T> dst) { - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; - dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); + 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<typename T> static void copy_profile_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, + const int main_segment_num, + const int profile_segment_num, MutableSpan<T> dst) { - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { + 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_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) +static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, + const ResultOffsets &offsets, + const AttributeDomain dst_domain, + const GSpan src_all, + GMutableSpan dst_all) { - GVArray interpolated_gvarray = info.profile.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray.get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) { using T = decltype(dummy); - switch (dst.domain) { + const Span<T> src = src_all.typed<T>(); + MutableSpan<T> dst = dst_all.typed<T>(); + switch (dst_domain) { case ATTR_DOMAIN_POINT: - copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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: - copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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: - copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + 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. */ @@ -568,198 +586,236 @@ static void copy_profile_point_attribute_to_mesh(const GSpan src, }); } -static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, - ResultAttributes &attributes) -{ - if (!attributes.curve_point_attributes.is_empty()) { - int i = 0; - info.spline.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_point_attributes[i]) { - copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), - info, - *attributes.curve_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } - if (!attributes.profile_point_attributes.is_empty()) { - int i = 0; - info.profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_point_attributes[i]) { - copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), - info, - *attributes.profile_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } -} - template<typename T> -static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) +static void copy_indices_to_offset_ranges(const VArray<T> &src, + const Span<int> curve_indices, + const Span<int> mesh_offsets, + MutableSpan<T> dst) { - for (const int i : IndexRange(src.size())) { - dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); - } + /* 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]]); + } + }); + }); } -/** - * Since the offsets for each combination of curve and profile spline are stored for every mesh - * domain, and this just needs to fill the chunks corresponding to each combination, we can use - * the same function for all mesh domains. - */ -static void copy_spline_attribute_to_mesh(const GSpan src, - const ResultOffsets &offsets, - ResultAttributeData &dst_attribute) +static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets, + const Span<int> curve_indices, + const AttributeDomain dst_domain, + const GVArray &src, + GMutableSpan dst) { + Span<int> 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); - switch (dst_attribute.domain) { - case ATTR_DOMAIN_POINT: - copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); - break; - default: - BLI_assert_unreachable(); - break; - } + copy_indices_to_offset_ranges(src.typed<T>(), curve_indices, offsets, dst.typed<T>()); }); } -static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, - const CurveEval &profile, - const ResultOffsets &offsets, - ResultAttributes &attributes) -{ - if (!attributes.curve_spline_attributes.is_empty()) { - int i = 0; - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), - offsets, - *attributes.curve_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } - if (!attributes.profile_spline_attributes.is_empty()) { - int i = 0; - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), - offsets, - *attributes.profile_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } -} - -Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, const bool fill_caps) +Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, + const CurvesGeometry &profile, + const bool fill_caps) { - Span<SplinePtr> profiles = profile.splines(); - Span<SplinePtr> curves = curve.splines(); + const CurvesInfo curves_info = get_curves_info(main, profile); - const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps); + 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()); - BKE_id_material_eval_ensure_default_slot(&mesh->id); mesh->flag |= ME_AUTOSMOOTH; mesh->smoothresh = DEG2RADF(180.0f); BKE_mesh_normals_tag_dirty(mesh); + MutableSpan<MVert> verts(mesh->mvert, mesh->totvert); + MutableSpan<MEdge> edges(mesh->medge, mesh->totedge); + MutableSpan<MLoop> loops(mesh->mloop, mesh->totloop); + MutableSpan<MPoly> polys(mesh->mpoly, mesh->totpoly); + + 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<float3> main_positions = main.evaluated_positions(); + const Span<float3> tangents = main.evaluated_tangents(); + const Span<float3> normals = main.evaluated_normals(); + const Span<float3> profile_positions = profile.evaluated_positions(); + + Vector<std::byte> eval_buffer; + + Curves main_id = {nullptr}; + main_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(main); + CurveComponent main_component; + main_component.replace(&main_id, GeometryOwnershipType::Editable); + + Curves profile_id = {nullptr}; + profile_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(profile); + CurveComponent profile_component; + profile_component.replace(&profile_id, GeometryOwnershipType::Editable); + + Span<float> radii = {}; + if (main_component.attribute_exists("radius")) { + radii = evaluated_attribute_if_necessary( + main_component.attribute_get_for_read<float>("radius", ATTR_DOMAIN_POINT, 1.0f), + main, + curves_info.main_type_counts, + eval_buffer) + .typed<float>(); + } + + 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), + verts.slice(info.vert_range)); + }); + + if (curves_info.profile_type_counts[CURVE_TYPE_BEZIER] > 0) { + const VArray<int8_t> curve_types = profile.curve_types(); + const VArray_Span<int8_t> handle_types_left{profile.handle_types_left()}; + const VArray_Span<int8_t> 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<AttributeIDRef> main_attributes; - /* Create the mesh component for retrieving attributes at this scope, since output attributes - * can keep a reference to the component for updating after retrieving write access. */ MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - ResultAttributes attributes = create_result_attributes(curve, profile, mesh_component); - threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { - for (const int i_spline : curves_range) { - const Spline &spline = *curves[i_spline]; - if (spline.evaluated_points_size() == 0) { - continue; - } - const int spline_start_index = i_spline * profiles.size(); - threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { - for (const int i_profile : profiles_range) { - const Spline &profile = *profiles[i_profile]; - const int i_mesh = spline_start_index + i_profile; - ResultInfo info{ - spline, - profile, - offsets.vert[i_mesh], - offsets.edge[i_mesh], - offsets.loop[i_mesh], - offsets.poly[i_mesh], - spline.evaluated_points_size(), - spline.evaluated_edges_size(), - profile.evaluated_points_size(), - profile.evaluated_edges_size(), - }; - - spline_extrude_to_mesh_data(info, - fill_caps, - {mesh->mvert, mesh->totvert}, - {mesh->medge, mesh->totedge}, - {mesh->mloop, mesh->totloop}, - {mesh->mpoly, mesh->totpoly}); - - copy_point_domain_attributes_to_mesh(info, attributes); - } - }); + main_component.attribute_foreach([&](const AttributeIDRef &id, + const AttributeMetaData meta_data) { + if (!should_add_attribute_to_mesh(main_component, mesh_component, id)) { + return true; } + main_attributes.add_new(id); + + const AttributeDomain src_domain = meta_data.domain; + const CustomDataType type = meta_data.data_type; + GVArray src = main_component.attribute_try_get_for_read(id, src_domain, type); + + const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(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, curves_info.main_type_counts, eval_buffer), + dst.as_span()); + } + else if (src_domain == ATTR_DOMAIN_CURVE) { + copy_curve_domain_attribute_to_mesh( + offsets, offsets.main_indices, dst_domain, src, dst.as_span()); + } + + dst.save(); + return true; }); - copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); + profile_component.attribute_foreach([&](const AttributeIDRef &id, + const AttributeMetaData meta_data) { + if (main_attributes.contains(id)) { + return true; + } + if (!should_add_attribute_to_mesh(profile_component, mesh_component, id)) { + return true; + } + const AttributeDomain src_domain = meta_data.domain; + const CustomDataType type = meta_data.data_type; + GVArray src = profile_component.attribute_try_get_for_read(id, src_domain, type); + + const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(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, curves_info.profile_type_counts, eval_buffer), + dst.as_span()); + } + else if (src_domain == ATTR_DOMAIN_CURVE) { + copy_curve_domain_attribute_to_mesh( + offsets, offsets.profile_indices, dst_domain, src, dst.as_span()); + } - for (OutputAttribute &output_attribute : attributes.attributes) { - output_attribute.save(); - } + dst.save(); + return true; + }); return mesh; } -static CurveEval get_curve_single_vert() +static CurvesGeometry get_curve_single_vert() { - CurveEval curve; - std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - spline->resize(1.0f); - spline->positions().fill(float3(0)); - spline->radii().fill(1.0f); - spline->tilts().fill(0.0f); - curve.add_spline(std::move(spline)); - - return curve; + CurvesGeometry curves(1, 1); + curves.offsets_for_write().last() = 1; + curves.positions_for_write().fill(float3(0)); + + return curves; } -Mesh *curve_to_wire_mesh(const CurveEval &curve) +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve) { - static const CurveEval vert_curve = get_curve_single_vert(); + static const CurvesGeometry vert_curve = get_curve_single_vert(); return curve_to_mesh_sweep(curve, vert_curve, false); } diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 24e156c3c4d..8e97884516c 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -840,6 +840,48 @@ void CurvesGeometry::interpolate_to_evaluated(const int curve_index, BLI_assert_unreachable(); } +void CurvesGeometry::interpolate_to_evaluated(const GSpan src, GMutableSpan dst) const +{ + BLI_assert(!this->runtime->offsets_cache_dirty); + BLI_assert(!this->runtime->nurbs_basis_cache_dirty); + const VArray<int8_t> types = this->curve_types(); + const VArray<int> resolution = this->resolution(); + const VArray<bool> cyclic = this->cyclic(); + const VArray<int8_t> nurbs_orders = this->nurbs_orders(); + const Span<float> nurbs_weights = this->nurbs_weights(); + + threading::parallel_for(this->curves_range(), 512, [&](IndexRange curves_range) { + for (const int curve_index : curves_range) { + const IndexRange points = this->points_for_curve(curve_index); + const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); + switch (types[curve_index]) { + case CURVE_TYPE_CATMULL_ROM: + curves::catmull_rom::interpolate_to_evaluated(src.slice(points), + cyclic[curve_index], + resolution[curve_index], + dst.slice(evaluated_points)); + continue; + case CURVE_TYPE_POLY: + dst.slice(evaluated_points).copy_from(src.slice(points)); + continue; + case CURVE_TYPE_BEZIER: + curves::bezier::interpolate_to_evaluated( + src.slice(points), + this->runtime->bezier_evaluated_offsets.as_span().slice(points), + dst.slice(evaluated_points)); + continue; + case CURVE_TYPE_NURBS: + curves::nurbs::interpolate_to_evaluated(this->runtime->nurbs_basis_cache[curve_index], + nurbs_orders[curve_index], + nurbs_weights.slice(points), + src.slice(points), + dst.slice(evaluated_points)); + continue; + } + } + }); +} + void CurvesGeometry::ensure_evaluated_lengths() const { if (!this->runtime->length_cache_dirty) { diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index defca433968..ff953ef5b46 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -25,6 +25,7 @@ #include "BLI_utildefines.h" #include "BKE_DerivedMesh.h" +#include "BKE_curves.hh" #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_editmesh.h" @@ -970,8 +971,7 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o } const Curves *curves = get_evaluated_curves_from_object(evaluated_object); if (curves) { - std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*curves); - return blender::bke::curve_to_wire_mesh(*curve); + return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry)); } return nullptr; } diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index da9ab9c313e..acf47f67168 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -387,6 +387,16 @@ class Vector { } /** + * Reset the size of the vector so that it contains new_size elements. + * All existing elements are destructed, and not copied if the data must be reallocated. + */ + void reinitialize(const int64_t new_size) + { + this->clear(); + this->resize(new_size); + } + + /** * Afterwards the vector has 0 elements, but will still have * memory to be refilled again. */ 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 e7a8c61290b..903a5e7c1d7 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 @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_spline.hh" +#include "BKE_curves.hh" #include "BKE_curve_to_mesh.hh" @@ -27,17 +27,18 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set, const bool fill_caps) { - const std::unique_ptr<CurveEval> curve = curves_to_curve_eval( - *geometry_set.get_curves_for_read()); + const Curves &curves = *geometry_set.get_curves_for_read(); + const Curves *profile_curves = profile_set.get_curves_for_read(); if (profile_curves == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(*curve); + Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry)); geometry_set.replace_mesh(mesh); } else { - const std::unique_ptr<CurveEval> profile_curve = curves_to_curve_eval(*profile_curves); - Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps); + Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry), + bke::CurvesGeometry::wrap(profile_curves->geometry), + fill_caps); geometry_set.replace_mesh(mesh); } } |