Blender V5.0
extend_curves.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
8
10#include "BLI_math_matrix.hh"
12#include "BLI_math_rotation.hh"
13#include "BLI_math_vector.hh"
14
15#include "BKE_attribute.hh"
16#include "BKE_curves.hh"
17#include "BKE_curves_utils.hh"
18#include "BKE_geometry_set.hh"
19
20#include "GEO_extend_curves.hh"
21
22namespace blender::geometry {
23
24static void extend_curve_straight(const float used_percent_length,
25 const float new_size,
26 const Span<int> start_points,
27 const Span<int> end_points,
28 const int curve,
29 const IndexRange new_curve,
30 const Span<float> use_start_lengths,
31 const Span<float> use_end_lengths,
32 MutableSpan<float3> positions)
33{
34 float overshoot_point_param = used_percent_length * (new_size - 1);
35 if (start_points[curve]) {
40 int index1 = math::floor(overshoot_point_param);
41 int index2 = math::ceil(overshoot_point_param);
42
43 /* When #overshoot_point_param is zero */
44 if (index2 == 0) {
45 index2 = 1;
46 }
47 float3 result = math::interpolate(positions[new_curve[index1]],
48 positions[new_curve[index2]],
49 fmodf(overshoot_point_param, 1.0f));
50 result -= positions[new_curve.first()];
52 result = positions[new_curve[1]] - positions[new_curve[0]];
53 }
54 positions[new_curve[0]] += result * (-use_start_lengths[curve] / math::length(result));
55 }
56
57 if (end_points[curve]) {
58 int index1 = new_size - 1 - math::floor(overshoot_point_param);
59 int index2 = new_size - 1 - math::ceil(overshoot_point_param);
60 float3 result = math::interpolate(positions[new_curve[index1]],
61 positions[new_curve[index2]],
62 fmodf(overshoot_point_param, 1.0f));
63 result -= positions[new_curve.last()];
65 result = positions[new_curve[new_size - 2]] - positions[new_curve[new_size - 1]];
66 }
67 positions[new_curve[new_size - 1]] += result *
68 (-use_end_lengths[curve] / math::length(result));
69 }
70}
71
72static void extend_curve_curved(const float used_percent_length,
73 const Span<int> start_points,
74 const Span<int> end_points,
75 const OffsetIndices<int> points_by_curve,
76 const int curve,
77 const IndexRange new_curve,
78 const Span<float> use_start_lengths,
79 const Span<float> use_end_lengths,
80 const float max_angle,
81 const float segment_influence,
82 const bool invert_curvature,
83 MutableSpan<float3> positions)
84{
85 /* Curvature calculation. */
86 const int first_old_index = start_points[curve] ? start_points[curve] : 0;
87 const int last_old_index = points_by_curve[curve].size() - 1 + first_old_index;
88 const int orig_totpoints = points_by_curve[curve].size();
89
90 /* The fractional amount of points to query when calculating the average curvature of the
91 * strokes. */
92 const float overshoot_parameter = used_percent_length * (orig_totpoints - 2);
93 int overshoot_pointcount = math::ceil(overshoot_parameter);
94 overshoot_pointcount = math::clamp(overshoot_pointcount, 1, orig_totpoints - 2);
95
96 /* Do for both sides without code duplication. */
97 float3 vec1, total_angle;
98 for (int k = 0; k < 2; k++) {
99 if ((k == 0 && !start_points[curve]) || (k == 1 && !end_points[curve])) {
100 continue;
101 }
102
103 const int start_i = k == 0 ? first_old_index : last_old_index;
104 const int dir_i = 1 - k * 2;
105
106 vec1 = positions[new_curve[start_i + dir_i]] - positions[new_curve[start_i]];
107 total_angle = float3({0, 0, 0});
108
109 float segment_length;
110 vec1 = math::normalize_and_get_length(vec1, segment_length);
111
112 float overshoot_length = 0.0f;
113
114 /* Accumulate rotation angle and length. */
115 int j = 0;
116 float3 no, vec2;
117 for (int i = start_i; j < overshoot_pointcount; i += dir_i, j++) {
118 /* Don't fully add last segment to get continuity in overshoot_fac. */
119 float fac = math::min(overshoot_parameter - j, 1.0f);
120
121 /* Read segments. */
122 vec2 = vec1;
123 vec1 = positions[new_curve[i + dir_i * 2]] - positions[new_curve[i + dir_i]];
124
125 float len;
127 float angle = math::angle_between(vec1, vec2).radian() * fac;
128
129 /* Add half of both adjacent legs of the current angle. */
130 const float added_len = (segment_length + len) * 0.5f * fac;
131 overshoot_length += added_len;
132 segment_length = len;
133
134 if (angle > max_angle) {
135 continue;
136 }
137 if (angle > M_PI * 0.995f) {
138 continue;
139 }
140
141 angle *= math::pow(added_len, segment_influence);
142
143 no = math::cross(vec1, vec2);
144 no = math::normalize(no) * angle;
145 total_angle += no;
146 }
147
148 if (UNLIKELY(overshoot_length == 0.0f)) {
149 /* Don't do a proper extension if the used points are all in the same position. */
150 continue;
151 }
152
153 vec1 = positions[new_curve[start_i]] - positions[new_curve[start_i + dir_i]];
154 /* In general curvature = 1/radius. For the case without the
155 * weights introduced by #segment_influence, the calculation is:
156 * `curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length` */
157 float curvature = normalize_v3(total_angle) / overshoot_length;
158 /* Compensate for the weights powf(added_len, segment_influence). */
159 curvature /= math::pow(overshoot_length / math::min(overshoot_parameter, float(j)),
160 segment_influence);
161 if (invert_curvature) {
162 curvature = -curvature;
163 }
164 const float dist = k == 0 ? use_start_lengths[curve] : use_end_lengths[curve];
165 const int extra_point_count = k == 0 ? start_points[curve] : end_points[curve];
166 const float angle_step = curvature * dist / extra_point_count;
167 float step_length = dist / extra_point_count;
168 if (math::abs(angle_step) > FLT_EPSILON) {
169 /* Make a direct step length from the assigned arc step length. */
170 step_length *= sin(angle_step * 0.5f) / (angle_step * 0.5f);
171 }
172 else {
173 total_angle = float3({0, 0, 0});
174 }
175 float prev_length;
176 vec1 = math::normalize_and_get_length(vec1, prev_length);
177 vec1 *= step_length;
178
179 /* Build rotation matrix here to get best performance. */
180 math::AxisAngle axis_base(total_angle, angle_step);
183
184 /* Rotate the starting direction to account for change in edge lengths. */
185 math::AxisAngle step_base(total_angle,
186 math::max(0.0f, 1.0f - math::abs(segment_influence)) *
187 (curvature * prev_length - angle_step) / 2.0f);
188 q = math::to_quaternion(step_base);
189 vec1 = math::transform_point(q, vec1);
190
191 /* Now iteratively accumulate the segments with a rotating added direction. */
192 for (int i = start_i - dir_i, j = 0; j < extra_point_count; i -= dir_i, j++) {
193 vec1 = rot * vec1;
194 positions[new_curve[i]] = vec1 + positions[new_curve[i + dir_i]];
195 }
196 }
197}
198
200 const IndexMask &selection,
201 const VArray<float> &start_lengths,
202 const VArray<float> &end_lengths,
203 const float overshoot_fac,
204 const bool follow_curvature,
205 const float point_density,
206 const float segment_influence,
207 const float max_angle,
208 const bool invert_curvature,
209 const GeometryNodeCurveSampleMode sample_mode,
210 const bke::AttributeFilter &attribute_filter)
211{
212 if (src_curves.points_num() < 2) {
213 return src_curves;
214 }
215 if (selection.is_empty()) {
216 return src_curves;
217 }
218
219 const int src_curves_num = src_curves.curves_num();
220
221 /* Extra point count at the start/end of extended strokes. For straight extension, or for strokes
222 * with only 2 points (thus unable to curve), the value of their respective index should be set
223 * to 1 to allow #extend_curves_straight() to identify strokes to work on. */
224 Array<int> start_points(src_curves_num, 0);
225 Array<int> end_points(src_curves_num, 0);
226
227 Array<float> use_start_lengths(src_curves_num);
228 Array<float> use_end_lengths(src_curves_num);
229
230 const OffsetIndices<int> points_by_curve = src_curves.points_by_curve();
231
232 src_curves.ensure_evaluated_lengths();
233 selection.foreach_index([&](const int curve) {
234 use_start_lengths[curve] = start_lengths[curve];
235 use_end_lengths[curve] = end_lengths[curve];
236 if (sample_mode == GEO_NODE_CURVE_SAMPLE_FACTOR) {
237 float total_length = src_curves.evaluated_length_total_for_curve(curve, false);
238 use_start_lengths[curve] *= total_length;
239 use_end_lengths[curve] *= total_length;
240 }
241 });
242
243 bke::CurvesGeometry dst_curves;
244
245 if (!follow_curvature) {
246 /* Use the old curves when extending straight when no new points are added. */
247 dst_curves = std::move(src_curves);
248 /* Enable affected curves for #extend_curves_straight(). */
249 index_mask::masked_fill<int>(start_points, 1, selection);
250 index_mask::masked_fill<int>(end_points, 1, selection);
251 }
252 else {
253 /* Copy only curves domain since we are not changing the number of curves here. */
254 dst_curves = bke::curves::copy_only_curve_domain(src_curves);
255 MutableSpan<int> dst_points_by_curve = dst_curves.offsets_for_write();
257 src_curves.points_by_curve(), src_curves.curves_range(), dst_points_by_curve);
258 /* Count how many points we need. */
259 selection.foreach_index([&](const int curve) {
260 const int point_count = dst_points_by_curve[curve];
261 if (point_count <= 2) {
262 /* Can't make a curve, set start/end points to 1 to allow straight extension. */
263 start_points[curve] = 1;
264 end_points[curve] = 1;
265 return;
266 }
267
268 const int count_start = (use_start_lengths[curve] > 0) ?
269 math::ceil(use_start_lengths[curve] * point_density) :
270 0;
271 const int count_end = (use_end_lengths[curve] > 0) ?
272 math::ceil(use_end_lengths[curve] * point_density) :
273 0;
274 dst_points_by_curve[curve] += count_start;
275 dst_points_by_curve[curve] += count_end;
276 start_points[curve] = count_start;
277 end_points[curve] = count_end;
278 });
279
280 OffsetIndices dst_indices = offset_indices::accumulate_counts_to_offsets(dst_points_by_curve);
281 int target_point_count = dst_points_by_curve.last();
282
283 /* Make destination to source map for points. */
284 Array<int> dst_to_src_point(target_point_count);
285 for (const int curve : src_curves.curves_range()) {
286 const int point_count = points_by_curve[curve].size();
287 int local_front = 0;
288 MutableSpan<int> new_points_by_curve = dst_to_src_point.as_mutable_span().slice(
289 dst_indices[curve]);
290 if (point_count <= 2) {
291 for (const int point_i : new_points_by_curve.index_range()) {
292 new_points_by_curve[point_i] = points_by_curve[curve][point_i];
293 }
294 continue;
295 }
296 if (follow_curvature) {
297 MutableSpan<int> starts = new_points_by_curve.slice(0, start_points[curve]);
298 starts.fill(points_by_curve[curve].first());
299 local_front = start_points[curve];
300 MutableSpan<int> ends = new_points_by_curve.slice(
301 new_points_by_curve.size() - end_points[curve], end_points[curve]);
302 ends.fill(points_by_curve[curve].last());
303 }
304 MutableSpan<int> original_points = new_points_by_curve.slice(local_front, point_count);
305 for (const int point_i : original_points.index_range()) {
306 original_points[point_i] = points_by_curve[curve][point_i];
307 }
308 }
309
310 dst_curves.resize(target_point_count, src_curves_num);
311
312 const bke::AttributeAccessor src_attributes = src_curves.attributes();
313 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
314
315 /* Transfer point attributes. */
316 gather_attributes(src_attributes,
319 attribute_filter,
320 dst_to_src_point,
321 dst_attributes);
322 }
323
324 MutableSpan<float3> positions = dst_curves.positions_for_write();
325
326 const OffsetIndices<int> new_points_by_curve = dst_curves.points_by_curve();
327 threading::parallel_for(dst_curves.curves_range(), 512, [&](const IndexRange curves_range) {
328 for (const int curve : curves_range) {
329 const IndexRange new_curve = new_points_by_curve[curve];
330 int new_size = new_curve.size();
331
332 /* #used_percent_length must always be finite and non-zero. */
333 const float used_percent_length = math::clamp(
334 isfinite(overshoot_fac) ? overshoot_fac : 0.1f, 1e-4f, 1.0f);
335
336 if (!follow_curvature || new_size == 2) {
337 extend_curve_straight(used_percent_length,
338 new_size,
339 start_points.as_span(),
340 end_points.as_span(),
341 curve,
342 new_curve,
343 use_start_lengths.as_span(),
344 use_end_lengths.as_span(),
345 positions);
346 }
347 else if (start_points[curve] > 0 || end_points[curve] > 0) {
348 extend_curve_curved(used_percent_length,
349 start_points.as_span(),
350 end_points.as_span(),
351 points_by_curve,
352 curve,
353 new_curve,
354 use_start_lengths.as_span(),
355 use_end_lengths.as_span(),
356 max_angle,
357 segment_influence,
358 invert_curvature,
359 positions);
360 }
361 }
362 });
363 if (src_curves.nurbs_has_custom_knots()) {
365 dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
366 }
367 return dst_curves;
368}
369
370} // namespace blender::geometry
Low-level operations for curves.
Low-level operations for curves.
#define M_PI
MINLINE float normalize_v3(float n[3])
#define UNLIKELY(x)
@ NURBS_KNOT_MODE_NORMAL
GeometryNodeCurveSampleMode
@ GEO_NODE_CURVE_SAMPLE_FACTOR
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
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
Definition BLI_span.hh:493
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 IndexRange index_range() const
Definition BLI_span.hh:670
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
void resize(int points_num, int curves_num)
AttributeAccessor attributes() const
float evaluated_length_total_for_curve(int curve_index, bool cyclic) const
MutableSpan< int > offsets_for_write()
void foreach_index(Fn &&fn) const
#define fmodf(x, y)
#define rot(x, k)
#define sin
void update_custom_knot_modes(const IndexMask &mask, const KnotsMode mode_for_regular, const KnotsMode mode_for_cyclic, bke::CurvesGeometry &curves)
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
static void extend_curve_straight(const float used_percent_length, const float new_size, const Span< int > start_points, const Span< int > end_points, const int curve, const IndexRange new_curve, const Span< float > use_start_lengths, const Span< float > use_end_lengths, MutableSpan< float3 > positions)
static void extend_curve_curved(const float used_percent_length, const Span< int > start_points, const Span< int > end_points, const OffsetIndices< int > points_by_curve, const int curve, const IndexRange new_curve, const Span< float > use_start_lengths, const Span< float > use_end_lengths, const float max_angle, const float segment_influence, const bool invert_curvature, MutableSpan< float3 > positions)
bke::CurvesGeometry extend_curves(bke::CurvesGeometry &src_curves, const IndexMask &selection, const VArray< float > &start_lengths, const VArray< float > &end_lengths, float overshoot_fac, bool follow_curvature, float point_density, float segment_influence, float max_angle, bool invert_curvature, GeometryNodeCurveSampleMode sample_mode, const bke::AttributeFilter &attribute_filter)
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
QuaternionBase< float > Quaternion
T pow(const T &x, const T &power)
T clamp(const T &a, const T &min, const T &max)
QuaternionBase< T > to_quaternion(const AxisAngleBase< T, AngleT > &axis_angle)
T floor(const T &a)
T length(const VecBase< T, Size > &a)
AngleRadianBase< T > angle_between(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
AxisAngleBase< float, AngleRadianBase< float > > AxisAngle
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T min(const T &a, const T &b)
bool is_zero(const T &a)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
T interpolate(const T &a, const T &b, const FactorT &t)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T ceil(const T &a)
T max(const T &a, const T &b)
T abs(const T &a)
MatT from_rotation(const RotationT &rotation)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
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
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
i
Definition text_draw.cc:230
uint len