Blender V5.0
fit_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array_utils.hh"
6#include "BLI_task.hh"
7
8#include "BKE_curves_utils.hh"
9#include "BKE_deform.hh"
10
11#include "GEO_fit_curves.hh"
12
13extern "C" {
14#include "curve_fit_nd.h"
15}
16
17namespace blender::geometry {
18
20 const IndexMask &curve_selection,
21 const VArray<float> &thresholds,
22 const VArray<bool> &corners,
23 const FitMethod method,
24 const bke::AttributeFilter &attribute_filter)
25{
26 if (curve_selection.is_empty()) {
27 return src_curves;
28 }
29
30 BLI_assert(thresholds.size() == src_curves.curves_num());
31 BLI_assert(corners.size() == src_curves.points_num());
32
33 const OffsetIndices src_points_by_curve = src_curves.offsets();
34 const Span<float3> src_positions = src_curves.positions();
35 const VArray<bool> src_cyclic = src_curves.cyclic();
36
39
40 IndexMaskMemory memory;
41 const IndexMask unselected_curves = curve_selection.complement(src_curves.curves_range(),
42 memory);
43
44 /* Write the new sizes to the dst_curve_sizes, they will be accumulated later to offsets. */
45 MutableSpan<int> dst_curve_sizes = dst_curves.offsets_for_write();
46 offset_indices::copy_group_sizes(src_points_by_curve, unselected_curves, dst_curve_sizes);
47 MutableSpan<int8_t> dst_curve_types = dst_curves.curve_types_for_write();
48
49 /* NOTE: These spans own the data from the curve fit C-API. */
50 Array<MutableSpan<float3>> cubic_array_per_curve(curve_selection.size());
51 Array<MutableSpan<int>> corner_indices_per_curve(curve_selection.size());
52 Array<MutableSpan<int>> original_indices_per_curve(curve_selection.size());
53
54 std::atomic<bool> success = false;
55 curve_selection.foreach_index(GrainSize(32), [&](const int64_t curve_i, const int64_t pos) {
56 const IndexRange points = src_points_by_curve[curve_i];
57 const Span<float3> curve_positions = src_positions.slice(points);
58 const bool is_cyclic = src_cyclic[curve_i];
59 const float epsilon = thresholds[curve_i];
60
61 /* Both curve fitting algorithms expect the first and last points for non-cyclic curves to be
62 * treated as if they were corners. */
63 const bool use_first_as_corner = !is_cyclic && !corners[points.first()];
64 const bool use_last_as_corner = !is_cyclic && !corners[points.last()];
65 Vector<int, 32> src_corners;
66 if (use_first_as_corner) {
67 src_corners.append(0);
68 }
69 if (points.size() > 2) {
70 for (const int i : IndexRange::from_begin_end(1, points.size() - 1)) {
71 if (corners[points[i]]) {
72 src_corners.append(i);
73 }
74 }
75 }
76 if (use_last_as_corner) {
77 src_corners.append(points.last());
78 }
79 const uint *src_corners_ptr = src_corners.is_empty() ?
80 nullptr :
81 reinterpret_cast<uint *>(src_corners.data());
82
83 const uint8_t flag = CURVE_FIT_CALC_HIGH_QUALIY | ((is_cyclic) ? CURVE_FIT_CALC_CYCLIC : 0);
84
85 float *cubic_array = nullptr;
86 uint32_t *orig_index_map = nullptr;
87 uint32_t cubic_array_size = 0;
88 uint32_t *corner_index_array = nullptr;
89 uint32_t corner_index_array_size = 0;
90 int error = 1;
91 if (method == FitMethod::Split) {
92 error = curve_fit_cubic_to_points_fl(curve_positions.cast<float>().data(),
93 curve_positions.size(),
94 3,
95 epsilon,
96 flag,
97 src_corners_ptr,
98 src_corners.size(),
99 &cubic_array,
100 &cubic_array_size,
101 &orig_index_map,
102 &corner_index_array,
103 &corner_index_array_size);
104 }
105 else if (method == FitMethod::Refit) {
106 error = curve_fit_cubic_to_points_refit_fl(curve_positions.cast<float>().data(),
107 curve_positions.size(),
108 3,
109 epsilon,
110 flag,
111 src_corners_ptr,
112 src_corners.size(),
113 /* Don't use automatic corner detection. */
114 FLT_MAX,
115 &cubic_array,
116 &cubic_array_size,
117 &orig_index_map,
118 &corner_index_array,
119 &corner_index_array_size);
120 }
121
122 if (error) {
123 /* Some error occurred. Fall back to using the input positions as the (poly) curve. */
124 dst_curve_sizes[curve_i] = points.size();
125 dst_curve_types[curve_i] = CURVE_TYPE_POLY;
126 return;
127 }
128
129 success.store(true, std::memory_order_relaxed);
130
131 const int dst_points_num = cubic_array_size;
132 BLI_assert(dst_points_num > 0);
133
134 dst_curve_sizes[curve_i] = dst_points_num;
135 dst_curve_types[curve_i] = CURVE_TYPE_BEZIER;
136
137 cubic_array_per_curve[pos] = MutableSpan<float3>(reinterpret_cast<float3 *>(cubic_array),
138 dst_points_num * 3);
139 corner_indices_per_curve[pos] = MutableSpan<int>(reinterpret_cast<int *>(corner_index_array),
140 corner_index_array_size);
141 original_indices_per_curve[pos] = MutableSpan<int>(reinterpret_cast<int *>(orig_index_map),
142 dst_points_num);
143 });
144
145 if (!success) {
146 /* None of the curve fittings succeeded. */
147 return src_curves;
148 }
149
151 dst_curve_sizes);
152 dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
153
154 const std::optional<Span<float3>> src_handles_left = src_curves.handle_positions_left();
155 const std::optional<Span<float3>> src_handles_right = src_curves.handle_positions_right();
156 const VArraySpan<int8_t> src_handle_types_left = src_curves.handle_types_left();
157 const VArraySpan<int8_t> src_handle_types_right = src_curves.handle_types_right();
158
159 MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
160 MutableSpan<float3> dst_handles_left = dst_curves.handle_positions_left_for_write();
161 MutableSpan<float3> dst_handles_right = dst_curves.handle_positions_right_for_write();
162 MutableSpan<int8_t> dst_handle_types_left = dst_curves.handle_types_left_for_write();
163 MutableSpan<int8_t> dst_handle_types_right = dst_curves.handle_types_right_for_write();
164
165 /* First handle the unselected curves. */
166 if (src_handles_left) {
167 array_utils::copy_group_to_group(src_points_by_curve,
168 dst_points_by_curve,
169 unselected_curves,
170 *src_handles_left,
171 dst_handles_left);
172 }
174 src_points_by_curve, dst_points_by_curve, unselected_curves, src_positions, dst_positions);
175 if (src_handles_right) {
176 array_utils::copy_group_to_group(src_points_by_curve,
177 dst_points_by_curve,
178 unselected_curves,
179 *src_handles_right,
180 dst_handles_right);
181 }
182 if (!src_handle_types_left.is_empty()) {
183 array_utils::copy_group_to_group(src_points_by_curve,
184 dst_points_by_curve,
185 unselected_curves,
186 src_handle_types_left,
187 dst_handle_types_left);
188 }
189 if (!src_handle_types_right.is_empty()) {
190 array_utils::copy_group_to_group(src_points_by_curve,
191 dst_points_by_curve,
192 unselected_curves,
193 src_handle_types_right,
194 dst_handle_types_right);
195 }
196
197 Array<int> old_by_new_map(dst_curves.points_num());
198 unselected_curves.foreach_index(GrainSize(1024), [&](const int64_t curve_i) {
199 const IndexRange src_points = src_points_by_curve[curve_i];
200 const IndexRange dst_points = dst_points_by_curve[curve_i];
201 array_utils::fill_index_range<int>(old_by_new_map.as_mutable_span().slice(dst_points),
202 src_points.start());
203 });
204
205 /* Now copy the data of the newly fitted curves. */
206 curve_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i, const int64_t pos) {
207 const IndexRange src_points = src_points_by_curve[curve_i];
208 const IndexRange dst_points = dst_points_by_curve[curve_i];
209 MutableSpan<float3> positions = dst_positions.slice(dst_points);
210 MutableSpan<int> old_by_new = old_by_new_map.as_mutable_span().slice(dst_points);
211
212 if (dst_curve_types[curve_i] == CURVE_TYPE_POLY) {
213 /* Handle the curves for which the curve fitting has failed. */
214 BLI_assert(src_points.size() == dst_points.size());
215 positions.copy_from(src_positions.slice(src_points));
216 dst_handles_left.slice(dst_points).copy_from(src_positions.slice(src_points));
217 dst_handles_right.slice(dst_points).copy_from(src_positions.slice(src_points));
218 dst_handle_types_left.slice(dst_points).fill(BEZIER_HANDLE_FREE);
219 dst_handle_types_right.slice(dst_points).fill(BEZIER_HANDLE_FREE);
220 array_utils::fill_index_range<int>(old_by_new, src_points.start());
221 return;
222 }
223
224 const Span<float3> cubic_array = cubic_array_per_curve[pos];
225 BLI_assert(dst_points.size() * 3 == cubic_array.size());
226 MutableSpan<float3> left_handles = dst_handles_left.slice(dst_points);
227 MutableSpan<float3> right_handles = dst_handles_right.slice(dst_points);
228 threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
229 for (const int i : range) {
230 const int index = i * 3;
231 positions[i] = cubic_array[index + 1];
232 left_handles[i] = cubic_array[index];
233 right_handles[i] = cubic_array[index + 2];
234 }
235 });
236
237 const Span<int> corner_indices = corner_indices_per_curve[pos];
238 dst_handle_types_left.slice(dst_points).fill(BEZIER_HANDLE_ALIGN);
239 dst_handle_types_right.slice(dst_points).fill(BEZIER_HANDLE_ALIGN);
240 dst_handle_types_left.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
241 dst_handle_types_right.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
242
243 const Span<int> original_indices = original_indices_per_curve[pos];
244 threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
245 for (const int i : range) {
246 old_by_new[i] = src_points[original_indices[i]];
247 }
248 });
249 });
250
251 dst_curves.update_curve_types();
252
253 bke::gather_attributes(
254 src_curves.attributes(),
255 bke::AttrDomain::Point,
256 bke::AttrDomain::Point,
257 bke::attribute_filter_with_skip_ref(
258 attribute_filter,
259 {"position", "handle_left", "handle_right", "handle_type_left", "handle_type_right"}),
260 old_by_new_map,
261 dst_curves.attributes_for_write());
262
263 /* Free all the data from the C-API. */
264 for (MutableSpan<float3> cubic_array : cubic_array_per_curve) {
265 free(cubic_array.data());
266 }
267 for (MutableSpan<int> corner_indices : corner_indices_per_curve) {
268 free(corner_indices.data());
269 }
270 for (MutableSpan<int> original_indices : original_indices_per_curve) {
271 free(original_indices.data());
272 }
273
274 return dst_curves;
275}
276
277} // namespace blender::geometry
Low-level operations for curves.
support for deformation groups and hooks.
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:73
#define BLI_assert(a)
Definition BLI_assert.h:46
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
@ BEZIER_HANDLE_FREE
@ BEZIER_HANDLE_ALIGN
long long int int64_t
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
constexpr int64_t first() const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
constexpr int64_t start() const
constexpr IndexRange index_range() 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 void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr void fill_indices(Span< IndexT > indices, const T &value) const
Definition BLI_span.hh:526
Span< NewT > constexpr cast() const
Definition BLI_span.hh:418
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr bool is_empty() const
Definition BLI_span.hh:260
int64_t size() const
void append(const T &value)
bool is_empty() const
VArray< int8_t > handle_types_left() const
MutableSpan< float3 > positions_for_write()
MutableSpan< int8_t > handle_types_right_for_write()
VArray< int8_t > handle_types_right() const
IndexRange curves_range() const
MutableSpan< int8_t > curve_types_for_write()
MutableSpan< float3 > handle_positions_left_for_write()
MutableSpan< float3 > handle_positions_right_for_write()
std::optional< Span< float3 > > handle_positions_left() const
Span< float3 > positions() const
std::optional< Span< float3 > > handle_positions_right() const
void resize(int points_num, int curves_num)
MutableSpan< int > offsets_for_write()
VArray< bool > cyclic() const
MutableSpan< int8_t > handle_types_left_for_write()
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_index(Fn &&fn) const
static bool is_cyclic(const Nurb *nu)
uint pos
static void error(const char *str)
void copy_group_to_group(OffsetIndices< int > src_offsets, OffsetIndices< int > dst_offsets, const IndexMask &selection, GSpan src, GMutableSpan dst)
void fill_index_range(MutableSpan< T > span, const T start=0)
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
bke::CurvesGeometry fit_poly_to_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &thresholds, const VArray< bool > &corners, FitMethod method, const bke::AttributeFilter &attribute_filter)
Definition fit_curves.cc:19
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
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
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
ListBase vertex_group_names
i
Definition text_draw.cc:230
uint8_t flag
Definition wm_window.cc:145