Blender V4.3
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
9#include <algorithm>
10#include <limits>
11
12#include "BLI_array_utils.hh"
14#include "BLI_kdopbvh.h"
15#include "BLI_kdtree.h"
16#include "BLI_math_vector.hh"
17#include "BLI_offset_indices.hh"
18#include "BLI_rect.h"
19#include "BLI_stack.hh"
20#include "BLI_task.hh"
21
22#include "BKE_attribute.hh"
23#include "BKE_curves.hh"
24#include "BKE_curves_utils.hh"
25#include "BKE_grease_pencil.hh"
26
27#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
211/* NOTE: The code here is an adapted version of #blender::geometry::point_merge_by_distance. */
213 const float merge_distance,
214 const IndexMask &selection,
215 const bke::AttributeFilter &attribute_filter)
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());
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 VArraySpan<T> src = src_attribute.varray.typed<T>();
320
321 threading::parallel_for(dst_curves.points_range(), 1024, [&](IndexRange range) {
322 for (const int dst_point_i : range) {
323 /* Create a separate mixer for every point to avoid allocating temporary buffers
324 * in the mixer the size of the result curves and to improve memory locality. */
325 bke::attribute_math::DefaultMixer<T> mixer{dst_attribute.span.slice(dst_point_i, 1)};
326
327 Span<int> src_merge_indices = merge_map_indices.as_span().slice(
328 map_offsets[dst_point_i]);
329 for (const int src_point_i : src_merge_indices) {
330 mixer.mix_in(0, src[src_point_i]);
331 }
332
333 mixer.finalize();
334 }
335 });
336
337 dst_attribute.finish();
338 }
339 });
340 });
341
342 return dst_curves;
343}
344
346 const ARegion &region,
347 const bke::CurvesGeometry &src_curves,
348 const float4x4 &layer_to_world,
349 const float merge_distance,
350 const IndexMask &selection,
351 const bke::AttributeFilter &attribute_filter)
352{
353 const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
354 const Span<float3> src_positions = src_curves.positions();
355 const float merge_distance_squared = merge_distance * merge_distance;
356
357 Array<float2> screen_start_points(src_curves.curves_num());
358 Array<float2> screen_end_points(src_curves.curves_num());
359 /* For comparing screen space positions use a 2D KDTree. Each curve adds 2 points. */
360 KDTree_2d *tree = BLI_kdtree_2d_new(2 * src_curves.curves_num());
361
362 threading::parallel_for(src_curves.curves_range(), 1024, [&](const IndexRange range) {
363 for (const int src_i : range) {
364 const IndexRange points = src_points_by_curve[src_i];
365 const float3 start_pos = src_positions[points.first()];
366 const float3 end_pos = src_positions[points.last()];
367 const float3 start_world = math::transform_point(layer_to_world, start_pos);
368 const float3 end_world = math::transform_point(layer_to_world, end_pos);
369
370 ED_view3d_project_float_global(
371 &region, start_world, screen_start_points[src_i], V3D_PROJ_TEST_NOP);
372 ED_view3d_project_float_global(
373 &region, end_world, screen_end_points[src_i], V3D_PROJ_TEST_NOP);
374 }
375 });
376 /* Note: KDTree insertion is not thread-safe, don't parallelize this. */
377 for (const int src_i : src_curves.curves_range()) {
378 BLI_kdtree_2d_insert(tree, src_i * 2, screen_start_points[src_i]);
379 BLI_kdtree_2d_insert(tree, src_i * 2 + 1, screen_end_points[src_i]);
380 }
381 BLI_kdtree_2d_balance(tree);
382
383 Array<int> connect_to_curve(src_curves.curves_num(), -1);
384 Array<bool> flip_direction(src_curves.curves_num(), false);
385 selection.foreach_index(GrainSize(512), [&](const int src_i) {
386 const float2 &start_co = screen_start_points[src_i];
387 const float2 &end_co = screen_end_points[src_i];
388 /* Index of KDTree points so they can be ignored. */
389 const int start_index = src_i * 2;
390 const int end_index = src_i * 2 + 1;
391
392 KDTreeNearest_2d nearest_start, nearest_end;
393 const bool is_start_ok = (BLI_kdtree_2d_find_nearest_cb_cpp(
394 tree,
395 start_co,
396 &nearest_start,
397 [&](const int other, const float * /*co*/, const float dist_sq) {
398 if (start_index == other || dist_sq > merge_distance_squared) {
399 return 0;
400 }
401 return 1;
402 }) != -1);
403 const bool is_end_ok = (BLI_kdtree_2d_find_nearest_cb_cpp(
404 tree,
405 end_co,
406 &nearest_end,
407 [&](const int other, const float * /*co*/, const float dist_sq) {
408 if (end_index == other || dist_sq > merge_distance_squared) {
409 return 0;
410 }
411 return 1;
412 }) != -1);
413
414 if (is_start_ok) {
415 const int curve_index = nearest_start.index / 2;
416 const bool is_end_point = bool(nearest_start.index % 2);
417 if (connect_to_curve[curve_index] < 0) {
418 connect_to_curve[curve_index] = src_i;
419 flip_direction[curve_index] = !is_end_point;
420 }
421 }
422 if (is_end_ok) {
423 const int curve_index = nearest_end.index / 2;
424 const bool is_end_point = bool(nearest_end.index % 2);
425 if (connect_to_curve[src_i] < 0) {
426 connect_to_curve[src_i] = curve_index;
427 flip_direction[curve_index] = is_end_point;
428 }
429 }
430 });
431 BLI_kdtree_2d_free(tree);
432
433 return geometry::curves_merge_endpoints(
434 src_curves, connect_to_curve, flip_direction, attribute_filter);
435}
436
437/* Generate points in an counter-clockwise arc between two directions. */
439 const float3 &to,
440 const float3 &center_pt,
441 const int corner_subdivisions,
442 const int src_point_index,
443 Vector<float3> &r_perimeter,
444 Vector<int> &r_src_indices)
445{
446 const float3 vec_from = from - center_pt;
447 const float3 vec_to = to - center_pt;
448 if (math::is_zero(vec_from) || math::is_zero(vec_to)) {
449 r_perimeter.append(center_pt);
450 r_src_indices.append(src_point_index);
451 return;
452 }
453
454 const float cos_angle = math::dot(vec_from.xy(), vec_to.xy());
455 const float sin_angle = vec_from.x * vec_to.y - vec_from.y * vec_to.x;
456 /* Compute angle in range [0, 2pi) so that the rotation is always counter-clockwise. */
457 const float angle = math::atan2(-sin_angle, -cos_angle) + M_PI;
458
459 /* Number of points is 2^(n+1) + 1 on half a circle (n=corner_subdivisions)
460 * so we multiply by (angle / pi) to get the right amount of
461 * points to insert. */
462 const int num_full = (1 << (corner_subdivisions + 1)) + 1;
463 const int num_points = num_full * math::abs(angle) / M_PI;
464 if (num_points < 2) {
465 r_perimeter.append(center_pt + vec_from);
466 r_src_indices.append(src_point_index);
467 return;
468 }
469 const float delta_angle = angle / float(num_points - 1);
470 const float delta_cos = math::cos(delta_angle);
471 const float delta_sin = math::sin(delta_angle);
472
473 float3 vec = vec_from;
474 for ([[maybe_unused]] const int i : IndexRange(num_points)) {
475 r_perimeter.append(center_pt + vec);
476 r_src_indices.append(src_point_index);
477
478 const float x = delta_cos * vec.x - delta_sin * vec.y;
479 const float y = delta_sin * vec.x + delta_cos * vec.y;
480 vec = float3(x, y, 0.0f);
481 }
482}
483
484/* Generate a semi-circle around a point, opposite the direction. */
485static void generate_cap(const float3 &point,
486 const float3 &tangent,
487 const float radius,
488 const int corner_subdivisions,
489 const eGPDstroke_Caps cap_type,
490 const int src_point_index,
491 Vector<float3> &r_perimeter,
492 Vector<int> &r_src_indices)
493{
494 const float3 normal = math::normalize(float3{tangent.y, -tangent.x, 0.0f});
495 switch (cap_type) {
497 generate_arc_from_point_to_point(point - normal * radius,
498 point + normal * radius,
499 point,
500 corner_subdivisions,
501 src_point_index,
502 r_perimeter,
503 r_src_indices);
504 break;
506 r_perimeter.append(point - normal * radius);
507 r_src_indices.append(src_point_index);
508 r_perimeter.append(point + normal * radius);
509 r_src_indices.append(src_point_index);
510 break;
513 break;
514 }
515}
516
517/* Generate a corner between two segments, with a rounded outer perimeter.
518 * NOTE: The perimeter is considered to be to the right hand side of the stroke. The left side
519 * perimeter can be generated by reversing the order of points. */
520static void generate_corner(const float3 &pt_a,
521 const float3 &pt_b,
522 const float3 &pt_c,
523 const float radius,
524 const int corner_subdivisions,
525 const int src_point_index,
526 Vector<float3> &r_perimeter,
527 Vector<int> &r_src_indices)
528{
529 const float length = math::length(pt_c - pt_b);
530 const float length_prev = math::length(pt_b - pt_a);
531 const float2 tangent = math::normalize((pt_c - pt_b).xy());
532 const float2 tangent_prev = math::normalize((pt_b - pt_a).xy());
533 const float3 normal = {tangent.y, -tangent.x, 0.0f};
534 const float3 normal_prev = {tangent_prev.y, -tangent_prev.x, 0.0f};
535
536 const float sin_angle = tangent_prev.x * tangent.y - tangent_prev.y * tangent.x;
537 /* Whether the corner is an inside or outside corner.
538 * This determines whether an arc is added or a single miter point. */
539 const bool is_outside_corner = (sin_angle >= 0.0f);
540 if (is_outside_corner) {
541 generate_arc_from_point_to_point(pt_b + normal_prev * radius,
542 pt_b + normal * radius,
543 pt_b,
544 corner_subdivisions,
545 src_point_index,
546 r_perimeter,
547 r_src_indices);
548 }
549 else {
550 const float2 avg_tangent = math::normalize(tangent_prev + tangent);
551 const float3 miter = {avg_tangent.y, -avg_tangent.x, 0.0f};
552 const float miter_invscale = math::dot(normal, miter);
553
554 /* Avoid division by tiny values for steep angles. */
555 const float3 miter_point = (radius < length * miter_invscale &&
556 radius < length_prev * miter_invscale) ?
557 pt_b + miter * radius / miter_invscale :
558 pt_b + miter * radius;
559
560 r_perimeter.append(miter_point);
561 r_src_indices.append(src_point_index);
562 }
563}
564
565static void generate_stroke_perimeter(const Span<float3> all_positions,
566 const Span<float> all_radii,
567 const IndexRange points,
568 const int corner_subdivisions,
569 const bool is_cyclic,
570 const bool use_caps,
571 const eGPDstroke_Caps start_cap_type,
572 const eGPDstroke_Caps end_cap_type,
573 const float outline_offset,
574 Vector<float3> &r_perimeter,
575 Vector<int> &r_point_counts,
576 Vector<int> &r_point_indices)
577{
578 const Span<float3> positions = all_positions.slice(points);
579 const int point_num = points.size();
580 if (point_num < 2) {
581 return;
582 }
583
584 auto add_corner = [&](const int a, const int b, const int c) {
585 const int point = points[b];
586 const float3 pt_a = positions[a];
587 const float3 pt_b = positions[b];
588 const float3 pt_c = positions[c];
589 const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
591 pt_a, pt_b, pt_c, radius, corner_subdivisions, point, r_perimeter, r_point_indices);
592 };
593 auto add_cap = [&](const int center_i, const int next_i, const eGPDstroke_Caps cap_type) {
594 const int point = points[center_i];
595 const float3 &center = positions[center_i];
596 const float3 dir = math::normalize(positions[next_i] - center);
597 const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
599 center, dir, radius, corner_subdivisions, cap_type, point, r_perimeter, r_point_indices);
600 };
601
602 /* Creates a single cyclic curve with end caps. */
603 if (use_caps) {
604 /* Open curves generate a start and end cap and a connecting stroke on either side. */
605 const int perimeter_start = r_perimeter.size();
606
607 /* Start cap. */
608 add_cap(0, 1, start_cap_type);
609
610 /* Right perimeter half. */
611 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
612 add_corner(i - 1, i, i + 1);
613 }
614 if (is_cyclic) {
615 add_corner(point_num - 2, point_num - 1, 0);
616 }
617
618 /* End cap. */
619 if (is_cyclic) {
620 /* End point is same as start point. */
621 add_cap(0, point_num - 1, end_cap_type);
622 }
623 else {
624 /* End point is last point of the curve. */
625 add_cap(point_num - 1, point_num - 2, end_cap_type);
626 }
627
628 /* Left perimeter half. */
629 if (is_cyclic) {
630 add_corner(0, point_num - 1, point_num - 2);
631 }
632 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
633 add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
634 }
635
636 const int perimeter_count = r_perimeter.size() - perimeter_start;
637 if (perimeter_count > 0) {
638 r_point_counts.append(perimeter_count);
639 }
640 }
641 else {
642 /* Generate separate "inside" and an "outside" perimeter curves.
643 * The distinction is arbitrary, called left/right here. */
644
645 /* Right side perimeter. */
646 const int left_perimeter_start = r_perimeter.size();
647 add_corner(point_num - 1, 0, 1);
648 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
649 add_corner(i - 1, i, i + 1);
650 }
651 add_corner(point_num - 2, point_num - 1, 0);
652 const int left_perimeter_count = r_perimeter.size() - left_perimeter_start;
653 if (left_perimeter_count > 0) {
654 r_point_counts.append(left_perimeter_count);
655 }
656
657 /* Left side perimeter. */
658 const int right_perimeter_start = r_perimeter.size();
659 add_corner(0, point_num - 1, point_num - 2);
660 for (const int i : points.index_range().drop_front(1).drop_back(1)) {
661 add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
662 }
663 add_corner(1, 0, point_num - 1);
664 const int right_perimeter_count = r_perimeter.size() - right_perimeter_start;
665 if (right_perimeter_count > 0) {
666 r_point_counts.append(right_perimeter_count);
667 }
668 }
669}
670
672 /* New points per curve count. */
674 /* New point coordinates. */
676 /* Source curve index. */
678 /* Source point index. */
680};
681
683 const IndexMask &strokes,
684 const float4x4 &transform,
685 const int corner_subdivisions,
686 const float outline_radius,
687 const float outline_offset,
688 const int material_index)
689{
690 const bke::CurvesGeometry &src_curves = drawing.strokes();
691 Span<float3> src_positions = src_curves.positions();
692 bke::AttributeAccessor src_attributes = src_curves.attributes();
693 const VArray<float> src_radii = drawing.radii();
694 const VArray<bool> src_cyclic = *src_attributes.lookup_or_default(
695 "cyclic", bke::AttrDomain::Curve, false);
696 const VArray<int8_t> src_start_caps = *src_attributes.lookup_or_default<int8_t>(
697 "start_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
698 const VArray<int8_t> src_end_caps = *src_attributes.lookup_or_default<int8_t>(
699 "end_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
700 const VArray<int> src_material_index = *src_attributes.lookup_or_default(
701 "material_index", bke::AttrDomain::Curve, 0);
702
703 /* Transform positions and radii. */
704 const float scale = math::average(math::to_scale(transform));
705 Array<float3> transformed_positions(src_positions.size());
706 Array<float> transformed_radii(src_radii.size());
707 threading::parallel_for(transformed_positions.index_range(), 4096, [&](const IndexRange range) {
708 for (const int i : range) {
709 transformed_positions[i] = math::transform_point(transform, src_positions[i]);
710 }
711 });
712 threading::parallel_for(transformed_radii.index_range(), 4096, [&](const IndexRange range) {
713 for (const int i : range) {
714 transformed_radii[i] = src_radii[i] * scale;
715 }
716 });
717
718 const float4x4 transform_inv = math::invert(transform);
720 strokes.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
721 PerimeterData &data = thread_data.local();
722
723 const bool is_cyclic_curve = src_cyclic[curve_i];
724 /* NOTE: Cyclic curves would better be represented by a cyclic perimeter without end caps, but
725 * we always generate caps for compatibility with GPv2. Fill materials cannot create holes, so
726 * a cyclic outline does not work well. */
727 const bool use_caps = true ;
728
729 const int prev_point_num = data.positions.size();
730 const int prev_curve_num = data.point_counts.size();
731 const IndexRange points = src_curves.points_by_curve()[curve_i];
732
733 generate_stroke_perimeter(transformed_positions,
734 transformed_radii,
735 points,
736 corner_subdivisions,
737 is_cyclic_curve,
738 use_caps,
739 eGPDstroke_Caps(src_start_caps[curve_i]),
740 eGPDstroke_Caps(src_end_caps[curve_i]),
741 outline_offset,
742 data.positions,
743 data.point_counts,
744 data.point_indices);
745
746 /* Transform perimeter positions back into object space. */
747 for (float3 &pos : data.positions.as_mutable_span().drop_front(prev_point_num)) {
748 pos = math::transform_point(transform_inv, pos);
749 }
750
751 data.curve_indices.append_n_times(curve_i, data.point_counts.size() - prev_curve_num);
752 });
753
754 int dst_curve_num = 0;
755 int dst_point_num = 0;
756 for (const PerimeterData &data : thread_data) {
757 BLI_assert(data.point_counts.size() == data.curve_indices.size());
758 BLI_assert(data.positions.size() == data.point_indices.size());
759 dst_curve_num += data.point_counts.size();
760 dst_point_num += data.positions.size();
761 }
762
763 bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
764 if (dst_point_num == 0 || dst_curve_num == 0) {
765 return dst_curves;
766 }
767
768 bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
769 bke::SpanAttributeWriter<bool> dst_cyclic = dst_attributes.lookup_or_add_for_write_span<bool>(
770 "cyclic", bke::AttrDomain::Curve);
771 bke::SpanAttributeWriter<int> dst_material = dst_attributes.lookup_or_add_for_write_span<int>(
772 "material_index", bke::AttrDomain::Curve);
773 bke::SpanAttributeWriter<float> dst_radius = dst_attributes.lookup_or_add_for_write_span<float>(
774 "radius", bke::AttrDomain::Point);
775 const MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
776 const MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
777 /* Source indices for attribute mapping. */
778 Array<int> dst_curve_map(dst_curve_num);
779 Array<int> dst_point_map(dst_point_num);
780
781 IndexRange curves;
782 IndexRange points;
783 for (const PerimeterData &data : thread_data) {
784 curves = curves.after(data.point_counts.size());
785 points = points.after(data.positions.size());
786
787 /* Append curve data. */
788 dst_curve_map.as_mutable_span().slice(curves).copy_from(data.curve_indices);
789 /* Curve offsets are accumulated below. */
790 dst_offsets.slice(curves).copy_from(data.point_counts);
791 dst_cyclic.span.slice(curves).fill(true);
792 if (material_index >= 0) {
793 dst_material.span.slice(curves).fill(material_index);
794 }
795 else {
796 for (const int i : curves.index_range()) {
797 dst_material.span[curves[i]] = src_material_index[data.curve_indices[i]];
798 }
799 }
800
801 /* Append point data. */
802 dst_positions.slice(points).copy_from(data.positions);
803 dst_point_map.as_mutable_span().slice(points).copy_from(data.point_indices);
804 dst_radius.span.slice(points).fill(outline_radius);
805 }
806 offset_indices::accumulate_counts_to_offsets(dst_curves.offsets_for_write());
807
808 bke::gather_attributes(src_attributes,
809 bke::AttrDomain::Point,
810 bke::AttrDomain::Point,
811 bke::attribute_filter_from_skip_ref({"position", "radius"}),
812 dst_point_map,
813 dst_attributes);
814 bke::gather_attributes(src_attributes,
815 bke::AttrDomain::Curve,
816 bke::AttrDomain::Curve,
817 bke::attribute_filter_from_skip_ref({"cyclic", "material_index"}),
818 dst_curve_map,
819 dst_attributes);
820
821 dst_cyclic.finish();
822 dst_material.finish();
823 dst_radius.finish();
824 dst_curves.update_curve_types();
825
826 return dst_curves;
827}
828
829namespace trim {
830
831enum Side : uint8_t { Start = 0, End = 1 };
832enum Distance : uint8_t { Min = 0, Max = 1 };
833
834/* When looking for intersections, we need a little padding, otherwise we could miss curves
835 * that intersect for the eye, but not in hard numbers. */
836static constexpr int BBOX_PADDING = 2;
837
838/* When creating new intersection points, we don't want them too close to their neighbor,
839 * because that clutters the geometry. This threshold defines what 'too close' is. */
840static constexpr float DISTANCE_FACTOR_THRESHOLD = 0.01f;
841
846struct Segment {
847 /* Curve index. */
848 int curve;
849
850 /* Point range of the segment: starting point and end point. Matches the point offsets
851 * in a CurvesGeometry. */
852 int point_range[2];
853
854 /* The normalized distance where the trim segment is intersected by another curve.
855 * For the outer ends of the trim segment the intersection distance is given between:
856 * - [start point - 1] and [start point]
857 * - [end point] and [end point + 1]
858 */
859 float intersection_distance[2];
860
861 /* Intersection flag: true if the start/end point of the segment is the result of an
862 * intersection, false if the point is the outer end of a curve. */
863 bool is_intersected[2];
864};
865
870struct Segments {
871 /* Collection of trim segments: parts of curves between other curves, to be removed from the
872 * geometry. */
874
875 /* Create an initial trim segment with a point range of one point. */
876 Segment *create_segment(const int curve, const int point)
877 {
878 Segment segment{};
879 segment.curve = curve;
880 segment.point_range[Side::Start] = point;
881 segment.point_range[Side::End] = point;
882
883 this->segments.append(std::move(segment));
884
885 return &this->segments.last();
886 }
887
888 /* Merge trim segments that are next to each other. */
890 {
891 Vector<Segment> merged_segments;
892
893 /* Note on performance: we deal with small numbers here, so we can afford the double loop. */
894 while (!this->segments.is_empty()) {
895 Segment a = this->segments.pop_last();
896
897 bool merged = false;
898 for (Segment &b : merged_segments) {
899 if (a.curve != b.curve) {
900 continue;
901 }
902 /* The segments overlap when the points ranges have overlap or are exactly adjacent. */
903 if ((a.point_range[Side::Start] <= b.point_range[Side::End] &&
904 a.point_range[Side::End] >= b.point_range[Side::Start]) ||
905 (a.point_range[Side::End] == b.point_range[Side::Start] - 1) ||
906 (b.point_range[Side::End] == a.point_range[Side::Start] - 1))
907 {
908 /* Merge the point ranges and related intersection data. */
909 const bool take_start_a = a.point_range[Side::Start] < b.point_range[Side::Start];
910 const bool take_end_a = a.point_range[Side::End] > b.point_range[Side::End];
911 b.point_range[Side::Start] = take_start_a ? a.point_range[Side::Start] :
912 b.point_range[Side::Start];
913 b.point_range[Side::End] = take_end_a ? a.point_range[Side::End] :
914 b.point_range[Side::End];
915 b.is_intersected[Side::Start] = take_start_a ? a.is_intersected[Side::Start] :
916 b.is_intersected[Side::Start];
917 b.is_intersected[Side::End] = take_end_a ? a.is_intersected[Side::End] :
918 b.is_intersected[Side::End];
919 b.intersection_distance[Side::Start] = take_start_a ?
920 a.intersection_distance[Side::Start] :
921 b.intersection_distance[Side::Start];
922 b.intersection_distance[Side::End] = take_end_a ? a.intersection_distance[Side::End] :
923 b.intersection_distance[Side::End];
924 merged = true;
925 break;
926 }
927 }
928 if (!merged) {
929 merged_segments.append(std::move(a));
930 }
931 }
932
933 this->segments = merged_segments;
934 }
935};
936
943 const float2 &co_b,
944 const float2 &co_c,
945 const float2 &co_d)
946{
947 /* Get intersection point. */
948 const float a1 = co_b[1] - co_a[1];
949 const float b1 = co_a[0] - co_b[0];
950 const float c1 = a1 * co_a[0] + b1 * co_a[1];
951
952 const float a2 = co_d[1] - co_c[1];
953 const float b2 = co_c[0] - co_d[0];
954 const float c2 = a2 * co_c[0] + b2 * co_c[1];
955
956 const float det = float(a1 * b2 - a2 * b1);
957 if (det == 0.0f) {
958 return 0.0f;
959 }
960
961 float2 isect((b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det);
962
963 /* Get normalized distance from point a to intersection point. */
964 const float length_ab = math::length(co_b - co_a);
965 float distance = (length_ab == 0.0f ?
966 0.0f :
967 math::clamp(math::length(isect - co_a) / length_ab, 0.0f, 1.0f));
968
969 return distance;
970}
971
975static void get_intersections_of_curve_with_curves(const int src_curve,
976 const bke::CurvesGeometry &src,
977 const Span<float2> screen_space_positions,
978 const Span<rcti> screen_space_curve_bounds,
979 MutableSpan<bool> r_is_intersected_after_point,
980 MutableSpan<float2> r_intersection_distance)
981{
982 const OffsetIndices<int> points_by_curve = src.points_by_curve();
983 const VArray<bool> is_cyclic = src.cyclic();
984
985 /* Edge case: skip curve with only one point. */
986 if (points_by_curve[src_curve].size() < 2) {
987 return;
988 }
989
990 /* Loop all curve points and check for intersections between point a and point a + 1. */
991 const IndexRange src_curve_points = points_by_curve[src_curve].drop_back(
992 is_cyclic[src_curve] ? 0 : 1);
993 for (const int point_a : src_curve_points) {
994 const int point_b = (point_a == points_by_curve[src_curve].last()) ? src_curve_points.first() :
995 point_a + 1;
996
997 /* Get coordinates of segment a-b. */
998 const float2 co_a = screen_space_positions[point_a];
999 const float2 co_b = screen_space_positions[point_b];
1000 rcti bbox_ab;
1001 BLI_rcti_init_minmax(&bbox_ab);
1002 BLI_rcti_do_minmax_v(&bbox_ab, int2(co_a));
1003 BLI_rcti_do_minmax_v(&bbox_ab, int2(co_b));
1005
1006 float intersection_distance_min = FLT_MAX;
1007 float intersection_distance_max = -FLT_MAX;
1008
1009 /* Loop all curves, looking for intersecting segments. */
1010 for (const int curve : src.curves_range()) {
1011 /* Only process curves with at least two points. */
1012 if (points_by_curve[curve].size() < 2) {
1013 continue;
1014 }
1015
1016 /* Bounding box check: skip curves that don't overlap segment a-b. */
1017 if (!BLI_rcti_isect(&bbox_ab, &screen_space_curve_bounds[curve], nullptr)) {
1018 continue;
1019 }
1020
1021 /* Find intersecting curve segments. */
1022 const IndexRange points = points_by_curve[curve].drop_back(is_cyclic[curve] ? 0 : 1);
1023 for (const int point_c : points) {
1024 const int point_d = (point_c == points_by_curve[curve].last()) ? points.first() :
1025 (point_c + 1);
1026
1027 /* Don't self check. */
1028 if (curve == src_curve &&
1029 (point_a == point_c || point_a == point_d || point_b == point_c || point_b == point_d))
1030 {
1031 continue;
1032 }
1033
1034 /* Skip when bounding boxes of a-b and c-d don't overlap. */
1035 const float2 co_c = screen_space_positions[point_c];
1036 const float2 co_d = screen_space_positions[point_d];
1037 rcti bbox_cd;
1038 BLI_rcti_init_minmax(&bbox_cd);
1039 BLI_rcti_do_minmax_v(&bbox_cd, int2(co_c));
1040 BLI_rcti_do_minmax_v(&bbox_cd, int2(co_d));
1042 if (!BLI_rcti_isect(&bbox_ab, &bbox_cd, nullptr)) {
1043 continue;
1044 }
1045
1046 /* Add some padding to the line segment c-d, otherwise we could just miss an
1047 * intersection. */
1048 const float2 padding_cd = math::normalize(co_d - co_c);
1049 const float2 padded_c = co_c - padding_cd;
1050 const float2 padded_d = co_d + padding_cd;
1051
1052 /* Check for intersection. */
1053 const auto isect = math::isect_seg_seg(co_a, co_b, padded_c, padded_d);
1054 if (ELEM(isect.kind, isect.LINE_LINE_CROSS, isect.LINE_LINE_EXACT)) {
1055 /* We found an intersection, set the intersection flag for segment a-b. */
1056 r_is_intersected_after_point[point_a] = true;
1057
1058 /* Calculate the intersection factor. This is the normalized distance (0..1) of the
1059 * intersection point on line segment a-b, measured from point a. */
1060 const float normalized_distance = get_intersection_distance_of_segments(
1061 co_a, co_b, co_c, co_d);
1062 intersection_distance_min = math::min(normalized_distance, intersection_distance_min);
1063 intersection_distance_max = math::max(normalized_distance, intersection_distance_max);
1064 }
1065 }
1066 }
1067
1068 if (r_is_intersected_after_point[point_a]) {
1069 r_intersection_distance[point_a][Distance::Min] = intersection_distance_min;
1070 r_intersection_distance[point_a][Distance::Max] = intersection_distance_max;
1071 }
1072 }
1073}
1074
1080 const int direction,
1081 const bke::CurvesGeometry &src,
1082 const Span<bool> is_intersected_after_point,
1083 const Span<float2> intersection_distance,
1084 MutableSpan<bool> point_is_in_segment)
1085{
1086 const OffsetIndices<int> points_by_curve = src.points_by_curve();
1087 const int point_first = points_by_curve[segment.curve].first();
1088 const int point_last = points_by_curve[segment.curve].last();
1089
1090 const Side segment_side = (direction == 1) ? Side::End : Side::Start;
1091 int point_a = segment.point_range[segment_side];
1092
1093 bool intersected = false;
1094 segment.is_intersected[segment_side] = false;
1095
1096 /* Walk along the curve points. */
1097 while ((direction == 1 && point_a < point_last) || (direction == -1 && point_a > point_first)) {
1098 const int point_b = point_a + direction;
1099 const bool at_end_of_curve = (direction == -1 && point_b == point_first) ||
1100 (direction == 1 && point_b == point_last);
1101
1102 /* Expand segment point range. */
1103 segment.point_range[segment_side] = point_a;
1104 point_is_in_segment[point_a] = true;
1105
1106 /* Check for intersections with other curves. The intersections were established in ascending
1107 * point order, so in forward direction we look at line segment a-b, in backward direction we
1108 * look at line segment b-a. */
1109 const int intersection_point = direction == 1 ? point_a : point_b;
1110 intersected = is_intersected_after_point[intersection_point];
1111
1112 /* Avoid orphaned points at the end of a curve. */
1113 if (at_end_of_curve &&
1114 ((direction == -1 &&
1115 intersection_distance[intersection_point][Distance::Max] < DISTANCE_FACTOR_THRESHOLD) ||
1116 (direction == 1 && intersection_distance[intersection_point][Distance::Min] >
1117 (1.0f - DISTANCE_FACTOR_THRESHOLD))))
1118 {
1119 intersected = false;
1120 break;
1121 }
1122
1123 /* When we hit an intersection, store the intersection distance. Potentially, line segment
1124 * a-b can be intersected by multiple curves, so we want to fetch the first intersection
1125 * point we bumped into. In forward direction this is the minimum distance, in backward
1126 * direction the maximum. */
1127 if (intersected) {
1128 segment.is_intersected[segment_side] = true;
1129 segment.intersection_distance[segment_side] =
1130 (direction == 1) ? intersection_distance[intersection_point][Distance::Min] :
1131 intersection_distance[intersection_point][Distance::Max];
1132 break;
1133 }
1134
1135 /* Keep walking along curve. */
1136 point_a += direction;
1137 }
1138
1139 /* Adjust point range at curve ends. */
1140 if (!intersected) {
1141 if (direction == -1) {
1142 segment.point_range[Side::Start] = point_first;
1143 point_is_in_segment[point_first] = true;
1144 }
1145 else {
1146 segment.point_range[Side::End] = point_last;
1147 point_is_in_segment[point_last] = true;
1148 }
1149 }
1150}
1151
1155static void expand_trim_segment(Segment &segment,
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 int8_t directions[2] = {-1, 1};
1162 for (const int8_t direction : directions) {
1164 direction,
1165 src,
1166 is_intersected_after_point,
1167 intersection_distance,
1168 point_is_in_segment);
1169 }
1170}
1171
1173 const Span<float2> screen_space_positions,
1174 const Span<rcti> screen_space_curve_bounds,
1175 const IndexMask &curve_selection,
1176 const Vector<Vector<int>> &selected_points_in_curves,
1177 const bool keep_caps)
1178{
1179 const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
1180
1181 /* For the selected curves, find all the intersections with other curves. */
1182 const int src_points_num = src.points_num();
1183 Array<bool> is_intersected_after_point(src_points_num, false);
1184 Array<float2> intersection_distance(src_points_num);
1185 curve_selection.foreach_index(GrainSize(32), [&](const int curve_i) {
1187 src,
1188 screen_space_positions,
1189 screen_space_curve_bounds,
1190 is_intersected_after_point,
1191 intersection_distance);
1192 });
1193
1194 /* Expand the selected curve points to trim segments (the part of the curve between two
1195 * intersections). */
1196 const VArray<bool> is_cyclic = src.cyclic();
1197 Array<bool> point_is_in_segment(src_points_num, false);
1198 threading::EnumerableThreadSpecific<Segments> trim_segments_by_thread;
1199 curve_selection.foreach_index(GrainSize(32), [&](const int curve_i, const int pos) {
1200 Segments &thread_segments = trim_segments_by_thread.local();
1201 for (const int selected_point : selected_points_in_curves[pos]) {
1202 /* Skip point when it is already part of a trim segment. */
1203 if (point_is_in_segment[selected_point]) {
1204 continue;
1205 }
1206
1207 /* Create new trim segment. */
1208 Segment *segment = thread_segments.create_segment(curve_i, selected_point);
1209
1210 /* Expand the trim segment in both directions until an intersection is found or the
1211 * end of the curve is reached. */
1213 *segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
1214
1215 /* When the end of a curve is reached and the curve is cyclic, we add an extra trim
1216 * segment for the cyclic second part. */
1217 if (is_cyclic[curve_i] &&
1218 (!segment->is_intersected[Side::Start] || !segment->is_intersected[Side::End]) &&
1219 !(!segment->is_intersected[Side::Start] && !segment->is_intersected[Side::End]))
1220 {
1221 const int cyclic_outer_point = !segment->is_intersected[Side::Start] ?
1222 src_points_by_curve[curve_i].last() :
1223 src_points_by_curve[curve_i].first();
1224 segment = thread_segments.create_segment(curve_i, cyclic_outer_point);
1225
1226 /* Expand this second segment. */
1228 *segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
1229 }
1230 }
1231 });
1232 Segments trim_segments;
1233 for (Segments &thread_segments : trim_segments_by_thread) {
1234 trim_segments.segments.extend(thread_segments.segments);
1235 }
1236
1237 /* Abort when no trim segments are found in the lasso area. */
1239 if (trim_segments.segments.is_empty()) {
1240 return dst;
1241 }
1242
1243 /* Merge adjacent trim segments. E.g. two point ranges of 0-10 and 11-20 will be merged
1244 * to one range of 0-20. */
1245 trim_segments.merge_adjacent_segments();
1246
1247 /* Create the point transfer data, for converting the source geometry into the new geometry.
1248 * First, add all curve points not affected by the trim tool. */
1249 Array<Vector<PointTransferData>> src_to_dst_points(src_points_num);
1250 for (const int src_curve : src.curves_range()) {
1251 const IndexRange src_points = src_points_by_curve[src_curve];
1252 for (const int src_point : src_points) {
1253 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1254 const int src_next_point = (src_point == src_points.last()) ? src_points.first() :
1255 (src_point + 1);
1256
1257 /* Add the source point only if it does not lie inside a trim segment. */
1258 if (!point_is_in_segment[src_point]) {
1259 dst_points.append({src_point, src_next_point, 0.0f, true, false});
1260 }
1261 }
1262 }
1263
1264 /* Add new curve points at the intersection points of the trim segments.
1265 *
1266 * a b
1267 * source curve o--------o---*---o--------o----*---o--------o
1268 * ^ ^
1269 * trim segment |-----------------|
1270 *
1271 * o = existing curve point
1272 * * = newly created curve point
1273 *
1274 * The curve points between *a and *b will be deleted.
1275 * The source curve will be cut in two:
1276 * - the first curve ends at *a
1277 * - the second curve starts at *b
1278 *
1279 * We avoid inserting a new point very close to the adjacent one, because that's just adding
1280 * clutter to the geometry.
1281 */
1282 for (const Segment &trim_segment : trim_segments.segments) {
1283 /* Intersection at trim segment start. */
1284 if (trim_segment.is_intersected[Side::Start] &&
1285 trim_segment.intersection_distance[Side::Start] > DISTANCE_FACTOR_THRESHOLD)
1286 {
1287 const int src_point = trim_segment.point_range[Side::Start] - 1;
1288 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1289 dst_points.append({src_point,
1290 src_point + 1,
1291 trim_segment.intersection_distance[Side::Start],
1292 false,
1293 false});
1294 }
1295 /* Intersection at trim segment end. */
1296 if (trim_segment.is_intersected[Side::End]) {
1297 const int src_point = trim_segment.point_range[Side::End];
1298 if (trim_segment.intersection_distance[Side::End] < (1.0f - DISTANCE_FACTOR_THRESHOLD)) {
1299 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
1300 dst_points.append({src_point,
1301 src_point + 1,
1302 trim_segment.intersection_distance[Side::End],
1303 false,
1304 true});
1305 }
1306 else {
1307 /* Mark the 'is_cut' flag on the next point, because a new curve is starting here after
1308 * the removed trim segment. */
1309 Vector<PointTransferData> &dst_points = src_to_dst_points[src_point + 1];
1310 for (PointTransferData &dst_point : dst_points) {
1311 if (dst_point.is_src_point) {
1312 dst_point.is_cut = true;
1313 }
1314 }
1315 }
1316 }
1317 }
1318
1319 /* Create the new curves geometry. */
1320 compute_topology_change(src, dst, src_to_dst_points, keep_caps);
1321
1322 return dst;
1323}
1324
1325} // namespace trim
1326
1328 const Object &object,
1329 const GreasePencil &grease_pencil,
1330 Span<MutableDrawingInfo> drawings,
1331 const int frame_number)
1332{
1334
1335 /* Upper bound for line count. Arrays are sized for easy index mapping, exact count isn't
1336 * necessary. Not all points are added to the BVH tree. */
1337 int max_bvh_lines = 0;
1338 for (const int i_drawing : drawings.index_range()) {
1339 if (drawings[i_drawing].frame_number == frame_number) {
1340 max_bvh_lines += drawings[i_drawing].drawing.strokes().evaluated_points_num();
1341 }
1342 }
1343
1344 data.tree = BLI_bvhtree_new(max_bvh_lines, 0.0f, 4, 6);
1345 data.start_positions.reinitialize(max_bvh_lines);
1346 data.end_positions.reinitialize(max_bvh_lines);
1347 /* Compute offsets array in advance. */
1348 data.drawing_offsets.reinitialize(drawings.size() + 1);
1349 for (const int i_drawing : drawings.index_range()) {
1350 const MutableDrawingInfo &info = drawings[i_drawing];
1351 data.drawing_offsets[i_drawing] = (drawings[i_drawing].frame_number == frame_number ?
1353 0);
1354 }
1355 OffsetIndices bvh_elements_by_drawing = offset_indices::accumulate_counts_to_offsets(
1356 data.drawing_offsets);
1357
1358 /* Insert a line for each point except end points. */
1359 for (const int i_drawing : drawings.index_range()) {
1360 const MutableDrawingInfo &info = drawings[i_drawing];
1361 if (drawings[i_drawing].frame_number != frame_number) {
1362 continue;
1363 }
1364
1365 const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
1366 const float4x4 layer_to_world = layer.to_world_space(object);
1367 const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc.rv3d, layer_to_world);
1368 const bke::CurvesGeometry &curves = info.drawing.strokes();
1369 const OffsetIndices evaluated_points_by_curve = curves.evaluated_points_by_curve();
1370 const VArray<bool> cyclic = curves.cyclic();
1371 const Span<float3> evaluated_positions = curves.evaluated_positions();
1372 const IndexMask curves_mask = curves.curves_range();
1373
1374 /* Range of indices in the BVH tree for this drawing. */
1375 const IndexRange bvh_index_range = bvh_elements_by_drawing[i_drawing];
1376 const MutableSpan<float2> start_positions = data.start_positions.as_mutable_span().slice(
1377 bvh_index_range);
1378 const MutableSpan<float2> end_positions = data.end_positions.as_mutable_span().slice(
1379 bvh_index_range);
1380
1381 curves_mask.foreach_index([&](const int i_curve) {
1382 const bool is_cyclic = cyclic[i_curve];
1383 const IndexRange evaluated_points = evaluated_points_by_curve[i_curve];
1384
1385 /* Compute screen space positions. */
1386 for (const int i_point : evaluated_points) {
1388 vc.region, evaluated_positions[i_point], projection);
1389 start_positions[i_point] = co;
1390
1391 /* Last point is only valid for cyclic curves, gets ignored for non-cyclic curves. */
1392 const int i_prev_point = (i_point > 0 ? i_point - 1 : evaluated_points.last());
1393 end_positions[i_prev_point] = co;
1394 }
1395
1396 for (const int i_point : evaluated_points.drop_back(1)) {
1397 const float2 &start = start_positions[i_point];
1398 const float2 &end = end_positions[i_point];
1399
1400 const float bb[6] = {start.x, start.y, 0.0f, end.x, end.y, 0.0f};
1401 BLI_bvhtree_insert(data.tree, bvh_index_range[i_point], bb, 2);
1402 }
1403 /* Last->first point segment only used for cyclic curves. */
1404 if (is_cyclic) {
1405 const float2 &start = start_positions.last();
1406 const float2 &end = end_positions.first();
1407
1408 const float bb[6] = {start.x, start.y, 0.0f, end.x, end.y, 0.0f};
1409 BLI_bvhtree_insert(data.tree, bvh_index_range[evaluated_points.last()], bb, 2);
1410 }
1411 });
1412 }
1413
1414 BLI_bvhtree_balance(data.tree);
1415
1416 return data;
1417}
1418
1420{
1421 if (data.tree) {
1422 BLI_bvhtree_free(data.tree);
1423 data.tree = nullptr;
1424 }
1425 data.drawing_offsets.reinitialize(0);
1426 data.start_positions.reinitialize(0);
1427 data.end_positions.reinitialize(0);
1428}
1429
1431 const IndexMask &curve_mask,
1432 const Span<float2> screen_space_positions,
1433 const Curves2DBVHTree &tree_data,
1434 const IndexRange tree_data_range,
1435 MutableSpan<bool> r_hits,
1436 std::optional<MutableSpan<float>> r_first_intersect_factors,
1437 std::optional<MutableSpan<float>> r_last_intersect_factors)
1438{
1439 /* Insert segments for cutting extensions on stroke intersection. */
1440 const OffsetIndices points_by_curve = curves.points_by_curve();
1441 const VArray<bool> cyclic = curves.cyclic();
1442
1443 struct RaycastArgs {
1444 const Curves2DBVHTree &tree_data;
1445 /* Indices that need to be ignored to avoid intersecting a line with itself or its immediate
1446 * neighbors. */
1447 int ignore_index1;
1448 int ignore_index2;
1449 int ignore_index3;
1450 };
1452 [](void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit) {
1453 using Result = math::isect_result<float2>;
1454
1455 const RaycastArgs &args = *static_cast<const RaycastArgs *>(userdata);
1456 if (ELEM(index, args.ignore_index1, args.ignore_index2, args.ignore_index3)) {
1457 return;
1458 }
1459
1460 const float2 ray_start = float2(ray->origin);
1461 const float2 ray_end = ray_start + float2(ray->direction) * ray->radius;
1462 const float2 &line_start = args.tree_data.start_positions[index];
1463 const float2 &line_end = args.tree_data.end_positions[index];
1464 Result result = math::isect_seg_seg(ray_start, ray_end, line_start, line_end);
1465 if (result.kind <= 0) {
1466 return;
1467 }
1468 const float dist = result.lambda * math::distance(ray_start, ray_end);
1469 if (dist >= hit->dist) {
1470 return;
1471 }
1472 /* These always need to be calculated for the BVH traversal function. */
1473 hit->index = index;
1474 hit->dist = result.lambda * math::distance(ray_start, ray_end);
1475 /* Don't need the hit point, only the lambda. */
1476 hit->no[0] = result.lambda;
1477 };
1478
1479 /* Ray-cast in the forward direction. Ignores intersections with neighboring lines. */
1480 auto do_raycast = [&](const int index_back,
1481 const int index,
1482 const int index_forward,
1483 float &r_lambda) -> bool {
1484 if (index_forward < 0) {
1485 return false;
1486 }
1487
1488 const float2 start = screen_space_positions[index];
1489 const float2 end = screen_space_positions[index_forward];
1490 float length;
1491 const float2 dir = math::normalize_and_get_length(end - start, length);
1492
1493 RaycastArgs args = {tree_data,
1494 index_back >= 0 ? int(tree_data_range[index_back]) : -1,
1495 int(tree_data_range[index]),
1496 index_forward >= 0 ? int(tree_data_range[index_forward]) : -1};
1497 BVHTreeRayHit hit;
1498 hit.index = -1;
1499 hit.dist = FLT_MAX;
1501 tree_data.tree, float3(start, 0.0f), float3(dir, 0.0f), length, &hit, callback, &args);
1502
1503 if (hit.index >= 0) {
1504 r_lambda = hit.no[0];
1505 return true;
1506 }
1507 return false;
1508 };
1509
1510 r_hits.fill(false);
1511 if (r_first_intersect_factors) {
1512 r_first_intersect_factors->fill(-1.0f);
1513 }
1514 if (r_last_intersect_factors) {
1515 r_last_intersect_factors->fill(-1.0f);
1516 }
1517
1518 curve_mask.foreach_index(GrainSize(1024), [&](const int i_curve) {
1519 const bool is_cyclic = cyclic[i_curve];
1520 const IndexRange points = points_by_curve[i_curve];
1521
1522 for (const int i_point : points) {
1523 const int i_prev_point = (i_point == points.first() ? (is_cyclic ? points.last() : -1) :
1524 i_point - 1);
1525 const int i_next_point = (i_point == points.last() ? (is_cyclic ? points.first() : -1) :
1526 i_point + 1);
1527 float lambda;
1528 /* Find first intersections by raycast from each point to the next. */
1529 if (do_raycast(i_prev_point, i_point, i_next_point, lambda)) {
1530 r_hits[i_point] = true;
1531 if (r_first_intersect_factors) {
1532 (*r_first_intersect_factors)[i_point] = lambda;
1533 }
1534 }
1535 /* Find last intersections by raycast from each point to the previous. */
1536 if (do_raycast(i_next_point, i_point, i_prev_point, lambda)) {
1537 /* Note: factor = (1 - lambda) because of reverse raycast. */
1538 if (r_last_intersect_factors) {
1539 (*r_last_intersect_factors)[i_point] = 1.0f - lambda;
1540 }
1541 }
1542 }
1543 });
1544}
1545
1547 const IndexMask &curve_mask,
1548 const Span<float2> screen_space_positions,
1549 const Curves2DBVHTree &tree_data,
1550 const IndexRange tree_data_range)
1551{
1552 const OffsetIndices points_by_curve = curves.points_by_curve();
1553 const VArray<bool> cyclic = curves.cyclic();
1554
1555 Array<bool> hits(curves.points_num());
1556 Array<float> first_hit_factors(curves.points_num());
1557 Array<float> last_hit_factors(curves.points_num());
1559 curve_mask,
1560 screen_space_positions,
1561 tree_data,
1562 tree_data_range,
1563 hits,
1564 first_hit_factors,
1565 last_hit_factors);
1566
1567 IndexMaskMemory memory;
1568 const IndexMask hit_mask = IndexMask::from_bools(hits, memory);
1569
1570 /* Count number of segments in each curve.
1571 * This is needed to write to the correct segments range for each curve. */
1573 result.segment_offsets.reinitialize(curves.curves_num() + 1);
1574 /* Only segments with hits are written to, initialize all to zero. */
1575 result.segment_offsets.fill(0);
1576 curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
1577 const IndexRange points = points_by_curve[curve_i];
1578 const IndexMask curve_hit_mask = hit_mask.slice_content(points);
1579 const bool is_cyclic = cyclic[curve_i];
1580
1581 /* Each hit splits a segment in two. Non-cyclic curves add the curve start point as a segment
1582 * start point. */
1583 result.segment_offsets[curve_i] = (is_cyclic ? 0 : 1) + curve_hit_mask.size();
1584 });
1585 const OffsetIndices segments_by_curve = offset_indices::accumulate_counts_to_offsets(
1586 result.segment_offsets);
1587
1588 const int num_segments = segments_by_curve.total_size();
1589 result.segment_start_points.reinitialize(num_segments);
1590 result.segment_start_fractions.reinitialize(num_segments);
1591
1592 curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
1593 const IndexRange points = points_by_curve[curve_i];
1594 const IndexMask curve_hit_mask = hit_mask.slice_content(points);
1595 const bool is_cyclic = cyclic[curve_i];
1596 const IndexRange segments = segments_by_curve[curve_i];
1597 const int hit_segments_start = (is_cyclic ? 0 : 1);
1598
1599 if (segments.is_empty()) {
1600 return;
1601 }
1602
1603 /* Add curve start a segment. */
1604 if (!is_cyclic) {
1605 result.segment_start_points[segments[0]] = points.first();
1606 result.segment_start_fractions[segments[0]] = 0.0f;
1607 }
1608
1609 curve_hit_mask.foreach_index([&](const int point_i, const int hit_i) {
1610 result.segment_start_points[segments[hit_segments_start + hit_i]] = point_i;
1611 result.segment_start_fractions[segments[hit_segments_start + hit_i]] =
1612 first_hit_factors[point_i];
1613 });
1614 });
1615
1616 return result;
1617}
1618
1619} // 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:97
#define BLI_assert(a)
Definition BLI_assert.h:50
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(* BVHTree_RayCastCallback)(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
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.c:478
void BLI_rcti_pad(struct rcti *rect, int pad_x, int pad_y)
Definition rct.c:623
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.c:490
unsigned int uint
#define ELEM(...)
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)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
const T * data() const
Definition BLI_array.hh:301
IndexRange index_range() const
Definition BLI_array.hh:349
const T & first() const
Definition BLI_array.hh:270
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
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 IndexRange slice(int64_t start, int64_t size) 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:574
constexpr void fill(const T &value) const
Definition BLI_span.hh:518
constexpr T & first() const
Definition BLI_span.hh:680
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:690
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
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
MutableSpan< int > offsets_for_write()
VArray< bool > cyclic() const
const bke::CurvesGeometry & strokes() const
void to_indices(MutableSpan< T > r_indices) const
IndexMask slice_content(IndexRange range) const
static IndexMask from_indices(Span< T > indices, IndexMaskMemory &memory)
void foreach_index(Fn &&fn) const
local_group_size(16, 16) .push_constant(Type b
DEGForeachIDComponentCallback callback
static bool is_cyclic(const Nurb *nu)
KDTree_3d * tree
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static ushort indices[]
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_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:95
float distance(float a, float b)
#define FLT_MAX
Definition stdcycles.h:14
__int64 int64_t
Definition stdint.h:89
unsigned char uint8_t
Definition stdint.h:78
signed char int8_t
Definition stdint.h:75
RegionView3D * rv3d
Definition ED_view3d.hh:76
ARegion * region
Definition ED_view3d.hh:73
VecBase< T, 2 > xy() const
Segment * create_segment(const int curve, const int point)
int xy[2]
Definition wm_draw.cc:170