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