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