Blender V4.3
curves_geometry.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
9#include <mutex>
10#include <utility>
11
12#include "MEM_guardedalloc.h"
13
14#include "BLI_array_utils.hh"
15#include "BLI_bounds.hh"
16#include "BLI_index_mask.hh"
18#include "BLI_math_matrix.hh"
20#include "BLI_memory_counter.hh"
22#include "BLI_task.hh"
23
24#include "BLO_read_write.hh"
25
26#include "DNA_curves_types.h"
27
28#include "BKE_attribute.hh"
29#include "BKE_attribute_math.hh"
31#include "BKE_curves.hh"
32#include "BKE_curves_utils.hh"
33#include "BKE_customdata.hh"
34#include "BKE_deform.hh"
35
36namespace blender::bke {
37
38static const std::string ATTR_POSITION = "position";
39static const std::string ATTR_RADIUS = "radius";
40static const std::string ATTR_TILT = "tilt";
41static const std::string ATTR_CURVE_TYPE = "curve_type";
42static const std::string ATTR_CYCLIC = "cyclic";
43static const std::string ATTR_RESOLUTION = "resolution";
44static const std::string ATTR_NORMAL_MODE = "normal_mode";
45static const std::string ATTR_HANDLE_TYPE_LEFT = "handle_type_left";
46static const std::string ATTR_HANDLE_TYPE_RIGHT = "handle_type_right";
47static const std::string ATTR_HANDLE_POSITION_LEFT = "handle_left";
48static const std::string ATTR_HANDLE_POSITION_RIGHT = "handle_right";
49static const std::string ATTR_NURBS_ORDER = "nurbs_order";
50static const std::string ATTR_NURBS_WEIGHT = "nurbs_weight";
51static const std::string ATTR_NURBS_KNOTS_MODE = "knots_mode";
52static const std::string ATTR_SURFACE_UV_COORDINATE = "surface_uv_coordinate";
53
54/* -------------------------------------------------------------------- */
58CurvesGeometry::CurvesGeometry() : CurvesGeometry(0, 0) {}
59
60CurvesGeometry::CurvesGeometry(const int point_num, const int curve_num)
61{
62 this->runtime = MEM_new<CurvesGeometryRuntime>(__func__);
63
64 this->point_num = point_num;
65 this->curve_num = curve_num;
69
72
73 if (curve_num > 0) {
74 this->curve_offsets = static_cast<int *>(
75 MEM_malloc_arrayN(this->curve_num + 1, sizeof(int), __func__));
76 this->runtime->curve_offsets_sharing_info = implicit_sharing::info_for_mem_free(
77 this->curve_offsets);
78#ifndef NDEBUG
79 this->offsets_for_write().fill(-1);
80#endif
81 /* Set common values for convenience. */
82 this->curve_offsets[0] = 0;
83 this->curve_offsets[this->curve_num] = this->point_num;
84 }
85 else {
86 this->curve_offsets = nullptr;
87 }
88
89 /* Fill the type counts with the default so they're in a valid state. */
90 this->runtime->type_counts[CURVE_TYPE_CATMULL_ROM] = curve_num;
91}
92
93CurvesGeometry::CurvesGeometry(const CurvesGeometry &other)
94{
95 this->curve_offsets = other.curve_offsets;
96 if (other.runtime->curve_offsets_sharing_info) {
97 other.runtime->curve_offsets_sharing_info->add_user();
98 }
99
100 CustomData_init_from(&other.point_data, &this->point_data, CD_MASK_ALL, other.point_num);
101 CustomData_init_from(&other.curve_data, &this->curve_data, CD_MASK_ALL, other.curve_num);
102
103 this->point_num = other.point_num;
104 this->curve_num = other.curve_num;
105
108
110
111 this->runtime = MEM_new<CurvesGeometryRuntime>(
112 __func__,
114 other.runtime->type_counts,
115 other.runtime->evaluated_offsets_cache,
116 other.runtime->nurbs_basis_cache,
117 other.runtime->evaluated_position_cache,
118 other.runtime->bounds_cache,
119 other.runtime->evaluated_length_cache,
120 other.runtime->evaluated_tangent_cache,
121 other.runtime->evaluated_normal_cache,
122 {},
123 true});
124
125 if (other.runtime->bake_materials) {
126 this->runtime->bake_materials = std::make_unique<bake::BakeMaterialsList>(
127 *other.runtime->bake_materials);
128 }
129}
130
131CurvesGeometry &CurvesGeometry::operator=(const CurvesGeometry &other)
132{
133 if (this == &other) {
134 return *this;
135 }
136 std::destroy_at(this);
137 new (this) CurvesGeometry(other);
138 return *this;
139}
140
141CurvesGeometry::CurvesGeometry(CurvesGeometry &&other)
142{
143 this->curve_offsets = other.curve_offsets;
144 other.curve_offsets = nullptr;
145
146 this->point_data = other.point_data;
147 CustomData_reset(&other.point_data);
148
149 this->curve_data = other.curve_data;
150 CustomData_reset(&other.curve_data);
151
152 this->point_num = other.point_num;
153 other.point_num = 0;
154
155 this->curve_num = other.curve_num;
156 other.curve_num = 0;
157
158 this->vertex_group_names = other.vertex_group_names;
159 BLI_listbase_clear(&other.vertex_group_names);
160
161 this->vertex_group_active_index = other.vertex_group_active_index;
162 other.vertex_group_active_index = 0;
163
164 this->attributes_active_index = other.attributes_active_index;
165 other.attributes_active_index = 0;
166
167 this->runtime = other.runtime;
168 other.runtime = nullptr;
169}
170
171CurvesGeometry &CurvesGeometry::operator=(CurvesGeometry &&other)
172{
173 if (this == &other) {
174 return *this;
175 }
176 std::destroy_at(this);
177 new (this) CurvesGeometry(std::move(other));
178 return *this;
179}
180
181CurvesGeometry::~CurvesGeometry()
182{
183 CustomData_free(&this->point_data, this->point_num);
184 CustomData_free(&this->curve_data, this->curve_num);
186 if (this->runtime) {
188 &this->runtime->curve_offsets_sharing_info);
189 MEM_delete(this->runtime);
190 }
191}
192
195/* -------------------------------------------------------------------- */
199static int domain_num(const CurvesGeometry &curves, const AttrDomain domain)
200{
201 return domain == AttrDomain::Point ? curves.points_num() : curves.curves_num();
202}
203
205{
206 return domain == AttrDomain::Point ? curves.point_data : curves.curve_data;
207}
208
209static const CustomData &domain_custom_data(const CurvesGeometry &curves, const AttrDomain domain)
210{
211 return domain == AttrDomain::Point ? curves.point_data : curves.curve_data;
212}
213
214template<typename T>
216 const AttrDomain domain,
217 const StringRef name,
218 const T default_value)
219{
220 const int num = domain_num(curves, domain);
222 const CustomData &custom_data = domain_custom_data(curves, domain);
223
224 const T *data = (const T *)CustomData_get_layer_named(&custom_data, type, name);
225 if (data != nullptr) {
226 return VArray<T>::ForSpan(Span<T>(data, num));
227 }
228 return VArray<T>::ForSingle(default_value, num);
229}
230
231template<typename T>
233 const AttrDomain domain,
234 const StringRef name)
235{
236 const int num = domain_num(curves, domain);
237 const CustomData &custom_data = domain_custom_data(curves, domain);
239
240 T *data = (T *)CustomData_get_layer_named(&custom_data, type, name);
241 if (data == nullptr) {
242 return {};
243 }
244 return {data, num};
245}
246
247template<typename T>
249 const AttrDomain domain,
250 const StringRef name,
251 const T default_value = T())
252{
253 const int num = domain_num(curves, domain);
254 if (num <= 0) {
255 return {};
256 }
258 CustomData &custom_data = domain_custom_data(curves, domain);
259
260 T *data = (T *)CustomData_get_layer_named_for_write(&custom_data, type, name, num);
261 if (data != nullptr) {
262 return {data, num};
263 }
264 data = (T *)CustomData_add_layer_named(&custom_data, type, CD_SET_DEFAULT, num, name);
265 MutableSpan<T> span = {data, num};
266 if (num > 0 && span.first() != default_value) {
267 span.fill(default_value);
268 }
269 return span;
270}
271
272VArray<int8_t> CurvesGeometry::curve_types() const
273{
276}
277
278MutableSpan<int8_t> CurvesGeometry::curve_types_for_write()
279{
281}
282
283void CurvesGeometry::fill_curve_types(const CurveType type)
284{
285 if (type == CURVE_TYPE_CATMULL_ROM) {
286 /* Avoid creating the attribute for Catmull Rom which is the default when the attribute doesn't
287 * exist anyway. */
288 this->attributes_for_write().remove("curve_type");
289 }
290 else {
291 this->curve_types_for_write().fill(type);
292 }
293 this->runtime->type_counts.fill(0);
294 this->runtime->type_counts[type] = this->curves_num();
295 this->tag_topology_changed();
296}
297
298void CurvesGeometry::fill_curve_types(const IndexMask &selection, const CurveType type)
299{
300 if (selection.size() == this->curves_num()) {
301 this->fill_curve_types(type);
302 return;
303 }
304 if (std::optional<int8_t> single_type = this->curve_types().get_if_single()) {
305 if (single_type == type) {
306 this->fill_curve_types(type);
307 return;
308 }
309 }
310 /* A potential performance optimization is only counting the changed indices. */
312 this->update_curve_types();
313 this->tag_topology_changed();
314}
315
316std::array<int, CURVE_TYPES_NUM> calculate_type_counts(const VArray<int8_t> &types)
317{
318 using CountsType = std::array<int, CURVE_TYPES_NUM>;
319 CountsType counts;
320 counts.fill(0);
321
322 if (types.is_single()) {
323 counts[types.get_internal_single()] = types.size();
324 return counts;
325 }
326
327 Span<int8_t> types_span = types.get_internal_span();
329 types.index_range(),
330 2048,
331 counts,
332 [&](const IndexRange curves_range, const CountsType &init) {
333 CountsType result = init;
334 for (const int curve_index : curves_range) {
335 result[types_span[curve_index]]++;
336 }
337 return result;
338 },
339 [](const CountsType &a, const CountsType &b) {
340 CountsType result = a;
341 for (const int i : IndexRange(CURVE_TYPES_NUM)) {
342 result[i] += b[i];
343 }
344 return result;
345 });
346}
347
348void CurvesGeometry::update_curve_types()
349{
350 this->runtime->type_counts = calculate_type_counts(this->curve_types());
351}
352
353Span<float3> CurvesGeometry::positions() const
354{
355 return get_span_attribute<float3>(*this, AttrDomain::Point, ATTR_POSITION);
356}
357MutableSpan<float3> CurvesGeometry::positions_for_write()
358{
359 return get_mutable_attribute<float3>(*this, AttrDomain::Point, ATTR_POSITION);
360}
361
362Span<int> CurvesGeometry::offsets() const
363{
364 if (this->curve_num == 0) {
365 return {};
366 }
367 return {this->curve_offsets, this->curve_num + 1};
368}
369MutableSpan<int> CurvesGeometry::offsets_for_write()
370{
371 if (this->curve_num == 0) {
372 return {};
373 }
375 &this->curve_offsets, &this->runtime->curve_offsets_sharing_info, this->curve_num + 1);
376 return {this->curve_offsets, this->curve_num + 1};
377}
378
379VArray<bool> CurvesGeometry::cyclic() const
380{
381 return get_varray_attribute<bool>(*this, AttrDomain::Curve, ATTR_CYCLIC, false);
382}
383MutableSpan<bool> CurvesGeometry::cyclic_for_write()
384{
385 return get_mutable_attribute<bool>(*this, AttrDomain::Curve, ATTR_CYCLIC, false);
386}
387
388VArray<int> CurvesGeometry::resolution() const
389{
390 return get_varray_attribute<int>(*this, AttrDomain::Curve, ATTR_RESOLUTION, 12);
391}
392MutableSpan<int> CurvesGeometry::resolution_for_write()
393{
394 return get_mutable_attribute<int>(*this, AttrDomain::Curve, ATTR_RESOLUTION, 12);
395}
396
397VArray<int8_t> CurvesGeometry::normal_mode() const
398{
399 return get_varray_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NORMAL_MODE, 0);
400}
401MutableSpan<int8_t> CurvesGeometry::normal_mode_for_write()
402{
403 return get_mutable_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NORMAL_MODE);
404}
405
406VArray<float> CurvesGeometry::tilt() const
407{
408 return get_varray_attribute<float>(*this, AttrDomain::Point, ATTR_TILT, 0.0f);
409}
410MutableSpan<float> CurvesGeometry::tilt_for_write()
411{
412 return get_mutable_attribute<float>(*this, AttrDomain::Point, ATTR_TILT);
413}
414
415VArray<int8_t> CurvesGeometry::handle_types_left() const
416{
417 return get_varray_attribute<int8_t>(*this, AttrDomain::Point, ATTR_HANDLE_TYPE_LEFT, 0);
418}
419MutableSpan<int8_t> CurvesGeometry::handle_types_left_for_write()
420{
421 return get_mutable_attribute<int8_t>(*this, AttrDomain::Point, ATTR_HANDLE_TYPE_LEFT, 0);
422}
423
424VArray<int8_t> CurvesGeometry::handle_types_right() const
425{
426 return get_varray_attribute<int8_t>(*this, AttrDomain::Point, ATTR_HANDLE_TYPE_RIGHT, 0);
427}
428MutableSpan<int8_t> CurvesGeometry::handle_types_right_for_write()
429{
430 return get_mutable_attribute<int8_t>(*this, AttrDomain::Point, ATTR_HANDLE_TYPE_RIGHT, 0);
431}
432
433Span<float3> CurvesGeometry::handle_positions_left() const
434{
435 return get_span_attribute<float3>(*this, AttrDomain::Point, ATTR_HANDLE_POSITION_LEFT);
436}
437MutableSpan<float3> CurvesGeometry::handle_positions_left_for_write()
438{
439 return get_mutable_attribute<float3>(*this, AttrDomain::Point, ATTR_HANDLE_POSITION_LEFT);
440}
441
442Span<float3> CurvesGeometry::handle_positions_right() const
443{
444 return get_span_attribute<float3>(*this, AttrDomain::Point, ATTR_HANDLE_POSITION_RIGHT);
445}
446MutableSpan<float3> CurvesGeometry::handle_positions_right_for_write()
447{
448 return get_mutable_attribute<float3>(*this, AttrDomain::Point, ATTR_HANDLE_POSITION_RIGHT);
449}
450
451VArray<int8_t> CurvesGeometry::nurbs_orders() const
452{
453 return get_varray_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NURBS_ORDER, 4);
454}
455MutableSpan<int8_t> CurvesGeometry::nurbs_orders_for_write()
456{
457 return get_mutable_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NURBS_ORDER, 4);
458}
459
460Span<float> CurvesGeometry::nurbs_weights() const
461{
462 return get_span_attribute<float>(*this, AttrDomain::Point, ATTR_NURBS_WEIGHT);
463}
464MutableSpan<float> CurvesGeometry::nurbs_weights_for_write()
465{
466 return get_mutable_attribute<float>(*this, AttrDomain::Point, ATTR_NURBS_WEIGHT);
467}
468
469VArray<int8_t> CurvesGeometry::nurbs_knots_modes() const
470{
471 return get_varray_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NURBS_KNOTS_MODE, 0);
472}
473MutableSpan<int8_t> CurvesGeometry::nurbs_knots_modes_for_write()
474{
475 return get_mutable_attribute<int8_t>(*this, AttrDomain::Curve, ATTR_NURBS_KNOTS_MODE, 0);
476}
477
478Span<float2> CurvesGeometry::surface_uv_coords() const
479{
480 return get_span_attribute<float2>(*this, AttrDomain::Curve, ATTR_SURFACE_UV_COORDINATE);
481}
482
483MutableSpan<float2> CurvesGeometry::surface_uv_coords_for_write()
484{
485 return get_mutable_attribute<float2>(*this, AttrDomain::Curve, ATTR_SURFACE_UV_COORDINATE);
486}
487
488Span<MDeformVert> CurvesGeometry::deform_verts() const
489{
490 const MDeformVert *dverts = static_cast<const MDeformVert *>(
491 CustomData_get_layer(&this->point_data, CD_MDEFORMVERT));
492 if (dverts == nullptr) {
493 return {};
494 }
495 return {dverts, this->point_num};
496}
497
498MutableSpan<MDeformVert> CurvesGeometry::deform_verts_for_write()
499{
500 MDeformVert *dvert = static_cast<MDeformVert *>(
501 CustomData_get_layer_for_write(&this->point_data, CD_MDEFORMVERT, this->point_num));
502 if (dvert != nullptr) {
503 return {dvert, this->point_num};
504 }
505 return {static_cast<MDeformVert *>(CustomData_add_layer(
506 &this->point_data, CD_MDEFORMVERT, CD_SET_DEFAULT, this->point_num)),
507 this->point_num};
508}
509
512/* -------------------------------------------------------------------- */
516template<typename CountFn> void build_offsets(MutableSpan<int> offsets, const CountFn &count_fn)
517{
518 int offset = 0;
519 for (const int i : offsets.drop_back(1).index_range()) {
520 offsets[i] = offset;
521 offset += count_fn(i);
522 }
523 offsets.last() = offset;
524}
525
526static void calculate_evaluated_offsets(const CurvesGeometry &curves,
527 MutableSpan<int> offsets,
528 MutableSpan<int> all_bezier_offsets)
529{
530 const OffsetIndices points_by_curve = curves.points_by_curve();
531 const VArray<int8_t> types = curves.curve_types();
532 const VArray<int> resolution = curves.resolution();
533 const VArray<bool> cyclic = curves.cyclic();
534
535 VArraySpan<int8_t> handle_types_left;
536 VArraySpan<int8_t> handle_types_right;
537 if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
538 handle_types_left = curves.handle_types_left();
539 handle_types_right = curves.handle_types_right();
540 }
541
542 const VArray<int8_t> nurbs_orders = curves.nurbs_orders();
543 const VArray<int8_t> nurbs_knots_modes = curves.nurbs_knots_modes();
544
545 build_offsets(offsets, [&](const int curve_index) -> int {
546 const IndexRange points = points_by_curve[curve_index];
547 switch (types[curve_index]) {
549 return curves::catmull_rom::calculate_evaluated_num(
550 points.size(), cyclic[curve_index], resolution[curve_index]);
551 case CURVE_TYPE_POLY:
552 return points.size();
553 case CURVE_TYPE_BEZIER: {
554 const IndexRange offsets = curves::per_curve_point_offsets_range(points, curve_index);
555 curves::bezier::calculate_evaluated_offsets(handle_types_left.slice(points),
556 handle_types_right.slice(points),
557 cyclic[curve_index],
558 resolution[curve_index],
559 all_bezier_offsets.slice(offsets));
560 return all_bezier_offsets[offsets.last()];
561 }
562 case CURVE_TYPE_NURBS:
563 return curves::nurbs::calculate_evaluated_num(points.size(),
564 nurbs_orders[curve_index],
565 cyclic[curve_index],
566 resolution[curve_index],
567 KnotsMode(nurbs_knots_modes[curve_index]));
568 }
570 return 0;
571 });
572}
573
574OffsetIndices<int> CurvesGeometry::evaluated_points_by_curve() const
575{
576 const CurvesGeometryRuntime &runtime = *this->runtime;
577 if (this->is_single_type(CURVE_TYPE_POLY)) {
578 /* When all the curves are poly curves, the evaluated offsets are the same as the control
579 * point offsets, so it's possible to completely avoid building a new offsets array. */
583 });
584 return this->points_by_curve();
585 }
586
588 r_data.evaluated_offsets.resize(this->curves_num() + 1);
589
590 if (this->has_curve_with_type(CURVE_TYPE_BEZIER)) {
591 r_data.all_bezier_offsets.resize(this->points_num() + this->curves_num());
592 }
593 else {
595 }
596
597 calculate_evaluated_offsets(*this, r_data.evaluated_offsets, r_data.all_bezier_offsets);
598 });
599
600 return OffsetIndices<int>(runtime.evaluated_offsets_cache.data().evaluated_offsets);
601}
602
603IndexMask CurvesGeometry::indices_for_curve_type(const CurveType type,
604 IndexMaskMemory &memory) const
605{
606 return this->indices_for_curve_type(type, this->curves_range(), memory);
607}
608
609IndexMask CurvesGeometry::indices_for_curve_type(const CurveType type,
610 const IndexMask &selection,
611 IndexMaskMemory &memory) const
612{
613 return curves::indices_for_type(
614 this->curve_types(), this->curve_type_counts(), type, selection, memory);
615}
616
617Array<int> CurvesGeometry::point_to_curve_map() const
618{
619 Array<int> map(this->points_num());
620 offset_indices::build_reverse_map(this->points_by_curve(), map);
621 return map;
622}
623
624void CurvesGeometry::ensure_nurbs_basis_cache() const
625{
626 const CurvesGeometryRuntime &runtime = *this->runtime;
627 runtime.nurbs_basis_cache.ensure([&](Vector<curves::nurbs::BasisCache> &r_data) {
628 IndexMaskMemory memory;
629 const IndexMask nurbs_mask = this->indices_for_curve_type(CURVE_TYPE_NURBS, memory);
630 if (nurbs_mask.is_empty()) {
631 r_data.clear_and_shrink();
632 return;
633 }
634
635 r_data.resize(this->curves_num());
636
637 const OffsetIndices<int> points_by_curve = this->points_by_curve();
638 const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
639 const VArray<bool> cyclic = this->cyclic();
640 const VArray<int8_t> orders = this->nurbs_orders();
641 const VArray<int8_t> knots_modes = this->nurbs_knots_modes();
642
643 nurbs_mask.foreach_segment(GrainSize(64), [&](const IndexMaskSegment segment) {
644 Vector<float, 32> knots;
645 for (const int curve_index : segment) {
646 const IndexRange points = points_by_curve[curve_index];
647 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
648
649 const int8_t order = orders[curve_index];
650 const bool is_cyclic = cyclic[curve_index];
651 const KnotsMode mode = KnotsMode(knots_modes[curve_index]);
652
653 if (!curves::nurbs::check_valid_num_and_order(points.size(), order, is_cyclic, mode)) {
654 r_data[curve_index].invalid = true;
655 continue;
656 }
657
658 knots.reinitialize(curves::nurbs::knots_num(points.size(), order, is_cyclic));
659 curves::nurbs::calculate_knots(points.size(), mode, order, is_cyclic, knots);
660 curves::nurbs::calculate_basis_cache(
661 points.size(), evaluated_points.size(), order, is_cyclic, knots, r_data[curve_index]);
662 }
663 });
664 });
665}
666
667Span<float3> CurvesGeometry::evaluated_positions() const
668{
669 const CurvesGeometryRuntime &runtime = *this->runtime;
670 if (this->is_single_type(CURVE_TYPE_POLY)) {
671 runtime.evaluated_position_cache.ensure(
672 [&](Vector<float3> &r_data) { r_data.clear_and_shrink(); });
673 return this->positions();
674 }
675 this->ensure_nurbs_basis_cache();
676 runtime.evaluated_position_cache.ensure([&](Vector<float3> &r_data) {
677 r_data.resize(this->evaluated_points_num());
678 MutableSpan<float3> evaluated_positions = r_data;
679
680 const OffsetIndices<int> points_by_curve = this->points_by_curve();
681 const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
682 const Span<float3> positions = this->positions();
683
684 auto evaluate_catmull = [&](const IndexMask &selection) {
685 const VArray<bool> cyclic = this->cyclic();
686 const VArray<int> resolution = this->resolution();
687 selection.foreach_index(GrainSize(128), [&](const int curve_index) {
688 const IndexRange points = points_by_curve[curve_index];
689 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
690 curves::catmull_rom::interpolate_to_evaluated(positions.slice(points),
691 cyclic[curve_index],
692 resolution[curve_index],
693 evaluated_positions.slice(evaluated_points));
694 });
695 };
696 auto evaluate_poly = [&](const IndexMask &selection) {
698 points_by_curve, evaluated_points_by_curve, selection, positions, evaluated_positions);
699 };
700 auto evaluate_bezier = [&](const IndexMask &selection) {
701 const Span<float3> handle_positions_left = this->handle_positions_left();
702 const Span<float3> handle_positions_right = this->handle_positions_right();
703 if (handle_positions_left.is_empty() || handle_positions_right.is_empty()) {
704 curves::fill_points(evaluated_points_by_curve, selection, float3(0), evaluated_positions);
705 return;
706 }
707 const Span<int> all_bezier_offsets =
708 runtime.evaluated_offsets_cache.data().all_bezier_offsets;
709 selection.foreach_index(GrainSize(128), [&](const int curve_index) {
710 const IndexRange points = points_by_curve[curve_index];
711 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
712 const IndexRange offsets = curves::per_curve_point_offsets_range(points, curve_index);
713 curves::bezier::calculate_evaluated_positions(positions.slice(points),
714 handle_positions_left.slice(points),
715 handle_positions_right.slice(points),
716 all_bezier_offsets.slice(offsets),
717 evaluated_positions.slice(evaluated_points));
718 });
719 };
720 auto evaluate_nurbs = [&](const IndexMask &selection) {
721 this->ensure_nurbs_basis_cache();
722 const VArray<int8_t> nurbs_orders = this->nurbs_orders();
723 const Span<float> nurbs_weights = this->nurbs_weights();
724 const Span<curves::nurbs::BasisCache> nurbs_basis_cache = runtime.nurbs_basis_cache.data();
725 selection.foreach_index(GrainSize(128), [&](const int curve_index) {
726 const IndexRange points = points_by_curve[curve_index];
727 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
728 curves::nurbs::interpolate_to_evaluated(nurbs_basis_cache[curve_index],
729 nurbs_orders[curve_index],
730 nurbs_weights.slice_safe(points),
731 positions.slice(points),
732 evaluated_positions.slice(evaluated_points));
733 });
734 };
735 curves::foreach_curve_by_type(this->curve_types(),
736 this->curve_type_counts(),
737 this->curves_range(),
738 evaluate_catmull,
739 evaluate_poly,
740 evaluate_bezier,
741 evaluate_nurbs);
742 });
743 return runtime.evaluated_position_cache.data();
744}
745
746Span<float3> CurvesGeometry::evaluated_tangents() const
747{
748 const CurvesGeometryRuntime &runtime = *this->runtime;
749 runtime.evaluated_tangent_cache.ensure([&](Vector<float3> &r_data) {
750 const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
751 const Span<float3> evaluated_positions = this->evaluated_positions();
752 const VArray<bool> cyclic = this->cyclic();
753
754 r_data.resize(this->evaluated_points_num());
755 MutableSpan<float3> tangents = r_data;
756
757 threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
758 for (const int curve_index : curves_range) {
759 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
760 curves::poly::calculate_tangents(evaluated_positions.slice(evaluated_points),
761 cyclic[curve_index],
762 tangents.slice(evaluated_points));
763 }
764 });
765
766 /* Correct the first and last tangents of non-cyclic Bezier curves so that they align with
767 * the inner handles. This is a separate loop to avoid the cost when Bezier type curves are
768 * not used. */
769 IndexMaskMemory memory;
770 const IndexMask bezier_mask = this->indices_for_curve_type(CURVE_TYPE_BEZIER, memory);
771 if (!bezier_mask.is_empty()) {
772 const OffsetIndices<int> points_by_curve = this->points_by_curve();
773 const Span<float3> positions = this->positions();
774 const Span<float3> handles_left = this->handle_positions_left();
775 const Span<float3> handles_right = this->handle_positions_right();
776
777 bezier_mask.foreach_index(GrainSize(1024), [&](const int curve_index) {
778 if (cyclic[curve_index]) {
779 return;
780 }
781 const IndexRange points = points_by_curve[curve_index];
782 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
783
784 const float epsilon = 1e-6f;
786 handles_right[points.first()], positions[points.first()], epsilon))
787 {
788 tangents[evaluated_points.first()] = math::normalize(handles_right[points.first()] -
789 positions[points.first()]);
790 }
792 handles_left[points.last()], positions[points.last()], epsilon))
793 {
794 tangents[evaluated_points.last()] = math::normalize(positions[points.last()] -
795 handles_left[points.last()]);
796 }
797 });
798 }
799 });
800 return runtime.evaluated_tangent_cache.data();
801}
802
804 const Span<float3> axes,
805 const Span<float> angles)
806{
807 for (const int i : directions.index_range()) {
808 const float3 axis = axes[i];
809 if (UNLIKELY(math::is_zero(axis))) {
810 continue;
811 }
812 directions[i] = math::rotate_direction_around_axis(directions[i], axis, angles[i]);
813 }
814}
815
817{
818 for (const int i : data.index_range()) {
819 data[i] = math::normalize(data[i]);
820 }
821}
822
834
835static void evaluate_generic_data_for_curve(const EvalData &eval_data,
836 const int curve_index,
837 const GSpan src,
838 GMutableSpan dst)
839{
840 const IndexRange points = eval_data.points_by_curve[curve_index];
841 switch (eval_data.types[curve_index]) {
843 curves::catmull_rom::interpolate_to_evaluated(
844 src, eval_data.cyclic[curve_index], eval_data.resolution[curve_index], dst);
845 break;
846 case CURVE_TYPE_POLY:
847 dst.copy_from(src);
848 break;
849 case CURVE_TYPE_BEZIER: {
850 const IndexRange offsets = curves::per_curve_point_offsets_range(points, curve_index);
851 curves::bezier::interpolate_to_evaluated(
852 src, eval_data.all_bezier_evaluated_offsets.slice(offsets), dst);
853 break;
854 }
855 case CURVE_TYPE_NURBS:
856 curves::nurbs::interpolate_to_evaluated(eval_data.nurbs_basis_cache[curve_index],
857 eval_data.nurbs_orders[curve_index],
858 eval_data.nurbs_weights.slice_safe(points),
859 src,
860 dst);
861 break;
862 }
863}
864
865Span<float3> CurvesGeometry::evaluated_normals() const
866{
867 const CurvesGeometryRuntime &runtime = *this->runtime;
868 this->ensure_nurbs_basis_cache();
869 runtime.evaluated_normal_cache.ensure([&](Vector<float3> &r_data) {
870 const OffsetIndices<int> points_by_curve = this->points_by_curve();
871 const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
872 const VArray<int8_t> types = this->curve_types();
873 const VArray<bool> cyclic = this->cyclic();
874 const VArray<int8_t> normal_mode = this->normal_mode();
875 const Span<float3> evaluated_tangents = this->evaluated_tangents();
876 const AttributeAccessor attributes = this->attributes();
877 const EvalData eval_data{
878 points_by_curve,
879 types,
880 cyclic,
881 this->resolution(),
882 runtime.evaluated_offsets_cache.data().all_bezier_offsets,
883 runtime.nurbs_basis_cache.data(),
884 this->nurbs_orders(),
885 this->nurbs_weights(),
886 };
887 const VArray<float> tilt = this->tilt();
888 VArraySpan<float> tilt_span;
889 const bool use_tilt = !(tilt.is_single() && tilt.get_internal_single() == 0.0f);
890 if (use_tilt) {
891 tilt_span = tilt;
892 }
893 VArraySpan<float3> custom_normal_span;
894 if (const VArray<float3> custom_normal = *attributes.lookup<float3>("custom_normal",
895 AttrDomain::Point))
896 {
897 custom_normal_span = custom_normal;
898 }
899
900 r_data.resize(this->evaluated_points_num());
901 MutableSpan<float3> evaluated_normals = r_data;
902
903 threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
904 /* Reuse a buffer for the evaluated tilts. */
905 Vector<float> evaluated_tilts;
906
907 for (const int curve_index : curves_range) {
908 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
909 switch (NormalMode(normal_mode[curve_index])) {
910 case NORMAL_MODE_Z_UP:
911 curves::poly::calculate_normals_z_up(evaluated_tangents.slice(evaluated_points),
912 evaluated_normals.slice(evaluated_points));
913 break;
915 curves::poly::calculate_normals_minimum(evaluated_tangents.slice(evaluated_points),
916 cyclic[curve_index],
917 evaluated_normals.slice(evaluated_points));
918 break;
919 case NORMAL_MODE_FREE:
920 if (custom_normal_span.is_empty()) {
921 curves::poly::calculate_normals_z_up(evaluated_tangents.slice(evaluated_points),
922 evaluated_normals.slice(evaluated_points));
923 }
924 else {
925 const Span<float3> src = custom_normal_span.slice(points_by_curve[curve_index]);
926 MutableSpan<float3> dst = evaluated_normals.slice(
927 evaluated_points_by_curve[curve_index]);
928 evaluate_generic_data_for_curve(eval_data, curve_index, src, dst);
929 normalize_span(dst);
930 }
931 break;
932 }
933
934 /* If the "tilt" attribute exists, rotate the normals around the tangents by the
935 * evaluated angles. We can avoid copying the tilts to evaluate them for poly curves. */
936 if (use_tilt) {
937 const IndexRange points = points_by_curve[curve_index];
938 if (types[curve_index] == CURVE_TYPE_POLY) {
939 rotate_directions_around_axes(evaluated_normals.slice(evaluated_points),
940 evaluated_tangents.slice(evaluated_points),
941 tilt_span.slice(points));
942 }
943 else {
944 evaluated_tilts.reinitialize(evaluated_points.size());
946 curve_index,
947 tilt_span.slice(points),
948 evaluated_tilts.as_mutable_span());
949 rotate_directions_around_axes(evaluated_normals.slice(evaluated_points),
950 evaluated_tangents.slice(evaluated_points),
951 evaluated_tilts.as_span());
952 }
953 }
954 }
955 });
956 });
957 return this->runtime->evaluated_normal_cache.data();
958}
959
960void CurvesGeometry::interpolate_to_evaluated(const int curve_index,
961 const GSpan src,
962 GMutableSpan dst) const
963{
964 const CurvesGeometryRuntime &runtime = *this->runtime;
965 const EvalData eval_data{
966 this->points_by_curve(),
967 this->curve_types(),
968 this->cyclic(),
969 this->resolution(),
970 runtime.evaluated_offsets_cache.data().all_bezier_offsets,
971 runtime.nurbs_basis_cache.data(),
972 this->nurbs_orders(),
973 this->nurbs_weights(),
974 };
975 BLI_assert(src.size() == this->points_by_curve()[curve_index].size());
976 BLI_assert(dst.size() == this->evaluated_points_by_curve()[curve_index].size());
977 evaluate_generic_data_for_curve(eval_data, curve_index, src, dst);
978}
979
980void CurvesGeometry::interpolate_to_evaluated(const GSpan src, GMutableSpan dst) const
981{
982 const CurvesGeometryRuntime &runtime = *this->runtime;
983 const OffsetIndices points_by_curve = this->points_by_curve();
984 const EvalData eval_data{
985 points_by_curve,
986 this->curve_types(),
987 this->cyclic(),
988 this->resolution(),
989 runtime.evaluated_offsets_cache.data().all_bezier_offsets,
990 runtime.nurbs_basis_cache.data(),
991 this->nurbs_orders(),
992 this->nurbs_weights(),
993 };
994 const OffsetIndices evaluated_points_by_curve = this->evaluated_points_by_curve();
995
996 threading::parallel_for(this->curves_range(), 512, [&](IndexRange curves_range) {
997 for (const int curve_index : curves_range) {
998 const IndexRange points = points_by_curve[curve_index];
999 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
1001 eval_data, curve_index, src.slice(points), dst.slice(evaluated_points));
1002 }
1003 });
1004}
1005
1006void CurvesGeometry::ensure_evaluated_lengths() const
1007{
1008 const CurvesGeometryRuntime &runtime = *this->runtime;
1009 runtime.evaluated_length_cache.ensure([&](Vector<float> &r_data) {
1010 /* Use an extra length value for the final cyclic segment for a consistent size
1011 * (see comment on #evaluated_length_cache). */
1012 const int total_num = this->evaluated_points_num() + this->curves_num();
1013 r_data.resize(total_num);
1014 MutableSpan<float> evaluated_lengths = r_data;
1015
1016 const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
1017 const Span<float3> evaluated_positions = this->evaluated_positions();
1018 const VArray<bool> curves_cyclic = this->cyclic();
1019
1020 threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
1021 for (const int curve_index : curves_range) {
1022 const bool cyclic = curves_cyclic[curve_index];
1023 const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
1024 const IndexRange lengths_range = this->lengths_range_for_curve(curve_index, cyclic);
1025 length_parameterize::accumulate_lengths(evaluated_positions.slice(evaluated_points),
1026 cyclic,
1027 evaluated_lengths.slice(lengths_range));
1028 }
1029 });
1030 });
1031}
1032
1033void CurvesGeometry::ensure_can_interpolate_to_evaluated() const
1034{
1035 this->evaluated_points_by_curve();
1036 this->ensure_nurbs_basis_cache();
1037}
1038
1041/* -------------------------------------------------------------------- */
1045void CurvesGeometry::resize(const int points_num, const int curves_num)
1046{
1047 if (points_num != this->point_num) {
1048 CustomData_realloc(&this->point_data, this->points_num(), points_num);
1049 this->point_num = points_num;
1050 }
1051 if (curves_num != this->curve_num) {
1052 CustomData_realloc(&this->curve_data, this->curves_num(), curves_num);
1053 implicit_sharing::resize_trivial_array(&this->curve_offsets,
1054 &this->runtime->curve_offsets_sharing_info,
1055 this->curve_num == 0 ? 0 : (this->curve_num + 1),
1056 curves_num + 1);
1057 /* Set common values for convenience. */
1058 this->curve_offsets[0] = 0;
1059 this->curve_offsets[curves_num] = this->point_num;
1060 this->curve_num = curves_num;
1061 }
1062 this->tag_topology_changed();
1063}
1064
1065void CurvesGeometry::tag_positions_changed()
1066{
1067 this->runtime->evaluated_position_cache.tag_dirty();
1068 this->runtime->evaluated_tangent_cache.tag_dirty();
1069 this->runtime->evaluated_normal_cache.tag_dirty();
1070 this->runtime->evaluated_length_cache.tag_dirty();
1071 this->runtime->bounds_cache.tag_dirty();
1072}
1073void CurvesGeometry::tag_topology_changed()
1074{
1075 this->tag_positions_changed();
1076 this->runtime->evaluated_offsets_cache.tag_dirty();
1077 this->runtime->nurbs_basis_cache.tag_dirty();
1078 this->runtime->check_type_counts = true;
1079}
1080void CurvesGeometry::tag_normals_changed()
1081{
1082 this->runtime->evaluated_normal_cache.tag_dirty();
1083}
1084void CurvesGeometry::tag_radii_changed() {}
1085
1086static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
1087{
1088 threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
1089 for (float3 &position : positions.slice(range)) {
1090 position += translation;
1091 }
1092 });
1093}
1094
1095static void transform_positions(MutableSpan<float3> positions, const float4x4 &matrix)
1096{
1097 threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
1098 for (float3 &position : positions.slice(range)) {
1099 position = math::transform_point(matrix, position);
1100 }
1101 });
1102}
1103
1104static void transform_normals(MutableSpan<float3> normals, const float4x4 &matrix)
1105{
1106 const float3x3 normal_transform = math::transpose(math::invert(float3x3(matrix)));
1107 threading::parallel_for(normals.index_range(), 1024, [&](const IndexRange range) {
1108 for (float3 &normal : normals.slice(range)) {
1109 normal = normal_transform * normal;
1110 }
1111 });
1112}
1113
1114void CurvesGeometry::calculate_bezier_auto_handles()
1115{
1116 if (!this->has_curve_with_type(CURVE_TYPE_BEZIER)) {
1117 return;
1118 }
1119 if (this->handle_positions_left().is_empty() || this->handle_positions_right().is_empty()) {
1120 return;
1121 }
1122 const OffsetIndices points_by_curve = this->points_by_curve();
1123 const VArray<int8_t> types = this->curve_types();
1124 const VArray<bool> cyclic = this->cyclic();
1125 const VArraySpan<int8_t> types_left{this->handle_types_left()};
1126 const VArraySpan<int8_t> types_right{this->handle_types_right()};
1127 const Span<float3> positions = this->positions();
1128 MutableSpan<float3> positions_left = this->handle_positions_left_for_write();
1129 MutableSpan<float3> positions_right = this->handle_positions_right_for_write();
1130
1131 threading::parallel_for(this->curves_range(), 128, [&](IndexRange range) {
1132 for (const int i_curve : range) {
1133 if (types[i_curve] == CURVE_TYPE_BEZIER) {
1134 const IndexRange points = points_by_curve[i_curve];
1135 curves::bezier::calculate_auto_handles(cyclic[i_curve],
1136 types_left.slice(points),
1137 types_right.slice(points),
1138 positions.slice(points),
1139 positions_left.slice(points),
1140 positions_right.slice(points));
1141 }
1142 }
1143 });
1144}
1145
1146void CurvesGeometry::translate(const float3 &translation)
1147{
1148 if (math::is_zero(translation)) {
1149 return;
1150 }
1151
1152 std::optional<Bounds<float3>> bounds;
1153 if (this->runtime->bounds_cache.is_cached()) {
1154 bounds = this->runtime->bounds_cache.data();
1155 }
1156
1157 translate_positions(this->positions_for_write(), translation);
1158 if (!this->handle_positions_left().is_empty()) {
1159 translate_positions(this->handle_positions_left_for_write(), translation);
1160 }
1161 if (!this->handle_positions_right().is_empty()) {
1162 translate_positions(this->handle_positions_right_for_write(), translation);
1163 }
1164 this->tag_positions_changed();
1165
1166 if (bounds) {
1167 bounds->min += translation;
1168 bounds->max += translation;
1169 this->runtime->bounds_cache.ensure([&](blender::Bounds<float3> &r_data) { r_data = *bounds; });
1170 }
1171}
1172
1173void CurvesGeometry::transform(const float4x4 &matrix)
1174{
1175 transform_positions(this->positions_for_write(), matrix);
1176 if (!this->handle_positions_left().is_empty()) {
1177 transform_positions(this->handle_positions_left_for_write(), matrix);
1178 }
1179 if (!this->handle_positions_right().is_empty()) {
1180 transform_positions(this->handle_positions_right_for_write(), matrix);
1181 }
1182 MutableAttributeAccessor attributes = this->attributes_for_write();
1183 if (SpanAttributeWriter normals = attributes.lookup_for_write_span<float3>("custom_normal")) {
1184 transform_normals(normals.span, matrix);
1185 normals.finish();
1186 }
1187 this->tag_positions_changed();
1188}
1189
1190std::optional<Bounds<float3>> CurvesGeometry::bounds_min_max() const
1191{
1192 if (this->points_num() == 0) {
1193 return std::nullopt;
1194 }
1195 this->runtime->bounds_cache.ensure(
1196 [&](Bounds<float3> &r_bounds) { r_bounds = *bounds::min_max(this->evaluated_positions()); });
1197 return this->runtime->bounds_cache.data();
1198}
1199
1200void CurvesGeometry::count_memory(MemoryCounter &memory) const
1201{
1202 memory.add_shared(this->runtime->curve_offsets_sharing_info, this->offsets().size_in_bytes());
1203 CustomData_count_memory(this->point_data, this->point_num, memory);
1204 CustomData_count_memory(this->curve_data, this->curve_num, memory);
1205}
1206
1208 const IndexMask &points_to_copy,
1209 const AttributeFilter &attribute_filter)
1210{
1211 const Array<int> point_to_curve_map = curves.point_to_curve_map();
1212 Array<int> curve_point_counts(curves.curves_num(), 0);
1213 points_to_copy.foreach_index(
1214 [&](const int64_t point_i) { curve_point_counts[point_to_curve_map[point_i]]++; });
1215
1216 IndexMaskMemory memory;
1217 const IndexMask curves_to_copy = IndexMask::from_predicate(
1218 curves.curves_range(), GrainSize(4096), memory, [&](const int64_t i) {
1219 return curve_point_counts[i] > 0;
1220 });
1221
1222 CurvesGeometry dst_curves(points_to_copy.size(), curves_to_copy.size());
1223
1224 BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
1225
1227 dst_curves.curves_num() > 1024,
1228 [&]() {
1229 if (curves_to_copy.is_empty()) {
1230 return;
1231 }
1232 MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
1234 curve_point_counts.as_span(), curves_to_copy, new_curve_offsets.drop_back(1));
1236 },
1237 [&]() {
1238 gather_attributes(curves.attributes(),
1239 AttrDomain::Point,
1240 AttrDomain::Point,
1241 attribute_filter,
1242 points_to_copy,
1243 dst_curves.attributes_for_write());
1244 gather_attributes(curves.attributes(),
1245 AttrDomain::Curve,
1246 AttrDomain::Curve,
1247 attribute_filter,
1248 curves_to_copy,
1249 dst_curves.attributes_for_write());
1250 });
1251
1252 if (dst_curves.curves_num() == curves.curves_num()) {
1253 dst_curves.runtime->type_counts = curves.runtime->type_counts;
1254 }
1255 else {
1256 dst_curves.remove_attributes_based_on_types();
1257 }
1258
1259 return dst_curves;
1260}
1261
1262void CurvesGeometry::remove_points(const IndexMask &points_to_delete,
1263 const AttributeFilter &attribute_filter)
1264{
1265 if (points_to_delete.is_empty()) {
1266 return;
1267 }
1268 if (points_to_delete.size() == this->points_num()) {
1269 *this = {};
1270 return;
1271 }
1272 IndexMaskMemory memory;
1273 const IndexMask points_to_copy = points_to_delete.complement(this->points_range(), memory);
1274 *this = curves_copy_point_selection(*this, points_to_copy, attribute_filter);
1275}
1276
1278 const IndexMask &curves_to_copy,
1279 const AttributeFilter &attribute_filter)
1280{
1281 const OffsetIndices points_by_curve = curves.points_by_curve();
1282 CurvesGeometry dst_curves(0, curves_to_copy.size());
1283 const OffsetIndices dst_points_by_curve = offset_indices::gather_selected_offsets(
1284 points_by_curve, curves_to_copy, dst_curves.offsets_for_write());
1285 dst_curves.resize(dst_points_by_curve.total_size(), dst_curves.curves_num());
1286
1287 BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
1288
1289 const AttributeAccessor src_attributes = curves.attributes();
1290 MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
1291
1292 gather_attributes_group_to_group(src_attributes,
1293 AttrDomain::Point,
1294 AttrDomain::Point,
1295 attribute_filter,
1296 points_by_curve,
1297 dst_points_by_curve,
1298 curves_to_copy,
1299 dst_attributes);
1300
1301 gather_attributes(src_attributes,
1302 AttrDomain::Curve,
1303 AttrDomain::Curve,
1304 attribute_filter,
1305 curves_to_copy,
1306 dst_attributes);
1307
1308 dst_curves.update_curve_types();
1310
1311 return dst_curves;
1312}
1313
1314void CurvesGeometry::remove_curves(const IndexMask &curves_to_delete,
1315 const AttributeFilter &attribute_filter)
1316{
1317 if (curves_to_delete.is_empty()) {
1318 return;
1319 }
1320 if (curves_to_delete.size() == this->curves_num()) {
1321 *this = {};
1322 return;
1323 }
1324 IndexMaskMemory memory;
1325 const IndexMask curves_to_copy = curves_to_delete.complement(this->curves_range(), memory);
1326 *this = curves_copy_curve_selection(*this, curves_to_copy, attribute_filter);
1327}
1328
1329template<typename T>
1331 const IndexMask &curve_selection,
1332 MutableSpan<T> data)
1333{
1334 const OffsetIndices points_by_curve = curves.points_by_curve();
1335 curve_selection.foreach_index(
1336 GrainSize(256), [&](const int curve_i) { data.slice(points_by_curve[curve_i]).reverse(); });
1337}
1338
1339template<typename T>
1341 const IndexMask &curve_selection,
1342 MutableSpan<T> data_a,
1343 MutableSpan<T> data_b)
1344{
1345 const OffsetIndices points_by_curve = curves.points_by_curve();
1346 curve_selection.foreach_index(GrainSize(256), [&](const int curve_i) {
1347 const IndexRange points = points_by_curve[curve_i];
1348 MutableSpan<T> a = data_a.slice(points);
1349 MutableSpan<T> b = data_b.slice(points);
1350 for (const int i : IndexRange(points.size() / 2)) {
1351 const int end_index = points.size() - 1 - i;
1352 std::swap(a[end_index], b[i]);
1353 std::swap(b[end_index], a[i]);
1354 }
1355 if (points.size() % 2) {
1356 const int64_t middle_index = points.size() / 2;
1357 std::swap(a[middle_index], b[middle_index]);
1358 }
1359 });
1360}
1361
1362void CurvesGeometry::reverse_curves(const IndexMask &curves_to_reverse)
1363{
1364 Set<StringRef> bezier_handle_names{{ATTR_HANDLE_POSITION_LEFT,
1368
1369 MutableAttributeAccessor attributes = this->attributes_for_write();
1370
1371 attributes.foreach_attribute([&](const AttributeIter &iter) {
1372 if (iter.domain != AttrDomain::Point) {
1373 return;
1374 }
1375 if (iter.data_type == CD_PROP_STRING) {
1376 return;
1377 }
1378 if (bezier_handle_names.contains(iter.name)) {
1379 return;
1380 }
1381
1382 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
1383 attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
1384 using T = decltype(dummy);
1385 reverse_curve_point_data<T>(*this, curves_to_reverse, attribute.span.typed<T>());
1386 });
1387 attribute.finish();
1388 return;
1389 });
1390
1391 /* In order to maintain the shape of Bezier curves, handle attributes must reverse, but also the
1392 * values for the left and right must swap. Use a utility to swap and reverse at the same time,
1393 * to avoid loading the attribute twice. Generally we can expect the right layer to exist when
1394 * the left does, but there's no need to count on it, so check for both attributes. */
1395
1396 if (attributes.contains(ATTR_HANDLE_POSITION_LEFT) &&
1397 attributes.contains(ATTR_HANDLE_POSITION_RIGHT))
1398 {
1400 curves_to_reverse,
1401 this->handle_positions_left_for_write(),
1402 this->handle_positions_right_for_write());
1403 }
1404 if (attributes.contains(ATTR_HANDLE_TYPE_LEFT) && attributes.contains(ATTR_HANDLE_TYPE_RIGHT)) {
1406 curves_to_reverse,
1407 this->handle_types_left_for_write(),
1408 this->handle_types_right_for_write());
1409 }
1410
1411 this->tag_topology_changed();
1412}
1413
1414void CurvesGeometry::remove_attributes_based_on_types()
1415{
1416 MutableAttributeAccessor attributes = this->attributes_for_write();
1417 if (!this->has_curve_with_type(CURVE_TYPE_BEZIER)) {
1418 attributes.remove(ATTR_HANDLE_TYPE_LEFT);
1419 attributes.remove(ATTR_HANDLE_TYPE_RIGHT);
1420 attributes.remove(ATTR_HANDLE_POSITION_LEFT);
1421 attributes.remove(ATTR_HANDLE_POSITION_RIGHT);
1422 }
1423 if (!this->has_curve_with_type(CURVE_TYPE_NURBS)) {
1424 attributes.remove(ATTR_NURBS_WEIGHT);
1425 attributes.remove(ATTR_NURBS_ORDER);
1426 attributes.remove(ATTR_NURBS_KNOTS_MODE);
1427 }
1428 if (!this->has_curve_with_type({CURVE_TYPE_BEZIER, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_NURBS})) {
1429 attributes.remove(ATTR_RESOLUTION);
1430 }
1431}
1432
1433CurvesGeometry curves_new_no_attributes(int point_num, int curve_num)
1434{
1435 CurvesGeometry curves(0, curve_num);
1436 curves.point_num = point_num;
1437 CustomData_free_layer_named(&curves.point_data, "position", 0);
1438 return curves;
1439}
1440
1443/* -------------------------------------------------------------------- */
1454template<typename T>
1456 const VArray<T> &old_values,
1457 MutableSpan<T> r_values)
1458{
1459 attribute_math::DefaultMixer<T> mixer(r_values);
1460
1461 const OffsetIndices points_by_curve = curves.points_by_curve();
1462 threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
1463 for (const int i_curve : range) {
1464 for (const int i_point : points_by_curve[i_curve]) {
1465 mixer.mix_in(i_curve, old_values[i_point]);
1466 }
1467 }
1468 mixer.finalize(range);
1469 });
1470}
1471
1479template<>
1481 const VArray<bool> &old_values,
1482 MutableSpan<bool> r_values)
1483{
1484 const OffsetIndices points_by_curve = curves.points_by_curve();
1485 r_values.fill(true);
1486 for (const int i_curve : IndexRange(curves.curves_num())) {
1487 for (const int i_point : points_by_curve[i_curve]) {
1488 if (!old_values[i_point]) {
1489 r_values[i_curve] = false;
1490 break;
1491 }
1492 }
1493 }
1494}
1495
1497 const GVArray &varray)
1498{
1499 GVArray new_varray;
1500 attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
1501 using T = decltype(dummy);
1502 if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
1503 Array<T> values(curves.curves_num());
1504 adapt_curve_domain_point_to_curve_impl<T>(curves, varray.typed<T>(), values);
1505 new_varray = VArray<T>::ForContainer(std::move(values));
1506 }
1507 });
1508 return new_varray;
1509}
1510
1518template<typename T>
1520 const VArray<T> &old_values,
1521 MutableSpan<T> r_values)
1522{
1523 const OffsetIndices points_by_curve = curves.points_by_curve();
1524 for (const int i_curve : IndexRange(curves.curves_num())) {
1525 r_values.slice(points_by_curve[i_curve]).fill(old_values[i_curve]);
1526 }
1527}
1528
1530 const GVArray &varray)
1531{
1532 GVArray new_varray;
1533 attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
1534 using T = decltype(dummy);
1535 Array<T> values(curves.points_num());
1536 adapt_curve_domain_curve_to_point_impl<T>(curves, varray.typed<T>(), values);
1537 new_varray = VArray<T>::ForContainer(std::move(values));
1538 });
1539 return new_varray;
1540}
1541
1542GVArray CurvesGeometry::adapt_domain(const GVArray &varray,
1543 const AttrDomain from,
1544 const AttrDomain to) const
1545{
1546 if (!varray) {
1547 return {};
1548 }
1549 if (varray.is_empty()) {
1550 return {};
1551 }
1552 if (from == to) {
1553 return varray;
1554 }
1555 if (varray.is_single()) {
1556 BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), value);
1557 varray.get_internal_single(value);
1558 return GVArray::ForSingle(varray.type(), this->attributes().domain_size(to), value);
1559 }
1560
1561 if (from == AttrDomain::Point && to == AttrDomain::Curve) {
1562 return adapt_curve_domain_point_to_curve(*this, varray);
1563 }
1564 if (from == AttrDomain::Curve && to == AttrDomain::Point) {
1565 return adapt_curve_domain_curve_to_point(*this, varray);
1566 }
1567
1569 return {};
1570}
1571
1574/* -------------------------------------------------------------------- */
1578void CurvesGeometry::blend_read(BlendDataReader &reader)
1579{
1580 this->runtime = MEM_new<blender::bke::CurvesGeometryRuntime>(__func__);
1581
1582 CustomData_blend_read(&reader, &this->point_data, this->point_num);
1583 CustomData_blend_read(&reader, &this->curve_data, this->curve_num);
1584
1585 if (this->curve_offsets) {
1587 &reader, &this->curve_offsets, [&]() {
1588 BLO_read_int32_array(&reader, this->curve_num + 1, &this->curve_offsets);
1589 return implicit_sharing::info_for_mem_free(this->curve_offsets);
1590 });
1591 }
1592
1593 BLO_read_struct_list(&reader, bDeformGroup, &this->vertex_group_names);
1594
1595 /* Recalculate curve type count cache that isn't saved in files. */
1596 this->update_curve_types();
1597}
1598
1599CurvesGeometry::BlendWriteData CurvesGeometry::blend_write_prepare()
1600{
1601 CurvesGeometry::BlendWriteData write_data;
1602 CustomData_blend_write_prepare(this->point_data, write_data.point_layers);
1603 CustomData_blend_write_prepare(this->curve_data, write_data.curve_layers);
1604 return write_data;
1605}
1606
1607void CurvesGeometry::blend_write(BlendWriter &writer,
1608 ID &id,
1609 const CurvesGeometry::BlendWriteData &write_data)
1610{
1612 &writer, &this->point_data, write_data.point_layers, this->point_num, CD_MASK_ALL, &id);
1614 &writer, &this->curve_data, write_data.curve_layers, this->curve_num, CD_MASK_ALL, &id);
1615
1616 if (this->curve_offsets) {
1618 &writer,
1619 this->curve_offsets,
1620 sizeof(int) * (this->curve_num + 1),
1621 this->runtime->curve_offsets_sharing_info,
1622 [&]() { BLO_write_int32_array(&writer, this->curve_num + 1, this->curve_offsets); });
1623 }
1624
1625 BKE_defbase_blend_write(&writer, &this->vertex_group_names);
1626}
1627
1630} // namespace blender::bke
Low-level operations for curves.
Low-level operations for curves.
CustomData interface, see also DNA_customdata_types.h.
void CustomData_blend_write_prepare(CustomData &data, blender::Vector< CustomDataLayer, 16 > &layers_to_write, const blender::Set< std::string > &skip_names={})
void CustomData_count_memory(const CustomData &data, int totelem, blender::MemoryCounter &memory)
void CustomData_realloc(CustomData *data, int old_size, int new_size, eCDAllocType alloctype=CD_CONSTRUCT)
void * CustomData_get_layer_named_for_write(CustomData *data, eCustomDataType type, blender::StringRef name, int totelem)
bool CustomData_free_layer_named(CustomData *data, blender::StringRef name, const int totelem)
const void * CustomData_get_layer(const CustomData *data, eCustomDataType type)
@ CD_SET_DEFAULT
const void * CustomData_get_layer_named(const CustomData *data, eCustomDataType type, blender::StringRef name)
void * CustomData_add_layer_named(CustomData *data, eCustomDataType type, eCDAllocType alloctype, int totelem, blender::StringRef name)
void CustomData_reset(CustomData *data)
void CustomData_blend_write(BlendWriter *writer, CustomData *data, blender::Span< CustomDataLayer > layers_to_write, int count, eCustomDataMask cddata_mask, ID *id)
void CustomData_init_from(const CustomData *source, CustomData *dest, eCustomDataMask mask, int totelem)
void CustomData_free(CustomData *data, int totelem)
void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count)
void * CustomData_get_layer_for_write(CustomData *data, eCustomDataType type, int totelem)
void * CustomData_add_layer(CustomData *data, eCustomDataType type, eCDAllocType alloctype, int totelem)
support for deformation groups and hooks.
void BKE_defbase_blend_write(BlendWriter *writer, const ListBase *defbase)
Definition deform.cc:1635
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:76
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name)
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
void void BLI_freelistN(struct ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:496
#define UNLIKELY(x)
void BLO_read_int32_array(BlendDataReader *reader, int array_size, int32_t **ptr_p)
Definition readfile.cc:4947
#define BLO_read_struct_list(reader, struct_name, list)
void BLO_write_shared(BlendWriter *writer, const void *data, size_t approximate_size_in_bytes, const blender::ImplicitSharingInfo *sharing_info, blender::FunctionRef< void()> write_fn)
const blender::ImplicitSharingInfo * BLO_read_shared(BlendDataReader *reader, T **data_ptr, blender::FunctionRef< const blender::ImplicitSharingInfo *()> read_fn)
CurveType
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
@ CURVE_TYPE_CATMULL_ROM
NormalMode
@ NORMAL_MODE_MINIMUM_TWIST
@ NORMAL_MODE_FREE
@ NORMAL_MODE_Z_UP
#define CURVE_TYPES_NUM
KnotsMode
#define CD_MASK_ALL
@ CD_MDEFORMVERT
@ CD_PROP_STRING
Read Guarded memory(de)allocation.
static const char * ATTR_POSITION
static void translate_positions(MutableSpan< float3 > positions, const float3 &translation)
void init()
Span< T > as_span() const
Definition BLI_array.hh:232
static const CPPType & get()
void copy_from(GSpan values)
GMutableSpan slice(const int64_t start, int64_t size) const
GSpan slice(const int64_t start, int64_t size) const
int64_t size() const
void get_internal_single(void *r_value) const
static GVArray ForSingle(const CPPType &type, int64_t size, const void *value)
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:619
constexpr void fill(const T &value) const
Definition BLI_span.hh:518
constexpr T & first() const
Definition BLI_span.hh:680
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:690
constexpr Span slice_safe(const int64_t start, const int64_t size) const
Definition BLI_span.hh:155
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr bool is_empty() const
Definition BLI_span.hh:261
static VArray ForSingle(T value, const int64_t size)
static VArray ForSpan(Span< T > values)
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
void reinitialize(const int64_t new_size)
Span< T > as_span() const
void clear_and_shrink()
SharedCache< Vector< curves::nurbs::BasisCache > > nurbs_basis_cache
Definition BKE_curves.hh:90
SharedCache< Vector< float > > evaluated_length_cache
SharedCache< Vector< float3 > > evaluated_tangent_cache
SharedCache< EvaluatedOffsets > evaluated_offsets_cache
Definition BKE_curves.hh:88
SharedCache< Vector< float3 > > evaluated_normal_cache
SharedCache< Bounds< float3 > > bounds_cache
SharedCache< Vector< float3 > > evaluated_position_cache
Definition BKE_curves.hh:96
const ImplicitSharingInfo * curve_offsets_sharing_info
Definition BKE_curves.hh:72
MutableSpan< int8_t > curve_types_for_write()
MutableAttributeAccessor attributes_for_write()
void resize(int points_num, int curves_num)
void fill_curve_types(CurveType type)
MutableSpan< int > offsets_for_write()
VArray< int8_t > curve_types() const
bool remove(const StringRef attribute_id)
bool add(const StringRef attribute_id, const AttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer)
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_index(Fn &&fn) const
void foreach_segment(Fn &&fn) const
void add_shared(const ImplicitSharingInfo *sharing_info, const FunctionRef< void(MemoryCounter &shared_memory)> count_fn)
local_group_size(16, 16) .push_constant(Type b
static bool is_cyclic(const Nurb *nu)
static void transform_positions(const Span< blender::float3 > src, const blender::float4x4 &transform, blender::MutableSpan< blender::float3 > dst)
IndexRange range
static char ** types
Definition makesdna.cc:71
void *(* MEM_malloc_arrayN)(size_t len, size_t size, const char *str)
Definition mallocn.cc:45
#define T
void copy_group_to_group(OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, GSpan src, GMutableSpan dst)
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
typename DefaultMixerStruct< T >::type DefaultMixer
static void normalize_span(MutableSpan< float3 > data)
static CustomData & domain_custom_data(CurvesGeometry &curves, const AttrDomain domain)
CurvesGeometry curves_copy_curve_selection(const CurvesGeometry &curves, const IndexMask &curves_to_copy, const AttributeFilter &attribute_filter)
CurvesGeometry curves_copy_point_selection(const CurvesGeometry &curves, const IndexMask &points_to_copy, const AttributeFilter &attribute_filter)
static const std::string ATTR_HANDLE_POSITION_RIGHT
static MutableSpan< T > get_mutable_attribute(CurvesGeometry &curves, const AttrDomain domain, const StringRef name, const T default_value=T())
static VArray< T > get_varray_attribute(const CurvesGeometry &curves, const AttrDomain domain, const StringRef name, const T default_value)
static const std::string ATTR_NURBS_WEIGHT
eCustomDataType cpp_type_to_custom_data_type(const CPPType &type)
static const std::string ATTR_HANDLE_TYPE_RIGHT
static const std::string ATTR_NURBS_KNOTS_MODE
static const std::string ATTR_RADIUS
static GVArray adapt_curve_domain_point_to_curve(const CurvesGeometry &curves, const GVArray &varray)
static const std::string ATTR_NORMAL_MODE
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
static const std::string ATTR_POSITION
static const std::string ATTR_HANDLE_TYPE_LEFT
CurvesGeometry curves_new_no_attributes(int point_num, int curve_num)
static const std::string ATTR_CYCLIC
static const std::string ATTR_SURFACE_UV_COORDINATE
std::array< int, CURVE_TYPES_NUM > calculate_type_counts(const VArray< int8_t > &types)
static void evaluate_generic_data_for_curve(const EvalData &eval_data, const int curve_index, const GSpan src, GMutableSpan dst)
static void transform_normals(MutableSpan< float3 > normals, const float4x4 &matrix)
static const std::string ATTR_RESOLUTION
static const std::string ATTR_TILT
static const std::string ATTR_CURVE_TYPE
static Span< T > get_span_attribute(const CurvesGeometry &curves, const AttrDomain domain, const StringRef name)
static const std::string ATTR_HANDLE_POSITION_LEFT
void build_offsets(MutableSpan< int > offsets, const CountFn &count_fn)
static void reverse_curve_point_data(const CurvesGeometry &curves, const IndexMask &curve_selection, MutableSpan< T > data)
void gather_attributes_group_to_group(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
static int domain_num(const CurvesGeometry &curves, const AttrDomain domain)
static const std::string ATTR_NURBS_ORDER
static void reverse_swap_curve_point_data(const CurvesGeometry &curves, const IndexMask &curve_selection, MutableSpan< T > data_a, MutableSpan< T > data_b)
static GVArray adapt_curve_domain_curve_to_point(const CurvesGeometry &curves, const GVArray &varray)
static void adapt_curve_domain_curve_to_point_impl(const CurvesGeometry &curves, const VArray< T > &old_values, MutableSpan< T > r_values)
static void rotate_directions_around_axes(MutableSpan< float3 > directions, const Span< float3 > axes, const Span< float > angles)
static void adapt_curve_domain_point_to_curve_impl(const CurvesGeometry &curves, const VArray< T > &old_values, MutableSpan< T > r_values)
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:46
void resize_trivial_array(T **data, const ImplicitSharingInfo **sharing_info, int64_t old_size, int64_t new_size)
const ImplicitSharingInfo * info_for_mem_free(void *data)
void free_shared_data(T **data, const ImplicitSharingInfo **sharing_info)
void make_trivial_data_mutable(T **data, const ImplicitSharingInfo **sharing_info, const int64_t size)
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
void accumulate_lengths(const Span< T > values, const bool cyclic, MutableSpan< float > lengths)
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
bool is_zero(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, float angle)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
bool almost_equal_relative(const VecBase< T, Size > &a, const VecBase< T, Size > &b, const T &epsilon_factor)
void build_reverse_map(OffsetIndices< int > offsets, MutableSpan< int > r_map)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
OffsetIndices< int > gather_selected_offsets(OffsetIndices< int > src_offsets, const IndexMask &selection, int start_offset, MutableSpan< int > dst_offsets)
void parallel_invoke(Functions &&...functions)
Definition BLI_task.hh:199
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:95
Value parallel_reduce(IndexRange range, int64_t grain_size, const Value &identity, const Function &function, const Reduction &reduction)
Definition BLI_task.hh:153
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
GPU_SHADER_INTERFACE_INFO(overlay_edit_curve_handle_iface, "vert").flat(Type pos vertex_in(1, Type::UINT, "data") .vertex_out(overlay_edit_curve_handle_iface) .geometry_layout(PrimitiveIn Frequency::GEOMETRY storage_buf(1, Qualifier::READ, "uint", "data[]", Frequency::GEOMETRY) .push_constant(Type Frequency::GEOMETRY selection[]
__int64 int64_t
Definition stdint.h:89
signed char int8_t
Definition stdint.h:75
CustomData point_data
CurvesGeometryRuntimeHandle * runtime
ListBase vertex_group_names
CustomData curve_data
Definition DNA_ID.h:413
const VArray< bool > & cyclic
const Span< float > nurbs_weights
const Span< int > all_bezier_evaluated_offsets
const VArray< int > & resolution
const Span< curves::nurbs::BasisCache > nurbs_basis_cache
const VArray< int8_t > & nurbs_orders
const VArray< int8_t > & types
const OffsetIndices< int > points_by_curve