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