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