Blender V4.3
curve_to_mesh_convert.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
6#include "BLI_array_utils.hh"
7#include "BLI_math_matrix.hh"
8#include "BLI_math_rotation.h"
9#include "BLI_set.hh"
10#include "BLI_task.hh"
11
12#include "BKE_attribute_math.hh"
13#include "BKE_curves.hh"
14#include "BKE_customdata.hh"
15#include "BKE_geometry_set.hh"
16#include "BKE_material.h"
17#include "BKE_mesh.hh"
18
19#include "BKE_curve_to_mesh.hh"
20
21namespace blender::bke {
22
23static int segments_num_no_duplicate_edge(const int points_num, const bool cyclic)
24{
25 if (points_num <= 2) {
26 return curves::segments_num(points_num, false);
27 }
28 return curves::segments_num(points_num, cyclic);
29}
30
31static void fill_mesh_topology(const int vert_offset,
32 const int edge_offset,
33 const int face_offset,
34 const int loop_offset,
35 const int main_point_num,
36 const int profile_point_num,
37 const bool main_cyclic,
38 const bool profile_cyclic,
39 const bool fill_caps,
41 MutableSpan<int> corner_verts,
42 MutableSpan<int> corner_edges,
43 MutableSpan<int> face_offsets)
44{
45 const int main_segment_num = segments_num_no_duplicate_edge(main_point_num, main_cyclic);
46 const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic);
47
48 if (profile_point_num == 1) {
49 for (const int i : IndexRange(main_point_num - 1)) {
50 int2 &edge = edges[edge_offset + i];
51 edge[0] = vert_offset + i;
52 edge[1] = vert_offset + i + 1;
53 }
54
55 if (main_cyclic && main_segment_num > 2) {
56 int2 &edge = edges[edge_offset + main_segment_num - 1];
57 edge[0] = vert_offset + main_point_num - 1;
58 edge[1] = vert_offset;
59 }
60 return;
61 }
62
63 /* Add the edges running along the length of the curve, starting at each profile vertex. */
64 const int main_edges_start = edge_offset;
65 for (const int i_profile : IndexRange(profile_point_num)) {
66 const int profile_edge_offset = main_edges_start + i_profile * main_segment_num;
67 for (const int i_ring : IndexRange(main_segment_num)) {
68 const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
69
70 const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
71 const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
72
73 int2 &edge = edges[profile_edge_offset + i_ring];
74 edge[0] = ring_vert_offset + i_profile;
75 edge[1] = next_ring_vert_offset + i_profile;
76 }
77 }
78
79 /* Add the edges running along each profile ring. */
80 const int profile_edges_start = main_edges_start + profile_point_num * main_segment_num;
81 for (const int i_ring : IndexRange(main_point_num)) {
82 const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
83
84 const int ring_edge_offset = profile_edges_start + i_ring * profile_segment_num;
85 for (const int i_profile : IndexRange(profile_segment_num)) {
86 const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
87
88 int2 &edge = edges[ring_edge_offset + i_profile];
89 edge[0] = ring_vert_offset + i_profile;
90 edge[1] = ring_vert_offset + i_next_profile;
91 }
92 }
93
94 /* Calculate face and corner indices. */
95 for (const int i_ring : IndexRange(main_segment_num)) {
96 const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
97
98 const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
99 const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
100
101 const int ring_edge_start = profile_edges_start + profile_segment_num * i_ring;
102 const int next_ring_edge_offset = profile_edges_start + profile_segment_num * i_next_ring;
103
104 const int ring_face_offset = face_offset + i_ring * profile_segment_num;
105 const int ring_loop_offset = loop_offset + i_ring * profile_segment_num * 4;
106
107 for (const int i_profile : IndexRange(profile_segment_num)) {
108 const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
109 const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
110
111 const int main_edge_start = main_edges_start + main_segment_num * i_profile;
112 const int next_main_edge_start = main_edges_start + main_segment_num * i_next_profile;
113
114 face_offsets[ring_face_offset + i_profile] = ring_segment_loop_offset;
115
116 corner_verts[ring_segment_loop_offset] = ring_vert_offset + i_profile;
117 corner_edges[ring_segment_loop_offset] = ring_edge_start + i_profile;
118
119 corner_verts[ring_segment_loop_offset + 1] = ring_vert_offset + i_next_profile;
120 corner_edges[ring_segment_loop_offset + 1] = next_main_edge_start + i_ring;
121
122 corner_verts[ring_segment_loop_offset + 2] = next_ring_vert_offset + i_next_profile;
123 corner_edges[ring_segment_loop_offset + 2] = next_ring_edge_offset + i_profile;
124
125 corner_verts[ring_segment_loop_offset + 3] = next_ring_vert_offset + i_profile;
126 corner_edges[ring_segment_loop_offset + 3] = main_edge_start + i_ring;
127 }
128 }
129
130 const bool has_caps = fill_caps && !main_cyclic && profile_cyclic && profile_point_num > 2;
131 if (has_caps) {
132 const int face_num = main_segment_num * profile_segment_num;
133 const int cap_loop_offset = loop_offset + face_num * 4;
134 const int cap_face_offset = face_offset + face_num;
135
136 face_offsets[cap_face_offset] = cap_loop_offset;
137 face_offsets[cap_face_offset + 1] = cap_loop_offset + profile_segment_num;
138
139 const int last_ring_index = main_point_num - 1;
140 const int last_ring_vert_offset = vert_offset + profile_point_num * last_ring_index;
141 const int last_ring_edge_offset = profile_edges_start + profile_segment_num * last_ring_index;
142
143 for (const int i : IndexRange(profile_segment_num)) {
144 const int i_inv = profile_segment_num - i - 1;
145 corner_verts[cap_loop_offset + i] = vert_offset + i_inv;
146 corner_edges[cap_loop_offset + i] = profile_edges_start + ((i == (profile_segment_num - 1)) ?
147 (profile_segment_num - 1) :
148 (i_inv - 1));
149 corner_verts[cap_loop_offset + profile_segment_num + i] = last_ring_vert_offset + i;
150 corner_edges[cap_loop_offset + profile_segment_num + i] = last_ring_edge_offset + i;
151 }
152 }
153}
154
156static void mark_bezier_vector_edges_sharp(const int profile_point_num,
157 const int main_segment_num,
158 const Span<int> control_point_offsets,
159 const Span<int8_t> handle_types_left,
160 const Span<int8_t> handle_types_right,
161 MutableSpan<bool> sharp_edges)
162{
163 const int main_edges_start = 0;
164 if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) {
165 sharp_edges.slice(main_edges_start, main_segment_num).fill(true);
166 }
167
168 for (const int i : IndexRange(profile_point_num).drop_front(1)) {
169 if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) {
170 const int offset = main_edges_start + main_segment_num * control_point_offsets[i];
171 sharp_edges.slice(offset, main_segment_num).fill(true);
172 }
173 }
174}
175
176static float4x4 build_point_matrix(const float3 &location,
177 const float3 &tangent,
178 const float3 &normal)
179{
180 float4x4 matrix = float4x4::identity();
181 matrix.x_axis() = tangent;
182 /* Normal and tangent may not be orthogonal in case of custom normals. */
183 matrix.y_axis() = math::normalize(math::cross(normal, tangent));
184 matrix.z_axis() = normal;
185 matrix.location() = location;
186 return matrix;
187}
188
189static void fill_mesh_positions(const int main_point_num,
190 const int profile_point_num,
191 const Span<float3> main_positions,
192 const Span<float3> profile_positions,
193 const Span<float3> tangents,
194 const Span<float3> normals,
195 const Span<float> radii,
196 MutableSpan<float3> mesh_positions)
197{
198 if (profile_point_num == 1) {
199 for (const int i_ring : IndexRange(main_point_num)) {
200 float4x4 point_matrix = build_point_matrix(
201 main_positions[i_ring], normals[i_ring], tangents[i_ring]);
202 if (!radii.is_empty()) {
203 point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
204 }
205 mesh_positions[i_ring] = math::transform_point(point_matrix, profile_positions.first());
206 }
207 }
208 else {
209 for (const int i_ring : IndexRange(main_point_num)) {
210 float4x4 point_matrix = build_point_matrix(
211 main_positions[i_ring], normals[i_ring], tangents[i_ring]);
212 if (!radii.is_empty()) {
213 point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
214 }
215
216 const int ring_vert_start = i_ring * profile_point_num;
217 for (const int i_profile : IndexRange(profile_point_num)) {
218 mesh_positions[ring_vert_start + i_profile] = math::transform_point(
219 point_matrix, profile_positions[i_profile]);
220 }
221 }
222 }
223}
224
228
229 /* Make sure these are spans because they are potentially accessed many times. */
232};
234{
235 return {main, profile, main.cyclic(), profile.cyclic()};
236}
237
239{
240 for (const int64_t i : offsets.index_range()) {
241 if (offsets[i].size() == 1) {
242 return true;
243 }
244 }
245 return false;
246}
247
250 int total;
251
257
258 /* The indices of the main and profile curves that form each combination. */
261
265};
266static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
267{
269 result.total = info.main.curves_num() * info.profile.curves_num();
270
271 const OffsetIndices<int> main_offsets = info.main.evaluated_points_by_curve();
272 const OffsetIndices<int> profile_offsets = info.profile.evaluated_points_by_curve();
273
275 result.total > 1024,
276 [&]() {
277 result.vert.reinitialize(result.total + 1);
278 result.edge.reinitialize(result.total + 1);
279 result.loop.reinitialize(result.total + 1);
280 result.face.reinitialize(result.total + 1);
281
282 int mesh_index = 0;
283 int vert_offset = 0;
284 int edge_offset = 0;
285 int loop_offset = 0;
286 int face_offset = 0;
287 for (const int i_main : main_offsets.index_range()) {
288 const bool main_cyclic = info.main_cyclic[i_main];
289 const int main_point_num = main_offsets[i_main].size();
290 const int main_segment_num = segments_num_no_duplicate_edge(main_point_num, main_cyclic);
291 for (const int i_profile : profile_offsets.index_range()) {
292 result.vert[mesh_index] = vert_offset;
293 result.edge[mesh_index] = edge_offset;
294 result.loop[mesh_index] = loop_offset;
295 result.face[mesh_index] = face_offset;
296
297 const bool profile_cyclic = info.profile_cyclic[i_profile];
298 const int profile_point_num = profile_offsets[i_profile].size();
299 const int profile_segment_num = curves::segments_num(profile_point_num,
300 profile_cyclic);
301
302 const bool has_caps = fill_caps && !main_cyclic && profile_cyclic &&
303 profile_point_num > 2;
304 const int tube_face_num = main_segment_num * profile_segment_num;
305
306 vert_offset += main_point_num * profile_point_num;
307
308 /* Add the ring edges, with one ring for every curve vertex, and the edge loops
309 * that run along the length of the curve, starting on the first profile. */
310 edge_offset += main_point_num * profile_segment_num +
311 main_segment_num * profile_point_num;
312
313 /* Add two cap N-gons for every ending. */
314 face_offset += tube_face_num + (has_caps ? 2 : 0);
315
316 /* All faces on the tube are quads, and all cap faces are N-gons with an edge for each
317 * profile edge. */
318 loop_offset += tube_face_num * 4 + (has_caps ? profile_segment_num * 2 : 0);
319
320 mesh_index++;
321 }
322 }
323
324 result.vert.last() = vert_offset;
325 result.edge.last() = edge_offset;
326 result.loop.last() = loop_offset;
327 result.face.last() = face_offset;
328 },
329 [&]() {
330 result.main_indices.reinitialize(result.total);
331 result.profile_indices.reinitialize(result.total);
332
333 int mesh_index = 0;
334 for (const int i_main : main_offsets.index_range()) {
335 for (const int i_profile : profile_offsets.index_range()) {
336 result.main_indices[mesh_index] = i_main;
337 result.profile_indices[mesh_index] = i_profile;
338 mesh_index++;
339 }
340 }
341 },
342 [&]() { result.any_single_point_main = offsets_contain_single_point(main_offsets); },
343 [&]() { result.any_single_point_profile = offsets_contain_single_point(profile_offsets); });
344
345 return result;
346}
347
349 const StringRef attribute_id)
350{
351 /* Only use a different domain if it is builtin and must only exist on one domain. */
352 if (!mesh_attributes.is_builtin(attribute_id)) {
353 return AttrDomain::Point;
354 }
355
356 std::optional<AttributeMetaData> meta_data = mesh_attributes.lookup_meta_data(attribute_id);
357 if (!meta_data) {
358 return AttrDomain::Point;
359 }
360
361 return meta_data->domain;
362}
363
364static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes,
365 const AttributeAccessor &mesh_attributes,
366 const StringRef id,
367 const AttributeMetaData &meta_data,
368 const AttributeFilter &attribute_filter)
369{
370
371 /* The position attribute has special non-generic evaluation. */
372 if (id == "position") {
373 return false;
374 }
375 /* Don't propagate built-in curves attributes that are not built-in on meshes. */
376 if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) {
377 return false;
378 }
379 if (attribute_filter.allow_skip(id)) {
380 return false;
381 }
382 if (meta_data.data_type == CD_PROP_STRING) {
383 return false;
384 }
385 return true;
386}
387
389 const CurvesGeometry &curves,
390 Vector<std::byte> &buffer)
391{
392 /* Poly curves evaluated points match the curve points, no need to interpolate. */
393 if (curves.is_single_type(CURVE_TYPE_POLY)) {
394 if (src.is_span()) {
395 return src.get_internal_span();
396 }
397 buffer.reinitialize(curves.points_num() * src.type().size());
398 src.materialize(buffer.data());
399 GMutableSpan eval{src.type(), buffer.data(), curves.points_num()};
400 return eval;
401 }
402
403 if (src.is_span()) {
404 buffer.reinitialize(curves.evaluated_points_num() * src.type().size());
405 GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()};
406 curves.interpolate_to_evaluated(src.get_internal_span(), eval);
407 return eval;
408 }
409 GVArraySpan src_buffer(src);
410 buffer.reinitialize(curves.evaluated_points_num() * src.type().size());
411 GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()};
412 curves.interpolate_to_evaluated(src_buffer, eval);
413 return eval;
414}
415
435template<typename Fn>
436static void foreach_curve_combination(const CurvesInfo &info,
437 const ResultOffsets &offsets,
438 const Fn &fn)
439{
440 const OffsetIndices<int> main_offsets = info.main.evaluated_points_by_curve();
441 const OffsetIndices<int> profile_offsets = info.profile.evaluated_points_by_curve();
442 const OffsetIndices<int> vert_offsets(offsets.vert);
443 const OffsetIndices<int> edge_offsets(offsets.edge);
444 const OffsetIndices<int> face_offsets(offsets.face);
445 const OffsetIndices<int> loop_offsets(offsets.loop);
446 threading::parallel_for(IndexRange(offsets.total), 512, [&](IndexRange range) {
447 for (const int i : range) {
448 const int i_main = offsets.main_indices[i];
449 const int i_profile = offsets.profile_indices[i];
450
451 const IndexRange main_points = main_offsets[i_main];
452 const IndexRange profile_points = profile_offsets[i_profile];
453
454 const bool main_cyclic = info.main_cyclic[i_main];
455 const bool profile_cyclic = info.profile_cyclic[i_profile];
456
457 /* Pass all information in a struct to avoid repeating arguments in many lambdas.
458 * The idea is that inlining `fn` will help avoid accessing unnecessary information,
459 * though that may or may not happen in practice. */
460 fn(CombinationInfo{i_main,
461 i_profile,
462 main_points,
463 profile_points,
464 main_cyclic,
465 profile_cyclic,
466 curves::segments_num(main_points.size(), main_cyclic),
467 curves::segments_num(profile_points.size(), profile_cyclic),
468 vert_offsets[i],
469 edge_offsets[i],
470 face_offsets[i],
471 loop_offsets[i]});
472 }
473 });
474}
475
476static void build_mesh_positions(const CurvesInfo &curves_info,
477 const ResultOffsets &offsets,
478 Vector<std::byte> &eval_buffer,
479 Mesh &mesh)
480{
481 BLI_assert(!mesh.attributes().contains("position"));
482 const Span<float3> profile_positions = curves_info.profile.evaluated_positions();
483 const bool ignore_profile_position = profile_positions.size() == 1 &&
484 math::is_equal(profile_positions.first(), float3(0.0f));
485 if (ignore_profile_position) {
486 if (mesh.verts_num == curves_info.main.points_num()) {
487 const GAttributeReader src = curves_info.main.attributes().lookup("position");
488 if (src.sharing_info && src.varray.is_span()) {
490 if (mesh.attributes_for_write().add<float3>("position", AttrDomain::Point, init)) {
491 return;
492 }
493 }
494 }
495 }
496 const Span<float3> main_positions = curves_info.main.evaluated_positions();
497 mesh.attributes_for_write().add<float3>("position", AttrDomain::Point, AttributeInitConstruct());
498 MutableSpan<float3> positions = mesh.vert_positions_for_write();
499 if (ignore_profile_position) {
500 array_utils::copy(main_positions, positions);
501 return;
502 }
503 const Span<float3> tangents = curves_info.main.evaluated_tangents();
504 const Span<float3> normals = curves_info.main.evaluated_normals();
505 Span<float> radii_eval;
506 if (const GVArray radii = *curves_info.main.attributes().lookup("radius", AttrDomain::Point)) {
507 radii_eval = evaluate_attribute(radii, curves_info.main, eval_buffer).typed<float>();
508 }
509 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
511 info.profile_points.size(),
512 main_positions.slice(info.main_points),
513 profile_positions.slice(info.profile_points),
514 tangents.slice(info.main_points),
515 normals.slice(info.main_points),
516 radii_eval.is_empty() ? radii_eval : radii_eval.slice(info.main_points),
517 positions.slice(info.vert_range));
518 });
519}
520
521template<typename T>
523 const int profile_point_num,
524 MutableSpan<T> dst)
525{
526 for (const int i_ring : src.index_range()) {
527 const int ring_vert_start = i_ring * profile_point_num;
528 dst.slice(ring_vert_start, profile_point_num).fill(src[i_ring]);
529 }
530}
531
532template<typename T>
534 const int profile_point_num,
535 const int main_segment_num,
536 const int profile_segment_num,
537 MutableSpan<T> dst)
538{
539 const int edges_start = profile_point_num * main_segment_num;
540 for (const int i_ring : src.index_range()) {
541 const int ring_edge_start = edges_start + profile_segment_num * i_ring;
542 dst.slice(ring_edge_start, profile_segment_num).fill(src[i_ring]);
543 }
544}
545
546template<typename T>
548 const int main_segment_num,
549 const int profile_segment_num,
550 MutableSpan<T> dst)
551{
552 for (const int i_ring : IndexRange(main_segment_num)) {
553 const int ring_face_start = profile_segment_num * i_ring;
554 dst.slice(ring_face_start, profile_segment_num).fill(src[i_ring]);
555 }
556}
557
559 const StringRef id,
560 const GAttributeReader &src,
561 MutableAttributeAccessor mesh_attributes)
562{
563 if (mesh_attributes.domain_size(AttrDomain::Point) != main.points_num()) {
564 return false;
565 }
566 if (!src.sharing_info || !src.varray.is_span()) {
567 return false;
568 }
569 return mesh_attributes.add(
570 id,
571 AttrDomain::Point,
572 bke::cpp_type_to_custom_data_type(src.varray.type()),
574}
575
577 const GAttributeReader &src,
578 GMutableSpan dst)
579{
580 if (dst.size() != main.evaluated_points_num()) {
581 return false;
582 }
583 if (!src.varray.is_span()) {
584 return false;
585 }
586 main.interpolate_to_evaluated(src.varray.get_internal_span(), dst);
587 return true;
588}
589
591 const StringRef id,
592 const ResultOffsets &offsets,
593 const AttrDomain dst_domain,
594 const GAttributeReader &src_attribute,
595 Vector<std::byte> &eval_buffer,
596 MutableAttributeAccessor mesh_attributes)
597{
598 if (dst_domain == AttrDomain::Point) {
599 if (try_sharing_point_data(curves_info.main, id, src_attribute, mesh_attributes)) {
600 return;
601 }
602 }
603 GSpanAttributeWriter dst_attribute = mesh_attributes.lookup_or_add_for_write_only_span(
604 id, dst_domain, bke::cpp_type_to_custom_data_type(src_attribute.varray.type()));
605 if (!dst_attribute) {
606 return;
607 }
608 if (dst_domain == AttrDomain::Point) {
609 if (try_direct_evaluate_point_data(curves_info.main, src_attribute, dst_attribute.span)) {
610 dst_attribute.finish();
611 return;
612 }
613 }
614 const GSpan src_all = evaluate_attribute(*src_attribute, curves_info.main, eval_buffer);
615 attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) {
616 using T = decltype(dummy);
617 const Span<T> src = src_all.typed<T>();
618 MutableSpan<T> dst = dst_attribute.span.typed<T>();
619 switch (dst_domain) {
620 case AttrDomain::Point:
621 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
622 copy_main_point_data_to_mesh_verts(
623 src.slice(info.main_points), info.profile_points.size(), dst.slice(info.vert_range));
624 });
625 break;
626 case AttrDomain::Edge:
627 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
628 copy_main_point_data_to_mesh_edges(src.slice(info.main_points),
629 info.profile_points.size(),
630 info.main_segment_num,
631 info.profile_segment_num,
632 dst.slice(info.edge_range));
633 });
634 break;
635 case AttrDomain::Face:
636 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
637 copy_main_point_data_to_mesh_faces(src.slice(info.main_points),
638 info.main_segment_num,
639 info.profile_segment_num,
640 dst.slice(info.face_range));
641 });
642 break;
643 case AttrDomain::Corner:
644 /* Unsupported for now, since there are no builtin attributes to convert into. */
645 break;
646 default:
647 BLI_assert_unreachable();
648 break;
649 }
650 });
651 dst_attribute.finish();
652}
653
654template<typename T>
656 const int main_point_num,
657 MutableSpan<T> dst)
658{
659 for (const int i_ring : IndexRange(main_point_num)) {
660 const int profile_vert_start = i_ring * src.size();
661 for (const int i_profile : src.index_range()) {
662 dst[profile_vert_start + i_profile] = src[i_profile];
663 }
664 }
665}
666
667template<typename T>
669 const int main_segment_num,
670 MutableSpan<T> dst)
671{
672 for (const int i_profile : src.index_range()) {
673 const int profile_edge_offset = i_profile * main_segment_num;
674 dst.slice(profile_edge_offset, main_segment_num).fill(src[i_profile]);
675 }
676}
677
678template<typename T>
680 const int main_segment_num,
681 const int profile_segment_num,
682 MutableSpan<T> dst)
683{
684 for (const int i_ring : IndexRange(main_segment_num)) {
685 const int profile_face_start = i_ring * profile_segment_num;
686 for (const int i_profile : IndexRange(profile_segment_num)) {
687 dst[profile_face_start + i_profile] = src[i_profile];
688 }
689 }
690}
691
693 const ResultOffsets &offsets,
694 const AttrDomain dst_domain,
695 const GSpan src_all,
696 GMutableSpan dst_all)
697{
698 attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) {
699 using T = decltype(dummy);
700 const Span<T> src = src_all.typed<T>();
701 MutableSpan<T> dst = dst_all.typed<T>();
702 switch (dst_domain) {
703 case AttrDomain::Point:
704 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
705 copy_profile_point_data_to_mesh_verts(
706 src.slice(info.profile_points), info.main_points.size(), dst.slice(info.vert_range));
707 });
708 break;
709 case AttrDomain::Edge:
710 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
711 copy_profile_point_data_to_mesh_edges(
712 src.slice(info.profile_points), info.main_segment_num, dst.slice(info.edge_range));
713 });
714 break;
715 case AttrDomain::Face:
716 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
717 copy_profile_point_data_to_mesh_faces(src.slice(info.profile_points),
718 info.main_segment_num,
719 info.profile_segment_num,
720 dst.slice(info.face_range));
721 });
722 break;
723 case AttrDomain::Corner:
724 /* Unsupported for now, since there are no builtin attributes to convert into. */
725 break;
726 default:
727 BLI_assert_unreachable();
728 break;
729 }
730 });
731}
732
733template<typename T>
735 const Span<int> curve_indices,
736 const OffsetIndices<int> mesh_offsets,
737 MutableSpan<T> dst)
738{
739 /* This unnecessarily instantiates the "is single" case (which should be handled elsewhere if
740 * it's ever used for attributes), but the alternative is duplicating the function for spans and
741 * other virtual arrays. */
742 devirtualize_varray(src, [&](const auto src) {
743 threading::parallel_for(curve_indices.index_range(), 512, [&](IndexRange range) {
744 for (const int i : range) {
745 dst.slice(mesh_offsets[i]).fill(src[curve_indices[i]]);
746 }
747 });
748 });
749}
750
752 const Span<int> curve_indices,
753 const AttrDomain dst_domain,
754 const GVArray &src,
755 GMutableSpan dst)
756{
757 Span<int> offsets;
758 switch (dst_domain) {
759 case AttrDomain::Point:
760 offsets = mesh_offsets.vert;
761 break;
762 case AttrDomain::Edge:
763 offsets = mesh_offsets.edge;
764 break;
765 case AttrDomain::Face:
766 offsets = mesh_offsets.face;
767 break;
768 case AttrDomain::Corner:
769 offsets = mesh_offsets.loop;
770 break;
771 default:
773 return;
774 }
775 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
776 using T = decltype(dummy);
777 copy_indices_to_offset_ranges(src.typed<T>(), curve_indices, offsets, dst.typed<T>());
778 });
779}
780
781static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
782 const ResultOffsets &offsets,
783 MutableAttributeAccessor mesh_attributes,
784 SpanAttributeWriter<bool> &sharp_edges)
785{
786 const CurvesGeometry &profile = curves_info.profile;
787 if (!profile.has_curve_with_type(CURVE_TYPE_BEZIER)) {
788 return;
789 }
790 const VArraySpan<int8_t> handle_types_left{profile.handle_types_left()};
791 const VArraySpan<int8_t> handle_types_right{profile.handle_types_right()};
792 if (!handle_types_left.contains(BEZIER_HANDLE_VECTOR) &&
793 !handle_types_right.contains(BEZIER_HANDLE_VECTOR))
794 {
795 return;
796 }
797
798 sharp_edges = mesh_attributes.lookup_or_add_for_write_span<bool>("sharp_edge", AttrDomain::Edge);
799
800 const OffsetIndices profile_points_by_curve = profile.points_by_curve();
801 const VArray<int8_t> types = profile.curve_types();
802 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
803 if (types[info.i_profile] == CURVE_TYPE_BEZIER) {
804 const IndexRange points = profile_points_by_curve[info.i_profile];
805 mark_bezier_vector_edges_sharp(points.size(),
806 info.main_segment_num,
807 profile.bezier_evaluated_offsets_for_curve(info.i_profile),
808 handle_types_left.slice(points),
809 handle_types_right.slice(points),
810 sharp_edges.span.slice(info.edge_range));
811 }
812 });
813}
814
816 const CurvesGeometry &profile,
817 const bool fill_caps,
818 const AttributeFilter &attribute_filter)
819{
820 const CurvesInfo curves_info = get_curves_info(main, profile);
821
822 const ResultOffsets offsets = calculate_result_offsets(curves_info, fill_caps);
823 if (offsets.vert.last() == 0) {
824 return nullptr;
825 }
826
827 /* Add the position attribute later so it can be shared in some cases. */
829 0, offsets.edge.last(), offsets.face.last(), offsets.loop.last());
830 CustomData_free_layer_named(&mesh->vert_data, "position", 0);
831 mesh->verts_num = offsets.vert.last();
832
833 MutableSpan<int2> edges = mesh->edges_for_write();
834 MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
835 MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
836 MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
837 MutableAttributeAccessor mesh_attributes = mesh->attributes_for_write();
838
839 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
841 info.edge_range.start(),
842 info.face_range.start(),
843 info.loop_range.start(),
844 info.main_points.size(),
845 info.profile_points.size(),
846 info.main_cyclic,
847 info.profile_cyclic,
848 fill_caps,
849 edges,
850 corner_verts,
851 corner_edges,
852 face_offsets);
853 });
854
855 if (fill_caps) {
856 /* TODO: This is used to keep the tests passing after refactoring mesh shade smooth flags. It
857 * can be removed if the tests are updated and the final shading results will be the same. */
858 SpanAttributeWriter<bool> sharp_faces = mesh_attributes.lookup_or_add_for_write_span<bool>(
859 "sharp_face", AttrDomain::Face);
860 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
861 const bool has_caps = fill_caps && !info.main_cyclic && info.profile_cyclic;
862 if (has_caps) {
863 const int face_num = info.main_segment_num * info.profile_segment_num;
864 const int cap_face_offset = info.face_range.start() + face_num;
865 sharp_faces.span[cap_face_offset] = true;
866 sharp_faces.span[cap_face_offset + 1] = true;
867 }
868 });
869 sharp_faces.finish();
870 }
871
872 Vector<std::byte> eval_buffer;
873
874 /* Make sure curve attributes can be interpolated. */
875 main.ensure_can_interpolate_to_evaluated();
876
877 build_mesh_positions(curves_info, offsets, eval_buffer, *mesh);
878
879 mesh->tag_overlapping_none();
880 if (!offsets.any_single_point_main) {
881 /* If there are no single point curves, every combination will have at least loose edges. */
882 mesh->tag_loose_verts_none();
883 if (!offsets.any_single_point_profile) {
884 /* If there are no single point profiles, every combination will have faces. */
885 mesh->tag_loose_edges_none();
886 }
887 }
888
889 SpanAttributeWriter<bool> sharp_edges;
890 write_sharp_bezier_edges(curves_info, offsets, mesh_attributes, sharp_edges);
891 if (fill_caps) {
892 if (!sharp_edges) {
893 sharp_edges = mesh_attributes.lookup_or_add_for_write_span<bool>("sharp_edge",
894 AttrDomain::Edge);
895 }
896 foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
897 if (info.main_cyclic || !info.profile_cyclic) {
898 return;
899 }
900 const int main_edges_start = info.edge_range.start();
901 const int last_ring_index = info.main_points.size() - 1;
902 const int profile_edges_start = main_edges_start +
904 const int last_ring_edge_offset = profile_edges_start +
905 info.profile_segment_num * last_ring_index;
906
907 sharp_edges.span.slice(profile_edges_start, info.profile_segment_num).fill(true);
908 sharp_edges.span.slice(last_ring_edge_offset, info.profile_segment_num).fill(true);
909 });
910 }
911 sharp_edges.finish();
912
913 const AttributeAccessor main_attributes = main.attributes();
914 main_attributes.foreach_attribute([&](const AttributeIter &iter) {
915 if (!should_add_attribute_to_mesh(main_attributes,
916 mesh_attributes,
917 iter.name,
918 {iter.domain, iter.data_type},
919 attribute_filter))
920 {
921 return;
922 }
923
924 const AttrDomain src_domain = iter.domain;
925 const eCustomDataType type = iter.data_type;
926 const GAttributeReader src = iter.get();
927 const AttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, iter.name);
928
929 if (src_domain == AttrDomain::Point) {
931 curves_info, iter.name, offsets, dst_domain, src, eval_buffer, mesh_attributes);
932 }
933 else if (src_domain == AttrDomain::Curve) {
935 iter.name, dst_domain, type);
936 if (dst) {
938 offsets, offsets.main_indices, dst_domain, *src, dst.span);
939 }
940 dst.finish();
941 }
942 });
943
944 /* Make sure profile attributes can be interpolated. */
945 profile.ensure_can_interpolate_to_evaluated();
946
947 const AttributeAccessor profile_attributes = profile.attributes();
948 profile_attributes.foreach_attribute([&](const AttributeIter &iter) {
949 if (main_attributes.contains(iter.name)) {
950 return;
951 }
952 if (!should_add_attribute_to_mesh(profile_attributes,
953 mesh_attributes,
954 iter.name,
955 {iter.domain, iter.data_type},
956 attribute_filter))
957 {
958 return;
959 }
960 const AttrDomain src_domain = iter.domain;
961 const eCustomDataType type = iter.data_type;
962 const GVArray src = *iter.get();
963
964 const AttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, iter.name);
966 iter.name, dst_domain, type);
967 if (!dst) {
968 return;
969 }
970
971 if (src_domain == AttrDomain::Point) {
973 offsets,
974 dst_domain,
975 evaluate_attribute(src, profile, eval_buffer),
976 dst.span);
977 }
978 else if (src_domain == AttrDomain::Curve) {
980 offsets, offsets.profile_indices, dst_domain, src, dst.span);
981 }
982
983 dst.finish();
984 });
985
986 return mesh;
987}
988
990{
991 CurvesGeometry curves(1, 1);
992 curves.offsets_for_write().last() = 1;
993 curves.positions_for_write().fill(float3(0.0f));
994 curves.fill_curve_types(CURVE_TYPE_POLY);
995
996 return curves;
997}
998
999Mesh *curve_to_wire_mesh(const CurvesGeometry &curve, const AttributeFilter &attribute_filter)
1000{
1001 static const CurvesGeometry vert_curve = get_curve_single_vert();
1002 return curve_to_mesh_sweep(curve, vert_curve, false, attribute_filter);
1003}
1004
1005} // namespace blender::bke
Low-level operations for curves.
CustomData interface, see also DNA_customdata_types.h.
bool CustomData_free_layer_named(CustomData *data, blender::StringRef name, const int totelem)
General operations, lookup, etc. for materials.
Mesh * BKE_mesh_new_nomain(int verts_num, int edges_num, int faces_num, int corners_num)
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_POLY
@ BEZIER_HANDLE_VECTOR
@ CD_PROP_STRING
void init()
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
const T & last(const int64_t n=0) const
Definition BLI_array.hh:285
int64_t size() const
Span< T > typed() const
const CPPType & type() const
const void * data() const
void materialize(void *dst) const
constexpr int64_t size() const
constexpr int64_t start() const
constexpr IndexRange drop_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr void fill(const T &value) const
Definition BLI_span.hh:518
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr const T & first() const
Definition BLI_span.hh:316
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
Definition BLI_span.hh:261
void reinitialize(const int64_t new_size)
bool is_builtin(const StringRef attribute_id) const
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader lookup(const StringRef attribute_id) const
std::optional< AttributeMetaData > lookup_meta_data(const StringRef attribute_id) const
int domain_size(const AttrDomain domain) const
bool contains(const StringRef attribute_id) const
GAttributeReader get() const
Span< float3 > evaluated_tangents() const
Span< float3 > evaluated_normals() const
OffsetIndices< int > evaluated_points_by_curve() const
Span< float3 > evaluated_positions() const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
bool add(const StringRef attribute_id, const AttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer)
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type)
int main()
bool point_is_sharp(Span< int8_t > handle_types_left, Span< int8_t > handle_types_right, int index)
int segments_num(const int points_num, const bool cyclic)
static void copy_main_point_data_to_mesh_verts(const Span< T > src, const int profile_point_num, MutableSpan< T > dst)
static void copy_indices_to_offset_ranges(const VArray< T > &src, const Span< int > curve_indices, const OffsetIndices< int > mesh_offsets, MutableSpan< T > dst)
static GSpan evaluate_attribute(const GVArray &src, const CurvesGeometry &curves, Vector< std::byte > &buffer)
static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets, const Span< int > curve_indices, const AttrDomain dst_domain, const GVArray &src, GMutableSpan dst)
static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile)
static void copy_profile_point_data_to_mesh_edges(const Span< T > src, const int main_segment_num, MutableSpan< T > dst)
Mesh * curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, bool fill_caps, const bke::AttributeFilter &attribute_filter={})
static CurvesGeometry get_curve_single_vert()
static void build_mesh_positions(const CurvesInfo &curves_info, const ResultOffsets &offsets, Vector< std::byte > &eval_buffer, Mesh &mesh)
static bool try_sharing_point_data(const CurvesGeometry &main, const StringRef id, const GAttributeReader &src, MutableAttributeAccessor mesh_attributes)
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)
static void foreach_curve_combination(const CurvesInfo &info, const ResultOffsets &offsets, const Fn &fn)
static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, const ResultOffsets &offsets, const AttrDomain dst_domain, const GSpan src_all, GMutableSpan dst_all)
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< bool > sharp_edges)
static AttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_attributes, const StringRef attribute_id)
static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info, const StringRef id, const ResultOffsets &offsets, const AttrDomain dst_domain, const GAttributeReader &src_attribute, Vector< std::byte > &eval_buffer, MutableAttributeAccessor mesh_attributes)
static void fill_mesh_topology(const int vert_offset, const int edge_offset, const int face_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< int2 > edges, MutableSpan< int > corner_verts, MutableSpan< int > corner_edges, MutableSpan< int > face_offsets)
static void write_sharp_bezier_edges(const CurvesInfo &curves_info, const ResultOffsets &offsets, MutableAttributeAccessor mesh_attributes, SpanAttributeWriter< bool > &sharp_edges)
static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes, const AttributeAccessor &mesh_attributes, const StringRef id, const AttributeMetaData &meta_data, const AttributeFilter &attribute_filter)
static void copy_profile_point_data_to_mesh_verts(const Span< T > src, const int main_point_num, MutableSpan< T > dst)
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< float3 > mesh_positions)
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)
static bool try_direct_evaluate_point_data(const CurvesGeometry &main, const GAttributeReader &src, GMutableSpan dst)
Mesh * curve_to_wire_mesh(const CurvesGeometry &curve, const bke::AttributeFilter &attribute_filter={})
static float4x4 build_point_matrix(const float3 &location, const float3 &tangent, const float3 &normal)
static int segments_num_no_duplicate_edge(const int points_num, const bool cyclic)
static bool offsets_contain_single_point(const OffsetIndices< int > offsets)
static void copy_profile_point_data_to_mesh_faces(const Span< T > src, const int main_segment_num, const int profile_segment_num, MutableSpan< T > dst)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
bool is_equal(const MatBase< T, NumCol, NumRow > &a, const MatBase< T, NumCol, NumRow > &b, const T epsilon=T(0))
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void parallel_invoke(Functions &&...functions)
Definition BLI_task.hh:199
void devirtualize_varray(const VArray< T > &varray, const Func &func, bool enable=true)
VecBase< float, 3 > float3
__int64 int64_t
Definition stdint.h:89
bool allow_skip(const StringRef name) const
const CurvesGeometry & profile
const ImplicitSharingInfo * sharing_info