Blender V4.3
convexhull_2d.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 * SPDX-FileCopyrightText: 2001 softSurfer (http://www.softsurfer.com)
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later */
5
10#include <algorithm>
11#include <cstdlib>
12#include <cstring>
13
14#include "MEM_guardedalloc.h"
15
16#include "BLI_bounds.hh"
17#include "BLI_convexhull_2d.h"
18#include "BLI_math_vector.h"
19#include "BLI_utildefines.h"
20
21#include "BLI_strict_flags.h" /* Keep last. */
22
27// #define USE_BRUTE_FORCE_ASSERT
28
36// #define USE_ANGLE_ITER_ORDER_ASSERT
37
38using namespace blender;
39
40/* -------------------------------------------------------------------- */
44static float sincos_rotate_cw_x(const float2 &sincos, const float2 &p)
45{
46 return (sincos[0] * p[0]) + (sincos[1] * p[1]);
47}
48
49static float sincos_rotate_cw_y(const float2 &sincos, const float2 &p)
50{
51 return (sincos[1] * p[0]) - (sincos[0] * p[1]);
52}
53
56/* -------------------------------------------------------------------- */
60/* Copyright 2001, softSurfer (http://www.softsurfer.com)
61 * This code may be freely used and modified for any purpose
62 * providing that this copyright notice is included with it.
63 * SoftSurfer makes no warranty for this code, and cannot be held
64 * liable for any real or imagined damage resulting from its use.
65 * Users of this code must verify correctness for their application.
66 * http://softsurfer.com/Archive/algorithm_0203/algorithm_0203.htm */
67
75static float is_left(const float p0[2], const float p1[2], const float p2[2])
76{
77 return (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1]);
78}
79
80static int convexhull_2d_sorted(const float (*points)[2], const int points_num, int r_points[])
81{
82 BLI_assert(points_num >= 2); /* Doesn't handle trivial cases. */
83 /* The output array `r_points[]` will be used as the stack. */
84 int bot = 0;
85 /* Indices for bottom and top of the stack. */
86 int top = -1;
87 /* Array scan index. */
88 int i;
89
90 const int minmin = 0;
91 const int maxmax = points_num - 1;
92 int minmax;
93 int maxmin;
94
95 float xmax;
96
97 /* Get the indices of points with min X-coord and min|max Y-coord. */
98 float xmin = points[0][0];
99 for (i = 1; i <= maxmax; i++) {
100 if (points[i][0] != xmin) {
101 break;
102 }
103 }
104
105 minmax = i - 1;
106 if (minmax == maxmax) { /* Degenerate case: all x-coords == X-min. */
107 r_points[++top] = minmin;
108 if (points[minmax][1] != points[minmin][1]) {
109 /* A nontrivial segment. */
110 r_points[++top] = minmax;
111 }
112 BLI_assert(top + 1 <= points_num);
113 return top + 1;
114 }
115
116 /* Get the indices of points with max X-coord and min|max Y-coord. */
117
118 xmax = points[maxmax][0];
119 for (i = maxmax - 1; i >= 0; i--) {
120 if (points[i][0] != xmax) {
121 break;
122 }
123 }
124 maxmin = i + 1;
125
126 /* Compute the lower hull on the stack `r_points`. */
127 r_points[++top] = minmin; /* Push `minmin` point onto stack. */
128 i = minmax;
129 while (++i <= maxmin) {
130 /* The lower line joins `points[minmin]` with `points[maxmin]`. */
131 if ((i < maxmin) && (is_left(points[minmin], points[maxmin], points[i]) >= 0)) {
132 continue; /* Ignore `points[i]` above or on the lower line. */
133 }
134
135 while (top > 0) { /* There are at least 2 points on the stack. */
136 /* Test if `points[i]` is left of the line at the stack top. */
137 if (is_left(points[r_points[top - 1]], points[r_points[top]], points[i]) > 0.0f) {
138 break; /* `points[i]` is a new hull vertex. */
139 }
140 top--; /* Pop top point off stack. */
141 }
142
143 r_points[++top] = i; /* Push `points[i]` onto stack. */
144 }
145
146 /* Next, compute the upper hull on the stack `r_points` above the bottom hull. */
147 if (maxmax != maxmin) { /* If distinct `xmax` points. */
148 r_points[++top] = maxmax; /* Push `maxmax` point onto stack. */
149 }
150
151 bot = top; /* the bottom point of the upper hull stack */
152 i = maxmin;
153 while (--i >= minmax) {
154 /* The upper line joins `points[maxmax]` with `points[minmax]`. */
155 if ((i > minmax) && (is_left(points[maxmax], points[minmax], points[i]) >= 0)) {
156 continue; /* Ignore points[i] below or on the upper line. */
157 }
158
159 while (top > bot) { /* At least 2 points on the upper stack. */
160 /* Test if `points[i]` is left of the line at the stack top. */
161 if (is_left(points[r_points[top - 1]], points[r_points[top]], points[i]) > 0.0f) {
162 break; /* points[i] is a new hull vertex. */
163 }
164 top--; /* Pop top point off stack. */
165 }
166
167 if (points[i][0] == points[r_points[0]][0] && points[i][1] == points[r_points[0]][1]) {
168 BLI_assert(top + 1 <= points_num);
169 return top + 1; /* Special case (mgomes). */
170 }
171
172 r_points[++top] = i; /* Push points[i] onto stack. */
173 }
174
175 if (minmax != minmin && r_points[0] != minmin) {
176 r_points[++top] = minmin; /* Push joining endpoint onto stack. */
177 }
178
179 BLI_assert(top + 1 <= points_num);
180 return top + 1;
181}
182
183int BLI_convexhull_2d(const float (*points)[2], const int points_num, int r_points[])
184{
185 BLI_assert(points_num >= 0);
186 if (points_num < 2) {
187 if (points_num == 1) {
188 r_points[0] = 0;
189 }
190 return points_num;
191 }
192 int *points_map = static_cast<int *>(MEM_mallocN(sizeof(int) * size_t(points_num), __func__));
193 float(*points_sort)[2] = static_cast<float(*)[2]>(
194 MEM_mallocN(sizeof(*points_sort) * size_t(points_num), __func__));
195
196 for (int i = 0; i < points_num; i++) {
197 points_map[i] = i;
198 }
199
200 /* Sort the points by X, then by Y. */
201 std::sort(points_map, points_map + points_num, [points](const int &a_index, const int &b_index) {
202 const float *a = points[a_index];
203 const float *b = points[b_index];
204 if (a[1] > b[1]) {
205 return false;
206 }
207 if (a[1] < b[1]) {
208 return true;
209 }
210
211 if (a[0] > b[0]) {
212 return false;
213 }
214 if (a[0] < b[0]) {
215 return true;
216 }
217 return false;
218 });
219
220 for (int i = 0; i < points_num; i++) {
221 copy_v2_v2(points_sort[i], points[points_map[i]]);
222 }
223
224 int points_hull_num = convexhull_2d_sorted(points_sort, points_num, r_points);
225
226 /* Map back to the unsorted index values. */
227 for (int i = 0; i < points_hull_num; i++) {
228 r_points[i] = points_map[r_points[i]];
229 }
230
231 MEM_freeN(points_map);
232 MEM_freeN(points_sort);
233
234 BLI_assert(points_hull_num <= points_num);
235 return points_hull_num;
236}
237
240/* -------------------------------------------------------------------- */
244#if defined(USE_BRUTE_FORCE_ASSERT) && !defined(NDEBUG)
245static float2 convexhull_aabb_fit_hull_2d_brute_force(const float (*points_hull)[2],
246 int points_hull_num)
247{
248 float area_best = FLT_MAX;
249 float2 sincos_best = {0.0f, 1.0f}; /* Track the best angle as a unit vector, delaying `atan2`. */
250
251 for (int i = 0; i < points_hull_num; i++) {
252 const int i_next = (i + 1) % points_hull_num;
253 /* 2D rotation matrix. */
254 float dvec_length = 0.0f;
256 float2(points_hull[i_next]) - float2(points_hull[i]), dvec_length);
257 if (UNLIKELY(dvec_length == 0.0f)) {
258 continue;
259 }
260
262 float area_test;
263
264 for (int j = 0; j < points_hull_num; j++) {
265 const float2 tvec = {
266 sincos_rotate_cw_x(sincos, points_hull[j]),
267 sincos_rotate_cw_y(sincos, points_hull[j]),
268 };
269
270 bounds[0].min = math::min(bounds[0].min, tvec[0]);
271 bounds[0].max = math::max(bounds[0].max, tvec[0]);
272 bounds[1].min = math::min(bounds[1].min, tvec[1]);
273 bounds[1].max = math::max(bounds[1].max, tvec[1]);
274
275 area_test = (bounds[0].max - bounds[0].min) * (bounds[1].max - bounds[1].min);
276 if (area_test > area_best) {
277 break;
278 }
279 }
280
281 if (area_test < area_best) {
282 area_best = area_test;
283 sincos_best = sincos;
284 }
285 }
286
287 return sincos_best;
288}
289#endif
290
293/* -------------------------------------------------------------------- */
304static float2 sincos_canonical(const float2 &sincos)
305{
306
307 /* Normalize doesn't ensure a `sin` / `cos` of 1.0/-1.0 ensures the other value is zero.
308 * Without the check for both 0.0 and 1.0, iteration may not be ordered. */
310 if (sincos[0] < 0.0f) {
311 if (sincos[1] < 0.0f) {
312 result[0] = -sincos[0];
313 result[1] = -sincos[1];
314 }
315 else if ((sincos[0] == -1.0f) && (sincos[1] == 0.0f)) {
316 result[0] = -sincos[0];
317 result[1] = sincos[1];
318 }
319 else {
320 result[0] = sincos[1];
321 result[1] = -sincos[0];
322 }
323 }
324 else {
325 if (sincos[1] < 0.0f) {
326 result[0] = -sincos[1];
327 result[1] = sincos[0];
328 }
329 else if ((sincos[0] == 0.0f) && (sincos[1] == 1.0f)) {
330 result[0] = sincos[1];
331 result[1] = sincos[0];
332 }
333 else {
334 result = sincos;
335 }
336 }
337
338 /* The range is [1.0, 0.0], it will approach but never return [0.0, 1.0],
339 * as the canonical version of this value gets flipped to [1.0, 0.0]. */
340 BLI_assert(result[0] > 0.0f);
341 BLI_assert(result[1] >= 0.0f);
342 return result;
343}
344
356
358{
359 if (a.sincos_canonical[0] < b.sincos_canonical[0]) {
360 return -1;
361 }
362 if (a.sincos_canonical[0] > b.sincos_canonical[0]) {
363 return 1;
364 }
365 /* Flipped intentionally. */
366 if (a.sincos_canonical[1] > b.sincos_canonical[1]) {
367 return -1;
368 }
369 if (a.sincos_canonical[1] < b.sincos_canonical[1]) {
370 return 1;
371 }
372
373 /* Flipped intentionally. */
374 if (a.index > b.index) {
375 return -1;
376 }
377 if (a.index < b.index) {
378 return 1;
379 }
380 return 0;
381}
382
398
411
413{
414 HullAngleStep **prev_p = &hiter.axis_ordered;
415 HullAngleStep *iter = hiter.axis_ordered;
416 while (iter && hull_angle_canonical_cmp(iter->angle, insert->angle) > 0) {
417 prev_p = &iter->next;
418 iter = iter->next;
419 }
420 *prev_p = insert;
421 insert->next = iter;
422}
423
425{
426 BLI_assert(hstep.index != -1);
427 while (hstep.index != hstep.index_max) {
428 const int i_curr = hstep.index;
429 const int i_next = (hstep.index + 1) % hiter.points_hull_num;
430 const float2 dir = float2(hiter.points_hull[i_next]) - float2(hiter.points_hull[i_curr]);
431 float dir_length = 0.0f;
432 const float2 sincos_test = math::normalize_and_get_length(dir, dir_length);
433 hstep.index = i_next;
434 if (LIKELY(dir_length != 0.0f)) {
435 hstep.angle.sincos = sincos_test;
436 hstep.angle.sincos_canonical = sincos_canonical(sincos_test);
437 hstep.angle.index = i_curr;
438 return true;
439 }
440 }
441
442 /* Reached the end, signal this axis shouldn't be stepped over. */
443 hstep.index = -1;
444 return false;
445}
446
447static HullAngleIter convexhull_2d_angle_iter_init(const float (*points_hull)[2],
448 const int points_hull_num)
449{
450 const int points_hull_num_minus_1 = points_hull_num - 1;
451 HullAngleIter hiter = {};
452 /* Aligned with `hiter.axis`. */
453 float range[2][2];
454 /* Initialize min-max range from the first point. */
455 for (int axis = 0; axis < 2; axis++) {
456 range[axis][0] = points_hull[0][axis];
457 range[axis][1] = points_hull[0][axis];
458 }
459 /* Expand from all other points.
460 *
461 * NOTE: Don't attempt to pick either side when there are multiple equal points.
462 * Walking backwards while checking `sincos_canonical` handles that. */
463 for (int i = 1; i < points_hull_num; i++) {
464 const float *p = points_hull[i];
465 for (int axis = 0; axis < 2; axis++) {
466 if (range[axis][0] < p[axis]) {
467 range[axis][0] = p[axis];
468 hiter.axis[axis][0].index = i;
469 }
470 if (range[axis][1] > p[axis]) {
471 range[axis][1] = p[axis];
472 hiter.axis[axis][1].index = i;
473 }
474 }
475 }
476
477 /* Step backwards, compute the actual `sincos_canonical` because it's possible
478 * an edge which is not *exactly* axis aligned normalizes to a value which is.
479 * Instead of attempting to guess when this might happen,
480 * simply calculate the value and walk backwards for a long as the canonical angle
481 * has a `sin` of 1.0 (which must always come first). */
482 for (int axis = 0; axis < 2; axis++) {
483 for (int i = 0; i < 2; i++) {
484 const int i_orig = hiter.axis[axis][i].index;
485 int i_curr = i_orig, i_prev;
486 /* Prevent an eternal loop (incredibly unlikely).
487 * In virtually all cases this will step back once
488 * (in the case of an axis-aligned edge) or not at all. */
489 while ((i_prev = (i_curr + points_hull_num_minus_1) % points_hull_num) != i_orig) {
490 float dir_length = 0.0f;
491 const float2 sincos_test = math::normalize_and_get_length(
492 float2(points_hull[i_curr]) - float2(points_hull[i_prev]), dir_length);
493 if (LIKELY(dir_length != 0.0f)) {
494 /* Account for 90 degree corners that may also have an axis-aligned canonical angle. */
495 if (math::abs(sincos_test[axis]) > 0.5f) {
496 break;
497 }
498 const float2 sincos_test_canonical = sincos_canonical(sincos_test);
499 if (LIKELY(sincos_test_canonical[0] != 1.0f)) {
500 break;
501 }
502 }
503 i_curr = i_prev;
504 hiter.axis[axis][i].index = i_curr;
505 }
506 }
507 }
508
509 /* Setup counter-clockwise limits. */
510 hiter.axis[0][0].index_max = hiter.axis[1][0].index; /* West to south. */
511 hiter.axis[1][0].index_max = hiter.axis[0][1].index; /* South to east. */
512 hiter.axis[0][1].index_max = hiter.axis[1][1].index; /* East to north. */
513 hiter.axis[1][1].index_max = hiter.axis[0][0].index; /* North to west. */
514
515 hiter.points_hull = points_hull;
516 hiter.points_hull_num = points_hull_num;
517
518 for (int axis = 0; axis < 2; axis++) {
519 for (int i = 0; i < 2; i++) {
520 hiter.axis[axis][i].angle.index = hiter.axis[axis][i].index;
521 if (convexhull_2d_angle_iter_step_on_axis(hiter, hiter.axis[axis][i])) {
522 hull_angle_insert_ordered(hiter, &hiter.axis[axis][i]);
523 }
524 }
525 }
526
527 return hiter;
528}
529
531{
532 HullAngleStep *hstep = hiter.axis_ordered;
533#ifdef USE_ANGLE_ITER_ORDER_ASSERT
534 const AngleCanonical angle_prev = hstep->angle;
535#endif
536
537 hiter.axis_ordered = hiter.axis_ordered->next;
538 if (convexhull_2d_angle_iter_step_on_axis(hiter, *hstep)) {
539 hull_angle_insert_ordered(hiter, hstep);
540 }
541
542#ifdef USE_ANGLE_ITER_ORDER_ASSERT
543 if (hiter.axis_ordered) {
544 hstep = hiter.axis_ordered;
545 BLI_assert(hull_angle_canonical_cmp(angle_prev, hiter.axis_ordered->angle) > 0);
546 }
547#endif
548}
549
552/* -------------------------------------------------------------------- */
561template<int Axis, int AxisSign>
562static float convexhull_2d_compute_extent_on_axis(const float (*points_hull)[2],
563 const int points_hull_num,
564 const float2 &sincos,
565 int *index_p)
566{
567 /* NOTE(@ideasman42): Use a forward search instead of attempting a search strategy
568 * computing upper & lower bounds (similar to a binary search). The rotating calipers
569 * are ensured to test ordered rotations between 0-90 degrees, meaning any cases where
570 * this function needs to step over many points will be limited to a small number of cases.
571 * Since scanning forward isn't expensive it shouldn't pose a problem. */
572 BLI_assert(*index_p >= 0);
573 const int index_init = *index_p;
574 int index_best = index_init;
575 float value_init = (Axis == 0) ? sincos_rotate_cw_x(sincos, points_hull[index_best]) :
576 sincos_rotate_cw_y(sincos, points_hull[index_best]);
577 float value_best = value_init;
578 /* Simply scan up the array. */
579 for (int count = 1; count < points_hull_num; count++) {
580 const int index_test = (index_init + count) % points_hull_num;
581 const float value_test = (Axis == 0) ? sincos_rotate_cw_x(sincos, points_hull[index_test]) :
582 sincos_rotate_cw_y(sincos, points_hull[index_test]);
583 if ((AxisSign == -1) ? (value_test > value_best) : (value_test < value_best)) {
584 break;
585 }
586 value_best = value_test;
587 index_best = index_test;
588 }
589
590 *index_p = index_best;
591 return value_best;
592}
593
594static float convexhull_aabb_fit_hull_2d(const float (*points_hull)[2], int points_hull_num)
595{
596 float area_best = FLT_MAX;
597 float2 sincos_best = {0.0f, 1.0f}; /* Track the best angle as a unit vector, delaying `atan2`. */
598 int index_best = INT_MAX;
599
600 /* Initialize to zero because the first pass uses the first index to set the bounds. */
601 blender::Bounds<int> bounds_index[2] = {{0, 0}, {0, 0}};
602
603 HullAngleIter hull_iter = convexhull_2d_angle_iter_init(points_hull, points_hull_num);
604
605 /* Use the axis aligned bounds as starting points. */
606 bounds_index[0].min = hull_iter.axis[0][1].angle.index;
607 bounds_index[0].max = hull_iter.axis[0][0].angle.index;
608 bounds_index[1].min = hull_iter.axis[1][0].angle.index;
609 bounds_index[1].max = hull_iter.axis[1][1].angle.index;
610 while (const HullAngleStep *hstep = hull_iter.axis_ordered) {
611 /* Step the calipers to the new rotation `sincos`, returning the bounds at the same time. */
612 blender::Bounds<float> bounds_test[2] = {
614 points_hull, points_hull_num, hstep->angle.sincos_canonical, &bounds_index[0].min),
616 points_hull, points_hull_num, hstep->angle.sincos_canonical, &bounds_index[0].max)},
618 points_hull, points_hull_num, hstep->angle.sincos_canonical, &bounds_index[1].min),
620 points_hull, points_hull_num, hstep->angle.sincos_canonical, &bounds_index[1].max)},
621 };
622
623 const float area_test = (bounds_test[0].max - bounds_test[0].min) *
624 (bounds_test[1].max - bounds_test[1].min);
625
626 if (area_test < area_best ||
627 /* Use the index as a tie breaker, this simply matches the behavior of checking
628 * all edges in-order and only overwriting past results when they're an improvement. */
629 ((area_test == area_best) && (hstep->angle.index < index_best)))
630 {
631 area_best = area_test;
632 sincos_best = hstep->angle.sincos;
633 index_best = hstep->angle.index;
634 }
635
637 }
638
639 const float angle = (area_best != FLT_MAX) ? float(atan2(sincos_best[0], sincos_best[1])) : 0.0f;
640
641#if defined(USE_BRUTE_FORCE_ASSERT) && !defined(NDEBUG)
642 {
643 /* Ensure the optimized result matches the brute-force version. */
644 const float2 sincos_test = convexhull_aabb_fit_hull_2d_brute_force(points_hull,
645 points_hull_num);
646 if (sincos_best != sincos_test) {
647 BLI_assert(sincos_best == sincos_test);
648 }
649 }
650#endif
651
652 return angle;
653}
654
655float BLI_convexhull_aabb_fit_points_2d(const float (*points)[2], int points_num)
656{
657 BLI_assert(points_num >= 0);
658 float angle = 0.0f;
659
660 int *index_map = static_cast<int *>(
661 MEM_mallocN(sizeof(*index_map) * size_t(points_num), __func__));
662
663 int points_hull_num = BLI_convexhull_2d(points, points_num, index_map);
664
665 if (points_hull_num > 1) {
666 float(*points_hull)[2] = static_cast<float(*)[2]>(
667 MEM_mallocN(sizeof(*points_hull) * size_t(points_hull_num), __func__));
668 for (int j = 0; j < points_hull_num; j++) {
669 copy_v2_v2(points_hull[j], points[index_map[j]]);
670 }
671
672 angle = convexhull_aabb_fit_hull_2d(points_hull, points_hull_num);
673 MEM_freeN(points_hull);
674 }
675
676 MEM_freeN(index_map);
677
678 return angle;
679}
680
#define BLI_assert(a)
Definition BLI_assert.h:50
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
MINLINE void copy_v2_v2(float r[2], const float a[2])
#define UNLIKELY(x)
#define LIKELY(x)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:125
Read Guarded memory(de)allocation.
local_group_size(16, 16) .push_constant(Type b
static int hull_angle_canonical_cmp(const AngleCanonical &a, const AngleCanonical &b)
static HullAngleIter convexhull_2d_angle_iter_init(const float(*points_hull)[2], const int points_hull_num)
static float convexhull_aabb_fit_hull_2d(const float(*points_hull)[2], int points_hull_num)
static void hull_angle_insert_ordered(HullAngleIter &hiter, HullAngleStep *insert)
static float convexhull_2d_compute_extent_on_axis(const float(*points_hull)[2], const int points_hull_num, const float2 &sincos, int *index_p)
static float2 sincos_canonical(const float2 &sincos)
static int convexhull_2d_sorted(const float(*points)[2], const int points_num, int r_points[])
float BLI_convexhull_aabb_fit_points_2d(const float(*points)[2], int points_num)
int BLI_convexhull_2d(const float(*points)[2], const int points_num, int r_points[])
static float sincos_rotate_cw_x(const float2 &sincos, const float2 &p)
static void convexhull_2d_angle_iter_step(HullAngleIter &hiter)
static float sincos_rotate_cw_y(const float2 &sincos, const float2 &p)
static bool convexhull_2d_angle_iter_step_on_axis(const HullAngleIter &hiter, HullAngleStep &hstep)
static float is_left(const float p0[2], const float p1[2], const float p2[2])
draw_view in_light_buf[] float
uint top
int count
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T min(const T &a, const T &b)
T max(const T &a, const T &b)
T abs(const T &a)
VecBase< float, 2 > float2
#define min(a, b)
Definition sort.c:32
#define FLT_MAX
Definition stdcycles.h:14
const float(* points_hull)[2]
HullAngleStep axis[2][2]
HullAngleStep * axis_ordered
HullAngleStep * next
AngleCanonical angle