Blender V4.3
smooth_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "BKE_curves.hh"
7#include "BKE_curves_utils.hh"
8
9#include "BLI_array.hh"
10#include "BLI_generic_span.hh"
11#include "BLI_index_mask.hh"
12#include "BLI_index_range.hh"
13#include "BLI_vector.hh"
14#include "BLI_virtual_array.hh"
15
16#include "GEO_smooth_curves.hh"
17
18namespace blender::geometry {
19
20template<typename T>
21static void gaussian_blur_1D(const Span<T> src,
22 const int iterations,
23 const VArray<float> &influence_by_point,
24 const bool smooth_ends,
25 const bool keep_shape,
26 const bool is_cyclic,
28{
54 BLI_assert(!src.is_empty());
55 BLI_assert(src.size() == dst.size());
56
57 /* Avoid computation if the there is just one point. */
58 if (src.size() == 1) {
59 return;
60 }
61
62 /* Weight Initialization. */
63 const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
64 (iterations * iterations) / 4 + 2 * iterations + 12;
65 double w = keep_shape ? 2.0 : 1.0;
66 double w2 = keep_shape ?
67 (1.0 / M_SQRT3) * exp((2 * iterations * iterations) / double(n_half * 3)) :
68 0.0;
69 Array<double> total_weight(src.size(), 0.0);
70
71 const int64_t total_points = src.size();
72 const int64_t last_pt = total_points - 1;
73
74 auto is_end_and_fixed = [smooth_ends, is_cyclic, last_pt](int index) {
75 return !smooth_ends && !is_cyclic && ELEM(index, 0, last_pt);
76 };
77
78 /* Initialize at zero. */
79 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
80 for (const int64_t index : range) {
81 if (!is_end_and_fixed(index)) {
82 dst[index] = T(0);
83 }
84 }
85 });
86
87 /* Compute weights. */
88 for (const int64_t step : IndexRange(iterations)) {
89 const int64_t offset = iterations - step;
90 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
91 for (const int64_t index : range) {
92 /* Filter out endpoints. */
93 if (is_end_and_fixed(index)) {
94 continue;
95 }
96
97 double w_before = w - w2;
98 double w_after = w - w2;
99
100 /* Compute the neighboring points. */
101 int64_t before = index - offset;
102 int64_t after = index + offset;
103 if (is_cyclic) {
104 before = (before % total_points + total_points) % total_points;
105 after = after % total_points;
106 }
107 else {
108 if (!smooth_ends && (before < 0)) {
109 w_before *= -before / float(index);
110 }
111 before = math::max(before, int64_t(0));
112
113 if (!smooth_ends && (after > last_pt)) {
114 w_after *= (after - (total_points - 1)) / float(total_points - 1 - index);
115 }
116 after = math::min(after, last_pt);
117 }
118
119 /* Add the neighboring values. */
120 const T bval = src[before];
121 const T aval = src[after];
122 const T cval = src[index];
123
124 dst[index] += (bval - cval) * w_before;
125 dst[index] += (aval - cval) * w_after;
126
127 /* Update the weight values. */
128 total_weight[index] += w_before;
129 total_weight[index] += w_after;
130 }
131 });
132
133 w *= (n_half + offset) / double(n_half + 1 - offset);
134 w2 *= (n_half * 3 + offset) / double(n_half * 3 + 1 - offset);
135 }
136
137 /* Normalize the weights. */
138 devirtualize_varray(influence_by_point, [&](const auto influence_by_point) {
139 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
140 for (const int64_t index : range) {
141 if (!is_end_and_fixed(index)) {
142 total_weight[index] += w - w2;
143 dst[index] = src[index] + influence_by_point[index] * dst[index] / total_weight[index];
144 }
145 }
146 });
147 });
148}
149
150void gaussian_blur_1D(const GSpan src,
151 const int iterations,
152 const VArray<float> &influence_by_point,
153 const bool smooth_ends,
154 const bool keep_shape,
155 const bool is_cyclic,
156 GMutableSpan dst)
157{
158 bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
159 using T = decltype(dummy);
160 /* Only allow smoothing of float, float2, or float3. */
161 /* Reduces unnecessary code generation. */
162 if constexpr (std::is_same_v<T, float> || std::is_same_v<T, float2> ||
163 std::is_same_v<T, float3>)
164 {
165 gaussian_blur_1D(src.typed<T>(),
166 iterations,
167 influence_by_point,
168 smooth_ends,
169 keep_shape,
170 is_cyclic,
171 dst.typed<T>());
172 }
173 });
174}
175
176void smooth_curve_attribute(const IndexMask &curves_to_smooth,
177 const OffsetIndices<int> points_by_curve,
178 const VArray<bool> &point_selection,
179 const VArray<bool> &cyclic,
180 const int iterations,
181 const VArray<float> &influence_by_point,
182 const bool smooth_ends,
183 const bool keep_shape,
184 GMutableSpan attribute_data)
185{
186 VArraySpan<float> influences(influence_by_point);
187
188 curves_to_smooth.foreach_index(GrainSize(512), [&](const int curve_i) {
189 Vector<std::byte> orig_data;
190 const IndexRange points = points_by_curve[curve_i];
191
192 IndexMaskMemory memory;
193 const IndexMask selection_mask = IndexMask::from_bools(points, point_selection, memory);
194 if (selection_mask.is_empty()) {
195 return;
196 }
197
198 selection_mask.foreach_range([&](const IndexRange range) {
199 GMutableSpan dst_data = attribute_data.slice(range);
200
201 orig_data.resize(dst_data.size_in_bytes());
202 dst_data.type().copy_assign_n(dst_data.data(), orig_data.data(), range.size());
203 const GSpan src_data(dst_data.type(), orig_data.data(), range.size());
204
205 gaussian_blur_1D(src_data,
206 iterations,
207 VArray<float>::ForSpan(influences.slice(range)),
208 smooth_ends,
209 keep_shape,
210 cyclic[curve_i],
211 dst_data);
212 });
213 });
214}
215
216void smooth_curve_attribute(const IndexMask &curves_to_smooth,
217 const OffsetIndices<int> points_by_curve,
218 const VArray<bool> &point_selection,
219 const VArray<bool> &cyclic,
220 const int iterations,
221 const float influence,
222 const bool smooth_ends,
223 const bool keep_shape,
224 GMutableSpan attribute_data)
225{
226 smooth_curve_attribute(curves_to_smooth,
227 points_by_curve,
228 point_selection,
229 cyclic,
230 iterations,
231 VArray<float>::ForSingle(influence, points_by_curve.total_size()),
232 smooth_ends,
233 keep_shape,
234 attribute_data);
235}
236
238 const IndexMask &curves_to_smooth,
239 const int iterations,
240 const VArray<float> &influence_by_point,
241 const bool smooth_ends,
242 const bool keep_shape)
243{
244 bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
245 const OffsetIndices points_by_curve = curves.points_by_curve();
246 const VArray<bool> cyclic = curves.cyclic();
247 const VArray<bool> point_selection = *curves.attributes().lookup_or_default<bool>(
248 ".selection", bke::AttrDomain::Point, true);
249 if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
250 bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
251 smooth_curve_attribute(curves_to_smooth,
252 points_by_curve,
253 point_selection,
254 cyclic,
255 iterations,
256 influence_by_point,
257 smooth_ends,
258 keep_shape,
259 positions.span);
260 positions.finish();
261 }
262 else {
263 IndexMaskMemory memory;
264 const IndexMask bezier_curves_to_smooth = curves.indices_for_curve_type(
265 CURVE_TYPE_BEZIER, curves_to_smooth, memory);
266
267 /* Write the positions of the handles and the control points into a flat array.
268 * This will smooth the handle positions together with the control point positions, because the
269 * smoothing algorithm takes neighboring values to apply the gaussian smoothing to. */
270 Array<float3> all_positions = bke::curves::bezier::retrieve_all_positions(
271 curves, bezier_curves_to_smooth);
272
273 VArraySpan<float> influences(influence_by_point);
274 bezier_curves_to_smooth.foreach_index(GrainSize(512), [&](const int curve) {
275 Vector<float3> orig_data;
276 const IndexRange points = points_by_curve[curve];
277
278 IndexMaskMemory memory;
279 const IndexMask selection_mask = IndexMask::from_bools(points, point_selection, memory);
280 if (selection_mask.is_empty()) {
281 return;
282 }
283
284 selection_mask.foreach_range([&](const IndexRange range) {
285 IndexRange positions_range(range.start() * 3, range.size() * 3);
286 /* Ignore the left handle of the first point and the right handle of the last point. */
287 if (!smooth_ends && !cyclic[curve]) {
288 positions_range = positions_range.drop_front(1).drop_back(1);
289 }
290 MutableSpan<float3> dst_data = all_positions.as_mutable_span().slice(positions_range);
291
292 orig_data.resize(dst_data.size());
293 orig_data.as_mutable_span().copy_from(dst_data);
294
295 /* The influence is mapped from handle+control point index to only control point index. */
296 Array<float> point_influences(positions_range.size());
297 if (!smooth_ends && !cyclic[curve]) {
298 threading::parallel_for(
299 positions_range.index_range(), 4096, [&](const IndexRange influences_range) {
300 for (const int index : influences_range) {
301 /* Account for the left handle of the first
302 * point being ignored. */
303 point_influences[index] = influences.slice(range)[(index + 1) / 3];
304 }
305 });
306 }
307 else {
308 threading::parallel_for(
309 positions_range.index_range(), 4096, [&](const IndexRange influences_range) {
310 for (const int index : influences_range) {
311 point_influences[index] = influences.slice(range)[index / 3];
312 }
313 });
314 }
315
316 gaussian_blur_1D(orig_data.as_span(),
317 iterations,
318 VArray<float>::ForSpan(point_influences.as_span()),
319 smooth_ends,
320 keep_shape,
321 cyclic[curve],
322 dst_data);
323 });
324 });
325
326 /* Copy the resulting values from the flat array back into the three position attributes for
327 * the left and right handles as well as the control points. */
328 bke::curves::bezier::write_all_positions(curves, bezier_curves_to_smooth, all_positions);
329
330 /* Smooth the other curve positions. */
331 const IndexMask other_curves_to_smooth = bezier_curves_to_smooth.complement(
332 curves.curves_range(), memory);
333 if (!other_curves_to_smooth.is_empty()) {
334 bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
335 smooth_curve_attribute(other_curves_to_smooth,
336 points_by_curve,
337 point_selection,
338 cyclic,
339 iterations,
340 influence_by_point,
341 smooth_ends,
342 keep_shape,
343 positions.span);
344 positions.finish();
345 }
346
347 curves.calculate_bezier_auto_handles();
348 }
349
350 curves.tag_positions_changed();
351}
352
354 const IndexMask &curves_to_smooth,
355 const int iterations,
356 const float influence,
357 const bool smooth_ends,
358 const bool keep_shape)
359{
361 curves_to_smooth,
362 iterations,
363 VArray<float>::ForSingle(influence, curves.points_num()),
364 smooth_ends,
365 keep_shape);
366}
367
368} // namespace blender::geometry
Low-level operations for curves.
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:50
#define M_SQRT3
#define ELEM(...)
typedef double(DMatrix)[4][4]
@ CURVE_TYPE_BEZIER
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
void copy_assign_n(const void *src, void *dst, int64_t n) const
int64_t size_in_bytes() const
GMutableSpan slice(const int64_t start, int64_t size) const
const CPPType & type() const
const CPPType & type() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t size() const
constexpr IndexRange index_range() const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr bool is_empty() const
Definition BLI_span.hh:261
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void foreach_range(Fn &&fn) const
void foreach_index(Fn &&fn) const
static bool is_cyclic(const Nurb *nu)
ccl_device_inline float3 exp(float3 v)
void smooth_curve_attribute(const IndexMask &curves_to_smooth, const OffsetIndices< int > points_by_curve, const VArray< bool > &point_selection, const VArray< bool > &cyclic, int iterations, float influence, bool smooth_ends, bool keep_shape, GMutableSpan attribute_data)
void smooth_curve_positions(bke::CurvesGeometry &curves, const IndexMask &curves_to_smooth, int iterations, const VArray< float > &influence_by_point, bool smooth_ends, bool keep_shape)
void gaussian_blur_1D(const GSpan src, int iterations, const VArray< float > &influence_by_point, const bool smooth_ends, const bool keep_shape, const bool is_cyclic, GMutableSpan dst)
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
void devirtualize_varray(const VArray< T > &varray, const Func &func, bool enable=true)
__int64 int64_t
Definition stdint.h:89