Blender V4.5
grease_pencil_geom.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
9#include <algorithm>
10
11#include "BLI_array_utils.hh"
13#include "BLI_kdopbvh.hh"
14#include "BLI_kdtree.h"
15#include "BLI_math_vector.hh"
16#include "BLI_offset_indices.hh"
17#include "BLI_rect.h"
18#include "BLI_stack.hh"
19#include "BLI_task.hh"
20
21#include "BKE_attribute.hh"
22#include "BKE_curves.hh"
23#include "BKE_curves_utils.hh"
24#include "BKE_grease_pencil.hh"
25
26#include "DNA_curves_types.h"
28
29#include "ED_curves.hh"
30#include "ED_grease_pencil.hh"
31#include "ED_view3d.hh"
32
33#include "GEO_merge_curves.hh"
34
35extern "C" {
36#include "curve_fit_nd.h"
37}
38
40
42 const IndexRange range,
43 const float epsilon,
44 const FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
45 MutableSpan<bool> points_to_delete)
46{
47 /* Mark all points to not be removed. */
48 points_to_delete.slice(range).fill(false);
49 int64_t total_points_to_remove = 0;
50
52 stack.push(range);
53 while (!stack.is_empty()) {
54 const IndexRange sub_range = stack.pop();
55 /* Skip ranges with less than 3 points. All points are kept. */
56 if (sub_range.size() < 3) {
57 continue;
58 }
59 const IndexRange inside_range = sub_range.drop_front(1).drop_back(1);
60 /* Compute the maximum distance and the corresponding index. */
61 float max_dist = -1.0f;
62 int max_index = -1;
63 for (const int64_t index : inside_range) {
64 const float dist = dist_function(sub_range.first(), sub_range.last(), index);
65 if (dist > max_dist) {
66 max_dist = dist;
67 max_index = index - sub_range.first();
68 }
69 }
70
71 if (max_dist > epsilon) {
72 /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat
73 * the search on the left & right side. */
74 stack.push(sub_range.slice(0, max_index + 1));
75 stack.push(sub_range.slice(max_index, sub_range.size() - max_index));
76 }
77 else {
78 /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */
79 total_points_to_remove += inside_range.size();
80 points_to_delete.slice(inside_range).fill(true);
81 }
82 }
83 return total_points_to_remove;
84}
85
87 const float error_threshold,
88 const IndexMask &corner_mask)
89{
90 if (points.is_empty()) {
91 return {};
92 }
93 double total_length = 0.0;
94 for (const int point_i : points.index_range().drop_front(1)) {
95 total_length += math::distance(points[point_i - 1], points[point_i]);
96 }
97 /* Just return a dot. */
98 if (total_length < 1e-8) {
99 return Array<float2>({points[0], points[0], points[0]});
100 }
101
102 Array<int32_t> indices(corner_mask.size());
103 corner_mask.to_indices(indices.as_mutable_span());
104 uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast<uint *>(indices.data());
105
106 float *r_cubic_array;
107 uint r_cubic_array_len;
108 int error = curve_fit_cubic_to_points_fl(*points.data(),
109 points.size(),
110 2,
111 error_threshold,
112 CURVE_FIT_CALC_HIGH_QUALIY,
113 indicies_ptr,
114 indices.size(),
115 &r_cubic_array,
116 &r_cubic_array_len,
117 nullptr,
118 nullptr,
119 nullptr);
120
121 if (error != 0) {
122 /* Some error occurred. Return. */
123 return {};
124 }
125
126 if (r_cubic_array == nullptr) {
127 return {};
128 }
129
130 Span<float2> r_cubic_array_span(reinterpret_cast<float2 *>(r_cubic_array),
131 r_cubic_array_len * 3);
132 Array<float2> curve_positions(r_cubic_array_span);
133 /* Free the c-style array. */
134 free(r_cubic_array);
135 return curve_positions;
136}
137
139 const float radius_min,
140 const float radius_max,
141 const int samples_max,
142 const float angle_threshold,
143 IndexMaskMemory &memory)
144{
145 if (points.is_empty()) {
146 return {};
147 }
148 if (points.size() == 1) {
149 return IndexMask::from_indices<int>({0}, memory);
150 }
151 uint *r_corners;
152 uint r_corner_len;
153 const int error = curve_fit_corners_detect_fl(*points.data(),
154 points.size(),
156 radius_min,
157 radius_max,
158 samples_max,
159 angle_threshold,
160 &r_corners,
161 &r_corner_len);
162 if (error != 0) {
163 /* Error occurred, return. */
164 return IndexMask();
165 }
166
167 if (r_corners == nullptr) {
168 return IndexMask();
169 }
170
171 BLI_assert(samples_max < std::numeric_limits<int>::max());
172 Span<int> indices(reinterpret_cast<int *>(r_corners), r_corner_len);
173 const IndexMask corner_mask = IndexMask::from_indices<int>(indices, memory);
174 /* Free the c-style array. */
175 free(r_corners);
176 return corner_mask;
177}
178
180 const Span<float> distances,
181 const IndexMask &selection,
182 const float merge_distance,
183 MutableSpan<int> r_merge_indices)
184{
185 /* We use a KDTree_1d here, because we can only merge neighboring points in the curves. */
186 KDTree_1d *tree = BLI_kdtree_1d_new(selection.size());
187 /* The selection is an IndexMask of the points just in this curve. */
188 selection.foreach_index_optimized<int64_t>([&](const int64_t i, const int64_t pos) {
189 BLI_kdtree_1d_insert(tree, pos, &distances[i - points.first()]);
190 });
191 BLI_kdtree_1d_balance(tree);
192
193 Array<int> selection_merge_indices(selection.size(), -1);
194 const int duplicate_count = BLI_kdtree_1d_calc_duplicates_fast(
195 tree, merge_distance, false, selection_merge_indices.data());
196 BLI_kdtree_1d_free(tree);
197
198 array_utils::fill_index_range<int>(r_merge_indices);
199
200 selection.foreach_index([&](const int src_index, const int pos) {
201 const int merge_index = selection_merge_indices[pos];
202 if (merge_index != -1) {
203 const int src_merge_index = selection[merge_index] - points.first();
204 r_merge_indices[src_index - points.first()] = src_merge_index;
205 }
206 });
207
208 return duplicate_count;
209}
210
212 const float merge_distance,
213 const IndexMask &selection,
214 const bke::AttributeFilter &attribute_filter)
215{
216 /* NOTE: The code here is an adapted version of #blender::geometry::point_merge_by_distance. */
217
218 const int src_point_size = src_curves.points_num();
219 if (src_point_size == 0) {
220 return {};
221 }
222 const OffsetIndices<int> points_by_curve = src_curves.points_by_curve();
223 const VArray<bool> cyclic = src_curves.cyclic();
224 src_curves.ensure_evaluated_lengths();
225
227 MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
228
229 std::atomic<int> total_duplicate_count = 0;
230 Array<Array<int>> merge_indices_per_curve(src_curves.curves_num());
231 threading::parallel_for(src_curves.curves_range(), 512, [&](const IndexRange range) {
232 for (const int curve_i : range) {
233 const IndexRange points = points_by_curve[curve_i];
234 merge_indices_per_curve[curve_i].reinitialize(points.size());
235
236 Array<float> distances_along_curve(points.size() + int(cyclic[curve_i]));
237 distances_along_curve.first() = 0.0f;
238 const Span<float> lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]);
239 distances_along_curve.as_mutable_span().drop_front(1).copy_from(lengths);
240
241 MutableSpan<int> merge_indices = merge_indices_per_curve[curve_i].as_mutable_span();
242 array_utils::fill_index_range<int>(merge_indices);
243
244 const int duplicate_count = curve_merge_by_distance(points,
245 distances_along_curve,
246 selection.slice_content(points),
247 merge_distance,
248 merge_indices);
249 /* Write the curve size. The counts will be accumulated to offsets below. */
250 dst_offsets[curve_i] = points.size() - duplicate_count;
251 total_duplicate_count += duplicate_count;
252 }
253 });
254
255 const int dst_point_size = src_point_size - total_duplicate_count;
256 dst_curves.resize(dst_point_size, src_curves.curves_num());
258
259 int merged_points = 0;
260 Array<int> src_to_dst_indices(src_point_size);
261 for (const int curve_i : src_curves.curves_range()) {
262 const IndexRange points = points_by_curve[curve_i];
263 const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
264 for (const int i : points.index_range()) {
265 const int point_i = points.start() + i;
266 src_to_dst_indices[point_i] = point_i - merged_points;
267 if (merge_indices[i] != i) {
268 merged_points++;
269 }
270 }
271 }
272
273 Array<int> point_merge_counts(dst_point_size, 0);
274 for (const int curve_i : src_curves.curves_range()) {
275 const IndexRange points = points_by_curve[curve_i];
276 const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
277 for (const int i : points.index_range()) {
278 const int merge_index = merge_indices[i];
279 const int point_src = points.start() + merge_index;
280 const int dst_index = src_to_dst_indices[point_src];
281 point_merge_counts[dst_index]++;
282 }
283 }
284
285 Array<int> map_offsets_data(dst_point_size + 1);
286 map_offsets_data.as_mutable_span().drop_back(1).copy_from(point_merge_counts);
287 OffsetIndices<int> map_offsets = offset_indices::accumulate_counts_to_offsets(map_offsets_data);
288
289 point_merge_counts.fill(0);
290
291 Array<int> merge_map_indices(src_point_size);
292 for (const int curve_i : src_curves.curves_range()) {
293 const IndexRange points = points_by_curve[curve_i];
294 const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
295 for (const int i : points.index_range()) {
296 const int point_i = points.start() + i;
297 const int merge_index = merge_indices[i];
298 const int dst_index = src_to_dst_indices[points.start() + merge_index];
299 merge_map_indices[map_offsets[dst_index].first() + point_merge_counts[dst_index]] = point_i;
300 point_merge_counts[dst_index]++;
301 }
302 }
303
304 bke::AttributeAccessor src_attributes = src_curves.attributes();
305 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
306 src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
307 if (attribute_filter.allow_skip(iter.name)) {
308 return;
309 }
310 if (iter.domain != bke::AttrDomain::Point) {
311 return;
312 }
313
314 bke::GAttributeReader src_attribute = iter.get();
315 bke::attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) {
316 using T = decltype(dummy);
317 if constexpr (!std::is_void_v<bke::attribute_math::DefaultMixer<T>>) {
318 bke::SpanAttributeWriter<T> dst_attribute =
319 dst_attributes.lookup_or_add_for_write_only_span<T>(iter.name, bke::AttrDomain::Point);
320 BLI_assert(dst_attribute);
321 VArraySpan<T> src = src_attribute.varray.typed<T>();
322
323 threading::parallel_for(dst_curves.points_range(), 1024, [&](IndexRange range) {
324 for (const int dst_point_i : range) {
325 /* Create a separate mixer for every point to avoid allocating temporary buffers
326 * in the mixer the size of the result curves and to improve memory locality. */
327 bke::attribute_math::DefaultMixer<T> mixer{dst_attribute.span.slice(dst_point_i, 1)};
328
329 Span<int> src_merge_indices = merge_map_indices.as_span().slice(
330 map_offsets[dst_point_i]);
331 for (const int src_point_i : src_merge_indices) {
332 mixer.mix_in(0, src[src_point_i]);
333 }
334
335 mixer.finalize();
336 }
337 });
338
339 dst_attribute.finish();
340 }
341 });
342 });
343
344 if (dst_curves.nurbs_has_custom_knots()) {
345 bke::curves::nurbs::update_custom_knot_modes(
346 dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
347 }
348 return dst_curves;
349}
350
352 const ARegion &region,
353 const bke::CurvesGeometry &src_curves,
354 const float4x4 &layer_to_world,
355 const float merge_distance,
356 const IndexMask &selection,
357 const bke::AttributeFilter &attribute_filter)
358{
359 const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
360 const Span<float3> src_positions = src_curves.positions();
361 const float merge_distance_squared = merge_distance * merge_distance;
362
363 Array<float2> screen_start_points(src_curves.curves_num());
364 Array<float2> screen_end_points(src_curves.curves_num());
365 const VArray<bool> cyclic = *src_curves.attributes().lookup_or_default<bool>(
366 "cyclic", bke::AttrDomain::Curve, false);
367 /* For comparing screen space positions use a 2D KDTree. Each curve adds 2 points. */
368 KDTree_2d *tree = BLI_kdtree_2d_new(2 * src_curves.curves_num());
369
370 threading::parallel_for(src_curves.curves_range(), 1024, [&](const IndexRange range) {
371 for (const int src_i : range) {
372 const IndexRange points = src_points_by_curve[src_i];
373 const float3 start_pos = src_positions[points.first()];
374 const float3 end_pos = src_positions[points.last()];
375 const float3 start_world = math::transform_point(layer_to_world, start_pos);
376 const float3 end_world = math::transform_point(layer_to_world, end_pos);
377
378 ED_view3d_project_float_global(
379 &region, start_world, screen_start_points[src_i], V3D_PROJ_TEST_NOP);
380 ED_view3d_project_float_global(
381 &region, end_world, screen_end_points[src_i], V3D_PROJ_TEST_NOP);
382 }
383 });
384 /* Note: KDTree insertion is not thread-safe, don't parallelize this. */
385 for (const int src_i : src_curves.curves_range()) {
386 if (cyclic[src_i] == true) {
387 continue;
388 }
389 BLI_kdtree_2d_insert(tree, src_i * 2, screen_start_points[src_i]);
390 BLI_kdtree_2d_insert(tree, src_i * 2 + 1, screen_end_points[src_i]);
391 }
392 BLI_kdtree_2d_balance(tree);
393
394 Array<int> connect_to_curve(src_curves.curves_num(), -1);
395 Array<bool> flip_direction(src_curves.curves_num(), false);
396 selection.foreach_index(GrainSize(512), [&](const int src_i) {
397 const float2 &start_co = screen_start_points[src_i];
398 const float2 &end_co = screen_end_points[src_i];
399 /* Index of KDTree points so they can be ignored. */
400 const int start_index = src_i * 2;
401 const int end_index = src_i * 2 + 1;
402
403 KDTreeNearest_2d nearest_start, nearest_end;
404 const bool is_start_ok = (BLI_kdtree_2d_find_nearest_cb_cpp(
405 tree,
406 start_co,
407 &nearest_start,
408 [&](const int other, const float * /*co*/, const float dist_sq) {
409 if (start_index == other || dist_sq > merge_distance_squared) {
410 return 0;
411 }
412 return 1;
413 }) != -1);
414 const bool is_end_ok = (BLI_kdtree_2d_find_nearest_cb_cpp(
415 tree,
416 end_co,
417 &nearest_end,
418 [&](const int other, const float * /*co*/, const float dist_sq) {
419 if (end_index == other || dist_sq > merge_distance_squared) {
420 return 0;
421 }
422 return 1;
423 }) != -1);
424
425 if (is_start_ok) {
426 const int curve_index = nearest_start.index / 2;
427 const bool is_end_point = bool(nearest_start.index % 2);
428 if (connect_to_curve[curve_index] < 0) {
429 connect_to_curve[curve_index] = src_i;
430 flip_direction[curve_index] = !is_end_point;
431 }
432 }
433 if (is_end_ok) {
434 const int curve_index = nearest_end.index / 2;
435 const bool is_end_point = bool(nearest_end.index % 2);
436 if (connect_to_curve[src_i] < 0) {
437 connect_to_curve[src_i] = curve_index;
438 flip_direction[curve_index] = is_end_point;
439 }
440 }
441 });
442 BLI_kdtree_2d_free(tree);
443
444 return geometry::curves_merge_endpoints(
445 src_curves, connect_to_curve, flip_direction, attribute_filter);
446}
447
448/* Generate a full circle around a point. */
449static void generate_circle_from_point(const float3 &pt,
450 const float radius,
451 const int corner_subdivisions,
452 const int src_point_index,
453 Vector<float3> &r_perimeter,
454 Vector<int> &r_src_indices)
455{
456 /* Number of points is 2^(n+2) on a full circle (n=corner_subdivisions). */
457 BLI_assert(corner_subdivisions >= 0);
458 const int num_points = 1 << (corner_subdivisions + 2);
459 const float delta_angle = 2 * M_PI / float(num_points);
460 const float delta_cos = math::cos(delta_angle);
461 const float delta_sin = math::sin(delta_angle);
462
463 float3 vec = float3(radius, 0, 0);
464 for ([[maybe_unused]] const int i : IndexRange(num_points)) {
465 r_perimeter.append(pt + vec);
466 r_src_indices.append(src_point_index);
467
468 const float x = delta_cos * vec.x - delta_sin * vec.y;
469 const float y = delta_sin * vec.x + delta_cos * vec.y;
470 vec = float3(x, y, 0.0f);
471 }
472}
473
474/* Generate points in an counter-clockwise arc between two directions. */
476 const float3 &to,
477 const float3 &center_pt,
478 const int corner_subdivisions,
479 const int src_point_index,
480 Vector<float3> &r_perimeter,
481 Vector<int> &r_src_indices)
482{
483 const float3 vec_from = from - center_pt;
484 const float3 vec_to = to - center_pt;
485 if (math::is_zero(vec_from) || math::is_zero(vec_to)) {
486 r_perimeter.append(center_pt);
487 r_src_indices.append(src_point_index);
488 return;
489 }
490
491 const float cos_angle = math::dot(vec_from.xy(), vec_to.xy());
492 const float sin_angle = vec_from.x * vec_to.y - vec_from.y * vec_to.x;
493 /* Compute angle in range [0, 2pi) so that the rotation is always counter-clockwise. */
494 const float angle = math::atan2(-sin_angle, -cos_angle) + M_PI;
495
496 /* Number of points is 2^(n+1) + 1 on half a circle (n=corner_subdivisions)
497 * so we multiply by (angle / pi) to get the right amount of
498 * points to insert. */
499 const int num_full = (1 << (corner_subdivisions + 1)) + 1;
500 const int num_points = num_full * math::abs(angle) / M_PI;
501 if (num_points < 2) {
502 r_perimeter.append(center_pt + vec_from);
503 r_src_indices.append(src_point_index);
504 return;
505 }
506 const float delta_angle = angle / float(num_points - 1);
507 const float delta_cos = math::cos(delta_angle);
508 const float delta_sin = math::sin(delta_angle);
509
510 float3 vec = vec_from;
511 for ([[maybe_unused]] const int i : IndexRange(num_points)) {
512 r_perimeter.append(center_pt + vec);
513 r_src_indices.append(src_point_index);
514
515 const float x = delta_cos * vec.x - delta_sin * vec.y;
516 const float y = delta_sin * vec.x + delta_cos * vec.y;
517 vec = float3(x, y, 0.0f);
518 }
519}
520
521/* Generate a semi-circle around a point, opposite the direction. */
522static void generate_cap(const float3 &point,
523 const float3 &tangent,
524 const float radius,
525 const int corner_subdivisions,
526 const eGPDstroke_Caps cap_type,
527 const int src_point_index,
528 Vector<float3> &r_perimeter,
529 Vector<int> &r_src_indices)
530{
531 const float3 normal = math::normalize(float3{tangent.y, -tangent.x, 0.0f});
532 switch (cap_type) {
534 generate_arc_from_point_to_point(point - normal * radius,
535 point + normal * radius,
536 point,
537 corner_subdivisions,
538 src_point_index,
539 r_perimeter,
540 r_src_indices);
541 break;
543 r_perimeter.append(point - normal * radius);
544 r_src_indices.append(src_point_index);
545 r_perimeter.append(point + normal * radius);
546 r_src_indices.append(src_point_index);
547 break;
550 break;
551 }
552}
553
554/* Generate a corner between two segments, with a rounded outer perimeter.
555 * NOTE: The perimeter is considered to be to the right hand side of the stroke. The left side
556 * perimeter can be generated by reversing the order of points. */
557static void generate_corner(const float3 &pt_a,
558 const float3 &pt_b,
559 const float3 &pt_c,
560 const float radius,
561 const int corner_subdivisions,
562 const int src_point_index,
563 Vector<float3> &r_perimeter,
564 Vector<int> &r_src_indices)
565{
566 const float length = math::length(pt_c - pt_b);
567 const float length_prev = math::length(pt_b - pt_a);
568 const float2 tangent = math::normalize((pt_c - pt_b).xy());
569 const float2 tangent_prev = math::normalize((pt_b - pt_a).xy());
570 const float3 normal = {tangent.y, -tangent.x, 0.0f};
571 const float3 normal_prev = {tangent_prev.y, -tangent_prev.x, 0.0f};
572
573 const float sin_angle = tangent_prev.x * tangent.y - tangent_prev.y * tangent.x;
574 /* Whether the corner is an inside or outside corner.
575 * This determines whether an arc is added or a single miter point. */
576 const bool is_outside_corner = (sin_angle >= 0.0f);
577 if (is_outside_corner) {
578 generate_arc_from_point_to_point(pt_b + normal_prev * radius,
579 pt_b + normal * radius,
580 pt_b,
581 corner_subdivisions,
582 src_point_index,
583 r_perimeter,
584 r_src_indices);
585 }
586 else {
587 const float2 avg_tangent = math::normalize(tangent_prev + tangent);
588 const float3 miter = {avg_tangent.y, -avg_tangent.x, 0.0f};
589 const float miter_invscale = math::dot(normal, miter);
590
591 /* Avoid division by tiny values for steep angles. */
592 const float3 miter_point = (radius < length * miter_invscale &&
593 radius < length_prev * miter_invscale) ?
594 pt_b + miter * radius / miter_invscale :
595 pt_b + miter * radius;
596
597 r_perimeter.append(miter_point);
598 r_src_indices.append(src_point_index);
599 }
600}
601
602static void generate_stroke_perimeter(const Span<float3> all_positions,
603 const Span<float> all_radii,
604 const IndexRange points,
605 const int corner_subdivisions,
606 const bool is_cyclic,
607 const bool use_caps,
608 const eGPDstroke_Caps start_cap_type,
609 const eGPDstroke_Caps end_cap_type,
610 const float outline_offset,
611 Vector<float3> &r_perimeter,
612 Vector<int> &r_point_counts,
613 Vector<int> &r_point_indices)
614{
615 const Span<float3> positions = all_positions.slice(points);
616 const int point_num = points.size();
617 if (point_num == 0) {
618 return;
619 }
620 if (point_num == 1) {
621 /* Generate a circle for a single point. */
622 const int perimeter_start = r_perimeter.size();
623 const int point = points.first();
624 const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
626 positions.first(), radius, corner_subdivisions, point, r_perimeter, r_point_indices);
627 const int perimeter_count = r_perimeter.size() - perimeter_start;
628 if (perimeter_count > 0) {
629 r_point_counts.append(perimeter_count);
630 }
631 return;
632 }
633
634 auto add_corner = [&](const int a, const int b, const int c) {
635 const int point = points[b];
636 const float3 pt_a = positions[a];
637 const float3 pt_b = positions[b];
638 const float3 pt_c = positions[c];
639 const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
641 pt_a, pt_b, pt_c, radius, corner_subdivisions, point, r_perimeter, r_point_indices);
642 };
643 auto add_cap = [&](const int center_i, const int next_i, const eGPDstroke_Caps cap_type) {
644 const int point = points[center_i];
645 const float3 &center = positions[center_i];
646 const float3 dir = math::normalize(positions[next_i] - center);
647 const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
649 center, dir, radius, corner_subdivisions, cap_type, point, r_perimeter, r_point_indices);
650 };
651
652 /* Creates a single cyclic curve with end caps. */
653 if (use_caps) {
654 /* Open curves generate a start and end cap and a connecting stroke on either side. */
655 const int perimeter_start = r_perimeter.size();
656
657 /* Start cap. */
658 add_cap(0, 1, start_cap_type);
659
660 /* Right perimeter half. */
661 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
662 add_corner(i - 1, i, i + 1);
663 }
664 if (is_cyclic) {
665 add_corner(point_num - 2, point_num - 1, 0);
666 }
667
668 /* End cap. */
669 if (is_cyclic) {
670 /* End point is same as start point. */
671 add_cap(0, point_num - 1, end_cap_type);
672 }
673 else {
674 /* End point is last point of the curve. */
675 add_cap(point_num - 1, point_num - 2, end_cap_type);
676 }
677
678 /* Left perimeter half. */
679 if (is_cyclic) {
680 add_corner(0, point_num - 1, point_num - 2);
681 }
682 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
683 add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
684 }
685
686 const int perimeter_count = r_perimeter.size() - perimeter_start;
687 if (perimeter_count > 0) {
688 r_point_counts.append(perimeter_count);
689 }
690 }
691 else {
692 /* Generate separate "inside" and an "outside" perimeter curves.
693 * The distinction is arbitrary, called left/right here. */
694
695 /* Right side perimeter. */
696 const int left_perimeter_start = r_perimeter.size();
697 add_corner(point_num - 1, 0, 1);
698 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
699 add_corner(i - 1, i, i + 1);
700 }
701 add_corner(point_num - 2, point_num - 1, 0);
702 const int left_perimeter_count = r_perimeter.size() - left_perimeter_start;
703 if (left_perimeter_count > 0) {
704 r_point_counts.append(left_perimeter_count);
705 }
706
707 /* Left side perimeter. */
708 const int right_perimeter_start = r_perimeter.size();
709 add_corner(0, point_num - 1, point_num - 2);
710 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
711 add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
712 }
713 add_corner(1, 0, point_num - 1);
714 const int right_perimeter_count = r_perimeter.size() - right_perimeter_start;
715 if (right_perimeter_count > 0) {
716 r_point_counts.append(right_perimeter_count);
717 }
718 }
719}
720
722 /* New points per curve count. */
724 /* New point coordinates. */
726 /* Source curve index. */
728 /* Source point index. */
730};
731
733 const IndexMask &strokes,
734 const float4x4 &transform,
735 const int corner_subdivisions,
736 const float outline_radius,
737 const float outline_offset,
738 const int material_index)
739{
740 const bke::CurvesGeometry &src_curves = drawing.strokes();
741 Span<float3> src_positions = src_curves.positions();
742 bke::AttributeAccessor src_attributes = src_curves.attributes();
743 const VArray<float> src_radii = drawing.radii();
744 const VArray<bool> src_cyclic = *src_attributes.lookup_or_default(
745 "cyclic", bke::AttrDomain::Curve, false);
746 const VArray<int8_t> src_start_caps = *src_attributes.lookup_or_default<int8_t>(
748 const VArray<int8_t> src_end_caps = *src_attributes.lookup_or_default<int8_t>(
750 const VArray<int> src_material_index = *src_attributes.lookup_or_default(
751 "material_index", bke::AttrDomain::Curve, 0);
752
753 /* Transform positions and radii. */
754 const float scale = math::average(math::to_scale(transform));
755 Array<float3> transformed_positions(src_positions.size());
756 Array<float> transformed_radii(src_radii.size());
757 threading::parallel_for(transformed_positions.index_range(), 4096, [&](const IndexRange range) {
758 for (const int i : range) {
759 transformed_positions[i] = math::transform_point(transform, src_positions[i]);
760 }
761 });
762 threading::parallel_for(transformed_radii.index_range(), 4096, [&](const IndexRange range) {
763 for (const int i : range) {
764 transformed_radii[i] = src_radii[i] * scale;
765 }
766 });
767
768 const float4x4 transform_inv = math::invert(transform);
770 strokes.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
771 PerimeterData &data = thread_data.local();
772
773 const bool is_cyclic_curve = src_cyclic[curve_i];
774 /* NOTE: Cyclic curves would better be represented by a cyclic perimeter without end caps, but
775 * we always generate caps for compatibility with GPv2. Fill materials cannot create holes, so
776 * a cyclic outline does not work well. */
777 const bool use_caps = true ;
778
779 const int prev_point_num = data.positions.size();
780 const int prev_curve_num = data.point_counts.size();
781 const IndexRange points = src_curves.points_by_curve()[curve_i];
782
783 generate_stroke_perimeter(transformed_positions,
784 transformed_radii,
785 points,
786 corner_subdivisions,
787 is_cyclic_curve,
788 use_caps,
789 eGPDstroke_Caps(src_start_caps[curve_i]),
790 eGPDstroke_Caps(src_end_caps[curve_i]),
791 outline_offset,
792 data.positions,
793 data.point_counts,
794 data.point_indices);
795
796 /* Transform perimeter positions back into object space. */
797 for (float3 &pos : data.positions.as_mutable_span().drop_front(prev_point_num)) {
798 pos = math::transform_point(transform_inv, pos);
799 }
800
801 data.curve_indices.append_n_times(curve_i, data.point_counts.size() - prev_curve_num);
802 });
803
804 int dst_curve_num = 0;
805 int dst_point_num = 0;
806 for (const PerimeterData &data : thread_data) {
807 BLI_assert(data.point_counts.size() == data.curve_indices.size());
808 BLI_assert(data.positions.size() == data.point_indices.size());
809 dst_curve_num += data.point_counts.size();
810 dst_point_num += data.positions.size();
811 }
812
813 bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
814 if (dst_point_num == 0 || dst_curve_num == 0) {
815 return dst_curves;
816 }
817
818 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
819 bke::SpanAttributeWriter<bool> dst_cyclic = dst_attributes.lookup_or_add_for_write_span<bool>(
820 "cyclic", bke::AttrDomain::Curve);
821 bke::SpanAttributeWriter<int> dst_material = dst_attributes.lookup_or_add_for_write_span<int>(
822 "material_index", bke::AttrDomain::Curve);
823 bke::SpanAttributeWriter<float> dst_radius = dst_attributes.lookup_or_add_for_write_span<float>(
824 "radius", bke::AttrDomain::Point);
825 const MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
826 const MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
827 /* Source indices for attribute mapping. */
828 Array<int> dst_curve_map(dst_curve_num);
829 Array<int> dst_point_map(dst_point_num);
830
831 IndexRange curves;
832 IndexRange points;
833 for (const PerimeterData &data : thread_data) {
834 curves = curves.after(data.point_counts.size());
835 points = points.after(data.positions.size());
836
837 /* Append curve data. */
838 dst_curve_map.as_mutable_span().slice(curves).copy_from(data.curve_indices);
839 /* Curve offsets are accumulated below. */
840 dst_offsets.slice(curves).copy_from(data.point_counts);
841 dst_cyclic.span.slice(curves).fill(true);
842 if (material_index >= 0) {
843 dst_material.span.slice(curves).fill(material_index);
844 }
845 else {
846 for (const int i : curves.index_range()) {
847 dst_material.span[curves[i]] = src_material_index[data.curve_indices[i]];
848 }
849 }
850
851 /* Append point data. */
852 dst_positions.slice(points).copy_from(data.positions);
853 dst_point_map.as_mutable_span().slice(points).copy_from(data.point_indices);
854 dst_radius.span.slice(points).fill(outline_radius);
855 }
856 offset_indices::accumulate_counts_to_offsets(dst_curves.offsets_for_write());
857
858 bke::gather_attributes(src_attributes,
859 bke::AttrDomain::Point,
860 bke::AttrDomain::Point,
861 bke::attribute_filter_from_skip_ref({"position", "radius"}),
862 dst_point_map,
863 dst_attributes);
864 bke::gather_attributes(src_attributes,
865 bke::AttrDomain::Curve,
866 bke::AttrDomain::Curve,
867 bke::attribute_filter_from_skip_ref({"cyclic", "material_index"}),
868 dst_curve_map,
869 dst_attributes);
870
871 dst_cyclic.finish();
872 dst_material.finish();
873 dst_radius.finish();
874 dst_curves.update_curve_types();
875
876 return dst_curves;
877}
878
879namespace trim {
880
881enum Side : uint8_t { Start = 0, End = 1 };
882enum Distance : uint8_t { Min = 0, Max = 1 };
883
884/* When looking for intersections, we need a little padding, otherwise we could miss curves
885 * that intersect for the eye, but not in hard numbers. */
886static constexpr int BBOX_PADDING = 2;
887
888/* When creating new intersection points, we don't want them too close to their neighbor,
889 * because that clutters the geometry. This threshold defines what 'too close' is. */
890static constexpr float DISTANCE_FACTOR_THRESHOLD = 0.01f;
891
896struct Segment {
897 /* Curve index. */
898 int curve;
899
900 /* Point range of the segment: starting point and end point. Matches the point offsets
901 * in a CurvesGeometry. */
903
904 /* The normalized distance where the trim segment is intersected by another curve.
905 * For the outer ends of the trim segment the intersection distance is given between:
906 * - [start point - 1] and [start point]
907 * - [end point] and [end point + 1]
908 */
910
911 /* Intersection flag: true if the start/end point of the segment is the result of an
912 * intersection, false if the point is the outer end of a curve. */
914};
915
920struct Segments {
921 /* Collection of trim segments: parts of curves between other curves, to be removed from the
922 * geometry. */
924
925 /* Create an initial trim segment with a point range of one point. */
926 Segment *create_segment(const int curve, const int point)
927 {
928 Segment segment{};
929 segment.curve = curve;
930 segment.point_range[Side::Start] = point;
931 segment.point_range[Side::End] = point;
932
933 this->segments.append(std::move(segment));
934
935 return &this->segments.last();
936 }
937
938 /* Merge trim segments that are next to each other. */
940 {
941 Vector<Segment> merged_segments;
942
943 /* Note on performance: we deal with small numbers here, so we can afford the double loop. */
944 while (!this->segments.is_empty()) {
945 Segment a = this->segments.pop_last();
946
947 bool merged = false;
948 for (Segment &b : merged_segments) {
949 if (a.curve != b.curve) {
950 continue;
951 }
952 /* The segments overlap when the points ranges have overlap or are exactly adjacent. */
953 if ((a.point_range[Side::Start] <= b.point_range[Side::End] &&
954 a.point_range[Side::End] >= b.point_range[Side::Start]) ||
955 (a.point_range[Side::End] == b.point_range[Side::Start] - 1) ||
956 (b.point_range[Side::End] == a.point_range[Side::Start] - 1))
957 {
958 /* Merge the point ranges and related intersection data. */
959 const bool take_start_a = a.point_range[Side::Start] < b.point_range[Side::Start];
960 const bool take_end_a = a.point_range[Side::End] > b.point_range[Side::End];
961 b.point_range[Side::Start] = take_start_a ? a.point_range[Side::Start] :
962 b.point_range[Side::Start];
963 b.point_range[Side::End] = take_end_a ? a.point_range[Side::End] :
964 b.point_range[Side::End];
965 b.is_intersected[Side::Start] = take_start_a ? a.is_intersected[Side::Start] :
966 b.is_intersected[Side::Start];
967 b.is_intersected[Side::End] = take_end_a ? a.is_intersected[Side::End] :
968 b.is_intersected[Side::End];
969 b.intersection_distance[Side::Start] = take_start_a ?
971 b.intersection_distance[Side::Start];
972 b.intersection_distance[Side::End] = take_end_a ? a.intersection_distance[Side::End] :
973 b.intersection_distance[Side::End];
974 merged = true;
975 break;
976 }
977 }
978 if (!merged) {
979 merged_segments.append(std::move(a));
980 }
981 }
982
983 this->segments = merged_segments;
984 }
985};
986
993 const float2 &co_b,
994 const float2 &co_c,
995 const float2 &co_d)
996{
997 /* Get intersection point. */
998 const float a1 = co_b[1] - co_a[1];
999 const float b1 = co_a[0] - co_b[0];
1000 const float c1 = a1 * co_a[0] + b1 * co_a[1];
1001
1002 const float a2 = co_d[1] - co_c[1];
1003 const float b2 = co_c[0] - co_d[0];
1004 const float c2 = a2 * co_c[0] + b2 * co_c[1];
1005
1006 const float det = (a1 * b2 - a2 * b1);
1007 if (det == 0.0f) {
1008 return 0.0f;
1009 }
1010
1011 float2 isect((b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det);
1012
1013 /* Get normalized distance from point a to intersection point. */
1014 const float length_ab = math::length(co_b - co_a);
1015 float distance = (length_ab == 0.0f ?
1016 0.0f :
1017 math::clamp(math::length(isect - co_a) / length_ab, 0.0f, 1.0f));
1018
1019 return distance;
1020}
1021
1025static void get_intersections_of_curve_with_curves(const int src_curve,
1026 const bke::CurvesGeometry &src,
1027 const Span<float2> screen_space_positions,
1028 const Span<rcti> screen_space_curve_bounds,
1029 MutableSpan<bool> r_is_intersected_after_point,
1030 MutableSpan<float2> r_intersection_distance)
1031{
1032 const OffsetIndices<int> points_by_curve = src.points_by_curve();
1033 const VArray<bool> is_cyclic = src.cyclic();
1034
1035 /* Edge case: skip curve with only one point. */
1036 if (points_by_curve[src_curve].size() < 2) {
1037 return;
1038 }
1039
1040 /* Loop all curve points and check for intersections between point a and point a + 1. */
1041 const IndexRange src_curve_points = points_by_curve[src_curve].drop_back(
1042 is_cyclic[src_curve] ? 0 : 1);
1043 for (const int point_a : src_curve_points) {
1044 const int point_b = (point_a == points_by_curve[src_curve].last()) ? src_curve_points.first() :
1045 point_a + 1;
1046
1047 /* Get coordinates of segment a-b. */
1048 const float2 co_a = screen_space_positions[point_a];
1049 const float2 co_b = screen_space_positions[point_b];
1050 rcti bbox_ab;
1051 BLI_rcti_init_minmax(&bbox_ab);
1052 BLI_rcti_do_minmax_v(&bbox_ab, int2(co_a));
1053 BLI_rcti_do_minmax_v(&bbox_ab, int2(co_b));
1055
1056 float intersection_distance_min = FLT_MAX;
1057 float intersection_distance_max = -FLT_MAX;
1058
1059 /* Loop all curves, looking for intersecting segments. */
1060 for (const int curve : src.curves_range()) {
1061 /* Only process curves with at least two points. */
1062 if (points_by_curve[curve].size() < 2) {
1063 continue;
1064 }
1065
1066 /* Bounding box check: skip curves that don't overlap segment a-b. */
1067 if (!BLI_rcti_isect(&bbox_ab, &screen_space_curve_bounds[curve], nullptr)) {
1068 continue;
1069 }
1070
1071 /* Find intersecting curve segments. */
1072 const IndexRange points = points_by_curve[curve].drop_back(is_cyclic[curve] ? 0 : 1);
1073 for (const int point_c : points) {
1074 const int point_d = (point_c == points_by_curve[curve].last()) ? points.first() :
1075 (point_c + 1);
1076
1077 /* Don't self check. */
1078 if (curve == src_curve &&
1079 (point_a == point_c || point_a == point_d || point_b == point_c || point_b == point_d))
1080 {
1081 continue;
1082 }
1083
1084 /* Skip when bounding boxes of a-b and c-d don't overlap. */
1085 const float2 co_c = screen_space_positions[point_c];
1086 const float2 co_d = screen_space_positions[point_d];
1087 rcti bbox_cd;
1088 BLI_rcti_init_minmax(&bbox_cd);
1089 BLI_rcti_do_minmax_v(&bbox_cd, int2(co_c));
1090 BLI_rcti_do_minmax_v(&bbox_cd, int2(co_d));
1092 if (!BLI_rcti_isect(&bbox_ab, &bbox_cd, nullptr)) {
1093 continue;
1094 }
1095
1096 /* Add some padding to the line segment c-d, otherwise we could just miss an
1097 * intersection. */
1098 const float2 padding_cd = math::normalize(co_d - co_c);
1099 const float2 padded_c = co_c - padding_cd;
1100 const float2 padded_d = co_d + padding_cd;
1101
1102 /* Check for intersection. */
1103 const auto isect = math::isect_seg_seg(co_a, co_b, padded_c, padded_d);
1104 if (ELEM(isect.kind, isect.LINE_LINE_CROSS, isect.LINE_LINE_EXACT)) {
1105 /* We found an intersection, set the intersection flag for segment a-b. */
1106 r_is_intersected_after_point[point_a] = true;
1107
1108 /* Calculate the intersection factor. This is the normalized distance (0..1) of the
1109 * intersection point on line segment a-b, measured from point a. */
1110 const float normalized_distance = get_intersection_distance_of_segments(
1111 co_a, co_b, co_c, co_d);
1112 intersection_distance_min = math::min(normalized_distance, intersection_distance_min);
1113 intersection_distance_max = math::max(normalized_distance, intersection_distance_max);
1114 }
1115 }
1116 }
1117
1118 if (r_is_intersected_after_point[point_a]) {
1119 r_intersection_distance[point_a][Distance::Min] = intersection_distance_min;
1120 r_intersection_distance[point_a][Distance::Max] = intersection_distance_max;
1121 }
1122 }
1123}
1124
1130 const int direction,
1131 const bke::CurvesGeometry &src,
1132 const Span<bool> is_intersected_after_point,
1133 const Span<float2> intersection_distance,
1134 MutableSpan<bool> point_is_in_segment)
1135{
1136 const OffsetIndices<int> points_by_curve = src.points_by_curve();
1137 const int point_first = points_by_curve[segment.curve].first();
1138 const int point_last = points_by_curve[segment.curve].last();
1139
1140 const Side segment_side = (direction == 1) ? Side::End : Side::Start;
1141 int point_a = segment.point_range[segment_side];
1142
1143 bool intersected = false;
1144 segment.is_intersected[segment_side] = false;
1145
1146 /* Walk along the curve points. */
1147 while ((direction == 1 && point_a < point_last) || (direction == -1 && point_a > point_first)) {
1148 const int point_b = point_a + direction;
1149 const bool at_end_of_curve = (direction == -1 && point_b == point_first) ||
1150 (direction == 1 && point_b == point_last);
1151
1152 /* Expand segment point range. */
1153 segment.point_range[segment_side] = point_a;
1154 point_is_in_segment[point_a] = true;
1155
1156 /* Check for intersections with other curves. The intersections were established in ascending
1157 * point order, so in forward direction we look at line segment a-b, in backward direction we
1158 * look at line segment b-a. */
1159 const int intersection_point = direction == 1 ? point_a : point_b;
1160 intersected = is_intersected_after_point[intersection_point];
1161
1162 /* Avoid orphaned points at the end of a curve. */
1163 if (at_end_of_curve &&
1164 ((direction == -1 &&
1165 intersection_distance[intersection_point][Distance::Max] < DISTANCE_FACTOR_THRESHOLD) ||
1166 (direction == 1 && intersection_distance[intersection_point][Distance::Min] >
1167 (1.0f - DISTANCE_FACTOR_THRESHOLD))))
1168 {
1169 intersected = false;
1170 break;
1171 }
1172
1173 /* When we hit an intersection, store the intersection distance. Potentially, line segment
1174 * a-b can be intersected by multiple curves, so we want to fetch the first intersection
1175 * point we bumped into. In forward direction this is the minimum distance, in backward
1176 * direction the maximum. */
1177 if (intersected) {
1178 segment.is_intersected[segment_side] = true;
1179 segment.intersection_distance[segment_side] =
1180 (direction == 1) ? intersection_distance[intersection_point][Distance::Min] :
1181 intersection_distance[intersection_point][Distance::Max];
1182 break;
1183 }
1184
1185 /* Keep walking along curve. */
1186 point_a += direction;
1187 }
1188
1189 /* Adjust point range at curve ends. */
1190 if (!intersected) {
1191 if (direction == -1) {
1192 segment.point_range[Side::Start] = point_first;
1193 point_is_in_segment[point_first] = true;
1194 }
1195 else {
1196 segment.point_range[Side::End] = point_last;
1197 point_is_in_segment[point_last] = true;
1198 }
1199 }
1200}
1201
1205static void expand_trim_segment(Segment &segment,
1206 const bke::CurvesGeometry &src,
1207 const Span<bool> is_intersected_after_point,
1208 const Span<float2> intersection_distance,
1209 MutableSpan<bool> point_is_in_segment)
1210{
1211 const int8_t directions[2] = {-1, 1};
1212 for (const int8_t direction : directions) {
1214 direction,
1215 src,
1216 is_intersected_after_point,
1217 intersection_distance,
1218 point_is_in_segment);
1219 }
1220}
1221
1223 const Span<float2> screen_space_positions,
1224 const Span<rcti> screen_space_curve_bounds,
1225 const IndexMask &curve_selection,
1226 const Vector<Vector<int>> &selected_points_in_curves,
1227 const bool keep_caps)
1228{
1229 const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
1230
1231 /* For the selected curves, find all the intersections with other curves. */
1232 const int src_points_num = src.points_num();
1233 Array<bool> is_intersected_after_point(src_points_num, false);
1234 Array<float2> intersection_distance(src_points_num);
1235 curve_selection.foreach_index(GrainSize(32), [&](const int curve_i) {
1237 src,
1238 screen_space_positions,
1239 screen_space_curve_bounds,
1240 is_intersected_after_point,
1241 intersection_distance);
1242 });
1243
1244 /* Expand the selected curve points to trim segments (the part of the curve between two
1245 * intersections). */
1246 const VArray<bool> is_cyclic = src.cyclic();
1247 Array<bool> point_is_in_segment(src_points_num, false);
1248 threading::EnumerableThreadSpecific<Segments> trim_segments_by_thread;
1249 curve_selection.foreach_index(GrainSize(32), [&](const int curve_i, const int pos) {
1250 Segments &thread_segments = trim_segments_by_thread.local();
1251 for (const int selected_point : selected_points_in_curves[pos]) {
1252 /* Skip point when it is already part of a trim segment. */
1253 if (point_is_in_segment[selected_point]) {
1254 continue;
1255 }
1256
1257 /* Create new trim segment. */
1258 Segment *segment = thread_segments.create_segment(curve_i, selected_point);
1259
1260 /* Expand the trim segment in both directions until an intersection is found or the
1261 * end of the curve is reached. */
1263 *segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
1264
1265 /* When the end of a curve is reached and the curve is cyclic, we add an extra trim
1266 * segment for the cyclic second part. */
1267 if (is_cyclic[curve_i] &&
1268 (!segment->is_intersected[Side::Start] || !segment->is_intersected[Side::End]) &&
1269 !(!segment->is_intersected[Side::Start] && !segment->is_intersected[Side::End]))
1270 {
1271 const int cyclic_outer_point = !segment->is_intersected[Side::Start] ?
1272 src_points_by_curve[curve_i].last() :
1273 src_points_by_curve[curve_i].first();
1274 segment = thread_segments.create_segment(curve_i, cyclic_outer_point);
1275
1276 /* Expand this second segment. */
1278 *segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
1279 }
1280 }
1281 });
1282 Segments trim_segments;
1283 for (Segments &thread_segments : trim_segments_by_thread) {
1284 trim_segments.segments.extend(thread_segments.segments);
1285 }
1286
1287 /* Abort when no trim segments are found in the lasso area. */
1289 if (trim_segments.segments.is_empty()) {
1290 return dst;
1291 }
1292
1293 /* Merge adjacent trim segments. E.g. two point ranges of 0-10 and 11-20 will be merged
1294 * to one range of 0-20. */
1295 trim_segments.merge_adjacent_segments();
1296
1297 /* Create the point transfer data, for converting the source geometry into the new geometry.
1298 * First, add all curve points not affected by the trim tool. */
1299 Array<Vector<PointTransferData>> src_to_dst_points(src_points_num);
1300 for (const int src_curve : src.curves_range()) {
1301 const IndexRange src_points = src_points_by_curve[src_curve];
1302 for (const int src_point : src_points) {
1303 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1304 const int src_next_point = (src_point == src_points.last()) ? src_points.first() :
1305 (src_point + 1);
1306
1307 /* Add the source point only if it does not lie inside a trim segment. */
1308 if (!point_is_in_segment[src_point]) {
1309 dst_points.append({src_point, src_next_point, 0.0f, true, false});
1310 }
1311 }
1312 }
1313
1314 /* Add new curve points at the intersection points of the trim segments.
1315 *
1316 * a b
1317 * source curve o--------o---*---o--------o----*---o--------o
1318 * ^ ^
1319 * trim segment |-----------------|
1320 *
1321 * o = existing curve point
1322 * * = newly created curve point
1323 *
1324 * The curve points between *a and *b will be deleted.
1325 * The source curve will be cut in two:
1326 * - the first curve ends at *a
1327 * - the second curve starts at *b
1328 *
1329 * We avoid inserting a new point very close to the adjacent one, because that's just adding
1330 * clutter to the geometry.
1331 */
1332 for (const Segment &trim_segment : trim_segments.segments) {
1333 /* Intersection at trim segment start. */
1334 if (trim_segment.is_intersected[Side::Start] &&
1336 {
1337 const int src_point = trim_segment.point_range[Side::Start] - 1;
1338 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1339 dst_points.append({src_point,
1340 src_point + 1,
1341 trim_segment.intersection_distance[Side::Start],
1342 false,
1343 false});
1344 }
1345 /* Intersection at trim segment end. */
1346 if (trim_segment.is_intersected[Side::End]) {
1347 const int src_point = trim_segment.point_range[Side::End];
1348 if (trim_segment.intersection_distance[Side::End] < (1.0f - DISTANCE_FACTOR_THRESHOLD)) {
1349 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1350 dst_points.append({src_point,
1351 src_point + 1,
1352 trim_segment.intersection_distance[Side::End],
1353 false,
1354 true});
1355 }
1356 else {
1357 /* Mark the 'is_cut' flag on the next point, because a new curve is starting here after
1358 * the removed trim segment. */
1359 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point + 1];
1360 for (PointTransferData &dst_point : dst_points) {
1361 if (dst_point.is_src_point) {
1362 dst_point.is_cut = true;
1363 }
1364 }
1365 }
1366 }
1367 }
1368
1369 /* Create the new curves geometry. */
1370 compute_topology_change(src, dst, src_to_dst_points, keep_caps);
1371
1372 return dst;
1373}
1374
1375} // namespace trim
1376
1378 const Object &object,
1379 const GreasePencil &grease_pencil,
1380 Span<MutableDrawingInfo> drawings,
1381 const int frame_number)
1382{
1384
1385 /* Upper bound for line count. Arrays are sized for easy index mapping, exact count isn't
1386 * necessary. Not all points are added to the BVH tree. */
1387 int max_bvh_lines = 0;
1388 for (const int i_drawing : drawings.index_range()) {
1389 if (drawings[i_drawing].frame_number == frame_number) {
1390 max_bvh_lines += drawings[i_drawing].drawing.strokes().evaluated_points_num();
1391 }
1392 }
1393
1394 data.tree = BLI_bvhtree_new(max_bvh_lines, 0.0f, 4, 6);
1395 data.start_positions.reinitialize(max_bvh_lines);
1396 data.end_positions.reinitialize(max_bvh_lines);
1397 /* Compute offsets array in advance. */
1398 data.drawing_offsets.reinitialize(drawings.size() + 1);
1399 for (const int i_drawing : drawings.index_range()) {
1400 const MutableDrawingInfo &info = drawings[i_drawing];
1401 data.drawing_offsets[i_drawing] = (drawings[i_drawing].frame_number == frame_number ?
1402 info.drawing.strokes().evaluated_points_num() :
1403 0);
1404 }
1406 data.drawing_offsets);
1407
1408 /* Insert a line for each point except end points. */
1409 for (const int i_drawing : drawings.index_range()) {
1410 const MutableDrawingInfo &info = drawings[i_drawing];
1411 if (drawings[i_drawing].frame_number != frame_number) {
1412 continue;
1413 }
1414
1415 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
1416 const float4x4 layer_to_world = layer.to_world_space(object);
1417 const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc.rv3d, layer_to_world);
1418 const bke::CurvesGeometry &curves = info.drawing.strokes();
1419 const OffsetIndices evaluated_points_by_curve = curves.evaluated_points_by_curve();
1420 const VArray<bool> cyclic = curves.cyclic();
1421 const Span<float3> evaluated_positions = curves.evaluated_positions();
1422 const IndexMask curves_mask = curves.curves_range();
1423
1424 /* Range of indices in the BVH tree for this drawing. */
1425 const IndexRange bvh_index_range = bvh_elements_by_drawing[i_drawing];
1426 const MutableSpan<float2> start_positions = data.start_positions.as_mutable_span().slice(
1427 bvh_index_range);
1428 const MutableSpan<float2> end_positions = data.end_positions.as_mutable_span().slice(
1429 bvh_index_range);
1430
1431 curves_mask.foreach_index([&](const int i_curve) {
1432 const bool is_cyclic = cyclic[i_curve];
1433 const IndexRange evaluated_points = evaluated_points_by_curve[i_curve];
1434
1435 /* Compute screen space positions. */
1436 for (const int i_point : evaluated_points) {
1438 vc.region, evaluated_positions[i_point], projection);
1439 start_positions[i_point] = co;
1440
1441 /* Last point is only valid for cyclic curves, gets ignored for non-cyclic curves. */
1442 const int i_prev_point = (i_point > 0 ? i_point - 1 : evaluated_points.last());
1443 end_positions[i_prev_point] = co;
1444 }
1445
1446 for (const int i_point : evaluated_points.drop_back(1)) {
1447 const float2 &start = start_positions[i_point];
1448 const float2 &end = end_positions[i_point];
1449
1450 const float bb[6] = {start.x, start.y, 0.0f, end.x, end.y, 0.0f};
1451 BLI_bvhtree_insert(data.tree, bvh_index_range[i_point], bb, 2);
1452 }
1453 /* Last->first point segment only used for cyclic curves. */
1454 if (is_cyclic) {
1455 const float2 &start = start_positions.last();
1456 const float2 &end = end_positions.first();
1457
1458 const float bb[6] = {start.x, start.y, 0.0f, end.x, end.y, 0.0f};
1459 BLI_bvhtree_insert(data.tree, bvh_index_range[evaluated_points.last()], bb, 2);
1460 }
1461 });
1462 }
1463
1465
1466 return data;
1467}
1468
1470{
1471 if (data.tree) {
1472 BLI_bvhtree_free(data.tree);
1473 data.tree = nullptr;
1474 }
1475 data.drawing_offsets.reinitialize(0);
1476 data.start_positions.reinitialize(0);
1477 data.end_positions.reinitialize(0);
1478}
1479
1481 const IndexMask &curve_mask,
1482 const Span<float2> screen_space_positions,
1483 const Curves2DBVHTree &tree_data,
1484 const IndexRange tree_data_range,
1485 MutableSpan<bool> r_hits,
1486 std::optional<MutableSpan<float>> r_first_intersect_factors,
1487 std::optional<MutableSpan<float>> r_last_intersect_factors)
1488{
1489 /* Insert segments for cutting extensions on stroke intersection. */
1490 const OffsetIndices points_by_curve = curves.points_by_curve();
1491 const VArray<bool> cyclic = curves.cyclic();
1492
1493 struct RaycastArgs {
1494 const Curves2DBVHTree &tree_data;
1495 /* Indices that need to be ignored to avoid intersecting a line with itself or its immediate
1496 * neighbors. */
1497 int ignore_index1;
1498 int ignore_index2;
1499 int ignore_index3;
1500 };
1501 BVHTree_RayCastCallback callback =
1502 [](void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit) {
1503 using Result = math::isect_result<float2>;
1504
1505 const RaycastArgs &args = *static_cast<const RaycastArgs *>(userdata);
1506 if (ELEM(index, args.ignore_index1, args.ignore_index2, args.ignore_index3)) {
1507 return;
1508 }
1509
1510 const float2 ray_start = float2(ray->origin);
1511 const float2 ray_end = ray_start + float2(ray->direction) * ray->radius;
1512 const float2 &line_start = args.tree_data.start_positions[index];
1513 const float2 &line_end = args.tree_data.end_positions[index];
1514 Result result = math::isect_seg_seg(ray_start, ray_end, line_start, line_end);
1515 if (result.kind <= 0) {
1516 return;
1517 }
1518 const float dist = result.lambda * math::distance(ray_start, ray_end);
1519 if (dist >= hit->dist) {
1520 return;
1521 }
1522 /* These always need to be calculated for the BVH traversal function. */
1523 hit->index = index;
1524 hit->dist = result.lambda * math::distance(ray_start, ray_end);
1525 /* Don't need the hit point, only the lambda. */
1526 hit->no[0] = result.lambda;
1527 };
1528
1529 /* Ray-cast in the forward direction. Ignores intersections with neighboring lines. */
1530 auto do_raycast = [&](const int index_back,
1531 const int index,
1532 const int index_forward,
1533 float &r_lambda) -> bool {
1534 if (index_forward < 0) {
1535 return false;
1536 }
1537
1538 const float2 start = screen_space_positions[index];
1539 const float2 end = screen_space_positions[index_forward];
1540 float length;
1541 const float2 dir = math::normalize_and_get_length(end - start, length);
1542
1543 RaycastArgs args = {tree_data,
1544 index_back >= 0 ? int(tree_data_range[index_back]) : -1,
1545 int(tree_data_range[index]),
1546 index_forward >= 0 ? int(tree_data_range[index_forward]) : -1};
1547 BVHTreeRayHit hit;
1548 hit.index = -1;
1549 hit.dist = FLT_MAX;
1551 tree_data.tree, float3(start, 0.0f), float3(dir, 0.0f), length, &hit, callback, &args);
1552
1553 if (hit.index >= 0) {
1554 r_lambda = hit.no[0];
1555 return true;
1556 }
1557 return false;
1558 };
1559
1560 r_hits.fill(false);
1561 if (r_first_intersect_factors) {
1562 r_first_intersect_factors->fill(-1.0f);
1563 }
1564 if (r_last_intersect_factors) {
1565 r_last_intersect_factors->fill(-1.0f);
1566 }
1567
1568 curve_mask.foreach_index(GrainSize(1024), [&](const int i_curve) {
1569 const bool is_cyclic = cyclic[i_curve];
1570 const IndexRange points = points_by_curve[i_curve];
1571
1572 for (const int i_point : points) {
1573 const int i_prev_point = (i_point == points.first() ? (is_cyclic ? points.last() : -1) :
1574 i_point - 1);
1575 const int i_next_point = (i_point == points.last() ? (is_cyclic ? points.first() : -1) :
1576 i_point + 1);
1577 float lambda;
1578 /* Find first intersections by raycast from each point to the next. */
1579 if (do_raycast(i_prev_point, i_point, i_next_point, lambda)) {
1580 r_hits[i_point] = true;
1581 if (r_first_intersect_factors) {
1582 (*r_first_intersect_factors)[i_point] = lambda;
1583 }
1584 }
1585 /* Find last intersections by raycast from each point to the previous. */
1586 if (do_raycast(i_next_point, i_point, i_prev_point, lambda)) {
1587 /* Note: factor = (1 - lambda) because of reverse raycast. */
1588 if (r_last_intersect_factors) {
1589 (*r_last_intersect_factors)[i_point] = 1.0f - lambda;
1590 }
1591 }
1592 }
1593 });
1594}
1595
1597 const IndexMask &curve_mask,
1598 const Span<float2> screen_space_positions,
1599 const Curves2DBVHTree &tree_data,
1600 const IndexRange tree_data_range)
1601{
1602 const OffsetIndices points_by_curve = curves.points_by_curve();
1603 const VArray<bool> cyclic = curves.cyclic();
1604
1605 Array<bool> hits(curves.points_num());
1606 Array<float> first_hit_factors(curves.points_num());
1607 Array<float> last_hit_factors(curves.points_num());
1609 curve_mask,
1610 screen_space_positions,
1611 tree_data,
1612 tree_data_range,
1613 hits,
1614 first_hit_factors,
1615 last_hit_factors);
1616
1617 IndexMaskMemory memory;
1618 const IndexMask hit_mask = IndexMask::from_bools(hits, memory);
1619
1620 /* Count number of segments in each curve.
1621 * This is needed to write to the correct segments range for each curve. */
1623 result.segment_offsets.reinitialize(curves.curves_num() + 1);
1624 /* Only segments with hits are written to, initialize all to zero. */
1625 result.segment_offsets.fill(0);
1626 curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
1627 const IndexRange points = points_by_curve[curve_i];
1628 const IndexMask curve_hit_mask = hit_mask.slice_content(points);
1629 const bool is_cyclic = cyclic[curve_i];
1630
1631 /* Each hit splits a segment in two. Non-cyclic curves add the curve start point as a segment
1632 * start point. */
1633 result.segment_offsets[curve_i] = (is_cyclic ? 0 : 1) + curve_hit_mask.size();
1634 });
1636 result.segment_offsets);
1637
1638 const int num_segments = segments_by_curve.total_size();
1639 result.segment_start_points.reinitialize(num_segments);
1640 result.segment_start_fractions.reinitialize(num_segments);
1641
1642 curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
1643 const IndexRange points = points_by_curve[curve_i];
1644 const IndexMask curve_hit_mask = hit_mask.slice_content(points);
1645 const bool is_cyclic = cyclic[curve_i];
1646 const IndexRange segments = segments_by_curve[curve_i];
1647 const int hit_segments_start = (is_cyclic ? 0 : 1);
1648
1649 if (segments.is_empty()) {
1650 return;
1651 }
1652
1653 /* Add curve start a segment. */
1654 if (!is_cyclic) {
1655 result.segment_start_points[segments[0]] = points.first();
1656 result.segment_start_fractions[segments[0]] = 0.0f;
1657 }
1658
1659 curve_hit_mask.foreach_index([&](const int point_i, const int hit_i) {
1660 result.segment_start_points[segments[hit_segments_start + hit_i]] = point_i;
1661 result.segment_start_fractions[segments[hit_segments_start + hit_i]] =
1662 first_hit_factors[point_i];
1663 });
1664 });
1665
1666 return result;
1667}
1668
1669} // namespace blender::ed::greasepencil
Low-level operations for curves.
Low-level operations for curves.
Low-level operations for grease pencil.
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
BVHTree * BLI_bvhtree_new(int maxsize, float epsilon, char tree_type, char axis)
void BLI_bvhtree_balance(BVHTree *tree)
void BLI_bvhtree_free(BVHTree *tree)
void BLI_bvhtree_insert(BVHTree *tree, int index, const float co[3], int numpoints)
void(*)(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit) BVHTree_RayCastCallback
int BLI_bvhtree_ray_cast(const BVHTree *tree, const float co[3], const float dir[3], float radius, BVHTreeRayHit *hit, BVHTree_RayCastCallback callback, void *userdata)
A KD-tree for nearest neighbor search.
void BLI_kdtree_nd_ free(KDTree *tree)
#define M_PI
void BLI_rcti_init_minmax(struct rcti *rect)
Definition rct.cc:474
void BLI_rcti_pad(struct rcti *rect, int pad_x, int pad_y)
Definition rct.cc:629
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
void BLI_rcti_do_minmax_v(struct rcti *rect, const int xy[2])
Definition rct.cc:486
unsigned int uint
#define ELEM(...)
@ NURBS_KNOT_MODE_NORMAL
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
blender::float4x4 ED_view3d_ob_project_mat_get_from_obmat(const RegionView3D *rv3d, const blender::float4x4 &obmat)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
constexpr IndexRange after(int64_t n) const
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
const T * data() const
Definition BLI_array.hh:301
IndexRange index_range() const
Definition BLI_array.hh:349
static IndexMask from_indices(Span< T > indices, IndexMaskMemory &memory)
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr bool is_empty() const
constexpr int64_t start() const
constexpr IndexRange slice(int64_t start, int64_t size) const
constexpr IndexRange index_range() const
constexpr IndexRange drop_front(int64_t n) 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 T & first() const
Definition BLI_span.hh:679
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
bool is_empty() const
Definition BLI_stack.hh:308
void push(const T &value)
Definition BLI_stack.hh:213
int64_t size() const
void append(const T &value)
const T & last(const int64_t n=0) const
bool is_empty() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
Span< float3 > positions() const
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
VArray< bool > cyclic() const
const bke::CurvesGeometry & strokes() const
float4x4 to_world_space(const Object &object) const
void to_indices(MutableSpan< T > r_indices) const
IndexMask slice_content(IndexRange range) const
void foreach_index_optimized(Fn &&fn) const
void foreach_index(Fn &&fn) const
static bool is_cyclic(const Nurb *nu)
KDTree_3d * tree
static ushort indices[]
uint pos
VecBase< float, 2 > float2
VecBase< int, 2 > int2
VecBase< float, 3 > float3
float length(VecOp< float, D >) RET
float distance(VecOp< float, D >, VecOp< float, D >) RET
static void error(const char *str)
void fill_index_range(MutableSpan< T > span, const T start=0)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
static constexpr float DISTANCE_FACTOR_THRESHOLD
bke::CurvesGeometry trim_curve_segments(const bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, const Span< rcti > screen_space_curve_bounds, const IndexMask &curve_selection, const Vector< Vector< int > > &selected_points_in_curves, const bool keep_caps)
static void expand_trim_segment(Segment &segment, const bke::CurvesGeometry &src, const Span< bool > is_intersected_after_point, const Span< float2 > intersection_distance, MutableSpan< bool > point_is_in_segment)
static float get_intersection_distance_of_segments(const float2 &co_a, const float2 &co_b, const float2 &co_c, const float2 &co_d)
static void expand_trim_segment_direction(Segment &segment, const int direction, const bke::CurvesGeometry &src, const Span< bool > is_intersected_after_point, const Span< float2 > intersection_distance, MutableSpan< bool > point_is_in_segment)
static void get_intersections_of_curve_with_curves(const int src_curve, const bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, const Span< rcti > screen_space_curve_bounds, MutableSpan< bool > r_is_intersected_after_point, MutableSpan< float2 > r_intersection_distance)
void find_curve_intersections(const bke::CurvesGeometry &curves, const IndexMask &curve_mask, const Span< float2 > screen_space_positions, const Curves2DBVHTree &tree_data, const IndexRange tree_data_range, MutableSpan< bool > r_hits, std::optional< MutableSpan< float > > r_first_intersect_factors, std::optional< MutableSpan< float > > r_last_intersect_factors)
blender::bke::CurvesGeometry curves_merge_by_distance(const bke::CurvesGeometry &src_curves, const float merge_distance, const IndexMask &selection, const bke::AttributeFilter &attribute_filter)
int64_t ramer_douglas_peucker_simplify(const IndexRange range, const float epsilon, const FunctionRef< float(int64_t, int64_t, int64_t)> dist_function, MutableSpan< bool > points_to_delete)
static void generate_circle_from_point(const float3 &pt, const float radius, const int corner_subdivisions, const int src_point_index, Vector< float3 > &r_perimeter, Vector< int > &r_src_indices)
static void generate_corner(const float3 &pt_a, const float3 &pt_b, const float3 &pt_c, const float radius, const int corner_subdivisions, const int src_point_index, Vector< float3 > &r_perimeter, Vector< int > &r_src_indices)
static void generate_arc_from_point_to_point(const float3 &from, const float3 &to, const float3 &center_pt, const int corner_subdivisions, const int src_point_index, Vector< float3 > &r_perimeter, Vector< int > &r_src_indices)
void free_curves_2d_bvh_data(Curves2DBVHTree &data)
CurveSegmentsData find_curve_segments(const bke::CurvesGeometry &curves, const IndexMask &curve_mask, const Span< float2 > screen_space_positions, const Curves2DBVHTree &tree_data, const IndexRange tree_data_range)
IndexMask polyline_detect_corners(Span< float2 > points, const float radius_min, const float radius_max, const int samples_max, const float angle_threshold, IndexMaskMemory &memory)
static void generate_stroke_perimeter(const Span< float3 > all_positions, const Span< float > all_radii, const IndexRange points, const int corner_subdivisions, const bool is_cyclic, const bool use_caps, const eGPDstroke_Caps start_cap_type, const eGPDstroke_Caps end_cap_type, const float outline_offset, Vector< float3 > &r_perimeter, Vector< int > &r_point_counts, Vector< int > &r_point_indices)
Array< PointTransferData > compute_topology_change(const bke::CurvesGeometry &src, bke::CurvesGeometry &dst, const Span< Vector< PointTransferData > > src_to_dst_points, const bool keep_caps)
static void generate_cap(const float3 &point, const float3 &tangent, const float radius, const int corner_subdivisions, const eGPDstroke_Caps cap_type, const int src_point_index, Vector< float3 > &r_perimeter, Vector< int > &r_src_indices)
bke::CurvesGeometry curves_merge_endpoints_by_distance(const ARegion &region, const bke::CurvesGeometry &src_curves, const float4x4 &layer_to_world, const float merge_distance, const IndexMask &selection, const bke::AttributeFilter &attribute_filter)
Array< float2 > polyline_fit_curve(Span< float2 > points, const float error_threshold, const IndexMask &corner_mask)
Curves2DBVHTree build_curves_2d_bvh_from_visible(const ViewContext &vc, const Object &object, const GreasePencil &grease_pencil, Span< MutableDrawingInfo > drawings, const int frame_number)
int curve_merge_by_distance(const IndexRange points, const Span< float > distances, const IndexMask &selection, const float merge_distance, MutableSpan< int > r_merge_indices)
bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &drawing, const IndexMask &strokes, const float4x4 &transform, const int corner_subdivisions, const float outline_radius, const float outline_offset, const int material_index)
T cos(const AngleRadianBase< T > &a)
T clamp(const T &a, const T &min, const T &max)
isect_result< VecBase< T, Size > > isect_seg_seg(const VecBase< T, Size > &v1, const VecBase< T, Size > &v2, const VecBase< T, Size > &v3, const VecBase< T, Size > &v4)
T distance(const T &a, const T &b)
T length(const VecBase< T, Size > &a)
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T average(const VecBase< T, Size > &a)
T min(const T &a, const T &b)
bool is_zero(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
T atan2(const T &y, const T &x)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > to_scale(const MatBase< T, NumCol, NumRow > &mat)
T sin(const AngleRadianBase< T > &a)
T max(const T &a, const T &b)
T abs(const T &a)
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:93
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
RegionView3D * rv3d
Definition ED_view3d.hh:80
ARegion * region
Definition ED_view3d.hh:77
VecBase< T, 2 > xy() const
Segment * create_segment(const int curve, const int point)
i
Definition text_draw.cc:230
int xy[2]
Definition wm_draw.cc:174