Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHoward Trickey <howard.trickey@gmail.com>2020-08-28 17:56:44 +0300
committerHoward Trickey <howard.trickey@gmail.com>2020-08-28 18:01:06 +0300
commit9e09b5c418c0a436e3c84ccf38c065527988b0a0 (patch)
treec0e81b8834aaf27c22a2734e364452fa2af5c6c1 /source/blender/blenlib
parent4a17508c6d2a24dfb7c018ae49c80f12b4d3e610 (diff)
Merge newboolean branch into master.
This is for design task T67744, Boolean Redesign. It adds a choice of solver to the Boolean modifier and the Intersect (Boolean) and Intersect (Knife) tools. The 'Fast' choice is the current Bmesh boolean. The new 'Exact' choice is a more advanced algorithm that supports overlapping geometry and uses more robust calculations, but is slower than the Fast choice. The default with this commit is set to 'Exact'. We can decide before the 2.91 release whether or not this is the right choice, but this choice now will get us more testing and feedback on the new code.
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r--source/blender/blenlib/BLI_delaunay_2d.h85
-rw-r--r--source/blender/blenlib/BLI_double2.hh143
-rw-r--r--source/blender/blenlib/BLI_double3.hh245
-rw-r--r--source/blender/blenlib/BLI_float2.hh46
-rw-r--r--source/blender/blenlib/BLI_float3.hh5
-rw-r--r--source/blender/blenlib/BLI_math_base.h8
-rw-r--r--source/blender/blenlib/BLI_math_boolean.hh61
-rw-r--r--source/blender/blenlib/BLI_math_mpq.hh36
-rw-r--r--source/blender/blenlib/BLI_math_vector.h11
-rw-r--r--source/blender/blenlib/BLI_mesh_boolean.hh79
-rw-r--r--source/blender/blenlib/BLI_mesh_intersect.hh359
-rw-r--r--source/blender/blenlib/BLI_mpq2.hh184
-rw-r--r--source/blender/blenlib/BLI_mpq3.hh281
-rw-r--r--source/blender/blenlib/CMakeLists.txt29
-rw-r--r--source/blender/blenlib/intern/delaunay_2d.c5173
-rw-r--r--source/blender/blenlib/intern/delaunay_2d.cc2500
-rw-r--r--source/blender/blenlib/intern/math_boolean.cc2533
-rw-r--r--source/blender/blenlib/intern/math_vec.cc193
-rw-r--r--source/blender/blenlib/intern/math_vector.c20
-rw-r--r--source/blender/blenlib/intern/math_vector_inline.c45
-rw-r--r--source/blender/blenlib/intern/mesh_boolean.cc3382
-rw-r--r--source/blender/blenlib/intern/mesh_intersect.cc3304
-rw-r--r--source/blender/blenlib/tests/BLI_delaunay_2d_test.cc2378
-rw-r--r--source/blender/blenlib/tests/BLI_mesh_boolean_test.cc908
-rw-r--r--source/blender/blenlib/tests/BLI_mesh_intersect_test.cc1072
25 files changed, 16738 insertions, 6342 deletions
diff --git a/source/blender/blenlib/BLI_delaunay_2d.h b/source/blender/blenlib/BLI_delaunay_2d.h
index a826a6b2677..7027477ac7f 100644
--- a/source/blender/blenlib/BLI_delaunay_2d.h
+++ b/source/blender/blenlib/BLI_delaunay_2d.h
@@ -18,6 +18,9 @@
/** \file
* \ingroup bli
+ *
+ * This header file contains both a C interface and a C++ interface
+ * to the 2D Constrained Delaunay Triangulation library routine.
*/
#ifdef __cplusplus
@@ -107,11 +110,6 @@ extern "C" {
* If zero is supplied for epsilon, an internal value of 1e-8 used
* instead, since this code will not work correctly if it is not allowed
* to merge "too near" vertices.
- *
- * Normally, if epsilon is non-zero, there is an "input modify" pass which
- * checks to see if some vertices are within epsilon of other edges, and
- * snapping them to those edges if so. You can skip this pass by setting
- * skip_input_modify to true. (This is also useful in some unit tests.)
*/
typedef struct CDT_input {
int verts_len;
@@ -123,7 +121,6 @@ typedef struct CDT_input {
int *faces_start_table;
int *faces_len_table;
float epsilon;
- bool skip_input_modify;
} CDT_input;
/**
@@ -150,12 +147,9 @@ typedef struct CDT_input {
* - faces_orig, faces_orig_start_table, faces_orig_len_table
*
* For edges, the edges_orig triple can also say which original face
- * edge is part of a given output edge. If an index in edges_orig
- * is greater than the input's edges_len, then subtract input's edges_len
- * from it to some number i: then the face edge that starts from the
- * input vertex at input's faces[i] is the corresponding face edge.
- * for convenience, face_edge_offset in the result will be the input's
- * edges_len, so that this conversion can be easily done by the caller.
+ * edge is part of a given output edge. See the comment below
+ * on the C++ interface for how to decode the entries in the edges_orig
+ * table.
*/
typedef struct CDT_result {
int verts_len;
@@ -207,4 +201,69 @@ void BLI_delaunay_2d_cdt_free(CDT_result *result);
#ifdef __cplusplus
}
-#endif
+
+/* C++ Interface. */
+
+# include "BLI_array.hh"
+# include "BLI_double2.hh"
+# include "BLI_math_mpq.hh"
+# include "BLI_mpq2.hh"
+# include "BLI_vector.hh"
+
+namespace blender::meshintersect {
+
+/* vec2<Arith_t> is a 2d vector with Arith_t as the type for coordinates. */
+template<typename Arith_t> struct vec2_impl;
+template<> struct vec2_impl<double> {
+ typedef double2 type;
+};
+
+# ifdef WITH_GMP
+template<> struct vec2_impl<mpq_class> {
+ typedef mpq2 type;
+};
+# endif
+
+template<typename Arith_t> using vec2 = typename vec2_impl<Arith_t>::type;
+
+template<typename Arith_t> class CDT_input {
+ public:
+ Array<vec2<Arith_t>> vert;
+ Array<std::pair<int, int>> edge;
+ Array<Vector<int>> face;
+ Arith_t epsilon{0};
+};
+
+template<typename Arith_t> class CDT_result {
+ public:
+ Array<vec2<Arith_t>> vert;
+ Array<std::pair<int, int>> edge;
+ Array<Vector<int>> face;
+ /** For each output vert, which input verts correspond to it? */
+ Array<Vector<int>> vert_orig;
+ /**
+ * For each output edge, which input edges does it overlap?
+ * The input edge ids are encoded as follows:
+ * if the value is less than face_edge_offset, then it is
+ * an index into the input edge[] array.
+ * else let (a, b) = the quotient and remainder of dividing
+ * the edge index by face_edge_offset; "a" will be the input face + 1,
+ * and "b" will be a position within that face.
+ */
+ Array<Vector<int>> edge_orig;
+ /** For each output face, which original faces does it overlap? */
+ Array<Vector<int>> face_orig;
+ /** Used to encode edge_orig (see above). */
+ int face_edge_offset;
+};
+
+CDT_result<double> delaunay_2d_calc(const CDT_input<double> &input, CDT_output_type output_type);
+
+# ifdef WITH_GMP
+CDT_result<mpq_class> delaunay_2d_calc(const CDT_input<mpq_class> &input,
+ CDT_output_type output_type);
+# endif
+
+} /* namespace blender::meshintersect */
+
+#endif /* __cplusplus */
diff --git a/source/blender/blenlib/BLI_double2.hh b/source/blender/blenlib/BLI_double2.hh
new file mode 100644
index 00000000000..3466b946e73
--- /dev/null
+++ b/source/blender/blenlib/BLI_double2.hh
@@ -0,0 +1,143 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_double3.hh"
+
+namespace blender {
+
+struct double2 {
+ double x, y;
+
+ double2() = default;
+
+ double2(const double *ptr) : x{ptr[0]}, y{ptr[1]}
+ {
+ }
+
+ double2(double x, double y) : x(x), y(y)
+ {
+ }
+
+ double2(const double3 &other) : x(other.x), y(other.y)
+ {
+ }
+
+ operator double *()
+ {
+ return &x;
+ }
+
+ operator const double *() const
+ {
+ return &x;
+ }
+
+ float length() const
+ {
+ return len_v2_db(*this);
+ }
+
+ friend double2 operator+(const double2 &a, const double2 &b)
+ {
+ return {a.x + b.x, a.y + b.y};
+ }
+
+ friend double2 operator-(const double2 &a, const double2 &b)
+ {
+ return {a.x - b.x, a.y - b.y};
+ }
+
+ friend double2 operator*(const double2 &a, double b)
+ {
+ return {a.x * b, a.y * b};
+ }
+
+ friend double2 operator/(const double2 &a, double b)
+ {
+ BLI_assert(b != 0.0);
+ return {a.x / b, a.y / b};
+ }
+
+ friend double2 operator*(double a, const double2 &b)
+ {
+ return b * a;
+ }
+
+ friend bool operator==(const double2 &a, const double2 &b)
+ {
+ return a.x == b.x && a.y == b.y;
+ }
+
+ friend bool operator!=(const double2 &a, const double2 &b)
+ {
+ return a.x != b.x || a.y != b.y;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const double2 &v)
+ {
+ stream << "(" << v.x << ", " << v.y << ")";
+ return stream;
+ }
+
+ static double dot(const double2 &a, const double2 &b)
+ {
+ return a.x * b.x + a.y * b.y;
+ }
+
+ static double2 interpolate(const double2 &a, const double2 &b, double t)
+ {
+ return a * (1 - t) + b * t;
+ }
+
+ static double2 abs(const double2 &a)
+ {
+ return double2(fabs(a.x), fabs(a.y));
+ }
+
+ static double distance(const double2 &a, const double2 &b)
+ {
+ return (a - b).length();
+ }
+
+ static double distance_squared(const double2 &a, const double2 &b)
+ {
+ return double2::dot(a, b);
+ }
+
+ struct isect_result {
+ enum {
+ LINE_LINE_COLINEAR = -1,
+ LINE_LINE_NONE = 0,
+ LINE_LINE_EXACT = 1,
+ LINE_LINE_CROSS = 2,
+ } kind;
+ double lambda;
+ double mu;
+ };
+
+ static isect_result isect_seg_seg(const double2 &v1,
+ const double2 &v2,
+ const double2 &v3,
+ const double2 &v4);
+};
+
+} // namespace blender
diff --git a/source/blender/blenlib/BLI_double3.hh b/source/blender/blenlib/BLI_double3.hh
new file mode 100644
index 00000000000..5b6204935d7
--- /dev/null
+++ b/source/blender/blenlib/BLI_double3.hh
@@ -0,0 +1,245 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#include <iostream>
+
+#include "BLI_math_vector.h"
+#include "BLI_span.hh"
+
+namespace blender {
+
+struct double3 {
+ double x, y, z;
+
+ double3() = default;
+
+ double3(const double *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}
+ {
+ }
+
+ double3(const double (*ptr)[3]) : double3((const double *)ptr)
+ {
+ }
+
+ explicit double3(double value) : x(value), y(value), z(value)
+ {
+ }
+
+ explicit double3(int value) : x(value), y(value), z(value)
+ {
+ }
+
+ double3(double x, double y, double z) : x{x}, y{y}, z{z}
+ {
+ }
+
+ operator const double *() const
+ {
+ return &x;
+ }
+
+ operator double *()
+ {
+ return &x;
+ }
+
+ double normalize_and_get_length()
+ {
+ return normalize_v3_db(*this);
+ }
+
+ double3 normalized() const
+ {
+ double3 result;
+ normalize_v3_v3_db(result, *this);
+ return result;
+ }
+
+ double length() const
+ {
+ return len_v3_db(*this);
+ }
+
+ double length_squared() const
+ {
+ return len_squared_v3_db(*this);
+ }
+
+ void reflect(const double3 &normal)
+ {
+ *this = this->reflected(normal);
+ }
+
+ double3 reflected(const double3 &normal) const
+ {
+ double3 result;
+ reflect_v3_v3v3_db(result, *this, normal);
+ return result;
+ }
+
+ static double3 safe_divide(const double3 &a, const double3 &b)
+ {
+ double3 result;
+ result.x = (b.x == 0.0) ? 0.0 : a.x / b.x;
+ result.y = (b.y == 0.0) ? 0.0 : a.y / b.y;
+ result.z = (b.z == 0.0) ? 0.0 : a.z / b.z;
+ return result;
+ }
+
+ void invert()
+ {
+ x = -x;
+ y = -y;
+ z = -z;
+ }
+
+ friend double3 operator+(const double3 &a, const double3 &b)
+ {
+ return {a.x + b.x, a.y + b.y, a.z + b.z};
+ }
+
+ void operator+=(const double3 &b)
+ {
+ this->x += b.x;
+ this->y += b.y;
+ this->z += b.z;
+ }
+
+ friend double3 operator-(const double3 &a, const double3 &b)
+ {
+ return {a.x - b.x, a.y - b.y, a.z - b.z};
+ }
+
+ friend double3 operator-(const double3 &a)
+ {
+ return {-a.x, -a.y, -a.z};
+ }
+
+ void operator-=(const double3 &b)
+ {
+ this->x -= b.x;
+ this->y -= b.y;
+ this->z -= b.z;
+ }
+
+ void operator*=(const double &scalar)
+ {
+ this->x *= scalar;
+ this->y *= scalar;
+ this->z *= scalar;
+ }
+
+ void operator*=(const double3 &other)
+ {
+ this->x *= other.x;
+ this->y *= other.y;
+ this->z *= other.z;
+ }
+
+ friend double3 operator*(const double3 &a, const double3 &b)
+ {
+ return {a.x * b.x, a.y * b.y, a.z * b.z};
+ }
+
+ friend double3 operator*(const double3 &a, const double &b)
+ {
+ return {a.x * b, a.y * b, a.z * b};
+ }
+
+ friend double3 operator*(const double &a, const double3 &b)
+ {
+ return b * a;
+ }
+
+ friend double3 operator/(const double3 &a, const double &b)
+ {
+ BLI_assert(b != 0.0);
+ return {a.x / b, a.y / b, a.z / b};
+ }
+
+ friend bool operator==(const double3 &a, const double3 &b)
+ {
+ return a.x == b.x && a.y == b.y && a.z == b.z;
+ }
+
+ friend bool operator!=(const double3 &a, const double3 &b)
+ {
+ return a.x != b.x || a.y != b.y || a.z != b.z;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const double3 &v)
+ {
+ stream << "(" << v.x << ", " << v.y << ", " << v.z << ")";
+ return stream;
+ }
+
+ static double dot(const double3 &a, const double3 &b)
+ {
+ return a.x * b.x + a.y * b.y + a.z * b.z;
+ }
+
+ static double3 cross_high_precision(const double3 &a, const double3 &b)
+ {
+ double3 result;
+ cross_v3_v3v3_db(result, a, b);
+ return result;
+ }
+
+ static double3 project(const double3 &a, const double3 &b)
+ {
+ double3 result;
+ project_v3_v3v3_db(result, a, b);
+ return result;
+ }
+
+ static double distance(const double3 &a, const double3 &b)
+ {
+ return (a - b).length();
+ }
+
+ static double distance_squared(const double3 &a, const double3 &b)
+ {
+ return double3::dot(a, b);
+ }
+
+ static double3 interpolate(const double3 &a, const double3 &b, double t)
+ {
+ return a * (1 - t) + b * t;
+ }
+
+ static double3 abs(const double3 &a)
+ {
+ return double3(fabs(a.x), fabs(a.y), fabs(a.z));
+ }
+
+ static int dominant_axis(const double3 &a)
+ {
+ double x = (a.x >= 0) ? a.x : -a.x;
+ double y = (a.y >= 0) ? a.y : -a.y;
+ double z = (a.z >= 0) ? a.z : -a.z;
+ return ((x > y) ? ((x > z) ? 0 : 2) : ((y > z) ? 1 : 2));
+ }
+
+ static double3 cross_poly(Span<double3> poly);
+};
+
+} // namespace blender
diff --git a/source/blender/blenlib/BLI_float2.hh b/source/blender/blenlib/BLI_float2.hh
index e55a8de4633..1290eb6c65c 100644
--- a/source/blender/blenlib/BLI_float2.hh
+++ b/source/blender/blenlib/BLI_float2.hh
@@ -47,6 +47,11 @@ struct float2 {
return &x;
}
+ float length() const
+ {
+ return len_v2(*this);
+ }
+
float2 &operator+=(const float2 &other)
{
x += other.x;
@@ -107,6 +112,47 @@ struct float2 {
return stream;
}
+ static float dot(const float2 &a, const float2 &b)
+ {
+ return a.x * b.x + a.y * b.y;
+ }
+
+ static float2 interpolate(const float2 &a, const float2 &b, float t)
+ {
+ return a * (1 - t) + b * t;
+ }
+
+ static float2 abs(const float2 &a)
+ {
+ return float2(fabsf(a.x), fabsf(a.y));
+ }
+
+ static float distance(const float2 &a, const float2 &b)
+ {
+ return (a - b).length();
+ }
+
+ static float distance_squared(const float2 &a, const float2 &b)
+ {
+ return float2::dot(a, b);
+ }
+
+ struct isect_result {
+ enum {
+ LINE_LINE_COLINEAR = -1,
+ LINE_LINE_NONE = 0,
+ LINE_LINE_EXACT = 1,
+ LINE_LINE_CROSS = 2,
+ } kind;
+ float lambda;
+ float mu;
+ };
+
+ static isect_result isect_seg_seg(const float2 &v1,
+ const float2 &v2,
+ const float2 &v3,
+ const float2 &v4);
+
friend bool operator==(const float2 &a, const float2 &b)
{
return a.x == b.x && a.y == b.y;
diff --git a/source/blender/blenlib/BLI_float3.hh b/source/blender/blenlib/BLI_float3.hh
index 2d90498fee8..17b3f56453c 100644
--- a/source/blender/blenlib/BLI_float3.hh
+++ b/source/blender/blenlib/BLI_float3.hh
@@ -243,6 +243,11 @@ struct float3 {
{
return a * (1 - t) + b * t;
}
+
+ static float3 abs(const float3 &a)
+ {
+ return float3(fabsf(a.x), fabsf(a.y), fabsf(a.z));
+ }
};
} // namespace blender
diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h
index f407da3133f..d2bb60717ca 100644
--- a/source/blender/blenlib/BLI_math_base.h
+++ b/source/blender/blenlib/BLI_math_base.h
@@ -242,6 +242,14 @@ double double_round(double x, int ndigits);
} \
(void)0
+# define BLI_ASSERT_UNIT_V3_DB(v) \
+ { \
+ const double _test_unit = len_squared_v3_db(v); \
+ BLI_assert(!(fabs(_test_unit - 1.0) >= BLI_ASSERT_UNIT_EPSILON) || \
+ !(fabs(_test_unit) >= BLI_ASSERT_UNIT_EPSILON)); \
+ } \
+ (void)0
+
# define BLI_ASSERT_UNIT_V2(v) \
{ \
const float _test_unit = len_squared_v2(v); \
diff --git a/source/blender/blenlib/BLI_math_boolean.hh b/source/blender/blenlib/BLI_math_boolean.hh
new file mode 100644
index 00000000000..da79e9eeaba
--- /dev/null
+++ b/source/blender/blenlib/BLI_math_boolean.hh
@@ -0,0 +1,61 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ * \brief Math vector functions needed specifically for mesh intersect and boolean.
+ */
+
+#include "BLI_double2.hh"
+#include "BLI_double3.hh"
+
+#ifdef WITH_GMP
+#include "BLI_math_mpq.hh"
+#include "BLI_mpq2.hh"
+#include "BLI_mpq3.hh"
+#endif
+
+namespace blender {
+
+/* #orient2d gives the exact result, using multi-precision arithmetic when result
+* is close to zero. orient3d_fast just uses double arithmetic, so may be
+* wrong if the answer is very close to zero.
+* Similarly, for #incircle and #incircle_fast. */
+int orient2d(const double2 &a, const double2 &b, const double2 &c);
+int orient2d_fast(const double2 &a, const double2 &b, const double2 &c);
+
+int incircle(const double2 &a, const double2 &b, const double2 &c, const double2 &d);
+int incircle_fast(const double2 &a, const double2 &b, const double2 &c, const double2 &d);
+
+
+/* #orient3d gives the exact result, using multi-precision arithmetic when result
+ * is close to zero. orient3d_fast just uses double arithmetic, so may be
+ * wrong if the answer is very close to zero.
+ * Similarly, for #insphere and #insphere_fast. */
+int orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d);
+int orient3d_fast(const double3 &a, const double3 &b, const double3 &c, const double3 &d);
+
+int insphere(const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e);
+int insphere_fast(const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e);
+
+#ifdef WITH_GMP
+int orient2d(const mpq2 &a, const mpq2 &b, const mpq2 &c);
+int incircle(const mpq2 &a, const mpq2 &b, const mpq2 &c, const mpq2 &d);
+int orient3d(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &d);
+#endif
+} // namespace blender
diff --git a/source/blender/blenlib/BLI_math_mpq.hh b/source/blender/blenlib/BLI_math_mpq.hh
new file mode 100644
index 00000000000..3d8c6349187
--- /dev/null
+++ b/source/blender/blenlib/BLI_math_mpq.hh
@@ -0,0 +1,36 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#ifdef WITH_GMP
+
+/* This file uses an external file header to define the multi-precision
+ * rational type, mpq_class.
+ * This class keeps separate multi-precision integer numerator and
+ * denominator, reduced to lowest terms after each arithmetic operation.
+ * It can be used where it is important to have exact arithmetic results.
+ *
+ * See gmplib.org for full documentation. In particular:
+ * https://gmplib.org/manual/C_002b_002b-Interface-Rationals
+ */
+# include "gmpxx.h"
+
+#endif /* WITH_GMP */
diff --git a/source/blender/blenlib/BLI_math_vector.h b/source/blender/blenlib/BLI_math_vector.h
index 1ccfe5d86b1..aa639cd3a5d 100644
--- a/source/blender/blenlib/BLI_math_vector.h
+++ b/source/blender/blenlib/BLI_math_vector.h
@@ -140,6 +140,7 @@ MINLINE void mul_v2_v2fl(float r[2], const float a[2], float f);
MINLINE void mul_v3_fl(float r[3], float f);
MINLINE void mul_v3db_db(double r[3], double f);
MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f);
+MINLINE void mul_v3_v3db_db(double r[3], const double a[3], double f);
MINLINE void mul_v2_v2(float r[2], const float a[2]);
MINLINE void mul_v2_v2v2(float r[2], const float a[2], const float b[2]);
MINLINE void mul_v3_v3(float r[3], const float a[3]);
@@ -236,17 +237,21 @@ MINLINE float len_manhattan_v3v3(const float a[3], const float b[3]) ATTR_WARN_U
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT;
+MINLINE double len_v3_db(const double a[3]) ATTR_WARN_UNUSED_RESULT;
+MINLINE double len_squared_v3_db(const double v[3]) ATTR_WARN_UNUSED_RESULT;
MINLINE float normalize_v2_length(float r[2], const float unit_scale);
MINLINE float normalize_v2_v2_length(float r[2], const float a[2], const float unit_scale);
MINLINE float normalize_v3_length(float r[3], const float unit_scale);
MINLINE float normalize_v3_v3_length(float r[3], const float a[3], const float unit_scale);
-MINLINE double normalize_v3_length_d(double n[3], const double unit_scale);
+MINLINE double normalize_v3_length_db(double n[3], const double unit_scale);
+MINLINE double normalize_v3_v3_length_db(double r[3], const double a[3], const double unit_scale);
MINLINE float normalize_v2(float r[2]);
MINLINE float normalize_v2_v2(float r[2], const float a[2]);
MINLINE float normalize_v3(float r[3]);
MINLINE float normalize_v3_v3(float r[3], const float a[3]);
-MINLINE double normalize_v3_d(double n[3]);
+MINLINE double normalize_v3_v3_db(double r[3], const double a[3]);
+MINLINE double normalize_v3_db(double n[3]);
/******************************* Interpolation *******************************/
@@ -402,6 +407,7 @@ void angle_poly_v3(float *angles, const float *verts[3], int len);
void project_v2_v2v2(float out[2], const float p[2], const float v_proj[2]);
void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3]);
+void project_v3_v3v3_db(double out[3], const double p[3], const double v_proj[3]);
void project_v2_v2v2_normalized(float out[2], const float p[2], const float v_proj[2]);
void project_v3_v3v3_normalized(float out[3], const float p[3], const float v_proj[3]);
void project_plane_v3_v3v3(float out[3], const float p[3], const float v_plane[3]);
@@ -410,6 +416,7 @@ void project_plane_normalized_v3_v3v3(float out[3], const float p[3], const floa
void project_plane_normalized_v2_v2v2(float out[2], const float p[2], const float v_plane[2]);
void project_v3_plane(float out[3], const float plane_no[3], const float plane_co[3]);
void reflect_v3_v3v3(float out[3], const float vec[3], const float normal[3]);
+void reflect_v3_v3v3_db(double out[3], const double vec[3], const double normal[3]);
void ortho_basis_v3v3_v3(float r_n1[3], float r_n2[3], const float n[3]);
void ortho_v3_v3(float out[3], const float v[3]);
void ortho_v2_v2(float out[2], const float v[2]);
diff --git a/source/blender/blenlib/BLI_mesh_boolean.hh b/source/blender/blenlib/BLI_mesh_boolean.hh
new file mode 100644
index 00000000000..693639f20f2
--- /dev/null
+++ b/source/blender/blenlib/BLI_mesh_boolean.hh
@@ -0,0 +1,79 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+/* The boolean functions in Blenlib use exact arithmetic, so require GMP. */
+#ifdef WITH_GMP
+
+# include "BLI_mesh_intersect.hh"
+# include <functional>
+
+namespace blender::meshintersect {
+
+/**
+ * Enum values after BOOLEAN_NONE need to match BMESH_ISECT_BOOLEAN_... values in
+ * editmesh_intersect.c. */
+enum class BoolOpType {
+ None = -1,
+ /* Aligned with #BooleanModifierOp. */
+ Intersect = 0,
+ Union = 1,
+ Difference = 2,
+};
+
+/**
+ * Do the boolean operation op on the mesh pm_in.
+ * The boolean operation has nshapes input shapes. Each is a disjoint subset of the input mesh.
+ * The shape_fn argument, when applied to an input face argument, says which shape it is in
+ * (should be a value from -1 to nshapes - 1: if -1, it is not part of any shape).
+ * The use_self arg says whether or not the function should assume that faces in the
+ * same shape intersect - if the argument is true, such self-intersections will be found.
+ * Sometimes the caller has already done a triangulation of the faces,
+ * and if so, *pm_triangulated contains a triangulation: if non-null, it contains a mesh
+ * of triangles, each of whose orig_field says which face in pm that triangle belongs to.
+ * pm arg isn't const because we may populate its verts (for debugging).
+ * Same goes for the pm_triangulated arg.
+ * The output IMesh will have faces whose orig fields map back to faces and edges in
+ * the input mesh.
+ */
+IMesh boolean_mesh(IMesh &imesh,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMesh *pm_triangulated,
+ IMeshArena *arena);
+
+/**
+ * This is like boolean, but operates on IMesh's whose faces are all triangles.
+ * It is exposed mainly for unit testing, at the moment: boolean_mesh() uses
+ * it to do most of its work.
+ */
+IMesh boolean_trimesh(IMesh &trimesh,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMeshArena *arena);
+
+} // namespace blender::meshintersect
+
+#endif /* WITH_GMP */
diff --git a/source/blender/blenlib/BLI_mesh_intersect.hh b/source/blender/blenlib/BLI_mesh_intersect.hh
new file mode 100644
index 00000000000..877363b998a
--- /dev/null
+++ b/source/blender/blenlib/BLI_mesh_intersect.hh
@@ -0,0 +1,359 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ *
+ * Mesh intersection library functions.
+ * Uses exact arithmetic, so need GMP.
+ */
+
+#ifdef WITH_GMP
+
+# include <iostream>
+
+# include "BLI_array.hh"
+# include "BLI_double3.hh"
+# include "BLI_index_range.hh"
+# include "BLI_map.hh"
+# include "BLI_math_mpq.hh"
+# include "BLI_mpq3.hh"
+# include "BLI_span.hh"
+# include "BLI_utility_mixins.hh"
+# include "BLI_vector.hh"
+
+namespace blender::meshintersect {
+
+constexpr int NO_INDEX = -1;
+
+/**
+ * Vertex coordinates are stored both as #double3 and #mpq3, which should agree.
+ * Most calculations are done in exact arithmetic, using the mpq3 version,
+ * but some predicates can be sped up by operating on doubles and using error analysis
+ * to find the cases where that is good enough.
+ * Vertices also carry along an id, created on allocation. The id
+ * is useful for making algorithms that don't depend on pointers.
+ * Also, they are easier to read while debugging.
+ * They also carry an orig index, which can be used to tie them back to
+ * vertices that the caller may have in a different way (e.g., #BMVert).
+ * An orig index can be #NO_INDEX, indicating the Vert was created by
+ * the algorithm and doesn't match an original Vert.
+ * Vertices can be reliably compared for equality,
+ * and hashed (on their co_exact field).
+ */
+struct Vert {
+ mpq3 co_exact;
+ double3 co;
+ int id = NO_INDEX;
+ int orig = NO_INDEX;
+
+ Vert() = default;
+ Vert(const mpq3 &mco, const double3 &dco, int id, int orig);
+ ~Vert() = default;
+
+ /** Test equality on the co_exact field. */
+ bool operator==(const Vert &other) const;
+
+ /** Hash on the co_exact field. */
+ uint64_t hash() const;
+};
+
+std::ostream &operator<<(std::ostream &os, const Vert *v);
+
+/**
+ * A Plane whose equation is `dot(norm, p) + d = 0`.
+ * The norm and d fields are always present, but the norm_exact
+ * and d_exact fields may be lazily populated. Since we don't
+ * store degenerate planes, we can tell if a the exact versions
+ * are not populated yet by having `norm_exact == 0`.
+ */
+struct Plane {
+ mpq3 norm_exact;
+ mpq_class d_exact;
+ double3 norm;
+ double d;
+
+ Plane() = default;
+ Plane(const mpq3 &norm_exact, const mpq_class &d_exact);
+ Plane(const double3 &norm, const double d);
+
+ /* Test equality on the exact fields. */
+ bool operator==(const Plane &other) const;
+
+ /* Hash onthe exact fields. */
+ uint64_t hash() const;
+
+ void make_canonical();
+ bool exact_populated() const;
+ void populate_exact();
+};
+
+std::ostream &operator<<(std::ostream &os, const Plane *plane);
+
+/**
+ * A #Face has a sequence of Verts that for a CCW ordering around them.
+ * Faces carry an index, created at allocation time, useful for making
+ * pointer-independent algorithms, and for debugging.
+ * They also carry an original index, meaningful to the caller.
+ * And they carry original edge indices too: each is a number meaningful
+ * to the caller for the edge starting from the corresponding face position.
+ * A "face position" is the index of a vertex around a face.
+ * Faces don't own the memory pointed at by the vert array.
+ * Also indexed by face position, the is_intersect array says
+ * for each edge whether or not it is the result of intersecting
+ * with another face in the intersect algorithm.
+ * Since the intersect algorithm needs the plane for each face,
+ * a #Face also stores the Plane of the face, but this is only
+ * populate later because not all faces will be intersected.
+ */
+struct Face : NonCopyable {
+ Array<const Vert *> vert;
+ Array<int> edge_orig;
+ Array<bool> is_intersect;
+ Plane *plane = nullptr;
+ int id = NO_INDEX;
+ int orig = NO_INDEX;
+
+ using FacePos = int;
+
+ Face() = default;
+ Face(Span<const Vert *> verts, int id, int orig, Span<int> edge_origs, Span<bool> is_intersect);
+ Face(Span<const Vert *> verts, int id, int orig);
+ ~Face();
+
+ bool is_tri() const
+ {
+ return vert.size() == 3;
+ }
+
+ /* Test equality of verts, in same positions. */
+ bool operator==(const Face &other) const;
+
+ /* Test equaliy faces allowing cyclic shifts. */
+ bool cyclic_equal(const Face &other) const;
+
+ FacePos next_pos(FacePos p) const
+ {
+ return (p + 1) % vert.size();
+ }
+
+ FacePos prev_pos(FacePos p) const
+ {
+ return (p + vert.size() - 1) % vert.size();
+ }
+
+ const Vert *const &operator[](int index) const
+ {
+ return vert[index];
+ }
+
+ int size() const
+ {
+ return vert.size();
+ }
+
+ const Vert *const *begin() const
+ {
+ return vert.begin();
+ }
+
+ const Vert *const *end() const
+ {
+ return vert.end();
+ }
+
+ IndexRange index_range() const
+ {
+ return IndexRange(vert.size());
+ }
+
+ void populate_plane(bool need_exact);
+
+ bool plane_populated() const
+ {
+ return plane != nullptr;
+ }
+};
+
+std::ostream &operator<<(std::ostream &os, const Face *f);
+
+/**
+ * #IMeshArena is the owner of the Vert and Face resources used
+ * during a run of one of the mesh-intersect main functions.
+ * It also keeps has a hash table of all Verts created so that it can
+ * ensure that only one instance of a Vert with a given co_exact will
+ * exist. I.e., it de-duplicates the vertices.
+ */
+class IMeshArena : NonCopyable, NonMovable {
+ class IMeshArenaImpl;
+ std::unique_ptr<IMeshArenaImpl> pimpl_;
+
+ public:
+ IMeshArena();
+ ~IMeshArena();
+
+ /**
+ * Provide hints to number of expected Verts and Faces expected
+ * to be allocated.
+ */
+ void reserve(int vert_num_hint, int face_num_hint);
+
+ int tot_allocated_verts() const;
+ int tot_allocated_faces() const;
+
+ /**
+ * These add routines find and return an existing Vert with the same
+ * co_exact, if it exists (the orig argument is ignored in this case),
+ * or else allocates and returns a new one. The index field of a
+ * newly allocated Vert will be the index in creation order.
+ */
+ const Vert *add_or_find_vert(const mpq3 &co, int orig);
+ const Vert *add_or_find_vert(const double3 &co, int orig);
+
+ Face *add_face(Span<const Vert *> verts,
+ int orig,
+ Span<int> edge_origs,
+ Span<bool> is_intersect);
+ Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs);
+ Face *add_face(Span<const Vert *> verts, int orig);
+
+ /** The following return #nullptr if not found. */
+ const Vert *find_vert(const mpq3 &co) const;
+ const Face *find_face(Span<const Vert *> verts) const;
+};
+
+/**
+ * A #blender::meshintersect::IMesh is a self-contained mesh structure
+ * that can be used in `blenlib` without depending on the rest of Blender.
+ * The Vert and #Face resources used in the #IMesh should be owned by
+ * some #IMeshArena.
+ * The Verts used by a #IMesh can be recovered from the Faces, so
+ * are usually not stored, but on request, the #IMesh can populate
+ * internal structures for indexing exactly the set of needed Verts,
+ * and also going from a Vert pointer to the index in that system.
+ */
+
+class IMesh {
+ Array<Face *> face_; /* Not `const` so can lazily populate planes. */
+ Array<const Vert *> vert_; /* Only valid if vert_populated_. */
+ Map<const Vert *, int> vert_to_index_; /* Only valid if vert_populated_. */
+ bool vert_populated_ = false;
+
+ public:
+ IMesh() = default;
+ IMesh(Span<Face *> faces) : face_(faces)
+ {
+ }
+
+ void set_faces(Span<Face *> faces);
+ Face *face(int index) const
+ {
+ return face_[index];
+ }
+
+ int face_size() const
+ {
+ return face_.size();
+ }
+
+ int vert_size() const
+ {
+ return vert_.size();
+ }
+
+ bool has_verts() const
+ {
+ return vert_populated_;
+ }
+
+ void set_dirty_verts()
+ {
+ vert_populated_ = false;
+ vert_to_index_.clear();
+ vert_ = Array<const Vert *>();
+ }
+
+ /* Pass `max_verts` if there is a good bound estimate on the maximum number of verts. */
+ void populate_vert();
+ void populate_vert(int max_verts);
+
+ const Vert *vert(int index) const
+ {
+ BLI_assert(vert_populated_);
+ return vert_[index];
+ }
+
+ /** Returns index in vert_ where v is, or #NO_INDEX. */
+ int lookup_vert(const Vert *v) const;
+
+ IndexRange vert_index_range() const
+ {
+ BLI_assert(vert_populated_);
+ return IndexRange(vert_.size());
+ }
+
+ IndexRange face_index_range() const
+ {
+ return IndexRange(face_.size());
+ }
+
+ Span<const Vert *> vertices() const
+ {
+ BLI_assert(vert_populated_);
+ return Span<const Vert *>(vert_);
+ }
+
+ Span<Face *> faces() const
+ {
+ return Span<Face *>(face_);
+ }
+
+ /**
+ * Replace face at given index with one that elides the
+ * vertices at the positions in face_pos_erase that are true.
+ * Use arena to allocate the new face in.
+ */
+ void erase_face_positions(int f_index, Span<bool> face_pos_erase, IMeshArena *arena);
+};
+
+std::ostream &operator<<(std::ostream &os, const IMesh &mesh);
+
+/**
+ * The output will have duplicate vertices merged and degenerate triangles ignored.
+ * If the input has overlapping co-planar triangles, then there will be
+ * as many duplicates as there are overlaps in each overlapping triangular region.
+ * The orig field of each #IndexedTriangle will give the orig index in the input #IMesh
+ * that the output triangle was a part of (input can have -1 for that field and then
+ * the index in `tri[]` will be used as the original index).
+ * The orig structure of the output #IMesh gives the originals for vertices and edges.
+ * Note: if the input tm_in has a non-empty orig structure, then it is ignored.
+ */
+IMesh trimesh_self_intersect(const IMesh &tm_in, IMeshArena *arena);
+
+IMesh trimesh_nary_intersect(const IMesh &tm_in,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMeshArena *arena);
+
+/** This has the side effect of populating verts in the #IMesh. */
+void write_obj_mesh(IMesh &m, const std::string &objname);
+
+} /* namespace blender::meshintersect */
+
+#endif /* WITH_GMP */
diff --git a/source/blender/blenlib/BLI_mpq2.hh b/source/blender/blenlib/BLI_mpq2.hh
new file mode 100644
index 00000000000..6261b50466b
--- /dev/null
+++ b/source/blender/blenlib/BLI_mpq2.hh
@@ -0,0 +1,184 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#ifdef WITH_GMP
+
+# include "BLI_math_mpq.hh"
+# include "BLI_mpq3.hh"
+
+namespace blender {
+
+struct mpq2 {
+ mpq_class x, y;
+
+ mpq2() = default;
+
+ mpq2(const mpq_class *ptr) : x{ptr[0]}, y{ptr[1]}
+ {
+ }
+
+ mpq2(mpq_class x, mpq_class y) : x(x), y(y)
+ {
+ }
+
+ mpq2(const mpq2 &other) : x(other.x), y(other.y)
+ {
+ }
+
+ mpq2(mpq2 &&other) noexcept : x(std::move(other.x)), y(std::move(other.y))
+ {
+ }
+
+ ~mpq2() = default;
+
+ mpq2 &operator=(const mpq2 &other)
+ {
+ if (this != &other) {
+ x = other.x;
+ y = other.y;
+ }
+ return *this;
+ }
+
+ mpq2 &operator=(mpq2 &&other) noexcept
+ {
+ x = std::move(other.x);
+ y = std::move(other.y);
+ return *this;
+ }
+
+ mpq2(const mpq3 &other) : x(other.x), y(other.y)
+ {
+ }
+
+ operator mpq_class *()
+ {
+ return &x;
+ }
+
+ operator const mpq_class *() const
+ {
+ return &x;
+ }
+
+ /**
+ * Cannot do this exactly in rational arithmetic!
+ * Approximate by going in and out of doubles.
+ */
+ mpq_class length() const
+ {
+ mpq_class lsquared = dot(*this, *this);
+ return mpq_class(sqrt(lsquared.get_d()));
+ }
+
+ friend mpq2 operator+(const mpq2 &a, const mpq2 &b)
+ {
+ return {a.x + b.x, a.y + b.y};
+ }
+
+ friend mpq2 operator-(const mpq2 &a, const mpq2 &b)
+ {
+ return {a.x - b.x, a.y - b.y};
+ }
+
+ friend mpq2 operator*(const mpq2 &a, mpq_class b)
+ {
+ return {a.x * b, a.y * b};
+ }
+
+ friend mpq2 operator/(const mpq2 &a, mpq_class b)
+ {
+ BLI_assert(b != 0);
+ return {a.x / b, a.y / b};
+ }
+
+ friend mpq2 operator*(mpq_class a, const mpq2 &b)
+ {
+ return b * a;
+ }
+
+ friend bool operator==(const mpq2 &a, const mpq2 &b)
+ {
+ return a.x == b.x && a.y == b.y;
+ }
+
+ friend bool operator!=(const mpq2 &a, const mpq2 &b)
+ {
+ return a.x != b.x || a.y != b.y;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const mpq2 &v)
+ {
+ stream << "(" << v.x << ", " << v.y << ")";
+ return stream;
+ }
+
+ static mpq_class dot(const mpq2 &a, const mpq2 &b)
+ {
+ return a.x * b.x + a.y * b.y;
+ }
+
+ static mpq2 interpolate(const mpq2 &a, const mpq2 &b, mpq_class t)
+ {
+ return a * (1 - t) + b * t;
+ }
+
+ static mpq2 abs(const mpq2 &a)
+ {
+ mpq_class abs_x = (a.x >= 0) ? a.x : -a.x;
+ mpq_class abs_y = (a.y >= 0) ? a.y : -a.y;
+ return mpq2(abs_x, abs_y);
+ }
+
+ static mpq_class distance(const mpq2 &a, const mpq2 &b)
+ {
+ return (a - b).length();
+ }
+
+ static mpq_class distance_squared(const mpq2 &a, const mpq2 &b)
+ {
+ return dot(a, b);
+ }
+
+ struct isect_result {
+ enum {
+ LINE_LINE_COLINEAR = -1,
+ LINE_LINE_NONE = 0,
+ LINE_LINE_EXACT = 1,
+ LINE_LINE_CROSS = 2,
+ } kind;
+ mpq_class lambda;
+ mpq_class mu;
+ };
+
+ static isect_result isect_seg_seg(const mpq2 &v1,
+ const mpq2 &v2,
+ const mpq2 &v3,
+ const mpq2 &v4);
+
+ /** There is a sensible use for hashing on exact arithmetic types. */
+ uint64_t hash() const;
+};
+
+} // namespace blender
+
+#endif /* WITH_GMP */
diff --git a/source/blender/blenlib/BLI_mpq3.hh b/source/blender/blenlib/BLI_mpq3.hh
new file mode 100644
index 00000000000..fb5e2b61cdb
--- /dev/null
+++ b/source/blender/blenlib/BLI_mpq3.hh
@@ -0,0 +1,281 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#ifdef WITH_GMP
+
+# include <iostream>
+
+# include "BLI_math.h"
+# include "BLI_math_mpq.hh"
+# include "BLI_span.hh"
+
+namespace blender {
+
+struct mpq3 {
+ mpq_class x, y, z;
+
+ mpq3() = default;
+
+ mpq3(const mpq_class *ptr) : x{ptr[0]}, y{ptr[1]}, z{ptr[2]}
+ {
+ }
+
+ mpq3(const mpq_class (*ptr)[3]) : mpq3((const mpq_class *)ptr)
+ {
+ }
+
+ explicit mpq3(mpq_class value) : x(value), y(value), z(value)
+ {
+ }
+
+ explicit mpq3(int value) : x(value), y(value), z(value)
+ {
+ }
+
+ mpq3(mpq_class x, mpq_class y, mpq_class z) : x{x}, y{y}, z{z}
+ {
+ }
+
+ operator const mpq_class *() const
+ {
+ return &x;
+ }
+
+ operator mpq_class *()
+ {
+ return &x;
+ }
+
+ /* Cannot do this exactly in rational arithmetic!
+ * Approximate by going in and out of doubles.
+ */
+ mpq_class normalize_and_get_length()
+ {
+ double dv[3] = {x.get_d(), y.get_d(), z.get_d()};
+ double len = normalize_v3_db(dv);
+ this->x = mpq_class(dv[0]);
+ this->y = mpq_class(dv[1]);
+ this->z = mpq_class(dv[2]);
+ return len;
+ }
+
+ mpq3 normalized() const
+ {
+ double dv[3] = {x.get_d(), y.get_d(), z.get_d()};
+ double dr[3];
+ normalize_v3_v3_db(dr, dv);
+ return mpq3(mpq_class(dr[0]), mpq_class(dr[1]), mpq_class(dr[2]));
+ }
+
+ /* Cannot do this exactly in rational arithmetic!
+ * Approximate by going in and out of double.
+ */
+ mpq_class length() const
+ {
+ mpq_class lsquared = this->length_squared();
+ double dsquared = lsquared.get_d();
+ double d = sqrt(dsquared);
+ return mpq_class(d);
+ }
+
+ mpq_class length_squared() const
+ {
+ return x * x + y * y + z * z;
+ }
+
+ void reflect(const mpq3 &normal)
+ {
+ *this = this->reflected(normal);
+ }
+
+ mpq3 reflected(const mpq3 &normal) const
+ {
+ mpq3 result;
+ const mpq_class dot2 = 2 * dot(*this, normal);
+ result.x = this->x - (dot2 * normal.x);
+ result.y = this->y - (dot2 * normal.y);
+ result.z = this->z - (dot2 * normal.z);
+ return result;
+ }
+
+ static mpq3 safe_divide(const mpq3 &a, const mpq3 &b)
+ {
+ mpq3 result;
+ result.x = (b.x == 0) ? mpq_class(0) : a.x / b.x;
+ result.y = (b.y == 0) ? mpq_class(0) : a.y / b.y;
+ result.z = (b.z == 0) ? mpq_class(0) : a.z / b.z;
+ return result;
+ }
+
+ void invert()
+ {
+ x = -x;
+ y = -y;
+ z = -z;
+ }
+
+ friend mpq3 operator+(const mpq3 &a, const mpq3 &b)
+ {
+ return mpq3(a.x + b.x, a.y + b.y, a.z + b.z);
+ }
+
+ void operator+=(const mpq3 &b)
+ {
+ this->x += b.x;
+ this->y += b.y;
+ this->z += b.z;
+ }
+
+ friend mpq3 operator-(const mpq3 &a, const mpq3 &b)
+ {
+ return mpq3(a.x - b.x, a.y - b.y, a.z - b.z);
+ }
+
+ friend mpq3 operator-(const mpq3 &a)
+ {
+ return mpq3(-a.x, -a.y, -a.z);
+ }
+
+ void operator-=(const mpq3 &b)
+ {
+ this->x -= b.x;
+ this->y -= b.y;
+ this->z -= b.z;
+ }
+
+ void operator*=(mpq_class scalar)
+ {
+ this->x *= scalar;
+ this->y *= scalar;
+ this->z *= scalar;
+ }
+
+ void operator*=(const mpq3 &other)
+ {
+ this->x *= other.x;
+ this->y *= other.y;
+ this->z *= other.z;
+ }
+
+ friend mpq3 operator*(const mpq3 &a, const mpq3 &b)
+ {
+ return {a.x * b.x, a.y * b.y, a.z * b.z};
+ }
+
+ friend mpq3 operator*(const mpq3 &a, const mpq_class &b)
+ {
+ return mpq3(a.x * b, a.y * b, a.z * b);
+ }
+
+ friend mpq3 operator*(const mpq_class &a, const mpq3 &b)
+ {
+ return mpq3(a * b.x, a * b.y, a * b.z);
+ }
+
+ friend mpq3 operator/(const mpq3 &a, const mpq_class &b)
+ {
+ BLI_assert(b != 0);
+ return mpq3(a.x / b, a.y / b, a.z / b);
+ }
+
+ friend bool operator==(const mpq3 &a, const mpq3 &b)
+ {
+ return a.x == b.x && a.y == b.y && a.z == b.z;
+ }
+
+ friend bool operator!=(const mpq3 &a, const mpq3 &b)
+ {
+ return a.x != b.x || a.y != b.y || a.z != b.z;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const mpq3 &v)
+ {
+ stream << "(" << v.x << ", " << v.y << ", " << v.z << ")";
+ return stream;
+ }
+
+ static mpq_class dot(const mpq3 &a, const mpq3 &b)
+ {
+ return a.x * b.x + a.y * b.y + a.z * b.z;
+ }
+
+ static mpq3 cross(const mpq3 &a, const mpq3 &b)
+ {
+ return mpq3(a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]);
+ }
+
+ static mpq3 cross_high_precision(const mpq3 &a, const mpq3 &b)
+ {
+ return cross(a, b);
+ }
+
+ static mpq3 project(const mpq3 &a, const mpq3 &b)
+ {
+ const mpq_class mul = mpq3::dot(a, b) / mpq3::dot(b, b);
+ return mpq3(mul * b[0], mul * b[1], mul * b[2]);
+ }
+
+ static mpq_class distance(const mpq3 &a, const mpq3 &b)
+ {
+ mpq3 diff(a.x - b.x, a.y - b.y, a.z - b.z);
+ return diff.length();
+ }
+
+ static mpq_class distance_squared(const mpq3 &a, const mpq3 &b)
+ {
+ mpq3 diff(a.x - b.x, a.y - b.y, a.z - b.z);
+ return mpq3::dot(diff, diff);
+ }
+
+ static mpq3 interpolate(const mpq3 &a, const mpq3 &b, mpq_class t)
+ {
+ mpq_class s = 1 - t;
+ return mpq3(a.x * s + b.x * t, a.y * s + b.y * t, a.z * s + b.z * t);
+ }
+
+ static mpq3 abs(const mpq3 &a)
+ {
+ mpq_class abs_x = (a.x >= 0) ? a.x : -a.x;
+ mpq_class abs_y = (a.y >= 0) ? a.y : -a.y;
+ mpq_class abs_z = (a.z >= 0) ? a.z : -a.z;
+ return mpq3(abs_x, abs_y, abs_z);
+ }
+
+ static int dominant_axis(const mpq3 &a)
+ {
+ mpq_class x = (a.x >= 0) ? a.x : -a.x;
+ mpq_class y = (a.y >= 0) ? a.y : -a.y;
+ mpq_class z = (a.z >= 0) ? a.z : -a.z;
+ return ((x > y) ? ((x > z) ? 0 : 2) : ((y > z) ? 1 : 2));
+ }
+
+ static mpq3 cross_poly(Span<mpq3> poly);
+
+ /** There is a sensible use for hashing on exact arithmetic types. */
+ uint64_t hash() const;
+};
+
+uint64_t hash_mpq_class(const mpq_class &value);
+
+} // namespace blender
+
+#endif /* WITH_GMP */
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 2f1c3436806..1db45cff09a 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -32,6 +32,7 @@ set(INC
set(INC_SYS
${ZLIB_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS}
+ ${GMP_INCLUDE_DIRS}
)
set(SRC
@@ -64,7 +65,7 @@ set(SRC
intern/boxpack_2d.c
intern/buffer.c
intern/convexhull_2d.c
- intern/delaunay_2d.c
+ intern/delaunay_2d.cc
intern/dot_export.cc
intern/dynlib.c
intern/easing.c
@@ -89,6 +90,7 @@ set(SRC
intern/math_base_inline.c
intern/math_base_safe_inline.c
intern/math_bits_inline.c
+ intern/math_boolean.cc
intern/math_color.c
intern/math_color_blend_inline.c
intern/math_color_inline.c
@@ -99,9 +101,12 @@ set(SRC
intern/math_rotation.c
intern/math_solvers.c
intern/math_statistics.c
+ intern/math_vec.cc
intern/math_vector.c
intern/math_vector_inline.c
intern/memory_utils.c
+ intern/mesh_boolean.cc
+ intern/mesh_intersect.cc
intern/noise.c
intern/path_util.c
intern/polyfill_2d.c
@@ -170,6 +175,8 @@ set(SRC
BLI_dlrbTree.h
BLI_dot_export.hh
BLI_dot_export_attribute_enums.hh
+ BLI_double2.hh
+ BLI_double3.hh
BLI_dynlib.h
BLI_dynstr.h
BLI_easing.h
@@ -214,11 +221,13 @@ set(SRC
BLI_math_base.h
BLI_math_base_safe.h
BLI_math_bits.h
+ BLI_math_boolean.hh
BLI_math_color.h
BLI_math_color_blend.h
BLI_math_geom.h
BLI_math_inline.h
BLI_math_interp.h
+ BLI_math_mpq.hh
BLI_math_matrix.h
BLI_math_rotation.h
BLI_math_solvers.h
@@ -230,6 +239,10 @@ set(SRC
BLI_memory_utils.h
BLI_memory_utils.hh
BLI_mempool.h
+ BLI_mesh_boolean.hh
+ BLI_mesh_intersect.hh
+ BLI_mpq2.hh
+ BLI_mpq3.hh
BLI_noise.h
BLI_path_util.h
BLI_polyfill_2d.h
@@ -306,6 +319,18 @@ if(WITH_TBB)
)
endif()
+if(WITH_GMP)
+ add_definitions(-DWITH_GMP)
+
+ list(APPEND INC_SYS
+ ${GMP_INCLUDE_DIRS}
+ )
+
+ list(APPEND LIB
+ ${GMP_LIBRARIES}
+ )
+endif()
+
if(WIN32)
list(APPEND INC
../../../intern/utfconv
@@ -374,6 +399,8 @@ if(WITH_GTESTS)
tests/BLI_math_vector_test.cc
tests/BLI_memiter_test.cc
tests/BLI_memory_utils_test.cc
+ tests/BLI_mesh_boolean_test.cc
+ tests/BLI_mesh_intersect_test.cc
tests/BLI_multi_value_map_test.cc
tests/BLI_path_util_test.cc
tests/BLI_polyfill_2d_test.cc
diff --git a/source/blender/blenlib/intern/delaunay_2d.c b/source/blender/blenlib/intern/delaunay_2d.c
deleted file mode 100644
index ee22859c1d6..00000000000
--- a/source/blender/blenlib/intern/delaunay_2d.c
+++ /dev/null
@@ -1,5173 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-/** \file
- * \ingroup bli
- *
- * Constrained 2d Delaunay Triangulation.
- */
-
-#include "MEM_guardedalloc.h"
-
-#include "BLI_array.h"
-#include "BLI_bitmap.h"
-#include "BLI_linklist.h"
-#include "BLI_math.h"
-#include "BLI_memarena.h"
-#include "BLI_mempool.h"
-
-#include "BLI_delaunay_2d.h"
-
-/* Uncomment this define to get helpful debugging functions etc. defined. */
-// #define DEBUG_CDT
-
-struct CDTEdge;
-struct CDTFace;
-struct CDTVert;
-
-typedef struct SymEdge {
- struct SymEdge *next; /* In face, doing CCW traversal of face. */
- struct SymEdge *rot; /* CCW around vert. */
- struct CDTVert *vert; /* Vert at origin. */
- struct CDTEdge *edge; /* Undirected edge this is for. */
- struct CDTFace *face; /* Face on left side. */
-} SymEdge;
-
-typedef struct CDTVert {
- double co[2]; /* Coordinate. */
- SymEdge *symedge; /* Some edge attached to it. */
- LinkNode *input_ids; /* List of corresponding vertex input ids. */
- int index; /* Index into array that cdt keeps. */
- int merge_to_index; /* Index of a CDTVert that this has merged to. -1 if no merge. */
- int visit_index; /* Which visit epoch has this been seen. */
-} CDTVert;
-
-typedef struct CDTEdge {
- LinkNode *input_ids; /* List of input edge ids that this is part of. */
- SymEdge symedges[2]; /* The directed edges for this edge. */
- bool in_queue; /* Used in flipping algorithm. */
-} CDTEdge;
-
-typedef struct CDTFace {
- SymEdge *symedge; /* A symedge in face; only used during output, so only valid then. */
- LinkNode *input_ids; /* List of input face ids that this is part of. */
- int visit_index; /* Which visit epoch has this been seen. */
- bool deleted; /* Marks this face no longer used. */
- bool in_queue; /* Used in remove_small_features algorithm. */
-} CDTFace;
-
-typedef struct CDT_state {
- LinkNode *edges; /* List of CDTEdge pointer. */
- LinkNode *faces; /* List of CDTFace pointer. */
- CDTFace *outer_face; /* Which CDTFace is the outer face. */
- CDTVert **vert_array; /* Array of CDTVert pointer, grows. */
- int vert_array_len; /* Current length of vert_array. */
- int vert_array_len_alloc; /* Allocated length of vert_array. */
- int input_vert_tot; /* How many verts were in input (will be first in vert_array). */
- double minx; /* Used for debug drawing. */
- double miny; /* Used for debug drawing. */
- double maxx; /* Used for debug drawing. */
- double maxy; /* Used for debug drawing. */
- double margin; /* Used for debug drawing. */
- int visit_count; /* Used for visiting things without having to initialized their visit fields. */
- int face_edge_offset; /* Input edge id where we start numbering the face edges. */
- MemArena *arena; /* Most allocations are done from here, so can free all at once at end. */
- BLI_mempool *listpool; /* Allocations of ListNodes done from this pool. */
- double epsilon; /* The user-specified nearness limit. */
- double epsilon_squared; /* Square of epsilon. */
- bool output_prepared; /* Set after the mesh has been modified for output (may not be all
- triangles now). */
-} CDT_state;
-
-#define DLNY_ARENASIZE 1 << 14
-
-#ifdef DEBUG_CDT
-# ifdef __GNUC__
-# define ATTU __attribute__((unused))
-# else
-# define ATTU
-# endif
-# define F2(p) p[0], p[1]
-# define F3(p) p[0], p[1], p[2]
-struct CrossData;
-ATTU static void dump_se(const SymEdge *se, const char *lab);
-ATTU static void dump_se_short(const SymEdge *se, const char *lab);
-ATTU static void dump_v(const CDTVert *v, const char *lab);
-ATTU static void dump_se_cycle(const SymEdge *se, const char *lab, const int limit);
-ATTU static void dump_id_list(const LinkNode *id_list, const char *lab);
-ATTU static void dump_cross_data(struct CrossData *cd, const char *lab);
-ATTU static void dump_cdt(const CDT_state *cdt, const char *lab);
-ATTU static void dump_cdt_vert_neighborhood(CDT_state *cdt, int v, int maxdist, const char *lab);
-ATTU static void cdt_draw(CDT_state *cdt, const char *lab);
-ATTU static void cdt_draw_region(
- CDT_state *cdt, const char *lab, double minx, double miny, double maxx, double maxy);
-
-ATTU static void cdt_draw_vertex_region(CDT_state *cdt, int v, double dist, const char *lab);
-ATTU static void cdt_draw_edge_region(
- CDT_state *cdt, int v1, int v2, double dist, const char *lab);
-ATTU static void write_cdt_input_to_file(const CDT_input *inp);
-ATTU static void validate_cdt(CDT_state *cdt,
- bool check_all_tris,
- bool check_delaunay,
- bool check_visibility);
-#endif
-
-static void exactinit(void);
-static double orient2d(const double *pa, const double *pb, const double *pc);
-static double incircle(const double *pa, const double *pb, const double *pc, const double *pd);
-
-/** Return other #SymEdge for same #CDTEdge as se. */
-BLI_INLINE SymEdge *sym(const SymEdge *se)
-{
- return se->next->rot;
-}
-
-/** Return SymEdge whose next is se. */
-BLI_INLINE SymEdge *prev(const SymEdge *se)
-{
- return se->rot->next->rot;
-}
-
-/**
- * Return true if a -- b -- c are in that order, assuming they are on a straight line according to
- * orient2d and we know the order is either `abc` or `bac`.
- * This means `ab . ac` and `bc . ac` must both be non-negative. */
-static bool in_line(const double a[2], const double b[2], const double c[2])
-{
- double ab[2], bc[2], ac[2];
- sub_v2_v2v2_db(ab, b, a);
- sub_v2_v2v2_db(bc, c, b);
- sub_v2_v2v2_db(ac, c, a);
- if (dot_v2v2_db(ab, ac) < 0.0) {
- return false;
- }
- return dot_v2v2_db(bc, ac) >= 0.0;
-}
-
-#ifndef NDEBUG
-/** Is s2 reachable from s1 by next pointers with < limit hops? */
-static bool reachable(SymEdge *s1, SymEdge *s2, int limit)
-{
- int count = 0;
- for (SymEdge *s = s1; s && count < limit; s = s->next) {
- if (s == s2) {
- return true;
- }
- count++;
- }
- return false;
-}
-#endif
-
-/** Using array to store these instead of linked list so can make a random selection from them. */
-static CDTVert *add_cdtvert(CDT_state *cdt, double x, double y)
-{
- CDTVert *v = BLI_memarena_alloc(cdt->arena, sizeof(*v));
- v->co[0] = x;
- v->co[1] = y;
- v->input_ids = NULL;
- v->symedge = NULL;
- if (cdt->vert_array_len == cdt->vert_array_len_alloc) {
- CDTVert **old_array = cdt->vert_array;
- cdt->vert_array_len_alloc *= 4;
- cdt->vert_array = BLI_memarena_alloc(cdt->arena,
- cdt->vert_array_len_alloc * sizeof(cdt->vert_array[0]));
- memmove(cdt->vert_array, old_array, cdt->vert_array_len * sizeof(cdt->vert_array[0]));
- }
- BLI_assert(cdt->vert_array_len < cdt->vert_array_len_alloc);
- v->index = cdt->vert_array_len;
- v->merge_to_index = -1;
- v->visit_index = 0;
- cdt->vert_array[cdt->vert_array_len++] = v;
- return v;
-}
-
-static CDTEdge *add_cdtedge(
- CDT_state *cdt, CDTVert *v1, CDTVert *v2, CDTFace *fleft, CDTFace *fright)
-{
- CDTEdge *e = BLI_memarena_alloc(cdt->arena, sizeof(*e));
- SymEdge *se = &e->symedges[0];
- SymEdge *sesym = &e->symedges[1];
- e->input_ids = NULL;
- e->in_queue = false;
- BLI_linklist_prepend_arena(&cdt->edges, (void *)e, cdt->arena);
- se->edge = sesym->edge = e;
- se->face = fleft;
- sesym->face = fright;
- se->vert = v1;
- if (v1->symedge == NULL) {
- v1->symedge = se;
- }
- sesym->vert = v2;
- if (v2->symedge == NULL) {
- v2->symedge = sesym;
- }
- se->next = sesym->next = se->rot = sesym->rot = NULL;
- return e;
-}
-
-static CDTFace *add_cdtface(CDT_state *cdt)
-{
- CDTFace *f = BLI_memarena_alloc(cdt->arena, sizeof(*f));
- f->visit_index = 0;
- f->deleted = false;
- f->symedge = NULL;
- f->input_ids = NULL;
- f->in_queue = false;
- BLI_linklist_prepend_arena(&cdt->faces, (void *)f, cdt->arena);
- return f;
-}
-
-static bool id_in_list(const LinkNode *id_list, int id)
-{
- const LinkNode *ln;
-
- for (ln = id_list; ln; ln = ln->next) {
- if (POINTER_AS_INT(ln->link) == id) {
- return true;
- }
- }
- return false;
-}
-
-/** is any id in (range_start, range_start+1, ... , range_end) in id_list? */
-static bool id_range_in_list(const LinkNode *id_list, int range_start, int range_end)
-{
- const LinkNode *ln;
- int id;
-
- for (ln = id_list; ln; ln = ln->next) {
- id = POINTER_AS_INT(ln->link);
- if (id >= range_start && id <= range_end) {
- return true;
- }
- }
- return false;
-}
-
-static void add_to_input_ids(LinkNode **dst, int input_id, CDT_state *cdt)
-{
- if (!id_in_list(*dst, input_id)) {
- BLI_linklist_prepend_arena(dst, POINTER_FROM_INT(input_id), cdt->arena);
- }
-}
-
-static void add_list_to_input_ids(LinkNode **dst, const LinkNode *src, CDT_state *cdt)
-{
- const LinkNode *ln;
-
- for (ln = src; ln; ln = ln->next) {
- add_to_input_ids(dst, POINTER_AS_INT(ln->link), cdt);
- }
-}
-
-BLI_INLINE bool is_border_edge(const CDTEdge *e, const CDT_state *cdt)
-{
- return e->symedges[0].face == cdt->outer_face || e->symedges[1].face == cdt->outer_face;
-}
-
-BLI_INLINE bool is_constrained_edge(const CDTEdge *e)
-{
- return e->input_ids != NULL;
-}
-
-BLI_INLINE bool is_deleted_edge(const CDTEdge *e)
-{
- return e->symedges[0].next == NULL;
-}
-
-BLI_INLINE bool is_original_vert(const CDTVert *v, CDT_state *cdt)
-{
- return (v->index < cdt->input_vert_tot);
-}
-
-/** Return the Symedge that goes from v1 to v2, if it exists, else return NULL. */
-static SymEdge *find_symedge_between_verts(const CDTVert *v1, const CDTVert *v2)
-{
- SymEdge *tstart, *t;
-
- t = tstart = v1->symedge;
- do {
- if (t->next->vert == v2) {
- return t;
- }
- } while ((t = t->rot) != tstart);
- return NULL;
-}
-
-/** Return the SymEdge attached to v that has face f, if it exists, else return NULL. */
-static SymEdge *find_symedge_with_face(const CDTVert *v, const CDTFace *f)
-{
- SymEdge *tstart, *t;
-
- t = tstart = v->symedge;
- do {
- if (t->face == f) {
- return t;
- }
- } while ((t = t->rot) != tstart);
- return NULL;
-}
-
-/** Is there already an edge between a and b? */
-static inline bool exists_edge(const CDTVert *v1, const CDTVert *v2)
-{
- return find_symedge_between_verts(v1, v2) != NULL;
-}
-
-/** Is the vertex v incident on face f? */
-static bool vert_touches_face(const CDTVert *v, const CDTFace *f)
-{
- SymEdge *se = v->symedge;
- do {
- if (se->face == f) {
- return true;
- }
- } while ((se = se->rot) != v->symedge);
- return false;
-}
-
-/**
- * Assume s1 and s2 are both SymEdges in a face with > 3 sides,
- * and one is not the next of the other.
- * Add an edge from s1->v to s2->v, splitting the face in two.
- * The original face will continue to be associated with the subface
- * that has s1, and a new face will be made for s2's new face.
- * Return the new diagonal's CDTEdge *.
- */
-static CDTEdge *add_diagonal(CDT_state *cdt, SymEdge *s1, SymEdge *s2)
-{
- CDTEdge *ediag;
- CDTFace *fold, *fnew;
- SymEdge *sdiag, *sdiagsym;
- SymEdge *s1prev, *s1prevsym, *s2prev, *s2prevsym, *se;
- BLI_assert(reachable(s1, s2, 20000));
- BLI_assert(reachable(s2, s1, 20000));
- fold = s1->face;
- fnew = add_cdtface(cdt);
- s1prev = prev(s1);
- s1prevsym = sym(s1prev);
- s2prev = prev(s2);
- s2prevsym = sym(s2prev);
- ediag = add_cdtedge(cdt, s1->vert, s2->vert, fnew, fold);
- sdiag = &ediag->symedges[0];
- sdiagsym = &ediag->symedges[1];
- sdiag->next = s2;
- sdiagsym->next = s1;
- s2prev->next = sdiagsym;
- s1prev->next = sdiag;
- s1->rot = sdiag;
- sdiag->rot = s1prevsym;
- s2->rot = sdiagsym;
- sdiagsym->rot = s2prevsym;
-#ifdef DEBUG_CDT
- BLI_assert(reachable(s2, sdiag, 2000));
-#endif
- for (se = s2; se != sdiag; se = se->next) {
- se->face = fnew;
- }
- add_list_to_input_ids(&fnew->input_ids, fold->input_ids, cdt);
- return ediag;
-}
-
-/**
- * Add a dangling edge from an isolated v to the vert at se in the same face as se->face.
- */
-static CDTEdge *add_vert_to_symedge_edge(CDT_state *cdt, CDTVert *v, SymEdge *se)
-{
- CDTEdge *e;
- SymEdge *se_rot, *se_rotsym, *new_se, *new_se_sym;
-
- se_rot = se->rot;
- se_rotsym = sym(se_rot);
- e = add_cdtedge(cdt, v, se->vert, se->face, se->face);
- new_se = &e->symedges[0];
- new_se_sym = &e->symedges[1];
- new_se->next = se;
- new_se_sym->next = new_se;
- new_se->rot = new_se;
- new_se_sym->rot = se_rot;
- se->rot = new_se_sym;
- se_rotsym->next = new_se_sym;
- return e;
-}
-
-/**
- * Connect the verts of se1 and se2, assuming that currently those two #SymEdges are on
- * the outer boundary (have face == outer_face) of two components that are isolated from
- * each other.
- */
-static CDTEdge *connect_separate_parts(CDT_state *cdt, SymEdge *se1, SymEdge *se2)
-{
- CDTEdge *e;
- SymEdge *se1_rot, *se1_rotsym, *se2_rot, *se2_rotsym, *new_se, *new_se_sym;
-
- BLI_assert(se1->face == cdt->outer_face && se2->face == cdt->outer_face);
- se1_rot = se1->rot;
- se1_rotsym = sym(se1_rot);
- se2_rot = se2->rot;
- se2_rotsym = sym(se2_rot);
- e = add_cdtedge(cdt, se1->vert, se2->vert, cdt->outer_face, cdt->outer_face);
- new_se = &e->symedges[0];
- new_se_sym = &e->symedges[1];
- new_se->next = se2;
- new_se_sym->next = se1;
- new_se->rot = se1_rot;
- new_se_sym->rot = se2_rot;
- se1->rot = new_se;
- se2->rot = new_se_sym;
- se1_rotsym->next = new_se;
- se2_rotsym->next = new_se_sym;
- return e;
-}
-
-/**
- * Split \a se at fraction \a lambda,
- * and return the new #CDTEdge that is the new second half.
- * Copy the edge input_ids into the new one.
- */
-static CDTEdge *split_edge(CDT_state *cdt, SymEdge *se, double lambda)
-{
- const double *a, *b;
- double p[2];
- CDTVert *v;
- CDTEdge *e;
- SymEdge *sesym, *newse, *newsesym, *senext, *sesymprev, *sesymprevsym;
- /* Split e at lambda. */
- a = se->vert->co;
- b = se->next->vert->co;
- sesym = sym(se);
- sesymprev = prev(sesym);
- sesymprevsym = sym(sesymprev);
- senext = se->next;
- p[0] = (1.0 - lambda) * a[0] + lambda * b[0];
- p[1] = (1.0 - lambda) * a[1] + lambda * b[1];
- v = add_cdtvert(cdt, p[0], p[1]);
- e = add_cdtedge(cdt, v, se->next->vert, se->face, sesym->face);
- sesym->vert = v;
- newse = &e->symedges[0];
- newsesym = &e->symedges[1];
- se->next = newse;
- newsesym->next = sesym;
- newse->next = senext;
- newse->rot = sesym;
- sesym->rot = newse;
- senext->rot = newsesym;
- newsesym->rot = sesymprevsym;
- sesymprev->next = newsesym;
- if (newsesym->vert->symedge == sesym) {
- newsesym->vert->symedge = newsesym;
- }
- add_list_to_input_ids(&e->input_ids, se->edge->input_ids, cdt);
- return e;
-}
-
-/**
- * Delete an edge from the structure. The new combined face on either side of
- * the deleted edge will be the one that was e's face.
- * There will be now an unused face, marked by setting its deleted flag,
- * and an unused #CDTEdge, marked by setting the next and rot pointers of
- * its #SymEdge(s) to NULL.
- * <pre>
- * . v2 .
- * / \ / \
- * /f|j\ / \
- * / | \ / \
- * |
- * A | B A
- * \ e| / \ /
- * \ | / \ /
- * \h|i/ \ /
- * . v1 .
- * </pre>
- * Also handle variant cases where one or both ends
- * are attached only to e.
- */
-static void delete_edge(CDT_state *cdt, SymEdge *e)
-{
- SymEdge *esym, *f, *h, *i, *j, *k, *jsym, *hsym;
- CDTFace *aface, *bface;
- CDTVert *v1, *v2;
- bool v1_isolated, v2_isolated;
-
- esym = sym(e);
- v1 = e->vert;
- v2 = esym->vert;
- aface = e->face;
- bface = esym->face;
- f = e->next;
- h = prev(e);
- i = esym->next;
- j = prev(esym);
- jsym = sym(j);
- hsym = sym(h);
- v1_isolated = (i == e);
- v2_isolated = (f == esym);
-
- if (!v1_isolated) {
- h->next = i;
- i->rot = hsym;
- }
- if (!v2_isolated) {
- j->next = f;
- f->rot = jsym;
- }
- if (!v1_isolated && !v2_isolated && aface != bface) {
- for (k = i; k != f; k = k->next) {
- k->face = aface;
- }
- }
-
- /* If e was representative symedge for v1 or v2, fix that. */
- if (v1_isolated) {
- v1->symedge = NULL;
- }
- else if (v1->symedge == e) {
- v1->symedge = i;
- }
- if (v2_isolated) {
- v2->symedge = NULL;
- }
- else if (v2->symedge == esym) {
- v2->symedge = f;
- }
-
- /* Mark SymEdge as deleted by setting all its pointers to NULL. */
- e->next = e->rot = NULL;
- esym->next = esym->rot = NULL;
- if (!v1_isolated && !v2_isolated && aface != bface) {
- bface->deleted = true;
- if (cdt->outer_face == bface) {
- cdt->outer_face = aface;
- }
- }
-}
-
-static CDT_state *cdt_init(const CDT_input *in)
-{
- int i;
- MemArena *arena = BLI_memarena_new(DLNY_ARENASIZE, __func__);
- CDT_state *cdt = BLI_memarena_calloc(arena, sizeof(CDT_state));
-
- cdt->epsilon = (double)in->epsilon;
- cdt->epsilon_squared = cdt->epsilon * cdt->epsilon;
- cdt->arena = arena;
- cdt->input_vert_tot = in->verts_len;
- cdt->vert_array_len_alloc = 2 * in->verts_len;
- cdt->vert_array = BLI_memarena_alloc(arena,
- cdt->vert_array_len_alloc * sizeof(*cdt->vert_array));
- cdt->listpool = BLI_mempool_create(
- sizeof(LinkNode), 128 + 4 * in->verts_len, 128 + in->verts_len, 0);
-
- for (i = 0; i < in->verts_len; i++) {
- add_cdtvert(cdt, (double)(in->vert_coords[i][0]), (double)(in->vert_coords[i][1]));
- }
- cdt->outer_face = add_cdtface(cdt);
- return cdt;
-}
-
-static void new_cdt_free(CDT_state *cdt)
-{
- BLI_mempool_destroy(cdt->listpool);
- BLI_memarena_free(cdt->arena);
-}
-
-typedef struct SiteInfo {
- CDTVert *v;
- int orig_index;
-} SiteInfo;
-
-static int site_lexicographic_cmp(const void *a, const void *b)
-{
- const SiteInfo *s1 = a;
- const SiteInfo *s2 = b;
- const double *co1 = s1->v->co;
- const double *co2 = s2->v->co;
-
- if (co1[0] < co2[0]) {
- return -1;
- }
- if (co1[0] > co2[0]) {
- return 1;
- }
- if (co1[1] < co2[1]) {
- return -1;
- }
- if (co1[1] > co2[1]) {
- return 1;
- }
- if (s1->orig_index < s2->orig_index) {
- return -1;
- }
- if (s1->orig_index > s2->orig_index) {
- return 1;
- }
- return 0;
-}
-
-BLI_INLINE bool vert_left_of_symedge(CDTVert *v, SymEdge *se)
-{
- return orient2d(v->co, se->vert->co, se->next->vert->co) > 0.0;
-}
-
-BLI_INLINE bool vert_right_of_symedge(CDTVert *v, SymEdge *se)
-{
- return orient2d(v->co, se->next->vert->co, se->vert->co) > 0.0;
-}
-
-/* Is se above basel? */
-BLI_INLINE bool dc_tri_valid(SymEdge *se, SymEdge *basel, SymEdge *basel_sym)
-{
- return orient2d(se->next->vert->co, basel_sym->vert->co, basel->vert->co) > 0.0;
-}
-
-/* Delaunay triangulate sites[start} to sites[end-1].
- * Assume sites are lexicographically sorted by coordinate.
- * Return SymEdge of ccw convex hull at left-most point in *r_le
- * and that of right-most point of cw convex null in *r_re.
- */
-static void dc_tri(
- CDT_state *cdt, SiteInfo *sites, int start, int end, SymEdge **r_le, SymEdge **r_re)
-{
- int n = end - start;
- int n2;
- CDTVert *v1, *v2, *v3;
- CDTEdge *ea, *eb, *ebasel;
- SymEdge *ldo, *ldi, *rdi, *rdo, *basel, *basel_sym, *lcand, *rcand, *t;
- double orient;
- bool valid_lcand, valid_rcand;
-#ifdef DEBUG_CDT
- char label_buf[100];
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "DC_TRI start=%d end=%d\n", start, end);
- }
-#endif
-
- BLI_assert(r_le != NULL && r_re != NULL);
- if (n <= 1) {
- *r_le = NULL;
- *r_re = NULL;
- return;
- }
- if (n <= 3) {
- v1 = sites[start].v;
- v2 = sites[start + 1].v;
- ea = add_cdtedge(cdt, v1, v2, cdt->outer_face, cdt->outer_face);
- ea->symedges[0].next = &ea->symedges[1];
- ea->symedges[1].next = &ea->symedges[0];
- ea->symedges[0].rot = &ea->symedges[0];
- ea->symedges[1].rot = &ea->symedges[1];
- if (n == 2) {
- *r_le = &ea->symedges[0];
- *r_re = &ea->symedges[1];
- return;
- }
- v3 = sites[start + 2].v;
- eb = add_vert_to_symedge_edge(cdt, v3, &ea->symedges[1]);
- orient = orient2d(v1->co, v2->co, v3->co);
- if (orient > 0.0) {
- add_diagonal(cdt, &eb->symedges[0], &ea->symedges[0]);
- *r_le = &ea->symedges[0];
- *r_re = &eb->symedges[0];
- }
- else if (orient < 0.0) {
- add_diagonal(cdt, &ea->symedges[0], &eb->symedges[0]);
- *r_le = ea->symedges[0].rot;
- *r_re = eb->symedges[0].rot;
- }
- else {
- /* Collinear points. Just return a line. */
- *r_le = &ea->symedges[0];
- *r_re = &eb->symedges[0];
- }
- return;
- }
- /* Here: n >= 4. Divide and conquer. */
- n2 = n / 2;
- BLI_assert(n2 >= 2 && end - (start + n2) >= 2);
-
- /* Delaunay triangulate two halves, L and R. */
- dc_tri(cdt, sites, start, start + n2, &ldo, &ldi);
- dc_tri(cdt, sites, start + n2, end, &rdi, &rdo);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\nDC_TRI merge step for start=%d, end=%d\n", start, end);
- dump_se(ldo, "ldo");
- dump_se(ldi, "ldi");
- dump_se(rdi, "rdi");
- dump_se(rdo, "rdo");
- if (dbg_level > 1) {
- sprintf(label_buf, "dc_tri(%d,%d)(%d,%d)", start, start + n2, start + n2, end);
- /* dump_cdt(cdt, label_buf); */
- cdt_draw(cdt, label_buf);
- }
- }
-#endif
-
- /* Find lower common tangent of L and R. */
- for (;;) {
- if (vert_left_of_symedge(rdi->vert, ldi)) {
- ldi = ldi->next;
- }
- else if (vert_right_of_symedge(ldi->vert, rdi)) {
- rdi = sym(rdi)->rot; /* Previous edge to rdi with same right face. */
- }
- else {
- break;
- }
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "common lower tangent is between\n");
- dump_se(rdi, "rdi");
- dump_se(ldi, "ldi");
- }
-#endif
- ebasel = connect_separate_parts(cdt, sym(rdi)->next, ldi);
- basel = &ebasel->symedges[0];
- basel_sym = &ebasel->symedges[1];
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- dump_se(basel, "basel");
- cdt_draw(cdt, "after basel made");
- }
-#endif
- if (ldi->vert == ldo->vert) {
- ldo = basel_sym;
- }
- if (rdi->vert == rdo->vert) {
- rdo = basel;
- }
-
- /* Merge loop. */
- for (;;) {
- /* Locate the first point lcand->next->vert encountered by rising bubble,
- * and delete L edges out of basel->next->vert that fail the circle test. */
- lcand = basel_sym->rot;
- rcand = basel_sym->next;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "\ntop of merge loop\n");
- dump_se(lcand, "lcand");
- dump_se(rcand, "rcand");
- dump_se(basel, "basel");
- }
-#endif
- if (dc_tri_valid(lcand, basel, basel_sym)) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "found valid lcand\n");
- dump_se(lcand, " lcand");
- }
-#endif
- while (incircle(basel_sym->vert->co,
- basel->vert->co,
- lcand->next->vert->co,
- lcand->rot->next->vert->co) > 0.0) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "incircle says to remove lcand\n");
- dump_se(lcand, " lcand");
- }
-#endif
- t = lcand->rot;
- delete_edge(cdt, sym(lcand));
- lcand = t;
- }
- }
- /* Symmetrically, locate first R point to be hit and delete R edges. */
- if (dc_tri_valid(rcand, basel, basel_sym)) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "found valid rcand\n");
- dump_se(rcand, " rcand");
- }
-#endif
- while (incircle(basel_sym->vert->co,
- basel->vert->co,
- rcand->next->vert->co,
- sym(rcand)->next->next->vert->co) > 0.0) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "incircle says to remove rcand\n");
- dump_se(lcand, " rcand");
- }
-#endif
- t = sym(rcand)->next;
- delete_edge(cdt, rcand);
- rcand = t;
- }
- }
- /* If both lcand and rcand are invalid, then basel is the common upper tangent. */
- valid_lcand = dc_tri_valid(lcand, basel, basel_sym);
- valid_rcand = dc_tri_valid(rcand, basel, basel_sym);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(
- stderr, "after bubbling up, valid_lcand=%d, valid_rcand=%d\n", valid_lcand, valid_rcand);
- dump_se(lcand, "lcand");
- dump_se(rcand, "rcand");
- }
-#endif
- if (!valid_lcand && !valid_rcand) {
- break;
- }
- /* The next cross edge to be connected is to either lcand->next->vert or rcand->next->vert;
- * if both are valid, choose the appropriate one using the incircle test.
- */
- if (!valid_lcand ||
- (valid_rcand &&
- incircle(lcand->next->vert->co, lcand->vert->co, rcand->vert->co, rcand->next->vert->co) >
- 0.0)) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "connecting rcand\n");
- dump_se(basel_sym, " se1=basel_sym");
- dump_se(rcand->next, " se2=rcand->next");
- }
-#endif
- ebasel = add_diagonal(cdt, rcand->next, basel_sym);
- }
- else {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "connecting lcand\n");
- dump_se(sym(lcand), " se1=sym(lcand)");
- dump_se(basel_sym->next, " se2=basel_sym->next");
- }
-#endif
- ebasel = add_diagonal(cdt, basel_sym->next, sym(lcand));
- }
- basel = &ebasel->symedges[0];
- basel_sym = &ebasel->symedges[1];
- BLI_assert(basel_sym->face == cdt->outer_face);
-#ifdef DEBUG_CDT
- if (dbg_level > 2) {
- cdt_draw(cdt, "after adding new crossedge");
- // dump_cdt(cdt, "after adding new crossedge");
- }
-#endif
- }
- *r_le = ldo;
- *r_re = rdo;
- BLI_assert(sym(ldo)->face == cdt->outer_face && rdo->face == cdt->outer_face);
-}
-
-/* Guibas-Stolfi Divide-and_Conquer algorithm. */
-static void dc_triangulate(CDT_state *cdt, SiteInfo *sites, int nsites)
-{
- int i, j, n;
- SymEdge *le, *re;
-
- /* Compress sites in place to eliminated verts that merge to others. */
- i = 0;
- j = 0;
- while (j < nsites) {
- /* Invariante: sites[0..i-1] have non-merged verts from 0..(j-1) in them. */
- sites[i] = sites[j++];
- if (sites[i].v->merge_to_index < 0) {
- i++;
- }
- }
- n = i;
- if (n == 0) {
- return;
- }
- dc_tri(cdt, sites, 0, n, &le, &re);
-}
-
-/**
- * Do a Delaunay Triangulation of the points in cdt->vert_array.
- * This is only a first step in the Constrained Delaunay triangulation,
- * because it doesn't yet deal with the segment constraints.
- * The algorithm used is the Divide & Conquer algorithm from the
- * Guibas-Stolfi "Primitives for the Manipulation of General Subdivision
- * and the Computation of Voronoi Diagrams" paper.
- * The data structure here is similar to but not exactly the same as
- * the quad-edge structure described in that paper.
- * The incircle and ccw tests are done using Shewchuk's exact
- * primitives (see below), so that this routine is robust.
- *
- * As a preprocessing step, we want to merge all vertices that are
- * within cdt->epsilon of each other. This is accomplished by lexicographically
- * sorting the coordinates first (which is needed anyway for the D&C algorithm).
- * The CDTVerts with merge_to_index not equal to -1 are after this regarded
- * as having been merged into the vertex with the corresponding index.
- */
-static void initial_triangulation(CDT_state *cdt)
-{
- int i, j, n;
- SiteInfo *sites;
- double *ico, *jco;
- double xend, yend, xcur;
- double epsilon = cdt->epsilon;
- double epsilon_squared = cdt->epsilon_squared;
-#ifdef SJF_WAY
- CDTEdge *e;
- CDTVert *va, *vb;
-#endif
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nINITIAL TRIANGULATION\n\n");
- }
-#endif
-
- /* First sort the vertices by lexicographic order of their
- * coordinates, breaking ties by putting earlier original-index
- * vertices first.
- */
- n = cdt->vert_array_len;
- if (n <= 1) {
- return;
- }
- sites = MEM_malloc_arrayN(n, sizeof(SiteInfo), __func__);
- for (i = 0; i < n; i++) {
- sites[i].v = cdt->vert_array[i];
- sites[i].orig_index = i;
- }
- qsort(sites, n, sizeof(SiteInfo), site_lexicographic_cmp);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "after sorting\n");
- for (i = 0; i < n; i++) {
- fprintf(stderr, "%d: orig index: %d, (%f,%f)\n", i, sites[i].orig_index, F2(sites[i].v->co));
- }
- }
-#endif
-
- /* Now de-duplicate according to user-defined epsilon.
- * We will merge a vertex into an earlier-indexed vertex
- * that is within epsilon (Euclidean distance).
- * Merges may cascade. So we may end up merging two things
- * that are farther than epsilon by transitive merging. Oh well.
- * Assume that merges are rare, so use simple searches in the
- * lexicographic ordering - likely we will soon hit y's with
- * the same x that are farther away than epsilon, and then
- * skipping ahead to the next biggest x, are likely to soon
- * find one of those farther away than epsilon.
- */
- for (i = 0; i < n - 1; i++) {
- ico = sites[i].v->co;
- /* Start j at next place that has both x and y coords within epsilon. */
- xend = ico[0] + epsilon;
- yend = ico[1] + epsilon;
- j = i + 1;
- while (j < n) {
- jco = sites[j].v->co;
- if (jco[0] > xend) {
- break; /* No more j's to process. */
- }
- if (jco[1] > yend) {
- /* Get past any string of v's with the same x and too-big y. */
- xcur = jco[0];
- while (++j < n) {
- if (sites[j].v->co[0] > xcur) {
- break;
- }
- }
- BLI_assert(j == n || sites[j].v->co[0] > xcur);
- if (j == n) {
- break;
- }
- jco = sites[j].v->co;
- if (jco[0] > xend || jco[1] > yend) {
- break;
- }
- }
- /* When here, vertex i and j are within epsilon by box test.
- * The Euclidean distance test is stricter, so need to do it too, now.
- */
- BLI_assert(j < n && jco[0] <= xend && jco[1] <= yend);
- if (len_squared_v2v2_db(ico, jco) <= epsilon_squared) {
- sites[j].v->merge_to_index = (sites[i].v->merge_to_index == -1) ?
- sites[i].orig_index :
- sites[i].v->merge_to_index;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr,
- "merged orig vert %d to %d\n",
- sites[j].orig_index,
- sites[j].v->merge_to_index);
- }
-#endif
- }
- j++;
- }
- }
-
- /* Now add non-dup vertices into triangulation in lexicographic order. */
-
- dc_triangulate(cdt, sites, n);
- MEM_freeN(sites);
-}
-
-/**
- * Use #LinkNode linked list as stack of #SymEdges, allocating from `cdt->listpool` .
- */
-typedef LinkNode *Stack;
-
-BLI_INLINE void push(Stack *stack, SymEdge *se, CDT_state *cdt)
-{
- BLI_linklist_prepend_pool(stack, se, cdt->listpool);
-}
-
-BLI_INLINE SymEdge *pop(Stack *stack, CDT_state *cdt)
-{
- return (SymEdge *)BLI_linklist_pop_pool(stack, cdt->listpool);
-}
-
-BLI_INLINE bool is_empty(Stack *stack)
-{
- return *stack == NULL;
-}
-
-/**
- * Re-triangulates, assuring constrained delaunay condition,
- * the pseudo-polygon that cycles from se.
- * "pseudo" because a vertex may be repeated.
- * See Anglada paper, "An Improved incremental algorithm
- * for constructing restricted Delaunay triangulations".
- */
-static void re_delaunay_triangulate(CDT_state *cdt, SymEdge *se)
-{
- SymEdge *ss, *first, *cse;
- CDTVert *a, *b, *c, *v;
- CDTEdge *ebc, *eca;
- int count;
-#ifdef DEBUG_CDT
- SymEdge *last;
- const int dbg_level = 0;
-#endif
-
- if (se->face == cdt->outer_face || sym(se)->face == cdt->outer_face) {
- return;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "retriangulate");
- dump_se_cycle(se, "poly ", 1000);
- }
-#endif
- /* 'se' is a diagonal just added, and it is base of area to retriangulate (face on its left) */
- count = 1;
- for (ss = se->next; ss != se; ss = ss->next) {
- count++;
- }
- if (count <= 3) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "nothing to do\n");
- }
-#endif
- return;
- }
- /* First and last are the SymEdges whose verts are first and last off of base,
- * continuing from 'se'. */
- first = se->next->next;
- /* We want to make a triangle with 'se' as base and some other c as 3rd vertex. */
- a = se->vert;
- b = se->next->vert;
- c = first->vert;
- cse = first;
-#ifdef DEBUG_CDT
- last = prev(se);
- if (dbg_level > 1) {
- dump_se(first, "first");
- dump_se(last, "last");
- dump_v(a, "a");
- dump_v(b, "b");
- dump_v(c, "c");
- }
-#endif
- for (ss = first->next; ss != se; ss = ss->next) {
- v = ss->vert;
- if (incircle(a->co, b->co, c->co, v->co) > 0.0) {
- c = v;
- cse = ss;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- dump_v(c, "new c ");
- }
-#endif
- }
- }
- /* Add diagonals necessary to make abc a triangle. */
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "make triangle abc exist where\n");
- dump_v(a, " a");
- dump_v(b, " b");
- dump_v(c, " c");
- }
-#endif
- ebc = NULL;
- eca = NULL;
- if (!exists_edge(b, c)) {
- ebc = add_diagonal(cdt, se->next, cse);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "added edge ebc\n");
- dump_se(&ebc->symedges[0], " ebc");
- }
-#endif
- }
- if (!exists_edge(c, a)) {
- eca = add_diagonal(cdt, cse, se);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "added edge eca\n");
- dump_se(&eca->symedges[0], " eca");
- }
-#endif
- }
- /* Now recurse. */
- if (ebc) {
- re_delaunay_triangulate(cdt, &ebc->symedges[1]);
- }
- if (eca) {
- re_delaunay_triangulate(cdt, &eca->symedges[1]);
- }
-}
-
-static double tri_orient(const SymEdge *t)
-{
- return orient2d(t->vert->co, t->next->vert->co, t->next->next->vert->co);
-}
-
-/**
- * The CrossData struct gives defines either an endpoint or an intermediate point
- * in the path we will take to insert an edge constraint.
- * Each such point will either be
- * (a) a vertex or
- * (b) a fraction lambda (0 < lambda < 1) along some #SymEdge.]
- *
- * In general, lambda=0 indicates case a and lambda != 0 indicates case be.
- * The 'in' edge gives the destination attachment point of a diagonal from the previous crossing,
- * and the 'out' edge gives the origin attachment point of a diagonal to the next crossing.
- * But in some cases, 'in' and 'out' are undefined or not needed, and will be NULL.
- *
- * For case (a), 'vert' will be the vertex, and lambda will be 0, and 'in' will be the #SymEdge
- * from 'vert' that has as face the one that you go through to get to this vertex. If you go
- * exactly along an edge then we set 'in' to NULL, since it won't be needed. The first crossing
- * will have 'in' = NULL. We set 'out' to the #SymEdge that has the face we go though to get to the
- * next crossing, or, if the next crossing is a case (a), then it is the edge that goes to that
- * next vertex. 'out' wlll be NULL for the last one.
- *
- * For case (b), vert will be NULL at first, and later filled in with the created split vertex,
- * and 'in' will be the #SymEdge that we go through, and lambda will be between 0 and 1,
- * the fraction from in's vert to in->next's vert to put the split vertex.
- * 'out' is not needed in this case, since the attachment point will be the sym of the first
- * half of the split edge.
- */
-typedef struct CrossData {
- double lambda;
- CDTVert *vert;
- SymEdge *in;
- SymEdge *out;
-} CrossData;
-
-static bool get_next_crossing_from_vert(CDT_state *cdt,
- CrossData *cd,
- CrossData *cd_next,
- const CDTVert *v2);
-
-/**
- * As part of finding crossings, we found a case where the next crossing goes through vert v.
- * If it came from a previous vert in cd, then cd_out is the edge that leads from that to v.
- * Else cd_out can be NULL, because it won't be used.
- * Set *cd_next to indicate this. We can set 'in' but not 'out'. We can set the 'out' of the
- * current cd.
- */
-static void fill_crossdata_for_through_vert(CDTVert *v,
- SymEdge *cd_out,
- CrossData *cd,
- CrossData *cd_next)
-{
- SymEdge *se;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-#endif
-
- cd_next->lambda = 0.0;
- cd_next->vert = v;
- cd_next->in = NULL;
- cd_next->out = NULL;
- if (cd->lambda == 0.0) {
- cd->out = cd_out;
- }
- else {
- /* One of the edges in the triangle with edge sym(cd->in) contains v. */
- se = sym(cd->in);
- if (se->vert != v) {
- se = se->next;
- if (se->vert != v) {
- se = se->next;
- }
- }
- BLI_assert(se->vert == v);
- cd_next->in = se;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- dump_cross_data(cd, "cd through vert, cd");
- dump_cross_data(cd_next, "cd_next through vert, cd");
- }
-#endif
-}
-
-/**
- * As part of finding crossings, we found a case where orient tests say that the next crossing
- * is on the #SymEdge t, while intersecting with the ray from \a curco to \a v2.
- * Find the intersection point and fill in the #CrossData for that point.
- * It may turn out that when doing the intersection, we get an answer that says that
- * this case is better handled as through-vertex case instead, so we may do that.
- * In the latter case, we want to avoid a situation where the current crossing is on an edge
- * and the next will be an endpoint of the same edge. When that happens, we "rewrite history"
- * and turn the current crossing into a vert one, and then extend from there.
- *
- * We cannot fill cd_next's 'out' edge yet, in the case that the next one ends up being a vert
- * case. We need to fill in cd's 'out' edge if it was a vert case.
- */
-static void fill_crossdata_for_intersect(CDT_state *cdt,
- const double *curco,
- const CDTVert *v2,
- SymEdge *t,
- CrossData *cd,
- CrossData *cd_next)
-{
- CDTVert *va, *vb, *vc;
- double lambda, mu, len_ab;
- SymEdge *se_vcva, *se_vcvb;
- int isect;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-#endif
-
- va = t->vert;
- vb = t->next->vert;
- vc = t->next->next->vert;
- se_vcvb = sym(t->next);
- se_vcva = t->next->next;
- BLI_assert(se_vcva->vert == vc && se_vcva->next->vert == va);
- BLI_assert(se_vcvb->vert == vc && se_vcvb->next->vert == vb);
- UNUSED_VARS_NDEBUG(vc);
- isect = isect_seg_seg_v2_lambda_mu_db(va->co, vb->co, curco, v2->co, &lambda, &mu);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- double co[2];
- fprintf(stderr, "crossdata for intersect gets lambda=%.17g, mu=%.17g\n", lambda, mu);
- fprintf(stderr,
- "isect=%s\n",
- isect == 2 ? "cross" : (isect == 1 ? "exact" : (isect == 0 ? "none" : "colinear")));
- fprintf(stderr,
- "va=v%d=(%g,%g), vb=v%d=(%g,%g), vc=v%d, curco=(%g,%g), v2=(%g,%g)\n",
- va->index,
- F2(va->co),
- vb->index,
- F2(vb->co),
- vc->index,
- F2(curco),
- F2(v2->co));
- dump_se_short(se_vcva, "vcva=");
- dump_se_short(se_vcvb, " vcvb=");
- interp_v2_v2v2_db(co, va->co, vb->co, lambda);
- fprintf(stderr, "\nco=(%.17g,%.17g)\n", F2(co));
- }
-#endif
- switch (isect) {
- case ISECT_LINE_LINE_CROSS:
- len_ab = len_v2v2_db(va->co, vb->co);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr,
- "len_ab=%g, near a=%g, near b=%g\n",
- len_ab,
- lambda * len_ab,
- (1.0 - lambda) * len_ab);
- }
-#endif
- if (lambda * len_ab <= cdt->epsilon) {
- fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
- }
- else if ((1.0 - lambda) * len_ab <= cdt->epsilon) {
- fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
- }
- else {
- *cd_next = (CrossData){lambda, NULL, t, NULL};
- if (cd->lambda == 0.0) {
- cd->out = se_vcva;
- }
- }
- break;
- case ISECT_LINE_LINE_EXACT:
- if (lambda == 0.0) {
- fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
- }
- else if (lambda == 1.0) {
- fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
- }
- else {
- *cd_next = (CrossData){lambda, NULL, t, NULL};
- if (cd->lambda == 0.0) {
- cd->out = se_vcva;
- }
- }
- break;
- case ISECT_LINE_LINE_NONE:
- /* It should be very near one end or other of segment. */
- if (lambda <= 0.5) {
- fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
- }
- else {
- fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
- }
- break;
- case ISECT_LINE_LINE_COLINEAR:
- if (len_squared_v2v2_db(va->co, v2->co) <= len_squared_v2v2_db(vb->co, v2->co)) {
- fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
- }
- else {
- fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
- }
- break;
- }
-}
-
-/**
- * As part of finding the crossings of a ray to v2, find the next crossing after 'cd', assuming
- * 'cd' represents a crossing that goes through a vertex.
- *
- * We do a rotational scan around cd's vertex, looking for the triangle where the ray from cd->vert
- * to v2 goes between the two arms from cd->vert, or where it goes along one of the edges.
- */
-static bool get_next_crossing_from_vert(CDT_state *cdt,
- CrossData *cd,
- CrossData *cd_next,
- const CDTVert *v2)
-{
- SymEdge *t, *tstart;
- CDTVert *vcur, *va, *vb;
- double orient1, orient2;
- bool ok;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nget_next_crossing_from_vert\n");
- dump_v(cd->vert, " cd->vert");
- }
-#endif
-
- t = tstart = cd->vert->symedge;
- vcur = cd->vert;
- ok = false;
- do {
- /*
- * The ray from vcur to v2 has to go either between two successive
- * edges around vcur or exactly along them. This time through the
- * loop, check to see if the ray goes along vcur-va
- * or between vcur-va and vcur-vb, where va is the end of t
- * and vb is the next vertex (on the next rot edge around vcur, but
- * should also be the next vert of triangle starting with vcur-va.
- */
- if (t->face != cdt->outer_face && tri_orient(t) < 0.0) {
- fprintf(stderr, "BAD TRI orientation %g\n", tri_orient(t)); /* TODO: handle this */
-#ifdef DEBUG_CDT
- dump_se_cycle(t, "top of ccw scan loop", 4);
-#endif
- }
- va = t->next->vert;
- vb = t->next->next->vert;
- orient1 = orient2d(t->vert->co, va->co, v2->co);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "non-final through vert case\n");
- dump_v(va, " va");
- dump_v(vb, " vb");
- fprintf(stderr, "orient1=%g\n", orient1);
- }
-#endif
- if (orient1 == 0.0 && in_line(vcur->co, va->co, v2->co)) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "ray goes through va\n");
- }
-#endif
- fill_crossdata_for_through_vert(va, t, cd, cd_next);
- ok = true;
- break;
- }
- if (t->face != cdt->outer_face) {
- orient2 = orient2d(vcur->co, vb->co, v2->co);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "orient2=%g\n", orient2);
- }
-#endif
- /* Don't handle orient2 == 0.0 case here: next rotation will get it. */
- if (orient1 > 0.0 && orient2 < 0.0) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "segment intersects\n");
- }
-#endif
- t = t->next;
- fill_crossdata_for_intersect(cdt, vcur->co, v2, t, cd, cd_next);
- ok = true;
- break;
- }
- }
- } while ((t = t->rot) != tstart);
-#ifdef DEBUG_CDT
- if (!ok) {
- /* Didn't find the exit! Shouldn't happen. */
- fprintf(stderr, "shouldn't happen: can't find next crossing from vert\n");
- }
-#endif
- return ok;
-}
-
-/**
- * As part of finding the crossings of a ray to 'v2', find the next crossing after 'cd', assuming
- * 'cd' represents a crossing that goes through a an edge, not at either end of that edge.
- *
- * We have the triangle 'vb-va-vc', where va and vb are the split edge and 'vc' is the third vertex
- * on that new side of the edge (should be closer to v2). The next crossing should be through 'vc'
- * or intersecting 'vb-vc' or 'va-vc'.
- */
-static void get_next_crossing_from_edge(CDT_state *cdt,
- CrossData *cd,
- CrossData *cd_next,
- const CDTVert *v2)
-{
- double curco[2];
- double orient;
- CDTVert *va, *vb, *vc;
- SymEdge *se_ac;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nget_next_crossing_from_edge\n");
- fprintf(stderr, " lambda=%.17g\n", cd->lambda);
- dump_se_short(cd->in, " cd->in");
- }
-#endif
-
- va = cd->in->vert;
- vb = cd->in->next->vert;
- interp_v2_v2v2_db(curco, va->co, vb->co, cd->lambda);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, " curco=(%.17g,%.17g)\n", F2(curco));
- }
-#endif
- se_ac = sym(cd->in)->next;
- vc = se_ac->next->vert;
- orient = orient2d(curco, v2->co, vc->co);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "now searching with third vertex ");
- dump_v(vc, "vc");
- fprintf(stderr, "orient2d(cur, v2, vc) = %g\n", orient);
- }
-#endif
- if (orient < 0.0) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "curco--v2 intersects vb--vc\n");
- }
-#endif
- fill_crossdata_for_intersect(cdt, curco, v2, se_ac->next, cd, cd_next);
- }
- else if (orient > 0.0) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "curco--v2 intersects va--vc\n");
- }
-#endif
- fill_crossdata_for_intersect(cdt, curco, v2, se_ac, cd, cd_next);
- }
- else {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "orient==0 case, so going through or to vc\n");
- }
-#endif
- *cd_next = (CrossData){0.0, vc, se_ac->next, NULL};
- }
-}
-
-/**
- * Add a constrained edge between v1 and v2 to cdt structure.
- * This may result in a number of #CDTEdges created, due to intersections
- * and partial overlaps with existing cdt vertices and edges.
- * Each created #CDTEdge will have input_id added to its input_ids list.
- *
- * If \a r_edges is not NULL, the #CDTEdges generated or found that go from
- * v1 to v2 are put into that linked list, in order.
- *
- * Assumes that #BLI_constrained_delaunay_get_output has not been called yet.
- */
-static void add_edge_constraint(
- CDT_state *cdt, CDTVert *v1, CDTVert *v2, int input_id, LinkNode **r_edges)
-{
- SymEdge *t, *se, *tstart, *tnext;
- int i, j, n, visit;
- bool ok;
- CrossData *crossings = NULL;
- CrossData *cd, *cd_prev, *cd_next;
- CDTVert *v;
- CDTEdge *edge;
- LinkNodePair edge_list = {NULL, NULL};
- BLI_array_staticdeclare(crossings, 128);
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nADD_EDGE_CONSTRAINT\n");
- dump_v(v1, " 1");
- dump_v(v2, " 2");
- }
-#endif
-
- if (r_edges) {
- *r_edges = NULL;
- }
-
- /*
- * Handle two special cases first:
- * 1) The two end vertices are the same (can happen because of merging).
- * 2) There is already an edge between v1 and v2.
- */
- if (v1 == v2) {
- return;
- }
- if ((t = find_symedge_between_verts(v1, v2)) != NULL) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "segment already there\n");
- }
-#endif
- add_to_input_ids(&t->edge->input_ids, input_id, cdt);
- if (r_edges != NULL) {
- BLI_linklist_append_pool(&edge_list, t->edge, cdt->listpool);
- *r_edges = edge_list.list;
- }
- return;
- }
-
- /*
- * Fill crossings array with CrossData points for intersection path from v1 to v2.
- *
- * At every point, the crossings array has the path so far, except that
- * the .out field of the last element of it may not be known yet -- if that
- * last element is a vertex, then we won't know the output edge until we
- * find the next crossing.
- *
- * To protect against infinite loops, we keep track of which vertices
- * we have visited by setting their visit_index to a new visit epoch.
- *
- * We check a special case first: where the segment is already there in
- * one hop. Saves a bunch of orient2d tests in that common case.
- */
- visit = ++cdt->visit_count;
- BLI_array_grow_one(crossings);
- crossings[0] = (CrossData){0.0, v1, NULL, NULL};
- while (!((n = BLI_array_len(crossings)) > 0 && crossings[n - 1].vert == v2)) {
- BLI_array_grow_one(crossings);
- cd = &crossings[n - 1];
- cd_next = &crossings[n];
- if (crossings[n - 1].lambda == 0.0) {
- ok = get_next_crossing_from_vert(cdt, cd, cd_next, v2);
- }
- else {
- get_next_crossing_from_edge(cdt, cd, cd_next, v2);
- }
- if (!ok || BLI_array_len(crossings) == 100000) {
- /* Shouldn't happen but if does, just bail out. */
-#ifdef DEBUG_CDT
- fprintf(stderr, "FAILURE adding segment, bailing out\n");
-#endif
- BLI_array_free(crossings);
- return;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "crossings[%d]: ", n);
- dump_cross_data(&crossings[n], "");
- }
-#endif
- if (crossings[n].lambda == 0.0) {
- if (crossings[n].vert->visit_index == visit) {
- fprintf(stderr, "WHOOPS, REVISIT. Bailing out.\n"); /*TODO: desperation search here. */
- BLI_array_free(crossings);
- return;
- }
- crossings[n].vert->visit_index = visit;
- }
- }
-
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\ncrossings found\n");
- for (i = 0; i < BLI_array_len(crossings); i++) {
- fprintf(stderr, "%d: ", i);
- dump_cross_data(&crossings[i], "");
- if (crossings[i].lambda == 0.0) {
- if (i == 0 || crossings[i - 1].lambda == 0.0) {
- BLI_assert(crossings[i].in == NULL);
- }
- else {
- BLI_assert(crossings[i].in != NULL && crossings[i].in->vert == crossings[i].vert);
- BLI_assert(crossings[i].in->face == sym(crossings[i - 1].in)->face);
- }
- if (i == BLI_array_len(crossings) - 1) {
- BLI_assert(crossings[i].vert == v2);
- BLI_assert(crossings[i].out == NULL);
- }
- else {
- BLI_assert(crossings[i].out->vert == crossings[i].vert);
- if (crossings[i + 1].lambda == 0.0) {
- BLI_assert(crossings[i].out->next->vert == crossings[i + 1].vert);
- }
- else {
- BLI_assert(crossings[i].out->face == crossings[i + 1].in->face);
- }
- }
- }
- else {
- if (i > 0 && crossings[i - 1].lambda == 0.0) {
- BLI_assert(crossings[i].in->face == crossings[i - 1].out->face);
- }
- BLI_assert(crossings[i].out == NULL);
- }
- }
- }
-#endif
-
- /*
- * Postprocess crossings.
- * Some crossings may have an intersection crossing followed
- * by a vertex crossing that is on the same edge that was just
- * intersected. We prefer to go directly from the previous
- * crossing directly to the vertex. This may chain backwards.
- *
- * This loop marks certain crossings as "deleted", by setting
- * their lambdas to -1.0.
- */
- for (i = 2; i < BLI_array_len(crossings); i++) {
- cd = &crossings[i];
- if (cd->lambda == 0.0) {
- v = cd->vert;
- for (j = i - 1; j > 0; j--) {
- cd_prev = &crossings[j];
- if ((cd_prev->lambda == 0.0 && cd_prev->vert != v) ||
- (cd_prev->lambda != 0.0 && cd_prev->in->vert != v && cd_prev->in->next->vert != v)) {
- break;
- }
- cd_prev->lambda = -1.0; /* Mark cd_prev as 'deleted'. */
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "deleted crossing %d\n", j);
- }
-#endif
- }
- if (j < i - 1) {
- /* Some crossings were deleted. Fix the in and out edges across gap. */
- cd_prev = &crossings[j];
- if (cd_prev->lambda == 0.0) {
- se = find_symedge_between_verts(cd_prev->vert, v);
- if (se == NULL) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "FAILURE(a) in delete crossings, bailing out.\n");
-#endif
- BLI_array_free(crossings);
- return;
- }
- cd_prev->out = se;
- cd->in = NULL;
- }
- else {
- se = find_symedge_with_face(v, sym(cd_prev->in)->face);
- if (se == NULL) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "FAILURE(b) in delete crossings, bailing out.\n");
-#endif
- BLI_array_free(crossings);
- return;
- }
- cd->in = se;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "after deleting crossings:\n");
- fprintf(stderr, "cross[%d]: ", j);
- dump_cross_data(cd_prev, "");
- fprintf(stderr, "cross[%d]: ", i);
- dump_cross_data(cd, "");
- }
-#endif
- }
- }
- }
-
- /*
- * Insert all intersection points on constrained edges.
- */
- for (i = 0; i < BLI_array_len(crossings); i++) {
- cd = &crossings[i];
- if (cd->lambda != 0.0 && cd->lambda != -1.0 && is_constrained_edge(cd->in->edge)) {
- edge = split_edge(cdt, cd->in, cd->lambda);
- cd->vert = edge->symedges[0].vert;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "insert vert for crossing %d: ", i);
- dump_v(cd->vert, "inserted");
- }
-#endif
- }
- }
-
- /*
- * Remove any crossed, non-intersected edges.
- */
- for (i = 0; i < BLI_array_len(crossings); i++) {
- cd = &crossings[i];
- if (cd->lambda != 0.0 && cd->lambda != -1.0 && !is_constrained_edge(cd->in->edge)) {
- delete_edge(cdt, cd->in);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "delete edge for crossing %d\n", i);
- }
-#endif
- }
- }
-
- /*
- * Insert segments for v1->v2.
- */
- tstart = crossings[0].out;
- for (i = 1; i < BLI_array_len(crossings); i++) {
- cd = &crossings[i];
- if (cd->lambda == -1.0) {
- continue; /* This crossing was deleted. */
- }
- t = tnext = NULL;
- if (cd->lambda != 0.0) {
- if (is_constrained_edge(cd->in->edge)) {
- t = cd->vert->symedge;
- tnext = sym(t)->next;
- }
- }
- else if (cd->lambda == 0.0) {
- t = cd->in;
- tnext = cd->out;
- if (t == NULL) {
- /* Previous non-deleted crossing must also have been a vert, and segment should exist. */
- for (j = i - 1; j >= 0; j--) {
- cd_prev = &crossings[j];
- if (cd_prev->lambda != -1.0) {
- break;
- }
- }
- BLI_assert(cd_prev->lambda == 0.0);
- BLI_assert(cd_prev->out->next->vert == cd->vert);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "edge to crossing %d already there\n", i);
- }
-#endif
- edge = cd_prev->out->edge;
- add_to_input_ids(&edge->input_ids, input_id, cdt);
- }
- }
- if (t != NULL) {
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "edge to crossing %d: insert diagonal between\n", i);
- dump_se(tstart, " ");
- dump_se(t, " ");
- dump_se_cycle(tstart, "tstart", 100);
- dump_se_cycle(t, "t", 100);
- }
-#endif
- if (tstart->next->vert == t->vert) {
- edge = tstart->edge;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "already there (b)\n");
- }
-#endif
- }
- else {
- edge = add_diagonal(cdt, tstart, t);
- }
- add_to_input_ids(&edge->input_ids, input_id, cdt);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "added\n");
- }
-#endif
- if (r_edges != NULL) {
- BLI_linklist_append_pool(&edge_list, edge, cdt->listpool);
- }
- /* Now retriangulate upper and lower gaps. */
- re_delaunay_triangulate(cdt, &edge->symedges[0]);
- re_delaunay_triangulate(cdt, &edge->symedges[1]);
- }
- if (i < BLI_array_len(crossings) - 1) {
- if (tnext != NULL) {
- tstart = tnext;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- fprintf(stderr, "now tstart = ");
- dump_se(tstart, "");
- }
-#endif
- }
- }
- }
-
- if (r_edges) {
- *r_edges = edge_list.list;
- }
- BLI_array_free(crossings);
-}
-
-/**
- * Add face_id to the input_ids lists of all #CDTFace's on the interior of the input face with that
- * id. face_symedge is on edge of the boundary of the input face, with assumption that interior is
- * on the left of that SymEdge.
- *
- * The algorithm is: starting from the #CDTFace for face_symedge, add the face_id and then
- * process all adjacent faces where the adjacency isn't across an edge that was a constraint added
- * for the boundary of the input face.
- * fedge_start..fedge_end is the inclusive range of edge input ids that are for the given face.
- *
- * Note: if the input face is not CCW oriented, we'll be labeling the outside, not the inside.
- * Note 2: if the boundary has self-crossings, this method will arbitrarily pick one of the
- * contiguous set of faces enclosed by parts of the boundary, leaving the other such untagged. This
- * may be a feature instead of a bug if the first contiguous section is most of the face and the
- * others are tiny self-crossing triangles at some parts of the boundary. On the other hand, if
- * decide we want to handle these in full generality, then will need a more complicated algorithm
- * (using "inside" tests and a parity rule) to decide on the interior.
- */
-static void add_face_ids(
- CDT_state *cdt, SymEdge *face_symedge, int face_id, int fedge_start, int fedge_end)
-{
- Stack stack;
- SymEdge *se, *se_start, *se_sym;
- CDTFace *face, *face_other;
- int visit;
-
- /* Can't loop forever since eventually would visit every face. */
- cdt->visit_count++;
- visit = cdt->visit_count;
- stack = NULL;
- push(&stack, face_symedge, cdt);
- while (!is_empty(&stack)) {
- se = pop(&stack, cdt);
- face = se->face;
- if (face->visit_index == visit) {
- continue;
- }
- face->visit_index = visit;
- add_to_input_ids(&face->input_ids, face_id, cdt);
- se_start = se;
- for (se = se->next; se != se_start; se = se->next) {
- if (!id_range_in_list(se->edge->input_ids, fedge_start, fedge_end)) {
- se_sym = sym(se);
- face_other = se_sym->face;
- if (face_other->visit_index != visit) {
- push(&stack, se_sym, cdt);
- }
- }
- }
- }
-}
-
-/* Delete_edge but try not to mess up outer face.
- * Also faces have symedges now, so make sure not
- * to mess those up either. */
-static void dissolve_symedge(CDT_state *cdt, SymEdge *se)
-{
- SymEdge *symse = sym(se);
- if (symse->face == cdt->outer_face) {
- se = sym(se);
- symse = sym(se);
- }
- if (cdt->outer_face->symedge == se || cdt->outer_face->symedge == symse) {
- /* Advancing by 2 to get past possible 'sym(se)'. */
- if (se->next->next == se) {
- cdt->outer_face->symedge = NULL;
- }
- else {
- cdt->outer_face->symedge = se->next->next;
- }
- }
- else {
- if (se->face->symedge == se) {
- se->face->symedge = se->next;
- }
- if (symse->face->symedge == symse) {
- symse->face->symedge = symse->next;
- }
- }
- delete_edge(cdt, se);
-}
-
-/* Slow way to get face and start vertex index within face for edge id e. */
-static bool get_face_edge_id_indices(const CDT_input *in, int e, int *r_f, int *r_fi)
-{
- int f;
- int id;
-
- id = in->edges_len;
- if (e < id) {
- return false;
- }
- for (f = 0; f < in->faces_len; f++) {
- if (e < id + in->faces_len_table[f]) {
- *r_f = f;
- *r_fi = e - id;
- return true;
- }
- id += in->faces_len_table[f];
- }
- return false;
-}
-
-/* Is pt_co when snapped to segment seg1 seg2 all of:
- * a) strictly within that segment
- * b) within epsilon from original pt_co
- * c) pt_co is not within epsilon of either seg1 or seg2.
- * Return true if so, and return in *r_lambda the fraction of the way from seg1 to seg2 of the
- * snapped point.
- */
-static bool check_vert_near_segment(const double *pt_co,
- const double *seg1,
- const double *seg2,
- double epsilon_squared,
- double *r_lambda)
-{
- double lambda, snap_co[2];
-
- lambda = closest_to_line_v2_db(snap_co, pt_co, seg1, seg2);
- *r_lambda = lambda;
- if (lambda <= 0.0 || lambda >= 1.0) {
- return false;
- }
- if (len_squared_v2v2_db(pt_co, snap_co) > epsilon_squared) {
- return false;
- }
- if (len_squared_v2v2_db(pt_co, seg1) <= epsilon_squared ||
- len_squared_v2v2_db(pt_co, seg2) <= epsilon_squared) {
- return false;
- }
- return true;
-}
-
-typedef struct EdgeVertLambda {
- int e_id;
- int v_id;
- double lambda;
-} EdgeVertLambda;
-
-/* For sorting first by edge id, then by lambda, then by vert id. */
-static int evl_cmp(const void *a, const void *b)
-{
- const EdgeVertLambda *area = a;
- const EdgeVertLambda *sb = b;
-
- if (area->e_id < sb->e_id) {
- return -1;
- }
- if (area->e_id > sb->e_id) {
- return 1;
- }
- if (area->lambda < sb->lambda) {
- return -1;
- }
- if (area->lambda > sb->lambda) {
- return 1;
- }
- if (area->v_id < sb->v_id) {
- return -1;
- }
- if (area->v_id > sb->v_id) {
- return 1;
- }
- return 0;
-}
-
-/**
- * If epsilon > 0, and input doesn't have skip_modify_input == true,
- * check input to see if any constraint edge ends (including face edges) come
- * within epsilon of another edge.
- * For all such cases, we want to split the constraint edge at the point nearest to near vertex
- * and move the vertex coordinates to be on that edge.
- * But exclude cases where they come within epsilon of either end because those will be handled
- * by vertex merging in the main triangulation algorithm.
- *
- * If any such splits are found, make a new CDT_input reflecting this change, and provide an
- * edge map to map from edge ids in the new input space to edge ids in the old input space.
- *
- * TODO: replace naive O(n^2) algorithm with kdopbvh-based one.
- */
-static const CDT_input *modify_input_for_near_edge_ends(const CDT_input *input, int **r_edge_map)
-{
- CDT_input *new_input = NULL;
- int e, eprev, e1, e2, f, fi, flen, start, i, j;
- int i_new, i_old, i_evl;
- int v11, v12, v21, v22;
- double co11[2], co12[2], co21[2], co22[2];
- double lambda;
- double eps = (double)input->epsilon;
- double eps_sq = eps * eps;
- int tot_edge_constraints, edges_len, tot_face_edges;
- int new_tot_face_edges, new_tot_con_edges;
- int delta_con_edges, delta_face_edges, cur_e_cnt;
- int *edge_map;
- int evl_len;
- EdgeVertLambda *edge_vert_lambda = NULL;
- BLI_array_staticdeclare(edge_vert_lambda, 128);
-#ifdef DEBUG_CDT
- EdgeVertLambda *evl;
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nMODIFY INPUT\n\n");
- }
-#endif
-
- *r_edge_map = NULL;
- if (input->epsilon == 0.0 || input->skip_input_modify ||
- (input->edges_len == 0 && input->faces_len == 0)) {
- return input;
- }
-
- /* Edge constraints are union of the explicitly provided edges and the implicit edges
- * that are part of the provided faces. We index constraints by have the first input->edges_len
- * ints standing for the explicit edge with the same index, and the rest being face edges in
- * the order that the faces appear and then edges within those faces, with indices offset by
- * input->edges_len.
- * Calculate tot_edge_constraints to be the sum of the two kinds of edges.
- * We first have to count the number of face edges.
- * That is the same as the number of vertices in the faces table, which
- * we can find by adding the last length to the last start.
- */
- edges_len = input->edges_len;
- tot_edge_constraints = edges_len;
- if (input->faces_len > 0) {
- tot_face_edges = input->faces_start_table[input->faces_len - 1] +
- input->faces_len_table[input->faces_len - 1];
- }
- else {
- tot_face_edges = 0;
- }
- tot_edge_constraints = edges_len + tot_face_edges;
-
- for (e1 = 0; e1 < tot_edge_constraints - 1; e1++) {
- if (e1 < edges_len) {
- v11 = input->edges[e1][0];
- v12 = input->edges[e1][1];
- }
- else {
- if (!get_face_edge_id_indices(input, e1, &f, &fi)) {
- /* Must be bad input. Will be caught later so don't need to signal here. */
- continue;
- }
- start = input->faces_start_table[f];
- flen = input->faces_len_table[f];
- v11 = input->faces[start + fi];
- v12 = input->faces[(fi == flen - 1) ? start : start + fi + 1];
- }
- for (e2 = e1 + 1; e2 < tot_edge_constraints; e2++) {
- if (e2 < edges_len) {
- v21 = input->edges[e2][0];
- v22 = input->edges[e2][1];
- }
- else {
- if (!get_face_edge_id_indices(input, e2, &f, &fi)) {
- continue;
- }
- start = input->faces_start_table[f];
- flen = input->faces_len_table[f];
- v21 = input->faces[start + fi];
- v22 = input->faces[(fi == flen - 1) ? start : start + fi + 1];
- }
- copy_v2db_v2fl(co11, input->vert_coords[v11]);
- copy_v2db_v2fl(co12, input->vert_coords[v12]);
- copy_v2db_v2fl(co21, input->vert_coords[v21]);
- copy_v2db_v2fl(co22, input->vert_coords[v22]);
- if (check_vert_near_segment(co11, co21, co22, eps_sq, &lambda)) {
-
- BLI_array_append(edge_vert_lambda, ((EdgeVertLambda){e2, v11, lambda}));
- }
- if (check_vert_near_segment(co12, co21, co22, eps_sq, &lambda)) {
- BLI_array_append(edge_vert_lambda, ((EdgeVertLambda){e2, v12, lambda}));
- }
- if (check_vert_near_segment(co21, co11, co12, eps_sq, &lambda)) {
- BLI_array_append(edge_vert_lambda, ((EdgeVertLambda){e1, v21, lambda}));
- }
- if (check_vert_near_segment(co22, co11, co12, eps_sq, &lambda)) {
- BLI_array_append(edge_vert_lambda, ((EdgeVertLambda){e1, v22, lambda}));
- }
- }
- }
-
- evl_len = BLI_array_len(edge_vert_lambda);
- if (evl_len > 0) {
- /* Sort to bring splits for each edge together,
- * and for each edge, to be in order of lambda. */
- qsort(edge_vert_lambda, evl_len, sizeof(EdgeVertLambda), evl_cmp);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\nafter sorting\n");
- for (i = 0; i < evl_len; i++) {
- evl = &edge_vert_lambda[i];
- fprintf(stderr, "e%d, v%d, %g\n", evl->e_id, evl->v_id, evl->lambda);
- }
- }
-#endif
-
- /* Remove dups in edge_vert_lambda, where dup means that the edge is the
- * same, and the verts are either the same or will be merged by epsilon-nearness.
- */
- i = 0;
- j = 0;
- /* In loop, copy from position j to position i. */
- for (j = 0; j < evl_len;) {
- int k;
- if (i != j) {
- memmove(&edge_vert_lambda[i], &edge_vert_lambda[j], sizeof(EdgeVertLambda));
- }
- for (k = j + 1; k < evl_len; k++) {
- int vj = edge_vert_lambda[j].v_id;
- int vk = edge_vert_lambda[k].v_id;
- if (vj != vk) {
- if (len_squared_v2v2(input->vert_coords[vj], input->vert_coords[vk]) > (float)eps_sq) {
- break;
- }
- }
- }
- j = k;
- i++;
- }
-
- if (i != evl_len) {
- evl_len = i;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\nduplicates eliminated\n");
- for (i = 0; i < evl_len; i++) {
- evl = &edge_vert_lambda[i];
- fprintf(stderr, "e%d, v%d, %g\n", evl->e_id, evl->v_id, evl->lambda);
- }
- }
-#endif
- }
- /* Find delta in number of constraint edges and face edges.
- * This may be overestimates of true number, due to duplicates. */
- delta_con_edges = 0;
- delta_face_edges = 0;
- cur_e_cnt = 0;
- eprev = -1;
- for (i = 0; i < evl_len; i++) {
- e = edge_vert_lambda[i].e_id;
- if (i > 0 && e > eprev) {
- /* New edge group. Previous group had cur_e_cnt split vertices.
- * That is the delta in the number of edges needed in input since
- * there will be cur_e_cnt + 1 edges replacing one edge.
- */
- if (eprev < edges_len) {
- delta_con_edges += cur_e_cnt;
- }
- else {
- delta_face_edges += cur_e_cnt;
- }
- cur_e_cnt = 1;
- ;
- }
- else {
- cur_e_cnt++;
- }
- eprev = e;
- }
- if (eprev < edges_len) {
- delta_con_edges += cur_e_cnt;
- }
- else {
- delta_face_edges += cur_e_cnt;
- }
- new_tot_con_edges = input->edges_len + delta_con_edges;
- if (input->faces_len > 0) {
- new_tot_face_edges = input->faces_start_table[input->faces_len - 1] +
- input->faces_len_table[input->faces_len - 1] + delta_face_edges;
- }
- else {
- new_tot_face_edges = 0;
- }
-
- /* Allocate new CDT_input, now we know sizes needed (perhaps overestimated a bit).
- * Caller will be responsible for freeing it and its arrays.
- */
- new_input = MEM_callocN(sizeof(CDT_input), __func__);
- new_input->epsilon = input->epsilon;
- new_input->verts_len = input->verts_len;
- new_input->vert_coords = (float(*)[2])MEM_malloc_arrayN(
- new_input->verts_len, sizeof(float[2]), __func__);
- /* We don't do it now, but may decide to change coords of snapped verts. */
- memmove(new_input->vert_coords,
- input->vert_coords,
- sizeof(float[2]) * (size_t)new_input->verts_len);
-
- if (edges_len > 0) {
- new_input->edges_len = new_tot_con_edges;
- new_input->edges = (int(*)[2])MEM_malloc_arrayN(new_tot_con_edges, sizeof(int[2]), __func__);
- }
-
- if (input->faces_len > 0) {
- new_input->faces_len = input->faces_len;
- new_input->faces_start_table = (int *)MEM_malloc_arrayN(
- new_input->faces_len, sizeof(int), __func__);
- new_input->faces_len_table = (int *)MEM_malloc_arrayN(
- new_input->faces_len, sizeof(int), __func__);
- new_input->faces = (int *)MEM_malloc_arrayN(new_tot_face_edges, sizeof(int), __func__);
- }
-
- edge_map = (int *)MEM_malloc_arrayN(
- new_tot_con_edges + new_tot_face_edges, sizeof(int), __func__);
- *r_edge_map = edge_map;
-
- i_new = i_old = i_evl = 0;
- e = edge_vert_lambda[0].e_id;
- /* First do new constraint edges. */
- for (i_old = 0; i_old < edges_len; i_old++) {
- if (i_old < e) {
- /* Edge for i_old not split; copy it into new_input. */
- new_input->edges[i_new][0] = input->edges[i_old][0];
- new_input->edges[i_new][1] = input->edges[i_old][1];
- edge_map[i_new] = i_old;
- i_new++;
- }
- else {
- /* Edge for i_old is split. */
- BLI_assert(i_old == e);
- new_input->edges[i_new][0] = input->edges[i_old][0];
- new_input->edges[i_new][1] = edge_vert_lambda[i_evl].v_id;
- edge_map[i_new] = i_old;
- i_new++;
- i_evl++;
- while (i_evl < evl_len && e == edge_vert_lambda[i_evl].e_id) {
- new_input->edges[i_new][0] = new_input->edges[i_new - 1][1];
- new_input->edges[i_new][1] = edge_vert_lambda[i_evl].v_id;
- edge_map[i_new] = i_old;
- i_new++;
- i_evl++;
- }
- new_input->edges[i_new][0] = new_input->edges[i_new - 1][1];
- new_input->edges[i_new][1] = input->edges[i_old][1];
- edge_map[i_new] = i_old;
- i_new++;
- if (i_evl < evl_len) {
- e = edge_vert_lambda[i_evl].e_id;
- }
- else {
- e = INT_MAX;
- }
- }
- }
- BLI_assert(i_new <= new_tot_con_edges);
- new_input->edges_len = i_new;
-
- /* Now do face constraints. */
- if (input->faces_len > 0) {
- f = 0;
- i_new = 0; /* Now will index cur place in new_input->faces. */
- while (i_old < tot_edge_constraints) {
- flen = input->faces_len_table[f];
- BLI_assert(i_old - edges_len == input->faces_start_table[f]);
- new_input->faces_start_table[f] = i_new;
- if (i_old + flen - 1 < e) {
- /* Face f is not split. */
- for (j = 0; j < flen; j++) {
- new_input->faces[i_new] = input->faces[i_old - edges_len + j];
- edge_map[i_new + new_input->edges_len] = i_old + j;
- i_new++;
- }
- i_old += flen;
- new_input->faces_len_table[f] = flen;
- f++;
- }
- else {
- /* Face f has at least one split edge. */
- int i_new_start = i_new;
- for (j = 0; j < flen; j++) {
- if (i_old + j < e) {
- /* jth edge of f is not split. */
- new_input->faces[i_new] = input->faces[i_old - edges_len + j];
- edge_map[i_new + new_input->edges_len] = i_old + j;
- i_new++;
- }
- else {
- /* jth edge of f is split. */
- BLI_assert(i_old + j == e);
- new_input->faces[i_new] = input->faces[i_old - edges_len + j];
- edge_map[i_new + new_input->edges_len] = i_old + j;
- i_new++;
- while (i_evl < evl_len && e == edge_vert_lambda[i_evl].e_id) {
- new_input->faces[i_new] = edge_vert_lambda[i_evl].v_id;
- edge_map[i_new + new_input->edges_len] = i_old + j;
- i_new++;
- i_evl++;
- }
- if (i_evl < evl_len) {
- e = edge_vert_lambda[i_evl].e_id;
- }
- else {
- e = INT_MAX;
- }
- }
- }
- new_input->faces_len_table[f] = i_new - i_new_start;
- i_old += flen;
- f++;
- }
- }
- }
-
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\nnew constraint edges\n");
- for (i = 0; i < new_input->edges_len; i++) {
- fprintf(stderr, " e%d: (%d,%d)\n", i, new_input->edges[i][0], new_input->edges[i][1]);
- }
- fprintf(stderr, "\nnew faces\n");
- for (f = 0; f < new_input->faces_len; f++) {
- flen = new_input->faces_len_table[f];
- start = new_input->faces_start_table[f];
- fprintf(stderr, " f%d: start=%d, len=%d\n ", f, start, flen);
- for (i = start; i < start + flen; i++) {
- fprintf(stderr, "%d ", new_input->faces[i]);
- }
- fprintf(stderr, "\n");
- }
- fprintf(stderr, "\nedge map (new->old)\n");
- for (i = 0; i < new_tot_con_edges + new_tot_face_edges; i++) {
- fprintf(stderr, " %d->%d\n", i, edge_map[i]);
- }
- }
-#endif
- }
-
- BLI_array_free(edge_vert_lambda);
- if (new_input != NULL) {
- return (const CDT_input *)new_input;
- }
- return input;
-}
-
-static void free_modified_input(CDT_input *input)
-{
- MEM_freeN(input->vert_coords);
- if (input->edges != NULL) {
- MEM_freeN(input->edges);
- }
- if (input->faces != NULL) {
- MEM_freeN(input->faces);
- MEM_freeN(input->faces_len_table);
- MEM_freeN(input->faces_start_table);
- }
- MEM_freeN(input);
-}
-
-/* Return true if we can merge se's vert into se->next's vert
- * without making the area of any new triangle formed by doing
- * that into a zero or negative area triangle.*/
-static bool can_collapse(const SymEdge *se)
-{
- SymEdge *loop_se;
- const double *co = se->next->vert->co;
-
- for (loop_se = se->rot; loop_se != se && loop_se->rot != se; loop_se = loop_se->rot) {
- if (orient2d(co, loop_se->next->vert->co, loop_se->rot->next->vert->co) <= 0.0) {
- return false;
- }
- }
- return true;
-}
-
-/*
- * Merge one end of e onto the other, fixing up surrounding faces.
- *
- * General situation looks something like:
- *
- * c-----e
- * / \ / \
- * / \ / \
- * a------b-----f
- * \ / \ /
- * \ / \ /
- * d-----g
- *
- * where ab is the tiny edge. We want to merge a and b and delete edge ab.
- * We don't want to change the coordinates of input vertices [We could revisit this
- * in the future, as API def doesn't prohibit this, but callers will appreciate if they
- * don't change.]
- * Sometimes the collapse shouldn't happen because the triangles formed by the changed
- * edges may end up with zero or negative area (see can_collapse, above).
- * So don't choose a collapse direction that is not allowed or one that has an original vertex
- * as origin and a non-original vertex as destination.
- * If both collapse directions are allowed by that rule, pick the one with the lower original
- * index.
- *
- * After merging, the faces abc and adb disappear (if they are not the outer face).
- * Suppose we merge b onto a.
- * Then edges cb and db are deleted. Face cbe becomes cae and face bdg becomes adg.
- * Any other faces attached to b now have a in their place.
- * We can do this by rotating edges round b, replacing their vert references with a.
- * Similar statements can be made about what happens when a merges into b;
- * in code below we'll swap a and b to make above lettering work for a b->a merge.
- * Return the vert at the collapsed edge, if a collapse happens.
- */
-static CDTVert *collapse_tiny_edge(CDT_state *cdt, CDTEdge *e)
-{
- CDTVert *va, *vb;
- SymEdge *ab_se, *ba_se, *bd_se, *bc_se, *ad_se, *ac_se;
- SymEdge *bg_se, *be_se, *se, *gb_se, *ca_se;
- bool can_collapse_a_to_b, can_collapse_b_to_a;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-#endif
-
- ab_se = &e->symedges[0];
- ba_se = &e->symedges[1];
- va = ab_se->vert;
- vb = ba_se->vert;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "\ncollapse_tiny_edge\n");
- dump_se(&e->symedges[0], "tiny edge");
- fprintf(stderr, "a = [%d], b = [%d]\n", va->index, vb->index);
- validate_cdt(cdt, true, false, true);
- }
-#endif
- can_collapse_a_to_b = can_collapse(ab_se);
- can_collapse_b_to_a = can_collapse(ba_se);
- /* Now swap a and b if necessary and possible, so that from this point on we are collapsing b to
- * a. */
- if (va->index > vb->index || !can_collapse_b_to_a) {
- if (can_collapse_a_to_b && !(is_original_vert(va, cdt) && !is_original_vert(vb, cdt))) {
- SWAP(CDTVert *, va, vb);
- ab_se = &e->symedges[1];
- ba_se = &e->symedges[0];
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "swapped a and b\n");
- }
-#endif
- }
- else {
- /* Neither collapse direction is OK. */
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "neither collapse direction ok\n");
- }
-#endif
- return NULL;
- }
- }
- bc_se = ab_se->next;
- bd_se = ba_se->rot;
- if (bd_se == ba_se) {
- /* Shouldn't happen. Wire edge in outer face. */
- fprintf(stderr, "unexpected wire edge\n");
- return NULL;
- }
- vb->merge_to_index = va->merge_to_index == -1 ? va->index : va->merge_to_index;
- vb->symedge = NULL;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr,
- "vb = v[%d] merges to va = v[%d], vb->merge_to_index=%d\n",
- vb->index,
- va->index,
- vb->merge_to_index);
- }
-#endif
- /* First fix the vertex of intermediate triangles, like bgf. */
- for (se = bd_se->rot; se != bc_se; se = se->rot) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- dump_se(se, "intermediate tri edge, setting vert to va");
- }
-#endif
- se->vert = va;
- }
- ad_se = sym(sym(bd_se)->rot);
- ca_se = bc_se->next;
- ac_se = sym(ca_se);
- if (bd_se->rot != bc_se) {
- bg_se = bd_se->rot;
- be_se = sym(bc_se)->next;
- gb_se = sym(bg_se);
- }
- else {
- bg_se = NULL;
- be_se = NULL;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "delete bd, inputs to ad\n");
- dump_se(bd_se, " bd");
- dump_se(ad_se, " ad");
- fprintf(stderr, "delete bc, inputs to ac\n");
- dump_se(bc_se, " bc");
- dump_se(ac_se, " ac");
- fprintf(stderr, "delete ab\n");
- dump_se(ab_se, " ab");
- if (bg_se != NULL) {
- fprintf(stderr, "fix up bg, be\n");
- dump_se(bg_se, " bg");
- dump_se(be_se, " be");
- }
- }
-#endif
- add_list_to_input_ids(&ad_se->edge->input_ids, bd_se->edge->input_ids, cdt);
- delete_edge(cdt, bd_se);
- add_list_to_input_ids(&ac_se->edge->input_ids, bc_se->edge->input_ids, cdt);
- delete_edge(cdt, sym(bc_se));
- /* At this point we have this:
- *
- * c-----e
- * / / \
- * / / \
- * a------b-----f
- * \ \ /
- * \ \ /
- * d-----g
- *
- * Or, if there is not bg_se and be_se, like this:
- *
- * c
- * /
- * /
- * a------b
- * \
- * \
- * d
- *
- * (But we've already changed the vert field for bg, bf, ..., be to be va.)
- */
- if (bg_se != NULL) {
- gb_se->next = ad_se;
- ad_se->rot = bg_se;
- ca_se->next = be_se;
- be_se->rot = ac_se;
- bg_se->vert = va;
- be_se->vert = va;
- }
- else {
- ca_se->next = ad_se;
- ad_se->rot = ac_se;
- }
- /* Don't use delete_edge as it changes too much. */
- ab_se->next = ab_se->rot = NULL;
- ba_se->next = ba_se->rot = NULL;
- if (va->symedge == ab_se) {
- va->symedge = ac_se;
- }
- return va;
-}
-
-/*
- * Check to see if e is tiny (length <= epsilon) and queue it if so.
- */
-static void maybe_enqueue_small_feature(CDT_state *cdt, CDTEdge *e, LinkNodePair *tiny_edge_queue)
-{
- SymEdge *se, *sesym;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nmaybe_enqueue_small_features\n");
- dump_se(&e->symedges[0], " se0");
- }
-#endif
-
- if (is_deleted_edge(e) || e->in_queue) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "returning because of e conditions\n");
- }
-#endif
- return;
- }
- se = &e->symedges[0];
- sesym = &e->symedges[1];
- if (len_squared_v2v2_db(se->vert->co, sesym->vert->co) <= cdt->epsilon_squared) {
- BLI_linklist_append_pool(tiny_edge_queue, e, cdt->listpool);
- e->in_queue = true;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "Queue tiny edge\n");
- }
-#endif
- }
-}
-
-/* Consider all edges in rot ring around v for possible enqueing as small features .*/
-static void maybe_enqueue_small_features(CDT_state *cdt, CDTVert *v, LinkNodePair *tiny_edge_queue)
-{
- SymEdge *se, *se_start;
-
- se = se_start = v->symedge;
- if (!se_start) {
- return;
- }
- do {
- maybe_enqueue_small_feature(cdt, se->edge, tiny_edge_queue);
- } while ((se = se->rot) != se_start);
-}
-
-/* Collapse small edges (length <= epsilon) until no more exist.
- */
-static void remove_small_features(CDT_state *cdt)
-{
- double epsilon = cdt->epsilon;
- LinkNodePair tiny_edge_queue = {NULL, NULL};
- BLI_mempool *pool = cdt->listpool;
- LinkNode *ln;
- CDTEdge *e;
- CDTVert *v_change;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nREMOVE_SMALL_FEATURES, epsilon=%g\n", epsilon);
- }
-#endif
-
- if (epsilon == 0.0) {
- return;
- }
-
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- maybe_enqueue_small_feature(cdt, e, &tiny_edge_queue);
- }
-
- while (tiny_edge_queue.list != NULL) {
- e = (CDTEdge *)BLI_linklist_pop_pool(&tiny_edge_queue.list, pool);
- if (tiny_edge_queue.list == NULL) {
- tiny_edge_queue.last_node = NULL;
- }
- e->in_queue = false;
- if (is_deleted_edge(e)) {
- continue;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "collapse tiny edge\n");
- dump_se(&e->symedges[0], "");
- }
-#endif
- v_change = collapse_tiny_edge(cdt, e);
- if (v_change) {
- maybe_enqueue_small_features(cdt, v_change, &tiny_edge_queue);
- }
- }
-}
-
-/* Remove all non-constraint edges. */
-static void remove_non_constraint_edges(CDT_state *cdt)
-{
- LinkNode *ln;
- CDTEdge *e;
- SymEdge *se;
-
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- se = &e->symedges[0];
- if (!is_deleted_edge(e) && !is_constrained_edge(e)) {
- dissolve_symedge(cdt, se);
- }
- }
-}
-
-/*
- * Remove the non-constraint edges, but leave enough of them so that all of the
- * faces that would be bmesh faces (that is, the faces that have some input representative)
- * are valid: they can't have holes, they can't have repeated vertices, and they can't have
- * repeated edges.
- *
- * Not essential, but to make the result look more aesthetically nice,
- * remove the edges in order of decreasing length, so that it is more likely that the
- * final remaining support edges are short, and therefore likely to make a fairly
- * direct path from an outer face to an inner hole face.
- */
-
-/* For sorting edges by decreasing length (squared). */
-struct EdgeToSort {
- double len_squared;
- CDTEdge *e;
-};
-
-static int edge_to_sort_cmp(const void *a, const void *b)
-{
- const struct EdgeToSort *e1 = a;
- const struct EdgeToSort *e2 = b;
-
- if (e1->len_squared > e2->len_squared) {
- return -1;
- }
- if (e1->len_squared < e2->len_squared) {
- return 1;
- }
- return 0;
-}
-
-static void remove_non_constraint_edges_leave_valid_bmesh(CDT_state *cdt)
-{
- LinkNode *ln;
- CDTEdge *e;
- SymEdge *se, *se2;
- CDTFace *fleft, *fright;
- bool dissolve;
- size_t nedges;
- int i, ndissolvable;
- const double *co1, *co2;
- struct EdgeToSort *sorted_edges;
-
- nedges = 0;
- for (ln = cdt->edges; ln; ln = ln->next) {
- nedges++;
- }
- if (nedges == 0) {
- return;
- }
- sorted_edges = BLI_memarena_alloc(cdt->arena, nedges * sizeof(*sorted_edges));
- i = 0;
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- if (!is_deleted_edge(e) && !is_constrained_edge(e)) {
- sorted_edges[i].e = e;
- co1 = e->symedges[0].vert->co;
- co2 = e->symedges[1].vert->co;
- sorted_edges[i].len_squared = len_squared_v2v2_db(co1, co2);
- i++;
- }
- }
- ndissolvable = i;
- qsort(sorted_edges, ndissolvable, sizeof(*sorted_edges), edge_to_sort_cmp);
- for (i = 0; i < ndissolvable; i++) {
- e = sorted_edges[i].e;
- se = &e->symedges[0];
- dissolve = true;
- if (true /*!edge_touches_frame(e)*/) {
- fleft = se->face;
- fright = sym(se)->face;
- if (fleft != cdt->outer_face && fright != cdt->outer_face &&
- (fleft->input_ids != NULL || fright->input_ids != NULL)) {
- /* Is there another symedge with same left and right faces?
- * Or is there a vertex not part of e touching the same left and right faces? */
- for (se2 = se->next; dissolve && se2 != se; se2 = se2->next) {
- if (sym(se2)->face == fright ||
- (se2->vert != se->next->vert && vert_touches_face(se2->vert, fright))) {
- dissolve = false;
- }
- }
- }
- }
- if (dissolve) {
- dissolve_symedge(cdt, se);
- }
- }
-}
-
-static void remove_outer_edges_until_constraints(CDT_state *cdt)
-{
- LinkNode *fstack = NULL;
- SymEdge *se, *se_start;
- CDTFace *f, *fsym;
- int visit = ++cdt->visit_count;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "remove_outer_edges_until_constraints\n");
- }
-#endif
-
- cdt->outer_face->visit_index = visit;
- /* Walk around outer face, adding faces on other side of dissolvable edges to stack. */
- se_start = se = cdt->outer_face->symedge;
- do {
- if (!is_constrained_edge(se->edge)) {
- fsym = sym(se)->face;
- if (fsym->visit_index != visit) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "pushing f=%p from symedge ", fsym);
- dump_se(se, "an outer edge");
- }
-#endif
- BLI_linklist_prepend_pool(&fstack, fsym, cdt->listpool);
- }
- }
- } while ((se = se->next) != se_start);
-
- while (fstack != NULL) {
- LinkNode *to_dissolve = NULL;
- bool dissolvable;
- f = (CDTFace *)BLI_linklist_pop_pool(&fstack, cdt->listpool);
- if (f->visit_index == visit) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "skipping f=%p, already visited\n", f);
- }
-#endif
- continue;
- }
- BLI_assert(f != cdt->outer_face);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "top of loop, f=%p\n", f);
- dump_se_cycle(f->symedge, "visit", 10000);
- if (dbg_level > 1) {
- dump_cdt(cdt, "cdt at top of loop");
- cdt_draw(cdt, "top of dissolve loop");
- }
- }
-#endif
- f->visit_index = visit;
- se_start = se = f->symedge;
- do {
- dissolvable = !is_constrained_edge(se->edge);
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- dump_se(se, "edge in f");
- fprintf(stderr, " dissolvable=%d\n", dissolvable);
- }
-#endif
- if (dissolvable) {
- fsym = sym(se)->face;
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- dump_se_cycle(fsym->symedge, "fsym", 10000);
- fprintf(stderr, " visited=%d\n", fsym->visit_index == visit);
- }
-#endif
- if (fsym->visit_index != visit) {
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "pushing face %p\n", fsym);
- dump_se_cycle(fsym->symedge, "pushed", 10000);
- }
-#endif
- BLI_linklist_prepend_pool(&fstack, fsym, cdt->listpool);
- }
- else {
- BLI_linklist_prepend_pool(&to_dissolve, se, cdt->listpool);
- }
- }
- se = se->next;
- } while (se != se_start);
- while (to_dissolve != NULL) {
- se = (SymEdge *)BLI_linklist_pop_pool(&to_dissolve, cdt->listpool);
- if (se->next != NULL) {
- dissolve_symedge(cdt, se);
- }
- }
- }
-}
-
-/**
- * Remove edges and merge faces to get desired output, as per options.
- * \note the cdt cannot be further changed after this.
- */
-static void prepare_cdt_for_output(CDT_state *cdt, const CDT_output_type output_type)
-{
- CDTFace *f;
- CDTEdge *e;
- LinkNode *ln;
-
- cdt->output_prepared = true;
- if (cdt->edges == NULL) {
- return;
- }
-
- /* Make sure all non-deleted faces have a symedge. */
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- if (!is_deleted_edge(e)) {
- if (e->symedges[0].face->symedge == NULL) {
- e->symedges[0].face->symedge = &e->symedges[0];
- }
- if (e->symedges[1].face->symedge == NULL) {
- e->symedges[1].face->symedge = &e->symedges[1];
- }
- }
- }
-#ifdef DEBUG_CDT
- /* All non-deleted faces should have a symedge now. */
- for (ln = cdt->faces; ln; ln = ln->next) {
- f = (CDTFace *)ln->link;
- if (!f->deleted) {
- BLI_assert(f->symedge != NULL);
- }
- }
-#else
- UNUSED_VARS(f);
-#endif
-
- if (output_type == CDT_CONSTRAINTS) {
- remove_non_constraint_edges(cdt);
- }
- else if (output_type == CDT_CONSTRAINTS_VALID_BMESH) {
- remove_non_constraint_edges_leave_valid_bmesh(cdt);
- }
- else if (output_type == CDT_INSIDE) {
- remove_outer_edges_until_constraints(cdt);
- }
-}
-
-static CDT_result *cdt_get_output(CDT_state *cdt,
- const CDT_input *input,
- const CDT_output_type output_type)
-{
- int i, j, nv, ne, nf, faces_len_total;
- int orig_map_size, orig_map_index;
- int *vert_to_output_map;
- CDT_result *result;
- CDTVert *v;
- LinkNode *lne, *lnf, *ln;
- SymEdge *se, *se_start;
- CDTEdge *e;
- CDTFace *f;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-
- if (dbg_level > 0) {
- fprintf(stderr, "\nCDT_GET_OUTPUT\n\n");
- }
-#endif
-
- prepare_cdt_for_output(cdt, output_type);
-
- result = (CDT_result *)MEM_callocN(sizeof(*result), __func__);
- if (cdt->vert_array_len == 0) {
- return result;
- }
-
-#ifdef DEBUG_CDT
- if (dbg_level > 1) {
- dump_cdt(cdt, "cdt to output");
- }
-#endif
-
- /* All verts without a merge_to_index will be output.
- * vert_to_output_map[i] will hold the output vertex index
- * corresponding to the vert in position i in cdt->vert_array.
- * Since merging picked the leftmost-lowermost representative,
- * that is not necessarily the same as the vertex with the lowest original
- * index (i.e., index in cdt->vert_array), so we need two passes:
- * one to get the non-merged-to vertices in vert_to_output_map,
- * and a second to put in the merge targets for merged-to vertices.
- */
- vert_to_output_map = BLI_memarena_alloc(cdt->arena, (size_t)cdt->vert_array_len * sizeof(int *));
- nv = 0;
- for (i = 0; i < cdt->vert_array_len; i++) {
- v = cdt->vert_array[i];
- if (v->merge_to_index == -1) {
- vert_to_output_map[i] = nv;
- nv++;
- }
- }
- if (nv <= 0) {
- return result;
- }
- if (nv < cdt->vert_array_len) {
- for (i = 0; i < input->verts_len; i++) {
- v = cdt->vert_array[i];
- if (v->merge_to_index != -1) {
- add_to_input_ids(&cdt->vert_array[v->merge_to_index]->input_ids, i, cdt);
- vert_to_output_map[i] = vert_to_output_map[v->merge_to_index];
- }
- }
- }
-
- result->verts_len = nv;
- result->vert_coords = MEM_malloc_arrayN(nv, sizeof(result->vert_coords[0]), __func__);
-
- /* Make the vertex "orig" map arrays, mapping output verts to lists of input ones. */
- orig_map_size = 0;
- for (i = 0; i < cdt->vert_array_len; i++) {
- if (cdt->vert_array[i]->merge_to_index == -1) {
- orig_map_size += 1 + BLI_linklist_count(cdt->vert_array[i]->input_ids);
- }
- }
- result->verts_orig_len_table = MEM_malloc_arrayN(nv, sizeof(int), __func__);
- result->verts_orig_start_table = MEM_malloc_arrayN(nv, sizeof(int), __func__);
- result->verts_orig = MEM_malloc_arrayN(orig_map_size, sizeof(int), __func__);
-
- orig_map_index = 0;
- i = 0;
- for (j = 0; j < cdt->vert_array_len; j++) {
- v = cdt->vert_array[j];
- if (v->merge_to_index == -1) {
- result->vert_coords[i][0] = (float)v->co[0];
- result->vert_coords[i][1] = (float)v->co[1];
- result->verts_orig_start_table[i] = orig_map_index;
- if (j < input->verts_len) {
- result->verts_orig[orig_map_index++] = j;
- }
- for (ln = v->input_ids; ln; ln = ln->next) {
- result->verts_orig[orig_map_index++] = POINTER_AS_INT(ln->link);
- }
- result->verts_orig_len_table[i] = orig_map_index - result->verts_orig_start_table[i];
- i++;
- }
- }
-
- ne = 0;
- orig_map_size = 0;
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- if (!is_deleted_edge(e)) {
- ne++;
- if (e->input_ids) {
- orig_map_size += BLI_linklist_count(e->input_ids);
- }
- }
- }
- if (ne != 0) {
- result->edges_len = ne;
- result->face_edge_offset = cdt->face_edge_offset;
- result->edges = MEM_malloc_arrayN(ne, sizeof(result->edges[0]), __func__);
- result->edges_orig_len_table = MEM_malloc_arrayN(ne, sizeof(int), __func__);
- result->edges_orig_start_table = MEM_malloc_arrayN(ne, sizeof(int), __func__);
- if (orig_map_size > 0) {
- result->edges_orig = MEM_malloc_arrayN(orig_map_size, sizeof(int), __func__);
- }
- orig_map_index = 0;
- i = 0;
- for (lne = cdt->edges; lne; lne = lne->next) {
- e = (CDTEdge *)lne->link;
- if (!is_deleted_edge(e)) {
- result->edges[i][0] = vert_to_output_map[e->symedges[0].vert->index];
- result->edges[i][1] = vert_to_output_map[e->symedges[1].vert->index];
- result->edges_orig_start_table[i] = orig_map_index;
- for (ln = e->input_ids; ln; ln = ln->next) {
- result->edges_orig[orig_map_index++] = POINTER_AS_INT(ln->link);
- }
- result->edges_orig_len_table[i] = orig_map_index - result->edges_orig_start_table[i];
- i++;
- }
- }
- }
-
- nf = 0;
- faces_len_total = 0;
- orig_map_size = 0;
- for (ln = cdt->faces; ln; ln = ln->next) {
- f = (CDTFace *)ln->link;
- if (!f->deleted && f != cdt->outer_face) {
- nf++;
- se = se_start = f->symedge;
- BLI_assert(se != NULL);
- do {
- faces_len_total++;
- se = se->next;
- } while (se != se_start);
- if (f->input_ids) {
- orig_map_size += BLI_linklist_count(f->input_ids);
- }
- }
- }
-
- if (nf != 0) {
- result->faces_len = nf;
- result->faces_len_table = MEM_malloc_arrayN(nf, sizeof(int), __func__);
- result->faces_start_table = MEM_malloc_arrayN(nf, sizeof(int), __func__);
- result->faces = MEM_malloc_arrayN(faces_len_total, sizeof(int), __func__);
- result->faces_orig_len_table = MEM_malloc_arrayN(nf, sizeof(int), __func__);
- result->faces_orig_start_table = MEM_malloc_arrayN(nf, sizeof(int), __func__);
- if (orig_map_size > 0) {
- result->faces_orig = MEM_malloc_arrayN(orig_map_size, sizeof(int), __func__);
- }
- orig_map_index = 0;
- i = 0;
- j = 0;
- for (lnf = cdt->faces; lnf; lnf = lnf->next) {
- f = (CDTFace *)lnf->link;
- if (!f->deleted && f != cdt->outer_face) {
- result->faces_start_table[i] = j;
- se = se_start = f->symedge;
- do {
- result->faces[j++] = vert_to_output_map[se->vert->index];
- se = se->next;
- } while (se != se_start);
- result->faces_len_table[i] = j - result->faces_start_table[i];
- result->faces_orig_start_table[i] = orig_map_index;
- for (ln = f->input_ids; ln; ln = ln->next) {
- result->faces_orig[orig_map_index++] = POINTER_AS_INT(ln->link);
- }
- result->faces_orig_len_table[i] = orig_map_index - result->faces_orig_start_table[i];
- i++;
- }
- }
- }
- return result;
-}
-
-/**
- * Calculate the Constrained Delaunay Triangulation of the 2d elements given in \a input.
- *
- * A Delaunay triangulation of a set of vertices is a triangulation where no triangle in the
- * triangulation has a circumcircle that strictly contains another vertex. Delaunay triangulations
- * are avoid long skinny triangles: they maximize the minimum angle of all triangles in the
- * triangulation.
- *
- * A Constrained Delaunay Triangulation adds the requirement that user-provided line segments must
- * appear as edges in the output (perhaps divided into several sub-segments). It is not required
- * that the input edges be non-intersecting: this routine will calculate the intersections. This
- * means that besides triangulating, this routine is also useful for general and robust 2d edge and
- * face intersection.
- *
- * This routine also takes an epsilon parameter in the \a input. Input vertices closer than epsilon
- * will be merged, and we collapse tiny edges (less than epsilon length).
- *
- * The current initial Deluanay triangulation algorithm is the Guibas-Stolfi Divide and Conquer
- * algorithm (see "Primitives for the Manipulation of General Subdivisions and the Computation of
- * Voronoi Diagrams"). and uses Shewchuk's exact predicates to issues where numeric errors cause
- * inconsistent geometric judgments. This is followed by inserting edge constraints (including the
- * edges implied by faces) using the algorithms discussed in "Fully Dynamic Constrained Delaunay
- * Triangulations" by Kallmann, Bieri, and Thalmann.
- *
- * \param input: points to a CDT_input struct which contains the vertices, edges, and faces to be
- * triangulated. \param output_type: specifies which edges to remove after doing the triangulation.
- * \return A pointer to an allocated CDT_result struct, which describes the triangulation in terms
- * of vertices, edges, and faces, and also has tables to map output elements back to input
- * elements. The caller must use BLI_delaunay_2d_cdt_free() on the result when done with it.
- *
- * See the header file BLI_delaunay_2d.h for details of the CDT_input and CDT_result structs and
- * the CDT_output_type enum.
- */
-CDT_result *BLI_delaunay_2d_cdt_calc(const CDT_input *input, const CDT_output_type output_type)
-{
- int nv = input->verts_len;
- int ne = input->edges_len;
- int nf = input->faces_len;
- int i, iv1, iv2, f, fedge_start, fedge_end, ei;
- CDT_state *cdt;
- CDTVert *v1, *v2;
- CDTEdge *face_edge;
- SymEdge *face_symedge;
- LinkNode *edge_list;
- CDT_result *result;
- const CDT_input *input_orig;
- int *new_edge_map;
- static bool called_exactinit = false;
-#ifdef DEBUG_CDT
- int dbg_level = 0;
-#endif
-
- /* The exact orientation and incircle primitives need a one-time initialization of certain
- * constants. */
- if (!called_exactinit) {
- exactinit();
- called_exactinit = true;
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr,
- "\n\nCDT CALC, nv=%d, ne=%d, nf=%d, eps=%g\n",
- input->verts_len,
- input->edges_len,
- input->faces_len,
- input->epsilon);
- }
- if (dbg_level == -1) {
- write_cdt_input_to_file(input);
- }
-#endif
-
- if ((nv > 0 && input->vert_coords == NULL) || (ne > 0 && input->edges == NULL) ||
- (nf > 0 && (input->faces == NULL || input->faces_start_table == NULL ||
- input->faces_len_table == NULL))) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "invalid input: unexpected NULL array(s)\n");
-#endif
- return NULL;
- }
-
- input_orig = input;
- input = modify_input_for_near_edge_ends(input, &new_edge_map);
- if (input != input_orig) {
- nv = input->verts_len;
- ne = input->edges_len;
- nf = input->faces_len;
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- fprintf(stderr, "input modified for near ends; now ne=%d\n", ne);
- }
-#endif
- }
- cdt = cdt_init(input);
- initial_triangulation(cdt);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- validate_cdt(cdt, true, false, false);
- if (dbg_level > 1) {
- cdt_draw(cdt, "after initial triangulation");
- }
- }
-#endif
-
- for (i = 0; i < ne; i++) {
- iv1 = input->edges[i][0];
- iv2 = input->edges[i][1];
- if (iv1 < 0 || iv1 >= nv || iv2 < 0 || iv2 >= nv) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "edge indices for e%d not valid: v1=%d, v2=%d, nv=%d\n", i, iv1, iv2, nv);
-#endif
- continue;
- }
- v1 = cdt->vert_array[iv1];
- v2 = cdt->vert_array[iv2];
- if (v1->merge_to_index != -1) {
- v1 = cdt->vert_array[v1->merge_to_index];
- }
- if (v2->merge_to_index != -1) {
- v2 = cdt->vert_array[v2->merge_to_index];
- }
- if (new_edge_map) {
- ei = new_edge_map[i];
- }
- else {
- ei = i;
- }
- add_edge_constraint(cdt, v1, v2, ei, NULL);
-#ifdef DEBUG_CDT
- if (dbg_level > 3) {
- char namebuf[60];
- sprintf(namebuf, "after edge constraint %d = (%d,%d)\n", i, iv1, iv2);
- cdt_draw(cdt, namebuf);
- // dump_cdt(cdt, namebuf);
- validate_cdt(cdt, true, true, false);
- }
-#endif
- }
-
- cdt->face_edge_offset = ne;
- for (f = 0; f < nf; f++) {
- int flen = input->faces_len_table[f];
- int fstart = input->faces_start_table[f];
- if (flen <= 2) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "face %d has length %d; ignored\n", f, flen);
-#endif
- continue;
- }
- for (i = 0; i < flen; i++) {
- int face_edge_id = cdt->face_edge_offset + fstart + i;
- if (new_edge_map) {
- face_edge_id = new_edge_map[face_edge_id];
- }
- iv1 = input->faces[fstart + i];
- iv2 = input->faces[fstart + ((i + 1) % flen)];
- if (iv1 < 0 || iv1 >= nv || iv2 < 0 || iv2 >= nv) {
-#ifdef DEBUG_CDT
- fprintf(stderr, "face indices not valid: f=%d, iv1=%d, iv2=%d, nv=%d\n", f, iv1, iv2, nv);
-#endif
- continue;
- }
- v1 = cdt->vert_array[iv1];
- v2 = cdt->vert_array[iv2];
- if (v1->merge_to_index != -1) {
- v1 = cdt->vert_array[v1->merge_to_index];
- }
- if (v2->merge_to_index != -1) {
- v2 = cdt->vert_array[v2->merge_to_index];
- }
- add_edge_constraint(cdt, v1, v2, face_edge_id, &edge_list);
-#ifdef DEBUG_CDT
- if (dbg_level > 2) {
- fprintf(stderr, "edges for edge %d:\n", i);
- for (LinkNode *ln = edge_list; ln; ln = ln->next) {
- CDTEdge *cdt_e = (CDTEdge *)ln->link;
- fprintf(stderr,
- " (%.2f,%.2f)->(%.2f,%.2f)\n",
- F2(cdt_e->symedges[0].vert->co),
- F2(cdt_e->symedges[1].vert->co));
- }
- }
- if (dbg_level > 2) {
- cdt_draw(cdt, "after a face edge");
- if (dbg_level > 3) {
- dump_cdt(cdt, "after a face edge");
- }
- validate_cdt(cdt, true, true, false);
- }
-#endif
- if (i == 0) {
- face_edge = (CDTEdge *)edge_list->link;
- face_symedge = &face_edge->symedges[0];
- if (face_symedge->vert != v1) {
- face_symedge = &face_edge->symedges[1];
- BLI_assert(face_symedge->vert == v1);
- }
- }
- BLI_linklist_free_pool(edge_list, NULL, cdt->listpool);
- }
- fedge_start = cdt->face_edge_offset + fstart;
- fedge_end = fedge_start + flen - 1;
- add_face_ids(cdt, face_symedge, f, fedge_start, fedge_end);
- }
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- validate_cdt(cdt, true, true, false);
- }
- if (dbg_level > 1) {
- cdt_draw(cdt, "after adding edges and faces");
- if (dbg_level > 2) {
- dump_cdt(cdt, "after adding edges and faces");
- }
- }
-#endif
-
- if (cdt->epsilon > 0.0) {
- remove_small_features(cdt);
-#ifdef DEBUG_CDT
- if (dbg_level > 2) {
- cdt_draw(cdt, "after remove small features\n");
- if (dbg_level > 3) {
- dump_cdt(cdt, "after remove small features\n");
- }
- }
-#endif
- }
-
- result = cdt_get_output(cdt, input, output_type);
-#ifdef DEBUG_CDT
- if (dbg_level > 0) {
- cdt_draw(cdt, "final");
- }
-#endif
-
- if (input != input_orig) {
- free_modified_input((CDT_input *)input);
- }
- if (new_edge_map != NULL) {
- MEM_freeN(new_edge_map);
- }
- new_cdt_free(cdt);
- return result;
-}
-
-void BLI_delaunay_2d_cdt_free(CDT_result *result)
-{
- if (result == NULL) {
- return;
- }
- if (result->vert_coords) {
- MEM_freeN(result->vert_coords);
- }
- if (result->edges) {
- MEM_freeN(result->edges);
- }
- if (result->faces) {
- MEM_freeN(result->faces);
- }
- if (result->faces_start_table) {
- MEM_freeN(result->faces_start_table);
- }
- if (result->faces_len_table) {
- MEM_freeN(result->faces_len_table);
- }
- if (result->verts_orig) {
- MEM_freeN(result->verts_orig);
- }
- if (result->verts_orig_start_table) {
- MEM_freeN(result->verts_orig_start_table);
- }
- if (result->verts_orig_len_table) {
- MEM_freeN(result->verts_orig_len_table);
- }
- if (result->edges_orig) {
- MEM_freeN(result->edges_orig);
- }
- if (result->edges_orig_start_table) {
- MEM_freeN(result->edges_orig_start_table);
- }
- if (result->edges_orig_len_table) {
- MEM_freeN(result->edges_orig_len_table);
- }
- if (result->faces_orig) {
- MEM_freeN(result->faces_orig);
- }
- if (result->faces_orig_start_table) {
- MEM_freeN(result->faces_orig_start_table);
- }
- if (result->faces_orig_len_table) {
- MEM_freeN(result->faces_orig_len_table);
- }
- MEM_freeN(result);
-}
-
-#ifdef DEBUG_CDT
-
-ATTU static const char *vertname(const CDTVert *v)
-{
- static char vertnamebuf[20];
-
- sprintf(vertnamebuf, "[%d]", v->index);
- return vertnamebuf;
-}
-
-ATTU static const char *sename(const SymEdge *se)
-{
- static char senamebuf[20];
-
- sprintf(senamebuf, "{%x}", (POINTER_AS_UINT(se)) & 0xFFFF);
- return senamebuf;
-}
-
-static void dump_v(const CDTVert *v, const char *lab)
-{
- fprintf(stderr, "%s%s(%.10f,%.10f)\n", lab, vertname(v), F2(v->co));
-}
-
-static void dump_se(const SymEdge *se, const char *lab)
-{
- if (se->next) {
- fprintf(stderr,
- "%s%s((%.10f,%.10f)->(%.10f,%.10f))",
- lab,
- vertname(se->vert),
- F2(se->vert->co),
- F2(se->next->vert->co));
- fprintf(stderr, "%s\n", vertname(se->next->vert));
- }
- else {
- fprintf(stderr, "%s%s((%.10f,%.10f)->NULL)\n", lab, vertname(se->vert), F2(se->vert->co));
- }
-}
-
-static void dump_se_short(const SymEdge *se, const char *lab)
-{
- if (se == NULL) {
- fprintf(stderr, "%sNULL", lab);
- }
- else {
- fprintf(stderr, "%s%s", lab, vertname(se->vert));
- fprintf(stderr, "%s", se->next == NULL ? "[NULL]" : vertname(se->next->vert));
- }
-}
-
-static void dump_se_cycle(const SymEdge *se, const char *lab, const int limit)
-{
- int count = 0;
- const SymEdge *s = se;
- fprintf(stderr, "%s:\n", lab);
- do {
- dump_se(s, " ");
- s = s->next;
- count++;
- } while (s != se && count < limit);
- if (count == limit) {
- fprintf(stderr, " limit hit without cycle!\n");
- }
-}
-
-static void dump_id_list(const LinkNode *id_list, const char *lab)
-{
- const LinkNode *ln;
- if (!id_list) {
- return;
- }
- fprintf(stderr, "%s", lab);
- for (ln = id_list; ln; ln = ln->next) {
- fprintf(stderr, "%d%c", POINTER_AS_INT(ln->link), ln->next ? ' ' : '\n');
- }
-}
-
-static void dump_cross_data(struct CrossData *cd, const char *lab)
-{
- fprintf(stderr, "%s", lab);
- if (cd->lambda == 0.0) {
- fprintf(stderr, "v%d", cd->vert->index);
- }
- else {
- fprintf(stderr, "lambda=%.17g", cd->lambda);
- }
- dump_se_short(cd->in, " in=");
- dump_se_short(cd->out, " out=");
- fprintf(stderr, "\n");
-}
-
-/* If filter_fn != NULL, only dump vert v its edges when filter_fn(cdt, v, filter_data) is true. */
-# define PL(p) (POINTER_AS_UINT(p) & 0xFFFF)
-static void dump_cdt_filtered(const CDT_state *cdt,
- bool (*filter_fn)(const CDT_state *, int, void *),
- void *filter_data,
- const char *lab)
-{
- LinkNode *ln;
- CDTVert *v, *vother;
- CDTEdge *e;
- CDTFace *f;
- SymEdge *se;
- int i, cnt;
-
- fprintf(stderr, "\nCDT %s\n", lab);
- fprintf(stderr, "\nVERTS\n");
- for (i = 0; i < cdt->vert_array_len; i++) {
- if (filter_fn && !filter_fn(cdt, i, filter_data)) {
- continue;
- }
- v = cdt->vert_array[i];
- fprintf(stderr, "%s %x: (%f,%f) symedge=%x", vertname(v), PL(v), F2(v->co), PL(v->symedge));
- if (v->merge_to_index == -1) {
- fprintf(stderr, "\n");
- }
- else {
- fprintf(stderr, " merge to %s\n", vertname(cdt->vert_array[v->merge_to_index]));
- continue;
- }
- dump_id_list(v->input_ids, " ");
- se = v->symedge;
- cnt = 0;
- if (se) {
- fprintf(stderr, " edges out:\n");
- do {
- if (se->next == NULL) {
- fprintf(stderr, " [NULL next/rot symedge, se=%x\n", PL(se));
- break;
- }
- if (se->next->next == NULL) {
- fprintf(stderr, " [NULL next-next/rot symedge, se=%x\n", PL(se));
- break;
- }
- vother = sym(se)->vert;
- fprintf(stderr, " %s (e=%x, se=%x)\n", vertname(vother), PL(se->edge), PL(se));
- se = se->rot;
- cnt++;
- } while (se != v->symedge && cnt < 25);
- fprintf(stderr, "\n");
- }
- }
- if (filter_fn) {
- return;
- }
- fprintf(stderr, "\nEDGES\n");
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- if (e->symedges[0].next == NULL) {
- continue;
- }
- fprintf(stderr, "%x:\n", PL(e));
- for (i = 0; i < 2; i++) {
- se = &e->symedges[i];
- fprintf(stderr,
- " se[%d] @%x: next=%x, rot=%x, vert=%x [%s] (%.2f,%.2f), edge=%x, face=%x\n",
- i,
- PL(se),
- PL(se->next),
- PL(se->rot),
- PL(se->vert),
- vertname(se->vert),
- F2(se->vert->co),
- PL(se->edge),
- PL(se->face));
- }
- dump_id_list(e->input_ids, " ");
- }
- fprintf(stderr, "\nFACES\n");
- for (ln = cdt->faces; ln; ln = ln->next) {
- f = (CDTFace *)ln->link;
- if (f->deleted) {
- continue;
- }
- if (f == cdt->outer_face) {
- fprintf(stderr, "%x: outer", PL(f));
- }
- fprintf(stderr, " symedge=%x\n", PL(f->symedge));
- dump_id_list(f->input_ids, " ");
- }
- fprintf(stderr, "\nOTHER\n");
- fprintf(stderr, "outer_face=%x\n", PL(cdt->outer_face));
- fprintf(
- stderr, "minx=%f, maxx=%f, miny=%f, maxy=%f\n", cdt->minx, cdt->maxx, cdt->miny, cdt->maxy);
- fprintf(stderr, "margin=%f\n", cdt->margin);
-}
-# undef PL
-
-static void dump_cdt(const CDT_state *cdt, const char *lab)
-{
- dump_cdt_filtered(cdt, NULL, NULL, lab);
-}
-
-typedef struct ReachableFilterData {
- int vstart_index;
- int maxdist;
-} ReachableFilterData;
-
-/* Stupid algorithm will repeat itself. Don't use for large n. */
-static bool reachable_filter(const CDT_state *cdt, int v_index, void *filter_data)
-{
- CDTVert *v;
- SymEdge *se;
- ReachableFilterData *rfd_in = (ReachableFilterData *)filter_data;
- ReachableFilterData rfd_next;
-
- if (v_index == rfd_in->vstart_index) {
- return true;
- }
- if (rfd_in->maxdist <= 0 || v_index < 0 || v_index >= cdt->vert_array_len) {
- return false;
- }
- else {
- v = cdt->vert_array[v_index];
- se = v->symedge;
- if (se != NULL) {
- rfd_next.vstart_index = rfd_in->vstart_index;
- rfd_next.maxdist = rfd_in->maxdist - 1;
- do {
- if (reachable_filter(cdt, se->next->vert->index, &rfd_next)) {
- return true;
- }
- se = se->rot;
- } while (se != v->symedge);
- }
- }
- return false;
-}
-
-static void set_min_max(CDT_state *cdt)
-{
- int i;
- double minx, maxx, miny, maxy;
- double *co;
-
- minx = miny = DBL_MAX;
- maxx = maxy = -DBL_MAX;
- for (i = 0; i < cdt->vert_array_len; i++) {
- co = cdt->vert_array[i]->co;
- if (co[0] < minx) {
- minx = co[0];
- }
- if (co[0] > maxx) {
- maxx = co[0];
- }
- if (co[1] < miny) {
- miny = co[1];
- }
- if (co[1] > maxy) {
- maxy = co[1];
- }
- }
- if (minx != DBL_MAX) {
- cdt->minx = minx;
- cdt->miny = miny;
- cdt->maxx = maxx;
- cdt->maxy = maxy;
- }
-}
-
-static void dump_cdt_vert_neighborhood(CDT_state *cdt, int v, int maxdist, const char *lab)
-{
- ReachableFilterData rfd;
- rfd.vstart_index = v;
- rfd.maxdist = maxdist;
- dump_cdt_filtered(cdt, reachable_filter, &rfd, lab);
-}
-
-/*
- * Make an html file with svg in it to display the argument cdt.
- * Mouse-overs will reveal the coordinates of vertices and edges.
- * Constraint edges are drawn thicker than non-constraint edges.
- * The first call creates DRAWFILE; subsequent calls append to it.
- */
-# define DRAWFILE "/tmp/debug_draw.html"
-# define MAX_DRAW_WIDTH 2000
-# define MAX_DRAW_HEIGHT 1400
-# define THIN_LINE 1
-# define THICK_LINE 4
-# define VERT_RADIUS 3
-# define DRAW_VERT_LABELS 1
-# define DRAW_EDGE_LABELS 0
-
-static void cdt_draw_region(
- CDT_state *cdt, const char *lab, double minx, double miny, double maxx, double maxy)
-{
- static bool append = false;
- FILE *f = fopen(DRAWFILE, append ? "a" : "w");
- int view_width, view_height;
- double width, height, aspect, scale;
- LinkNode *ln;
- CDTVert *v, *u;
- CDTEdge *e;
- int i, strokew;
-
- width = maxx - minx;
- height = maxy - miny;
- aspect = height / width;
- view_width = MAX_DRAW_WIDTH;
- view_height = (int)(view_width * aspect);
- if (view_height > MAX_DRAW_HEIGHT) {
- view_height = MAX_DRAW_HEIGHT;
- view_width = (int)(view_height / aspect);
- }
- scale = view_width / width;
-
-# define SX(x) ((x - minx) * scale)
-# define SY(y) ((maxy - y) * scale)
-
- if (!f) {
- printf("couldn't open file %s\n", DRAWFILE);
- return;
- }
- fprintf(f, "<div>%s</div>\n<div>\n", lab);
- fprintf(f,
- "<svg version=\"1.1\" "
- "xmlns=\"http://www.w3.org/2000/svg\" "
- "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
- "xml:space=\"preserve\"\n");
- fprintf(f, "width=\"%d\" height=\"%d\">/n", view_width, view_height);
-
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- if (is_deleted_edge(e)) {
- continue;
- }
- u = e->symedges[0].vert;
- v = e->symedges[1].vert;
- strokew = is_constrained_edge(e) ? THICK_LINE : THIN_LINE;
- fprintf(f,
- "<line fill=\"none\" stroke=\"black\" stroke-width=\"%d\" "
- "x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\">\n",
- strokew,
- SX(u->co[0]),
- SY(u->co[1]),
- SX(v->co[0]),
- SY(v->co[1]));
- fprintf(f, " <title>%s", vertname(u));
- fprintf(f, "%s</title>\n", vertname(v));
- fprintf(f, "</line>\n");
-# if DRAW_EDGE_LABELS
- fprintf(f,
- "<text x=\"%f\" y=\"%f\" font-size=\"small\">",
- SX(0.5 * (u->co[0] + v->co[0])),
- SY(0.5 * (u->co[1] + v->co[1])));
- fprintf(f, "%s", vertname(u));
- fprintf(f, "%s", vertname(v));
- fprintf(f, "%s", sename(&e->symedges[0]));
- fprintf(f, "%s</text>\n", sename(&e->symedges[1]));
-# endif
- }
- i = 0;
- for (; i < cdt->vert_array_len; i++) {
- v = cdt->vert_array[i];
- if (v->merge_to_index != -1) {
- continue;
- }
- fprintf(f,
- "<circle fill=\"black\" cx=\"%f\" cy=\"%f\" r=\"%d\">\n",
- SX(v->co[0]),
- SY(v->co[1]),
- VERT_RADIUS);
- fprintf(f, " <title>%s(%.10f,%.10f)</title>\n", vertname(v), v->co[0], v->co[1]);
- fprintf(f, "</circle>\n");
-# if DRAW_VERT_LABELS
- fprintf(f,
- "<text x=\"%f\" y=\"%f\" font-size=\"small\">%s</text>\n",
- SX(v->co[0]) + VERT_RADIUS,
- SY(v->co[1]) - VERT_RADIUS,
- vertname(v));
-# endif
- }
-
- fprintf(f, "</svg>\n</div>\n");
- fclose(f);
- append = true;
-# undef SX
-# undef SY
-}
-
-static void cdt_draw(CDT_state *cdt, const char *lab)
-{
- double draw_margin, minx, maxx, miny, maxy;
-
- set_min_max(cdt);
- draw_margin = (cdt->maxx - cdt->minx + cdt->maxy - cdt->miny + 1) * 0.05;
- minx = cdt->minx - draw_margin;
- maxx = cdt->maxx + draw_margin;
- miny = cdt->miny - draw_margin;
- maxy = cdt->maxy + draw_margin;
- cdt_draw_region(cdt, lab, minx, miny, maxx, maxy);
-}
-
-static void cdt_draw_vertex_region(CDT_state *cdt, int v, double dist, const char *lab)
-{
- const double *co = cdt->vert_array[v]->co;
- cdt_draw_region(cdt, lab, co[0] - dist, co[1] - dist, co[0] + dist, co[1] + dist);
-}
-
-static void cdt_draw_edge_region(CDT_state *cdt, int v1, int v2, double dist, const char *lab)
-{
- const double *co1 = cdt->vert_array[v1]->co;
- const double *co2 = cdt->vert_array[v2]->co;
- double minx, miny, maxx, maxy;
-
- minx = min_dd(co1[0], co2[0]);
- miny = min_dd(co1[1], co2[1]);
- maxx = max_dd(co1[0], co2[0]);
- maxy = max_dd(co1[1], co2[1]);
- cdt_draw_region(cdt, lab, minx - dist, miny - dist, maxx + dist, maxy + dist);
-}
-
-# define CDTFILE "/tmp/cdtinput.txt"
-static void write_cdt_input_to_file(const CDT_input *inp)
-{
- int i, j;
- FILE *f = fopen(CDTFILE, "w");
-
- fprintf(f, "%d %d %d\n", inp->verts_len, inp->edges_len, inp->faces_len);
- for (i = 0; i < inp->verts_len; i++) {
- fprintf(f, "%.17f %.17f\n", inp->vert_coords[i][0], inp->vert_coords[i][1]);
- }
- for (i = 0; i < inp->edges_len; i++) {
- fprintf(f, "%d %d\n", inp->edges[i][0], inp->edges[i][1]);
- }
- for (i = 0; i < inp->faces_len; i++) {
- for (j = 0; j < inp->faces_len_table[i]; j++) {
- fprintf(f, "%d ", inp->faces[j + inp->faces_start_table[i]]);
- }
- fprintf(f, "\n");
- }
- fclose(f);
-}
-
-# ifndef NDEBUG /* Only used in assert. */
-/*
- * Is a visible from b: i.e., ab crosses no edge of cdt?
- * If constrained is true, consider only constrained edges as possible crossed edges.
- * In any case, don't count an edge ab itself.
- * Note: this is an expensive test if there are a lot of edges.
- */
-static bool is_visible(const CDTVert *a, const CDTVert *b, bool constrained, const CDT_state *cdt)
-{
- const LinkNode *ln;
- const CDTEdge *e;
- const SymEdge *se, *senext;
- double lambda, mu;
- int ikind;
-
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (const CDTEdge *)ln->link;
- if (is_deleted_edge(e) || is_border_edge(e, cdt)) {
- continue;
- }
- if (constrained && !is_constrained_edge(e)) {
- continue;
- }
- se = (const SymEdge *)&e->symedges[0];
- senext = se->next;
- if ((a == se->vert || a == senext->vert) || b == se->vert || b == se->next->vert) {
- continue;
- }
- ikind = isect_seg_seg_v2_lambda_mu_db(
- a->co, b->co, se->vert->co, senext->vert->co, &lambda, &mu);
- if (ikind != ISECT_LINE_LINE_NONE) {
- if (ikind == ISECT_LINE_LINE_COLINEAR) {
- /* TODO: special test here for overlap. */
- continue;
- }
- /* Allow an intersection very near or at ends, to allow for numerical error. */
- if (lambda > FLT_EPSILON && (1.0 - lambda) > FLT_EPSILON && mu > FLT_EPSILON &&
- (1.0 - mu) > FLT_EPSILON) {
- return false;
- }
- }
- }
- return true;
-}
-# endif
-
-# ifndef NDEBUG /* Only used in assert. */
-/*
- * Check that edge ab satisfies constrained delaunay condition:
- * That is, for all non-constraint, non-border edges ab,
- * (1) ab is visible in the constraint graph; and
- * (2) there is a circle through a and b such that any vertex v connected by an edge to a or b
- * is not inside that circle.
- * The argument 'se' specifies ab by: a is se's vert and b is se->next's vert.
- * Return true if check is OK.
- */
-static bool is_delaunay_edge(const SymEdge *se)
-{
- int i;
- CDTVert *a, *b, *c;
- const SymEdge *sesym, *curse, *ss;
- bool ok[2];
-
- if (!is_constrained_edge(se->edge)) {
- return true;
- }
- sesym = sym(se);
- a = se->vert;
- b = se->next->vert;
- /* Try both the triangles adjacent to se's edge for circle. */
- for (i = 0; i < 2; i++) {
- ok[i] = true;
- curse = (i == 0) ? se : sesym;
- a = curse->vert;
- b = curse->next->vert;
- c = curse->next->next->vert;
- for (ss = curse->rot; ss != curse; ss = ss->rot) {
- ok[i] |= incircle(a->co, b->co, c->co, ss->next->vert->co) <= 0.0;
- }
- }
- return ok[0] || ok[1];
-}
-# endif
-
-# ifndef NDEBUG
-static bool plausible_non_null_ptr(void *p)
-{
- return p > (void *)0x1000;
-}
-# endif
-
-static void validate_cdt(CDT_state *cdt,
- bool check_all_tris,
- bool check_delaunay,
- bool check_visibility)
-{
- LinkNode *ln;
- int totedges, totfaces, totverts;
- CDTEdge *e;
- SymEdge *se, *sesym, *s;
- CDTVert *v, *v1, *v2, *v3;
- CDTFace *f;
- int i, limit;
- bool isborder;
-
- if (cdt->output_prepared) {
- return;
- }
- if (cdt->edges == NULL || cdt->edges->next == NULL) {
- return;
- }
-
- BLI_assert(cdt != NULL);
- totedges = 0;
- for (ln = cdt->edges; ln; ln = ln->next) {
- e = (CDTEdge *)ln->link;
- se = &e->symedges[0];
- sesym = &e->symedges[1];
- if (is_deleted_edge(e)) {
- BLI_assert(se->rot == NULL && sesym->next == NULL && sesym->rot == NULL);
- continue;
- }
- totedges++;
- isborder = is_border_edge(e, cdt);
- BLI_assert(se->vert != sesym->vert);
- BLI_assert(se->edge == sesym->edge && se->edge == e);
- BLI_assert(sym(se) == sesym && sym(sesym) == se);
- for (i = 0; i < 2; i++) {
- se = &e->symedges[i];
- v = se->vert;
- f = se->face;
- BLI_assert(plausible_non_null_ptr(v));
- if (f != NULL) {
- BLI_assert(plausible_non_null_ptr(f));
- }
- BLI_assert(plausible_non_null_ptr(se->next));
- BLI_assert(plausible_non_null_ptr(se->rot));
- if (check_all_tris && se->face != cdt->outer_face) {
- limit = 3;
- }
- else {
- limit = 10000;
- }
- BLI_assert(reachable(se->next, se, limit));
- if (limit == 3) {
- v1 = se->vert;
- v2 = se->next->vert;
- v3 = se->next->next->vert;
- /* The triangle should be positively oriented, but because
- * the insertion of intersection vertices doesn't use exact
- * arithmetic, this may not be true, so allow a little slop. */
- BLI_assert(orient2d(v1->co, v2->co, v3->co) >= -FLT_EPSILON);
- BLI_assert(orient2d(v2->co, v3->co, v1->co) >= -FLT_EPSILON);
- BLI_assert(orient2d(v3->co, v1->co, v2->co) >= -FLT_EPSILON);
- }
- UNUSED_VARS_NDEBUG(limit);
- BLI_assert(se->next->next != se);
- s = se;
- do {
- BLI_assert(prev(s)->next == s);
- BLI_assert(s->rot == sym(prev(s)));
- s = s->next;
- } while (s != se);
- }
- if (check_visibility) {
- BLI_assert(isborder || is_visible(se->vert, se->next->vert, false, cdt));
- }
- if (!isborder && check_delaunay) {
- BLI_assert(is_delaunay_edge(se));
- }
- }
- totverts = 0;
- for (i = 0; i < cdt->vert_array_len; i++) {
- v = cdt->vert_array[i];
- BLI_assert(plausible_non_null_ptr(v));
- if (v->merge_to_index != -1) {
- BLI_assert(v->merge_to_index >= 0 && v->merge_to_index < cdt->vert_array_len);
- continue;
- }
- totverts++;
- BLI_assert(cdt->vert_array_len <= 1 || v->symedge->vert == v);
- }
- totfaces = 0;
- for (ln = cdt->faces; ln; ln = ln->next) {
- f = (CDTFace *)ln->link;
- BLI_assert(plausible_non_null_ptr(f));
- if (f->deleted) {
- continue;
- }
- totfaces++;
- if (f == cdt->outer_face) {
- continue;
- }
- }
- /* Euler's formula for planar graphs. */
- if (check_all_tris && totfaces > 1) {
- BLI_assert(totverts - totedges + totfaces == 2);
- }
-}
-#endif
-
-/* Jonathan Shewchuk's adaptive predicates, trimmed to those needed here.
- * Permission obtained by private communication from Jonathan to include this code in Blender.
- */
-
-/*
- * Routines for Arbitrary Precision Floating-point Arithmetic
- * and Fast Robust Geometric Predicates
- * (predicates.c)
- *
- * May 18, 1996
- *
- * Placed in the public domain by
- * Jonathan Richard Shewchuk
- * School of Computer Science
- * Carnegie Mellon University
- * 5000 Forbes Avenue
- * Pittsburgh, Pennsylvania 15213-3891
- * jrs@cs.cmu.edu
- *
- * This file contains C implementation of algorithms for exact addition
- * and multiplication of floating-point numbers, and predicates for
- * robustly performing the orientation and incircle tests used in
- * computational geometry. The algorithms and underlying theory are
- * described in Jonathan Richard Shewchuk. "Adaptive Precision Floating-
- * Point Arithmetic and Fast Robust Geometric Predicates." Technical
- * Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon
- * University, Pittsburgh, Pennsylvania, May 1996. (Submitted to
- * Discrete & Computational Geometry.)
- *
- * This file, the paper listed above, and other information are available
- * from the Web page http://www.cs.cmu.edu/~quake/robust.html .
- *
- * Using this code:
- *
- * First, read the short or long version of the paper (from the Web page
- * above).
- *
- * Be sure to call exactinit() once, before calling any of the arithmetic
- * functions or geometric predicates. Also be sure to turn on the
- * optimizer when compiling this file.
- *
- * On some machines, the exact arithmetic routines might be defeated by the
- * use of internal extended precision floating-point registers. Sometimes
- * this problem can be fixed by defining certain values to be volatile,
- * thus forcing them to be stored to memory and rounded off. This isn't
- * a great solution, though, as it slows the arithmetic down.
- *
- * To try this out, write "#define INEXACT volatile" below. Normally,
- * however, INEXACT should be defined to be nothing. ("#define INEXACT".)
- */
-
-#define INEXACT /* Nothing */
-/* #define INEXACT volatile */
-
-/* Which of the following two methods of finding the absolute values is
- * fastest is compiler-dependent. A few compilers can inline and optimize
- * the fabs() call; but most will incur the overhead of a function call,
- * which is disastrously slow. A faster way on IEEE machines might be to
- * mask the appropriate bit, but that's difficult to do in C.
- */
-
-#define Absolute(a) ((a) >= 0.0 ? (a) : -(a))
-/* #define Absolute(a) fabs(a) */
-
-/* Many of the operations are broken up into two pieces, a main part that
- * performs an approximate operation, and a "tail" that computes the
- * roundoff error of that operation.
- *
- * The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(),
- * Split(), and Two_Product() are all implemented as described in the
- * reference. Each of these macros requires certain variables to be
- * defined in the calling routine. The variables `bvirt', `c', `abig',
- * `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because
- * they store the result of an operation that may incur roundoff error.
- * The input parameter `x' (or the highest numbered `x_' parameter) must
- * also be declared `INEXACT'.
- */
-
-#define Fast_Two_Sum_Tail(a, b, x, y) \
- bvirt = x - a; \
- y = b - bvirt
-
-#define Fast_Two_Sum(a, b, x, y) \
- x = (double)(a + b); \
- Fast_Two_Sum_Tail(a, b, x, y)
-
-#define Fast_Two_Diff_Tail(a, b, x, y) \
- bvirt = a - x; \
- y = bvirt - b
-
-#define Fast_Two_Diff(a, b, x, y) \
- x = (double)(a - b); \
- Fast_Two_Diff_Tail(a, b, x, y)
-
-#define Two_Sum_Tail(a, b, x, y) \
- bvirt = (double)(x - a); \
- avirt = x - bvirt; \
- bround = b - bvirt; \
- around = a - avirt; \
- y = around + bround
-
-#define Two_Sum(a, b, x, y) \
- x = (double)(a + b); \
- Two_Sum_Tail(a, b, x, y)
-
-#define Two_Diff_Tail(a, b, x, y) \
- bvirt = (double)(a - x); \
- avirt = x + bvirt; \
- bround = bvirt - b; \
- around = a - avirt; \
- y = around + bround
-
-#define Two_Diff(a, b, x, y) \
- x = (double)(a - b); \
- Two_Diff_Tail(a, b, x, y)
-
-#define Split(a, ahi, alo) \
- c = (double)(splitter * a); \
- abig = (double)(c - a); \
- ahi = c - abig; \
- alo = a - ahi
-
-#define Two_Product_Tail(a, b, x, y) \
- Split(a, ahi, alo); \
- Split(b, bhi, blo); \
- err1 = x - (ahi * bhi); \
- err2 = err1 - (alo * bhi); \
- err3 = err2 - (ahi * blo); \
- y = (alo * blo) - err3
-
-#define Two_Product(a, b, x, y) \
- x = (double)(a * b); \
- Two_Product_Tail(a, b, x, y)
-
-#define Two_Product_Presplit(a, b, bhi, blo, x, y) \
- x = (double)(a * b); \
- Split(a, ahi, alo); \
- err1 = x - (ahi * bhi); \
- err2 = err1 - (alo * bhi); \
- err3 = err2 - (ahi * blo); \
- y = (alo * blo) - err3
-
-#define Square_Tail(a, x, y) \
- Split(a, ahi, alo); \
- err1 = x - (ahi * ahi); \
- err3 = err1 - ((ahi + ahi) * alo); \
- y = (alo * alo) - err3
-
-#define Square(a, x, y) \
- x = (double)(a * a); \
- Square_Tail(a, x, y)
-
-#define Two_One_Sum(a1, a0, b, x2, x1, x0) \
- Two_Sum(a0, b, _i, x0); \
- Two_Sum(a1, _i, x2, x1)
-
-#define Two_One_Diff(a1, a0, b, x2, x1, x0) \
- Two_Diff(a0, b, _i, x0); \
- Two_Sum(a1, _i, x2, x1)
-
-#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \
- Two_One_Sum(a1, a0, b0, _j, _0, x0); \
- Two_One_Sum(_j, _0, b1, x3, x2, x1)
-
-#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \
- Two_One_Diff(a1, a0, b0, _j, _0, x0); \
- Two_One_Diff(_j, _0, b1, x3, x2, x1)
-
-static double splitter; /* = 2^ceiling(p / 2) + 1. Used to split floats in half. */
-static double m_epsilon; /* = 2^(-p). Used to estimate roundoff errors. */
-/* A set of coefficients used to calculate maximum roundoff errors. */
-static double resulterrbound;
-static double ccwerrboundA, ccwerrboundB, ccwerrboundC;
-static double o3derrboundA, o3derrboundB, o3derrboundC;
-static double iccerrboundA, iccerrboundB, iccerrboundC;
-static double isperrboundA, isperrboundB, isperrboundC;
-
-/* exactinit() Initialize the variables used for exact arithmetic.
- *
- * `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in
- * floating-point arithmetic. `epsilon' bounds the relative roundoff
- * error. It is used for floating-point error analysis.
- *
- * `splitter' is used to split floating-point numbers into two
- * half-length significands for exact multiplication.
- *
- * I imagine that a highly optimizing compiler might be too smart for its
- * own good, and somehow cause this routine to fail, if it pretends that
- * floating-point arithmetic is too much like real arithmetic.
- *
- * Don't change this routine unless you fully understand it.
- */
-
-static void exactinit(void)
-{
- double half;
- double check, lastcheck;
- int every_other;
-
- every_other = 1;
- half = 0.5;
- m_epsilon = 1.0;
- splitter = 1.0;
- check = 1.0;
- /* Repeatedly divide `epsilon' by two until it is too small to add to
- * one without causing roundoff. (Also check if the sum is equal to
- * the previous sum, for machines that round up instead of using exact
- * rounding. Not that this library will work on such machines anyway.
- */
- do {
- lastcheck = check;
- m_epsilon *= half;
- if (every_other) {
- splitter *= 2.0;
- }
- every_other = !every_other;
- check = 1.0 + m_epsilon;
- } while ((check != 1.0) && (check != lastcheck));
- splitter += 1.0;
-
- /* Error bounds for orientation and incircle tests. */
- resulterrbound = (3.0 + 8.0 * m_epsilon) * m_epsilon;
- ccwerrboundA = (3.0 + 16.0 * m_epsilon) * m_epsilon;
- ccwerrboundB = (2.0 + 12.0 * m_epsilon) * m_epsilon;
- ccwerrboundC = (9.0 + 64.0 * m_epsilon) * m_epsilon * m_epsilon;
- o3derrboundA = (7.0 + 56.0 * m_epsilon) * m_epsilon;
- o3derrboundB = (3.0 + 28.0 * m_epsilon) * m_epsilon;
- o3derrboundC = (26.0 + 288.0 * m_epsilon) * m_epsilon * m_epsilon;
- iccerrboundA = (10.0 + 96.0 * m_epsilon) * m_epsilon;
- iccerrboundB = (4.0 + 48.0 * m_epsilon) * m_epsilon;
- iccerrboundC = (44.0 + 576.0 * m_epsilon) * m_epsilon * m_epsilon;
- isperrboundA = (16.0 + 224.0 * m_epsilon) * m_epsilon;
- isperrboundB = (5.0 + 72.0 * m_epsilon) * m_epsilon;
- isperrboundC = (71.0 + 1408.0 * m_epsilon) * m_epsilon * m_epsilon;
-}
-
-/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero
- * components from the output expansion.
- *
- * Sets h = e + f. See the long version of my paper for details.
- *
- * If round-to-even is used (as with IEEE 754), maintains the strongly
- * non-overlapping property. (That is, if e is strongly non-overlapping, h
- * will be also.) Does NOT maintain the non-overlapping or non-adjacent
- * properties.
- */
-
-static int fast_expansion_sum_zeroelim(
- int elen, const double *e, int flen, const double *f, double *h) /* h cannot be e or f. */
-{
- double Q;
- INEXACT double Qnew;
- INEXACT double hh;
- INEXACT double bvirt;
- double avirt, bround, around;
- int eindex, findex, hindex;
- double enow, fnow;
-
- enow = e[0];
- fnow = f[0];
- eindex = findex = 0;
- if ((fnow > enow) == (fnow > -enow)) {
- Q = enow;
- enow = e[++eindex];
- }
- else {
- Q = fnow;
- fnow = f[++findex];
- }
- hindex = 0;
- if ((eindex < elen) && (findex < flen)) {
- if ((fnow > enow) == (fnow > -enow)) {
- Fast_Two_Sum(enow, Q, Qnew, hh);
- enow = e[++eindex];
- }
- else {
- Fast_Two_Sum(fnow, Q, Qnew, hh);
- fnow = f[++findex];
- }
- Q = Qnew;
- if (hh != 0.0) {
- h[hindex++] = hh;
- }
- while ((eindex < elen) && (findex < flen)) {
- if ((fnow > enow) == (fnow > -enow)) {
- Two_Sum(Q, enow, Qnew, hh);
- enow = e[++eindex];
- }
- else {
- Two_Sum(Q, fnow, Qnew, hh);
- fnow = f[++findex];
- }
- Q = Qnew;
- if (hh != 0.0) {
- h[hindex++] = hh;
- }
- }
- }
- while (eindex < elen) {
- Two_Sum(Q, enow, Qnew, hh);
- enow = e[++eindex];
- Q = Qnew;
- if (hh != 0.0) {
- h[hindex++] = hh;
- }
- }
- while (findex < flen) {
- Two_Sum(Q, fnow, Qnew, hh);
- fnow = f[++findex];
- Q = Qnew;
- if (hh != 0.0) {
- h[hindex++] = hh;
- }
- }
- if ((Q != 0.0) || (hindex == 0)) {
- h[hindex++] = Q;
- }
- return hindex;
-}
-
-/* scale_expansion_zeroelim() Multiply an expansion by a scalar,
- * eliminating zero components from the
- * output expansion.
- *
- * Sets h = be. See either version of my paper for details.
- *
- * Maintains the nonoverlapping property. If round-to-even is used (as
- * with IEEE 754), maintains the strongly nonoverlapping and nonadjacent
- * properties as well. (That is, if e has one of these properties, so
- * will h.)
- */
-
-static int scale_expansion_zeroelim(int elen,
- const double *e,
- double b,
- double *h) /* e and h cannot be the same. */
-{
- INEXACT double Q, sum;
- double hh;
- INEXACT double product1;
- double product0;
- int eindex, hindex;
- double enow;
- INEXACT double bvirt;
- double avirt, bround, around;
- INEXACT double c;
- INEXACT double abig;
- double ahi, alo, bhi, blo;
- double err1, err2, err3;
-
- Split(b, bhi, blo);
- Two_Product_Presplit(e[0], b, bhi, blo, Q, hh);
- hindex = 0;
- if (hh != 0) {
- h[hindex++] = hh;
- }
- for (eindex = 1; eindex < elen; eindex++) {
- enow = e[eindex];
- Two_Product_Presplit(enow, b, bhi, blo, product1, product0);
- Two_Sum(Q, product0, sum, hh);
- if (hh != 0) {
- h[hindex++] = hh;
- }
- Fast_Two_Sum(product1, sum, Q, hh);
- if (hh != 0) {
- h[hindex++] = hh;
- }
- }
- if ((Q != 0.0) || (hindex == 0)) {
- h[hindex++] = Q;
- }
- return hindex;
-}
-
-/* estimate() Produce a one-word estimate of an expansion's value.
- *
- * See either version of my paper for details.
- */
-
-static double estimate(int elen, const double *e)
-{
- double Q;
- int eindex;
-
- Q = e[0];
- for (eindex = 1; eindex < elen; eindex++) {
- Q += e[eindex];
- }
- return Q;
-}
-
-/* orient2d() Adaptive exact 2D orientation test. Robust.
- *
- * Return a positive value if the points pa, pb, and pc occur
- * in counterclockwise order; a negative value if they occur
- * in clockwise order; and zero if they are collinear. The
- * result is also a rough approximation of twice the signed
- * area of the triangle defined by the three points.
- *
- * This uses exact arithmetic to ensure a correct answer. The
- * result returned is the determinant of a matrix.
- * This determinant is computed adaptively, in the sense that exact
- * arithmetic is used only to the degree it is needed to ensure that the
- * returned value has the correct sign. Hence, orient2d() is usually quite
- * fast, but will run more slowly when the input points are collinear or
- * nearly so.
- */
-
-static double orient2dadapt(const double *pa, const double *pb, const double *pc, double detsum)
-{
- INEXACT double acx, acy, bcx, bcy;
- double acxtail, acytail, bcxtail, bcytail;
- INEXACT double detleft, detright;
- double detlefttail, detrighttail;
- double det, errbound;
- double B[4], C1[8], C2[12], D[16];
- INEXACT double B3;
- int C1length, C2length, Dlength;
- double u[4];
- INEXACT double u3;
- INEXACT double s1, t1;
- double s0, t0;
-
- INEXACT double bvirt;
- double avirt, bround, around;
- INEXACT double c;
- INEXACT double abig;
- double ahi, alo, bhi, blo;
- double err1, err2, err3;
- INEXACT double _i, _j;
- double _0;
-
- acx = (double)(pa[0] - pc[0]);
- bcx = (double)(pb[0] - pc[0]);
- acy = (double)(pa[1] - pc[1]);
- bcy = (double)(pb[1] - pc[1]);
-
- Two_Product(acx, bcy, detleft, detlefttail);
- Two_Product(acy, bcx, detright, detrighttail);
-
- Two_Two_Diff(detleft, detlefttail, detright, detrighttail, B3, B[2], B[1], B[0]);
- B[3] = B3;
-
- det = estimate(4, B);
- errbound = ccwerrboundB * detsum;
- if ((det >= errbound) || (-det >= errbound)) {
- return det;
- }
-
- Two_Diff_Tail(pa[0], pc[0], acx, acxtail);
- Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail);
- Two_Diff_Tail(pa[1], pc[1], acy, acytail);
- Two_Diff_Tail(pb[1], pc[1], bcy, bcytail);
-
- if ((acxtail == 0.0) && (acytail == 0.0) && (bcxtail == 0.0) && (bcytail == 0.0)) {
- return det;
- }
-
- errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det);
- det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
- if ((det >= errbound) || (-det >= errbound)) {
- return det;
- }
-
- Two_Product(acxtail, bcy, s1, s0);
- Two_Product(acytail, bcx, t1, t0);
- Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1);
-
- Two_Product(acx, bcytail, s1, s0);
- Two_Product(acy, bcxtail, t1, t0);
- Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2);
-
- Two_Product(acxtail, bcytail, s1, s0);
- Two_Product(acytail, bcxtail, t1, t0);
- Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D);
-
- return (D[Dlength - 1]);
-}
-
-static double orient2d(const double *pa, const double *pb, const double *pc)
-{
- double detleft, detright, det;
- double detsum, errbound;
-
- detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]);
- detright = (pa[1] - pc[1]) * (pb[0] - pc[0]);
- det = detleft - detright;
-
- if (detleft > 0.0) {
- if (detright <= 0.0) {
- return det;
- }
- detsum = detleft + detright;
- }
- else if (detleft < 0.0) {
- if (detright >= 0.0) {
- return det;
- }
- detsum = -detleft - detright;
- }
- else {
- return det;
- }
-
- errbound = ccwerrboundA * detsum;
- if ((det >= errbound) || (-det >= errbound)) {
- return det;
- }
-
- return orient2dadapt(pa, pb, pc, detsum);
-}
-
-/* incircle() Adaptive exact 2D incircle test. Robust.
- *
- * Return a positive value if the point pd lies inside the
- * circle passing through pa, pb, and pc; a negative value if
- * it lies outside; and zero if the four points are cocircular.
- * The points pa, pb, and pc must be in counterclockwise
- * order, or the sign of the result will be reversed.
- *
- * This uses exact arithmetic to ensure a correct answer.
- * The result returned is the determinant of a matrix.
- * This determinant is computed adaptively, in the sense that exact
- * arithmetic is used only to the degree it is needed to ensure that the
- * returned value has the correct sign. Hence, incircle() is usually quite
- * fast, but will run more slowly when the input points are cocircular or
- * nearly so.
- *
- * This function is allowed to be long for two reasons. Firstly, it was taken
- * from an external source and only slightly adapted, and keeping its original
- * form will make integration of upstream changes easier. Secondly, it is very
- * sensitive to floating point errors, and refactoring may break it in subtle
- * and hard to detect ways.
- * NOLINTNEXTLINE: readability-function-size */
-static double incircleadapt(
- const double *pa, const double *pb, const double *pc, const double *pd, double permanent)
-{
- INEXACT double adx, bdx, cdx, ady, bdy, cdy;
- double det, errbound;
-
- INEXACT double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1;
- double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0;
- double bc[4], ca[4], ab[4];
- INEXACT double bc3, ca3, ab3;
- double axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32];
- int axbclen, axxbclen, aybclen, ayybclen, alen;
- double bxca[8], bxxca[16], byca[8], byyca[16], bdet[32];
- int bxcalen, bxxcalen, bycalen, byycalen, blen;
- double cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32];
- int cxablen, cxxablen, cyablen, cyyablen, clen;
- double abdet[64];
- int ablen;
- double fin1[1152], fin2[1152];
- double *finnow, *finother, *finswap;
- int finlength;
-
- double adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail;
- INEXACT double adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1;
- double adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0;
- double aa[4], bb[4], cc[4];
- INEXACT double aa3, bb3, cc3;
- INEXACT double ti1, tj1;
- double ti0, tj0;
- double u[4], v[4];
- INEXACT double u3, v3;
- double temp8[8], temp16a[16], temp16b[16], temp16c[16];
- double temp32a[32], temp32b[32], temp48[48], temp64[64];
- int temp8len, temp16alen, temp16blen, temp16clen;
- int temp32alen, temp32blen, temp48len, temp64len;
- double axtbb[8], axtcc[8], aytbb[8], aytcc[8];
- int axtbblen, axtcclen, aytbblen, aytcclen;
- double bxtaa[8], bxtcc[8], bytaa[8], bytcc[8];
- int bxtaalen, bxtcclen, bytaalen, bytcclen;
- double cxtaa[8], cxtbb[8], cytaa[8], cytbb[8];
- int cxtaalen, cxtbblen, cytaalen, cytbblen;
- double axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8];
- int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen;
- double axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16];
- int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen;
- double axtbctt[8], aytbctt[8], bxtcatt[8];
- double bytcatt[8], cxtabtt[8], cytabtt[8];
- int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen;
- double abt[8], bct[8], cat[8];
- int abtlen, bctlen, catlen;
- double abtt[4], bctt[4], catt[4];
- int abttlen, bcttlen, cattlen;
- INEXACT double abtt3, bctt3, catt3;
- double negate;
-
- INEXACT double bvirt;
- double avirt, bround, around;
- INEXACT double c;
- INEXACT double abig;
- double ahi, alo, bhi, blo;
- double err1, err2, err3;
- INEXACT double _i, _j;
- double _0;
-
- adx = (double)(pa[0] - pd[0]);
- bdx = (double)(pb[0] - pd[0]);
- cdx = (double)(pc[0] - pd[0]);
- ady = (double)(pa[1] - pd[1]);
- bdy = (double)(pb[1] - pd[1]);
- cdy = (double)(pc[1] - pd[1]);
-
- Two_Product(bdx, cdy, bdxcdy1, bdxcdy0);
- Two_Product(cdx, bdy, cdxbdy1, cdxbdy0);
- Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]);
- bc[3] = bc3;
- axbclen = scale_expansion_zeroelim(4, bc, adx, axbc);
- axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc);
- aybclen = scale_expansion_zeroelim(4, bc, ady, aybc);
- ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc);
- alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet);
-
- Two_Product(cdx, ady, cdxady1, cdxady0);
- Two_Product(adx, cdy, adxcdy1, adxcdy0);
- Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]);
- ca[3] = ca3;
- bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca);
- bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca);
- bycalen = scale_expansion_zeroelim(4, ca, bdy, byca);
- byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca);
- blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet);
-
- Two_Product(adx, bdy, adxbdy1, adxbdy0);
- Two_Product(bdx, ady, bdxady1, bdxady0);
- Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]);
- ab[3] = ab3;
- cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab);
- cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab);
- cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab);
- cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab);
- clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet);
-
- ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
- finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1);
-
- det = estimate(finlength, fin1);
- errbound = iccerrboundB * permanent;
- if ((det >= errbound) || (-det >= errbound)) {
- return det;
- }
-
- Two_Diff_Tail(pa[0], pd[0], adx, adxtail);
- Two_Diff_Tail(pa[1], pd[1], ady, adytail);
- Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail);
- Two_Diff_Tail(pb[1], pd[1], bdy, bdytail);
- Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail);
- Two_Diff_Tail(pc[1], pd[1], cdy, cdytail);
- if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) && (adytail == 0.0) &&
- (bdytail == 0.0) && (cdytail == 0.0)) {
- return det;
- }
-
- errbound = iccerrboundC * permanent + resulterrbound * Absolute(det);
- det += ((adx * adx + ady * ady) *
- ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) +
- 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) +
- ((bdx * bdx + bdy * bdy) *
- ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) +
- 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) +
- ((cdx * cdx + cdy * cdy) *
- ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) +
- 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx));
- if ((det >= errbound) || (-det >= errbound)) {
- return det;
- }
-
- finnow = fin1;
- finother = fin2;
-
- if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) {
- Square(adx, adxadx1, adxadx0);
- Square(ady, adyady1, adyady0);
- Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]);
- aa[3] = aa3;
- }
- if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) {
- Square(bdx, bdxbdx1, bdxbdx0);
- Square(bdy, bdybdy1, bdybdy0);
- Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]);
- bb[3] = bb3;
- }
- if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) {
- Square(cdx, cdxcdx1, cdxcdx0);
- Square(cdy, cdycdy1, cdycdy0);
- Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]);
- cc[3] = cc3;
- }
-
- if (adxtail != 0.0) {
- axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc);
- temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, temp16a);
-
- axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc);
- temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b);
-
- axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb);
- temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (adytail != 0.0) {
- aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc);
- temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, temp16a);
-
- aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb);
- temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b);
-
- aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc);
- temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (bdxtail != 0.0) {
- bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca);
- temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, temp16a);
-
- bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa);
- temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b);
-
- bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc);
- temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (bdytail != 0.0) {
- bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca);
- temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, temp16a);
-
- bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc);
- temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b);
-
- bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa);
- temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (cdxtail != 0.0) {
- cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab);
- temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, temp16a);
-
- cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb);
- temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b);
-
- cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa);
- temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (cdytail != 0.0) {
- cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab);
- temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, temp16a);
-
- cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa);
- temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b);
-
- cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb);
- temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c);
-
- temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
-
- if ((adxtail != 0.0) || (adytail != 0.0)) {
- if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) {
- Two_Product(bdxtail, cdy, ti1, ti0);
- Two_Product(bdx, cdytail, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- negate = -bdy;
- Two_Product(cdxtail, negate, ti1, ti0);
- negate = -bdytail;
- Two_Product(cdx, negate, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
- v[3] = v3;
- bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct);
-
- Two_Product(bdxtail, cdytail, ti1, ti0);
- Two_Product(cdxtail, bdytail, tj1, tj0);
- Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]);
- bctt[3] = bctt3;
- bcttlen = 4;
- }
- else {
- bct[0] = 0.0;
- bctlen = 1;
- bctt[0] = 0.0;
- bcttlen = 1;
- }
-
- if (adxtail != 0.0) {
- temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a);
- axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct);
- temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- if (bdytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (cdytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
-
- temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, temp32a);
- axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt);
- temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, temp16a);
- temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (adytail != 0.0) {
- temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a);
- aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct);
- temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
-
- temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, temp32a);
- aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt);
- temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, temp16a);
- temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- }
- if ((bdxtail != 0.0) || (bdytail != 0.0)) {
- if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) {
- Two_Product(cdxtail, ady, ti1, ti0);
- Two_Product(cdx, adytail, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- negate = -cdy;
- Two_Product(adxtail, negate, ti1, ti0);
- negate = -cdytail;
- Two_Product(adx, negate, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
- v[3] = v3;
- catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat);
-
- Two_Product(cdxtail, adytail, ti1, ti0);
- Two_Product(adxtail, cdytail, tj1, tj0);
- Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]);
- catt[3] = catt3;
- cattlen = 4;
- }
- else {
- cat[0] = 0.0;
- catlen = 1;
- catt[0] = 0.0;
- cattlen = 1;
- }
-
- if (bdxtail != 0.0) {
- temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a);
- bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat);
- temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- if (cdytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (adytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
-
- temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, temp32a);
- bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt);
- temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, temp16a);
- temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (bdytail != 0.0) {
- temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a);
- bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat);
- temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
-
- temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, temp32a);
- bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt);
- temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, temp16a);
- temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- }
- if ((cdxtail != 0.0) || (cdytail != 0.0)) {
- if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) {
- Two_Product(adxtail, bdy, ti1, ti0);
- Two_Product(adx, bdytail, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
- u[3] = u3;
- negate = -ady;
- Two_Product(bdxtail, negate, ti1, ti0);
- negate = -adytail;
- Two_Product(bdx, negate, tj1, tj0);
- Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
- v[3] = v3;
- abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt);
-
- Two_Product(adxtail, bdytail, ti1, ti0);
- Two_Product(bdxtail, adytail, tj1, tj0);
- Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]);
- abtt[3] = abtt3;
- abttlen = 4;
- }
- else {
- abt[0] = 0.0;
- abtlen = 1;
- abtt[0] = 0.0;
- abttlen = 1;
- }
-
- if (cdxtail != 0.0) {
- temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a);
- cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt);
- temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- if (adytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (bdytail != 0.0) {
- temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8);
- temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, temp16a);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
-
- temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, temp32a);
- cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt);
- temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, temp16a);
- temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- if (cdytail != 0.0) {
- temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a);
- cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt);
- temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, temp32a);
- temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
-
- temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, temp32a);
- cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt);
- temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, temp16a);
- temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, temp16b);
- temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
- temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
- finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
- finswap = finnow;
- finnow = finother;
- finother = finswap;
- }
- }
-
- return finnow[finlength - 1];
-}
-
-static double incircle(const double *pa, const double *pb, const double *pc, const double *pd)
-{
- double adx, bdx, cdx, ady, bdy, cdy;
- double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
- double alift, blift, clift;
- double det;
- double permanent, errbound;
-
- adx = pa[0] - pd[0];
- bdx = pb[0] - pd[0];
- cdx = pc[0] - pd[0];
- ady = pa[1] - pd[1];
- bdy = pb[1] - pd[1];
- cdy = pc[1] - pd[1];
-
- bdxcdy = bdx * cdy;
- cdxbdy = cdx * bdy;
- alift = adx * adx + ady * ady;
-
- cdxady = cdx * ady;
- adxcdy = adx * cdy;
- blift = bdx * bdx + bdy * bdy;
-
- adxbdy = adx * bdy;
- bdxady = bdx * ady;
- clift = cdx * cdx + cdy * cdy;
-
- det = alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) + clift * (adxbdy - bdxady);
-
- permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift +
- (Absolute(cdxady) + Absolute(adxcdy)) * blift +
- (Absolute(adxbdy) + Absolute(bdxady)) * clift;
- errbound = iccerrboundA * permanent;
- if ((det > errbound) || (-det > errbound)) {
- return det;
- }
-
- return incircleadapt(pa, pb, pc, pd, permanent);
-}
diff --git a/source/blender/blenlib/intern/delaunay_2d.cc b/source/blender/blenlib/intern/delaunay_2d.cc
new file mode 100644
index 00000000000..7b0f6a658ce
--- /dev/null
+++ b/source/blender/blenlib/intern/delaunay_2d.cc
@@ -0,0 +1,2500 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include "BLI_array.hh"
+#include "BLI_double2.hh"
+#include "BLI_linklist.h"
+#include "BLI_math_boolean.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mpq2.hh"
+#include "BLI_vector.hh"
+
+#include "BLI_delaunay_2d.h"
+
+namespace blender::meshintersect {
+
+/* Throughout this file, template argument T will be an
+ * arithmetic-like type, like float, double, or mpq_class. */
+
+template<typename T> T math_abs(const T v)
+{
+ return (v < 0) ? -v : v;
+}
+
+#ifdef WITH_GMP
+template<> mpq_class math_abs<mpq_class>(const mpq_class v)
+{
+ return abs(v);
+}
+#endif
+
+template<> double math_abs<double>(const double v)
+{
+ return fabs(v);
+}
+
+template<typename T> double math_to_double(const T UNUSED(v))
+{
+ BLI_assert(false); /* Need implementation for other type. */
+ return 0.0;
+}
+
+#ifdef WITH_GMP
+template<> double math_to_double<mpq_class>(const mpq_class v)
+{
+ return v.get_d();
+}
+#endif
+
+template<> double math_to_double<double>(const double v)
+{
+ return v;
+}
+
+/**
+ * Define a templated 2D arrangement of vertices, edges, and faces.
+ * The #SymEdge data structure is the basis for a structure that allows
+ * easy traversal to neighboring (by topology) geometric elements.
+ * Each of #CDTVert, #CDTEdge, and #CDTFace have an input_id linked list,
+ * whose nodes contain integers that keep track of which input verts, edges,
+ * and faces, respectively, that the element was derived from.
+ *
+ * While this could be cleaned up some, it is usable by other routines in Blender
+ * that need to keep track of a 2D arrangement, with topology.
+ */
+template<typename Arith_t> struct CDTVert;
+template<typename Arith_t> struct CDTEdge;
+template<typename Arith_t> struct CDTFace;
+
+template<typename Arith_t> struct SymEdge {
+ /** Next #SymEdge in face, doing CCW traversal of face. */
+ SymEdge<Arith_t> *next{nullptr};
+ /** Next #SymEdge CCW around vert. */
+ SymEdge<Arith_t> *rot{nullptr};
+ /** Vert at origin. */
+ CDTVert<Arith_t> *vert{nullptr};
+ /** Un-directed edge this is for. */
+ CDTEdge<Arith_t> *edge{nullptr};
+ /** Face on left side. */
+ CDTFace<Arith_t> *face{nullptr};
+
+ SymEdge() = default;
+};
+
+/**
+ * Return other #SymEdge for same #CDTEdge as \a se.
+ */
+template<typename T> inline SymEdge<T> *sym(const SymEdge<T> *se)
+{
+ return se->next->rot;
+}
+
+/** Return #SymEdge whose next is \a se. */
+template<typename T> inline SymEdge<T> *prev(const SymEdge<T> *se)
+{
+ return se->rot->next->rot;
+}
+
+template<typename Arith_t> struct CDTVert {
+ /** Coordinate. */
+ vec2<Arith_t> co;
+ /** Some edge attached to it. */
+ SymEdge<Arith_t> *symedge{nullptr};
+ /** List of corresponding vertex input ids. */
+ LinkNode *input_ids{nullptr};
+ /** Index into array that #CDTArrangement keeps. */
+ int index{-1};
+ /** Index of a CDTVert that this has merged to. -1 if no merge. */
+ int merge_to_index{-1};
+ /** Used by algorithms operating on CDT structures. */
+ int visit_index{0};
+
+ CDTVert() = default;
+ explicit CDTVert(const vec2<Arith_t> &pt);
+};
+
+template<typename Arith_t> struct CDTEdge {
+ /** List of input edge ids that this is part of. */
+ LinkNode *input_ids{nullptr};
+ /** The directed edges for this edge. */
+ SymEdge<Arith_t> symedges[2]{SymEdge<Arith_t>(), SymEdge<Arith_t>()};
+
+ CDTEdge() = default;
+};
+
+template<typename Arith_t> struct CDTFace {
+ /** A symedge in face; only used during output, so only valid then. */
+ SymEdge<Arith_t> *symedge{nullptr};
+ /** List of input face ids that this is part of. */
+ LinkNode *input_ids{nullptr};
+ /** Used by algorithms operating on CDT structures. */
+ int visit_index{0};
+ /** Marks this face no longer used. */
+ bool deleted{false};
+
+ CDTFace() = default;
+};
+
+template<typename Arith_t> struct CDTArrangement {
+ /* The arrangement owns the memory pointed to by the pointers in these vectors.
+ * They are pointers instead of actual structures because these vectors may be resized and
+ * other elements refer to the elements by pointer. */
+
+ /** The verts. Some may be merged to others (see their merge_to_index). */
+ Vector<CDTVert<Arith_t> *> verts;
+ /** The edges. Some may be deleted (SymEdge next and rot pointers are null). */
+ Vector<CDTEdge<Arith_t> *> edges;
+ /** The faces. Some may be deleted (see their delete member). */
+ Vector<CDTFace<Arith_t> *> faces;
+ /** Which CDTFace is the outer face. */
+ CDTFace<Arith_t> *outer_face{nullptr};
+
+ CDTArrangement() = default;
+ ~CDTArrangement();
+
+ /** Hint to how much space to reserve in the Vectors of the arrangement,
+ * based on these counts of input elements. */
+ void reserve(int num_verts, int num_edges, int num_faces);
+
+ /**
+ * Add a new vertex to the arrangement, with the given 2D coordinate.
+ * It will not be connected to anything yet.
+ */
+ CDTVert<Arith_t> *add_vert(const vec2<Arith_t> &pt);
+
+ /**
+ * Add an edge from v1 to v2. The edge will have a left face and a right face,
+ * specified by \a fleft and \a fright. The edge will not be connected to anything yet.
+ * If the vertices do not yet have a #SymEdge pointer,
+ * their pointer is set to the #SymEdge in this new edge.
+ */
+ CDTEdge<Arith_t> *add_edge(CDTVert<Arith_t> *v1,
+ CDTVert<Arith_t> *v2,
+ CDTFace<Arith_t> *fleft,
+ CDTFace<Arith_t> *fright);
+
+ /**
+ * Add a new face. It is disconnected until an add_edge makes it the
+ * left or right face of an edge.
+ */
+ CDTFace<Arith_t> *add_face();
+
+ /** Make a new edge from v to se->vert, splicing it in. */
+ CDTEdge<Arith_t> *add_vert_to_symedge_edge(CDTVert<Arith_t> *v, SymEdge<Arith_t> *se);
+
+ /**
+ * Assuming s1 and s2 are both #SymEdge's in a face with > 3 sides and one is not the next of the
+ * other, Add an edge from `s1->v` to `s2->v`, splitting the face in two. The original face will
+ * be the one that s1 has as left face, and a new face will be added and made s2 and its
+ * next-cycle's left face.
+ */
+ CDTEdge<Arith_t> *add_diagonal(SymEdge<Arith_t> *s1, SymEdge<Arith_t> *s2);
+
+ /**
+ * Connect the verts of se1 and se2, assuming that currently those two #SymEdge's are on the
+ * outer boundary (have face == outer_face) of two components that are isolated from each other.
+ */
+ CDTEdge<Arith_t> *connect_separate_parts(SymEdge<Arith_t> *se1, SymEdge<Arith_t> *se2);
+
+ /**
+ * Split se at fraction lambda, and return the new #CDTEdge that is the new second half.
+ * Copy the edge input_ids into the new one.
+ */
+ CDTEdge<Arith_t> *split_edge(SymEdge<Arith_t> *se, Arith_t lambda);
+
+ /**
+ * Delete an edge. The new combined face on either side of the deleted edge will be the one that
+ * was e's face. There will now be an unused face, which will be marked deleted, and an unused
+ * #CDTEdge, marked by setting the next and rot pointers of its #SymEdge's to #nullptr.
+ */
+ void delete_edge(SymEdge<Arith_t> *se);
+
+ /**
+ * If the vertex with index i in the vert array has not been merge, return it.
+ * Else return the one that it has merged to.
+ */
+ CDTVert<Arith_t> *get_vert_resolve_merge(int i)
+ {
+ CDTVert<Arith_t> *v = this->verts[i];
+ if (v->merge_to_index != -1) {
+ v = this->verts[v->merge_to_index];
+ }
+ return v;
+ }
+};
+
+template<typename T> class CDT_state {
+ public:
+ CDTArrangement<T> cdt;
+ /** How many verts were in input (will be first in vert_array). */
+ int input_vert_tot;
+ /** Used for visiting things without having to initialized their visit fields. */
+ int visit_count;
+ /**
+ * Edge ids for face start with this, and each face gets this much index space
+ * to encode positions within the face.
+ */
+ int face_edge_offset;
+ /** How close before coords considered equal. */
+ T epsilon;
+
+ explicit CDT_state(int num_input_verts, int num_input_edges, int num_input_faces, T epsilon);
+ ~CDT_state()
+ {
+ }
+};
+
+template<typename T> CDTArrangement<T>::~CDTArrangement()
+{
+ for (int i : this->verts.index_range()) {
+ CDTVert<T> *v = this->verts[i];
+ BLI_linklist_free(v->input_ids, nullptr);
+ delete v;
+ this->verts[i] = nullptr;
+ }
+ for (int i : this->edges.index_range()) {
+ CDTEdge<T> *e = this->edges[i];
+ BLI_linklist_free(e->input_ids, nullptr);
+ delete e;
+ this->edges[i] = nullptr;
+ }
+ for (int i : this->faces.index_range()) {
+ CDTFace<T> *f = this->faces[i];
+ BLI_linklist_free(f->input_ids, nullptr);
+ delete f;
+ this->faces[i] = nullptr;
+ }
+}
+
+#define DEBUG_CDT
+#ifdef DEBUG_CDT
+/* Some functions to aid in debugging. */
+template<typename T> std::string vertname(const CDTVert<T> *v)
+{
+ std::stringstream ss;
+ ss << "[" << v->index << "]";
+ return ss.str();
+}
+
+/* Abbreviated pointer value is easier to read in dumps. */
+static std::string trunc_ptr(const void *p)
+{
+ constexpr int TRUNC_PTR_MASK = 0xFFFF;
+ std::stringstream ss;
+ ss << std::hex << (POINTER_AS_INT(p) & TRUNC_PTR_MASK);
+ return ss.str();
+}
+
+template<typename T> std::string sename(const SymEdge<T> *se)
+{
+ std::stringstream ss;
+ ss << "{" << trunc_ptr(se) << "}";
+ return ss.str();
+}
+
+template<typename T> std::ostream &operator<<(std::ostream &os, const SymEdge<T> &se)
+{
+ if (se.next) {
+ os << vertname(se.vert) << "(" << se.vert->co << "->" << se.next->vert->co << ")"
+ << vertname(se.next->vert);
+ }
+ else {
+ os << vertname(se.vert) << "(" << se.vert->co << "->NULL)";
+ }
+ return os;
+}
+
+template<typename T> std::ostream &operator<<(std::ostream &os, const SymEdge<T> *se)
+{
+ os << *se;
+ return os;
+}
+
+template<typename T> std::string short_se_dump(const SymEdge<T> *se)
+{
+ if (se == nullptr) {
+ return std::string("NULL");
+ }
+ return vertname(se->vert) +
+ (se->next == nullptr ? std::string("[NULL]") : vertname(se->next->vert));
+}
+
+template<typename T> std::ostream &operator<<(std::ostream &os, const CDT_state<T> &cdt_state)
+{
+ os << "\nCDT\n\nVERTS\n";
+ for (const CDTVert<T> *v : cdt_state.cdt.verts) {
+ os << vertname(v) << " " << trunc_ptr(v) << ": " << v->co
+ << " symedge=" << trunc_ptr(v->symedge);
+ if (v->merge_to_index == -1) {
+ os << "\n";
+ }
+ else {
+ os << " merge to " << vertname(cdt_state.cdt.verts[v->merge_to_index]) << "\n";
+ }
+ const SymEdge<T> *se = v->symedge;
+ int cnt = 0;
+ constexpr int print_count_limit = 25;
+ if (se) {
+ os << " edges out:\n";
+ do {
+ if (se->next == NULL) {
+ os << " [NULL] next/rot symedge, se=" << trunc_ptr(se) << "\n";
+ break;
+ }
+ if (se->next->next == NULL) {
+ os << " [NULL] next-next/rot symedge, se=" << trunc_ptr(se) << "\n";
+ break;
+ }
+ const CDTVert<T> *vother = sym(se)->vert;
+ os << " " << vertname(vother) << "(e=" << trunc_ptr(se->edge)
+ << ", se=" << trunc_ptr(se) << ")\n";
+ se = se->rot;
+ cnt++;
+ } while (se != v->symedge && cnt < print_count_limit);
+ os << "\n";
+ }
+ }
+ os << "\nEDGES\n";
+ for (const CDTEdge<T> *e : cdt_state.cdt.edges) {
+ if (e->symedges[0].next == nullptr) {
+ continue;
+ }
+ os << trunc_ptr(&e) << ":\n";
+ for (int i = 0; i < 2; ++i) {
+ const SymEdge<T> *se = &e->symedges[i];
+ os << " se[" << i << "] @" << trunc_ptr(se) << " next=" << trunc_ptr(se->next)
+ << ", rot=" << trunc_ptr(se->rot) << ", vert=" << trunc_ptr(se->vert) << " "
+ << vertname(se->vert) << " " << se->vert->co << ", edge=" << trunc_ptr(se->edge)
+ << ", face=" << trunc_ptr(se->face) << "\n";
+ }
+ }
+ os << "\nFACES\n";
+ os << "outer_face=" << trunc_ptr(cdt_state.cdt.outer_face) << "\n";
+ /* Only after prepare_output do faces have non-null symedges. */
+ if (cdt_state.cdt.outer_face->symedge != nullptr) {
+ for (const CDTFace<T> *f : cdt_state.cdt.faces) {
+ if (!f->deleted) {
+ os << trunc_ptr(f) << " symedge=" << trunc_ptr(f->symedge) << "\n";
+ }
+ }
+ }
+ return os;
+}
+
+template<typename T> void cdt_draw(const std::string &label, const CDTArrangement<T> &cdt)
+{
+ static bool append = false; /* Will be set to true after first call. */
+
+/* Would like to use #BKE_tempdir_base() here, but that brings in dependence on kernel library.
+ * This is just for developer debugging anyway, and should never be called in production Blender.
+ */
+# ifdef _WIN32
+ const char *drawfile = "./debug_draw.html";
+# else
+ const char *drawfile = "/tmp/debug_draw.html";
+# endif
+ constexpr int max_draw_width = 1800;
+ constexpr int max_draw_height = 1600;
+ constexpr double margin_expand = 0.05;
+ constexpr int thin_line = 1;
+ constexpr int thick_line = 4;
+ constexpr int vert_radius = 3;
+ constexpr bool draw_vert_labels = true;
+ constexpr bool draw_edge_labels = false;
+
+ if (cdt.verts.size() == 0) {
+ return;
+ }
+ vec2<double> vmin(DBL_MAX, DBL_MAX);
+ vec2<double> vmax(-DBL_MAX, -DBL_MAX);
+ for (const CDTVert<T> *v : cdt.verts) {
+ for (int i = 0; i < 2; ++i) {
+ double dvi = math_to_double(v->co[i]);
+ if (dvi < vmin[i]) {
+ vmin[i] = dvi;
+ }
+ if (dvi > vmax[i]) {
+ vmax[i] = dvi;
+ }
+ }
+ }
+ double draw_margin = ((vmax.x - vmin.x) + (vmax.y - vmin.y)) * margin_expand;
+ double minx = vmin.x - draw_margin;
+ double maxx = vmax.x + draw_margin;
+ double miny = vmin.y - draw_margin;
+ double maxy = vmax.y + draw_margin;
+
+ double width = maxx - minx;
+ double height = maxy - miny;
+ double aspect = height / width;
+ int view_width = max_draw_width;
+ int view_height = static_cast<int>(view_width * aspect);
+ if (view_height > max_draw_height) {
+ view_height = max_draw_height;
+ view_width = static_cast<int>(view_height / aspect);
+ }
+ double scale = view_width / width;
+
+# define SX(x) ((math_to_double(x) - minx) * scale)
+# define SY(y) ((maxy - math_to_double(y)) * scale)
+
+ std::ofstream f;
+ if (append) {
+ f.open(drawfile, std::ios_base::app);
+ }
+ else {
+ f.open(drawfile);
+ }
+ if (!f) {
+ std::cout << "Could not open file " << drawfile << "\n";
+ return;
+ }
+
+ f << "<div>" << label << "</div>\n<div>\n"
+ << "<svg version=\"1.1\" "
+ "xmlns=\"http://www.w3.org/2000/svg\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
+ "xml:space=\"preserve\"\n"
+ << "width=\"" << view_width << "\" height=\"" << view_height << "\">n";
+
+ for (const CDTEdge<T> *e : cdt.edges) {
+ if (e->symedges[0].next == nullptr) {
+ continue;
+ }
+ const CDTVert<T> *u = e->symedges[0].vert;
+ const CDTVert<T> *v = e->symedges[1].vert;
+ const vec2<T> &uco = u->co;
+ const vec2<T> &vco = v->co;
+ int strokew = e->input_ids == nullptr ? thin_line : thick_line;
+ f << "<line fill=\"none\" stroke=\"black\" stroke-width=\"" << strokew << "\" x1=\""
+ << SX(uco[0]) << "\" y1=\"" << SY(uco[1]) << "\" x2=\"" << SX(vco[0]) << "\" y2=\""
+ << SY(vco[1]) << "\">\n";
+ f << " <title>" << vertname(u) << vertname(v) << "</title>\n";
+ f << "</line>\n";
+ if (draw_edge_labels) {
+ f << "<text x=\"" << SX((uco[0] + vco[0]) / 2) << "\" y=\"" << SY((uco[1] + vco[1]) / 2)
+ << "\" font-size=\"small\">";
+ f << vertname(u) << vertname(v) << sename(&e->symedges[0]) << sename(&e->symedges[1])
+ << "</text>\n";
+ }
+ }
+
+ int i = 0;
+ for (const CDTVert<T> *v : cdt.verts) {
+ f << "<circle fill=\"black\" cx=\"" << SX(v->co[0]) << "\" cy=\"" << SY(v->co[1]) << "\" r=\""
+ << vert_radius << "\">\n";
+ f << " <title>[" << i << "]" << v->co << "</title>\n";
+ f << "</circle>\n";
+ if (draw_vert_labels) {
+ f << "<text x=\"" << SX(v->co[0]) + vert_radius << "\" y=\"" << SY(v->co[1]) - vert_radius
+ << "\" font-size=\"small\">[" << i << "]</text>\n";
+ }
+ ++i;
+ }
+
+ append = true;
+# undef SX
+# undef SY
+}
+#endif
+
+/**
+ * Return true if `a -- b -- c` are in that order, assuming they are on a straight line according
+ * to #orient2d and we know the order is either `abc` or `bac`.
+ * This means `ab . ac` and `bc . ac` must both be non-negative.
+ */
+template<typename T> bool in_line(const vec2<T> &a, const vec2<T> &b, const vec2<T> &c)
+{
+ vec2<T> ab = b - a;
+ vec2<T> bc = c - b;
+ vec2<T> ac = c - a;
+ if (vec2<T>::dot(ab, ac) < 0) {
+ return false;
+ }
+ return vec2<T>::dot(bc, ac) >= 0;
+}
+
+template<typename T> CDTVert<T>::CDTVert(const vec2<T> &pt)
+{
+ this->co = pt;
+ this->input_ids = nullptr;
+ this->symedge = nullptr;
+ this->index = -1;
+ this->merge_to_index = -1;
+ this->visit_index = 0;
+}
+
+template<typename T> CDTVert<T> *CDTArrangement<T>::add_vert(const vec2<T> &pt)
+{
+ CDTVert<T> *v = new CDTVert<T>(pt);
+ int index = this->verts.append_and_get_index(v);
+ v->index = index;
+ return v;
+}
+
+template<typename T>
+CDTEdge<T> *CDTArrangement<T>::add_edge(CDTVert<T> *v1,
+ CDTVert<T> *v2,
+ CDTFace<T> *fleft,
+ CDTFace<T> *fright)
+{
+ CDTEdge<T> *e = new CDTEdge<T>();
+ this->edges.append(e);
+ SymEdge<T> *se = &e->symedges[0];
+ SymEdge<T> *sesym = &e->symedges[1];
+ se->edge = sesym->edge = e;
+ se->face = fleft;
+ sesym->face = fright;
+ se->vert = v1;
+ if (v1->symedge == nullptr) {
+ v1->symedge = se;
+ }
+ sesym->vert = v2;
+ if (v2->symedge == nullptr) {
+ v2->symedge = sesym;
+ }
+ se->next = sesym->next = se->rot = sesym->rot = nullptr;
+ return e;
+}
+
+template<typename T> CDTFace<T> *CDTArrangement<T>::add_face()
+{
+ CDTFace<T> *f = new CDTFace<T>();
+ this->faces.append(f);
+ return f;
+}
+
+template<typename T> void CDTArrangement<T>::reserve(int num_verts, int num_edges, int num_faces)
+{
+ /* These reserves are just guesses; OK if they aren't exactly right since vectors will resize. */
+ this->verts.reserve(2 * num_verts);
+ this->edges.reserve(3 * num_verts + 2 * num_edges + 3 * 2 * num_faces);
+ this->faces.reserve(2 * num_verts + 2 * num_edges + 2 * num_faces);
+}
+
+template<typename T>
+CDT_state<T>::CDT_state(int num_input_verts, int num_input_edges, int num_input_faces, T epsilon)
+{
+ this->input_vert_tot = num_input_verts;
+ this->cdt.reserve(num_input_verts, num_input_edges, num_input_faces);
+ this->cdt.outer_face = this->cdt.add_face();
+ this->epsilon = epsilon;
+ this->visit_count = 0;
+}
+
+static bool id_in_list(const LinkNode *id_list, int id)
+{
+ const LinkNode *ln;
+
+ for (ln = id_list; ln != nullptr; ln = ln->next) {
+ if (POINTER_AS_INT(ln->link) == id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Is any id in (range_start, range_start+1, ... , range_end) in id_list? */
+static bool id_range_in_list(const LinkNode *id_list, int range_start, int range_end)
+{
+ const LinkNode *ln;
+ int id;
+
+ for (ln = id_list; ln != nullptr; ln = ln->next) {
+ id = POINTER_AS_INT(ln->link);
+ if (id >= range_start && id <= range_end) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void add_to_input_ids(LinkNode **dst, int input_id)
+{
+ if (!id_in_list(*dst, input_id)) {
+ BLI_linklist_prepend(dst, POINTER_FROM_INT(input_id));
+ }
+}
+
+static void add_list_to_input_ids(LinkNode **dst, const LinkNode *src)
+{
+ const LinkNode *ln;
+
+ for (ln = src; ln != nullptr; ln = ln->next) {
+ add_to_input_ids(dst, POINTER_AS_INT(ln->link));
+ }
+}
+
+template<typename T> inline bool is_border_edge(const CDTEdge<T> *e, const CDT_state<T> *cdt)
+{
+ return e->symedges[0].face == cdt->outer_face || e->symedges[1].face == cdt->outer_face;
+}
+
+template<typename T> inline bool is_constrained_edge(const CDTEdge<T> *e)
+{
+ return e->input_ids != NULL;
+}
+
+template<typename T> inline bool is_deleted_edge(const CDTEdge<T> *e)
+{
+ return e->symedges[0].next == NULL;
+}
+
+template<typename T> inline bool is_original_vert(const CDTVert<T> *v, CDT_state<T> *cdt)
+{
+ return (v->index < cdt->input_vert_tot);
+}
+
+/* Return the Symedge that goes from v1 to v2, if it exists, else return nullptr. */
+template<typename T>
+SymEdge<T> *find_symedge_between_verts(const CDTVert<T> *v1, const CDTVert<T> *v2)
+{
+ SymEdge<T> *t = v1->symedge;
+ SymEdge<T> *tstart = t;
+ do {
+ if (t->next->vert == v2) {
+ return t;
+ }
+ } while ((t = t->rot) != tstart);
+ return nullptr;
+}
+
+/**
+ * Return the SymEdge attached to v that has face f, if it exists, else return nullptr.
+ */
+template<typename T> SymEdge<T> *find_symedge_with_face(const CDTVert<T> *v, const CDTFace<T> *f)
+{
+ SymEdge<T> *t = v->symedge;
+ SymEdge<T> *tstart = t;
+ do {
+ if (t->face == f) {
+ return t;
+ }
+ } while ((t = t->rot) != tstart);
+ return nullptr;
+}
+
+/**
+ * Is there already an edge between a and b?
+ */
+template<typename T> inline bool exists_edge(const CDTVert<T> *v1, const CDTVert<T> *v2)
+{
+ return find_symedge_between_verts(v1, v2) != nullptr;
+}
+
+/**
+ * Is the vertex v incident on face f?
+ */
+template<typename T> bool vert_touches_face(const CDTVert<T> *v, const CDTFace<T> *f)
+{
+ SymEdge<T> *se = v->symedge;
+ do {
+ if (se->face == f) {
+ return true;
+ }
+ } while ((se = se->rot) != v->symedge);
+ return false;
+}
+
+/**
+ * Assume s1 and s2 are both #SymEdges in a face with > 3 sides,
+ * and one is not the next of the other.
+ * Add an edge from `s1->v` to `s2->v`, splitting the face in two.
+ * The original face will continue to be associated with the sub-face
+ * that has s1, and a new face will be made for s2's new face.
+ * Return the new diagonal's #CDTEdge pointer.
+ */
+template<typename T> CDTEdge<T> *CDTArrangement<T>::add_diagonal(SymEdge<T> *s1, SymEdge<T> *s2)
+{
+ CDTFace<T> *fold = s1->face;
+ CDTFace<T> *fnew = this->add_face();
+ SymEdge<T> *s1prev = prev(s1);
+ SymEdge<T> *s1prevsym = sym(s1prev);
+ SymEdge<T> *s2prev = prev(s2);
+ SymEdge<T> *s2prevsym = sym(s2prev);
+ CDTEdge<T> *ediag = this->add_edge(s1->vert, s2->vert, fnew, fold);
+ SymEdge<T> *sdiag = &ediag->symedges[0];
+ SymEdge<T> *sdiagsym = &ediag->symedges[1];
+ sdiag->next = s2;
+ sdiagsym->next = s1;
+ s2prev->next = sdiagsym;
+ s1prev->next = sdiag;
+ s1->rot = sdiag;
+ sdiag->rot = s1prevsym;
+ s2->rot = sdiagsym;
+ sdiagsym->rot = s2prevsym;
+ for (SymEdge<T> *se = s2; se != sdiag; se = se->next) {
+ se->face = fnew;
+ }
+ add_list_to_input_ids(&fnew->input_ids, fold->input_ids);
+ return ediag;
+}
+
+template<typename T>
+CDTEdge<T> *CDTArrangement<T>::add_vert_to_symedge_edge(CDTVert<T> *v, SymEdge<T> *se)
+{
+ SymEdge<T> *se_rot = se->rot;
+ SymEdge<T> *se_rotsym = sym(se_rot);
+ /* TODO: check: I think last arg in next should be sym(se)->face. */
+ CDTEdge<T> *e = this->add_edge(v, se->vert, se->face, se->face);
+ SymEdge<T> *new_se = &e->symedges[0];
+ SymEdge<T> *new_se_sym = &e->symedges[1];
+ new_se->next = se;
+ new_se_sym->next = new_se;
+ new_se->rot = new_se;
+ new_se_sym->rot = se_rot;
+ se->rot = new_se_sym;
+ se_rotsym->next = new_se_sym;
+ return e;
+}
+
+/**
+ * Connect the verts of se1 and se2, assuming that currently those two #SymEdge's are on
+ * the outer boundary (have face == outer_face) of two components that are isolated from
+ * each other.
+ */
+template<typename T>
+CDTEdge<T> *CDTArrangement<T>::connect_separate_parts(SymEdge<T> *se1, SymEdge<T> *se2)
+{
+ BLI_assert(se1->face == this->outer_face && se2->face == this->outer_face);
+ SymEdge<T> *se1_rot = se1->rot;
+ SymEdge<T> *se1_rotsym = sym(se1_rot);
+ SymEdge<T> *se2_rot = se2->rot;
+ SymEdge<T> *se2_rotsym = sym(se2_rot);
+ CDTEdge<T> *e = this->add_edge(se1->vert, se2->vert, this->outer_face, this->outer_face);
+ SymEdge<T> *new_se = &e->symedges[0];
+ SymEdge<T> *new_se_sym = &e->symedges[1];
+ new_se->next = se2;
+ new_se_sym->next = se1;
+ new_se->rot = se1_rot;
+ new_se_sym->rot = se2_rot;
+ se1->rot = new_se;
+ se2->rot = new_se_sym;
+ se1_rotsym->next = new_se;
+ se2_rotsym->next = new_se_sym;
+ return e;
+}
+
+/**
+ * Split se at fraction lambda,
+ * and return the new #CDTEdge that is the new second half.
+ * Copy the edge input_ids into the new one.
+ */
+template<typename T> CDTEdge<T> *CDTArrangement<T>::split_edge(SymEdge<T> *se, T lambda)
+{
+ /* Split e at lambda. */
+ const vec2<T> *a = &se->vert->co;
+ const vec2<T> *b = &se->next->vert->co;
+ SymEdge<T> *sesym = sym(se);
+ SymEdge<T> *sesymprev = prev(sesym);
+ SymEdge<T> *sesymprevsym = sym(sesymprev);
+ SymEdge<T> *senext = se->next;
+ CDTVert<T> *v = this->add_vert(vec2<T>::interpolate(*a, *b, lambda));
+ CDTEdge<T> *e = this->add_edge(v, se->next->vert, se->face, sesym->face);
+ sesym->vert = v;
+ SymEdge<T> *newse = &e->symedges[0];
+ SymEdge<T> *newsesym = &e->symedges[1];
+ se->next = newse;
+ newsesym->next = sesym;
+ newse->next = senext;
+ newse->rot = sesym;
+ sesym->rot = newse;
+ senext->rot = newsesym;
+ newsesym->rot = sesymprevsym;
+ sesymprev->next = newsesym;
+ if (newsesym->vert->symedge == sesym) {
+ newsesym->vert->symedge = newsesym;
+ }
+ add_list_to_input_ids(&e->input_ids, se->edge->input_ids);
+ return e;
+}
+
+/**
+ * Delete an edge from the structure. The new combined face on either side of
+ * the deleted edge will be the one that was e's face.
+ * There will be now an unused face, marked by setting its deleted flag,
+ * and an unused #CDTEdge, marked by setting the next and rot pointers of
+ * its #SymEdges to #nullptr.
+ * <pre>
+ * . v2 .
+ * / \ / \
+ * /f|j\ / \
+ * / | \ / \
+ * |
+ * A | B A
+ * \ e| / \ /
+ * \ | / \ /
+ * \h|i/ \ /
+ * . v1 .
+ * </pre>
+ * Also handle variant cases where one or both ends
+ * are attached only to e.
+ */
+template<typename T> void CDTArrangement<T>::delete_edge(SymEdge<T> *se)
+{
+ SymEdge<T> *sesym = sym(se);
+ CDTVert<T> *v1 = se->vert;
+ CDTVert<T> *v2 = sesym->vert;
+ CDTFace<T> *aface = se->face;
+ CDTFace<T> *bface = sesym->face;
+ SymEdge<T> *f = se->next;
+ SymEdge<T> *h = prev(se);
+ SymEdge<T> *i = sesym->next;
+ SymEdge<T> *j = prev(sesym);
+ SymEdge<T> *jsym = sym(j);
+ SymEdge<T> *hsym = sym(h);
+ bool v1_isolated = (i == se);
+ bool v2_isolated = (f == sesym);
+
+ if (!v1_isolated) {
+ h->next = i;
+ i->rot = hsym;
+ }
+ if (!v2_isolated) {
+ j->next = f;
+ f->rot = jsym;
+ }
+ if (!v1_isolated && !v2_isolated && aface != bface) {
+ for (SymEdge<T> *k = i; k != f; k = k->next) {
+ k->face = aface;
+ }
+ }
+
+ /* If e was representative symedge for v1 or v2, fix that. */
+ if (v1_isolated) {
+ v1->symedge = nullptr;
+ }
+ else if (v1->symedge == se) {
+ v1->symedge = i;
+ }
+ if (v2_isolated) {
+ v2->symedge = nullptr;
+ }
+ else if (v2->symedge == sesym) {
+ v2->symedge = f;
+ }
+
+ /* Mark SymEdge as deleted by setting all its pointers to NULL. */
+ se->next = se->rot = nullptr;
+ sesym->next = sesym->rot = nullptr;
+ if (!v1_isolated && !v2_isolated && aface != bface) {
+ bface->deleted = true;
+ if (this->outer_face == bface) {
+ this->outer_face = aface;
+ }
+ }
+}
+
+template<typename T> class SiteInfo {
+ public:
+ CDTVert<T> *v;
+ int orig_index;
+};
+
+/**
+ * Compare function for lexicographic sort: x, then y, then index.
+ */
+template<typename T> bool site_lexicographic_sort(const SiteInfo<T> &a, const SiteInfo<T> &b)
+{
+ const vec2<T> &co_a = a.v->co;
+ const vec2<T> &co_b = b.v->co;
+ if (co_a[0] < co_b[0]) {
+ return true;
+ }
+ if (co_a[0] > co_b[0]) {
+ return false;
+ }
+ if (co_a[1] < co_b[1]) {
+ return true;
+ }
+ if (co_a[1] > co_b[1]) {
+ return false;
+ }
+ return a.orig_index < b.orig_index;
+}
+
+/**
+ * Find series of equal vertices in the sorted sites array
+ * and use the vertices merge_to_index to indicate that
+ * all vertices after the first merge to the first.
+ */
+template<typename T> void find_site_merges(Array<SiteInfo<T>> &sites)
+{
+ int n = sites.size();
+ for (int i = 0; i < n - 1; ++i) {
+ int j = i + 1;
+ while (j < n && sites[j].v->co == sites[i].v->co) {
+ sites[j].v->merge_to_index = sites[i].orig_index;
+ ++j;
+ }
+ if (j - i > 1) {
+ i = j - 1; /* j-1 because loop head will add another 1. */
+ }
+ }
+}
+
+template<typename T> inline bool vert_left_of_symedge(CDTVert<T> *v, SymEdge<T> *se)
+{
+ return orient2d(v->co, se->vert->co, se->next->vert->co) > 0;
+}
+
+template<typename T> inline bool vert_right_of_symedge(CDTVert<T> *v, SymEdge<T> *se)
+{
+ return orient2d(v->co, se->next->vert->co, se->vert->co) > 0;
+}
+
+/* Is se above basel? */
+template<typename T>
+inline bool dc_tri_valid(SymEdge<T> *se, SymEdge<T> *basel, SymEdge<T> *basel_sym)
+{
+ return orient2d(se->next->vert->co, basel_sym->vert->co, basel->vert->co) > 0;
+}
+
+/**
+ * Delaunay triangulate sites[start} to sites[end-1].
+ * Assume sites are lexicographically sorted by coordinate.
+ * Return #SymEdge of CCW convex hull at left-most point in *r_le
+ * and that of right-most point of cw convex null in *r_re.
+ */
+template<typename T>
+void dc_tri(CDTArrangement<T> *cdt,
+ Array<SiteInfo<T>> &sites,
+ int start,
+ int end,
+ SymEdge<T> **r_le,
+ SymEdge<T> **r_re)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "DC_TRI start=" << start << " end=" << end << "\n";
+ }
+ int n = end - start;
+ if (n <= 1) {
+ *r_le = nullptr;
+ *r_re = nullptr;
+ return;
+ }
+
+ /* Base case: if n <= 3, triangulate directly. */
+ if (n <= 3) {
+ CDTVert<T> *v1 = sites[start].v;
+ CDTVert<T> *v2 = sites[start + 1].v;
+ CDTEdge<T> *ea = cdt->add_edge(v1, v2, cdt->outer_face, cdt->outer_face);
+ ea->symedges[0].next = &ea->symedges[1];
+ ea->symedges[1].next = &ea->symedges[0];
+ ea->symedges[0].rot = &ea->symedges[0];
+ ea->symedges[1].rot = &ea->symedges[1];
+ if (n == 2) {
+ *r_le = &ea->symedges[0];
+ *r_re = &ea->symedges[1];
+ return;
+ }
+ CDTVert<T> *v3 = sites[start + 2].v;
+ CDTEdge<T> *eb = cdt->add_vert_to_symedge_edge(v3, &ea->symedges[1]);
+ int orient = orient2d(v1->co, v2->co, v3->co);
+ if (orient > 0) {
+ cdt->add_diagonal(&eb->symedges[0], &ea->symedges[0]);
+ *r_le = &ea->symedges[0];
+ *r_re = &eb->symedges[0];
+ }
+ else if (orient < 0) {
+ cdt->add_diagonal(&ea->symedges[0], &eb->symedges[0]);
+ *r_le = ea->symedges[0].rot;
+ *r_re = eb->symedges[0].rot;
+ }
+ else {
+ /* Collinear points. Just return a line. */
+ *r_le = &ea->symedges[0];
+ *r_re = &eb->symedges[0];
+ }
+ return;
+ }
+ /* Recursive case. Do left (L) and right (R) halves seperately, then join. */
+ int n2 = n / 2;
+ BLI_assert(n2 >= 2 && end - (start + n2) >= 2);
+ SymEdge<T> *ldo;
+ SymEdge<T> *ldi;
+ SymEdge<T> *rdi;
+ SymEdge<T> *rdo;
+ dc_tri(cdt, sites, start, start + n2, &ldo, &ldi);
+ dc_tri(cdt, sites, start + n2, end, &rdi, &rdo);
+ if (dbg_level > 0) {
+ std::cout << "\nDC_TRI merge step for start=" << start << ", end=" << end << "\n";
+ std::cout << "ldo " << ldo << "\n"
+ << "ldi " << ldi << "\n"
+ << "rdi " << rdi << "\n"
+ << "rdo " << rdo << "\n";
+ if (dbg_level > 1) {
+ std::string lab = "dc_tri (" + std::to_string(start) + "," + std::to_string(start + n2) +
+ ")(" + std::to_string(start + n2) + "," + std::to_string(end) + ")";
+ cdt_draw(lab, *cdt);
+ }
+ }
+ /* Find lower common tangent of L and R. */
+ for (;;) {
+ if (vert_left_of_symedge(rdi->vert, ldi)) {
+ ldi = ldi->next;
+ }
+ else if (vert_right_of_symedge(ldi->vert, rdi)) {
+ rdi = sym(rdi)->rot; /* Previous edge to rdi with same right face. */
+ }
+ else {
+ break;
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "common lower tangent in between\n"
+ << "rdi " << rdi << "\n"
+ << "ldi" << ldi << "\n";
+ }
+
+ CDTEdge<T> *ebasel = cdt->connect_separate_parts(sym(rdi)->next, ldi);
+ SymEdge<T> *basel = &ebasel->symedges[0];
+ SymEdge<T> *basel_sym = &ebasel->symedges[1];
+ if (dbg_level > 1) {
+ std::cout << "basel " << basel;
+ cdt_draw("after basel made", *cdt);
+ }
+ if (ldi->vert == ldo->vert) {
+ ldo = basel_sym;
+ }
+ if (rdi->vert == rdo->vert) {
+ rdo = basel;
+ }
+
+ /* Merge loop. */
+ for (;;) {
+ /* Locate the first point lcand->next->vert encountered by rising bubble,
+ * and delete L edges out of basel->next->vert that fail the circle test. */
+ SymEdge<T> *lcand = basel_sym->rot;
+ SymEdge<T> *rcand = basel_sym->next;
+ if (dbg_level > 1) {
+ std::cout << "\ntop of merge loop\n";
+ std::cout << "lcand " << lcand << "\n"
+ << "rcand " << rcand << "\n"
+ << "basel " << basel << "\n";
+ }
+ if (dc_tri_valid(lcand, basel, basel_sym)) {
+ if (dbg_level > 1) {
+ std::cout << "found valid lcand\n";
+ std::cout << " lcand" << lcand << "\n";
+ }
+ while (incircle(basel_sym->vert->co,
+ basel->vert->co,
+ lcand->next->vert->co,
+ lcand->rot->next->vert->co) > 0.0) {
+ if (dbg_level > 1) {
+ std::cout << "incircle says to remove lcand\n";
+ std::cout << " lcand" << lcand << "\n";
+ }
+ SymEdge<T> *t = lcand->rot;
+ cdt->delete_edge(sym(lcand));
+ lcand = t;
+ }
+ }
+ /* Symmetrically, locate first R point to be hit and delete R edges. */
+ if (dc_tri_valid(rcand, basel, basel_sym)) {
+ if (dbg_level > 1) {
+ std::cout << "found valid rcand\n";
+ std::cout << " rcand" << rcand << "\n";
+ }
+ while (incircle(basel_sym->vert->co,
+ basel->vert->co,
+ rcand->next->vert->co,
+ sym(rcand)->next->next->vert->co) > 0.0) {
+ if (dbg_level > 0) {
+ std::cout << "incircle says to remove rcand\n";
+ std::cout << " rcand" << rcand << "\n";
+ }
+ SymEdge<T> *t = sym(rcand)->next;
+ cdt->delete_edge(rcand);
+ rcand = t;
+ }
+ }
+ /* If both lcand and rcand are invalid, then basel is the common upper tangent. */
+ bool valid_lcand = dc_tri_valid(lcand, basel, basel_sym);
+ bool valid_rcand = dc_tri_valid(rcand, basel, basel_sym);
+ if (dbg_level > 0) {
+ std::cout << "after bubbling up, valid_lcand=" << valid_lcand
+ << ", valid_rand=" << valid_rcand << "\n";
+ std::cout << "lcand" << lcand << "\n"
+ << "rcand" << rcand << "\n";
+ }
+ if (!valid_lcand && !valid_rcand) {
+ break;
+ }
+ /* The next cross edge to be connected is to either `lcand->next->vert` or `rcand->next->vert`;
+ * if both are valid, choose the appropriate one using the #incircle test. */
+ if (!valid_lcand ||
+ (valid_rcand &&
+ incircle(lcand->next->vert->co, lcand->vert->co, rcand->vert->co, rcand->next->vert->co) >
+ 0)) {
+ if (dbg_level > 0) {
+ std::cout << "connecting rcand\n";
+ std::cout << " se1=basel_sym" << basel_sym << "\n";
+ std::cout << " se2=rcand->next" << rcand->next << "\n";
+ }
+ ebasel = cdt->add_diagonal(rcand->next, basel_sym);
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "connecting lcand\n";
+ std::cout << " se1=sym(lcand)" << sym(lcand) << "\n";
+ std::cout << " se2=basel_sym->next" << basel_sym->next << "\n";
+ }
+ ebasel = cdt->add_diagonal(basel_sym->next, sym(lcand));
+ }
+ basel = &ebasel->symedges[0];
+ basel_sym = &ebasel->symedges[1];
+ BLI_assert(basel_sym->face == cdt->outer_face);
+ if (dbg_level > 2) {
+ cdt_draw("after adding new crossedge", *cdt);
+ }
+ }
+ *r_le = ldo;
+ *r_re = rdo;
+ BLI_assert(sym(ldo)->face == cdt->outer_face && rdo->face == cdt->outer_face);
+}
+
+/* Guibas-Stolfi Divide-and_Conquer algorithm. */
+template<typename T> void dc_triangulate(CDTArrangement<T> *cdt, Array<SiteInfo<T>> &sites)
+{
+ /* Compress sites in place to eliminted verts that merge to others. */
+ int i = 0;
+ int j = 0;
+ int nsites = sites.size();
+ while (j < nsites) {
+ /* Invariante: sites[0..i-1] have non-merged verts from 0..(j-1) in them. */
+ sites[i] = sites[j++];
+ if (sites[i].v->merge_to_index < 0) {
+ i++;
+ }
+ }
+ int n = i;
+ if (n == 0) {
+ return;
+ }
+ SymEdge<T> *le;
+ SymEdge<T> *re;
+ dc_tri(cdt, sites, 0, n, &le, &re);
+}
+
+/**
+ * Do a Delaunay Triangulation of the points in cdt.verts.
+ * This is only a first step in the Constrained Delaunay triangulation,
+ * because it doesn't yet deal with the segment constraints.
+ * The algorithm used is the Divide & Conquer algorithm from the
+ * Guibas-Stolfi "Primitives for the Manipulation of General Subdivision
+ * and the Computation of Voronoi Diagrams" paper.
+ * The data structure here is similar to but not exactly the same as
+ * the quad-edge structure described in that paper.
+ * If T is not exact arithmetic, incircle and CCW tests are done using
+ * Shewchuk's exact primitives, so that this routine is robust.
+ *
+ * As a preprocessing step, we want to merge all vertices that the same.
+ * This is accomplished by lexicographically
+ * sorting the coordinates first (which is needed anyway for the D&C algorithm).
+ * The CDTVerts with merge_to_index not equal to -1 are after this regarded
+ * as having been merged into the vertex with the corresponding index.
+ */
+template<typename T> void initial_triangulation(CDTArrangement<T> *cdt)
+{
+ int n = cdt->verts.size();
+ if (n <= 1) {
+ return;
+ }
+ Array<SiteInfo<T>> sites(n);
+ for (int i = 0; i < n; ++i) {
+ sites[i].v = cdt->verts[i];
+ sites[i].orig_index = i;
+ }
+ std::sort(sites.begin(), sites.end(), site_lexicographic_sort<T>);
+ find_site_merges(sites);
+ dc_triangulate(cdt, sites);
+}
+
+/**
+ * Re-triangulates, assuring constrained delaunay condition,
+ * the pseudo-polygon that cycles from se.
+ * "pseudo" because a vertex may be repeated.
+ * See Anglada paper, "An Improved incremental algorithm
+ * for constructing restricted Delaunay triangulations".
+ */
+template<typename T> static void re_delaunay_triangulate(CDTArrangement<T> *cdt, SymEdge<T> *se)
+{
+ if (se->face == cdt->outer_face || sym(se)->face == cdt->outer_face) {
+ return;
+ }
+ /* 'se' is a diagonal just added, and it is base of area to retriangulate (face on its left) */
+ int count = 1;
+ for (SymEdge<T> *ss = se->next; ss != se; ss = ss->next) {
+ count++;
+ }
+ if (count <= 3) {
+ return;
+ }
+ /* First and last are the SymEdges whose verts are first and last off of base,
+ * continuing from 'se'. */
+ SymEdge<T> *first = se->next->next;
+ /* We want to make a triangle with 'se' as base and some other c as 3rd vertex. */
+ CDTVert<T> *a = se->vert;
+ CDTVert<T> *b = se->next->vert;
+ CDTVert<T> *c = first->vert;
+ SymEdge<T> *cse = first;
+ for (SymEdge<T> *ss = first->next; ss != se; ss = ss->next) {
+ CDTVert<T> *v = ss->vert;
+ if (incircle(a->co, b->co, c->co, v->co) > 0) {
+ c = v;
+ cse = ss;
+ }
+ }
+ /* Add diagonals necessary to make abc a triangle. */
+ CDTEdge<T> *ebc = nullptr;
+ CDTEdge<T> *eca = nullptr;
+ if (!exists_edge(b, c)) {
+ ebc = cdt->add_diagonal(se->next, cse);
+ }
+ if (!exists_edge(c, a)) {
+ eca = cdt->add_diagonal(cse, se);
+ }
+ /* Now recurse. */
+ if (ebc) {
+ re_delaunay_triangulate(cdt, &ebc->symedges[1]);
+ }
+ if (eca) {
+ re_delaunay_triangulate(cdt, &eca->symedges[1]);
+ }
+}
+
+template<typename T> inline int tri_orient(const SymEdge<T> *t)
+{
+ return orient2d(t->vert->co, t->next->vert->co, t->next->next->vert->co);
+}
+
+/**
+ * The #CrossData class defines either an endpoint or an intermediate point
+ * in the path we will take to insert an edge constraint.
+ * Each such point will either be
+ * (a) a vertex or
+ * (b) a fraction lambda (0 < lambda < 1) along some #SymEdge.]
+ *
+ * In general, lambda=0 indicates case a and lambda != 0 indicates case be.
+ * The 'in' edge gives the destination attachment point of a diagonal from the previous crossing,
+ * and the 'out' edge gives the origin attachment point of a diagonal to the next crossing.
+ * But in some cases, 'in' and 'out' are undefined or not needed, and will be NULL.
+ *
+ * For case (a), 'vert' will be the vertex, and lambda will be 0, and 'in' will be the #SymEdge
+ * from 'vert' that has as face the one that you go through to get to this vertex. If you go
+ * exactly along an edge then we set 'in' to NULL, since it won't be needed. The first crossing
+ * will have 'in' = NULL. We set 'out' to the #SymEdge that has the face we go though to get to the
+ * next crossing, or, if the next crossing is a case (a), then it is the edge that goes to that
+ * next vertex. 'out' will be NULL for the last one.
+ *
+ * For case (b), vert will be NULL at first, and later filled in with the created split vertex,
+ * and 'in' will be the #SymEdge that we go through, and lambda will be between 0 and 1,
+ * the fraction from in's vert to in->next's vert to put the split vertex.
+ * 'out' is not needed in this case, since the attachment point will be the sym of the first
+ * half of the split edge.
+ */
+template<typename T> class CrossData {
+ public:
+ T lambda = T(0);
+ CDTVert<T> *vert;
+ SymEdge<T> *in;
+ SymEdge<T> *out;
+
+ CrossData() : lambda(T(0)), vert(nullptr), in(nullptr), out(nullptr)
+ {
+ }
+ CrossData(T l, CDTVert<T> *v, SymEdge<T> *i, SymEdge<T> *o) : lambda(l), vert(v), in(i), out(o)
+ {
+ }
+ CrossData(const CrossData &other)
+ : lambda(other.lambda), vert(other.vert), in(other.in), out(other.out)
+ {
+ }
+ CrossData(CrossData &&other) noexcept
+ : lambda(std::move(other.lambda)),
+ vert(std::move(other.vert)),
+ in(std::move(other.in)),
+ out(std::move(other.out))
+ {
+ }
+ ~CrossData() = default;
+ CrossData &operator=(const CrossData &other)
+ {
+ if (this != &other) {
+ lambda = other.lambda;
+ vert = other.vert;
+ in = other.in;
+ out = other.out;
+ }
+ return *this;
+ }
+ CrossData &operator=(CrossData &&other) noexcept
+ {
+ lambda = std::move(other.lambda);
+ vert = std::move(other.vert);
+ in = std::move(other.in);
+ out = std::move(other.out);
+ return *this;
+ }
+};
+
+template<typename T>
+bool get_next_crossing_from_vert(CDT_state<T> *cdt_state,
+ CrossData<T> *cd,
+ CrossData<T> *cd_next,
+ const CDTVert<T> *v2);
+
+/**
+ * As part of finding crossings, we found a case where the next crossing goes through vert v.
+ * If it came from a previous vert in cd, then cd_out is the edge that leads from that to v.
+ * Else cd_out can be NULL, because it won't be used.
+ * Set *cd_next to indicate this. We can set 'in' but not 'out'. We can set the 'out' of the
+ * current cd.
+ */
+template<typename T>
+void fill_crossdata_for_through_vert(CDTVert<T> *v,
+ SymEdge<T> *cd_out,
+ CrossData<T> *cd,
+ CrossData<T> *cd_next)
+{
+ SymEdge<T> *se;
+
+ cd_next->lambda = T(0);
+ cd_next->vert = v;
+ cd_next->in = NULL;
+ cd_next->out = NULL;
+ if (cd->lambda == 0) {
+ cd->out = cd_out;
+ }
+ else {
+ /* One of the edges in the triangle with edge sym(cd->in) contains v. */
+ se = sym(cd->in);
+ if (se->vert != v) {
+ se = se->next;
+ if (se->vert != v) {
+ se = se->next;
+ }
+ }
+ BLI_assert(se->vert == v);
+ cd_next->in = se;
+ }
+}
+
+/**
+ * As part of finding crossings, we found a case where orient tests say that the next crossing
+ * is on the #SymEdge t, while intersecting with the ray from \a curco to \a v2.
+ * Find the intersection point and fill in the #CrossData for that point.
+ * It may turn out that when doing the intersection, we get an answer that says that
+ * this case is better handled as through-vertex case instead, so we may do that.
+ * In the latter case, we want to avoid a situation where the current crossing is on an edge
+ * and the next will be an endpoint of the same edge. When that happens, we "rewrite history"
+ * and turn the current crossing into a vert one, and then extend from there.
+ *
+ * We cannot fill cd_next's 'out' edge yet, in the case that the next one ends up being a vert
+ * case. We need to fill in cd's 'out' edge if it was a vert case.
+ */
+template<typename T>
+void fill_crossdata_for_intersect(const vec2<T> &curco,
+ const CDTVert<T> *v2,
+ SymEdge<T> *t,
+ CrossData<T> *cd,
+ CrossData<T> *cd_next,
+ const T epsilon)
+{
+ CDTVert<T> *va = t->vert;
+ CDTVert<T> *vb = t->next->vert;
+ CDTVert<T> *vc = t->next->next->vert;
+ SymEdge<T> *se_vcvb = sym(t->next);
+ SymEdge<T> *se_vcva = t->next->next;
+ BLI_assert(se_vcva->vert == vc && se_vcva->next->vert == va);
+ BLI_assert(se_vcvb->vert == vc && se_vcvb->next->vert == vb);
+ UNUSED_VARS_NDEBUG(vc);
+ auto isect = vec2<T>::isect_seg_seg(va->co, vb->co, curco, v2->co);
+ T &lambda = isect.lambda;
+ switch (isect.kind) {
+ case vec2<T>::isect_result::LINE_LINE_CROSS: {
+#ifdef WITH_GMP
+ if (!std::is_same<T, mpq_class>::value) {
+#else
+ if (true) {
+#endif
+ T len_ab = vec2<T>::distance(va->co, vb->co);
+ if (lambda * len_ab <= epsilon) {
+ fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
+ }
+ else if ((1 - lambda) * len_ab <= epsilon) {
+ fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
+ }
+ else {
+ *cd_next = CrossData<T>(lambda, nullptr, t, nullptr);
+ if (cd->lambda == 0) {
+ cd->out = se_vcva;
+ }
+ }
+ }
+ else {
+ *cd_next = CrossData<T>(lambda, nullptr, t, nullptr);
+ if (cd->lambda == 0) {
+ cd->out = se_vcva;
+ }
+ }
+ break;
+ }
+ case vec2<T>::isect_result::LINE_LINE_EXACT: {
+ if (lambda == 0) {
+ fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
+ }
+ else if (lambda == 1) {
+ fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
+ }
+ else {
+ *cd_next = CrossData<T>(lambda, nullptr, t, nullptr);
+ if (cd->lambda == 0) {
+ cd->out = se_vcva;
+ }
+ }
+ break;
+ }
+ case vec2<T>::isect_result::LINE_LINE_NONE: {
+#ifdef WITH_GMP
+ if (std::is_same<T, mpq_class>::value) {
+ BLI_assert(false);
+ }
+#endif
+ /* It should be very near one end or other of segment. */
+ const T middle_lambda = 0.5;
+ if (lambda <= middle_lambda) {
+ fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
+ }
+ else {
+ fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
+ }
+ break;
+ }
+ case vec2<T>::isect_result::LINE_LINE_COLINEAR: {
+ if (vec2<T>::distance_squared(va->co, v2->co) <= vec2<T>::distance_squared(vb->co, v2->co)) {
+ fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
+ }
+ else {
+ fill_crossdata_for_through_vert(vb, se_vcvb, cd, cd_next);
+ }
+ break;
+ }
+ }
+} // namespace blender::meshintersect
+
+/**
+ * As part of finding the crossings of a ray to v2, find the next crossing after 'cd', assuming
+ * 'cd' represents a crossing that goes through a vertex.
+ *
+ * We do a rotational scan around cd's vertex, looking for the triangle where the ray from cd->vert
+ * to v2 goes between the two arms from cd->vert, or where it goes along one of the edges.
+ */
+template<typename T>
+bool get_next_crossing_from_vert(CDT_state<T> *cdt_state,
+ CrossData<T> *cd,
+ CrossData<T> *cd_next,
+ const CDTVert<T> *v2)
+{
+ SymEdge<T> *tstart = cd->vert->symedge;
+ SymEdge<T> *t = tstart;
+ CDTVert<T> *vcur = cd->vert;
+ bool ok = false;
+ do {
+ /* The ray from `vcur` to v2 has to go either between two successive
+ * edges around `vcur` or exactly along them. This time through the
+ * loop, check to see if the ray goes along `vcur-va`
+ * or between `vcur-va` and `vcur-vb`, where va is the end of t
+ * and vb is the next vertex (on the next rot edge around vcur, but
+ * should also be the next vert of triangle starting with `vcur-va`. */
+ if (t->face != cdt_state->cdt.outer_face && tri_orient(t) < 0) {
+ BLI_assert(false); /* Shouldn't happen. */
+ }
+ CDTVert<T> *va = t->next->vert;
+ CDTVert<T> *vb = t->next->next->vert;
+ int orient1 = orient2d(t->vert->co, va->co, v2->co);
+ if (orient1 == 0 && in_line<T>(vcur->co, va->co, v2->co)) {
+ fill_crossdata_for_through_vert(va, t, cd, cd_next);
+ ok = true;
+ break;
+ }
+ if (t->face != cdt_state->cdt.outer_face) {
+ int orient2 = orient2d(vcur->co, vb->co, v2->co);
+ /* Don't handle orient2 == 0 case here: next rotation will get it. */
+ if (orient1 > 0 && orient2 < 0) {
+ /* Segment intersection. */
+ t = t->next;
+ fill_crossdata_for_intersect(vcur->co, v2, t, cd, cd_next, cdt_state->epsilon);
+ ok = true;
+ break;
+ }
+ }
+ } while ((t = t->rot) != tstart);
+ return ok;
+}
+
+/**
+ * As part of finding the crossings of a ray to `v2`, find the next crossing after 'cd', assuming
+ * 'cd' represents a crossing that goes through a an edge, not at either end of that edge.
+ *
+ * We have the triangle `vb-va-vc`, where `va` and vb are the split edge and `vc` is the third
+ * vertex on that new side of the edge (should be closer to `v2`).
+ * The next crossing should be through `vc` or intersecting `vb-vc` or `va-vc`.
+ */
+template<typename T>
+void get_next_crossing_from_edge(CrossData<T> *cd,
+ CrossData<T> *cd_next,
+ const CDTVert<T> *v2,
+ const T epsilon)
+{
+ CDTVert<T> *va = cd->in->vert;
+ CDTVert<T> *vb = cd->in->next->vert;
+ vec2<T> curco = vec2<T>::interpolate(va->co, vb->co, cd->lambda);
+ SymEdge<T> *se_ac = sym(cd->in)->next;
+ CDTVert<T> *vc = se_ac->next->vert;
+ int orient = orient2d(curco, v2->co, vc->co);
+ if (orient < 0) {
+ fill_crossdata_for_intersect<T>(curco, v2, se_ac->next, cd, cd_next, epsilon);
+ }
+ else if (orient > 0.0) {
+ fill_crossdata_for_intersect(curco, v2, se_ac, cd, cd_next, epsilon);
+ }
+ else {
+ *cd_next = CrossData<T>{0.0, vc, se_ac->next, nullptr};
+ }
+}
+
+constexpr int inline_crossings_size = 128;
+template<typename T>
+void dump_crossings(const Vector<CrossData<T>, inline_crossings_size> &crossings)
+{
+ std::cout << "CROSSINGS\n";
+ for (int i = 0; i < crossings.size(); ++i) {
+ std::cout << i << ": ";
+ const CrossData<T> &cd = crossings[i];
+ if (cd.lambda == 0) {
+ std::cout << "v" << cd.vert->index;
+ }
+ else {
+ std::cout << "lambda=" << cd.lambda;
+ }
+ if (cd.in != nullptr) {
+ std::cout << " in=" << short_se_dump(cd.in);
+ std::cout << " out=" << short_se_dump(cd.out);
+ }
+ std::cout << "\n";
+ }
+}
+
+/**
+ * Add a constrained edge between v1 and v2 to cdt structure.
+ * This may result in a number of #CDTEdges created, due to intersections
+ * and partial overlaps with existing cdt vertices and edges.
+ * Each created #CDTEdge will have input_id added to its input_ids list.
+ *
+ * If \a r_edges is not NULL, the #CDTEdges generated or found that go from
+ * v1 to v2 are put into that linked list, in order.
+ *
+ * Assumes that #blender_constrained_delaunay_get_output has not been called yet.
+ */
+template<typename T>
+void add_edge_constraint(
+ CDT_state<T> *cdt_state, CDTVert<T> *v1, CDTVert<T> *v2, int input_id, LinkNode **r_edges)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nADD EDGE CONSTRAINT\n" << vertname(v1) << " " << vertname(v2) << "\n";
+ }
+ LinkNodePair edge_list = {NULL, NULL};
+
+ if (r_edges) {
+ *r_edges = NULL;
+ }
+
+ /*
+ * Handle two special cases first:
+ * 1) The two end vertices are the same (can happen because of merging).
+ * 2) There is already an edge between v1 and v2.
+ */
+ if (v1 == v2) {
+ return;
+ }
+ SymEdge<T> *t = find_symedge_between_verts(v1, v2);
+ if (t != nullptr) {
+ /* Segment already there. */
+ add_to_input_ids(&t->edge->input_ids, input_id);
+ if (r_edges != NULL) {
+ BLI_linklist_append(&edge_list, t->edge);
+ *r_edges = edge_list.list;
+ }
+ return;
+ }
+
+ /*
+ * Fill crossings array with CrossData points for intersection path from v1 to v2.
+ *
+ * At every point, the crossings array has the path so far, except that
+ * the .out field of the last element of it may not be known yet -- if that
+ * last element is a vertex, then we won't know the output edge until we
+ * find the next crossing.
+ *
+ * To protect against infinite loops, we keep track of which vertices
+ * we have visited by setting their visit_index to a new visit epoch.
+ *
+ * We check a special case first: where the segment is already there in
+ * one hop. Saves a bunch of orient2d tests in that common case.
+ */
+ int visit = ++cdt_state->visit_count;
+ Vector<CrossData<T>, inline_crossings_size> crossings;
+ crossings.append(CrossData<T>(T(0), v1, nullptr, nullptr));
+ int n;
+ while (!((n = crossings.size()) > 0 && crossings[n - 1].vert == v2)) {
+ crossings.append(CrossData<T>());
+ CrossData<T> *cd = &crossings[n - 1];
+ CrossData<T> *cd_next = &crossings[n];
+ bool ok;
+ if (crossings[n - 1].lambda == 0) {
+ ok = get_next_crossing_from_vert(cdt_state, cd, cd_next, v2);
+ }
+ else {
+ get_next_crossing_from_edge(cd, cd_next, v2, cdt_state->epsilon);
+ ok = true;
+ }
+ constexpr int unreasonably_large_crossings = 100000;
+ if (!ok || crossings.size() == unreasonably_large_crossings) {
+ /* Shouldn't happen but if does, just bail out. */
+ BLI_assert(false);
+ return;
+ }
+ if (crossings[n].lambda == 0) {
+ if (crossings[n].vert->visit_index == visit) {
+ /* Shouldn't happen but if it does, just bail out. */
+ BLI_assert(false);
+ return;
+ }
+ crossings[n].vert->visit_index = visit;
+ }
+ }
+
+ if (dbg_level > 0) {
+ dump_crossings(crossings);
+ }
+
+ /*
+ * Post-process crossings.
+ * Some crossings may have an intersection crossing followed
+ * by a vertex crossing that is on the same edge that was just
+ * intersected. We prefer to go directly from the previous
+ * crossing directly to the vertex. This may chain backwards.
+ *
+ * This loop marks certain crossings as "deleted", by setting
+ * their lambdas to -1.0.
+ */
+ int ncrossings = crossings.size();
+ for (int i = 2; i < ncrossings; ++i) {
+ CrossData<T> *cd = &crossings[i];
+ if (cd->lambda == 0.0) {
+ CDTVert<T> *v = cd->vert;
+ int j;
+ CrossData<T> *cd_prev;
+ for (j = i - 1; j > 0; --j) {
+ cd_prev = &crossings[j];
+ if ((cd_prev->lambda == 0.0 && cd_prev->vert != v) ||
+ (cd_prev->lambda != 0.0 && cd_prev->in->vert != v && cd_prev->in->next->vert != v)) {
+ break;
+ }
+ cd_prev->lambda = -1.0; /* Mark cd_prev as 'deleted'. */
+ }
+ if (j < i - 1) {
+ /* Some crossings were deleted. Fix the in and out edges across gap. */
+ cd_prev = &crossings[j];
+ SymEdge<T> *se;
+ if (cd_prev->lambda == 0.0) {
+ se = find_symedge_between_verts(cd_prev->vert, v);
+ if (se == NULL) {
+ return;
+ }
+ cd_prev->out = se;
+ cd->in = NULL;
+ }
+ else {
+ se = find_symedge_with_face(v, sym(cd_prev->in)->face);
+ if (se == NULL) {
+ return;
+ }
+ cd->in = se;
+ }
+ }
+ }
+ }
+
+ /*
+ * Insert all intersection points on constrained edges.
+ */
+ for (int i = 0; i < ncrossings; ++i) {
+ CrossData<T> *cd = &crossings[i];
+ if (cd->lambda != 0.0 && cd->lambda != -1.0 && is_constrained_edge(cd->in->edge)) {
+ CDTEdge<T> *edge = cdt_state->cdt.split_edge(cd->in, cd->lambda);
+ cd->vert = edge->symedges[0].vert;
+ }
+ }
+
+ /*
+ * Remove any crossed, non-intersected edges.
+ */
+ for (int i = 0; i < ncrossings; ++i) {
+ CrossData<T> *cd = &crossings[i];
+ if (cd->lambda != 0.0 && cd->lambda != -1.0 && !is_constrained_edge(cd->in->edge)) {
+ cdt_state->cdt.delete_edge(cd->in);
+ }
+ }
+
+ /*
+ * Insert segments for v1->v2.
+ */
+ SymEdge<T> *tstart = crossings[0].out;
+ for (int i = 1; i < ncrossings; i++) {
+ CrossData<T> *cd = &crossings[i];
+ if (cd->lambda == -1.0) {
+ continue; /* This crossing was deleted. */
+ }
+ t = NULL;
+ SymEdge<T> *tnext = t;
+ CDTEdge<T> *edge;
+ if (cd->lambda != 0.0) {
+ if (is_constrained_edge(cd->in->edge)) {
+ t = cd->vert->symedge;
+ tnext = sym(t)->next;
+ }
+ }
+ else if (cd->lambda == 0.0) {
+ t = cd->in;
+ tnext = cd->out;
+ if (t == NULL) {
+ /* Previous non-deleted crossing must also have been a vert, and segment should exist. */
+ int j;
+ CrossData<T> *cd_prev;
+ for (j = i - 1; j >= 0; j--) {
+ cd_prev = &crossings[j];
+ if (cd_prev->lambda != -1.0) {
+ break;
+ }
+ }
+ BLI_assert(cd_prev->lambda == 0.0);
+ BLI_assert(cd_prev->out->next->vert == cd->vert);
+ edge = cd_prev->out->edge;
+ add_to_input_ids(&edge->input_ids, input_id);
+ if (r_edges != NULL) {
+ BLI_linklist_append(&edge_list, edge);
+ }
+ }
+ }
+ if (t != NULL) {
+ if (tstart->next->vert == t->vert) {
+ edge = tstart->edge;
+ }
+ else {
+ edge = cdt_state->cdt.add_diagonal(tstart, t);
+ }
+ add_to_input_ids(&edge->input_ids, input_id);
+ if (r_edges != NULL) {
+ BLI_linklist_append(&edge_list, edge);
+ }
+ /* Now retriangulate upper and lower gaps. */
+ re_delaunay_triangulate(&cdt_state->cdt, &edge->symedges[0]);
+ re_delaunay_triangulate(&cdt_state->cdt, &edge->symedges[1]);
+ }
+ if (i < ncrossings - 1) {
+ if (tnext != NULL) {
+ tstart = tnext;
+ }
+ }
+ }
+
+ if (r_edges) {
+ *r_edges = edge_list.list;
+ }
+}
+
+/**
+ * Incrementally add edge input edge as a constraint. This may cause the graph structure
+ * to change, in cases where the constraints intersect existing edges.
+ * The code will ensure that #CDTEdge's created will have ids that tie them back
+ * to the original edge constraint index.
+ */
+template<typename T> void add_edge_constraints(CDT_state<T> *cdt_state, const CDT_input<T> &input)
+{
+ int ne = input.edge.size();
+ int nv = input.vert.size();
+ for (int i = 0; i < ne; i++) {
+ int iv1 = input.edge[i].first;
+ int iv2 = input.edge[i].second;
+ if (iv1 < 0 || iv1 >= nv || iv2 < 0 || iv2 >= nv) {
+ /* Ignore invalid indices in edges. */
+ continue;
+ }
+ CDTVert<T> *v1 = cdt_state->cdt.get_vert_resolve_merge(iv1);
+ CDTVert<T> *v2 = cdt_state->cdt.get_vert_resolve_merge(iv2);
+ add_edge_constraint(cdt_state, v1, v2, i, nullptr);
+ }
+ cdt_state->face_edge_offset = ne;
+}
+
+/**
+ * Add face_id to the input_ids lists of all #CDTFace's on the interior of the input face with that
+ * id. face_symedge is on edge of the boundary of the input face, with assumption that interior is
+ * on the left of that #SymEdge.
+ *
+ * The algorithm is: starting from the #CDTFace for face_symedge, add the face_id and then
+ * process all adjacent faces where the adjacency isn't across an edge that was a constraint added
+ * for the boundary of the input face.
+ * fedge_start..fedge_end is the inclusive range of edge input ids that are for the given face.
+ *
+ * Note: if the input face is not CCW oriented, we'll be labeling the outside, not the inside.
+ * Note 2: if the boundary has self-crossings, this method will arbitrarily pick one of the
+ * contiguous set of faces enclosed by parts of the boundary, leaving the other such un-tagged.
+ * This may be a feature instead of a bug if the first contiguous section is most of the face and
+ * the others are tiny self-crossing triangles at some parts of the boundary.
+ * On the other hand, if decide we want to handle these in full generality, then will need a more
+ * complicated algorithm (using "inside" tests and a parity rule) to decide on the interior.
+ */
+template<typename T>
+void add_face_ids(
+ CDT_state<T> *cdt_state, SymEdge<T> *face_symedge, int face_id, int fedge_start, int fedge_end)
+{
+ /* Can't loop forever since eventually would visit every face. */
+ cdt_state->visit_count++;
+ int visit = cdt_state->visit_count;
+ Vector<SymEdge<T> *> stack;
+ stack.append(face_symedge);
+ while (!stack.is_empty()) {
+ SymEdge<T> *se = stack.pop_last();
+ CDTFace<T> *face = se->face;
+ if (face->visit_index == visit) {
+ continue;
+ }
+ face->visit_index = visit;
+ add_to_input_ids(&face->input_ids, face_id);
+ SymEdge<T> *se_start = se;
+ for (se = se->next; se != se_start; se = se->next) {
+ if (!id_range_in_list(se->edge->input_ids, fedge_start, fedge_end)) {
+ SymEdge<T> *se_sym = sym(se);
+ CDTFace<T> *face_other = se_sym->face;
+ if (face_other->visit_index != visit) {
+ stack.append(se_sym);
+ }
+ }
+ }
+ }
+}
+
+/* Return a power of 10 that is greater than or equal to x. */
+static int power_of_10_greater_equal_to(int x)
+{
+ if (x <= 0) {
+ return 1;
+ }
+ int ans = 1;
+ BLI_assert(x < INT_MAX / 10);
+ while (ans < x) {
+ ans *= 10;
+ }
+ return ans;
+}
+
+/**
+ Incrementally each edge of each input face as an edge constraint.
+ * The code will ensure that the #CDTEdge's created will have ids that tie them
+ * back to the original face edge (using a numbering system for those edges
+ * that starts with cdt->face_edge_offset, and continues with the edges in
+ * order around each face in turn. And then the next face starts at
+ * cdt->face_edge_offset beyond the start for the previous face.
+ */
+template<typename T> void add_face_constraints(CDT_state<T> *cdt_state, const CDT_input<T> &input)
+{
+ int nv = input.vert.size();
+ int nf = input.face.size();
+ int fstart = 0;
+ SymEdge<T> *face_symedge0 = nullptr;
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ int maxflen = 0;
+ for (int f = 0; f < nf; f++) {
+ maxflen = max_ii(maxflen, input.face[f].size());
+ }
+ /* For convenience in debugging, make face_edge_offset be a power of 10. */
+ cdt_state->face_edge_offset = power_of_10_greater_equal_to(
+ max_ii(maxflen, cdt_state->face_edge_offset));
+ /* The original_edge encoding scheme doesn't work if the following is false.
+ * If we really have that many faces and that large a max face length that when multiplied
+ * together the are >= INT_MAX, then the Delaunay calculation will take unreasonably long anyway.
+ */
+ BLI_assert(INT_MAX / cdt_state->face_edge_offset > nf);
+ for (int f = 0; f < nf; f++) {
+ int flen = input.face[f].size();
+ if (flen <= 2) {
+ /* Ignore faces with fewer than 3 vertices. */
+ fstart += flen;
+ continue;
+ }
+ int fedge_start = (f + 1) * cdt_state->face_edge_offset;
+ for (int i = 0; i < flen; i++) {
+ int face_edge_id = fedge_start + i;
+ int iv1 = input.face[f][i];
+ int iv2 = input.face[f][(i + 1) % flen];
+ if (iv1 < 0 || iv1 >= nv || iv2 < 0 || iv2 >= nv) {
+ /* Ignore face edges with invalid vertices. */
+ continue;
+ }
+ CDTVert<T> *v1 = cdt->get_vert_resolve_merge(iv1);
+ CDTVert<T> *v2 = cdt->get_vert_resolve_merge(iv2);
+ LinkNode *edge_list;
+ add_edge_constraint(cdt_state, v1, v2, face_edge_id, &edge_list);
+ /* Set a new face_symedge0 each time since earlier ones may not
+ * survive later symedge splits. Really, just want the one when
+ * i == flen -1, but this code guards against that one somehow
+ * being null.
+ */
+ if (edge_list != nullptr) {
+ CDTEdge<T> *face_edge = static_cast<CDTEdge<T> *>(edge_list->link);
+ face_symedge0 = &face_edge->symedges[0];
+ if (face_symedge0->vert != v1) {
+ face_symedge0 = &face_edge->symedges[1];
+ BLI_assert(face_symedge0->vert == v1);
+ }
+ }
+ BLI_linklist_free(edge_list, nullptr);
+ }
+ int fedge_end = fedge_start + flen - 1;
+ if (face_symedge0 != nullptr) {
+ add_face_ids(cdt_state, face_symedge0, f, fedge_start, fedge_end);
+ }
+ fstart += flen;
+ }
+}
+
+/* Delete_edge but try not to mess up outer face.
+ * Also faces have symedges now, so make sure not
+ * to mess those up either. */
+template<typename T> void dissolve_symedge(CDT_state<T> *cdt_state, SymEdge<T> *se)
+{
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ SymEdge<T> *symse = sym(se);
+ if (symse->face == cdt->outer_face) {
+ se = sym(se);
+ symse = sym(se);
+ }
+ if (cdt->outer_face->symedge == se || cdt->outer_face->symedge == symse) {
+ /* Advancing by 2 to get past possible 'sym(se)'. */
+ if (se->next->next == se) {
+ cdt->outer_face->symedge = NULL;
+ }
+ else {
+ cdt->outer_face->symedge = se->next->next;
+ }
+ }
+ else {
+ if (se->face->symedge == se) {
+ se->face->symedge = se->next;
+ }
+ if (symse->face->symedge == symse) {
+ symse->face->symedge = symse->next;
+ }
+ }
+ cdt->delete_edge(se);
+}
+
+/**
+ * Remove all non-constraint edges.
+ */
+template<typename T> void remove_non_constraint_edges(CDT_state<T> *cdt_state)
+{
+ for (CDTEdge<T> *e : cdt_state->cdt.edges) {
+ SymEdge<T> *se = &e->symedges[0];
+ if (!is_deleted_edge(e) && !is_constrained_edge(e)) {
+ dissolve_symedge(cdt_state, se);
+ }
+ }
+}
+
+/*
+ * Remove the non-constraint edges, but leave enough of them so that all of the
+ * faces that would be #BMesh faces (that is, the faces that have some input representative)
+ * are valid: they can't have holes, they can't have repeated vertices, and they can't have
+ * repeated edges.
+ *
+ * Not essential, but to make the result look more aesthetically nice,
+ * remove the edges in order of decreasing length, so that it is more likely that the
+ * final remaining support edges are short, and therefore likely to make a fairly
+ * direct path from an outer face to an inner hole face.
+ */
+
+/**
+ * For sorting edges by decreasing length (squared).
+ */
+template<typename T> struct EdgeToSort {
+ T len_squared = T(0);
+ CDTEdge<T> *e{nullptr};
+
+ EdgeToSort() = default;
+ EdgeToSort(const EdgeToSort &other) : len_squared(other.len_squared), e(other.e)
+ {
+ }
+ EdgeToSort(EdgeToSort &&other) noexcept : len_squared(std::move(other.len_squared)), e(other.e)
+ {
+ }
+ ~EdgeToSort() = default;
+ EdgeToSort &operator=(const EdgeToSort &other)
+ {
+ if (this != &other) {
+ len_squared = other.len_squared;
+ e = other.e;
+ }
+ return *this;
+ }
+ EdgeToSort &operator=(EdgeToSort &&other)
+ {
+ len_squared = std::move(other.len_squared);
+ e = other.e;
+ return *this;
+ }
+};
+
+template<typename T> void remove_non_constraint_edges_leave_valid_bmesh(CDT_state<T> *cdt_state)
+{
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ size_t nedges = cdt->edges.size();
+ if (nedges == 0) {
+ return;
+ }
+ Vector<EdgeToSort<T>> dissolvable_edges;
+ dissolvable_edges.reserve(cdt->edges.size());
+ int i = 0;
+ for (CDTEdge<T> *e : cdt->edges) {
+ if (!is_deleted_edge(e) && !is_constrained_edge(e)) {
+ dissolvable_edges.append(EdgeToSort<T>());
+ dissolvable_edges[i].e = e;
+ const vec2<T> &co1 = e->symedges[0].vert->co;
+ const vec2<T> &co2 = e->symedges[1].vert->co;
+ dissolvable_edges[i].len_squared = vec2<T>::distance_squared(co1, co2);
+ i++;
+ }
+ }
+ std::sort(dissolvable_edges.begin(),
+ dissolvable_edges.end(),
+ [](const EdgeToSort<T> &a, const EdgeToSort<T> &b) -> bool {
+ return (a.len_squared < b.len_squared);
+ });
+ for (EdgeToSort<T> &ets : dissolvable_edges) {
+ CDTEdge<T> *e = ets.e;
+ SymEdge<T> *se = &e->symedges[0];
+ bool dissolve = true;
+ CDTFace<T> *fleft = se->face;
+ CDTFace<T> *fright = sym(se)->face;
+ if (fleft != cdt->outer_face && fright != cdt->outer_face &&
+ (fleft->input_ids != nullptr || fright->input_ids != nullptr)) {
+ /* Is there another #SymEdge with same left and right faces?
+ * Or is there a vertex not part of e touching the same left and right faces? */
+ for (SymEdge<T> *se2 = se->next; dissolve && se2 != se; se2 = se2->next) {
+ if (sym(se2)->face == fright ||
+ (se2->vert != se->next->vert && vert_touches_face(se2->vert, fright))) {
+ dissolve = false;
+ }
+ }
+ }
+
+ if (dissolve) {
+ dissolve_symedge(cdt_state, se);
+ }
+ }
+}
+
+template<typename T> void remove_outer_edges_until_constraints(CDT_state<T> *cdt_state)
+{
+ // LinkNode *fstack = NULL;
+ // SymEdge *se, *se_start;
+ // CDTFace *f, *fsym;
+ int visit = ++cdt_state->visit_count;
+
+ cdt_state->cdt.outer_face->visit_index = visit;
+ /* Walk around outer face, adding faces on other side of dissolvable edges to stack. */
+ Vector<CDTFace<T> *> fstack;
+ SymEdge<T> *se_start = cdt_state->cdt.outer_face->symedge;
+ SymEdge<T> *se = se_start;
+ do {
+ if (!is_constrained_edge(se->edge)) {
+ CDTFace<T> *fsym = sym(se)->face;
+ if (fsym->visit_index != visit) {
+ fstack.append(fsym);
+ }
+ }
+ } while ((se = se->next) != se_start);
+
+ while (!fstack.is_empty()) {
+ LinkNode *to_dissolve = nullptr;
+ bool dissolvable;
+ CDTFace<T> *f = fstack.pop_last();
+ if (f->visit_index == visit) {
+ continue;
+ }
+ BLI_assert(f != cdt_state->cdt.outer_face);
+ f->visit_index = visit;
+ se_start = se = f->symedge;
+ do {
+ dissolvable = !is_constrained_edge(se->edge);
+ if (dissolvable) {
+ CDTFace<T> *fsym = sym(se)->face;
+ if (fsym->visit_index != visit) {
+ fstack.append(fsym);
+ }
+ else {
+ BLI_linklist_prepend(&to_dissolve, se);
+ }
+ }
+ se = se->next;
+ } while (se != se_start);
+ while (to_dissolve != NULL) {
+ se = static_cast<SymEdge<T> *>(BLI_linklist_pop(&to_dissolve));
+ if (se->next != NULL) {
+ dissolve_symedge(cdt_state, se);
+ }
+ }
+ }
+}
+
+/**
+ * Remove edges and merge faces to get desired output, as per options.
+ * \note the cdt cannot be further changed after this.
+ */
+template<typename T>
+void prepare_cdt_for_output(CDT_state<T> *cdt_state, const CDT_output_type output_type)
+{
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ if (cdt->edges.is_empty()) {
+ return;
+ }
+
+ /* Make sure all non-deleted faces have a symedge. */
+ for (CDTEdge<T> *e : cdt->edges) {
+ if (!is_deleted_edge(e)) {
+ if (e->symedges[0].face->symedge == nullptr) {
+ e->symedges[0].face->symedge = &e->symedges[0];
+ }
+ if (e->symedges[1].face->symedge == nullptr) {
+ e->symedges[1].face->symedge = &e->symedges[1];
+ }
+ }
+ }
+
+ if (output_type == CDT_CONSTRAINTS) {
+ remove_non_constraint_edges(cdt_state);
+ }
+ else if (output_type == CDT_CONSTRAINTS_VALID_BMESH) {
+ remove_non_constraint_edges_leave_valid_bmesh(cdt_state);
+ }
+ else if (output_type == CDT_INSIDE) {
+ remove_outer_edges_until_constraints(cdt_state);
+ }
+}
+
+template<typename T>
+CDT_result<T> get_cdt_output(CDT_state<T> *cdt_state,
+ const CDT_input<T> UNUSED(input),
+ CDT_output_type output_type)
+{
+ prepare_cdt_for_output(cdt_state, output_type);
+ CDT_result<T> result;
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ result.face_edge_offset = cdt_state->face_edge_offset;
+
+ /* All verts without a merge_to_index will be output.
+ * vert_to_output_map[i] will hold the output vertex index
+ * corresponding to the vert in position i in cdt->verts.
+ * This first loop sets vert_to_output_map for un-merged verts. */
+ int verts_size = cdt->verts.size();
+ Array<int> vert_to_output_map(verts_size);
+ int nv = 0;
+ for (int i = 0; i < verts_size; ++i) {
+ CDTVert<T> *v = cdt->verts[i];
+ if (v->merge_to_index == -1) {
+ vert_to_output_map[i] = nv;
+ ++nv;
+ }
+ }
+ if (nv <= 0) {
+ return result;
+ }
+ /* Now we can set vert_to_output_map for merged verts,
+ * and also add the input indices of merged verts to the input_ids
+ * list of the merge target if they were an original input id. */
+ if (nv < verts_size) {
+ for (int i = 0; i < verts_size; ++i) {
+ CDTVert<T> *v = cdt->verts[i];
+ if (v->merge_to_index != -1) {
+ if (i < cdt_state->input_vert_tot) {
+ add_to_input_ids(&cdt->verts[v->merge_to_index]->input_ids, i);
+ }
+ vert_to_output_map[i] = vert_to_output_map[v->merge_to_index];
+ }
+ }
+ }
+ result.vert = Array<vec2<T>>(nv);
+ result.vert_orig = Array<Vector<int>>(nv);
+ int i_out = 0;
+ for (int i = 0; i < verts_size; ++i) {
+ CDTVert<T> *v = cdt->verts[i];
+ if (v->merge_to_index == -1) {
+ result.vert[i_out] = v->co;
+ if (i < cdt_state->input_vert_tot) {
+ result.vert_orig[i_out].append(i);
+ }
+ for (LinkNode *ln = v->input_ids; ln; ln = ln->next) {
+ result.vert_orig[i_out].append(POINTER_AS_INT(ln->link));
+ }
+ ++i_out;
+ }
+ }
+
+ /* All non-deleted edges will be output. */
+ int ne = std::count_if(cdt->edges.begin(), cdt->edges.end(), [](const CDTEdge<T> *e) -> bool {
+ return !is_deleted_edge(e);
+ });
+ result.edge = Array<std::pair<int, int>>(ne);
+ result.edge_orig = Array<Vector<int>>(ne);
+ int e_out = 0;
+ for (const CDTEdge<T> *e : cdt->edges) {
+ if (!is_deleted_edge(e)) {
+ int vo1 = vert_to_output_map[e->symedges[0].vert->index];
+ int vo2 = vert_to_output_map[e->symedges[1].vert->index];
+ result.edge[e_out] = std::pair<int, int>(vo1, vo2);
+ for (LinkNode *ln = e->input_ids; ln; ln = ln->next) {
+ result.edge_orig[e_out].append(POINTER_AS_INT(ln->link));
+ }
+ ++e_out;
+ }
+ }
+
+ /* All non-deleted, non-outer faces will be output. */
+ int nf = std::count_if(cdt->faces.begin(), cdt->faces.end(), [=](const CDTFace<T> *f) -> bool {
+ return !f->deleted && f != cdt->outer_face;
+ });
+ result.face = Array<Vector<int>>(nf);
+ result.face_orig = Array<Vector<int>>(nf);
+ int f_out = 0;
+ for (const CDTFace<T> *f : cdt->faces) {
+ if (!f->deleted && f != cdt->outer_face) {
+ SymEdge<T> *se = f->symedge;
+ BLI_assert(se != nullptr);
+ SymEdge<T> *se_start = se;
+ do {
+ result.face[f_out].append(vert_to_output_map[se->vert->index]);
+ se = se->next;
+ } while (se != se_start);
+ for (LinkNode *ln = f->input_ids; ln; ln = ln->next) {
+ result.face_orig[f_out].append(POINTER_AS_INT(ln->link));
+ }
+ ++f_out;
+ }
+ }
+ return result;
+}
+
+/**
+ * Add all the input verts into cdt. This will deduplicate,
+ * setting vertices merge_to_index to show merges.
+ */
+template<typename T> void add_input_verts(CDT_state<T> *cdt_state, const CDT_input<T> &input)
+{
+ for (int i = 0; i < cdt_state->input_vert_tot; ++i) {
+ cdt_state->cdt.add_vert(input.vert[i]);
+ }
+}
+
+template<typename T>
+CDT_result<T> delaunay_calc(const CDT_input<T> &input, CDT_output_type output_type)
+{
+ int nv = input.vert.size();
+ int ne = input.edge.size();
+ int nf = input.face.size();
+ CDT_state<T> cdt_state(nv, ne, nf, input.epsilon);
+ add_input_verts(&cdt_state, input);
+ initial_triangulation(&cdt_state.cdt);
+ add_edge_constraints(&cdt_state, input);
+ add_face_constraints(&cdt_state, input);
+ return get_cdt_output(&cdt_state, input, output_type);
+}
+
+blender::meshintersect::CDT_result<double> delaunay_2d_calc(const CDT_input<double> &input,
+ CDT_output_type output_type)
+{
+ return delaunay_calc(input, output_type);
+}
+
+#ifdef WITH_GMP
+blender::meshintersect::CDT_result<mpq_class> delaunay_2d_calc(const CDT_input<mpq_class> &input,
+ CDT_output_type output_type)
+{
+ return delaunay_calc(input, output_type);
+}
+#endif
+
+} /* namespace blender::meshintersect */
+
+/* C interface. */
+
+/**
+ This function uses the double version of #CDT::delaunay_calc.
+ * Almost all of the work here is to convert between C++ #Arrays<Vector<int>>
+ * and a C version that linearizes all the elements and uses a "start"
+ * and "len" array to say where the individual vectors start and how
+ * long they are.
+ */
+extern "C" ::CDT_result *BLI_delaunay_2d_cdt_calc(const ::CDT_input *input,
+ const CDT_output_type output_type)
+{
+ blender::meshintersect::CDT_input<double> in;
+ in.vert = blender::Array<blender::meshintersect::vec2<double>>(input->verts_len);
+ in.edge = blender::Array<std::pair<int, int>>(input->edges_len);
+ in.face = blender::Array<blender::Vector<int>>(input->faces_len);
+ for (int v = 0; v < input->verts_len; ++v) {
+ double x = static_cast<double>(input->vert_coords[v][0]);
+ double y = static_cast<double>(input->vert_coords[v][1]);
+ in.vert[v] = blender::meshintersect::vec2<double>(x, y);
+ }
+ for (int e = 0; e < input->edges_len; ++e) {
+ in.edge[e] = std::pair<int, int>(input->edges[e][0], input->edges[e][1]);
+ }
+ for (int f = 0; f < input->faces_len; ++f) {
+ in.face[f] = blender::Vector<int>(input->faces_len_table[f]);
+ int fstart = input->faces_start_table[f];
+ for (int j = 0; j < input->faces_len_table[f]; ++j) {
+ in.face[f][j] = input->faces[fstart + j];
+ }
+ }
+ in.epsilon = static_cast<double>(input->epsilon);
+
+ blender::meshintersect::CDT_result<double> res = blender::meshintersect::delaunay_2d_calc(
+ in, output_type);
+
+ ::CDT_result *output = static_cast<::CDT_result *>(MEM_mallocN(sizeof(*output), __func__));
+ int nv = output->verts_len = res.vert.size();
+ int ne = output->edges_len = res.edge.size();
+ int nf = output->faces_len = res.face.size();
+ int tot_v_orig = 0;
+ int tot_e_orig = 0;
+ int tot_f_orig = 0;
+ int tot_f_lens = 0;
+ for (int v = 0; v < nv; ++v) {
+ tot_v_orig += res.vert_orig[v].size();
+ }
+ for (int e = 0; e < ne; ++e) {
+ tot_e_orig += res.edge_orig[e].size();
+ }
+ for (int f = 0; f < nf; ++f) {
+ tot_f_orig += res.face_orig[f].size();
+ tot_f_lens += res.face[f].size();
+ }
+
+ output->vert_coords = static_cast<decltype(output->vert_coords)>(
+ MEM_malloc_arrayN(nv, sizeof(output->vert_coords[0]), __func__));
+ output->verts_orig = static_cast<int *>(MEM_malloc_arrayN(tot_v_orig, sizeof(int), __func__));
+ output->verts_orig_start_table = static_cast<int *>(
+ MEM_malloc_arrayN(nv, sizeof(int), __func__));
+ output->verts_orig_len_table = static_cast<int *>(MEM_malloc_arrayN(nv, sizeof(int), __func__));
+ output->edges = static_cast<decltype(output->edges)>(
+ MEM_malloc_arrayN(ne, sizeof(output->edges[0]), __func__));
+ output->edges_orig = static_cast<int *>(MEM_malloc_arrayN(tot_e_orig, sizeof(int), __func__));
+ output->edges_orig_start_table = static_cast<int *>(
+ MEM_malloc_arrayN(ne, sizeof(int), __func__));
+ output->edges_orig_len_table = static_cast<int *>(MEM_malloc_arrayN(ne, sizeof(int), __func__));
+ output->faces = static_cast<int *>(MEM_malloc_arrayN(tot_f_lens, sizeof(int), __func__));
+ output->faces_start_table = static_cast<int *>(MEM_malloc_arrayN(nf, sizeof(int), __func__));
+ output->faces_len_table = static_cast<int *>(MEM_malloc_arrayN(nf, sizeof(int), __func__));
+ output->faces_orig = static_cast<int *>(MEM_malloc_arrayN(tot_f_orig, sizeof(int), __func__));
+ output->faces_orig_start_table = static_cast<int *>(
+ MEM_malloc_arrayN(nf, sizeof(int), __func__));
+ output->faces_orig_len_table = static_cast<int *>(MEM_malloc_arrayN(nf, sizeof(int), __func__));
+
+ int v_orig_index = 0;
+ for (int v = 0; v < nv; ++v) {
+ output->vert_coords[v][0] = static_cast<float>(res.vert[v][0]);
+ output->vert_coords[v][1] = static_cast<float>(res.vert[v][1]);
+ int this_start = v_orig_index;
+ output->verts_orig_start_table[v] = this_start;
+ for (int j : res.vert_orig[v].index_range()) {
+ output->verts_orig[v_orig_index++] = res.vert_orig[v][j];
+ }
+ output->verts_orig_len_table[v] = v_orig_index - this_start;
+ }
+ int e_orig_index = 0;
+ for (int e = 0; e < ne; ++e) {
+ output->edges[e][0] = res.edge[e].first;
+ output->edges[e][1] = res.edge[e].second;
+ int this_start = e_orig_index;
+ output->edges_orig_start_table[e] = this_start;
+ for (int j : res.edge_orig[e].index_range()) {
+ output->edges_orig[e_orig_index++] = res.edge_orig[e][j];
+ }
+ output->edges_orig_len_table[e] = e_orig_index - this_start;
+ }
+ int f_orig_index = 0;
+ int f_index = 0;
+ for (int f = 0; f < nf; ++f) {
+ output->faces_start_table[f] = f_index;
+ int flen = res.face[f].size();
+ output->faces_len_table[f] = flen;
+ for (int j = 0; j < flen; ++j) {
+ output->faces[f_index++] = res.face[f][j];
+ }
+ int this_start = f_orig_index;
+ output->faces_orig_start_table[f] = this_start;
+ for (int k : res.face_orig[f].index_range()) {
+ output->faces_orig[f_orig_index++] = res.face_orig[f][k];
+ }
+ output->faces_orig_len_table[f] = f_orig_index - this_start;
+ }
+ return output;
+}
+
+extern "C" void BLI_delaunay_2d_cdt_free(::CDT_result *result)
+{
+ MEM_freeN(result->vert_coords);
+ MEM_freeN(result->edges);
+ MEM_freeN(result->faces);
+ MEM_freeN(result->faces_start_table);
+ MEM_freeN(result->faces_len_table);
+ MEM_freeN(result->verts_orig);
+ MEM_freeN(result->verts_orig_start_table);
+ MEM_freeN(result->verts_orig_len_table);
+ MEM_freeN(result->edges_orig);
+ MEM_freeN(result->edges_orig_start_table);
+ MEM_freeN(result->edges_orig_len_table);
+ MEM_freeN(result->faces_orig);
+ MEM_freeN(result->faces_orig_start_table);
+ MEM_freeN(result->faces_orig_len_table);
+ MEM_freeN(result);
+}
diff --git a/source/blender/blenlib/intern/math_boolean.cc b/source/blender/blenlib/intern/math_boolean.cc
new file mode 100644
index 00000000000..fc6d117fa1d
--- /dev/null
+++ b/source/blender/blenlib/intern/math_boolean.cc
@@ -0,0 +1,2533 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_double2.hh"
+#include "BLI_double3.hh"
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_hash.hh"
+#include "BLI_math_boolean.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mpq2.hh"
+#include "BLI_mpq3.hh"
+#include "BLI_span.hh"
+#include "BLI_utildefines.h"
+
+namespace blender {
+
+#ifdef WITH_GMP
+/**
+ * Return +1 if a, b, c are in CCW order around a circle in the plane.
+ * Return -1 if they are in CW order, and 0 if they are in line.
+ */
+int orient2d(const mpq2 &a, const mpq2 &b, const mpq2 &c)
+{
+ mpq_class detleft = (a[0] - c[0]) * (b[1] - c[1]);
+ mpq_class detright = (a[1] - c[1]) * (b[0] - c[0]);
+ mpq_class det = detleft - detright;
+ return sgn(det);
+}
+
+/**
+ Return +1 if d is in the oriented circle through a, b, and c.
+ * The oriented circle goes CCW through a, b, and c.
+ * Return -1 if d is outside, and 0 if it is on the circle.
+ */
+int incircle(const mpq2 &a, const mpq2 &b, const mpq2 &c, const mpq2 &d)
+{
+ mpq_class adx = a[0] - d[0];
+ mpq_class bdx = b[0] - d[0];
+ mpq_class cdx = c[0] - d[0];
+ mpq_class ady = a[1] - d[1];
+ mpq_class bdy = b[1] - d[1];
+ mpq_class cdy = c[1] - d[1];
+
+ mpq_class bdxcdy = bdx * cdy;
+ mpq_class cdxbdy = cdx * bdy;
+ mpq_class alift = adx * adx + ady * ady;
+
+ mpq_class cdxady = cdx * ady;
+ mpq_class adxcdy = adx * cdy;
+ mpq_class blift = bdx * bdx + bdy * bdy;
+
+ mpq_class adxbdy = adx * bdy;
+ mpq_class bdxady = bdx * ady;
+ mpq_class clift = cdx * cdx + cdy * cdy;
+
+ mpq_class det = alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) +
+ clift * (adxbdy - bdxady);
+ return sgn(det);
+}
+
+/**
+ * Return +1 if d is below the plane containing a, b, c (which appear
+ * CCW when viewed from above the plane).
+ * Return -1 if d is above the plane.
+ * Return 0 if it is on the plane.
+ */
+int orient3d(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &d)
+{
+ mpq_class adx = a[0] - d[0];
+ mpq_class bdx = b[0] - d[0];
+ mpq_class cdx = c[0] - d[0];
+ mpq_class ady = a[1] - d[1];
+ mpq_class bdy = b[1] - d[1];
+ mpq_class cdy = c[1] - d[1];
+ mpq_class adz = a[2] - d[2];
+ mpq_class bdz = b[2] - d[2];
+ mpq_class cdz = c[2] - d[2];
+
+ mpq_class bdxcdy = bdx * cdy;
+ mpq_class cdxbdy = cdx * bdy;
+
+ mpq_class cdxady = cdx * ady;
+ mpq_class adxcdy = adx * cdy;
+
+ mpq_class adxbdy = adx * bdy;
+ mpq_class bdxady = bdx * ady;
+
+ mpq_class det = adz * (bdxcdy - cdxbdy) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady);
+ return sgn(det);
+}
+#endif /* WITH_GMP */
+
+/**
+ * For double versions of orient and incircle functions, use robust predicates
+ * that give exact answers for double inputs.
+ * First, encapsulate functions frm Jonathan Shewchuk's implementation.
+ * After this namespace, see the implementation of the double3 primitives.
+ */
+namespace robust_pred {
+
+/* Using Shewchuk's file here, edited to removed unneeded functions,
+ * change REAL to double everywhere, added const to some arguments,
+ * and to export only the following declared non-static functions.
+ *
+ * Since this is C++, an instantiated singleton class is used to make
+ * sure that exactinit() is called once.
+ * (Because of undefinedness of when this is called in initialization of all
+ * modules, other modules shouldn't use these functions in initialization.)
+ */
+
+void exactinit();
+double orient2dfast(const double *pa, const double *pb, const double *pc);
+double orient2d(const double *pa, const double *pb, const double *pc);
+double orient3dfast(const double *pa, const double *pb, const double *pc, const double *pd);
+double orient3d(const double *pa, const double *pb, const double *pc, const double *pd);
+double incirclefast(const double *pa, const double *pb, const double *pc, const double *pd);
+double incircle(const double *pa, const double *pb, const double *pc, const double *pd);
+double inspherefast(
+ const double *pa, const double *pb, const double *pc, const double *pd, const double *pe);
+double insphere(
+ const double *pa, const double *pb, const double *pc, const double *pd, const double *pe);
+
+class RobustInitCaller {
+ public:
+ RobustInitCaller()
+ {
+ exactinit();
+ }
+};
+
+static RobustInitCaller init_caller;
+
+/* Routines for Arbitrary Precision Floating-point Arithmetic
+ * and Fast Robust Geometric Predicates
+ * (predicates.c)
+ *
+ * May 18, 1996
+ *
+ * Placed in the public domain by
+ * Jonathan Richard Shewchuk
+ * School of Computer Science
+ * Carnegie Mellon University
+ * 5000 Forbes Avenue
+ * Pittsburgh, Pennsylvania 15213-3891
+ * jrs@cs.cmu.edu
+ *
+ * This file contains C implementation of algorithms for exact addition
+ * and multiplication of floating-point numbers, and predicates for
+ * robustly performing the orientation and incircle tests used in
+ * computational geometry. The algorithms and underlying theory are
+ * described in Jonathan Richard Shewchuk. "Adaptive Precision Floating-
+ * Point Arithmetic and Fast Robust Geometric Predicates." Technical
+ * Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon
+ * University, Pittsburgh, Pennsylvania, May 1996. (Submitted to
+ * Discrete & Computational Geometry.)
+ *
+ * This file, the paper listed above, and other information are available
+ * from the Web page http://www.cs.cmu.edu/~quake/robust.html .
+ *
+ *
+ * Using this code:
+ *
+ * First, read the short or long version of the paper (from the Web page above).
+ *
+ * Be sure to call #exactinit() once, before calling any of the arithmetic
+ * functions or geometric predicates. Also be sure to turn on the
+ * optimizer when compiling this file.
+ */
+
+/* On some machines, the exact arithmetic routines might be defeated by the
+ * use of internal extended precision floating-point registers. Sometimes
+ * this problem can be fixed by defining certain values to be volatile,
+ * thus forcing them to be stored to memory and rounded off. This isn't
+ * a great solution, though, as it slows the arithmetic down.
+ *
+ * To try this out, write "#define INEXACT volatile" below. Normally,
+ * however, INEXACT should be defined to be nothing. ("#define INEXACT".)
+ */
+
+#define INEXACT /* Nothing */
+/* #define INEXACT volatile */
+
+/* Which of the following two methods of finding the absolute values is
+ * fastest is compiler-dependent. A few compilers can inline and optimize
+ * the fabs() call; but most will incur the overhead of a function call,
+ * which is disastrously slow. A faster way on IEEE machines might be to
+ * mask the appropriate bit, but that's difficult to do in C.
+ */
+
+#define Absolute(a) ((a) >= 0.0 ? (a) : -(a))
+/* #define Absolute(a) fabs(a) */
+
+/* Many of the operations are broken up into two pieces, a main part that
+ * performs an approximate operation, and a "tail" that computes the
+ * round-off error of that operation.
+ *
+ * The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(),
+ * Split(), and Two_Product() are all implemented as described in the
+ * reference. Each of these macros requires certain variables to be
+ * defined in the calling routine. The variables `bvirt', `c', `abig',
+ * `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because
+ * they store the result of an operation that may incur round-off error.
+ * The input parameter `x' (or the highest numbered `x_' parameter) must
+ * also be declared `INEXACT'.
+ */
+
+#define Fast_Two_Sum_Tail(a, b, x, y) \
+ bvirt = x - a; \
+ y = b - bvirt
+
+#define Fast_Two_Sum(a, b, x, y) \
+ x = (double)(a + b); \
+ Fast_Two_Sum_Tail(a, b, x, y)
+
+#define Fast_Two_Diff_Tail(a, b, x, y) \
+ bvirt = a - x; \
+ y = bvirt - b
+
+#define Fast_Two_Diff(a, b, x, y) \
+ x = (double)(a - b); \
+ Fast_Two_Diff_Tail(a, b, x, y)
+
+#define Two_Sum_Tail(a, b, x, y) \
+ bvirt = (double)(x - a); \
+ avirt = x - bvirt; \
+ bround = b - bvirt; \
+ around = a - avirt; \
+ y = around + bround
+
+#define Two_Sum(a, b, x, y) \
+ x = (double)(a + b); \
+ Two_Sum_Tail(a, b, x, y)
+
+#define Two_Diff_Tail(a, b, x, y) \
+ bvirt = (double)(a - x); \
+ avirt = x + bvirt; \
+ bround = bvirt - b; \
+ around = a - avirt; \
+ y = around + bround
+
+#define Two_Diff(a, b, x, y) \
+ x = (double)(a - b); \
+ Two_Diff_Tail(a, b, x, y)
+
+#define Split(a, ahi, alo) \
+ c = (double)(splitter * a); \
+ abig = (double)(c - a); \
+ ahi = c - abig; \
+ alo = a - ahi
+
+#define Two_Product_Tail(a, b, x, y) \
+ Split(a, ahi, alo); \
+ Split(b, bhi, blo); \
+ err1 = x - (ahi * bhi); \
+ err2 = err1 - (alo * bhi); \
+ err3 = err2 - (ahi * blo); \
+ y = (alo * blo) - err3
+
+#define Two_Product(a, b, x, y) \
+ x = (double)(a * b); \
+ Two_Product_Tail(a, b, x, y)
+
+#define Two_Product_Presplit(a, b, bhi, blo, x, y) \
+ x = (double)(a * b); \
+ Split(a, ahi, alo); \
+ err1 = x - (ahi * bhi); \
+ err2 = err1 - (alo * bhi); \
+ err3 = err2 - (ahi * blo); \
+ y = (alo * blo) - err3
+
+#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \
+ x = (double)(a * b); \
+ err1 = x - (ahi * bhi); \
+ err2 = err1 - (alo * bhi); \
+ err3 = err2 - (ahi * blo); \
+ y = (alo * blo) - err3
+
+#define Square_Tail(a, x, y) \
+ Split(a, ahi, alo); \
+ err1 = x - (ahi * ahi); \
+ err3 = err1 - ((ahi + ahi) * alo); \
+ y = (alo * alo) - err3
+
+#define Square(a, x, y) \
+ x = (double)(a * a); \
+ Square_Tail(a, x, y)
+
+#define Two_One_Sum(a1, a0, b, x2, x1, x0) \
+ Two_Sum(a0, b, _i, x0); \
+ Two_Sum(a1, _i, x2, x1)
+
+#define Two_One_Diff(a1, a0, b, x2, x1, x0) \
+ Two_Diff(a0, b, _i, x0); \
+ Two_Sum(a1, _i, x2, x1)
+
+#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \
+ Two_One_Sum(a1, a0, b0, _j, _0, x0); \
+ Two_One_Sum(_j, _0, b1, x3, x2, x1)
+
+#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \
+ Two_One_Diff(a1, a0, b0, _j, _0, x0); \
+ Two_One_Diff(_j, _0, b1, x3, x2, x1)
+
+#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \
+ Two_One_Sum(a1, a0, b, _j, x1, x0); \
+ Two_One_Sum(a3, a2, _j, x4, x3, x2)
+
+#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \
+ Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \
+ Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1)
+
+#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \
+ Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \
+ Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2)
+
+#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, x3, x2, x1, x0) \
+ Four_One_Sum(a3, a2, a1, a0, b, _j, x3, x2, x1, x0); \
+ Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4)
+
+#define Eight_Two_Sum( \
+ a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \
+ Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, _1, _0, x0); \
+ Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, x3, x2, x1)
+
+#define Eight_Four_Sum(a7, \
+ a6, \
+ a5, \
+ a4, \
+ a3, \
+ a2, \
+ a1, \
+ a0, \
+ b4, \
+ b3, \
+ b1, \
+ b0, \
+ x11, \
+ x10, \
+ x9, \
+ x8, \
+ x7, \
+ x6, \
+ x5, \
+ x4, \
+ x3, \
+ x2, \
+ x1, \
+ x0) \
+ Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, _2, _1, _0, x1, x0); \
+ Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, x7, x6, x5, x4, x3, x2)
+
+#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \
+ Split(b, bhi, blo); \
+ Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \
+ Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, x1); \
+ Fast_Two_Sum(_j, _k, x3, x2)
+
+#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \
+ Split(b, bhi, blo); \
+ Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \
+ Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, x1); \
+ Fast_Two_Sum(_j, _k, _i, x2); \
+ Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, x3); \
+ Fast_Two_Sum(_j, _k, _i, x4); \
+ Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, x5); \
+ Fast_Two_Sum(_j, _k, x7, x6)
+
+#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \
+ Split(a0, a0hi, a0lo); \
+ Split(b0, bhi, blo); \
+ Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \
+ Split(a1, a1hi, a1lo); \
+ Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, _1); \
+ Fast_Two_Sum(_j, _k, _l, _2); \
+ Split(b1, bhi, blo); \
+ Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \
+ Two_Sum(_1, _0, _k, x1); \
+ Two_Sum(_2, _k, _j, _1); \
+ Two_Sum(_l, _j, _m, _2); \
+ Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _n, _0); \
+ Two_Sum(_1, _0, _i, x2); \
+ Two_Sum(_2, _i, _k, _1); \
+ Two_Sum(_m, _k, _l, _2); \
+ Two_Sum(_j, _n, _k, _0); \
+ Two_Sum(_1, _0, _j, x3); \
+ Two_Sum(_2, _j, _i, _1); \
+ Two_Sum(_l, _i, _m, _2); \
+ Two_Sum(_1, _k, _i, x4); \
+ Two_Sum(_2, _i, _k, x5); \
+ Two_Sum(_m, _k, x7, x6)
+
+#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \
+ Square(a0, _j, x0); \
+ _0 = a0 + a0; \
+ Two_Product(a1, _0, _k, _1); \
+ Two_One_Sum(_k, _1, _j, _l, _2, x1); \
+ Square(a1, _j, _1); \
+ Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2)
+
+static double splitter; /* = 2^ceiling(p / 2) + 1. Used to split floats in half. */
+static double epsilon; /* = 2^(-p). Used to estimate round-off errors. */
+/* A set of coefficients used to calculate maximum round-off errors. */
+static double resulterrbound;
+static double ccwerrboundA, ccwerrboundB, ccwerrboundC;
+static double o3derrboundA, o3derrboundB, o3derrboundC;
+static double iccerrboundA, iccerrboundB, iccerrboundC;
+static double isperrboundA, isperrboundB, isperrboundC;
+
+/**
+ * exactinit() Initialize the variables used for exact arithmetic.
+ *
+ * `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in
+ * floating-point arithmetic. `epsilon' bounds the relative round-off
+ * error. It is used for floating-point error analysis.
+ *
+ * `splitter' is used to split floating-point numbers into two half-
+ * length significands for exact multiplication.
+ *
+ * I imagine that a highly optimizing compiler might be too smart for its
+ * own good, and somehow cause this routine to fail, if it pretends that
+ * floating-point arithmetic is too much like real arithmetic.
+ *
+ * Don't change this routine unless you fully understand it.
+ */
+
+void exactinit()
+{
+ double half;
+ double check, lastcheck;
+ int every_other;
+
+ every_other = 1;
+ half = 0.5;
+ epsilon = 1.0;
+ splitter = 1.0;
+ check = 1.0;
+ /* Repeatedly divide `epsilon' by two until it is too small to add to
+ * one without causing round-off. (Also check if the sum is equal to
+ * the previous sum, for machines that round up instead of using exact
+ * rounding. Not that this library will work on such machines anyway. */
+ do {
+ lastcheck = check;
+ epsilon *= half;
+ if (every_other) {
+ splitter *= 2.0;
+ }
+ every_other = !every_other;
+ check = 1.0 + epsilon;
+ } while ((check != 1.0) && (check != lastcheck));
+ splitter += 1.0;
+
+ /* Error bounds for orientation and #incircle tests. */
+ resulterrbound = (3.0 + 8.0 * epsilon) * epsilon;
+ ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon;
+ ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon;
+ ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon;
+ o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon;
+ o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon;
+ o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon;
+ iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon;
+ iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon;
+ iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon;
+ isperrboundA = (16.0 + 224.0 * epsilon) * epsilon;
+ isperrboundB = (5.0 + 72.0 * epsilon) * epsilon;
+ isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon;
+}
+
+/**
+ * fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero
+ * components from the output expansion.
+ *
+ * Sets h = e + f. See the long version of my paper for details.
+ * h cannot be e or f.
+ */
+static int fast_expansion_sum_zeroelim(
+ int elen, const double *e, int flen, const double *f, double *h)
+{
+ double Q;
+ INEXACT double Qnew;
+ INEXACT double hh;
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ int eindex, findex, hindex;
+ double enow, fnow;
+
+ enow = e[0];
+ fnow = f[0];
+ eindex = findex = 0;
+ if ((fnow > enow) == (fnow > -enow)) {
+ Q = enow;
+ enow = e[++eindex];
+ }
+ else {
+ Q = fnow;
+ fnow = f[++findex];
+ }
+ hindex = 0;
+ if ((eindex < elen) && (findex < flen)) {
+ if ((fnow > enow) == (fnow > -enow)) {
+ Fast_Two_Sum(enow, Q, Qnew, hh);
+ enow = e[++eindex];
+ }
+ else {
+ Fast_Two_Sum(fnow, Q, Qnew, hh);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh != 0.0) {
+ h[hindex++] = hh;
+ }
+ while ((eindex < elen) && (findex < flen)) {
+ if ((fnow > enow) == (fnow > -enow)) {
+ Two_Sum(Q, enow, Qnew, hh);
+ enow = e[++eindex];
+ }
+ else {
+ Two_Sum(Q, fnow, Qnew, hh);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh != 0.0) {
+ h[hindex++] = hh;
+ }
+ }
+ }
+ while (eindex < elen) {
+ Two_Sum(Q, enow, Qnew, hh);
+ enow = e[++eindex];
+ Q = Qnew;
+ if (hh != 0.0) {
+ h[hindex++] = hh;
+ }
+ }
+ while (findex < flen) {
+ Two_Sum(Q, fnow, Qnew, hh);
+ fnow = f[++findex];
+ Q = Qnew;
+ if (hh != 0.0) {
+ h[hindex++] = hh;
+ }
+ }
+ if ((Q != 0.0) || (hindex == 0)) {
+ h[hindex++] = Q;
+ }
+ return hindex;
+}
+
+/* scale_expansion_zeroelim() Multiply an expansion by a scalar,
+ * eliminating zero components from the
+ * output expansion.
+ *
+ * Sets h = be. See either version of my paper for details.
+ * e and h cannot be the same.
+ */
+static int scale_expansion_zeroelim(int elen, const double *e, double b, double *h)
+{
+ INEXACT double Q, sum;
+ double hh;
+ INEXACT double product1;
+ double product0;
+ int eindex, hindex;
+ double enow;
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+
+ Split(b, bhi, blo);
+ Two_Product_Presplit(e[0], b, bhi, blo, Q, hh);
+ hindex = 0;
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ for (eindex = 1; eindex < elen; eindex++) {
+ enow = e[eindex];
+ Two_Product_Presplit(enow, b, bhi, blo, product1, product0);
+ Two_Sum(Q, product0, sum, hh);
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ Fast_Two_Sum(product1, sum, Q, hh);
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ }
+ if ((Q != 0.0) || (hindex == 0)) {
+ h[hindex++] = Q;
+ }
+ return hindex;
+}
+
+/* estimate() Produce a one-word estimate of an expansion's value. */
+static double estimate(int elen, const double *e)
+{
+ double Q;
+ int eindex;
+
+ Q = e[0];
+ for (eindex = 1; eindex < elen; eindex++) {
+ Q += e[eindex];
+ }
+ return Q;
+}
+
+/**
+ * orient2dfast() Approximate 2D orientation test. Non-robust.
+ * orient2d() Adaptive exact 2D orientation test. Robust.
+ * Return a positive value if the points pa, pb, and pc occur
+ * in counterclockwise order; a negative value if they occur
+ * in clockwise order; and zero if they are co-linear. The
+ * result is also a rough approximation of twice the signed
+ * area of the triangle defined by the three points.
+ *
+ * The second uses exact arithmetic to ensure a correct answer. The
+ * result returned is the determinant of a matrix. In orient2d() only,
+ * this determinant is computed adaptively, in the sense that exact
+ * arithmetic is used only to the degree it is needed to ensure that the
+ * returned value has the correct sign. Hence, orient2d() is usually quite
+ * fast, but will run more slowly when the input points are co-linear or
+ * nearly so.
+ */
+
+double orient2dfast(const double *pa, const double *pb, const double *pc)
+{
+ double acx, bcx, acy, bcy;
+
+ acx = pa[0] - pc[0];
+ bcx = pb[0] - pc[0];
+ acy = pa[1] - pc[1];
+ bcy = pb[1] - pc[1];
+ return acx * bcy - acy * bcx;
+}
+
+static double orient2dadapt(const double *pa, const double *pb, const double *pc, double detsum)
+{
+ INEXACT double acx, acy, bcx, bcy;
+ double acxtail, acytail, bcxtail, bcytail;
+ INEXACT double detleft, detright;
+ double detlefttail, detrighttail;
+ double det, errbound;
+ double B[4], C1[8], C2[12], D[16];
+ INEXACT double B3;
+ int C1length, C2length, Dlength;
+ double u[4];
+ INEXACT double u3;
+ INEXACT double s1, t1;
+ double s0, t0;
+
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+ INEXACT double _i, _j;
+ double _0;
+
+ acx = (double)(pa[0] - pc[0]);
+ bcx = (double)(pb[0] - pc[0]);
+ acy = (double)(pa[1] - pc[1]);
+ bcy = (double)(pb[1] - pc[1]);
+
+ Two_Product(acx, bcy, detleft, detlefttail);
+ Two_Product(acy, bcx, detright, detrighttail);
+
+ Two_Two_Diff(detleft, detlefttail, detright, detrighttail, B3, B[2], B[1], B[0]);
+ B[3] = B3;
+
+ det = estimate(4, B);
+ errbound = ccwerrboundB * detsum;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pc[0], acx, acxtail);
+ Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail);
+ Two_Diff_Tail(pa[1], pc[1], acy, acytail);
+ Two_Diff_Tail(pb[1], pc[1], bcy, bcytail);
+
+ if ((acxtail == 0.0) && (acytail == 0.0) && (bcxtail == 0.0) && (bcytail == 0.0)) {
+ return det;
+ }
+
+ errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det);
+ det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Product(acxtail, bcy, s1, s0);
+ Two_Product(acytail, bcx, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1);
+
+ Two_Product(acx, bcytail, s1, s0);
+ Two_Product(acy, bcxtail, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2);
+
+ Two_Product(acxtail, bcytail, s1, s0);
+ Two_Product(acytail, bcxtail, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D);
+
+ return (D[Dlength - 1]);
+}
+
+double orient2d(const double *pa, const double *pb, const double *pc)
+{
+ double detleft, detright, det;
+ double detsum, errbound;
+
+ detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]);
+ detright = (pa[1] - pc[1]) * (pb[0] - pc[0]);
+ det = detleft - detright;
+
+ if (detleft > 0.0) {
+ if (detright <= 0.0) {
+ return det;
+ }
+ detsum = detleft + detright;
+ }
+ else if (detleft < 0.0) {
+ if (detright >= 0.0) {
+ return det;
+ }
+ detsum = -detleft - detright;
+ }
+ else {
+ return det;
+ }
+
+ errbound = ccwerrboundA * detsum;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ return orient2dadapt(pa, pb, pc, detsum);
+}
+
+/**
+ * orient3dfast() Approximate 3D orientation test. Nonrobust.
+ * orient3d() Adaptive exact 3D orientation test. Robust.
+ *
+ * Return a positive value if the point pd lies below the
+ * plane passing through pa, pb, and pc; "below" is defined so
+ * that pa, pb, and pc appear in counterclockwise order when
+ * viewed from above the plane. Returns a negative value if
+ * pd lies above the plane. Returns zero if the points are
+ * co-planar. The result is also a rough approximation of six
+ * times the signed volume of the tetrahedron defined by the
+ * four points.
+ *
+ * The second uses exact arithmetic to ensure a correct answer. The
+ * result returned is the determinant of a matrix. In orient3d() only,
+ * this determinant is computed adaptively, in the sense that exact
+ * arithmetic is used only to the degree it is needed to ensure that the
+ * returned value has the correct sign. Hence, orient3d() is usually quite
+ * fast, but will run more slowly when the input points are co-planar or
+ * nearly so.
+ */
+
+double orient3dfast(const double *pa, const double *pb, const double *pc, const double *pd)
+{
+ double adx, bdx, cdx;
+ double ady, bdy, cdy;
+ double adz, bdz, cdz;
+
+ adx = pa[0] - pd[0];
+ bdx = pb[0] - pd[0];
+ cdx = pc[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdy = pb[1] - pd[1];
+ cdy = pc[1] - pd[1];
+ adz = pa[2] - pd[2];
+ bdz = pb[2] - pd[2];
+ cdz = pc[2] - pd[2];
+
+ return adx * (bdy * cdz - bdz * cdy) + bdx * (cdy * adz - cdz * ady) +
+ cdx * (ady * bdz - adz * bdy);
+}
+
+/**
+ * \note since this code comes from an external source, prefer not to break it
+ * up to fix this clang-tidy warning.
+ * NOLINTNEXTLINE: readability-function-size
+ */
+static double orient3dadapt(
+ const double *pa, const double *pb, const double *pc, const double *pd, double permanent)
+{
+ INEXACT double adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz;
+ double det, errbound;
+
+ INEXACT double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1;
+ double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0;
+ double bc[4], ca[4], ab[4];
+ INEXACT double bc3, ca3, ab3;
+ double adet[8], bdet[8], cdet[8];
+ int alen, blen, clen;
+ double abdet[16];
+ int ablen;
+ double *finnow, *finother, *finswap;
+ double fin1[192], fin2[192];
+ int finlength;
+
+ double adxtail, bdxtail, cdxtail;
+ double adytail, bdytail, cdytail;
+ double adztail, bdztail, cdztail;
+ INEXACT double at_blarge, at_clarge;
+ INEXACT double bt_clarge, bt_alarge;
+ INEXACT double ct_alarge, ct_blarge;
+ double at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4];
+ int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen;
+ INEXACT double bdxt_cdy1, cdxt_bdy1, cdxt_ady1;
+ INEXACT double adxt_cdy1, adxt_bdy1, bdxt_ady1;
+ double bdxt_cdy0, cdxt_bdy0, cdxt_ady0;
+ double adxt_cdy0, adxt_bdy0, bdxt_ady0;
+ INEXACT double bdyt_cdx1, cdyt_bdx1, cdyt_adx1;
+ INEXACT double adyt_cdx1, adyt_bdx1, bdyt_adx1;
+ double bdyt_cdx0, cdyt_bdx0, cdyt_adx0;
+ double adyt_cdx0, adyt_bdx0, bdyt_adx0;
+ double bct[8], cat[8], abt[8];
+ int bctlen, catlen, abtlen;
+ INEXACT double bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1;
+ INEXACT double adxt_cdyt1, adxt_bdyt1, bdxt_adyt1;
+ double bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0;
+ double adxt_cdyt0, adxt_bdyt0, bdxt_adyt0;
+ double u[4], v[12], w[16];
+ INEXACT double u3;
+ int vlength, wlength;
+ double negate;
+
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+ INEXACT double _i, _j, _k;
+ double _0;
+
+ adx = (double)(pa[0] - pd[0]);
+ bdx = (double)(pb[0] - pd[0]);
+ cdx = (double)(pc[0] - pd[0]);
+ ady = (double)(pa[1] - pd[1]);
+ bdy = (double)(pb[1] - pd[1]);
+ cdy = (double)(pc[1] - pd[1]);
+ adz = (double)(pa[2] - pd[2]);
+ bdz = (double)(pb[2] - pd[2]);
+ cdz = (double)(pc[2] - pd[2]);
+
+ Two_Product(bdx, cdy, bdxcdy1, bdxcdy0);
+ Two_Product(cdx, bdy, cdxbdy1, cdxbdy0);
+ Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]);
+ bc[3] = bc3;
+ alen = scale_expansion_zeroelim(4, bc, adz, adet);
+
+ Two_Product(cdx, ady, cdxady1, cdxady0);
+ Two_Product(adx, cdy, adxcdy1, adxcdy0);
+ Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]);
+ ca[3] = ca3;
+ blen = scale_expansion_zeroelim(4, ca, bdz, bdet);
+
+ Two_Product(adx, bdy, adxbdy1, adxbdy0);
+ Two_Product(bdx, ady, bdxady1, bdxady0);
+ Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]);
+ ab[3] = ab3;
+ clen = scale_expansion_zeroelim(4, ab, cdz, cdet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1);
+
+ det = estimate(finlength, fin1);
+ errbound = o3derrboundB * permanent;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pd[0], adx, adxtail);
+ Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail);
+ Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail);
+ Two_Diff_Tail(pa[1], pd[1], ady, adytail);
+ Two_Diff_Tail(pb[1], pd[1], bdy, bdytail);
+ Two_Diff_Tail(pc[1], pd[1], cdy, cdytail);
+ Two_Diff_Tail(pa[2], pd[2], adz, adztail);
+ Two_Diff_Tail(pb[2], pd[2], bdz, bdztail);
+ Two_Diff_Tail(pc[2], pd[2], cdz, cdztail);
+
+ if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) && (adytail == 0.0) &&
+ (bdytail == 0.0) && (cdytail == 0.0) && (adztail == 0.0) && (bdztail == 0.0) &&
+ (cdztail == 0.0)) {
+ return det;
+ }
+
+ errbound = o3derrboundC * permanent + resulterrbound * Absolute(det);
+ det += (adz * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) +
+ adztail * (bdx * cdy - bdy * cdx)) +
+ (bdz * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) +
+ bdztail * (cdx * ady - cdy * adx)) +
+ (cdz * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) +
+ cdztail * (adx * bdy - ady * bdx));
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ finnow = fin1;
+ finother = fin2;
+
+ if (adxtail == 0.0) {
+ if (adytail == 0.0) {
+ at_b[0] = 0.0;
+ at_blen = 1;
+ at_c[0] = 0.0;
+ at_clen = 1;
+ }
+ else {
+ negate = -adytail;
+ Two_Product(negate, bdx, at_blarge, at_b[0]);
+ at_b[1] = at_blarge;
+ at_blen = 2;
+ Two_Product(adytail, cdx, at_clarge, at_c[0]);
+ at_c[1] = at_clarge;
+ at_clen = 2;
+ }
+ }
+ else {
+ if (adytail == 0.0) {
+ Two_Product(adxtail, bdy, at_blarge, at_b[0]);
+ at_b[1] = at_blarge;
+ at_blen = 2;
+ negate = -adxtail;
+ Two_Product(negate, cdy, at_clarge, at_c[0]);
+ at_c[1] = at_clarge;
+ at_clen = 2;
+ }
+ else {
+ Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0);
+ Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0);
+ Two_Two_Diff(
+ adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, at_blarge, at_b[2], at_b[1], at_b[0]);
+ at_b[3] = at_blarge;
+ at_blen = 4;
+ Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0);
+ Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0);
+ Two_Two_Diff(
+ adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, at_clarge, at_c[2], at_c[1], at_c[0]);
+ at_c[3] = at_clarge;
+ at_clen = 4;
+ }
+ }
+ if (bdxtail == 0.0) {
+ if (bdytail == 0.0) {
+ bt_c[0] = 0.0;
+ bt_clen = 1;
+ bt_a[0] = 0.0;
+ bt_alen = 1;
+ }
+ else {
+ negate = -bdytail;
+ Two_Product(negate, cdx, bt_clarge, bt_c[0]);
+ bt_c[1] = bt_clarge;
+ bt_clen = 2;
+ Two_Product(bdytail, adx, bt_alarge, bt_a[0]);
+ bt_a[1] = bt_alarge;
+ bt_alen = 2;
+ }
+ }
+ else {
+ if (bdytail == 0.0) {
+ Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]);
+ bt_c[1] = bt_clarge;
+ bt_clen = 2;
+ negate = -bdxtail;
+ Two_Product(negate, ady, bt_alarge, bt_a[0]);
+ bt_a[1] = bt_alarge;
+ bt_alen = 2;
+ }
+ else {
+ Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0);
+ Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0);
+ Two_Two_Diff(
+ bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, bt_clarge, bt_c[2], bt_c[1], bt_c[0]);
+ bt_c[3] = bt_clarge;
+ bt_clen = 4;
+ Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0);
+ Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0);
+ Two_Two_Diff(
+ bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, bt_alarge, bt_a[2], bt_a[1], bt_a[0]);
+ bt_a[3] = bt_alarge;
+ bt_alen = 4;
+ }
+ }
+ if (cdxtail == 0.0) {
+ if (cdytail == 0.0) {
+ ct_a[0] = 0.0;
+ ct_alen = 1;
+ ct_b[0] = 0.0;
+ ct_blen = 1;
+ }
+ else {
+ negate = -cdytail;
+ Two_Product(negate, adx, ct_alarge, ct_a[0]);
+ ct_a[1] = ct_alarge;
+ ct_alen = 2;
+ Two_Product(cdytail, bdx, ct_blarge, ct_b[0]);
+ ct_b[1] = ct_blarge;
+ ct_blen = 2;
+ }
+ }
+ else {
+ if (cdytail == 0.0) {
+ Two_Product(cdxtail, ady, ct_alarge, ct_a[0]);
+ ct_a[1] = ct_alarge;
+ ct_alen = 2;
+ negate = -cdxtail;
+ Two_Product(negate, bdy, ct_blarge, ct_b[0]);
+ ct_b[1] = ct_blarge;
+ ct_blen = 2;
+ }
+ else {
+ Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0);
+ Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0);
+ Two_Two_Diff(
+ cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, ct_alarge, ct_a[2], ct_a[1], ct_a[0]);
+ ct_a[3] = ct_alarge;
+ ct_alen = 4;
+ Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0);
+ Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0);
+ Two_Two_Diff(
+ cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, ct_blarge, ct_b[2], ct_b[1], ct_b[0]);
+ ct_b[3] = ct_blarge;
+ ct_blen = 4;
+ }
+ }
+
+ bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct);
+ wlength = scale_expansion_zeroelim(bctlen, bct, adz, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat);
+ wlength = scale_expansion_zeroelim(catlen, cat, bdz, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt);
+ wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ if (adztail != 0.0) {
+ vlength = scale_expansion_zeroelim(4, bc, adztail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdztail != 0.0) {
+ vlength = scale_expansion_zeroelim(4, ca, bdztail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdztail != 0.0) {
+ vlength = scale_expansion_zeroelim(4, ab, cdztail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ if (adxtail != 0.0) {
+ if (bdytail != 0.0) {
+ Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0);
+ Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (cdztail != 0.0) {
+ Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ if (cdytail != 0.0) {
+ negate = -adxtail;
+ Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0);
+ Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (bdztail != 0.0) {
+ Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ }
+ if (bdxtail != 0.0) {
+ if (cdytail != 0.0) {
+ Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0);
+ Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (adztail != 0.0) {
+ Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ if (adytail != 0.0) {
+ negate = -bdxtail;
+ Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0);
+ Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (cdztail != 0.0) {
+ Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ }
+ if (cdxtail != 0.0) {
+ if (adytail != 0.0) {
+ Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0);
+ Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (bdztail != 0.0) {
+ Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ if (bdytail != 0.0) {
+ negate = -cdxtail;
+ Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0);
+ Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (adztail != 0.0) {
+ Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ }
+
+ if (adztail != 0.0) {
+ wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdztail != 0.0) {
+ wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdztail != 0.0) {
+ wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ return finnow[finlength - 1];
+}
+
+double orient3d(const double *pa, const double *pb, const double *pc, const double *pd)
+{
+ double adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz;
+ double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ double det;
+ double permanent, errbound;
+
+ adx = pa[0] - pd[0];
+ bdx = pb[0] - pd[0];
+ cdx = pc[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdy = pb[1] - pd[1];
+ cdy = pc[1] - pd[1];
+ adz = pa[2] - pd[2];
+ bdz = pb[2] - pd[2];
+ cdz = pc[2] - pd[2];
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+
+ det = adz * (bdxcdy - cdxbdy) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady);
+
+ permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) +
+ (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) +
+ (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz);
+ errbound = o3derrboundA * permanent;
+ if ((det > errbound) || (-det > errbound)) {
+ return det;
+ }
+
+ return orient3dadapt(pa, pb, pc, pd, permanent);
+}
+
+/**
+ * incirclefast() Approximate 2D incircle test. Non-robust.
+ * incircle()
+ *
+ * Return a positive value if the point pd lies inside the
+ * circle passing through pa, pb, and pc; a negative value if
+ * it lies outside; and zero if the four points are co-circular.
+ * The points pa, pb, and pc must be in counterclockwise
+ * order, or the sign of the result will be reversed.
+ *
+ * The second uses exact arithmetic to ensure a correct answer. The
+ * result returned is the determinant of a matrix. In incircle() only,
+ * this determinant is computed adaptively, in the sense that exact
+ * arithmetic is used only to the degree it is needed to ensure that the
+ * returned value has the correct sign. Hence, incircle() is usually quite
+ * fast, but will run more slowly when the input points are co-circular or
+ * nearly so.
+ */
+
+double incirclefast(const double *pa, const double *pb, const double *pc, const double *pd)
+{
+ double adx, ady, bdx, bdy, cdx, cdy;
+ double abdet, bcdet, cadet;
+ double alift, blift, clift;
+
+ adx = pa[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdx = pb[0] - pd[0];
+ bdy = pb[1] - pd[1];
+ cdx = pc[0] - pd[0];
+ cdy = pc[1] - pd[1];
+
+ abdet = adx * bdy - bdx * ady;
+ bcdet = bdx * cdy - cdx * bdy;
+ cadet = cdx * ady - adx * cdy;
+ alift = adx * adx + ady * ady;
+ blift = bdx * bdx + bdy * bdy;
+ clift = cdx * cdx + cdy * cdy;
+
+ return alift * bcdet + blift * cadet + clift * abdet;
+}
+
+/**
+ * \note since this code comes from an external source, prefer not to break it
+ * up to fix this clang-tidy warning.
+ * NOLINTNEXTLINE: readability-function-size
+ */
+static double incircleadapt(
+ const double *pa, const double *pb, const double *pc, const double *pd, double permanent)
+{
+ INEXACT double adx, bdx, cdx, ady, bdy, cdy;
+ double det, errbound;
+
+ INEXACT double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1;
+ double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0;
+ double bc[4], ca[4], ab[4];
+ INEXACT double bc3, ca3, ab3;
+ double axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32];
+ int axbclen, axxbclen, aybclen, ayybclen, alen;
+ double bxca[8], bxxca[16], byca[8], byyca[16], bdet[32];
+ int bxcalen, bxxcalen, bycalen, byycalen, blen;
+ double cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32];
+ int cxablen, cxxablen, cyablen, cyyablen, clen;
+ double abdet[64];
+ int ablen;
+ double fin1[1152], fin2[1152];
+ double *finnow, *finother, *finswap;
+ int finlength;
+
+ double adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail;
+ INEXACT double adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1;
+ double adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0;
+ double aa[4], bb[4], cc[4];
+ INEXACT double aa3, bb3, cc3;
+ INEXACT double ti1, tj1;
+ double ti0, tj0;
+ double u[4], v[4];
+ INEXACT double u3, v3;
+ double temp8[8], temp16a[16], temp16b[16], temp16c[16];
+ double temp32a[32], temp32b[32], temp48[48], temp64[64];
+ int temp8len, temp16alen, temp16blen, temp16clen;
+ int temp32alen, temp32blen, temp48len, temp64len;
+ double axtbb[8], axtcc[8], aytbb[8], aytcc[8];
+ int axtbblen, axtcclen, aytbblen, aytcclen;
+ double bxtaa[8], bxtcc[8], bytaa[8], bytcc[8];
+ int bxtaalen, bxtcclen, bytaalen, bytcclen;
+ double cxtaa[8], cxtbb[8], cytaa[8], cytbb[8];
+ int cxtaalen, cxtbblen, cytaalen, cytbblen;
+ double axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8];
+ int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen;
+ double axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16];
+ int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen;
+ double axtbctt[8], aytbctt[8], bxtcatt[8];
+ double bytcatt[8], cxtabtt[8], cytabtt[8];
+ int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen;
+ double abt[8], bct[8], cat[8];
+ int abtlen, bctlen, catlen;
+ double abtt[4], bctt[4], catt[4];
+ int abttlen, bcttlen, cattlen;
+ INEXACT double abtt3, bctt3, catt3;
+ double negate;
+
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+ INEXACT double _i, _j;
+ double _0;
+
+ adx = (double)(pa[0] - pd[0]);
+ bdx = (double)(pb[0] - pd[0]);
+ cdx = (double)(pc[0] - pd[0]);
+ ady = (double)(pa[1] - pd[1]);
+ bdy = (double)(pb[1] - pd[1]);
+ cdy = (double)(pc[1] - pd[1]);
+
+ Two_Product(bdx, cdy, bdxcdy1, bdxcdy0);
+ Two_Product(cdx, bdy, cdxbdy1, cdxbdy0);
+ Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]);
+ bc[3] = bc3;
+ axbclen = scale_expansion_zeroelim(4, bc, adx, axbc);
+ axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc);
+ aybclen = scale_expansion_zeroelim(4, bc, ady, aybc);
+ ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc);
+ alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet);
+
+ Two_Product(cdx, ady, cdxady1, cdxady0);
+ Two_Product(adx, cdy, adxcdy1, adxcdy0);
+ Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]);
+ ca[3] = ca3;
+ bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca);
+ bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca);
+ bycalen = scale_expansion_zeroelim(4, ca, bdy, byca);
+ byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca);
+ blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet);
+
+ Two_Product(adx, bdy, adxbdy1, adxbdy0);
+ Two_Product(bdx, ady, bdxady1, bdxady0);
+ Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]);
+ ab[3] = ab3;
+ cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab);
+ cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab);
+ cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab);
+ cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab);
+ clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1);
+
+ det = estimate(finlength, fin1);
+ errbound = iccerrboundB * permanent;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pd[0], adx, adxtail);
+ Two_Diff_Tail(pa[1], pd[1], ady, adytail);
+ Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail);
+ Two_Diff_Tail(pb[1], pd[1], bdy, bdytail);
+ Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail);
+ Two_Diff_Tail(pc[1], pd[1], cdy, cdytail);
+ if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) && (adytail == 0.0) &&
+ (bdytail == 0.0) && (cdytail == 0.0)) {
+ return det;
+ }
+
+ errbound = iccerrboundC * permanent + resulterrbound * Absolute(det);
+ det += ((adx * adx + ady * ady) *
+ ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) +
+ 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) +
+ ((bdx * bdx + bdy * bdy) *
+ ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) +
+ 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) +
+ ((cdx * cdx + cdy * cdy) *
+ ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) +
+ 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx));
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ finnow = fin1;
+ finother = fin2;
+
+ if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) {
+ Square(adx, adxadx1, adxadx0);
+ Square(ady, adyady1, adyady0);
+ Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]);
+ aa[3] = aa3;
+ }
+ if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) {
+ Square(bdx, bdxbdx1, bdxbdx0);
+ Square(bdy, bdybdy1, bdybdy0);
+ Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]);
+ bb[3] = bb3;
+ }
+ if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) {
+ Square(cdx, cdxcdx1, cdxcdx0);
+ Square(cdy, cdycdy1, cdycdy0);
+ Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]);
+ cc[3] = cc3;
+ }
+
+ if (adxtail != 0.0) {
+ axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc);
+ temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, temp16a);
+
+ axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc);
+ temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b);
+
+ axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb);
+ temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (adytail != 0.0) {
+ aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc);
+ temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, temp16a);
+
+ aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb);
+ temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b);
+
+ aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc);
+ temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdxtail != 0.0) {
+ bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca);
+ temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, temp16a);
+
+ bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa);
+ temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b);
+
+ bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc);
+ temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdytail != 0.0) {
+ bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca);
+ temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, temp16a);
+
+ bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc);
+ temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b);
+
+ bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa);
+ temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdxtail != 0.0) {
+ cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab);
+ temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, temp16a);
+
+ cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb);
+ temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b);
+
+ cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa);
+ temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdytail != 0.0) {
+ cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab);
+ temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, temp16a);
+
+ cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa);
+ temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b);
+
+ cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb);
+ temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ if ((adxtail != 0.0) || (adytail != 0.0)) {
+ if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) {
+ Two_Product(bdxtail, cdy, ti1, ti0);
+ Two_Product(bdx, cdytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -bdy;
+ Two_Product(cdxtail, negate, ti1, ti0);
+ negate = -bdytail;
+ Two_Product(cdx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct);
+
+ Two_Product(bdxtail, cdytail, ti1, ti0);
+ Two_Product(cdxtail, bdytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]);
+ bctt[3] = bctt3;
+ bcttlen = 4;
+ }
+ else {
+ bct[0] = 0.0;
+ bctlen = 1;
+ bctt[0] = 0.0;
+ bcttlen = 1;
+ }
+
+ if (adxtail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a);
+ axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct);
+ temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (bdytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, temp32a);
+ axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt);
+ temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, temp16a);
+ temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (adytail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a);
+ aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct);
+ temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, temp32a);
+ aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt);
+ temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, temp16a);
+ temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ if ((bdxtail != 0.0) || (bdytail != 0.0)) {
+ if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) {
+ Two_Product(cdxtail, ady, ti1, ti0);
+ Two_Product(cdx, adytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -cdy;
+ Two_Product(adxtail, negate, ti1, ti0);
+ negate = -cdytail;
+ Two_Product(adx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat);
+
+ Two_Product(cdxtail, adytail, ti1, ti0);
+ Two_Product(adxtail, cdytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]);
+ catt[3] = catt3;
+ cattlen = 4;
+ }
+ else {
+ cat[0] = 0.0;
+ catlen = 1;
+ catt[0] = 0.0;
+ cattlen = 1;
+ }
+
+ if (bdxtail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a);
+ bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat);
+ temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (cdytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (adytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, temp32a);
+ bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt);
+ temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, temp16a);
+ temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdytail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a);
+ bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat);
+ temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, temp32a);
+ bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt);
+ temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, temp16a);
+ temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+ if ((cdxtail != 0.0) || (cdytail != 0.0)) {
+ if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) {
+ Two_Product(adxtail, bdy, ti1, ti0);
+ Two_Product(adx, bdytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -ady;
+ Two_Product(bdxtail, negate, ti1, ti0);
+ negate = -adytail;
+ Two_Product(bdx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt);
+
+ Two_Product(adxtail, bdytail, ti1, ti0);
+ Two_Product(bdxtail, adytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]);
+ abtt[3] = abtt3;
+ abttlen = 4;
+ }
+ else {
+ abt[0] = 0.0;
+ abtlen = 1;
+ abtt[0] = 0.0;
+ abttlen = 1;
+ }
+
+ if (cdxtail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a);
+ cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt);
+ temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ if (adytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (bdytail != 0.0) {
+ temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, temp16a, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, temp32a);
+ cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt);
+ temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, temp16a);
+ temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ if (cdytail != 0.0) {
+ temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a);
+ cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt);
+ temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, temp48, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+
+ temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, temp32a);
+ cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt);
+ temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, temp16a);
+ temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, temp64, finother);
+ finswap = finnow;
+ finnow = finother;
+ finother = finswap;
+ }
+ }
+
+ return finnow[finlength - 1];
+}
+
+double incircle(const double *pa, const double *pb, const double *pc, const double *pd)
+{
+ double adx, bdx, cdx, ady, bdy, cdy;
+ double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ double alift, blift, clift;
+ double det;
+ double permanent, errbound;
+
+ adx = pa[0] - pd[0];
+ bdx = pb[0] - pd[0];
+ cdx = pc[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdy = pb[1] - pd[1];
+ cdy = pc[1] - pd[1];
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+ alift = adx * adx + ady * ady;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+ blift = bdx * bdx + bdy * bdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+ clift = cdx * cdx + cdy * cdy;
+
+ det = alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) + clift * (adxbdy - bdxady);
+
+ permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift +
+ (Absolute(cdxady) + Absolute(adxcdy)) * blift +
+ (Absolute(adxbdy) + Absolute(bdxady)) * clift;
+ errbound = iccerrboundA * permanent;
+ if ((det > errbound) || (-det > errbound)) {
+ return det;
+ }
+
+ return incircleadapt(pa, pb, pc, pd, permanent);
+}
+
+/**
+ * inspherefast() Approximate 3D insphere test. Non-robust.
+ * insphere() Adaptive exact 3D insphere test. Robust.
+ *
+ * Return a positive value if the point pe lies inside the
+ * sphere passing through pa, pb, pc, and pd; a negative value
+ * if it lies outside; and zero if the five points are
+ * co-spherical. The points pa, pb, pc, and pd must be ordered
+ * so that they have a positive orientation (as defined by
+ * orient3d()), or the sign of the result will be reversed.
+ *
+ * The second uses exact arithmetic to ensure a correct answer. The
+ * result returned is the determinant of a matrix. In insphere() only,
+ * this determinant is computed adaptively, in the sense that exact
+ * arithmetic is used only to the degree it is needed to ensure that the
+ * returned value has the correct sign. Hence, insphere() is usually quite
+ * fast, but will run more slowly when the input points are co-spherical or
+ * nearly so.
+ */
+
+double inspherefast(
+ const double *pa, const double *pb, const double *pc, const double *pd, const double *pe)
+{
+ double aex, bex, cex, dex;
+ double aey, bey, cey, dey;
+ double aez, bez, cez, dez;
+ double alift, blift, clift, dlift;
+ double ab, bc, cd, da, ac, bd;
+ double abc, bcd, cda, dab;
+
+ aex = pa[0] - pe[0];
+ bex = pb[0] - pe[0];
+ cex = pc[0] - pe[0];
+ dex = pd[0] - pe[0];
+ aey = pa[1] - pe[1];
+ bey = pb[1] - pe[1];
+ cey = pc[1] - pe[1];
+ dey = pd[1] - pe[1];
+ aez = pa[2] - pe[2];
+ bez = pb[2] - pe[2];
+ cez = pc[2] - pe[2];
+ dez = pd[2] - pe[2];
+
+ ab = aex * bey - bex * aey;
+ bc = bex * cey - cex * bey;
+ cd = cex * dey - dex * cey;
+ da = dex * aey - aex * dey;
+
+ ac = aex * cey - cex * aey;
+ bd = bex * dey - dex * bey;
+
+ abc = aez * bc - bez * ac + cez * ab;
+ bcd = bez * cd - cez * bd + dez * bc;
+ cda = cez * da + dez * ac + aez * cd;
+ dab = dez * ab + aez * bd + bez * da;
+
+ alift = aex * aex + aey * aey + aez * aez;
+ blift = bex * bex + bey * bey + bez * bez;
+ clift = cex * cex + cey * cey + cez * cez;
+ dlift = dex * dex + dey * dey + dez * dez;
+
+ return (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
+}
+
+static double insphereexact(
+ const double *pa, const double *pb, const double *pc, const double *pd, const double *pe)
+{
+ INEXACT double axby1, bxcy1, cxdy1, dxey1, exay1;
+ INEXACT double bxay1, cxby1, dxcy1, exdy1, axey1;
+ INEXACT double axcy1, bxdy1, cxey1, dxay1, exby1;
+ INEXACT double cxay1, dxby1, excy1, axdy1, bxey1;
+ double axby0, bxcy0, cxdy0, dxey0, exay0;
+ double bxay0, cxby0, dxcy0, exdy0, axey0;
+ double axcy0, bxdy0, cxey0, dxay0, exby0;
+ double cxay0, dxby0, excy0, axdy0, bxey0;
+ double ab[4], bc[4], cd[4], de[4], ea[4];
+ double ac[4], bd[4], ce[4], da[4], eb[4];
+ double temp8a[8], temp8b[8], temp16[16];
+ int temp8alen, temp8blen, temp16len;
+ double abc[24], bcd[24], cde[24], dea[24], eab[24];
+ double abd[24], bce[24], cda[24], deb[24], eac[24];
+ int abclen, bcdlen, cdelen, dealen, eablen;
+ int abdlen, bcelen, cdalen, deblen, eaclen;
+ double temp48a[48], temp48b[48];
+ int temp48alen, temp48blen;
+ double abcd[96], bcde[96], cdea[96], deab[96], eabc[96];
+ int abcdlen, bcdelen, cdealen, deablen, eabclen;
+ double temp192[192];
+ double det384x[384], det384y[384], det384z[384];
+ int xlen, ylen, zlen;
+ double detxy[768];
+ int xylen;
+ double adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152];
+ int alen, blen, clen, dlen, elen;
+ double abdet[2304], cddet[2304], cdedet[3456];
+ int ablen, cdlen;
+ double deter[5760];
+ int deterlen;
+ int i;
+
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+ INEXACT double _i, _j;
+ double _0;
+
+ Two_Product(pa[0], pb[1], axby1, axby0);
+ Two_Product(pb[0], pa[1], bxay1, bxay0);
+ Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]);
+
+ Two_Product(pb[0], pc[1], bxcy1, bxcy0);
+ Two_Product(pc[0], pb[1], cxby1, cxby0);
+ Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]);
+
+ Two_Product(pc[0], pd[1], cxdy1, cxdy0);
+ Two_Product(pd[0], pc[1], dxcy1, dxcy0);
+ Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]);
+
+ Two_Product(pd[0], pe[1], dxey1, dxey0);
+ Two_Product(pe[0], pd[1], exdy1, exdy0);
+ Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]);
+
+ Two_Product(pe[0], pa[1], exay1, exay0);
+ Two_Product(pa[0], pe[1], axey1, axey0);
+ Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]);
+
+ Two_Product(pa[0], pc[1], axcy1, axcy0);
+ Two_Product(pc[0], pa[1], cxay1, cxay0);
+ Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]);
+
+ Two_Product(pb[0], pd[1], bxdy1, bxdy0);
+ Two_Product(pd[0], pb[1], dxby1, dxby0);
+ Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]);
+
+ Two_Product(pc[0], pe[1], cxey1, cxey0);
+ Two_Product(pe[0], pc[1], excy1, excy0);
+ Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]);
+
+ Two_Product(pd[0], pa[1], dxay1, dxay0);
+ Two_Product(pa[0], pd[1], axdy1, axdy0);
+ Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]);
+
+ Two_Product(pe[0], pb[1], exby1, exby0);
+ Two_Product(pb[0], pe[1], bxey1, bxey0);
+ Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]);
+
+ temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a);
+ abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, abc);
+
+ temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a);
+ bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, bcd);
+
+ temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a);
+ cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, cde);
+
+ temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a);
+ dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, dea);
+
+ temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a);
+ eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, eab);
+
+ temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a);
+ abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, abd);
+
+ temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a);
+ bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, bce);
+
+ temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a);
+ cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, cda);
+
+ temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a);
+ deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, deb);
+
+ temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a);
+ eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, eac);
+
+ temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a);
+ temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b);
+ for (i = 0; i < temp48blen; i++) {
+ temp48b[i] = -temp48b[i];
+ }
+ bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, temp48blen, temp48b, bcde);
+ xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192);
+ xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x);
+ ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192);
+ ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y);
+ zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192);
+ zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z);
+ xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy);
+ alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet);
+
+ temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a);
+ temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b);
+ for (i = 0; i < temp48blen; i++) {
+ temp48b[i] = -temp48b[i];
+ }
+ cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, temp48blen, temp48b, cdea);
+ xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192);
+ xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x);
+ ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192);
+ ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y);
+ zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192);
+ zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z);
+ xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy);
+ blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet);
+
+ temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a);
+ temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b);
+ for (i = 0; i < temp48blen; i++) {
+ temp48b[i] = -temp48b[i];
+ }
+ deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, temp48blen, temp48b, deab);
+ xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192);
+ xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x);
+ ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192);
+ ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y);
+ zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192);
+ zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z);
+ xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy);
+ clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet);
+
+ temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a);
+ temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b);
+ for (i = 0; i < temp48blen; i++) {
+ temp48b[i] = -temp48b[i];
+ }
+ eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, temp48blen, temp48b, eabc);
+ xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192);
+ xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x);
+ ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192);
+ ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y);
+ zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192);
+ zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z);
+ xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy);
+ dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet);
+
+ temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a);
+ temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b);
+ for (i = 0; i < temp48blen; i++) {
+ temp48b[i] = -temp48b[i];
+ }
+ abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, temp48blen, temp48b, abcd);
+ xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192);
+ xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x);
+ ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192);
+ ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y);
+ zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192);
+ zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z);
+ xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy);
+ elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet);
+ cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet);
+ deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter);
+
+ return deter[deterlen - 1];
+}
+
+static double insphereadapt(const double *pa,
+ const double *pb,
+ const double *pc,
+ const double *pd,
+ const double *pe,
+ double permanent)
+{
+ INEXACT double aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez;
+ double det, errbound;
+
+ INEXACT double aexbey1, bexaey1, bexcey1, cexbey1;
+ INEXACT double cexdey1, dexcey1, dexaey1, aexdey1;
+ INEXACT double aexcey1, cexaey1, bexdey1, dexbey1;
+ double aexbey0, bexaey0, bexcey0, cexbey0;
+ double cexdey0, dexcey0, dexaey0, aexdey0;
+ double aexcey0, cexaey0, bexdey0, dexbey0;
+ double ab[4], bc[4], cd[4], da[4], ac[4], bd[4];
+ INEXACT double ab3, bc3, cd3, da3, ac3, bd3;
+ double abeps, bceps, cdeps, daeps, aceps, bdeps;
+ double temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48];
+ int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len;
+ double xdet[96], ydet[96], zdet[96], xydet[192];
+ int xlen, ylen, zlen, xylen;
+ double adet[288], bdet[288], cdet[288], ddet[288];
+ int alen, blen, clen, dlen;
+ double abdet[576], cddet[576];
+ int ablen, cdlen;
+ double fin1[1152];
+ int finlength;
+
+ double aextail, bextail, cextail, dextail;
+ double aeytail, beytail, ceytail, deytail;
+ double aeztail, beztail, ceztail, deztail;
+
+ INEXACT double bvirt;
+ double avirt, bround, around;
+ INEXACT double c;
+ INEXACT double abig;
+ double ahi, alo, bhi, blo;
+ double err1, err2, err3;
+ INEXACT double _i, _j;
+ double _0;
+
+ aex = (double)(pa[0] - pe[0]);
+ bex = (double)(pb[0] - pe[0]);
+ cex = (double)(pc[0] - pe[0]);
+ dex = (double)(pd[0] - pe[0]);
+ aey = (double)(pa[1] - pe[1]);
+ bey = (double)(pb[1] - pe[1]);
+ cey = (double)(pc[1] - pe[1]);
+ dey = (double)(pd[1] - pe[1]);
+ aez = (double)(pa[2] - pe[2]);
+ bez = (double)(pb[2] - pe[2]);
+ cez = (double)(pc[2] - pe[2]);
+ dez = (double)(pd[2] - pe[2]);
+
+ Two_Product(aex, bey, aexbey1, aexbey0);
+ Two_Product(bex, aey, bexaey1, bexaey0);
+ Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]);
+ ab[3] = ab3;
+
+ Two_Product(bex, cey, bexcey1, bexcey0);
+ Two_Product(cex, bey, cexbey1, cexbey0);
+ Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]);
+ bc[3] = bc3;
+
+ Two_Product(cex, dey, cexdey1, cexdey0);
+ Two_Product(dex, cey, dexcey1, dexcey0);
+ Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]);
+ cd[3] = cd3;
+
+ Two_Product(dex, aey, dexaey1, dexaey0);
+ Two_Product(aex, dey, aexdey1, aexdey0);
+ Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]);
+ da[3] = da3;
+
+ Two_Product(aex, cey, aexcey1, aexcey0);
+ Two_Product(cex, aey, cexaey1, cexaey0);
+ Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]);
+ ac[3] = ac3;
+
+ Two_Product(bex, dey, bexdey1, bexdey0);
+ Two_Product(dex, bey, dexbey1, dexbey0);
+ Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]);
+ bd[3] = bd3;
+
+ temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a);
+ temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b);
+ temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, temp16len, temp16, temp24);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48);
+ xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48);
+ ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48);
+ zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet);
+ xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet);
+ alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet);
+
+ temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b);
+ temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, temp16len, temp16, temp24);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48);
+ xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48);
+ ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48);
+ zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet);
+ xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet);
+ blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet);
+
+ temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a);
+ temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b);
+ temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, temp16len, temp16, temp24);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48);
+ xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48);
+ ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48);
+ zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet);
+ xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet);
+ clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet);
+
+ temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a);
+ temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b);
+ temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c);
+ temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, temp16);
+ temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, temp16len, temp16, temp24);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48);
+ xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48);
+ ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet);
+ temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48);
+ zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet);
+ xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet);
+ dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet);
+ finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1);
+
+ det = estimate(finlength, fin1);
+ errbound = isperrboundB * permanent;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pe[0], aex, aextail);
+ Two_Diff_Tail(pa[1], pe[1], aey, aeytail);
+ Two_Diff_Tail(pa[2], pe[2], aez, aeztail);
+ Two_Diff_Tail(pb[0], pe[0], bex, bextail);
+ Two_Diff_Tail(pb[1], pe[1], bey, beytail);
+ Two_Diff_Tail(pb[2], pe[2], bez, beztail);
+ Two_Diff_Tail(pc[0], pe[0], cex, cextail);
+ Two_Diff_Tail(pc[1], pe[1], cey, ceytail);
+ Two_Diff_Tail(pc[2], pe[2], cez, ceztail);
+ Two_Diff_Tail(pd[0], pe[0], dex, dextail);
+ Two_Diff_Tail(pd[1], pe[1], dey, deytail);
+ Two_Diff_Tail(pd[2], pe[2], dez, deztail);
+ if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) && (bextail == 0.0) &&
+ (beytail == 0.0) && (beztail == 0.0) && (cextail == 0.0) && (ceytail == 0.0) &&
+ (ceztail == 0.0) && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) {
+ return det;
+ }
+
+ errbound = isperrboundC * permanent + resulterrbound * Absolute(det);
+ abeps = (aex * beytail + bey * aextail) - (aey * bextail + bex * aeytail);
+ bceps = (bex * ceytail + cey * bextail) - (bey * cextail + cex * beytail);
+ cdeps = (cex * deytail + dey * cextail) - (cey * dextail + dex * ceytail);
+ daeps = (dex * aeytail + aey * dextail) - (dey * aextail + aex * deytail);
+ aceps = (aex * ceytail + cey * aextail) - (aey * cextail + cex * aeytail);
+ bdeps = (bex * deytail + dey * bextail) - (bey * dextail + dex * beytail);
+ det +=
+ (((bex * bex + bey * bey + bez * bez) * ((cez * daeps + dez * aceps + aez * cdeps) +
+ (ceztail * da3 + deztail * ac3 + aeztail * cd3)) +
+ (dex * dex + dey * dey + dez * dez) * ((aez * bceps - bez * aceps + cez * abeps) +
+ (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) -
+ ((aex * aex + aey * aey + aez * aez) * ((bez * cdeps - cez * bdeps + dez * bceps) +
+ (beztail * cd3 - ceztail * bd3 + deztail * bc3)) +
+ (cex * cex + cey * cey + cez * cez) * ((dez * abeps + aez * bdeps + bez * daeps) +
+ (deztail * ab3 + aeztail * bd3 + beztail * da3)))) +
+ 2.0 *
+ (((bex * bextail + bey * beytail + bez * beztail) * (cez * da3 + dez * ac3 + aez * cd3) +
+ (dex * dextail + dey * deytail + dez * deztail) *
+ (aez * bc3 - bez * ac3 + cez * ab3)) -
+ ((aex * aextail + aey * aeytail + aez * aeztail) * (bez * cd3 - cez * bd3 + dez * bc3) +
+ (cex * cextail + cey * ceytail + cez * ceztail) *
+ (dez * ab3 + aez * bd3 + bez * da3)));
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ return insphereexact(pa, pb, pc, pd, pe);
+}
+
+double insphere(
+ const double *pa, const double *pb, const double *pc, const double *pd, const double *pe)
+{
+ double aex, bex, cex, dex;
+ double aey, bey, cey, dey;
+ double aez, bez, cez, dez;
+ double aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey;
+ double aexcey, cexaey, bexdey, dexbey;
+ double alift, blift, clift, dlift;
+ double ab, bc, cd, da, ac, bd;
+ double abc, bcd, cda, dab;
+ double aezplus, bezplus, cezplus, dezplus;
+ double aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus;
+ double cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus;
+ double aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus;
+ double det;
+ double permanent, errbound;
+
+ aex = pa[0] - pe[0];
+ bex = pb[0] - pe[0];
+ cex = pc[0] - pe[0];
+ dex = pd[0] - pe[0];
+ aey = pa[1] - pe[1];
+ bey = pb[1] - pe[1];
+ cey = pc[1] - pe[1];
+ dey = pd[1] - pe[1];
+ aez = pa[2] - pe[2];
+ bez = pb[2] - pe[2];
+ cez = pc[2] - pe[2];
+ dez = pd[2] - pe[2];
+
+ aexbey = aex * bey;
+ bexaey = bex * aey;
+ ab = aexbey - bexaey;
+ bexcey = bex * cey;
+ cexbey = cex * bey;
+ bc = bexcey - cexbey;
+ cexdey = cex * dey;
+ dexcey = dex * cey;
+ cd = cexdey - dexcey;
+ dexaey = dex * aey;
+ aexdey = aex * dey;
+ da = dexaey - aexdey;
+
+ aexcey = aex * cey;
+ cexaey = cex * aey;
+ ac = aexcey - cexaey;
+ bexdey = bex * dey;
+ dexbey = dex * bey;
+ bd = bexdey - dexbey;
+
+ abc = aez * bc - bez * ac + cez * ab;
+ bcd = bez * cd - cez * bd + dez * bc;
+ cda = cez * da + dez * ac + aez * cd;
+ dab = dez * ab + aez * bd + bez * da;
+
+ alift = aex * aex + aey * aey + aez * aez;
+ blift = bex * bex + bey * bey + bez * bez;
+ clift = cex * cex + cey * cey + cez * cez;
+ dlift = dex * dex + dey * dey + dez * dez;
+
+ det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
+
+ aezplus = Absolute(aez);
+ bezplus = Absolute(bez);
+ cezplus = Absolute(cez);
+ dezplus = Absolute(dez);
+ aexbeyplus = Absolute(aexbey);
+ bexaeyplus = Absolute(bexaey);
+ bexceyplus = Absolute(bexcey);
+ cexbeyplus = Absolute(cexbey);
+ cexdeyplus = Absolute(cexdey);
+ dexceyplus = Absolute(dexcey);
+ dexaeyplus = Absolute(dexaey);
+ aexdeyplus = Absolute(aexdey);
+ aexceyplus = Absolute(aexcey);
+ cexaeyplus = Absolute(cexaey);
+ bexdeyplus = Absolute(bexdey);
+ dexbeyplus = Absolute(dexbey);
+ permanent = ((cexdeyplus + dexceyplus) * bezplus + (dexbeyplus + bexdeyplus) * cezplus +
+ (bexceyplus + cexbeyplus) * dezplus) *
+ alift +
+ ((dexaeyplus + aexdeyplus) * cezplus + (aexceyplus + cexaeyplus) * dezplus +
+ (cexdeyplus + dexceyplus) * aezplus) *
+ blift +
+ ((aexbeyplus + bexaeyplus) * dezplus + (bexdeyplus + dexbeyplus) * aezplus +
+ (dexaeyplus + aexdeyplus) * bezplus) *
+ clift +
+ ((bexceyplus + cexbeyplus) * aezplus + (cexaeyplus + aexceyplus) * bezplus +
+ (aexbeyplus + bexaeyplus) * cezplus) *
+ dlift;
+ errbound = isperrboundA * permanent;
+ if ((det > errbound) || (-det > errbound)) {
+ return det;
+ }
+
+ return insphereadapt(pa, pb, pc, pd, pe, permanent);
+}
+
+} /* namespace robust_pred */
+
+static int sgn(double x)
+{
+ return (x > 0) ? 1 : ((x < 0) ? -1 : 0);
+}
+
+int orient2d(const double2 &a, const double2 &b, const double2 &c)
+{
+ return sgn(blender::robust_pred::orient2d(a, b, c));
+}
+
+int orient2d_fast(const double2 &a, const double2 &b, const double2 &c)
+{
+ return sgn(blender::robust_pred::orient2dfast(a, b, c));
+}
+
+int incircle(const double2 &a, const double2 &b, const double2 &c, const double2 &d)
+{
+ return sgn(robust_pred::incircle(a, b, c, d));
+}
+
+int incircle_fast(const double2 &a, const double2 &b, const double2 &c, const double2 &d)
+{
+ return sgn(robust_pred::incirclefast(a, b, c, d));
+}
+
+int orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
+{
+ return sgn(robust_pred::orient3d(a, b, c, d));
+}
+
+int orient3d_fast(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
+{
+ return sgn(robust_pred::orient3dfast(a, b, c, d));
+}
+
+int insphere(
+ const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e)
+{
+ return sgn(robust_pred::insphere(a, b, c, d, e));
+}
+
+int insphere_fast(
+ const double3 &a, const double3 &b, const double3 &c, const double3 &d, const double3 &e)
+{
+ return sgn(robust_pred::inspherefast(a, b, c, d, e));
+}
+
+} // namespace blender
diff --git a/source/blender/blenlib/intern/math_vec.cc b/source/blender/blenlib/intern/math_vec.cc
new file mode 100644
index 00000000000..865a1986214
--- /dev/null
+++ b/source/blender/blenlib/intern/math_vec.cc
@@ -0,0 +1,193 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_double2.hh"
+#include "BLI_double3.hh"
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_hash.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mpq2.hh"
+#include "BLI_mpq3.hh"
+#include "BLI_span.hh"
+#include "BLI_utildefines.h"
+
+namespace blender {
+
+float2::isect_result float2::isect_seg_seg(const float2 &v1,
+ const float2 &v2,
+ const float2 &v3,
+ const float2 &v4)
+{
+ float2::isect_result ans;
+ float div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
+ if (div == 0.0f) {
+ ans.lambda = 0.0f;
+ ans.mu = 0.0f;
+ ans.kind = float2::isect_result::LINE_LINE_COLINEAR;
+ }
+ else {
+ ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
+ ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
+ if (ans.lambda >= 0.0f && ans.lambda <= 1.0f && ans.mu >= 0.0f && ans.mu <= 1.0f) {
+ if (ans.lambda == 0.0f || ans.lambda == 1.0f || ans.mu == 0.0f || ans.mu == 1.0f) {
+ ans.kind = float2::isect_result::LINE_LINE_EXACT;
+ }
+ else {
+ ans.kind = float2::isect_result::LINE_LINE_CROSS;
+ }
+ }
+ else {
+ ans.kind = float2::isect_result::LINE_LINE_NONE;
+ }
+ }
+ return ans;
+}
+
+double2::isect_result double2::isect_seg_seg(const double2 &v1,
+ const double2 &v2,
+ const double2 &v3,
+ const double2 &v4)
+{
+ double2::isect_result ans;
+ double div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
+ if (div == 0.0) {
+ ans.lambda = 0.0;
+ ans.mu = 0.0;
+ ans.kind = double2::isect_result::LINE_LINE_COLINEAR;
+ }
+ else {
+ ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
+ ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
+ if (ans.lambda >= 0.0 && ans.lambda <= 1.0 && ans.mu >= 0.0 && ans.mu <= 1.0) {
+ if (ans.lambda == 0.0 || ans.lambda == 1.0 || ans.mu == 0.0 || ans.mu == 1.0) {
+ ans.kind = double2::isect_result::LINE_LINE_EXACT;
+ }
+ else {
+ ans.kind = double2::isect_result::LINE_LINE_CROSS;
+ }
+ }
+ else {
+ ans.kind = double2::isect_result::LINE_LINE_NONE;
+ }
+ }
+ return ans;
+}
+
+#ifdef WITH_GMP
+mpq2::isect_result mpq2::isect_seg_seg(const mpq2 &v1,
+ const mpq2 &v2,
+ const mpq2 &v3,
+ const mpq2 &v4)
+{
+ mpq2::isect_result ans;
+ mpq_class div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0]);
+ if (div == 0.0) {
+ ans.lambda = 0.0;
+ ans.mu = 0.0;
+ ans.kind = mpq2::isect_result::LINE_LINE_COLINEAR;
+ }
+ else {
+ ans.lambda = ((v1[1] - v3[1]) * (v4[0] - v3[0]) - (v1[0] - v3[0]) * (v4[1] - v3[1])) / div;
+ ans.mu = ((v1[1] - v3[1]) * (v2[0] - v1[0]) - (v1[0] - v3[0]) * (v2[1] - v1[1])) / div;
+ if (ans.lambda >= 0 && ans.lambda <= 1 && ans.mu >= 0 && ans.mu <= 1) {
+ if (ans.lambda == 0 || ans.lambda == 1 || ans.mu == 0 || ans.mu == 1) {
+ ans.kind = mpq2::isect_result::LINE_LINE_EXACT;
+ }
+ else {
+ ans.kind = mpq2::isect_result::LINE_LINE_CROSS;
+ }
+ }
+ else {
+ ans.kind = mpq2::isect_result::LINE_LINE_NONE;
+ }
+ }
+ return ans;
+}
+#endif
+
+double3 double3::cross_poly(Span<double3> poly)
+{
+ /* Newell's Method. */
+ int nv = static_cast<int>(poly.size());
+ if (nv < 3) {
+ return double3(0, 0, 0);
+ }
+ const double3 *v_prev = &poly[nv - 1];
+ const double3 *v_curr = &poly[0];
+ double3 n(0, 0, 0);
+ for (int i = 0; i < nv;) {
+ n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]);
+ n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]);
+ n[2] = n[2] + ((*v_prev)[0] - (*v_curr)[0]) * ((*v_prev)[1] + (*v_curr)[1]);
+ v_prev = v_curr;
+ ++i;
+ if (i < nv) {
+ v_curr = &poly[i];
+ }
+ }
+ return n;
+}
+
+mpq3 mpq3::cross_poly(Span<mpq3> poly)
+{
+ /* Newell's Method. */
+ int nv = static_cast<int>(poly.size());
+ if (nv < 3) {
+ return mpq3(0);
+ }
+ const mpq3 *v_prev = &poly[nv - 1];
+ const mpq3 *v_curr = &poly[0];
+ mpq3 n(0);
+ for (int i = 0; i < nv;) {
+ n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]);
+ n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]);
+ n[2] = n[2] + ((*v_prev)[0] - (*v_curr)[0]) * ((*v_prev)[1] + (*v_curr)[1]);
+ v_prev = v_curr;
+ ++i;
+ if (i < nv) {
+ v_curr = &poly[i];
+ }
+ }
+ return n;
+}
+
+uint64_t hash_mpq_class(const mpq_class &value)
+{
+ /* TODO: better/faster implementation of this. */
+ return DefaultHash<float>{}(static_cast<float>(value.get_d()));
+}
+
+uint64_t mpq2::hash() const
+{
+ uint64_t hashx = hash_mpq_class(this->x);
+ uint64_t hashy = hash_mpq_class(this->y);
+ return hashx ^ (hashy * 33);
+}
+
+uint64_t mpq3::hash() const
+{
+ uint64_t hashx = hash_mpq_class(this->x);
+ uint64_t hashy = hash_mpq_class(this->y);
+ uint64_t hashz = hash_mpq_class(this->z);
+ return hashx ^ (hashy * 33) ^ (hashz * 33 * 37);
+}
+
+} // namespace blender
diff --git a/source/blender/blenlib/intern/math_vector.c b/source/blender/blenlib/intern/math_vector.c
index 909d508e262..fb3ea539df1 100644
--- a/source/blender/blenlib/intern/math_vector.c
+++ b/source/blender/blenlib/intern/math_vector.c
@@ -669,6 +669,15 @@ void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3])
out[2] = mul * v_proj[2];
}
+void project_v3_v3v3_db(double out[3], const double p[3], const double v_proj[3])
+{
+ const double mul = dot_v3v3_db(p, v_proj) / dot_v3v3_db(v_proj, v_proj);
+
+ out[0] = mul * v_proj[0];
+ out[1] = mul * v_proj[1];
+ out[2] = mul * v_proj[2];
+}
+
/**
* Project \a p onto a unit length \a v_proj
*/
@@ -796,6 +805,17 @@ void reflect_v3_v3v3(float out[3], const float v[3], const float normal[3])
out[2] = v[2] - (dot2 * normal[2]);
}
+void reflect_v3_v3v3_db(double out[3], const double v[3], const double normal[3])
+{
+ const double dot2 = 2.0 * dot_v3v3_db(v, normal);
+
+ /* BLI_ASSERT_UNIT_V3_DB(normal); this assert is not known? */
+
+ out[0] = v[0] - (dot2 * normal[0]);
+ out[1] = v[1] - (dot2 * normal[1]);
+ out[2] = v[2] - (dot2 * normal[2]);
+}
+
/**
* Takes a vector and computes 2 orthogonal directions.
*
diff --git a/source/blender/blenlib/intern/math_vector_inline.c b/source/blender/blenlib/intern/math_vector_inline.c
index 1b47832589e..598a22f6aa3 100644
--- a/source/blender/blenlib/intern/math_vector_inline.c
+++ b/source/blender/blenlib/intern/math_vector_inline.c
@@ -571,6 +571,13 @@ MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f)
r[2] = a[2] * f;
}
+MINLINE void mul_v3_v3db_db(double r[3], const double a[3], double f)
+{
+ r[0] = a[0] * f;
+ r[1] = a[1] * f;
+ r[2] = a[2] * f;
+}
+
MINLINE void mul_v2_v2(float r[2], const float a[2])
{
r[0] *= a[0];
@@ -988,6 +995,11 @@ MINLINE float len_squared_v3(const float v[3])
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
+MINLINE double len_squared_v3_db(const double v[3])
+{
+ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+}
+
MINLINE float len_manhattan_v2(const float v[2])
{
return fabsf(v[0]) + fabsf(v[1]);
@@ -1045,6 +1057,11 @@ MINLINE float len_v3(const float a[3])
return sqrtf(dot_v3v3(a, a));
}
+MINLINE double len_v3_db(const double a[3])
+{
+ return sqrt(dot_v3v3_db(a, a));
+}
+
MINLINE float len_squared_v2v2(const float a[2], const float b[2])
{
float d[2];
@@ -1161,7 +1178,29 @@ MINLINE float normalize_v3_v3(float r[3], const float a[3])
return normalize_v3_v3_length(r, a, 1.0f);
}
-MINLINE double normalize_v3_length_d(double n[3], const double unit_length)
+MINLINE double normalize_v3_v3_length_db(double r[3], const double a[3], double unit_length)
+{
+ double d = dot_v3v3_db(a, a);
+
+ /* a larger value causes normalize errors in a
+ * scaled down models with camera extreme close */
+ if (d > 1.0e-70) {
+ d = sqrt(d);
+ mul_v3_v3db_db(r, a, unit_length / d);
+ }
+ else {
+ zero_v3_db(r);
+ d = 0.0;
+ }
+
+ return d;
+}
+MINLINE double normalize_v3_v3_db(double r[3], const double a[3])
+{
+ return normalize_v3_v3_length_db(r, a, 1.0);
+}
+
+MINLINE double normalize_v3_length_db(double n[3], const double unit_length)
{
double d = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
@@ -1184,9 +1223,9 @@ MINLINE double normalize_v3_length_d(double n[3], const double unit_length)
return d;
}
-MINLINE double normalize_v3_d(double n[3])
+MINLINE double normalize_v3_db(double n[3])
{
- return normalize_v3_length_d(n, 1.0);
+ return normalize_v3_length_db(n, 1.0);
}
MINLINE float normalize_v3_length(float n[3], const float unit_length)
diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc
new file mode 100644
index 00000000000..bb8b14ebdc6
--- /dev/null
+++ b/source/blender/blenlib/intern/mesh_boolean.cc
@@ -0,0 +1,3382 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+#ifdef WITH_GMP
+
+# include <algorithm>
+# include <fstream>
+# include <iostream>
+
+# include "BLI_array.hh"
+# include "BLI_assert.h"
+# include "BLI_delaunay_2d.h"
+# include "BLI_hash.hh"
+# include "BLI_map.hh"
+# include "BLI_math.h"
+# include "BLI_math_boolean.hh"
+# include "BLI_math_mpq.hh"
+# include "BLI_mesh_intersect.hh"
+# include "BLI_mpq3.hh"
+# include "BLI_set.hh"
+# include "BLI_span.hh"
+# include "BLI_stack.hh"
+# include "BLI_vector.hh"
+# include "BLI_vector_set.hh"
+
+# include "BLI_mesh_boolean.hh"
+
+namespace blender::meshintersect {
+
+/**
+ * Edge as two `const` Vert *'s, in a canonical order (lower vert id first).
+ * We use the Vert id field for hashing to get algorithms
+ * that yield predictable results from run-to-run and machine-to-machine.
+ */
+class Edge {
+ const Vert *v_[2]{nullptr, nullptr};
+
+ public:
+ Edge() = default;
+ Edge(const Vert *v0, const Vert *v1)
+ {
+ if (v0->id <= v1->id) {
+ v_[0] = v0;
+ v_[1] = v1;
+ }
+ else {
+ v_[0] = v1;
+ v_[1] = v0;
+ }
+ }
+
+ const Vert *v0() const
+ {
+ return v_[0];
+ }
+
+ const Vert *v1() const
+ {
+ return v_[1];
+ }
+
+ const Vert *operator[](int i) const
+ {
+ return v_[i];
+ }
+
+ bool operator==(Edge other) const
+ {
+ return v_[0]->id == other.v_[0]->id && v_[1]->id == other.v_[1]->id;
+ }
+
+ uint64_t hash() const
+ {
+ constexpr uint64_t h1 = 33;
+ uint64_t v0hash = DefaultHash<int>{}(v_[0]->id);
+ uint64_t v1hash = DefaultHash<int>{}(v_[1]->id);
+ return v0hash ^ (v1hash * h1);
+ }
+};
+
+static std::ostream &operator<<(std::ostream &os, const Edge &e)
+{
+ if (e.v0() == nullptr) {
+ BLI_assert(e.v1() == nullptr);
+ os << "(null,null)";
+ }
+ else {
+ os << "(" << e.v0() << "," << e.v1() << ")";
+ }
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const Span<int> &a)
+{
+ for (int i : a.index_range()) {
+ os << a[i];
+ if (i != a.size() - 1) {
+ os << " ";
+ }
+ }
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const Array<int> &iarr)
+{
+ os << Span<int>(iarr);
+ return os;
+}
+
+/** Holds information about topology of an #IMesh that is all triangles. */
+class TriMeshTopology : NonCopyable {
+ /** Triangles that contain a given Edge (either order). */
+ Map<Edge, Vector<int> *> edge_tri_;
+ /** Edges incident on each vertex. */
+ Map<const Vert *, Vector<Edge>> vert_edges_;
+
+ public:
+ TriMeshTopology(const IMesh &tm);
+ ~TriMeshTopology();
+
+ /* If e is manifold, return index of the other triangle (not t) that has it.
+ * Else return NO_INDEX. */
+ int other_tri_if_manifold(Edge e, int t) const
+ {
+ if (edge_tri_.contains(e)) {
+ auto *p = edge_tri_.lookup(e);
+ if (p->size() == 2) {
+ return ((*p)[0] == t) ? (*p)[1] : (*p)[0];
+ }
+ }
+ return NO_INDEX;
+ }
+
+ /* Which triangles share edge e (in either orientation)? */
+ const Vector<int> *edge_tris(Edge e) const
+ {
+ return edge_tri_.lookup_default(e, nullptr);
+ }
+
+ /* Which edges are incident on the given vertex?
+ * We assume v has some incident edges. */
+ const Vector<Edge> &vert_edges(const Vert *v) const
+ {
+ return vert_edges_.lookup(v);
+ }
+
+ Map<Edge, Vector<int> *>::ItemIterator edge_tri_map_items() const
+ {
+ return edge_tri_.items();
+ }
+};
+
+TriMeshTopology::TriMeshTopology(const IMesh &tm)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "TRIMESHTOPOLOGY CONSTRUCTION\n";
+ }
+ /* If everything were manifold, `F+V-E=2` and `E=3F/2`.
+ * So an likely overestimate, allowing for non-manifoldness, is `E=2F` and `V=F`. */
+ const int estimate_num_edges = 2 * tm.face_size();
+ const int estimate_num_verts = tm.face_size();
+ edge_tri_.reserve(estimate_num_edges);
+ vert_edges_.reserve(estimate_num_verts);
+ for (int t : tm.face_index_range()) {
+ const Face &tri = *tm.face(t);
+ BLI_assert(tri.is_tri());
+ for (int i = 0; i < 3; ++i) {
+ const Vert *v = tri[i];
+ const Vert *vnext = tri[(i + 1) % 3];
+ Edge e(v, vnext);
+ Vector<Edge> *edges = vert_edges_.lookup_ptr(v);
+ if (edges == nullptr) {
+ vert_edges_.add_new(v, Vector<Edge>());
+ edges = vert_edges_.lookup_ptr(v);
+ BLI_assert(edges != nullptr);
+ }
+ edges->append_non_duplicates(e);
+ auto createf = [t](Vector<int> **pvec) { *pvec = new Vector<int>{t}; };
+ auto modifyf = [t](Vector<int> **pvec) { (*pvec)->append_non_duplicates(t); };
+ this->edge_tri_.add_or_modify(Edge(v, vnext), createf, modifyf);
+ }
+ }
+ /* Debugging. */
+ if (dbg_level > 0) {
+ std::cout << "After TriMeshTopology construction\n";
+ for (auto item : edge_tri_.items()) {
+ std::cout << "tris for edge " << item.key << ": " << *item.value << "\n";
+ constexpr bool print_stats = false;
+ if (print_stats) {
+ edge_tri_.print_stats();
+ }
+ }
+ for (auto item : vert_edges_.items()) {
+ std::cout << "edges for vert " << item.key << ":\n";
+ for (const Edge &e : item.value) {
+ std::cout << " " << e << "\n";
+ }
+ std::cout << "\n";
+ }
+ }
+}
+
+TriMeshTopology::~TriMeshTopology()
+{
+ for (const Vector<int> *vec : edge_tri_.values()) {
+ delete vec;
+ }
+}
+
+/** A Patch is a maximal set of triangles that share manifold edges only. */
+class Patch {
+ Vector<int> tri_; /* Indices of triangles in the Patch. */
+
+ public:
+ Patch() = default;
+
+ void add_tri(int t)
+ {
+ tri_.append(t);
+ }
+
+ int tot_tri() const
+ {
+ return tri_.size();
+ }
+
+ int tri(int i) const
+ {
+ return tri_[i];
+ }
+
+ IndexRange tri_range() const
+ {
+ return IndexRange(tri_.size());
+ }
+
+ Span<int> tris() const
+ {
+ return Span<int>(tri_);
+ }
+
+ int cell_above{NO_INDEX};
+ int cell_below{NO_INDEX};
+ int component{NO_INDEX};
+};
+
+static std::ostream &operator<<(std::ostream &os, const Patch &patch)
+{
+ os << "Patch " << patch.tris();
+ if (patch.cell_above != NO_INDEX) {
+ os << " cell_above=" << patch.cell_above;
+ }
+ else {
+ os << " cell_above not set";
+ }
+ if (patch.cell_below != NO_INDEX) {
+ os << " cell_below=" << patch.cell_below;
+ }
+ else {
+ os << " cell_below not set";
+ }
+ return os;
+}
+
+class PatchesInfo {
+ /** All of the Patches for a #IMesh. */
+ Vector<Patch> patch_;
+ /** Patch index for corresponding triangle. */
+ Array<int> tri_patch_;
+ /** Shared edge for incident patches; (-1, -1) if none. */
+ Map<std::pair<int, int>, Edge> pp_edge_;
+
+ public:
+ explicit PatchesInfo(int ntri)
+ {
+ constexpr int max_expected_patch_patch_incidences = 100;
+ tri_patch_ = Array<int>(ntri, NO_INDEX);
+ pp_edge_.reserve(max_expected_patch_patch_incidences);
+ }
+
+ int tri_patch(int t) const
+ {
+ return tri_patch_[t];
+ }
+
+ int add_patch()
+ {
+ int patch_index = patch_.append_and_get_index(Patch());
+ return patch_index;
+ }
+
+ void grow_patch(int patch_index, int t)
+ {
+ tri_patch_[t] = patch_index;
+ patch_[patch_index].add_tri(t);
+ }
+
+ bool tri_is_assigned(int t) const
+ {
+ return tri_patch_[t] != NO_INDEX;
+ }
+
+ const Patch &patch(int patch_index) const
+ {
+ return patch_[patch_index];
+ }
+
+ Patch &patch(int patch_index)
+ {
+ return patch_[patch_index];
+ }
+
+ int tot_patch() const
+ {
+ return patch_.size();
+ }
+
+ IndexRange index_range() const
+ {
+ return IndexRange(patch_.size());
+ }
+
+ const Patch *begin() const
+ {
+ return patch_.begin();
+ }
+
+ const Patch *end() const
+ {
+ return patch_.end();
+ }
+
+ Patch *begin()
+ {
+ return patch_.begin();
+ }
+
+ Patch *end()
+ {
+ return patch_.end();
+ }
+
+ void add_new_patch_patch_edge(int p1, int p2, Edge e)
+ {
+ pp_edge_.add_new(std::pair<int, int>(p1, p2), e);
+ pp_edge_.add_new(std::pair<int, int>(p2, p1), e);
+ }
+
+ Edge patch_patch_edge(int p1, int p2)
+ {
+ return pp_edge_.lookup_default(std::pair<int, int>(p1, p2), Edge());
+ }
+};
+
+static bool apply_bool_op(BoolOpType bool_optype, const Array<int> &winding);
+
+/**
+ * A Cell is a volume of 3-space, surrounded by patches.
+ * We will partition all 3-space into Cells.
+ * One cell, the Ambient cell, contains all other cells.
+ */
+class Cell {
+ Vector<int> patches_;
+ Array<int> winding_;
+ int merged_to_{NO_INDEX};
+ bool winding_assigned_{false};
+ /* in_output_volume_ will be true when this cell should be in the output volume. */
+ bool in_output_volume_{false};
+ /* zero_volume_ will be true when this is a zero-volume cell (inside a stack of identical
+ * triangles). */
+ bool zero_volume_{false};
+
+ public:
+ Cell() = default;
+
+ void add_patch(int p)
+ {
+ patches_.append(p);
+ }
+
+ void add_patch_non_duplicates(int p)
+ {
+ patches_.append_non_duplicates(p);
+ }
+
+ const Span<int> patches() const
+ {
+ return Span<int>(patches_);
+ }
+
+ const Span<int> winding() const
+ {
+ return Span<int>(winding_);
+ }
+
+ void init_winding(int winding_len)
+ {
+ winding_ = Array<int>(winding_len);
+ }
+
+ void seed_ambient_winding()
+ {
+ winding_.fill(0);
+ winding_assigned_ = true;
+ }
+
+ void set_winding_and_in_output_volume(const Cell &from_cell,
+ int shape,
+ int delta,
+ BoolOpType bool_optype)
+ {
+ std::copy(from_cell.winding().begin(), from_cell.winding().end(), winding_.begin());
+ winding_[shape] += delta;
+ winding_assigned_ = true;
+ in_output_volume_ = apply_bool_op(bool_optype, winding_);
+ }
+
+ bool in_output_volume() const
+ {
+ return in_output_volume_;
+ }
+
+ bool winding_assigned() const
+ {
+ return winding_assigned_;
+ }
+
+ bool zero_volume() const
+ {
+ return zero_volume_;
+ }
+
+ int merged_to() const
+ {
+ return merged_to_;
+ }
+
+ void set_merged_to(int c)
+ {
+ merged_to_ = c;
+ }
+
+ /**
+ * Call this when it is possible that this Cell has zero volume,
+ * and if it does, set zero_volume_ to true.
+ */
+ void check_for_zero_volume(const PatchesInfo &pinfo, const IMesh &mesh);
+};
+
+static std::ostream &operator<<(std::ostream &os, const Cell &cell)
+{
+ os << "Cell patches " << cell.patches();
+ if (cell.winding().size() > 0) {
+ os << " winding=" << cell.winding();
+ os << " in_output_volume=" << cell.in_output_volume();
+ }
+ os << " zv=" << cell.zero_volume();
+ return os;
+}
+
+static bool tris_have_same_verts(const IMesh &mesh, int t1, int t2)
+{
+ const Face &tri1 = *mesh.face(t1);
+ const Face &tri2 = *mesh.face(t2);
+ BLI_assert(tri1.size() == 3 && tri2.size() == 3);
+ if (tri1.vert[0] == tri2.vert[0]) {
+ return ((tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[2]) ||
+ (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[1]));
+ }
+ if (tri1.vert[0] == tri2.vert[1]) {
+ return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[2]) ||
+ (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[0]));
+ }
+ if (tri1.vert[0] == tri2.vert[2]) {
+ return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[1]) ||
+ (tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[0]));
+ }
+ return false;
+}
+
+/**
+ * A Cell will have zero volume if it is bounded by exactly two patches and those
+ * patches are geometrically identical triangles (perhaps flipped versions of each other).
+ * If this Cell has zero volume, set its zero_volume_ member to true.
+ */
+void Cell::check_for_zero_volume(const PatchesInfo &pinfo, const IMesh &mesh)
+{
+ if (patches_.size() == 2) {
+ const Patch &p1 = pinfo.patch(patches_[0]);
+ const Patch &p2 = pinfo.patch(patches_[1]);
+ if (p1.tot_tri() == 1 && p2.tot_tri() == 1) {
+ if (tris_have_same_verts(mesh, p1.tri(0), p2.tri(0))) {
+ zero_volume_ = true;
+ }
+ }
+ }
+}
+
+/* Information about all the Cells. */
+class CellsInfo {
+ Vector<Cell> cell_;
+
+ public:
+ CellsInfo() = default;
+
+ int add_cell()
+ {
+ int index = cell_.append_and_get_index(Cell());
+ return index;
+ }
+
+ Cell &cell(int c)
+ {
+ return cell_[c];
+ }
+
+ const Cell &cell(int c) const
+ {
+ return cell_[c];
+ }
+
+ int tot_cell() const
+ {
+ return cell_.size();
+ }
+
+ IndexRange index_range() const
+ {
+ return cell_.index_range();
+ }
+
+ const Cell *begin() const
+ {
+ return cell_.begin();
+ }
+
+ const Cell *end() const
+ {
+ return cell_.end();
+ }
+
+ Cell *begin()
+ {
+ return cell_.begin();
+ }
+
+ Cell *end()
+ {
+ return cell_.end();
+ }
+
+ void init_windings(int winding_len)
+ {
+ for (Cell &cell : cell_) {
+ cell.init_winding(winding_len);
+ }
+ }
+};
+
+/**
+ * For Debugging: write a .obj file showing the patch/cell structure or just the cells.
+ */
+static void write_obj_cell_patch(const IMesh &m,
+ const CellsInfo &cinfo,
+ const PatchesInfo &pinfo,
+ bool cells_only,
+ const std::string &name)
+{
+ /* Would like to use #BKE_tempdir_base() here, but that brings in dependence on kernel library.
+ * This is just for developer debugging anyway,
+ * and should never be called in production Blender. */
+# ifdef _WIN_32
+ const char *objdir = BLI_getenv("HOME");
+# else
+ const char *objdir = "/tmp/";
+# endif
+
+ std::string fname = std::string(objdir) + name + std::string("_cellpatch.obj");
+ std::ofstream f;
+ f.open(fname);
+ if (!f) {
+ std::cout << "Could not open file " << fname << "\n";
+ return;
+ }
+
+ /* Copy IMesh so can populate verts. */
+ IMesh mm = m;
+ mm.populate_vert();
+ f << "o cellpatch\n";
+ for (const Vert *v : mm.vertices()) {
+ const double3 dv = v->co;
+ f << "v " << dv[0] << " " << dv[1] << " " << dv[2] << "\n";
+ }
+ if (!cells_only) {
+ for (int p : pinfo.index_range()) {
+ f << "g patch" << p << "\n";
+ const Patch &patch = pinfo.patch(p);
+ for (int t : patch.tris()) {
+ const Face &tri = *mm.face(t);
+ f << "f ";
+ for (const Vert *v : tri) {
+ f << mm.lookup_vert(v) + 1 << " ";
+ }
+ f << "\n";
+ }
+ }
+ }
+ for (int c : cinfo.index_range()) {
+ f << "g cell" << c << "\n";
+ const Cell &cell = cinfo.cell(c);
+ for (int p : cell.patches()) {
+ const Patch &patch = pinfo.patch(p);
+ for (int t : patch.tris()) {
+ const Face &tri = *mm.face(t);
+ f << "f ";
+ for (const Vert *v : tri) {
+ f << mm.lookup_vert(v) + 1 << " ";
+ }
+ f << "\n";
+ }
+ }
+ }
+ f.close();
+}
+
+static void merge_cells(int merge_to, int merge_from, CellsInfo &cinfo, PatchesInfo &pinfo)
+{
+ if (merge_to == merge_from) {
+ return;
+ }
+ Cell &merge_from_cell = cinfo.cell(merge_from);
+ Cell &merge_to_cell = cinfo.cell(merge_to);
+ int final_merge_to = merge_to;
+ while (merge_to_cell.merged_to() != NO_INDEX) {
+ final_merge_to = merge_to_cell.merged_to();
+ merge_to_cell = cinfo.cell(final_merge_to);
+ }
+ for (Patch &patch : pinfo) {
+ if (patch.cell_above == merge_from) {
+ patch.cell_above = final_merge_to;
+ }
+ if (patch.cell_below == merge_from) {
+ patch.cell_below = final_merge_to;
+ }
+ }
+ for (int cell_p : merge_from_cell.patches()) {
+ merge_to_cell.add_patch_non_duplicates(cell_p);
+ }
+ merge_from_cell.set_merged_to(final_merge_to);
+}
+
+/**
+ * Partition the triangles of \a tm into Patches.
+ */
+static PatchesInfo find_patches(const IMesh &tm, const TriMeshTopology &tmtopo)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nFIND_PATCHES\n";
+ }
+ int ntri = tm.face_size();
+ PatchesInfo pinfo(ntri);
+ /* Algorithm: Grow patches across manifold edges as long as there are unassigned triangles. */
+ Stack<int> cur_patch_grow;
+ for (int t : tm.face_index_range()) {
+ if (pinfo.tri_patch(t) == -1) {
+ cur_patch_grow.push(t);
+ int cur_patch_index = pinfo.add_patch();
+ while (!cur_patch_grow.is_empty()) {
+ int tcand = cur_patch_grow.pop();
+ if (dbg_level > 1) {
+ std::cout << "pop tcand = " << tcand << "; assigned = " << pinfo.tri_is_assigned(tcand)
+ << "\n";
+ }
+ if (pinfo.tri_is_assigned(tcand)) {
+ continue;
+ }
+ if (dbg_level > 1) {
+ std::cout << "grow patch from seed tcand=" << tcand << "\n";
+ }
+ pinfo.grow_patch(cur_patch_index, tcand);
+ const Face &tri = *tm.face(tcand);
+ for (int i = 0; i < 3; ++i) {
+ Edge e(tri[i], tri[(i + 1) % 3]);
+ int t_other = tmtopo.other_tri_if_manifold(e, tcand);
+ if (dbg_level > 1) {
+ std::cout << " edge " << e << " generates t_other=" << t_other << "\n";
+ }
+ if (t_other != NO_INDEX) {
+ if (!pinfo.tri_is_assigned(t_other)) {
+ if (dbg_level > 1) {
+ std::cout << " push t_other = " << t_other << "\n";
+ }
+ cur_patch_grow.push(t_other);
+ }
+ }
+ else {
+ /* e is non-manifold. Set any patch-patch incidences we can. */
+ if (dbg_level > 1) {
+ std::cout << " e non-manifold case\n";
+ }
+ const Vector<int> *etris = tmtopo.edge_tris(e);
+ if (etris != nullptr) {
+ for (int i : etris->index_range()) {
+ int t_other = (*etris)[i];
+ if (t_other != tcand && pinfo.tri_is_assigned(t_other)) {
+ int p_other = pinfo.tri_patch(t_other);
+ if (p_other == cur_patch_index) {
+ continue;
+ }
+ if (pinfo.patch_patch_edge(cur_patch_index, p_other).v0() == nullptr) {
+ pinfo.add_new_patch_patch_edge(cur_patch_index, p_other, e);
+ if (dbg_level > 1) {
+ std::cout << "added patch_patch_edge (" << cur_patch_index << "," << p_other
+ << ") = " << e << "\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "\nafter FIND_PATCHES: found " << pinfo.tot_patch() << " patches\n";
+ for (int p : pinfo.index_range()) {
+ std::cout << p << ": " << pinfo.patch(p) << "\n";
+ }
+ if (dbg_level > 1) {
+ std::cout << "\ntriangle map\n";
+ for (int t : tm.face_index_range()) {
+ std::cout << t << ": patch " << pinfo.tri_patch(t) << "\n";
+ }
+ }
+ std::cout << "\npatch-patch incidences\n";
+ for (int p1 : pinfo.index_range()) {
+ for (int p2 : pinfo.index_range()) {
+ Edge e = pinfo.patch_patch_edge(p1, p2);
+ if (e.v0() != nullptr) {
+ std::cout << "p" << p1 << " and p" << p2 << " share edge " << e << "\n";
+ }
+ }
+ }
+ }
+ return pinfo;
+}
+
+/**
+ * If e is an edge in tri, return the vertex that isn't part of tri,
+ * the "flap" vertex, or nullptr if e is not part of tri.
+ * Also, e may be reversed in tri.
+ * Set *r_rev to true if it is reversed, else false.
+ */
+static const Vert *find_flap_vert(const Face &tri, const Edge e, bool *r_rev)
+{
+ *r_rev = false;
+ const Vert *flapv;
+ if (tri[0] == e.v0()) {
+ if (tri[1] == e.v1()) {
+ *r_rev = false;
+ flapv = tri[2];
+ }
+ else {
+ if (tri[2] != e.v1()) {
+ return nullptr;
+ }
+ *r_rev = true;
+ flapv = tri[1];
+ }
+ }
+ else if (tri[1] == e.v0()) {
+ if (tri[2] == e.v1()) {
+ *r_rev = false;
+ flapv = tri[0];
+ }
+ else {
+ if (tri[0] != e.v1()) {
+ return nullptr;
+ }
+ *r_rev = true;
+ flapv = tri[2];
+ }
+ }
+ else {
+ if (tri[2] != e.v0()) {
+ return nullptr;
+ }
+ if (tri[0] == e.v1()) {
+ *r_rev = false;
+ flapv = tri[1];
+ }
+ else {
+ if (tri[1] != e.v1()) {
+ return nullptr;
+ }
+ *r_rev = true;
+ flapv = tri[0];
+ }
+ }
+ return flapv;
+}
+
+/**
+ * Triangle \a tri and tri0 share edge e.
+ * Classify \a tri with respect to tri0 as described in
+ * sort_tris_around_edge, and return 1, 2, 3, or 4 as \a tri is:
+ * (1) co-planar with tri0 and on same side of e
+ * (2) co-planar with tri0 and on opposite side of e
+ * (3) below plane of tri0
+ * (4) above plane of tri0
+ * For "above" and "below", we use the orientation of non-reversed
+ * orientation of tri0.
+ * Because of the way the intersect mesh was made, we can assume
+ * that if a triangle is in class 1 then it is has the same flap vert
+ * as tri0.
+ */
+static int sort_tris_class(const Face &tri, const Face &tri0, const Edge e)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "classify e = " << e << "\n";
+ }
+ mpq3 a0 = tri0[0]->co_exact;
+ mpq3 a1 = tri0[1]->co_exact;
+ mpq3 a2 = tri0[2]->co_exact;
+ bool rev;
+ bool rev0;
+ const Vert *flapv0 = find_flap_vert(tri0, e, &rev0);
+ const Vert *flapv = find_flap_vert(tri, e, &rev);
+ if (dbg_level > 0) {
+ std::cout << " t0 = " << tri0[0] << " " << tri0[1] << " " << tri0[2];
+ std::cout << " rev0 = " << rev0 << " flapv0 = " << flapv0 << "\n";
+ std::cout << " t = " << tri[0] << " " << tri[1] << " " << tri[2];
+ std::cout << " rev = " << rev << " flapv = " << flapv << "\n";
+ }
+ BLI_assert(flapv != nullptr && flapv0 != nullptr);
+ const mpq3 flap = flapv->co_exact;
+ /* orient will be positive if flap is below oriented plane of a0,a1,a2. */
+ int orient = orient3d(a0, a1, a2, flap);
+ int ans;
+ if (orient > 0) {
+ ans = rev0 ? 4 : 3;
+ }
+ else if (orient < 0) {
+ ans = rev0 ? 3 : 4;
+ }
+ else {
+ ans = flapv == flapv0 ? 1 : 2;
+ }
+ if (dbg_level > 0) {
+ std::cout << " orient = " << orient << " ans = " << ans << "\n";
+ }
+ return ans;
+}
+
+constexpr int EXTRA_TRI_INDEX = INT_MAX;
+
+/**
+ * To ensure consistent ordering of co-planar triangles if they happen to be sorted around
+ * more than one edge, sort the triangle indices in g (in place) by their index -- but also apply
+ * a sign to the index: positive if the triangle has edge e in the same orientation,
+ * otherwise negative.
+ */
+static void sort_by_signed_triangle_index(Vector<int> &g,
+ const Edge e,
+ const IMesh &tm,
+ const Face *extra_tri)
+{
+ Array<int> signed_g(g.size());
+ for (int i : g.index_range()) {
+ const Face &tri = g[i] == EXTRA_TRI_INDEX ? *extra_tri : *tm.face(g[i]);
+ bool rev;
+ find_flap_vert(tri, e, &rev);
+ signed_g[i] = rev ? -g[i] : g[i];
+ }
+ std::sort(signed_g.begin(), signed_g.end());
+
+ for (int i : g.index_range()) {
+ g[i] = abs(signed_g[i]);
+ }
+}
+
+/**
+ * Sort the triangles \a tris, which all share edge e, as they appear
+ * geometrically clockwise when looking down edge e.
+ * Triangle t0 is the first triangle in the top-level call
+ * to this recursive routine. The merge step below differs
+ * for the top level call and all the rest, so this distinguishes those cases.
+ * Care is taken in the case of duplicate triangles to have
+ * an ordering that is consistent with that which would happen
+ * if another edge of the triangle were sorted around.
+ *
+ * We sometimes need to do this with an extra triangle that is not part of tm.
+ * To accommodate this:
+ * If extra_tri is non-null, then an index of EXTRA_TRI_INDEX should use it for the triangle.
+ */
+static Array<int> sort_tris_around_edge(const IMesh &tm,
+ const TriMeshTopology &tmtopo,
+ const Edge e,
+ const Span<int> tris,
+ const int t0,
+ const Face *extra_tri)
+{
+ /* Divide and conquer, quick-sort-like sort.
+ * Pick a triangle t0, then partition into groups:
+ * (1) co-planar with t0 and on same side of e
+ * (2) co-planar with t0 and on opposite side of e
+ * (3) below plane of t0
+ * (4) above plane of t0
+ * Each group is sorted and then the sorts are merged to give the answer.
+ * We don't expect the input array to be very large - should typically
+ * be only 3 or 4 - so OK to make copies of arrays instead of swapping
+ * around in a single array. */
+ const int dbg_level = 0;
+ if (tris.size() == 0) {
+ return Array<int>();
+ }
+ if (dbg_level > 0) {
+ if (t0 == tris[0]) {
+ std::cout << "\n";
+ }
+ std::cout << "sort_tris_around_edge " << e << "\n";
+ std::cout << "tris = " << tris << "\n";
+ std::cout << "t0 = " << t0 << "\n";
+ }
+ Vector<int> g1{tris[0]};
+ Vector<int> g2;
+ Vector<int> g3;
+ Vector<int> g4;
+ std::array<Vector<int> *, 4> groups = {&g1, &g2, &g3, &g4};
+ const Face &triref = *tm.face(tris[0]);
+ for (int i : tris.index_range()) {
+ if (i == 0) {
+ continue;
+ }
+ int t = tris[i];
+ BLI_assert(t < tm.face_size() || (t == EXTRA_TRI_INDEX && extra_tri != nullptr));
+ const Face &tri = (t == EXTRA_TRI_INDEX) ? *extra_tri : *tm.face(t);
+ if (dbg_level > 2) {
+ std::cout << "classifying tri " << t << " with respect to " << tris[0] << "\n";
+ }
+ int group_num = sort_tris_class(tri, triref, e);
+ if (dbg_level > 2) {
+ std::cout << " classify result : " << group_num << "\n";
+ }
+ groups[group_num - 1]->append(t);
+ }
+ if (dbg_level > 1) {
+ std::cout << "g1 = " << g1 << "\n";
+ std::cout << "g2 = " << g2 << "\n";
+ std::cout << "g3 = " << g3 << "\n";
+ std::cout << "g4 = " << g4 << "\n";
+ }
+ if (g1.size() > 1) {
+ sort_by_signed_triangle_index(g1, e, tm, extra_tri);
+ if (dbg_level > 1) {
+ std::cout << "g1 sorted: " << g1 << "\n";
+ }
+ }
+ if (g2.size() > 1) {
+ sort_by_signed_triangle_index(g2, e, tm, extra_tri);
+ if (dbg_level > 1) {
+ std::cout << "g2 sorted: " << g2 << "\n";
+ }
+ }
+ if (g3.size() > 1) {
+ Array<int> g3sorted = sort_tris_around_edge(tm, tmtopo, e, g3, t0, extra_tri);
+ std::copy(g3sorted.begin(), g3sorted.end(), g3.begin());
+ if (dbg_level > 1) {
+ std::cout << "g3 sorted: " << g3 << "\n";
+ }
+ }
+ if (g4.size() > 1) {
+ Array<int> g4sorted = sort_tris_around_edge(tm, tmtopo, e, g4, t0, extra_tri);
+ std::copy(g4sorted.begin(), g4sorted.end(), g4.begin());
+ if (dbg_level > 1) {
+ std::cout << "g4 sorted: " << g4 << "\n";
+ }
+ }
+ int group_tot_size = g1.size() + g2.size() + g3.size() + g4.size();
+ Array<int> ans(group_tot_size);
+ int *p = ans.begin();
+ if (tris[0] == t0) {
+ p = std::copy(g1.begin(), g1.end(), p);
+ p = std::copy(g4.begin(), g4.end(), p);
+ p = std::copy(g2.begin(), g2.end(), p);
+ std::copy(g3.begin(), g3.end(), p);
+ }
+ else {
+ p = std::copy(g3.begin(), g3.end(), p);
+ p = std::copy(g1.begin(), g1.end(), p);
+ p = std::copy(g4.begin(), g4.end(), p);
+ std::copy(g2.begin(), g2.end(), p);
+ }
+ if (dbg_level > 0) {
+ std::cout << "sorted tris = " << ans << "\n";
+ }
+ return ans;
+}
+
+/**
+ * Find the Cells around edge e.
+ * This possibly makes new cells in \a cinfo, and sets up the
+ * bipartite graph edges between cells and patches.
+ * Will modify \a pinfo and \a cinfo and the patches and cells they contain.
+ */
+static void find_cells_from_edge(const IMesh &tm,
+ const TriMeshTopology &tmtopo,
+ PatchesInfo &pinfo,
+ CellsInfo &cinfo,
+ const Edge e)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CELLS_FROM_EDGE " << e << "\n";
+ }
+ const Vector<int> *edge_tris = tmtopo.edge_tris(e);
+ BLI_assert(edge_tris != nullptr);
+ Array<int> sorted_tris = sort_tris_around_edge(
+ tm, tmtopo, e, Span<int>(*edge_tris), (*edge_tris)[0], nullptr);
+
+ int n_edge_tris = edge_tris->size();
+ Array<int> edge_patches(n_edge_tris);
+ for (int i = 0; i < n_edge_tris; ++i) {
+ edge_patches[i] = pinfo.tri_patch(sorted_tris[i]);
+ if (dbg_level > 1) {
+ std::cout << "edge_patches[" << i << "] = " << edge_patches[i] << "\n";
+ }
+ }
+ for (int i = 0; i < n_edge_tris; ++i) {
+ int inext = (i + 1) % n_edge_tris;
+ int r_index = edge_patches[i];
+ int rnext_index = edge_patches[inext];
+ Patch &r = pinfo.patch(r_index);
+ Patch &rnext = pinfo.patch(rnext_index);
+ bool r_flipped;
+ bool rnext_flipped;
+ find_flap_vert(*tm.face(sorted_tris[i]), e, &r_flipped);
+ find_flap_vert(*tm.face(sorted_tris[inext]), e, &rnext_flipped);
+ int *r_follow_cell = r_flipped ? &r.cell_below : &r.cell_above;
+ int *rnext_prev_cell = rnext_flipped ? &rnext.cell_above : &rnext.cell_below;
+ if (dbg_level > 0) {
+ std::cout << "process patch pair " << r_index << " " << rnext_index << "\n";
+ std::cout << " r_flipped = " << r_flipped << " rnext_flipped = " << rnext_flipped << "\n";
+ std::cout << " r_follow_cell (" << (r_flipped ? "below" : "above")
+ << ") = " << *r_follow_cell << "\n";
+ std::cout << " rnext_prev_cell (" << (rnext_flipped ? "above" : "below")
+ << ") = " << *rnext_prev_cell << "\n";
+ }
+ if (*r_follow_cell == NO_INDEX && *rnext_prev_cell == NO_INDEX) {
+ /* Neither is assigned: make a new cell. */
+ int c = cinfo.add_cell();
+ *r_follow_cell = c;
+ *rnext_prev_cell = c;
+ Cell &cell = cinfo.cell(c);
+ cell.add_patch(r_index);
+ cell.add_patch(rnext_index);
+ cell.check_for_zero_volume(pinfo, tm);
+ if (dbg_level > 0) {
+ std::cout << " made new cell " << c << "\n";
+ std::cout << " p" << r_index << "." << (r_flipped ? "cell_below" : "cell_above") << " = c"
+ << c << "\n";
+ std::cout << " p" << rnext_index << "." << (rnext_flipped ? "cell_above" : "cell_below")
+ << " = c" << c << "\n";
+ }
+ }
+ else if (*r_follow_cell != NO_INDEX && *rnext_prev_cell == NO_INDEX) {
+ int c = *r_follow_cell;
+ *rnext_prev_cell = c;
+ Cell &cell = cinfo.cell(c);
+ cell.add_patch(rnext_index);
+ cell.check_for_zero_volume(pinfo, tm);
+ if (dbg_level > 0) {
+ std::cout << " reuse r_follow: p" << rnext_index << "."
+ << (rnext_flipped ? "cell_above" : "cell_below") << " = c" << c << "\n";
+ }
+ }
+ else if (*r_follow_cell == NO_INDEX && *rnext_prev_cell != NO_INDEX) {
+ int c = *rnext_prev_cell;
+ *r_follow_cell = c;
+ Cell &cell = cinfo.cell(c);
+ cell.add_patch(r_index);
+ cell.check_for_zero_volume(pinfo, tm);
+ if (dbg_level > 0) {
+ std::cout << " reuse rnext prev: rprev_p" << r_index << "."
+ << (r_flipped ? "cell_below" : "cell_above") << " = c" << c << "\n";
+ }
+ }
+ else {
+ if (*r_follow_cell != *rnext_prev_cell) {
+ if (dbg_level > 0) {
+ std::cout << " merge cell " << *rnext_prev_cell << " into cell " << *r_follow_cell
+ << "\n";
+ }
+ merge_cells(*r_follow_cell, *rnext_prev_cell, cinfo, pinfo);
+ }
+ }
+ }
+}
+
+/**
+ * Find the partition of 3-space into Cells.
+ * This assigns the cell_above and cell_below for each Patch.
+ */
+static CellsInfo find_cells(const IMesh &tm, const TriMeshTopology &tmtopo, PatchesInfo &pinfo)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nFIND_CELLS\n";
+ }
+ CellsInfo cinfo;
+ /* For each unique edge shared between patch pairs, process it. */
+ Set<Edge> processed_edges;
+ int np = pinfo.tot_patch();
+ for (int p = 0; p < np; ++p) {
+ for (int q = p + 1; q < np; ++q) {
+ Edge e = pinfo.patch_patch_edge(p, q);
+ if (e.v0() != nullptr) {
+ if (!processed_edges.contains(e)) {
+ processed_edges.add_new(e);
+ find_cells_from_edge(tm, tmtopo, pinfo, cinfo, e);
+ }
+ }
+ }
+ }
+ /* Some patches may have no cells at this point. These are either:
+ * (a) a closed manifold patch only incident on itself (sphere, torus, klein bottle, etc.).
+ * (b) an open manifold patch only incident on itself (has non-manifold boundaries).
+ * Make above and below cells for these patches. This will create a disconnected patch-cell
+ * bipartite graph, which will have to be fixed later. */
+ for (int p : pinfo.index_range()) {
+ Patch &patch = pinfo.patch(p);
+ if (patch.cell_above == NO_INDEX) {
+ int c = cinfo.add_cell();
+ patch.cell_above = c;
+ Cell &cell = cinfo.cell(c);
+ cell.add_patch(p);
+ }
+ if (patch.cell_below == NO_INDEX) {
+ int c = cinfo.add_cell();
+ patch.cell_below = c;
+ Cell &cell = cinfo.cell(c);
+ cell.add_patch(p);
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "\nFIND_CELLS found " << cinfo.tot_cell() << " cells\nCells\n";
+ for (int i : cinfo.index_range()) {
+ std::cout << i << ": " << cinfo.cell(i) << "\n";
+ }
+ std::cout << "Patches\n";
+ for (int i : pinfo.index_range()) {
+ std::cout << i << ": " << pinfo.patch(i) << "\n";
+ }
+ if (dbg_level > 1) {
+ write_obj_cell_patch(tm, cinfo, pinfo, false, "postfindcells");
+ }
+ }
+ return cinfo;
+}
+
+/**
+ * Find the connected patch components (connects are via intermediate cells), and put
+ * component numbers in each patch.
+ * Return a Vector of components - each a Vector of the patch ids in the component.
+ */
+static Vector<Vector<int>> find_patch_components(const CellsInfo &cinfo, PatchesInfo &pinfo)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_PATCH_COMPONENTS\n";
+ }
+ if (pinfo.tot_patch() == 0) {
+ return Vector<Vector<int>>();
+ }
+ int current_component = 0;
+ Stack<int> stack; /* Patch indices to visit. */
+ Vector<Vector<int>> ans;
+ for (int pstart : pinfo.index_range()) {
+ Patch &patch_pstart = pinfo.patch(pstart);
+ if (patch_pstart.component != NO_INDEX) {
+ continue;
+ }
+ ans.append(Vector<int>());
+ ans[current_component].append(pstart);
+ stack.push(pstart);
+ patch_pstart.component = current_component;
+ while (!stack.is_empty()) {
+ int p = stack.pop();
+ Patch &patch = pinfo.patch(p);
+ BLI_assert(patch.component == current_component);
+ for (int c : {patch.cell_above, patch.cell_below}) {
+ for (int pn : cinfo.cell(c).patches()) {
+ Patch &patch_neighbor = pinfo.patch(pn);
+ if (patch_neighbor.component == NO_INDEX) {
+ patch_neighbor.component = current_component;
+ stack.push(pn);
+ ans[current_component].append(pn);
+ }
+ }
+ }
+ }
+ ++current_component;
+ }
+ if (dbg_level > 0) {
+ std::cout << "found " << ans.size() << " components\n";
+ for (int comp : ans.index_range()) {
+ std::cout << comp << ": " << ans[comp] << "\n";
+ }
+ }
+ return ans;
+}
+
+/**
+ * Do all patches have cell_above and cell_below set?
+ * Is the bipartite graph connected?
+ */
+static bool patch_cell_graph_ok(const CellsInfo &cinfo, const PatchesInfo &pinfo)
+{
+ for (int c : cinfo.index_range()) {
+ const Cell &cell = cinfo.cell(c);
+ if (cell.merged_to() != NO_INDEX) {
+ continue;
+ }
+ if (cell.patches().size() == 0) {
+ std::cout << "Patch/Cell graph disconnected at Cell " << c << " with no patches\n";
+ return false;
+ }
+ for (int p : cell.patches()) {
+ if (p >= pinfo.tot_patch()) {
+ std::cout << "Patch/Cell graph has bad patch index at Cell " << c << "\n";
+ return false;
+ }
+ }
+ }
+ for (int p : pinfo.index_range()) {
+ const Patch &patch = pinfo.patch(p);
+ if (patch.cell_above == NO_INDEX || patch.cell_below == NO_INDEX) {
+ std::cout << "Patch/Cell graph disconnected at Patch " << p
+ << " with one or two missing cells\n";
+ return false;
+ }
+ if (patch.cell_above >= cinfo.tot_cell() || patch.cell_below >= cinfo.tot_cell()) {
+ std::cout << "Patch/Cell graph has bad cell index at Patch " << p << "\n";
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Is trimesh tm PWN ("Piece-wise constant Winding Number")?
+ * See Zhou et al. paper for exact definition, but roughly
+ * means that the faces connect so as to form closed volumes.
+ * The actual definition says that if you calculate the
+ * generalized winding number of every point not exactly on
+ * the mesh, it will always be an integer.
+ * Necessary (but not sufficient) conditions that a mesh be PWN:
+ * No edges with a non-zero sum of incident face directions.
+ * I think that cases like Klein bottles are likely to satisfy
+ * this without being PWN. So this routine will be only
+ * approximately right.
+ */
+static bool is_pwn(const IMesh &tm, const TriMeshTopology &tmtopo)
+{
+ constexpr int dbg_level = 0;
+ for (auto item : tmtopo.edge_tri_map_items()) {
+ const Edge &edge = item.key;
+ int tot_orient = 0;
+ /* For each face t attached to edge, add +1 if the edge
+ * is positively in t, and -1 if negatively in t. */
+ for (int t : *item.value) {
+ const Face &face = *tm.face(t);
+ BLI_assert(face.size() == 3);
+ for (int i : face.index_range()) {
+ if (face[i] == edge.v0()) {
+ if (face[(i + 1) % 3] == edge.v1()) {
+ ++tot_orient;
+ }
+ else {
+ BLI_assert(face[(i + 3 - 1) % 3] == edge.v1());
+ --tot_orient;
+ }
+ }
+ }
+ }
+ if (tot_orient != 0) {
+ if (dbg_level > 0) {
+ std::cout << "edge causing non-pwn: " << edge << "\n";
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Find which of the cells around edge e contains point p.
+ * Do this by inserting a dummy triangle containing v and sorting the
+ * triangles around the edge to find out where in the sort order
+ * the dummy triangle lies, then finding which cell is between
+ * the two triangles on either side of the dummy.
+ */
+static int find_cell_for_point_near_edge(mpq3 p,
+ const Edge &e,
+ const IMesh &tm,
+ const TriMeshTopology &tmtopo,
+ const PatchesInfo &pinfo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CELL_FOR_POINT_NEAR_EDGE, p=" << p << " e=" << e << "\n";
+ }
+ const Vector<int> *etris = tmtopo.edge_tris(e);
+ const Vert *dummy_vert = arena->add_or_find_vert(p, NO_INDEX);
+ const Face *dummy_tri = arena->add_face({e.v0(), e.v1(), dummy_vert},
+ NO_INDEX,
+ {NO_INDEX, NO_INDEX, NO_INDEX},
+ {false, false, false});
+ BLI_assert(etris != nullptr);
+ Array<int> edge_tris(etris->size() + 1);
+ std::copy(etris->begin(), etris->end(), edge_tris.begin());
+ edge_tris[edge_tris.size() - 1] = EXTRA_TRI_INDEX;
+ Array<int> sorted_tris = sort_tris_around_edge(
+ tm, tmtopo, e, edge_tris, edge_tris[0], dummy_tri);
+ if (dbg_level > 0) {
+ std::cout << "sorted tris = " << sorted_tris << "\n";
+ }
+ int *p_sorted_dummy = std::find(sorted_tris.begin(), sorted_tris.end(), EXTRA_TRI_INDEX);
+ BLI_assert(p_sorted_dummy != sorted_tris.end());
+ int dummy_index = p_sorted_dummy - sorted_tris.begin();
+ int prev_tri = (dummy_index == 0) ? sorted_tris[sorted_tris.size() - 1] :
+ sorted_tris[dummy_index - 1];
+ int next_tri = (dummy_index == sorted_tris.size() - 1) ? sorted_tris[0] :
+ sorted_tris[dummy_index + 1];
+ if (dbg_level > 0) {
+ std::cout << "prev tri to dummy = " << prev_tri << "; next tri to dummy = " << next_tri
+ << "\n";
+ }
+ const Patch &prev_patch = pinfo.patch(pinfo.tri_patch(prev_tri));
+ if (dbg_level > 0) {
+ std::cout << "prev_patch = " << prev_patch << "\n";
+ }
+ bool prev_flipped;
+ find_flap_vert(*tm.face(prev_tri), e, &prev_flipped);
+ int c = prev_flipped ? prev_patch.cell_below : prev_patch.cell_above;
+ if (dbg_level > 0) {
+ std::cout << "find_cell_for_point_near_edge returns " << c << "\n";
+ }
+ return c;
+}
+
+/**
+ * Find the ambient cell -- that is, the cell that is outside
+ * all other cells.
+ * If component_patches != nullptr, restrict consideration to patches
+ * in that vector.
+ *
+ * The method is to find an edge known to be on the convex hull
+ * of the mesh, then insert a dummy triangle that has that edge
+ * and a point known to be outside the whole mesh. Then sorting
+ * the triangles around the edge will reveal where the dummy triangle
+ * fits in that sorting order, and hence, the two adjacent patches
+ * to the dummy triangle - thus revealing the cell that the point
+ * known to be outside the whole mesh is in.
+ */
+static int find_ambient_cell(const IMesh &tm,
+ const Vector<int> *component_patches,
+ const TriMeshTopology &tmtopo,
+ const PatchesInfo &pinfo,
+ IMeshArena *arena)
+{
+ int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_AMBIENT_CELL\n";
+ }
+ /* First find a vertex with the maximum x value. */
+ /* Prefer not to populate the verts in the #IMesh just for this. */
+ const Vert *v_extreme;
+ mpq_class extreme_x;
+ if (component_patches == nullptr) {
+ v_extreme = (*tm.face(0))[0];
+ extreme_x = v_extreme->co_exact.x;
+ for (const Face *f : tm.faces()) {
+ for (const Vert *v : *f) {
+ const mpq_class &x = v->co_exact.x;
+ if (x > extreme_x) {
+ v_extreme = v;
+ extreme_x = x;
+ }
+ }
+ }
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "restrict to patches " << *component_patches << "\n";
+ }
+ int p0 = (*component_patches)[0];
+ v_extreme = (*tm.face(pinfo.patch(p0).tri(0)))[0];
+ extreme_x = v_extreme->co_exact.x;
+ for (int p : *component_patches) {
+ for (int t : pinfo.patch(p).tris()) {
+ const Face *f = tm.face(t);
+ for (const Vert *v : *f) {
+ const mpq_class &x = v->co_exact.x;
+ if (x > extreme_x) {
+ v_extreme = v;
+ extreme_x = x;
+ }
+ }
+ }
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "v_extreme = " << v_extreme << "\n";
+ }
+ /* Find edge attached to v_extreme with max absolute slope
+ * when projected onto the XY plane. That edge is guaranteed to
+ * be on the convex hull of the mesh. */
+ const Vector<Edge> &edges = tmtopo.vert_edges(v_extreme);
+ const mpq_class extreme_y = v_extreme->co_exact.y;
+ Edge ehull;
+ mpq_class max_abs_slope = -1;
+ for (Edge e : edges) {
+ const Vert *v_other = (e.v0() == v_extreme) ? e.v1() : e.v0();
+ const mpq3 &co_other = v_other->co_exact;
+ mpq_class delta_x = co_other.x - extreme_x;
+ if (delta_x == 0) {
+ /* Vertical slope. */
+ ehull = e;
+ break;
+ }
+ mpq_class abs_slope = abs((co_other.y - extreme_y) / delta_x);
+ if (abs_slope > max_abs_slope) {
+ ehull = e;
+ max_abs_slope = abs_slope;
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "ehull = " << ehull << " slope = " << max_abs_slope << "\n";
+ }
+ /* Sort triangles around ehull, including a dummy triangle that include a known point in ambient
+ * cell. */
+ mpq3 p_in_ambient = v_extreme->co_exact;
+ p_in_ambient.x += 1;
+ int c_ambient = find_cell_for_point_near_edge(p_in_ambient, ehull, tm, tmtopo, pinfo, arena);
+ if (dbg_level > 0) {
+ std::cout << "FIND_AMBIENT_CELL returns " << c_ambient << "\n";
+ }
+ return c_ambient;
+}
+
+/**
+ * We need an edge on the convex hull of the edges incident on \a closestp
+ * in order to sort around, including a dummy triangle that has \a testp and
+ * the sorting edge vertices. So we don't want an edge that is co-linear
+ * with the line through \a testp and \a closestp.
+ * The method is to project onto a plane that contains `testp-closestp`,
+ * and then choose the edge that, when projected, has the maximum absolute
+ * slope (regarding the line `testp-closestp` as the x-axis for slope computation).
+ */
+static Edge find_good_sorting_edge(const Vert *testp,
+ const Vert *closestp,
+ const TriMeshTopology &tmtopo)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_GOOD_SORTING_EDGE testp = " << testp << ", closestp = " << closestp << "\n";
+ }
+ /* We want to project the edges incident to closestp onto a plane
+ * whose ordinate direction will be regarded as going from closetp to testp,
+ * and whose abscissa direction is some perpendicular to that.
+ * A perpendicular direction can be found by swapping two coordinates
+ * and negating one, and zeroing out the third, being careful that one
+ * of the swapped vertices is non-zero. */
+ const mpq3 &co_closest = closestp->co_exact;
+ const mpq3 &co_test = testp->co_exact;
+ BLI_assert(co_test != co_closest);
+ mpq3 abscissa = co_test - co_closest;
+ /* Find a non-zero-component axis of abscissa. */
+ int axis;
+ for (axis = 0; axis < 3; ++axis) {
+ if (abscissa[axis] != 0) {
+ break;
+ }
+ }
+ BLI_assert(axis < 3);
+ int axis_next = (axis + 1) % 3;
+ int axis_next_next = (axis_next + 1) % 3;
+ mpq3 ordinate;
+ ordinate[axis] = abscissa[axis_next];
+ ordinate[axis_next] = -abscissa[axis];
+ ordinate[axis_next_next] = 0;
+ /* By construction, dot(abscissa, ordinate) == 0, so they are perpendicular. */
+ mpq3 normal = mpq3::cross(abscissa, ordinate);
+ if (dbg_level > 0) {
+ std::cout << "abscissa = " << abscissa << "\n";
+ std::cout << "ordinate = " << ordinate << "\n";
+ std::cout << "normal = " << normal << "\n";
+ }
+ mpq_class nlen2 = normal.length_squared();
+ mpq_class max_abs_slope = -1;
+ Edge esort;
+ const Vector<Edge> &edges = tmtopo.vert_edges(closestp);
+ for (Edge e : edges) {
+ const Vert *v_other = (e.v0() == closestp) ? e.v1() : e.v0();
+ const mpq3 &co_other = v_other->co_exact;
+ mpq3 evec = co_other - co_closest;
+ /* Get projection of evec onto plane of abscissa and ordinate. */
+ mpq3 proj_evec = evec - (mpq3::dot(evec, normal) / nlen2) * normal;
+ /* The projection calculations along the abscissa and ordinate should
+ * be scaled by 1/abscissa and 1/ordinate respectively,
+ * but we can skip: it won't affect which `evec` has the maximum slope. */
+ mpq_class evec_a = mpq3::dot(proj_evec, abscissa);
+ mpq_class evec_o = mpq3::dot(proj_evec, ordinate);
+ if (dbg_level > 0) {
+ std::cout << "e = " << e << "\n";
+ std::cout << "v_other = " << v_other << "\n";
+ std::cout << "evec = " << evec << ", proj_evec = " << proj_evec << "\n";
+ std::cout << "evec_a = " << evec_a << ", evec_o = " << evec_o << "\n";
+ }
+ if (evec_a == 0) {
+ /* evec is perpendicular to abscissa. */
+ esort = e;
+ if (dbg_level > 0) {
+ std::cout << "perpendicular esort is " << esort << "\n";
+ }
+ break;
+ }
+ mpq_class abs_slope = abs(evec_o / evec_a);
+ if (abs_slope > max_abs_slope) {
+ esort = e;
+ max_abs_slope = abs_slope;
+ if (dbg_level > 0) {
+ std::cout << "with abs_slope " << abs_slope << " new esort is " << esort << "\n";
+ }
+ }
+ }
+ return esort;
+}
+
+/**
+ * Find the cell that contains v. Consider the cells adjacent to triangle t.
+ * The close_edge and close_vert values are what were returned by
+ * #closest_on_tri_to_point when determining that v was close to t.
+ * They will indicate whether the point of closest approach to t is to
+ * an edge of t, a vertex of t, or somewhere inside t.
+ *
+ * The algorithm is similar to the one for find_ambient_cell, except that
+ * instead of an arbitrary point known to be outside the whole mesh, we
+ * have a particular point (v) and we just want to determine the patches
+ * that that point is between in sorting-around-an-edge order.
+ */
+static int find_containing_cell(const Vert *v,
+ int t,
+ int close_edge,
+ int close_vert,
+ const PatchesInfo &pinfo,
+ const IMesh &tm,
+ const TriMeshTopology &tmtopo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CONTAINING_CELL v=" << v << ", t=" << t << "\n";
+ }
+ const Face &tri = *tm.face(t);
+ Edge etest;
+ if (close_edge == -1 && close_vert == -1) {
+ /* Choose any edge if closest point is inside the triangle. */
+ close_edge = 0;
+ }
+ if (close_edge != -1) {
+ const Vert *v0 = tri[close_edge];
+ const Vert *v1 = tri[(close_edge + 1) % 3];
+ const Vector<Edge> &edges = tmtopo.vert_edges(v0);
+ if (dbg_level > 0) {
+ std::cout << "look for edge containing " << v0 << " and " << v1 << "\n";
+ std::cout << " in edges: ";
+ for (Edge e : edges) {
+ std::cout << e << " ";
+ }
+ std::cout << "\n";
+ }
+ for (Edge e : edges) {
+ if ((e.v0() == v0 && e.v1() == v1) || (e.v0() == v1 && e.v1() == v0)) {
+ etest = e;
+ break;
+ }
+ }
+ }
+ else {
+ int cv = close_vert;
+ const Vert *vert_cv = tri[cv];
+ if (vert_cv == v) {
+ /* Need to use another one to find sorting edge. */
+ vert_cv = tri[(cv + 1) % 3];
+ BLI_assert(vert_cv != v);
+ }
+ etest = find_good_sorting_edge(v, vert_cv, tmtopo);
+ }
+ BLI_assert(etest.v0() != nullptr);
+ if (dbg_level > 0) {
+ std::cout << "etest = " << etest << "\n";
+ }
+ int c = find_cell_for_point_near_edge(v->co_exact, etest, tm, tmtopo, pinfo, arena);
+ if (dbg_level > 0) {
+ std::cout << "find_containing_cell returns " << c << "\n";
+ }
+ return c;
+}
+
+/**
+ * Find the closest point in triangle (a, b, c) to point p.
+ * Return the distance squared to that point.
+ * Also, if the closest point in the triangle is on a vertex,
+ * return 0, 1, or 2 for a, b, c in *r_vert; else -1.
+ * If the closest point is on an edge, return 0, 1, or 2
+ * for edges ab, bc, or ca in *r_edge; else -1.
+ * (Adapted from #closest_on_tri_to_point_v3()).
+ */
+static mpq_class closest_on_tri_to_point(
+ const mpq3 &p, const mpq3 &a, const mpq3 &b, const mpq3 &c, int *r_edge, int *r_vert)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "CLOSEST_ON_TRI_TO_POINT p = " << p << "\n";
+ std::cout << " a = " << a << ", b = " << b << ", c = " << c << "\n";
+ }
+ /* Check if p in vertex region outside a. */
+ mpq3 ab = b - a;
+ mpq3 ac = c - a;
+ mpq3 ap = p - a;
+ mpq_class d1 = mpq3::dot(ab, ap);
+ mpq_class d2 = mpq3::dot(ac, ap);
+ if (d1 <= 0 && d2 <= 0) {
+ /* Barycentric coordinates (1,0,0). */
+ *r_edge = -1;
+ *r_vert = 0;
+ if (dbg_level > 0) {
+ std::cout << " answer = a\n";
+ }
+ return mpq3::distance_squared(p, a);
+ }
+ /* Check if p in vertex region outside b. */
+ mpq3 bp = p - b;
+ mpq_class d3 = mpq3::dot(ab, bp);
+ mpq_class d4 = mpq3::dot(ac, bp);
+ if (d3 >= 0 && d4 <= d3) {
+ /* Barycentric coordinates (0,1,0). */
+ *r_edge = -1;
+ *r_vert = 1;
+ if (dbg_level > 0) {
+ std::cout << " answer = b\n";
+ }
+ return mpq3::distance_squared(p, b);
+ }
+ /* Check if p in region of ab. */
+ mpq_class vc = d1 * d4 - d3 * d2;
+ if (vc <= 0 && d1 >= 0 && d3 <= 0) {
+ mpq_class v = d1 / (d1 - d3);
+ /* Barycentric coordinates (1-v,v,0). */
+ mpq3 r = a + v * ab;
+ *r_vert = -1;
+ *r_edge = 0;
+ if (dbg_level > 0) {
+ std::cout << " answer = on ab at " << r << "\n";
+ }
+ return mpq3::distance_squared(p, r);
+ }
+ /* Check if p in vertex region outside c. */
+ mpq3 cp = p - c;
+ mpq_class d5 = mpq3::dot(ab, cp);
+ mpq_class d6 = mpq3::dot(ac, cp);
+ if (d6 >= 0 && d5 <= d6) {
+ /* Barycentric coordinates (0,0,1). */
+ *r_edge = -1;
+ *r_vert = 2;
+ if (dbg_level > 0) {
+ std::cout << " answer = c\n";
+ }
+ return mpq3::distance_squared(p, c);
+ }
+ /* Check if p in edge region of ac. */
+ mpq_class vb = d5 * d2 - d1 * d6;
+ if (vb <= 0 && d2 >= 0 && d6 <= 0) {
+ mpq_class w = d2 / (d2 - d6);
+ /* Barycentric coordinates (1-w,0,w). */
+ mpq3 r = a + w * ac;
+ *r_vert = -1;
+ *r_edge = 2;
+ if (dbg_level > 0) {
+ std::cout << " answer = on ac at " << r << "\n";
+ }
+ return mpq3::distance_squared(p, r);
+ }
+ /* Check if p in edge region of bc. */
+ mpq_class va = d3 * d6 - d5 * d4;
+ if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
+ mpq_class w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
+ /* Barycentric coordinates (0,1-w,w). */
+ mpq3 r = c - b;
+ r = w * r;
+ r = r + b;
+ *r_vert = -1;
+ *r_edge = 1;
+ if (dbg_level > 0) {
+ std::cout << " answer = on bc at " << r << "\n";
+ }
+ return mpq3::distance_squared(p, r);
+ }
+ /* p inside face region. Compute barycentric coordinates (u,v,w). */
+ mpq_class denom = 1 / (va + vb + vc);
+ mpq_class v = vb * denom;
+ mpq_class w = vc * denom;
+ ac = w * ac;
+ mpq3 r = a + v * ab;
+ r = r + ac;
+ *r_vert = -1;
+ *r_edge = -1;
+ if (dbg_level > 0) {
+ std::cout << " answer = inside at " << r << "\n";
+ }
+ return mpq3::distance_squared(p, r);
+}
+
+struct ComponentContainer {
+ int containing_component{NO_INDEX};
+ int nearest_cell{NO_INDEX};
+ mpq_class dist_to_cell;
+
+ ComponentContainer(int cc, int cell, mpq_class d)
+ : containing_component(cc), nearest_cell(cell), dist_to_cell(d)
+ {
+ }
+};
+
+/**
+ * Find out all the components, not equal to comp, that contain a point
+ * in comp in a non-ambient cell of those components.
+ * In other words, find the components that comp is nested inside
+ * (maybe not directly nested, which is why there can be more than one).
+ */
+static Vector<ComponentContainer> find_component_containers(int comp,
+ const Vector<Vector<int>> &components,
+ const Array<int> &ambient_cell,
+ const IMesh &tm,
+ const PatchesInfo &pinfo,
+ const TriMeshTopology &tmtopo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_COMPONENT_CONTAINERS for comp " << comp << "\n";
+ }
+ Vector<ComponentContainer> ans;
+ int test_p = components[comp][0];
+ int test_t = pinfo.patch(test_p).tri(0);
+ const Vert *test_v = tm.face(test_t)[0].vert[0];
+ if (dbg_level > 0) {
+ std::cout << "test vertex in comp: " << test_v << "\n";
+ }
+ for (int comp_other : components.index_range()) {
+ if (comp == comp_other) {
+ continue;
+ }
+ if (dbg_level > 0) {
+ std::cout << "comp_other = " << comp_other << "\n";
+ }
+ int nearest_tri = NO_INDEX;
+ int nearest_tri_close_vert = -1;
+ int nearest_tri_close_edge = -1;
+ mpq_class nearest_tri_dist_squared;
+ for (int p : components[comp_other]) {
+ const Patch &patch = pinfo.patch(p);
+ for (int t : patch.tris()) {
+ const Face &tri = *tm.face(t);
+ if (dbg_level > 1) {
+ std::cout << "tri " << t << " = " << &tri << "\n";
+ }
+ int close_vert;
+ int close_edge;
+ mpq_class d2 = closest_on_tri_to_point(test_v->co_exact,
+ tri[0]->co_exact,
+ tri[1]->co_exact,
+ tri[2]->co_exact,
+ &close_edge,
+ &close_vert);
+ if (dbg_level > 1) {
+ std::cout << " close_edge=" << close_edge << " close_vert=" << close_vert
+ << " dsquared=" << d2.get_d() << "\n";
+ }
+ if (nearest_tri == NO_INDEX || d2 < nearest_tri_dist_squared) {
+ nearest_tri = t;
+ nearest_tri_close_edge = close_edge;
+ nearest_tri_close_vert = close_vert;
+ nearest_tri_dist_squared = d2;
+ }
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "closest tri to comp=" << comp << " in comp_other=" << comp_other << " is t"
+ << nearest_tri << "\n";
+ const Face *tn = tm.face(nearest_tri);
+ std::cout << "tri = " << tn << "\n";
+ std::cout << " (" << tn->vert[0]->co << "," << tn->vert[1]->co << "," << tn->vert[2]->co
+ << ")\n";
+ }
+ int containing_cell = find_containing_cell(test_v,
+ nearest_tri,
+ nearest_tri_close_edge,
+ nearest_tri_close_vert,
+
+ pinfo,
+ tm,
+ tmtopo,
+ arena);
+ if (dbg_level > 0) {
+ std::cout << "containing cell = " << containing_cell << "\n";
+ }
+ if (containing_cell != ambient_cell[comp_other]) {
+ ans.append(ComponentContainer(comp_other, containing_cell, nearest_tri_dist_squared));
+ }
+ }
+ return ans;
+}
+
+/**
+ * The cells and patches are supposed to form a bipartite graph.
+ * The graph may be disconnected (if parts of meshes are nested or side-by-side
+ * without intersection with other each other).
+ * Connect the bipartite graph. This involves discovering the connected components
+ * of the patches, then the nesting structure of those components.
+ */
+static void finish_patch_cell_graph(const IMesh &tm,
+ CellsInfo &cinfo,
+ PatchesInfo &pinfo,
+ const TriMeshTopology &tmtopo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FINISH_PATCH_CELL_GRAPH\n";
+ }
+ Vector<Vector<int>> components = find_patch_components(cinfo, pinfo);
+ if (components.size() <= 1) {
+ if (dbg_level > 0) {
+ std::cout << "one component so finish_patch_cell_graph does no work\n";
+ }
+ return;
+ }
+ if (dbg_level > 0) {
+ std::cout << "components:\n";
+ for (int comp : components.index_range()) {
+ std::cout << comp << ": " << components[comp] << "\n";
+ }
+ }
+ Array<int> ambient_cell(components.size());
+ for (int comp : components.index_range()) {
+ ambient_cell[comp] = find_ambient_cell(tm, &components[comp], tmtopo, pinfo, arena);
+ }
+ if (dbg_level > 0) {
+ std::cout << "ambient cells:\n";
+ for (int comp : ambient_cell.index_range()) {
+ std::cout << comp << ": " << ambient_cell[comp] << "\n";
+ }
+ }
+ int tot_components = components.size();
+ Array<Vector<ComponentContainer>> comp_cont(tot_components);
+ for (int comp : components.index_range()) {
+ comp_cont[comp] = find_component_containers(
+ comp, components, ambient_cell, tm, pinfo, tmtopo, arena);
+ }
+ if (dbg_level > 0) {
+ std::cout << "component containers:\n";
+ for (int comp : comp_cont.index_range()) {
+ std::cout << comp << ": ";
+ for (const ComponentContainer &cc : comp_cont[comp]) {
+ std::cout << "[containing_comp=" << cc.containing_component
+ << ", nearest_cell=" << cc.nearest_cell << ", d2=" << cc.dist_to_cell << "] ";
+ }
+ std::cout << "\n";
+ }
+ }
+ if (dbg_level > 1) {
+ write_obj_cell_patch(tm, cinfo, pinfo, false, "beforemerge");
+ }
+ /* For nested components, merge their ambient cell with the nearest containing cell. */
+ Vector<int> outer_components;
+ for (int comp : comp_cont.index_range()) {
+ if (comp_cont[comp].size() == 0) {
+ outer_components.append(comp);
+ }
+ else {
+ ComponentContainer &closest = comp_cont[comp][0];
+ for (int i = 1; i < comp_cont[comp].size(); ++i) {
+ if (comp_cont[comp][i].dist_to_cell < closest.dist_to_cell) {
+ closest = comp_cont[comp][i];
+ }
+ }
+ int comp_ambient = ambient_cell[comp];
+ int cont_cell = closest.nearest_cell;
+ if (dbg_level > 0) {
+ std::cout << "merge comp " << comp << "'s ambient cell=" << comp_ambient << " to cell "
+ << cont_cell << "\n";
+ }
+ merge_cells(cont_cell, comp_ambient, cinfo, pinfo);
+ }
+ }
+ /* For outer components (not nested in any other component), merge their ambient cells. */
+ if (outer_components.size() > 1) {
+ int merged_ambient = ambient_cell[outer_components[0]];
+ for (int i = 1; i < outer_components.size(); ++i) {
+ if (dbg_level > 0) {
+ std::cout << "merge comp " << outer_components[i]
+ << "'s ambient cell=" << ambient_cell[outer_components[i]] << " to cell "
+ << merged_ambient << "\n";
+ }
+ merge_cells(merged_ambient, ambient_cell[outer_components[i]], cinfo, pinfo);
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "after FINISH_PATCH_CELL_GRAPH\nCells\n";
+ for (int i : cinfo.index_range()) {
+ if (cinfo.cell(i).merged_to() == NO_INDEX) {
+ std::cout << i << ": " << cinfo.cell(i) << "\n";
+ }
+ }
+ std::cout << "Patches\n";
+ for (int i : pinfo.index_range()) {
+ std::cout << i << ": " << pinfo.patch(i) << "\n";
+ }
+ if (dbg_level > 1) {
+ write_obj_cell_patch(tm, cinfo, pinfo, false, "finished");
+ }
+ }
+}
+
+/**
+ * Starting with ambient cell c_ambient, with all zeros for winding numbers,
+ * propagate winding numbers to all the other cells.
+ * There will be a vector of \a nshapes winding numbers in each cell, one per
+ * input shape.
+ * As one crosses a patch into a new cell, the original shape (mesh part)
+ * that that patch was part of dictates which winding number changes.
+ * The shape_fn(triangle_number) function should return the shape that the
+ * triangle is part of.
+ * Also, as soon as the winding numbers for a cell are set, use bool_optype
+ * to decide whether that cell is included or excluded from the boolean output.
+ * If included, the cell's in_output_volume will be set to true.
+ */
+static void propagate_windings_and_in_output_volume(PatchesInfo &pinfo,
+ CellsInfo &cinfo,
+ int c_ambient,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn)
+{
+ int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "PROPAGATE_WINDINGS, ambient cell = " << c_ambient << "\n";
+ }
+ Cell &cell_ambient = cinfo.cell(c_ambient);
+ cell_ambient.seed_ambient_winding();
+ /* Use a vector as a queue. It can't grow bigger than number of cells. */
+ Vector<int> queue;
+ queue.reserve(cinfo.tot_cell());
+ int queue_head = 0;
+ queue.append(c_ambient);
+ while (queue_head < queue.size()) {
+ int c = queue[queue_head++];
+ if (dbg_level > 1) {
+ std::cout << "process cell " << c << "\n";
+ }
+ Cell &cell = cinfo.cell(c);
+ for (int p : cell.patches()) {
+ Patch &patch = pinfo.patch(p);
+ bool p_above_c = patch.cell_below == c;
+ int c_neighbor = p_above_c ? patch.cell_above : patch.cell_below;
+ if (dbg_level > 1) {
+ std::cout << " patch " << p << " p_above_c = " << p_above_c << "\n";
+ std::cout << " c_neighbor = " << c_neighbor << "\n";
+ }
+ Cell &cell_neighbor = cinfo.cell(c_neighbor);
+ if (!cell_neighbor.winding_assigned()) {
+ int winding_delta = p_above_c ? -1 : 1;
+ int t = patch.tri(0);
+ int shape = shape_fn(t);
+ BLI_assert(shape < nshapes);
+ UNUSED_VARS_NDEBUG(nshapes);
+ if (dbg_level > 1) {
+ std::cout << " representative tri " << t << ": in shape " << shape << "\n";
+ }
+ cell_neighbor.set_winding_and_in_output_volume(cell, shape, winding_delta, op);
+ if (dbg_level > 1) {
+ std::cout << " now cell_neighbor = " << cell_neighbor << "\n";
+ }
+ queue.append(c_neighbor);
+ BLI_assert(queue.size() <= cinfo.tot_cell());
+ }
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "\nPROPAGATE_WINDINGS result\n";
+ for (int i = 0; i < cinfo.tot_cell(); ++i) {
+ std::cout << i << ": " << cinfo.cell(i) << "\n";
+ }
+ }
+}
+
+/**
+ * Given an array of winding numbers, where the ith entry is a cell's winding
+ * number with respect to input shape (mesh part) i, return true if the
+ * cell should be included in the output of the boolean operation.
+ * Intersection: all the winding numbers must be nonzero.
+ * Union: at least one winding number must be nonzero.
+ * Difference (first shape minus the rest): first winding number must be nonzero
+ * and the rest must have at least one zero winding number.
+ */
+static bool apply_bool_op(BoolOpType bool_optype, const Array<int> &winding)
+{
+ int nw = winding.size();
+ BLI_assert(nw > 0);
+ switch (bool_optype) {
+ case BoolOpType::Intersect: {
+ for (int i = 0; i < nw; ++i) {
+ if (winding[i] == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ case BoolOpType::Union: {
+ for (int i = 0; i < nw; ++i) {
+ if (winding[i] != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case BoolOpType::Difference: {
+ /* if nw > 2, make it shape 0 minus the union of the rest. */
+ if (winding[0] == 0) {
+ return false;
+ }
+ if (nw == 1) {
+ return true;
+ }
+ for (int i = 1; i < nw; ++i) {
+ if (winding[i] == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
+/**
+ * Special processing for extract_from_in_output_volume_diffs to handle
+ * triangles that are part of stacks of geometrically identical
+ * triangles enclosing zero volume cells.
+ */
+static void extract_zero_volume_cell_tris(Vector<Face *> &r_tris,
+ const IMesh &tm_subdivided,
+ const PatchesInfo &pinfo,
+ const CellsInfo &cinfo,
+ IMeshArena *arena)
+{
+ int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "extract_zero_volume_cell_tris\n";
+ }
+ /* Find patches that are adjacent to zero-volume cells. */
+ Array<bool> adj_to_zv(pinfo.tot_patch());
+ for (int p : pinfo.index_range()) {
+ const Patch &patch = pinfo.patch(p);
+ if (cinfo.cell(patch.cell_above).zero_volume() || cinfo.cell(patch.cell_below).zero_volume()) {
+ adj_to_zv[p] = true;
+ }
+ else {
+ adj_to_zv[p] = false;
+ }
+ }
+ /* Partition the adj_to_zv patches into stacks. */
+ Vector<Vector<int>> patch_stacks;
+ Array<bool> allocated_to_stack(pinfo.tot_patch(), false);
+ for (int p : pinfo.index_range()) {
+ if (!adj_to_zv[p] || allocated_to_stack[p]) {
+ continue;
+ }
+ int stack_index = patch_stacks.size();
+ patch_stacks.append(Vector<int>{p});
+ Vector<int> &stack = patch_stacks[stack_index];
+ Vector<bool> flipped{false};
+ allocated_to_stack[p] = true;
+ /* We arbitrarily choose p's above and below directions as above and below for whole stack.
+ * Triangles in the stack that don't follow that convention are marked with flipped = true.
+ * The non-zero-volume cell above the whole stack, following this convention, is
+ * above_stack_cell. The non-zero-volume cell below the whole stack is #below_stack_cell. */
+ /* First, walk in the above_cell direction from p. */
+ int pwalk = p;
+ const Patch *pwalk_patch = &pinfo.patch(pwalk);
+ int c = pwalk_patch->cell_above;
+ const Cell *cell = &cinfo.cell(c);
+ while (cell->zero_volume()) {
+ /* In zero-volume cells, the cell should have exactly two patches. */
+ BLI_assert(cell->patches().size() == 2);
+ int pother = cell->patches()[0] == pwalk ? cell->patches()[1] : cell->patches()[0];
+ bool flip = pinfo.patch(pother).cell_above == c;
+ flipped.append(flip);
+ stack.append(pother);
+ allocated_to_stack[pother] = true;
+ pwalk = pother;
+ pwalk_patch = &pinfo.patch(pwalk);
+ c = flip ? pwalk_patch->cell_below : pwalk_patch->cell_above;
+ cell = &cinfo.cell(c);
+ }
+ const Cell *above_stack_cell = cell;
+ /* Now walk in the below_cell direction from p. */
+ pwalk = p;
+ pwalk_patch = &pinfo.patch(pwalk);
+ c = pwalk_patch->cell_below;
+ cell = &cinfo.cell(c);
+ while (cell->zero_volume()) {
+ BLI_assert(cell->patches().size() == 2);
+ int pother = cell->patches()[0] == pwalk ? cell->patches()[1] : cell->patches()[0];
+ bool flip = pinfo.patch(pother).cell_below == c;
+ flipped.append(flip);
+ stack.append(pother);
+ allocated_to_stack[pother] = true;
+ pwalk = pother;
+ pwalk_patch = &pinfo.patch(pwalk);
+ c = flip ? pwalk_patch->cell_above : pwalk_patch->cell_below;
+ cell = &cinfo.cell(c);
+ }
+ const Cell *below_stack_cell = cell;
+ if (dbg_level > 0) {
+ std::cout << "process zero-volume patch stack " << stack << "\n";
+ std::cout << "flipped = ";
+ for (bool b : flipped) {
+ std::cout << b << " ";
+ }
+ std::cout << "\n";
+ }
+ if (above_stack_cell->in_output_volume() ^ below_stack_cell->in_output_volume()) {
+ bool need_flipped_tri = above_stack_cell->in_output_volume();
+ if (dbg_level > 0) {
+ std::cout << "need tri " << (need_flipped_tri ? "flipped" : "") << "\n";
+ }
+ int t_to_add = NO_INDEX;
+ for (int i : stack.index_range()) {
+ if (flipped[i] == need_flipped_tri) {
+ t_to_add = pinfo.patch(stack[i]).tri(0);
+ if (dbg_level > 0) {
+ std::cout << "using tri " << t_to_add << "\n";
+ }
+ r_tris.append(tm_subdivided.face(t_to_add));
+ break;
+ }
+ }
+ if (t_to_add == NO_INDEX) {
+ const Face *f = tm_subdivided.face(pinfo.patch(p).tri(0));
+ const Face &tri = *f;
+ /* We need flipped version or else we would have found it above. */
+ std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
+ std::array<int, 3> flipped_e_origs = {
+ tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
+ std::array<bool, 3> flipped_is_intersect = {
+ tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
+ Face *flipped_f = arena->add_face(
+ flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
+ r_tris.append(flipped_f);
+ }
+ }
+ }
+}
+
+/**
+ * Extract the output mesh from tm_subdivided and return it as a new mesh.
+ * The cells in \a cinfo must have cells-to-be-retained with in_output_volume set.
+ * We keep only triangles between those in the output volume and those not in.
+ * We flip the normals of any triangle that has an in_output_volume cell above
+ * and a not-in_output_volume cell below.
+ * For all stacks of exact duplicate co-planar triangles, we want to
+ * include either one version of the triangle or none, depending on
+ * whether the in_output_volume in_output_volumes on either side of the stack are
+ * different or the same.
+ */
+static IMesh extract_from_in_output_volume_diffs(const IMesh &tm_subdivided,
+ const PatchesInfo &pinfo,
+ const CellsInfo &cinfo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nEXTRACT_FROM_FLAG_DIFFS\n";
+ }
+ Vector<Face *> out_tris;
+ out_tris.reserve(tm_subdivided.face_size());
+ bool any_zero_volume_cell = false;
+ for (int t : tm_subdivided.face_index_range()) {
+ int p = pinfo.tri_patch(t);
+ const Patch &patch = pinfo.patch(p);
+ const Cell &cell_above = cinfo.cell(patch.cell_above);
+ const Cell &cell_below = cinfo.cell(patch.cell_below);
+ if (dbg_level > 0) {
+ std::cout << "tri " << t << ": cell_above=" << patch.cell_above
+ << " cell_below=" << patch.cell_below << "\n";
+ std::cout << " in_output_volume_above=" << cell_above.in_output_volume()
+ << " in_output_volume_below=" << cell_below.in_output_volume() << "\n";
+ }
+ bool adjacent_zero_volume_cell = cell_above.zero_volume() || cell_below.zero_volume();
+ any_zero_volume_cell |= adjacent_zero_volume_cell;
+ if (cell_above.in_output_volume() ^ cell_below.in_output_volume() &&
+ !adjacent_zero_volume_cell) {
+ bool flip = cell_above.in_output_volume();
+ if (dbg_level > 0) {
+ std::cout << "need tri " << t << " flip=" << flip << "\n";
+ }
+ Face *f = tm_subdivided.face(t);
+ if (flip) {
+ Face &tri = *f;
+ std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
+ std::array<int, 3> flipped_e_origs = {
+ tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
+ std::array<bool, 3> flipped_is_intersect = {
+ tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
+ Face *flipped_f = arena->add_face(
+ flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
+ out_tris.append(flipped_f);
+ }
+ else {
+ out_tris.append(f);
+ }
+ }
+ }
+ if (any_zero_volume_cell) {
+ extract_zero_volume_cell_tris(out_tris, tm_subdivided, pinfo, cinfo, arena);
+ }
+ return IMesh(out_tris);
+}
+
+static const char *bool_optype_name(BoolOpType op)
+{
+ switch (op) {
+ case BoolOpType::None:
+ return "none";
+ case BoolOpType::Intersect:
+ return "intersect";
+ case BoolOpType::Union:
+ return "union";
+ case BoolOpType::Difference:
+ return "difference";
+ default:
+ return "<unknown>";
+ }
+}
+
+static mpq3 calc_point_inside_tri(const Face &tri)
+{
+ const Vert *v0 = tri.vert[0];
+ const Vert *v1 = tri.vert[1];
+ const Vert *v2 = tri.vert[2];
+ mpq3 ans = v0->co_exact / 3 + v1->co_exact / 3 + v2->co_exact / 3;
+ return ans;
+}
+
+/**
+ * Return the Generalized Winding Number of point \a testp with respect to the
+ * volume implied by the faces for which shape_fn returns the value shape.
+ * See "Robust Inside-Outside Segmentation using Generalized Winding Numbers"
+ * by Jacobson, Kavan, and Sorkine-Hornung.
+ * This is like a winding number in that if it is positive, the point
+ * is inside the volume. But it is tolerant of not-completely-watertight
+ * volumes, still doing a passable job of classifying inside/outside
+ * as we intuitively understand that to mean.
+ *
+ * TOOD: speed up this calculation using the hierarchical algorithm in that paper.
+ */
+static double generalized_winding_number(const IMesh &tm,
+ std::function<int(int)> shape_fn,
+ const double3 &testp,
+ int shape)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "GENERALIZED_WINDING_NUMBER testp = " << testp << ", shape = " << shape << "\n";
+ }
+ double gwn = 0;
+ for (int t : tm.face_index_range()) {
+ const Face *f = tm.face(t);
+ const Face &tri = *f;
+ if (shape_fn(tri.orig) == shape) {
+ if (dbg_level > 0) {
+ std::cout << "accumulate for tri t = " << t << " = " << f << "\n";
+ }
+ const Vert *v0 = tri.vert[0];
+ const Vert *v1 = tri.vert[1];
+ const Vert *v2 = tri.vert[2];
+ double3 a = v0->co - testp;
+ double3 b = v1->co - testp;
+ double3 c = v2->co - testp;
+ /* Calculate the solid angle of abc relative to origin.
+ * See "The Solid Angle of a Plane Triangle" by Oosterom and Strackee
+ * for the derivation of the formula. */
+ double alen = a.length();
+ double blen = b.length();
+ double clen = c.length();
+ double3 bxc = double3::cross_high_precision(b, c);
+ double num = double3::dot(a, bxc);
+ double denom = alen * blen * clen + double3::dot(a, b) * clen + double3::dot(a, c) * blen +
+ double3::dot(b, c) * alen;
+ if (denom == 0.0) {
+ if (dbg_level > 0) {
+ std::cout << "denom == 0, skipping this tri\n";
+ }
+ continue;
+ }
+ double x = atan2(num, denom);
+ double fgwn = 2.0 * x;
+ if (dbg_level > 0) {
+ std::cout << "tri contributes " << fgwn << "\n";
+ }
+ gwn += fgwn;
+ }
+ }
+ gwn = gwn / (M_PI * 4.0);
+ if (dbg_level > 0) {
+ std::cout << "final gwn = " << gwn << "\n";
+ }
+ return gwn;
+}
+
+/**
+ * Return true if point \a testp is inside the volume implied by the
+ * faces for which the shape_fn returns the value shape.
+ */
+static bool point_is_inside_shape(const IMesh &tm,
+ std::function<int(int)> shape_fn,
+ const double3 &testp,
+ int shape)
+{
+ double gwn = generalized_winding_number(tm, shape_fn, testp, shape);
+ /* Due to floating point error, an outside point should get a value
+ * of zero for gwn, but may have a very slightly positive value instead.
+ * It is not important to get this epsilon very small, because practical
+ * cases of interest will have gwn at least 0.2 if it is not zero. */
+ return (gwn > 0.01);
+}
+
+/**
+ * Use the Generalized Winding Number method for deciding if a patch of the
+ * mesh is supposed to be included or excluded in the boolean result,
+ * and return the mesh that is the boolean result.
+ */
+static IMesh gwn_boolean(const IMesh &tm,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ const PatchesInfo &pinfo,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "GWN_BOOLEAN\n";
+ }
+ IMesh ans;
+ Vector<Face *> out_faces;
+ out_faces.reserve(tm.face_size());
+ BLI_assert(nshapes == 2); /* TODO: generalize. */
+ UNUSED_VARS_NDEBUG(nshapes);
+ for (int p : pinfo.index_range()) {
+ const Patch &patch = pinfo.patch(p);
+ /* For test triangle, choose one in the middle of patch list
+ * as the ones near the beginning may be very near other patches. */
+ int test_t_index = patch.tri(patch.tot_tri() / 2);
+ Face &tri_test = *tm.face(test_t_index);
+ /* Assume all triangles in a patch are in the same shape. */
+ int shape = shape_fn(tri_test.orig);
+ if (dbg_level > 0) {
+ std::cout << "process patch " << p << " = " << patch << "\n";
+ std::cout << "test tri = " << test_t_index << " = " << &tri_test << "\n";
+ std::cout << "shape = " << shape << "\n";
+ }
+ if (shape == -1) {
+ continue;
+ }
+ mpq3 test_point = calc_point_inside_tri(tri_test);
+ double3 test_point_db(test_point[0].get_d(), test_point[1].get_d(), test_point[2].get_d());
+ if (dbg_level > 0) {
+ std::cout << "test point = " << test_point_db << "\n";
+ }
+ int other_shape = 1 - shape;
+ bool inside = point_is_inside_shape(tm, shape_fn, test_point_db, other_shape);
+ if (dbg_level > 0) {
+ std::cout << "test point is " << (inside ? "inside\n" : "outside\n");
+ }
+ bool do_remove;
+ bool do_flip;
+ switch (op) {
+ case BoolOpType::Intersect:
+ do_remove = !inside;
+ do_flip = false;
+ break;
+ case BoolOpType::Union:
+ do_remove = inside;
+ do_flip = false;
+ break;
+ case BoolOpType::Difference:
+ do_remove = (shape == 0) ? inside : !inside;
+ do_flip = (shape == 1);
+ break;
+ default:
+ do_remove = false;
+ do_flip = false;
+ BLI_assert(false);
+ }
+ if (dbg_level > 0) {
+ std::cout << "result for patch " << p << ": remove=" << do_remove << ", flip=" << do_flip
+ << "\n";
+ }
+ if (!do_remove) {
+ for (int t : patch.tris()) {
+ Face *f = tm.face(t);
+ if (!do_flip) {
+ out_faces.append(f);
+ }
+ else {
+ Face &tri = *f;
+ /* We need flipped version of f. */
+ Array<const Vert *> flipped_vs = {tri[0], tri[2], tri[1]};
+ Array<int> flipped_e_origs = {tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
+ Array<bool> flipped_is_intersect = {
+ tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
+ Face *flipped_f = arena->add_face(
+ flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
+ out_faces.append(flipped_f);
+ }
+ }
+ }
+ }
+ ans.set_faces(out_faces);
+ return ans;
+}
+
+/**
+ * Which CDT output edge index is for an edge between output verts
+ * v1 and v2 (in either order)?
+ * \return -1 if none.
+ */
+static int find_cdt_edge(const CDT_result<mpq_class> &cdt_out, int v1, int v2)
+{
+ for (int e : cdt_out.edge.index_range()) {
+ const std::pair<int, int> &edge = cdt_out.edge[e];
+ if ((edge.first == v1 && edge.second == v2) || (edge.first == v2 && edge.second == v1)) {
+ return e;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Tessellate face f into triangles and return an array of `const Face *`
+ * giving that triangulation.
+ * Care is taken so that the original edge index associated with
+ * each edge in the output triangles either matches the original edge
+ * for the (identical) edge of f, or else is -1. So diagonals added
+ * for triangulation can later be identified by having #NO_INDEX for original.
+ */
+static Array<Face *> triangulate_poly(Face *f, IMeshArena *arena)
+{
+ int flen = f->size();
+ CDT_input<mpq_class> cdt_in;
+ cdt_in.vert = Array<mpq2>(flen);
+ cdt_in.face = Array<Vector<int>>(1);
+ cdt_in.face[0].reserve(flen);
+ for (int i : f->index_range()) {
+ cdt_in.face[0].append(i);
+ }
+ /* Project poly along dominant axis of normal to get 2d coords. */
+ if (!f->plane_populated()) {
+ f->populate_plane(false);
+ }
+ const double3 &poly_normal = f->plane->norm;
+ int axis = double3::dominant_axis(poly_normal);
+ /* If project down y axis as opposed to x or z, the orientation
+ * of the polygon will be reversed.
+ * Yet another reversal happens if the poly normal in the dominant
+ * direction is opposite that of the positive dominant axis. */
+ bool rev1 = (axis == 1);
+ bool rev2 = poly_normal[axis] < 0;
+ bool rev = rev1 ^ rev2;
+ for (int i = 0; i < flen; ++i) {
+ int ii = rev ? flen - i - 1 : i;
+ mpq2 &p2d = cdt_in.vert[ii];
+ int k = 0;
+ for (int j = 0; j < 3; ++j) {
+ if (j != axis) {
+ p2d[k++] = (*f)[ii]->co_exact[j];
+ }
+ }
+ }
+ CDT_result<mpq_class> cdt_out = delaunay_2d_calc(cdt_in, CDT_INSIDE);
+ int n_tris = cdt_out.face.size();
+ Array<Face *> ans(n_tris);
+ for (int t = 0; t < n_tris; ++t) {
+ int i_v_out[3];
+ const Vert *v[3];
+ int eo[3];
+ for (int i = 0; i < 3; ++i) {
+ i_v_out[i] = cdt_out.face[t][i];
+ v[i] = (*f)[cdt_out.vert_orig[i_v_out[i]][0]];
+ }
+ for (int i = 0; i < 3; ++i) {
+ int e_out = find_cdt_edge(cdt_out, i_v_out[i], i_v_out[(i + 1) % 3]);
+ BLI_assert(e_out != -1);
+ eo[i] = NO_INDEX;
+ for (int orig : cdt_out.edge_orig[e_out]) {
+ if (orig != NO_INDEX) {
+ eo[i] = orig;
+ break;
+ }
+ }
+ }
+ if (rev) {
+ ans[t] = arena->add_face(
+ {v[0], v[2], v[1]}, f->orig, {eo[2], eo[1], eo[0]}, {false, false, false});
+ }
+ else {
+ ans[t] = arena->add_face(
+ {v[0], v[1], v[2]}, f->orig, {eo[0], eo[1], eo[2]}, {false, false, false});
+ }
+ }
+ return ans;
+}
+
+/**
+ * Return an #IMesh that is a triangulation of a mesh with general
+ * polygonal faces, #imesh.
+ * Added diagonals will be distinguishable by having edge original
+ * indices of #NO_INDEX.
+ */
+static IMesh triangulate_polymesh(IMesh &imesh, IMeshArena *arena)
+{
+ Vector<Face *> face_tris;
+ constexpr int estimated_tris_per_face = 3;
+ face_tris.reserve(estimated_tris_per_face * imesh.face_size());
+ for (Face *f : imesh.faces()) {
+ /* Tessellate face f, following plan similar to #BM_face_calc_tesselation. */
+ int flen = f->size();
+ if (flen == 3) {
+ face_tris.append(f);
+ }
+ else if (flen == 4) {
+ const Vert *v0 = (*f)[0];
+ const Vert *v1 = (*f)[1];
+ const Vert *v2 = (*f)[2];
+ const Vert *v3 = (*f)[3];
+ int eo_01 = f->edge_orig[0];
+ int eo_12 = f->edge_orig[1];
+ int eo_23 = f->edge_orig[2];
+ int eo_30 = f->edge_orig[3];
+ Face *f0 = arena->add_face({v0, v1, v2}, f->orig, {eo_01, eo_12, -1}, {false, false, false});
+ Face *f1 = arena->add_face({v0, v2, v3}, f->orig, {-1, eo_23, eo_30}, {false, false, false});
+ face_tris.append(f0);
+ face_tris.append(f1);
+ }
+ else {
+ Array<Face *> tris = triangulate_poly(f, arena);
+ for (Face *tri : tris) {
+ face_tris.append(tri);
+ }
+ }
+ }
+ return IMesh(face_tris);
+}
+
+/**
+ * If \a tri1 and \a tri2 have a common edge (in opposite orientation),
+ * return the indices into \a tri1 and \a tri2 where that common edge starts. Else return (-1,-1).
+ */
+static std::pair<int, int> find_tris_common_edge(const Face &tri1, const Face &tri2)
+{
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ if (tri1[(i + 1) % 3] == tri2[j] && tri1[i] == tri2[(j + 1) % 3]) {
+ return std::pair<int, int>(i, j);
+ }
+ }
+ }
+ return std::pair<int, int>(-1, -1);
+}
+
+struct MergeEdge {
+ /** Length (squared) of the edge, used for sorting. */
+ double len_squared = 0.0;
+ /* v1 and v2 are the ends of the edge, ordered so that `v1->id < v2->id` */
+ const Vert *v1 = nullptr;
+ const Vert *v2 = nullptr;
+ /* left_face and right_face are indices into #FaceMergeState.face. */
+ int left_face = -1;
+ int right_face = -1;
+ int orig = -1; /* An edge orig index that can be used for this edge. */
+ /** Is it allowed to dissolve this edge? */
+ bool dissolvable = false;
+ /** Is this an intersect edge? */
+ bool is_intersect = false;
+
+ MergeEdge() = default;
+
+ MergeEdge(const Vert *va, const Vert *vb)
+ {
+ if (va->id < vb->id) {
+ this->v1 = va;
+ this->v2 = vb;
+ }
+ else {
+ this->v1 = vb;
+ this->v2 = va;
+ }
+ };
+};
+
+struct MergeFace {
+ /** The current sequence of Verts forming this face. */
+ Vector<const Vert *> vert;
+ /** For each position in face, what is index in #FaceMergeState of edge for that position? */
+ Vector<int> edge;
+ /** If not -1, merge_to gives a face index in #FaceMergeState that this is merged to. */
+ int merge_to = -1;
+ /** A face->orig that can be used for the merged face. */
+ int orig = -1;
+};
+struct FaceMergeState {
+ /**
+ * The faces being considered for merging. Some will already have been merge (merge_to != -1).
+ */
+ Vector<MergeFace> face;
+ /**
+ * The edges that are part of the faces in face[], together with current topological
+ * information (their left and right faces) and whether or not they are dissolvable.
+ */
+ Vector<MergeEdge> edge;
+ /**
+ * `edge_map` maps a pair of `const Vert *` ids (in canonical order: smaller id first)
+ * to the index in the above edge vector in which to find the corresponding #MergeEdge.
+ */
+ Map<std::pair<int, int>, int> edge_map;
+};
+
+static std::ostream &operator<<(std::ostream &os, const FaceMergeState &fms)
+{
+ os << "faces:\n";
+ for (int f : fms.face.index_range()) {
+ const MergeFace &mf = fms.face[f];
+ std::cout << f << ": orig=" << mf.orig << " verts ";
+ for (const Vert *v : mf.vert) {
+ std::cout << v << " ";
+ }
+ std::cout << "\n";
+ std::cout << " edges " << mf.edge << "\n";
+ std::cout << " merge_to = " << mf.merge_to << "\n";
+ }
+ os << "\nedges:\n";
+ for (int e : fms.edge.index_range()) {
+ const MergeEdge &me = fms.edge[e];
+ std::cout << e << ": (" << me.v1 << "," << me.v2 << ") left=" << me.left_face
+ << " right=" << me.right_face << " dis=" << me.dissolvable << " orig=" << me.orig
+ << " is_int=" << me.is_intersect << "\n";
+ }
+ return os;
+}
+
+/**
+ * \a tris all have the same original face.
+ * Find the 2d edge/triangle topology for these triangles, but only the ones facing in the
+ * norm direction, and whether each edge is dissolvable or not.
+ */
+static void init_face_merge_state(FaceMergeState *fms,
+ const Vector<int> &tris,
+ const IMesh &tm,
+ const double3 &norm)
+{
+ constexpr int dbg_level = 0;
+ /* Reserve enough faces and edges so that neither will have to resize. */
+ fms->face.reserve(tris.size() + 1);
+ fms->edge.reserve(3 * tris.size());
+ fms->edge_map.reserve(3 * tris.size());
+ if (dbg_level > 0) {
+ std::cout << "\nINIT_FACE_MERGE_STATE\n";
+ }
+ for (int t : tris.index_range()) {
+ MergeFace mf;
+ const Face &tri = *tm.face(tris[t]);
+ if (dbg_level > 0) {
+ std::cout << "process tri = " << &tri << "\n";
+ }
+ BLI_assert(tri.plane_populated());
+ if (double3::dot(norm, tri.plane->norm) <= 0.0) {
+ if (dbg_level > 0) {
+ std::cout << "triangle has wrong orientation, skipping\n";
+ }
+ continue;
+ }
+ mf.vert.append(tri[0]);
+ mf.vert.append(tri[1]);
+ mf.vert.append(tri[2]);
+ mf.orig = tri.orig;
+ int f = fms->face.append_and_get_index(mf);
+ if (dbg_level > 1) {
+ std::cout << "appended MergeFace for tri at f = " << f << "\n";
+ }
+ for (int i = 0; i < 3; ++i) {
+ int inext = (i + 1) % 3;
+ MergeEdge new_me(mf.vert[i], mf.vert[inext]);
+ std::pair<int, int> canon_vs(new_me.v1->id, new_me.v2->id);
+ int me_index = fms->edge_map.lookup_default(canon_vs, -1);
+ if (dbg_level > 1) {
+ std::cout << "new_me = canon_vs = " << new_me.v1 << ", " << new_me.v2 << "\n";
+ std::cout << "me_index lookup = " << me_index << "\n";
+ }
+ if (me_index == -1) {
+ double3 vec = new_me.v2->co - new_me.v1->co;
+ new_me.len_squared = vec.length_squared();
+ new_me.orig = tri.edge_orig[i];
+ new_me.is_intersect = tri.is_intersect[i];
+ new_me.dissolvable = (new_me.orig == NO_INDEX && !new_me.is_intersect);
+ fms->edge.append(new_me);
+ me_index = fms->edge.size() - 1;
+ fms->edge_map.add_new(canon_vs, me_index);
+ if (dbg_level > 1) {
+ std::cout << "added new me with me_index = " << me_index << "\n";
+ std::cout << " len_squared = " << new_me.len_squared << " orig = " << new_me.orig
+ << ", is_intersect" << new_me.is_intersect
+ << ", dissolvable = " << new_me.dissolvable << "\n";
+ }
+ }
+ MergeEdge &me = fms->edge[me_index];
+ if (dbg_level > 1) {
+ std::cout << "retrieved me at index " << me_index << ":\n";
+ std::cout << " v1 = " << me.v1 << " v2 = " << me.v2 << "\n";
+ std::cout << " dis = " << me.dissolvable << " int = " << me.is_intersect << "\n";
+ std::cout << " left_face = " << me.left_face << " right_face = " << me.right_face << "\n";
+ }
+ if (me.dissolvable && tri.edge_orig[i] != NO_INDEX) {
+ if (dbg_level > 1) {
+ std::cout << "reassigning orig to " << tri.edge_orig[i] << ", dissolvable = false\n";
+ }
+ me.dissolvable = false;
+ me.orig = tri.edge_orig[i];
+ }
+ if (me.dissolvable && tri.is_intersect[i]) {
+ if (dbg_level > 1) {
+ std::cout << "reassigning dissolvable = false, is_intersect = true\n";
+ }
+ me.dissolvable = false;
+ me.is_intersect = true;
+ }
+ /* This face is left or right depending on orientation of edge. */
+ if (me.v1 == mf.vert[i]) {
+ if (dbg_level > 1) {
+ std::cout << "me.v1 == mf.vert[i] so set edge[" << me_index << "].left_face = " << f
+ << "\n";
+ }
+ BLI_assert(me.left_face == -1);
+ fms->edge[me_index].left_face = f;
+ }
+ else {
+ if (dbg_level > 1) {
+ std::cout << "me.v1 != mf.vert[i] so set edge[" << me_index << "].right_face = " << f
+ << "\n";
+ }
+ BLI_assert(me.right_face == -1);
+ fms->edge[me_index].right_face = f;
+ }
+ fms->face[f].edge.append(me_index);
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << *fms;
+ }
+}
+
+/**
+ * To have a valid #BMesh, there are constraints on what edges can be removed.
+ * We cannot remove an edge if (a) it would create two disconnected boundary parts
+ * (which will happen if there's another edge sharing the same two faces);
+ * or (b) it would create a face with a repeated vertex.
+ */
+static bool dissolve_leaves_valid_bmesh(FaceMergeState *fms,
+ const MergeEdge &me,
+ int me_index,
+ const MergeFace &mf_left,
+ const MergeFace &mf_right)
+{
+ int a_edge_start = mf_left.edge.first_index_of_try(me_index);
+ BLI_assert(a_edge_start != -1);
+ int alen = mf_left.vert.size();
+ int blen = mf_right.vert.size();
+ int b_left_face = me.right_face;
+ bool ok = true;
+ /* Is there another edge, not me, in A's face, whose right face is B's left? */
+ for (int a_e_index = (a_edge_start + 1) % alen; ok && a_e_index != a_edge_start;
+ a_e_index = (a_e_index + 1) % alen) {
+ const MergeEdge &a_me_cur = fms->edge[mf_left.edge[a_e_index]];
+ if (a_me_cur.right_face == b_left_face) {
+ ok = false;
+ }
+ }
+ /* Is there a vert in A, not me.v1 or me.v2, that is also in B?
+ * One could avoid this O(n^2) algorithm if had a structure
+ * saying which faces a vertex touches. */
+ for (int a_v_index = 0; ok && a_v_index < alen; ++a_v_index) {
+ const Vert *a_v = mf_left.vert[a_v_index];
+ if (a_v != me.v1 && a_v != me.v2) {
+ for (int b_v_index = 0; b_v_index < blen; ++b_v_index) {
+ const Vert *b_v = mf_right.vert[b_v_index];
+ if (a_v == b_v) {
+ ok = false;
+ }
+ }
+ }
+ }
+ return ok;
+}
+
+/**
+ * mf_left and mf_right should share a #MergeEdge me, having index me_index.
+ * We change mf_left to remove edge me and insert the appropriate edges of
+ * mf_right in between the start and end vertices of that edge.
+ * We change the left face of the spliced-in edges to be mf_left's index.
+ * We mark the merge_to property of mf_right, which is now in essence deleted.
+ */
+static void splice_faces(
+ FaceMergeState *fms, MergeEdge &me, int me_index, MergeFace &mf_left, MergeFace &mf_right)
+{
+ int a_edge_start = mf_left.edge.first_index_of_try(me_index);
+ int b_edge_start = mf_right.edge.first_index_of_try(me_index);
+ BLI_assert(a_edge_start != -1 && b_edge_start != -1);
+ int alen = mf_left.vert.size();
+ int blen = mf_right.vert.size();
+ Vector<const Vert *> splice_vert;
+ Vector<int> splice_edge;
+ splice_vert.reserve(alen + blen - 2);
+ splice_edge.reserve(alen + blen - 2);
+ int ai = 0;
+ while (ai < a_edge_start) {
+ splice_vert.append(mf_left.vert[ai]);
+ splice_edge.append(mf_left.edge[ai]);
+ ++ai;
+ }
+ int bi = b_edge_start + 1;
+ while (bi != b_edge_start) {
+ if (bi >= blen) {
+ bi = 0;
+ if (bi == b_edge_start) {
+ break;
+ }
+ }
+ splice_vert.append(mf_right.vert[bi]);
+ splice_edge.append(mf_right.edge[bi]);
+ if (mf_right.vert[bi] == fms->edge[mf_right.edge[bi]].v1) {
+ fms->edge[mf_right.edge[bi]].left_face = me.left_face;
+ }
+ else {
+ fms->edge[mf_right.edge[bi]].right_face = me.left_face;
+ }
+ ++bi;
+ }
+ ai = a_edge_start + 1;
+ while (ai < alen) {
+ splice_vert.append(mf_left.vert[ai]);
+ splice_edge.append(mf_left.edge[ai]);
+ ++ai;
+ }
+ mf_right.merge_to = me.left_face;
+ mf_left.vert = splice_vert;
+ mf_left.edge = splice_edge;
+ me.left_face = -1;
+ me.right_face = -1;
+}
+
+/**
+ * Given that fms has been properly initialized to contain a set of faces that
+ * together form a face or part of a face of the original #IMesh, and that
+ * it has properly recorded with faces are dissolvable, dissolve as many edges as possible.
+ * We try to dissolve in decreasing order of edge length, so that it is more likely
+ * that the final output doesn't have awkward looking long edges with extreme angles.
+ */
+static void do_dissolve(FaceMergeState *fms)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 1) {
+ std::cout << "\nDO_DISSOLVE\n";
+ }
+ Vector<int> dissolve_edges;
+ for (int e : fms->edge.index_range()) {
+ if (fms->edge[e].dissolvable) {
+ dissolve_edges.append(e);
+ }
+ }
+ if (dissolve_edges.size() == 0) {
+ return;
+ }
+ /* Things look nicer if we dissolve the longer edges first. */
+ std::sort(
+ dissolve_edges.begin(), dissolve_edges.end(), [fms](const int &a, const int &b) -> bool {
+ return (fms->edge[a].len_squared > fms->edge[b].len_squared);
+ });
+ if (dbg_level > 0) {
+ std::cout << "Sorted dissolvable edges: " << dissolve_edges << "\n";
+ }
+ for (int me_index : dissolve_edges) {
+ MergeEdge &me = fms->edge[me_index];
+ if (me.left_face == -1 || me.right_face == -1) {
+ continue;
+ }
+ MergeFace &mf_left = fms->face[me.left_face];
+ MergeFace &mf_right = fms->face[me.right_face];
+ if (!dissolve_leaves_valid_bmesh(fms, me, me_index, mf_left, mf_right)) {
+ continue;
+ }
+ if (dbg_level > 0) {
+ std::cout << "Removing edge " << me_index << "\n";
+ }
+ splice_faces(fms, me, me_index, mf_left, mf_right);
+ if (dbg_level > 1) {
+ std::cout << "state after removal:\n";
+ std::cout << *fms;
+ }
+ }
+}
+
+/**
+ * Given that \a tris form a triangulation of a face or part of a face that was in \a imesh_in,
+ * merge as many of the triangles together as possible, by dissolving the edges between them.
+ * We can only dissolve triangulation edges that don't overlap real input edges, and we
+ * can only dissolve them if doing so leaves the remaining faces able to create valid #BMesh.
+ * We can tell edges that don't overlap real input edges because they will have an
+ * "original edge" that is different from #NO_INDEX.
+ * \note it is possible that some of the triangles in \a tris have reversed orientation
+ * to the rest, so we have to handle the two cases separately.
+ */
+static Vector<Face *> merge_tris_for_face(Vector<int> tris,
+ const IMesh &tm,
+ const IMesh &imesh_in,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "merge_tris_for_face\n";
+ std::cout << "tris: " << tris << "\n";
+ }
+ Vector<Face *> ans;
+ if (tris.size() <= 1) {
+ if (tris.size() == 1) {
+ ans.append(tm.face(tris[0]));
+ }
+ return ans;
+ }
+ bool done = false;
+ double3 first_tri_normal = tm.face(tris[0])->plane->norm;
+ double3 second_tri_normal = tm.face(tris[1])->plane->norm;
+ if (tris.size() == 2 && double3::dot(first_tri_normal, second_tri_normal) > 0.0) {
+ /* Is this a case where quad with one diagonal remained unchanged?
+ * Worth special handling because this case will be very common. */
+ Face &tri1 = *tm.face(tris[0]);
+ Face &tri2 = *tm.face(tris[1]);
+ Face *in_face = imesh_in.face(tri1.orig);
+ if (in_face->size() == 4) {
+ std::pair<int, int> estarts = find_tris_common_edge(tri1, tri2);
+ if (estarts.first != -1 && tri1.edge_orig[estarts.first] == NO_INDEX) {
+ if (dbg_level > 0) {
+ std::cout << "try recovering orig quad case\n";
+ std::cout << "tri1 = " << &tri1 << "\n";
+ std::cout << "tri1 = " << &tri2 << "\n";
+ }
+ int i0 = estarts.first;
+ int i1 = (i0 + 1) % 3;
+ int i2 = (i0 + 2) % 3;
+ int j2 = (estarts.second + 2) % 3;
+ Face tryface({tri1[i1], tri1[i2], tri1[i0], tri2[j2]}, -1, -1, {}, {});
+ if (tryface.cyclic_equal(*in_face)) {
+ if (dbg_level > 0) {
+ std::cout << "inface = " << in_face << "\n";
+ std::cout << "quad recovery worked\n";
+ }
+ ans.append(in_face);
+ done = true;
+ }
+ }
+ }
+ }
+ if (done) {
+ return ans;
+ }
+
+ double3 first_tri_normal_rev = -first_tri_normal;
+ for (const double3 &norm : {first_tri_normal, first_tri_normal_rev}) {
+ FaceMergeState fms;
+ init_face_merge_state(&fms, tris, tm, norm);
+ do_dissolve(&fms);
+ if (dbg_level > 0) {
+ std::cout << "faces in merged result:\n";
+ }
+ for (const MergeFace &mf : fms.face) {
+ if (mf.merge_to == -1) {
+ Array<int> e_orig(mf.edge.size());
+ Array<bool> is_intersect(mf.edge.size());
+ for (int i : mf.edge.index_range()) {
+ e_orig[i] = fms.edge[mf.edge[i]].orig;
+ is_intersect[i] = fms.edge[mf.edge[i]].is_intersect;
+ }
+ Face *facep = arena->add_face(mf.vert, mf.orig, e_orig, is_intersect);
+ ans.append(facep);
+ if (dbg_level > 0) {
+ std::cout << " " << facep << "\n";
+ }
+ }
+ }
+ }
+ return ans;
+}
+
+/**
+ * Return an array, paralleling imesh_out.vert, saying which vertices can be dissolved.
+ * A vertex v can be dissolved if (a) it is not an input vertex; (b) it has valence 2;
+ * and (c) if v's two neighboring vertices are u and w, then (u,v,w) forms a straight line.
+ * Return the number of dissolvable vertices in r_count_dissolve.
+ */
+static Array<bool> find_dissolve_verts(IMesh &imesh_out, int *r_count_dissolve)
+{
+ imesh_out.populate_vert();
+ /* dissolve[i] will say whether imesh_out.vert(i) can be dissolved. */
+ Array<bool> dissolve(imesh_out.vert_size());
+ for (int v_index : imesh_out.vert_index_range()) {
+ const Vert &vert = *imesh_out.vert(v_index);
+ dissolve[v_index] = (vert.orig == NO_INDEX);
+ }
+ /* neighbors[i] will be a pair giving the up-to-two neighboring vertices
+ * of the vertex v in position i of imesh_out.vert.
+ * If we encounter a third, then v will not be dissolvable. */
+ Array<std::pair<const Vert *, const Vert *>> neighbors(
+ imesh_out.vert_size(), std::pair<const Vert *, const Vert *>(nullptr, nullptr));
+ for (int f : imesh_out.face_index_range()) {
+ const Face &face = *imesh_out.face(f);
+ for (int i : face.index_range()) {
+ const Vert *v = face[i];
+ int v_index = imesh_out.lookup_vert(v);
+ BLI_assert(v_index != NO_INDEX);
+ if (dissolve[v_index]) {
+ const Vert *n1 = face[face.next_pos(i)];
+ const Vert *n2 = face[face.prev_pos(i)];
+ const Vert *f_n1 = neighbors[v_index].first;
+ const Vert *f_n2 = neighbors[v_index].second;
+ if (f_n1 != nullptr) {
+ /* Already has a neighbor in another face; can't dissolve unless they are the same. */
+ if (!((n1 == f_n2 && n2 == f_n1) || (n1 == f_n1 && n2 == f_n2))) {
+ /* Different neighbors, so can't dissolve. */
+ dissolve[v_index] = false;
+ }
+ }
+ else {
+ /* These are the first-seen neighbors. */
+ neighbors[v_index] = std::pair<const Vert *, const Vert *>(n1, n2);
+ }
+ }
+ }
+ }
+ int count = 0;
+ for (int v_out : imesh_out.vert_index_range()) {
+ if (dissolve[v_out]) {
+ dissolve[v_out] = false; /* Will set back to true if final condition is satisfied. */
+ const std::pair<const Vert *, const Vert *> &nbrs = neighbors[v_out];
+ if (nbrs.first != nullptr) {
+ BLI_assert(nbrs.second != nullptr);
+ const mpq3 &co1 = nbrs.first->co_exact;
+ const mpq3 &co2 = nbrs.second->co_exact;
+ const mpq3 &co = imesh_out.vert(v_out)->co_exact;
+ mpq3 dir1 = co - co1;
+ mpq3 dir2 = co2 - co;
+ mpq3 cross = mpq3::cross(dir1, dir2);
+ if (cross[0] == 0 && cross[1] == 0 && cross[2] == 0) {
+ dissolve[v_out] = true;
+ ++count;
+ }
+ }
+ }
+ }
+ if (r_count_dissolve != nullptr) {
+ *r_count_dissolve = count;
+ }
+ return dissolve;
+}
+
+/**
+ * The dissolve array parallels the `imesh.vert` array. Wherever it is true,
+ * remove the corresponding vertex from the vertices in the faces of
+ * `imesh.faces` to account for the close-up of the gaps in `imesh.vert`.
+ */
+static void dissolve_verts(IMesh *imesh, const Array<bool> dissolve, IMeshArena *arena)
+{
+ constexpr int inline_face_size = 100;
+ Vector<bool, inline_face_size> face_pos_erase;
+ for (int f : imesh->face_index_range()) {
+ const Face &face = *imesh->face(f);
+ face_pos_erase.clear();
+ int num_erase = 0;
+ for (const Vert *v : face) {
+ int v_index = imesh->lookup_vert(v);
+ BLI_assert(v_index != NO_INDEX);
+ if (dissolve[v_index]) {
+ face_pos_erase.append(true);
+ ++num_erase;
+ }
+ else {
+ face_pos_erase.append(false);
+ }
+ }
+ if (num_erase > 0) {
+ imesh->erase_face_positions(f, face_pos_erase, arena);
+ }
+ }
+ imesh->set_dirty_verts();
+}
+
+/**
+ * The main boolean function operates on a triangle #IMesh and produces a
+ * Triangle #IMesh as output.
+ * This function converts back into a general polygonal mesh by removing
+ * any possible triangulation edges (which can be identified because they
+ * will have an original edge that is NO_INDEX.
+ * Not all triangulation edges can be removed: if they ended up non-trivially overlapping a real
+ * input edge, then we need to keep it. Also, some are necessary to make the output satisfy
+ * the "valid #BMesh" property: we can't produce output faces that have repeated vertices in them,
+ * or have several disconnected boundaries (e.g., faces with holes).
+ */
+static IMesh polymesh_from_trimesh_with_dissolve(const IMesh &tm_out,
+ const IMesh &imesh_in,
+ IMeshArena *arena)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 1) {
+ std::cout << "\nPOLYMESH_FROM_TRIMESH_WITH_DISSOLVE\n";
+ }
+ /* For now: need plane normals for all triangles. */
+ for (Face *tri : tm_out.faces()) {
+ tri->populate_plane(false);
+ }
+ /* Gather all output triangles that are part of each input face.
+ * face_output_tris[f] will be indices of triangles in tm_out
+ * that have f as their original face. */
+ int tot_in_face = imesh_in.face_size();
+ Array<Vector<int>> face_output_tris(tot_in_face);
+ for (int t : tm_out.face_index_range()) {
+ const Face &tri = *tm_out.face(t);
+ int in_face = tri.orig;
+ face_output_tris[in_face].append(t);
+ }
+ if (dbg_level > 1) {
+ std::cout << "face_output_tris:\n";
+ for (int f : face_output_tris.index_range()) {
+ std::cout << f << ": " << face_output_tris[f] << "\n";
+ }
+ }
+
+ /* Merge triangles that we can from face_output_tri to make faces for output.
+ * face_output_face[f] will be new original const Face *'s that
+ * make up whatever part of the boolean output remains of input face f. */
+ Array<Vector<Face *>> face_output_face(tot_in_face);
+ int tot_out_face = 0;
+ for (int in_f : imesh_in.face_index_range()) {
+ if (dbg_level > 1) {
+ std::cout << "merge tris for face " << in_f << "\n";
+ }
+ int num_out_tris_for_face = face_output_tris.size();
+ if (num_out_tris_for_face == 0) {
+ continue;
+ }
+ face_output_face[in_f] = merge_tris_for_face(face_output_tris[in_f], tm_out, imesh_in, arena);
+ tot_out_face += face_output_face[in_f].size();
+ }
+ Array<Face *> face(tot_out_face);
+ int out_f_index = 0;
+ for (int in_f : imesh_in.face_index_range()) {
+ const Vector<Face *> &f_faces = face_output_face[in_f];
+ if (f_faces.size() > 0) {
+ std::copy(f_faces.begin(), f_faces.end(), &face[out_f_index]);
+ out_f_index += f_faces.size();
+ }
+ }
+ IMesh imesh_out(face);
+ /* Dissolve vertices that were (a) not original; and (b) now have valence 2 and
+ * are between two other vertices that are exactly in line with them.
+ * These were created because of triangulation edges that have been dissolved. */
+ int count_dissolve;
+ Array<bool> v_dissolve = find_dissolve_verts(imesh_out, &count_dissolve);
+ if (count_dissolve > 0) {
+ dissolve_verts(&imesh_out, v_dissolve, arena);
+ }
+ if (dbg_level > 1) {
+ write_obj_mesh(imesh_out, "boolean_post_dissolve");
+ }
+
+ return imesh_out;
+}
+
+/**
+ * This function does a boolean operation on a TriMesh with nshapes inputs.
+ * All the shapes are combined in tm_in.
+ * The shape_fn function should take a triangle index in tm_in and return
+ * a number in the range 0 to `nshapes-1`, to say which shape that triangle is in.
+ */
+IMesh boolean_trimesh(IMesh &tm_in,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "BOOLEAN of " << nshapes << " operand" << (nshapes == 1 ? "" : "s")
+ << " op=" << bool_optype_name(op) << "\n";
+ if (dbg_level > 1) {
+ tm_in.populate_vert();
+ std::cout << "boolean_trimesh input:\n" << tm_in;
+ write_obj_mesh(tm_in, "boolean_in");
+ }
+ }
+ if (tm_in.face_size() == 0) {
+ return IMesh(tm_in);
+ }
+ IMesh tm_si;
+ if (use_self) {
+ tm_si = trimesh_self_intersect(tm_in, arena);
+ }
+ else {
+ tm_si = trimesh_nary_intersect(tm_in, nshapes, shape_fn, use_self, arena);
+ }
+ if (dbg_level > 1) {
+ write_obj_mesh(tm_si, "boolean_tm_si");
+ std::cout << "\nboolean_tm_input after intersection:\n" << tm_si;
+ }
+ /* It is possible for tm_si to be empty if all the input triangles are bogus/degenerate. */
+ if (tm_si.face_size() == 0 || op == BoolOpType::None) {
+ return tm_si;
+ }
+ auto si_shape_fn = [shape_fn, tm_si](int t) { return shape_fn(tm_si.face(t)->orig); };
+ TriMeshTopology tm_si_topo(tm_si);
+ PatchesInfo pinfo = find_patches(tm_si, tm_si_topo);
+ IMesh tm_out;
+ if (!is_pwn(tm_si, tm_si_topo)) {
+ if (dbg_level > 0) {
+ std::cout << "Input is not PWN, using gwn method\n";
+ }
+ tm_out = gwn_boolean(tm_si, op, nshapes, shape_fn, pinfo, arena);
+ }
+ else {
+ CellsInfo cinfo = find_cells(tm_si, tm_si_topo, pinfo);
+ if (dbg_level > 0) {
+ std::cout << "Input is PWN\n";
+ }
+ finish_patch_cell_graph(tm_si, cinfo, pinfo, tm_si_topo, arena);
+ bool pc_ok = patch_cell_graph_ok(cinfo, pinfo);
+ if (!pc_ok) {
+ /* TODO: if bad input can lead to this, diagnose the problem. */
+ std::cout << "Something funny about input or a bug in boolean\n";
+ return IMesh(tm_in);
+ }
+ cinfo.init_windings(nshapes);
+ int c_ambient = find_ambient_cell(tm_si, nullptr, tm_si_topo, pinfo, arena);
+ if (c_ambient == NO_INDEX) {
+ /* TODO: find a way to propagate this error to user properly. */
+ std::cout << "Could not find an ambient cell; input not valid?\n";
+ return IMesh(tm_si);
+ }
+ propagate_windings_and_in_output_volume(pinfo, cinfo, c_ambient, op, nshapes, si_shape_fn);
+ tm_out = extract_from_in_output_volume_diffs(tm_si, pinfo, cinfo, arena);
+ if (dbg_level > 0) {
+ /* Check if output is PWN. */
+ TriMeshTopology tm_out_topo(tm_out);
+ if (!is_pwn(tm_out, tm_out_topo)) {
+ std::cout << "OUTPUT IS NOT PWN!\n";
+ }
+ }
+ }
+ if (dbg_level > 1) {
+ write_obj_mesh(tm_out, "boolean_tm_output");
+ std::cout << "boolean tm output:\n" << tm_out;
+ }
+ return tm_out;
+}
+
+static void dump_test_spec(IMesh &imesh)
+{
+ std::cout << "test spec = " << imesh.vert_size() << " " << imesh.face_size() << "\n";
+ for (const Vert *v : imesh.vertices()) {
+ std::cout << v->co_exact[0] << " " << v->co_exact[1] << " " << v->co_exact[2] << " # "
+ << v->co[0] << " " << v->co[1] << " " << v->co[2] << "\n";
+ }
+ for (const Face *f : imesh.faces()) {
+ for (const Vert *fv : *f) {
+ std::cout << imesh.lookup_vert(fv) << " ";
+ }
+ std::cout << "\n";
+ }
+}
+
+/**
+ * Do the boolean operation op on the polygon mesh imesh_in.
+ * See the header file for a complete description.
+ */
+IMesh boolean_mesh(IMesh &imesh,
+ BoolOpType op,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMesh *imesh_triangulated,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nBOOLEAN_MESH\n"
+ << nshapes << " operand" << (nshapes == 1 ? "" : "s")
+ << " op=" << bool_optype_name(op) << "\n";
+ if (dbg_level > 1) {
+ write_obj_mesh(imesh, "boolean_mesh_in");
+ std::cout << imesh;
+ if (dbg_level > 2) {
+ dump_test_spec(imesh);
+ }
+ }
+ }
+ IMesh *tm_in = imesh_triangulated;
+ IMesh our_triangulation;
+ if (tm_in == nullptr) {
+ our_triangulation = triangulate_polymesh(imesh, arena);
+ tm_in = &our_triangulation;
+ }
+ if (dbg_level > 1) {
+ write_obj_mesh(*tm_in, "boolean_tm_in");
+ }
+ IMesh tm_out = boolean_trimesh(*tm_in, op, nshapes, shape_fn, use_self, arena);
+ if (dbg_level > 1) {
+ std::cout << "bool_trimesh_output:\n" << tm_out;
+ write_obj_mesh(tm_out, "bool_trimesh_output");
+ }
+ IMesh ans = polymesh_from_trimesh_with_dissolve(tm_out, imesh, arena);
+ if (dbg_level > 0) {
+ std::cout << "boolean_mesh output:\n" << ans;
+ }
+ return ans;
+}
+
+} // namespace blender::meshintersect
+
+#endif // WITH_GMP
diff --git a/source/blender/blenlib/intern/mesh_intersect.cc b/source/blender/blenlib/intern/mesh_intersect.cc
new file mode 100644
index 00000000000..d973775a797
--- /dev/null
+++ b/source/blender/blenlib/intern/mesh_intersect.cc
@@ -0,0 +1,3304 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+/* The #blender::meshintersect API needs GMP. */
+#ifdef WITH_GMP
+
+# include <algorithm>
+# include <fstream>
+# include <iostream>
+
+# include "BLI_allocator.hh"
+# include "BLI_array.hh"
+# include "BLI_assert.h"
+# include "BLI_delaunay_2d.h"
+# include "BLI_double3.hh"
+# include "BLI_float3.hh"
+# include "BLI_hash.hh"
+# include "BLI_kdopbvh.h"
+# include "BLI_map.hh"
+# include "BLI_math_boolean.hh"
+# include "BLI_math_mpq.hh"
+# include "BLI_mpq2.hh"
+# include "BLI_mpq3.hh"
+# include "BLI_span.hh"
+# include "BLI_task.h"
+# include "BLI_threads.h"
+# include "BLI_vector.hh"
+# include "BLI_vector_set.hh"
+
+# include "PIL_time.h"
+
+# include "BLI_mesh_intersect.hh"
+
+// # define PERFDEBUG
+
+namespace blender::meshintersect {
+
+# ifdef PERFDEBUG
+static void perfdata_init(void);
+static void incperfcount(int countnum);
+static void bumpperfcount(int countnum, int amt);
+static void doperfmax(int maxnum, int val);
+static void dump_perfdata(void);
+# endif
+
+/** For debugging, can disable threading in intersect code with this static constant. */
+static constexpr bool intersect_use_threading = true;
+
+Vert::Vert(const mpq3 &mco, const double3 &dco, int id, int orig)
+ : co_exact(mco), co(dco), id(id), orig(orig)
+{
+}
+
+bool Vert::operator==(const Vert &other) const
+{
+ return this->co_exact == other.co_exact;
+}
+
+uint64_t Vert::hash() const
+{
+ return co_exact.hash();
+}
+
+std::ostream &operator<<(std::ostream &os, const Vert *v)
+{
+ os << "v" << v->id;
+ if (v->orig != NO_INDEX) {
+ os << "o" << v->orig;
+ }
+ os << v->co;
+ return os;
+}
+
+bool Plane::operator==(const Plane &other) const
+{
+ return norm_exact == other.norm_exact && d_exact == other.d_exact;
+}
+
+void Plane::make_canonical()
+{
+ if (norm_exact[0] != 0) {
+ mpq_class den = norm_exact[0];
+ norm_exact = mpq3(1, norm_exact[1] / den, norm_exact[2] / den);
+ d_exact = d_exact / den;
+ }
+ else if (norm_exact[1] != 0) {
+ mpq_class den = norm_exact[1];
+ norm_exact = mpq3(0, 1, norm_exact[2] / den);
+ d_exact = d_exact / den;
+ }
+ else {
+ if (norm_exact[2] != 0) {
+ mpq_class den = norm_exact[2];
+ norm_exact = mpq3(0, 0, 1);
+ d_exact = d_exact / den;
+ }
+ else {
+ /* A degenerate plane. */
+ d_exact = 0;
+ }
+ }
+ norm = double3(norm_exact[0].get_d(), norm_exact[1].get_d(), norm_exact[2].get_d());
+ d = d_exact.get_d();
+}
+
+Plane::Plane(const mpq3 &norm_exact, const mpq_class &d_exact)
+ : norm_exact(norm_exact), d_exact(d_exact)
+{
+ norm = double3(norm_exact[0].get_d(), norm_exact[1].get_d(), norm_exact[2].get_d());
+ d = d_exact.get_d();
+}
+
+Plane::Plane(const double3 &norm, const double d) : norm(norm), d(d)
+{
+ norm_exact = mpq3(0, 0, 0); /* Marks as "exact not yet populated". */
+}
+
+/** This is wrong for degenerate planes, but we don't expect to call it on those. */
+bool Plane::exact_populated() const
+{
+ return norm_exact[0] != 0 || norm_exact[1] != 0 || norm_exact[2] != 0;
+}
+
+uint64_t Plane::hash() const
+{
+ constexpr uint64_t h1 = 33;
+ constexpr uint64_t h2 = 37;
+ constexpr uint64_t h3 = 39;
+ uint64_t hashx = hash_mpq_class(this->norm_exact.x);
+ uint64_t hashy = hash_mpq_class(this->norm_exact.y);
+ uint64_t hashz = hash_mpq_class(this->norm_exact.z);
+ uint64_t hashd = hash_mpq_class(this->d_exact);
+ uint64_t ans = hashx ^ (hashy * h1) ^ (hashz * h1 * h2) ^ (hashd * h1 * h2 * h3);
+ return ans;
+}
+
+std::ostream &operator<<(std::ostream &os, const Plane *plane)
+{
+ os << "[" << plane->norm << ";" << plane->d << "]";
+ return os;
+}
+
+Face::Face(
+ Span<const Vert *> verts, int id, int orig, Span<int> edge_origs, Span<bool> is_intersect)
+ : vert(verts), edge_orig(edge_origs), is_intersect(is_intersect), id(id), orig(orig)
+{
+}
+
+Face::Face(Span<const Vert *> verts, int id, int orig) : vert(verts), id(id), orig(orig)
+{
+}
+
+void Face::populate_plane(bool need_exact)
+{
+ if (plane != nullptr) {
+ if (!need_exact || plane->exact_populated()) {
+ return;
+ }
+ }
+ if (need_exact) {
+ mpq3 normal_exact;
+ if (vert.size() > 3) {
+ Array<mpq3> co(vert.size());
+ for (int i : index_range()) {
+ co[i] = vert[i]->co_exact;
+ }
+ normal_exact = mpq3::cross_poly(co);
+ }
+ else {
+ mpq3 tr02 = vert[0]->co_exact - vert[2]->co_exact;
+ mpq3 tr12 = vert[1]->co_exact - vert[2]->co_exact;
+ normal_exact = mpq3::cross(tr02, tr12);
+ }
+ mpq_class d_exact = -mpq3::dot(normal_exact, vert[0]->co_exact);
+ plane = new Plane(normal_exact, d_exact);
+ }
+ else {
+ double3 normal;
+ if (vert.size() > 3) {
+ Array<double3> co(vert.size());
+ for (int i : index_range()) {
+ co[i] = vert[i]->co;
+ }
+ normal = double3::cross_poly(co);
+ }
+ else {
+ double3 tr02 = vert[0]->co - vert[2]->co;
+ double3 tr12 = vert[1]->co - vert[2]->co;
+ normal = double3::cross_high_precision(tr02, tr12);
+ }
+ double d = -double3::dot(normal, vert[0]->co);
+ plane = new Plane(normal, d);
+ }
+}
+
+Face::~Face()
+{
+ if (plane != nullptr) {
+ delete plane;
+ }
+}
+
+bool Face::operator==(const Face &other) const
+{
+ if (this->size() != other.size()) {
+ return false;
+ }
+ for (FacePos i : index_range()) {
+ /* Can test pointer equality since we will have
+ * unique vert pointers for unique co_equal's. */
+ if (this->vert[i] != other.vert[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Face::cyclic_equal(const Face &other) const
+{
+ if (this->size() != other.size()) {
+ return false;
+ }
+ int flen = this->size();
+ for (FacePos start : index_range()) {
+ for (FacePos start_other : index_range()) {
+ bool ok = true;
+ for (int i = 0; ok && i < flen; ++i) {
+ FacePos p = (start + i) % flen;
+ FacePos p_other = (start_other + i) % flen;
+ if (this->vert[p] != other.vert[p_other]) {
+ ok = false;
+ }
+ }
+ if (ok) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+std::ostream &operator<<(std::ostream &os, const Face *f)
+{
+ os << "f" << f->id << "o" << f->orig << "[";
+ for (const Vert *v : *f) {
+ os << "v" << v->id;
+ if (v->orig != NO_INDEX) {
+ os << "o" << v->orig;
+ }
+ if (v != f->vert[f->size() - 1]) {
+ os << " ";
+ }
+ }
+ os << "]";
+ if (f->orig != NO_INDEX) {
+ os << "o" << f->orig;
+ }
+ os << " e_orig[";
+ for (int i : f->index_range()) {
+ os << f->edge_orig[i];
+ if (f->is_intersect[i]) {
+ os << "#";
+ }
+ if (i != f->size() - 1) {
+ os << " ";
+ }
+ }
+ os << "]";
+ return os;
+}
+
+/**
+ * Un-comment the following to try using a spin-lock instead of
+ * a mutex in the arena allocation routines.
+ * Initial tests showed that it doesn't seem to help very much,
+ * if at all, to use a spin-lock.
+ */
+// #define USE_SPINLOCK
+
+/**
+ * #IMeshArena is the owner of the Vert and Face resources used
+ * during a run of one of the mesh-intersect main functions.
+ * It also keeps has a hash table of all Verts created so that it can
+ * ensure that only one instance of a Vert with a given co_exact will
+ * exist. I.e., it de-duplicates the vertices.
+ */
+class IMeshArena::IMeshArenaImpl : NonCopyable, NonMovable {
+
+ /**
+ * Don't use Vert itself as key since resizing may move
+ * pointers to the Vert around, and we need to have those pointers
+ * stay the same throughout the lifetime of the #IMeshArena.
+ */
+ struct VSetKey {
+ Vert *vert;
+
+ VSetKey(Vert *p) : vert(p)
+ {
+ }
+
+ uint32_t hash() const
+ {
+ return vert->hash();
+ }
+
+ bool operator==(const VSetKey &other) const
+ {
+ return *this->vert == *other.vert;
+ }
+ };
+
+ VectorSet<VSetKey> vset_; /* TODO: replace with Set */
+
+ /**
+ * Ownership of the Vert memory is here, so destroying this reclaims that memory.
+ *
+ * TODO: replace these with pooled allocation, and just destroy the pools at the end.
+ */
+ Vector<std::unique_ptr<Vert>> allocated_verts_;
+ Vector<std::unique_ptr<Face>> allocated_faces_;
+
+ /* Use these to allocate ids when Verts and Faces are allocated. */
+ int next_vert_id_ = 0;
+ int next_face_id_ = 0;
+
+ /* Need a lock when multi-threading to protect allocation of new elements. */
+# ifdef USE_SPINLOCK
+ SpinLock lock_;
+# else
+ ThreadMutex *mutex_;
+# endif
+
+ public:
+ IMeshArenaImpl()
+ {
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_init(&lock_);
+# else
+ mutex_ = BLI_mutex_alloc();
+# endif
+ }
+ }
+ ~IMeshArenaImpl()
+ {
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_end(&lock_);
+# else
+ BLI_mutex_free(mutex_);
+# endif
+ }
+ }
+
+ void reserve(int vert_num_hint, int face_num_hint)
+ {
+ vset_.reserve(vert_num_hint);
+ allocated_verts_.reserve(vert_num_hint);
+ allocated_faces_.reserve(face_num_hint);
+ }
+
+ int tot_allocated_verts() const
+ {
+ return allocated_verts_.size();
+ }
+
+ int tot_allocated_faces() const
+ {
+ return allocated_faces_.size();
+ }
+
+ const Vert *add_or_find_vert(const mpq3 &co, int orig)
+ {
+ double3 dco(co[0].get_d(), co[1].get_d(), co[2].get_d());
+ return add_or_find_vert(co, dco, orig);
+ }
+
+ const Vert *add_or_find_vert(const double3 &co, int orig)
+ {
+ mpq3 mco(co[0], co[1], co[2]);
+ return add_or_find_vert(mco, co, orig);
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs, Span<bool> is_intersect)
+ {
+ Face *f = new Face(verts, next_face_id_++, orig, edge_origs, is_intersect);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ allocated_faces_.append(std::unique_ptr<Face>(f));
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return f;
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs)
+ {
+ Array<bool> is_intersect(verts.size(), false);
+ return add_face(verts, orig, edge_origs, is_intersect);
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig)
+ {
+ Array<int> edge_origs(verts.size(), NO_INDEX);
+ Array<bool> is_intersect(verts.size(), false);
+ return add_face(verts, orig, edge_origs, is_intersect);
+ }
+
+ const Vert *find_vert(const mpq3 &co)
+ {
+ const Vert *ans;
+ Vert vtry(co, double3(), NO_INDEX, NO_INDEX);
+ VSetKey vskey(&vtry);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ int i = vset_.index_of_try(vskey);
+ if (i == -1) {
+ ans = nullptr;
+ }
+ else {
+ ans = vset_[i].vert;
+ }
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return ans;
+ }
+
+ /**
+ * This is slow. Only used for unit tests right now.
+ * Since it is only used for that purpose, access is not lock-protected.
+ * The argument vs can be a cyclic shift of the actual stored Face.
+ */
+ const Face *find_face(Span<const Vert *> vs)
+ {
+ Array<int> eorig(vs.size(), NO_INDEX);
+ Array<bool> is_intersect(vs.size(), false);
+ Face ftry(vs, NO_INDEX, NO_INDEX, eorig, is_intersect);
+ for (const int i : allocated_faces_.index_range()) {
+ if (ftry.cyclic_equal(*allocated_faces_[i])) {
+ return allocated_faces_[i].get();
+ }
+ }
+ return nullptr;
+ }
+
+ private:
+ const Vert *add_or_find_vert(const mpq3 &mco, const double3 &dco, int orig)
+ {
+ /* Don't allocate Vert yet, in case it is already there. */
+ Vert vtry(mco, dco, NO_INDEX, NO_INDEX);
+ const Vert *ans;
+ VSetKey vskey(&vtry);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ int i = vset_.index_of_try(vskey);
+ if (i == -1) {
+ vskey.vert = new Vert(mco, dco, next_vert_id_++, orig);
+ vset_.add_new(vskey);
+ allocated_verts_.append(std::unique_ptr<Vert>(vskey.vert));
+ ans = vskey.vert;
+ }
+ else {
+ /* It was a duplicate, so return the existing one.
+ * Note that the returned Vert may have a different orig.
+ * This is the intended semantics: if the Vert already
+ * exists then we are merging verts and using the first-seen
+ * one as the canonical one. */
+ ans = vset_[i].vert;
+ }
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return ans;
+ };
+};
+
+IMeshArena::IMeshArena()
+{
+ pimpl_ = std::unique_ptr<IMeshArenaImpl>(new IMeshArenaImpl());
+}
+
+IMeshArena::~IMeshArena()
+{
+}
+
+void IMeshArena::reserve(int vert_num_hint, int face_num_hint)
+{
+ pimpl_->reserve(vert_num_hint, face_num_hint);
+}
+
+int IMeshArena::tot_allocated_verts() const
+{
+ return pimpl_->tot_allocated_verts();
+}
+
+int IMeshArena::tot_allocated_faces() const
+{
+ return pimpl_->tot_allocated_faces();
+}
+
+const Vert *IMeshArena::add_or_find_vert(const mpq3 &co, int orig)
+{
+ return pimpl_->add_or_find_vert(co, orig);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts,
+ int orig,
+ Span<int> edge_origs,
+ Span<bool> is_intersect)
+{
+ return pimpl_->add_face(verts, orig, edge_origs, is_intersect);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs)
+{
+ return pimpl_->add_face(verts, orig, edge_origs);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts, int orig)
+{
+ return pimpl_->add_face(verts, orig);
+}
+
+const Vert *IMeshArena::add_or_find_vert(const double3 &co, int orig)
+{
+ return pimpl_->add_or_find_vert(co, orig);
+}
+
+const Vert *IMeshArena::find_vert(const mpq3 &co) const
+{
+ return pimpl_->find_vert(co);
+}
+
+const Face *IMeshArena::find_face(Span<const Vert *> verts) const
+{
+ return pimpl_->find_face(verts);
+}
+
+void IMesh::set_faces(Span<Face *> faces)
+{
+ face_ = faces;
+}
+
+int IMesh::lookup_vert(const Vert *v) const
+{
+ BLI_assert(vert_populated_);
+ return vert_to_index_.lookup_default(v, NO_INDEX);
+}
+
+void IMesh::populate_vert()
+{
+ /* This is likely an overestimate, since verts are shared between
+ * faces. It is ok if estimate is over or even under. */
+ constexpr int ESTIMATE_VERTS_PER_FACE = 4;
+ int estimate_num_verts = ESTIMATE_VERTS_PER_FACE * face_.size();
+ populate_vert(estimate_num_verts);
+}
+
+void IMesh::populate_vert(int max_verts)
+{
+ if (vert_populated_) {
+ return;
+ }
+ vert_to_index_.reserve(max_verts);
+ int next_allocate_index = 0;
+ for (const Face *f : face_) {
+ for (const Vert *v : *f) {
+ if (v->id == 1) {
+ }
+ int index = vert_to_index_.lookup_default(v, NO_INDEX);
+ if (index == NO_INDEX) {
+ BLI_assert(next_allocate_index < UINT_MAX - 2);
+ vert_to_index_.add(v, next_allocate_index++);
+ }
+ }
+ }
+ int tot_v = next_allocate_index;
+ vert_ = Array<const Vert *>(tot_v);
+ for (auto item : vert_to_index_.items()) {
+ int index = item.value;
+ BLI_assert(index < tot_v);
+ vert_[index] = item.key;
+ }
+ /* Easier debugging (at least when there are no merged input verts)
+ * if output vert order is same as input, with new verts at the end.
+ * TODO: when all debugged, set fix_order = false. */
+ const bool fix_order = true;
+ if (fix_order) {
+ std::sort(vert_.begin(), vert_.end(), [](const Vert *a, const Vert *b) {
+ if (a->orig != NO_INDEX && b->orig != NO_INDEX) {
+ return a->orig < b->orig;
+ }
+ if (a->orig != NO_INDEX) {
+ return true;
+ }
+ if (b->orig != NO_INDEX) {
+ return false;
+ }
+ return a->id < b->id;
+ });
+ for (int i : vert_.index_range()) {
+ const Vert *v = vert_[i];
+ vert_to_index_.add_overwrite(v, i);
+ }
+ }
+ vert_populated_ = true;
+}
+
+void IMesh::erase_face_positions(int f_index, Span<bool> face_pos_erase, IMeshArena *arena)
+{
+ const Face *cur_f = this->face(f_index);
+ int cur_len = cur_f->size();
+ int num_to_erase = 0;
+ for (int i : cur_f->index_range()) {
+ if (face_pos_erase[i]) {
+ ++num_to_erase;
+ }
+ }
+ if (num_to_erase == 0) {
+ return;
+ }
+ int new_len = cur_len - num_to_erase;
+ if (new_len < 3) {
+ /* Invalid erase. Don't do anything. */
+ return;
+ }
+ Array<const Vert *> new_vert(new_len);
+ Array<int> new_edge_orig(new_len);
+ Array<bool> new_is_intersect(new_len);
+ int new_index = 0;
+ for (int i : cur_f->index_range()) {
+ if (!face_pos_erase[i]) {
+ new_vert[new_index] = (*cur_f)[i];
+ new_edge_orig[new_index] = cur_f->edge_orig[i];
+ new_is_intersect[new_index] = cur_f->is_intersect[i];
+ ++new_index;
+ }
+ }
+ BLI_assert(new_index == new_len);
+ this->face_[f_index] = arena->add_face(new_vert, cur_f->orig, new_edge_orig, new_is_intersect);
+}
+
+std::ostream &operator<<(std::ostream &os, const IMesh &mesh)
+{
+ if (mesh.has_verts()) {
+ os << "Verts:\n";
+ int i = 0;
+ for (const Vert *v : mesh.vertices()) {
+ os << i << ": " << v << "\n";
+ ++i;
+ }
+ }
+ os << "\nFaces:\n";
+ int i = 0;
+ for (const Face *f : mesh.faces()) {
+ os << i << ": " << f << "\n";
+ if (f->plane != nullptr) {
+ os << " plane=" << f->plane << " eorig=[";
+ for (Face::FacePos p = 0; p < f->size(); ++p) {
+ os << f->edge_orig[p] << " ";
+ }
+ os << "]\n";
+ }
+ ++i;
+ }
+ return os;
+}
+
+struct BoundingBox {
+ float3 min{FLT_MAX, FLT_MAX, FLT_MAX};
+ float3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
+
+ BoundingBox() = default;
+ BoundingBox(const float3 &min, const float3 &max) : min(min), max(max)
+ {
+ }
+ BoundingBox(const BoundingBox &other) : min(other.min), max(other.max)
+ {
+ }
+ BoundingBox(BoundingBox &&other) noexcept : min(std::move(other.min)), max(std::move(other.max))
+ {
+ }
+ ~BoundingBox() = default;
+ BoundingBox operator=(const BoundingBox &other)
+ {
+ if (this != &other) {
+ min = other.min;
+ max = other.max;
+ }
+ return *this;
+ }
+ BoundingBox operator=(BoundingBox &&other) noexcept
+ {
+ min = std::move(other.min);
+ max = std::move(other.max);
+ return *this;
+ }
+
+ void combine(const float3 &p)
+ {
+ min.x = min_ff(min.x, p.x);
+ min.y = min_ff(min.y, p.y);
+ min.z = min_ff(min.z, p.z);
+ max.x = max_ff(max.x, p.x);
+ max.y = max_ff(max.y, p.y);
+ max.z = max_ff(max.z, p.z);
+ }
+
+ void combine(const double3 &p)
+ {
+ min.x = min_ff(min.x, static_cast<float>(p.x));
+ min.y = min_ff(min.y, static_cast<float>(p.y));
+ min.z = min_ff(min.z, static_cast<float>(p.z));
+ max.x = max_ff(max.x, static_cast<float>(p.x));
+ max.y = max_ff(max.y, static_cast<float>(p.y));
+ max.z = max_ff(max.z, static_cast<float>(p.z));
+ }
+
+ void combine(const BoundingBox &bb)
+ {
+ min.x = min_ff(min.x, bb.min.x);
+ min.y = min_ff(min.y, bb.min.y);
+ min.z = min_ff(min.z, bb.min.z);
+ max.x = max_ff(max.x, bb.max.x);
+ max.y = max_ff(max.y, bb.max.y);
+ max.z = max_ff(max.z, bb.max.z);
+ }
+
+ void expand(float pad)
+ {
+ min.x -= pad;
+ min.y -= pad;
+ min.z -= pad;
+ max.x += pad;
+ max.y += pad;
+ max.z += pad;
+ }
+};
+
+/**
+ * Assume bounding boxes have been expanded by a sufficient epsilon on all sides
+ * so that the comparisons against the bb bounds are sufficient to guarantee that
+ * if an overlap or even touching could happen, this will return true.
+ */
+static bool bbs_might_intersect(const BoundingBox &bb_a, const BoundingBox &bb_b)
+{
+ return isect_aabb_aabb_v3(bb_a.min, bb_a.max, bb_b.min, bb_b.max);
+}
+
+/**
+ * Data and functions to calculate bounding boxes and pad them, in parallel.
+ * The bounding box calculation has the additional task of calculating the maximum
+ * absolute value of any coordinate in the mesh, which will be used to calculate
+ * the pad value.
+ */
+struct BBChunkData {
+ double max_abs_val = 0.0;
+};
+
+struct BBCalcData {
+ const IMesh &im;
+ Array<BoundingBox> *face_bounding_box;
+
+ BBCalcData(const IMesh &im, Array<BoundingBox> *fbb) : im(im), face_bounding_box(fbb){};
+};
+
+static void calc_face_bb_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict tls)
+{
+ BBCalcData *bbdata = static_cast<BBCalcData *>(userdata);
+ double max_abs = 0.0;
+ const Face &face = *bbdata->im.face(iter);
+ BoundingBox &bb = (*bbdata->face_bounding_box)[iter];
+ for (const Vert *v : face) {
+ bb.combine(v->co);
+ for (int i = 0; i < 3; ++i) {
+ max_abs = max_dd(max_abs, fabs(v->co[i]));
+ }
+ }
+ BBChunkData *chunk = static_cast<BBChunkData *>(tls->userdata_chunk);
+ chunk->max_abs_val = max_dd(max_abs, chunk->max_abs_val);
+}
+
+struct BBPadData {
+ Array<BoundingBox> *face_bounding_box;
+ double pad;
+
+ BBPadData(Array<BoundingBox> *fbb, double pad) : face_bounding_box(fbb), pad(pad){};
+};
+
+static void pad_face_bb_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ BBPadData *pad_data = static_cast<BBPadData *>(userdata);
+ (*pad_data->face_bounding_box)[iter].expand(pad_data->pad);
+}
+
+static void calc_face_bb_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ BBChunkData *bbchunk_join = static_cast<BBChunkData *>(chunk_join);
+ BBChunkData *bbchunk = static_cast<BBChunkData *>(chunk);
+ bbchunk_join->max_abs_val = max_dd(bbchunk_join->max_abs_val, bbchunk->max_abs_val);
+}
+
+/**
+ * We will expand the bounding boxes by an epsilon on all sides so that
+ * the "less than" tests in isect_aabb_aabb_v3 are sufficient to detect
+ * touching or overlap.
+ */
+static Array<BoundingBox> calc_face_bounding_boxes(const IMesh &m)
+{
+ int n = m.face_size();
+ Array<BoundingBox> ans(n);
+ TaskParallelSettings settings;
+ BBCalcData data(m, &ans);
+ BBChunkData chunk_data;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.userdata_chunk = &chunk_data;
+ settings.userdata_chunk_size = sizeof(chunk_data);
+ settings.func_reduce = calc_face_bb_reduce;
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, n, &data, calc_face_bb_range_func, &settings);
+ double max_abs_val = chunk_data.max_abs_val;
+ constexpr float pad_factor = 10.0f;
+ float pad = max_abs_val == 0.0f ? FLT_EPSILON : 2 * FLT_EPSILON * max_abs_val;
+ pad *= pad_factor; /* For extra safety. */
+ TaskParallelSettings pad_settings;
+ BLI_parallel_range_settings_defaults(&pad_settings);
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BBPadData pad_data(&ans, pad);
+ BLI_task_parallel_range(0, n, &pad_data, pad_face_bb_range_func, &pad_settings);
+ return ans;
+}
+
+/**
+ * A cluster of co-planar triangles, by index.
+ * A pair of triangles T0 and T1 is said to "non-trivially co-planar-intersect"
+ * if they are co-planar, intersect, and their intersection is not just existing
+ * elements (verts, edges) of both triangles.
+ * A co-planar cluster is said to be "nontrivial" if it has more than one triangle
+ * and every triangle in it non-trivially co-planar-intersects with at least one other
+ * triangle in the cluster.
+ */
+class CoplanarCluster {
+ Vector<int> tris_;
+ BoundingBox bb_;
+
+ public:
+ CoplanarCluster() = default;
+ CoplanarCluster(int t, const BoundingBox &bb)
+ {
+ this->add_tri(t, bb);
+ }
+ CoplanarCluster(const CoplanarCluster &other) : tris_(other.tris_), bb_(other.bb_)
+ {
+ }
+ CoplanarCluster(CoplanarCluster &&other) noexcept
+ : tris_(std::move(other.tris_)), bb_(std::move(other.bb_))
+ {
+ }
+ ~CoplanarCluster() = default;
+ CoplanarCluster &operator=(const CoplanarCluster &other)
+ {
+ if (this != &other) {
+ tris_ = other.tris_;
+ bb_ = other.bb_;
+ }
+ return *this;
+ }
+ CoplanarCluster &operator=(CoplanarCluster &&other) noexcept
+ {
+ tris_ = std::move(other.tris_);
+ bb_ = std::move(other.bb_);
+ return *this;
+ }
+
+ /* Assume that caller knows this will not be a duplicate. */
+ void add_tri(int t, const BoundingBox &bb)
+ {
+ tris_.append(t);
+ bb_ = bb;
+ }
+ int tot_tri() const
+ {
+ return tris_.size();
+ }
+ int tri(int index) const
+ {
+ return tris_[index];
+ }
+ const int *begin() const
+ {
+ return tris_.begin();
+ }
+ const int *end() const
+ {
+ return tris_.end();
+ }
+
+ const BoundingBox &bounding_box() const
+ {
+ return bb_;
+ }
+};
+
+/**
+ * Maintains indexed set of #CoplanarCluster, with the added ability
+ * to efficiently find the cluster index of any given triangle
+ * (the max triangle index needs to be given in the initializer).
+ * The #tri_cluster(t) function returns -1 if t is not part of any cluster.
+ */
+class CoplanarClusterInfo {
+ Vector<CoplanarCluster> clusters_;
+ Array<int> tri_cluster_;
+
+ public:
+ CoplanarClusterInfo() = default;
+ explicit CoplanarClusterInfo(int numtri) : tri_cluster_(Array<int>(numtri))
+ {
+ tri_cluster_.fill(-1);
+ }
+
+ int tri_cluster(int t) const
+ {
+ BLI_assert(t < tri_cluster_.size());
+ return tri_cluster_[t];
+ }
+
+ int add_cluster(CoplanarCluster cl)
+ {
+ int c_index = clusters_.append_and_get_index(cl);
+ for (int t : cl) {
+ BLI_assert(t < tri_cluster_.size());
+ tri_cluster_[t] = c_index;
+ }
+ return c_index;
+ }
+
+ int tot_cluster() const
+ {
+ return clusters_.size();
+ }
+
+ const CoplanarCluster *begin()
+ {
+ return clusters_.begin();
+ }
+
+ const CoplanarCluster *end()
+ {
+ return clusters_.end();
+ }
+
+ IndexRange index_range() const
+ {
+ return clusters_.index_range();
+ }
+
+ const CoplanarCluster &cluster(int index) const
+ {
+ BLI_assert(index < clusters_.size());
+ return clusters_[index];
+ }
+};
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarCluster &cl);
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarClusterInfo &clinfo);
+
+enum ITT_value_kind { INONE, IPOINT, ISEGMENT, ICOPLANAR };
+
+struct ITT_value {
+ enum ITT_value_kind kind;
+ mpq3 p1; /* Only relevant for IPOINT and ISEGMENT kind. */
+ mpq3 p2; /* Only relevant for ISEGMENT kind. */
+ int t_source; /* Index of the source triangle that intersected the target one. */
+
+ ITT_value() : kind(INONE), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k) : kind(k), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k, int tsrc) : kind(k), t_source(tsrc)
+ {
+ }
+ ITT_value(ITT_value_kind k, const mpq3 &p1) : kind(k), p1(p1), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k, const mpq3 &p1, const mpq3 &p2)
+ : kind(k), p1(p1), p2(p2), t_source(-1)
+ {
+ }
+ ITT_value(const ITT_value &other)
+ : kind(other.kind), p1(other.p1), p2(other.p2), t_source(other.t_source)
+ {
+ }
+ ITT_value(ITT_value &&other) noexcept
+ : kind(other.kind),
+ p1(std::move(other.p1)),
+ p2(std::move(other.p2)),
+ t_source(other.t_source)
+ {
+ }
+ ~ITT_value()
+ {
+ }
+ ITT_value &operator=(const ITT_value &other)
+ {
+ if (this != &other) {
+ kind = other.kind;
+ p1 = other.p1;
+ p2 = other.p2;
+ t_source = other.t_source;
+ }
+ return *this;
+ }
+ ITT_value &operator=(ITT_value &&other) noexcept
+ {
+ kind = other.kind;
+ p1 = std::move(other.p1);
+ p2 = std::move(other.p2);
+ t_source = other.t_source;
+ return *this;
+ }
+};
+
+static std::ostream &operator<<(std::ostream &os, const ITT_value &itt);
+
+/**
+ * Project a 3d vert to a 2d one by eliding proj_axis. This does not create
+ * degeneracies as long as the projection axis is one where the corresponding
+ * component of the originating plane normal is non-zero.
+ */
+static mpq2 project_3d_to_2d(const mpq3 &p3d, int proj_axis)
+{
+ mpq2 p2d;
+ switch (proj_axis) {
+ case (0): {
+ p2d[0] = p3d[1];
+ p2d[1] = p3d[2];
+ break;
+ }
+ case (1): {
+ p2d[0] = p3d[0];
+ p2d[1] = p3d[2];
+ break;
+ }
+ case (2): {
+ p2d[0] = p3d[0];
+ p2d[1] = p3d[1];
+ break;
+ }
+ default:
+ BLI_assert(false);
+ }
+ return p2d;
+}
+
+/**
+ Is a point in the interior of a 2d triangle or on one of its
+ * edges but not either endpoint of the edge?
+ * orient[pi][i] is the orientation test of the point pi against
+ * the side of the triangle starting at index i.
+ * Assume the triangle is non-degenerate and CCW-oriented.
+ * Then answer is true if p is left of or on all three of triangle a's edges,
+ * and strictly left of at least on of them.
+ */
+static bool non_trivially_2d_point_in_tri(const int orients[3][3], int pi)
+{
+ int p_left_01 = orients[pi][0];
+ int p_left_12 = orients[pi][1];
+ int p_left_20 = orients[pi][2];
+ return (p_left_01 >= 0 && p_left_12 >= 0 && p_left_20 >= 0 &&
+ (p_left_01 + p_left_12 + p_left_20) >= 2);
+}
+
+/**
+ * Given orients as defined in non_trivially_2d_intersect, do the triangles
+ * overlap in a "hex" pattern? That is, the overlap region is a hexagon, which
+ * one gets by having, each point of one triangle being strictly right-of one
+ * edge of the other and strictly left of the other two edges; and vice versa.
+ */
+static bool non_trivially_2d_hex_overlap(int orients[2][3][3])
+{
+ for (int ab = 0; ab < 2; ++ab) {
+ for (int i = 0; i < 3; ++i) {
+ bool ok = orients[ab][i][0] + orients[ab][i][1] + orients[ab][i][2] == 1 &&
+ orients[ab][i][0] != 0 && orients[ab][i][1] != 0 && orients[i][2] != 0;
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * Given orients as defined in non_trivially_2d_intersect, do the triangles
+ * have one shared edge in a "folded-over" configuration?
+ * As well as a shared edge, the third vertex of one triangle needs to be
+ * right-of one and left-of the other two edges of the other triangle.
+ */
+static bool non_trivially_2d_shared_edge_overlap(int orients[2][3][3],
+ const mpq2 *a[3],
+ const mpq2 *b[3])
+{
+ for (int i = 0; i < 3; ++i) {
+ int in = (i + 1) % 3;
+ int inn = (i + 2) % 3;
+ for (int j = 0; j < 3; ++j) {
+ int jn = (j + 1) % 3;
+ int jnn = (j + 2) % 3;
+ if (*a[i] == *b[j] && *a[in] == *b[jn]) {
+ /* Edge from a[i] is shared with edge from b[j]. */
+ /* See if a[inn] is right-of or on one of the other edges of b.
+ * If it is on, then it has to be right-of or left-of the shared edge,
+ * depending on which edge it is. */
+ if (orients[0][inn][jn] < 0 || orients[0][inn][jnn] < 0) {
+ return true;
+ }
+ if (orients[0][inn][jn] == 0 && orients[0][inn][j] == 1) {
+ return true;
+ }
+ if (orients[0][inn][jnn] == 0 && orients[0][inn][j] == -1) {
+ return true;
+ }
+ /* Similarly for `b[jnn]`. */
+ if (orients[1][jnn][in] < 0 || orients[1][jnn][inn] < 0) {
+ return true;
+ }
+ if (orients[1][jnn][in] == 0 && orients[1][jnn][i] == 1) {
+ return true;
+ }
+ if (orients[1][jnn][inn] == 0 && orients[1][jnn][i] == -1) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Are the triangles the same, perhaps with some permutation of vertices?
+ */
+static bool same_triangles(const mpq2 *a[3], const mpq2 *b[3])
+{
+ for (int i = 0; i < 3; ++i) {
+ if (a[0] == b[i] && a[1] == b[(i + 1) % 3] && a[2] == b[(i + 2) % 3]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Do 2d triangles (a[0], a[1], a[2]) and (b[0], b[1], b2[2]) intersect at more than just shared
+ * vertices or a shared edge? This is true if any point of one triangle is non-trivially inside the
+ * other. NO: that isn't quite sufficient: there is also the case where the verts are all mutually
+ * outside the other's triangle, but there is a hexagonal overlap region where they overlap.
+ */
+static bool non_trivially_2d_intersect(const mpq2 *a[3], const mpq2 *b[3])
+{
+ /* TODO: Could experiment with trying bounding box tests before these.
+ * TODO: Find a less expensive way than 18 orient tests to do this. */
+
+ /* `orients[0][ai][bi]` is orient of point `a[ai]` compared to segment starting at `b[bi]`.
+ * `orients[1][bi][ai]` is orient of point `b[bi]` compared to segment starting at `a[ai]`. */
+ int orients[2][3][3];
+ for (int ab = 0; ab < 2; ++ab) {
+ for (int ai = 0; ai < 3; ++ai) {
+ for (int bi = 0; bi < 3; ++bi) {
+ if (ab == 0) {
+ orients[0][ai][bi] = orient2d(*b[bi], *b[(bi + 1) % 3], *a[ai]);
+ }
+ else {
+ orients[1][bi][ai] = orient2d(*a[ai], *a[(ai + 1) % 3], *b[bi]);
+ }
+ }
+ }
+ }
+ return non_trivially_2d_point_in_tri(orients[0], 0) ||
+ non_trivially_2d_point_in_tri(orients[0], 1) ||
+ non_trivially_2d_point_in_tri(orients[0], 2) ||
+ non_trivially_2d_point_in_tri(orients[1], 0) ||
+ non_trivially_2d_point_in_tri(orients[1], 1) ||
+ non_trivially_2d_point_in_tri(orients[1], 2) || non_trivially_2d_hex_overlap(orients) ||
+ non_trivially_2d_shared_edge_overlap(orients, a, b) || same_triangles(a, b);
+ return true;
+}
+
+/**
+ * Does triangle t in tm non-trivially non-co-planar intersect any triangle
+ * in `CoplanarCluster cl`? Assume t is known to be in the same plane as all
+ * the triangles in cl, and that proj_axis is a good axis to project down
+ * to solve this problem in 2d.
+ */
+static bool non_trivially_coplanar_intersects(const IMesh &tm,
+ int t,
+ const CoplanarCluster &cl,
+ int proj_axis)
+{
+ const Face &tri = *tm.face(t);
+ mpq2 v0 = project_3d_to_2d(tri[0]->co_exact, proj_axis);
+ mpq2 v1 = project_3d_to_2d(tri[1]->co_exact, proj_axis);
+ mpq2 v2 = project_3d_to_2d(tri[2]->co_exact, proj_axis);
+ if (orient2d(v0, v1, v2) != 1) {
+ mpq2 tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+ }
+ for (const int cl_t : cl) {
+ const Face &cl_tri = *tm.face(cl_t);
+ mpq2 ctv0 = project_3d_to_2d(cl_tri[0]->co_exact, proj_axis);
+ mpq2 ctv1 = project_3d_to_2d(cl_tri[1]->co_exact, proj_axis);
+ mpq2 ctv2 = project_3d_to_2d(cl_tri[2]->co_exact, proj_axis);
+ if (orient2d(ctv0, ctv1, ctv2) != 1) {
+ mpq2 tmp = ctv1;
+ ctv1 = ctv2;
+ ctv2 = tmp;
+ }
+ const mpq2 *v[] = {&v0, &v1, &v2};
+ const mpq2 *ctv[] = {&ctv0, &ctv1, &ctv2};
+ if (non_trivially_2d_intersect(v, ctv)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Keeping this code for a while, but for now, almost all
+ * trivial intersects are found before calling intersect_tri_tri now.
+ */
+# if 0
+/**
+ * Do tri1 and tri2 intersect at all, and if so, is the intersection
+ * something other than a common vertex or a common edge?
+ * The \a itt value is the result of calling intersect_tri_tri on tri1, tri2.
+ */
+static bool non_trivial_intersect(const ITT_value &itt, const Face * tri1, const Face * tri2)
+{
+ if (itt.kind == INONE) {
+ return false;
+ }
+ const Face * tris[2] = {tri1, tri2};
+ if (itt.kind == IPOINT) {
+ bool has_p_as_vert[2] {false, false};
+ for (int i = 0; i < 2; ++i) {
+ for (const Vert * v : *tris[i]) {
+ if (itt.p1 == v->co_exact) {
+ has_p_as_vert[i] = true;
+ break;
+ }
+ }
+ }
+ return !(has_p_as_vert[0] && has_p_as_vert[1]);
+ }
+ if (itt.kind == ISEGMENT) {
+ bool has_seg_as_edge[2] = {false, false};
+ for (int i = 0; i < 2; ++i) {
+ const Face &t = *tris[i];
+ for (int pos : t.index_range()) {
+ int nextpos = t.next_pos(pos);
+ if ((itt.p1 == t[pos]->co_exact && itt.p2 == t[nextpos]->co_exact) ||
+ (itt.p2 == t[pos]->co_exact && itt.p1 == t[nextpos]->co_exact)) {
+ has_seg_as_edge[i] = true;
+ break;
+ }
+ }
+ }
+ return !(has_seg_as_edge[0] && has_seg_as_edge[1]);
+ }
+ BLI_assert(itt.kind == ICOPLANAR);
+ /* TODO: refactor this common code with code above. */
+ int proj_axis = mpq3::dominant_axis(tri1->plane.norm_exact);
+ mpq2 tri_2d[2][3];
+ for (int i = 0; i < 2; ++i) {
+ mpq2 v0 = project_3d_to_2d((*tris[i])[0]->co_exact, proj_axis);
+ mpq2 v1 = project_3d_to_2d((*tris[i])[1]->co_exact, proj_axis);
+ mpq2 v2 = project_3d_to_2d((*tris[i])[2]->co_exact, proj_axis);
+ if (mpq2::orient2d(v0, v1, v2) != 1) {
+ mpq2 tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+ }
+ tri_2d[i][0] = v0;
+ tri_2d[i][1] = v1;
+ tri_2d[i][2] = v2;
+ }
+ const mpq2 *va[] = {&tri_2d[0][0], &tri_2d[0][1], &tri_2d[0][2]};
+ const mpq2 *vb[] = {&tri_2d[1][0], &tri_2d[1][1], &tri_2d[1][2]};
+ return non_trivially_2d_intersect(va, vb);
+}
+# endif
+
+/**
+ * The sup and index functions are defined in the paper:
+ * EXACT GEOMETRIC COMPUTATION USING CASCADING, by
+ * Burnikel, Funke, and Seel. They are used to find absolute
+ * bounds on the error due to doing a calculation in double
+ * instead of exactly. For calculations involving only +, -, and *,
+ * the supremum is the same function except using absolute values
+ * on inputs and using + instead of -.
+ * The index function follows these rules:
+ * index(x op y) = 1 + max(index(x), index(y)) for op + or -
+ * index(x * y) = 1 + index(x) + index(y)
+ * index(x) = 0 if input x can be represented exactly as a double
+ * index(x) = 1 otherwise.
+ *
+ * With these rules in place, we know an absolute error bound:
+ *
+ * |E_exact - E| <= supremum(E) * index(E) * DBL_EPSILON
+ *
+ * where E_exact is what would have been the exact value of the
+ * expression and E is the one calculated with doubles.
+ *
+ * So the sign of E is the same as the sign of E_exact if
+ * |E| > supremum(E) * index(E) * DBL_EPSILON
+ *
+ * Note: a possible speedup would be to have a simple function
+ * that calculates the error bound if one knows that all values
+ * are less than some global maximum - most of the function would
+ * be calculated ahead of time. The global max could be passed
+ * from above.
+ */
+static double supremum_dot_cross(const double3 &a, const double3 &b)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ double3 c;
+ /* This is dot(cross(a, b), cross(a,b)) but using absolute values for a and b
+ * and always using + when operation is + or -. */
+ c[0] = abs_a[1] * abs_b[2] + abs_a[2] * abs_b[1];
+ c[1] = abs_a[2] * abs_b[0] + abs_a[0] * abs_b[2];
+ c[2] = abs_a[0] * abs_b[1] + abs_a[1] * abs_b[0];
+ return double3::dot(c, c);
+}
+
+/**
+ * Used with supremum to get error bound. See Burnikel et al paper.
+ * index_plane_coord is the index of a plane coordinate calculated
+ * for a triangle using the usual formula, assuming the input
+ * coordinates have index 1.
+ * index_cross is the index of each coordinate of the cross product.
+ * It is actually 2 + 2 * (max index of input coords).
+ * index_dot_cross is the index of the dot product of two cross products.
+ * It is actually 7 + 4 * (max index of input coords)
+ */
+constexpr int index_dot_cross = 11;
+
+static double supremum_dot(const double3 &a, const double3 &b)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ return double3::dot(abs_a, abs_b);
+}
+
+/* Actually index_dot = 3 + 2 * (max index of input coordinates). */
+/* The index of dot when inputs are plane_coords with index 1 is much higher.
+ * Plane coords have index 6.
+ */
+constexpr int index_dot_plane_coords = 15;
+
+static double supremum_orient3d(const double3 &a,
+ const double3 &b,
+ const double3 &c,
+ const double3 &d)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ double3 abs_c = double3::abs(c);
+ double3 abs_d = double3::abs(d);
+ double adx = abs_a[0] + abs_d[0];
+ double bdx = abs_b[0] + abs_d[0];
+ double cdx = abs_c[0] + abs_d[0];
+ double ady = abs_a[1] + abs_d[1];
+ double bdy = abs_b[1] + abs_d[1];
+ double cdy = abs_c[1] + abs_d[1];
+ double adz = abs_a[2] + abs_d[2];
+ double bdz = abs_b[2] + abs_d[2];
+ double cdz = abs_c[2] + abs_d[2];
+
+ double bdxcdy = bdx * cdy;
+ double cdxbdy = cdx * bdy;
+
+ double cdxady = cdx * ady;
+ double adxcdy = adx * cdy;
+
+ double adxbdy = adx * bdy;
+ double bdxady = bdx * ady;
+
+ double det = adz * (bdxcdy + cdxbdy) + bdz * (cdxady + adxcdy) + cdz * (adxbdy + bdxady);
+ return det;
+}
+
+/** Actually index_orient3d = 10 + 4 * (max degree of input coordinates) */
+constexpr int index_orient3d = 14;
+
+/**
+ * Return the approximate orient3d of the four double3's, with
+ * the guarantee that if the value is -1 or 1 then the underlying
+ * mpq3 test would also have returned that value.
+ * When the return value is 0, we are not sure of the sign.
+ */
+static int filter_orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
+{
+ double o3dfast = orient3d_fast(a, b, c, d);
+ if (o3dfast == 0.0) {
+ return 0;
+ }
+ double err_bound = supremum_orient3d(a, b, c, d) * index_orient3d * DBL_EPSILON;
+ if (fabs(o3dfast) > err_bound) {
+ return o3dfast > 0.0 ? 1 : -1;
+ }
+ return 0;
+}
+
+/**
+ * Return the approximate orient3d of the triangle plane points and v, with
+ * the guarantee that if the value is -1 or 1 then the underlying
+ * mpq3 test would also have returned that value.
+ * When the return value is 0, we are not sure of the sign.
+ */
+static int filter_tri_plane_vert_orient3d(const Face &tri, const Vert *v)
+{
+ return filter_orient3d(tri[0]->co, tri[1]->co, tri[2]->co, v->co);
+}
+
+/**
+ * Are vectors a and b parallel or nearly parallel?
+ * This routine should only return false if we are certain
+ * that they are not parallel, taking into account the
+ * possible numeric errors and input value approximation.
+ */
+static bool near_parallel_vecs(const double3 &a, const double3 &b)
+{
+ double3 cr = double3::cross_high_precision(a, b);
+ double cr_len_sq = cr.length_squared();
+ if (cr_len_sq == 0.0) {
+ return true;
+ }
+ double err_bound = supremum_dot_cross(a, b) * index_dot_cross * DBL_EPSILON;
+ if (cr_len_sq > err_bound) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Return true if we are sure that dot(a,b) > 0, taking into
+ * account the error bounds due to numeric errors and input value
+ * approximation.
+ */
+static bool dot_must_be_positive(const double3 &a, const double3 &b)
+{
+ double d = double3::dot(a, b);
+ if (d <= 0.0) {
+ return false;
+ }
+ double err_bound = supremum_dot(a, b) * index_dot_plane_coords * DBL_EPSILON;
+ if (d > err_bound) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Return the approximate side of point p on a plane with normal plane_no and point plane_p.
+ * The answer will be 1 if p is definitely above the plane, -1 if it is definitely below.
+ * If the answer is 0, we are unsure about which side of the plane (or if it is on the plane).
+ * In exact arithmetic, the answer is just `sgn(dot(p - plane_p, plane_no))`.
+ *
+ * The plane_no input is constructed, so has a higher index.
+ */
+constexpr int index_plane_side = 3 + 2 * index_dot_plane_coords;
+
+static int filter_plane_side(const double3 &p,
+ const double3 &plane_p,
+ const double3 &plane_no,
+ const double3 &abs_p,
+ const double3 &abs_plane_p,
+ const double3 &abs_plane_no)
+{
+ double d = double3::dot(p - plane_p, plane_no);
+ if (d == 0.0) {
+ return 0;
+ }
+ double supremum = double3::dot(abs_p + abs_plane_p, abs_plane_no);
+ double err_bound = supremum * index_plane_side * DBL_EPSILON;
+ if (d > err_bound) {
+ return d > 0 ? 1 : -1;
+ }
+ return 0;
+}
+
+/**
+ * A fast, non-exhaustive test for non_trivial intersection.
+ * If this returns false then we are sure that tri1 and tri2
+ * do not intersect. If it returns true, they may or may not
+ * non-trivially intersect.
+ * We assume that bounding box overlap tests have already been
+ * done, so don't repeat those here. This routine is checking
+ * for the very common cases (when doing mesh self-intersect)
+ * where triangles share an edge or a vertex, but don't
+ * otherwise intersect.
+ */
+static bool may_non_trivially_intersect(Face *t1, Face *t2)
+{
+ Face &tri1 = *t1;
+ Face &tri2 = *t2;
+ BLI_assert(t1->plane_populated() && t2->plane_populated());
+ Face::FacePos share1_pos[3];
+ Face::FacePos share2_pos[3];
+ int n_shared = 0;
+ for (Face::FacePos p1 = 0; p1 < 3; ++p1) {
+ const Vert *v1 = tri1[p1];
+ for (Face::FacePos p2 = 0; p2 < 3; ++p2) {
+ const Vert *v2 = tri2[p2];
+ if (v1 == v2) {
+ share1_pos[n_shared] = p1;
+ share2_pos[n_shared] = p2;
+ ++n_shared;
+ }
+ }
+ }
+ if (n_shared == 2) {
+ /* t1 and t2 share an entire edge.
+ * If their normals are not parallel, they cannot non-trivially intersect. */
+ if (!near_parallel_vecs(tri1.plane->norm, tri2.plane->norm)) {
+ return false;
+ }
+ /* The normals are parallel or nearly parallel.
+ * If the normals are in the same direction and the edges have opposite
+ * directions in the two triangles, they cannot non-trivially intersect. */
+ bool erev1 = tri1.prev_pos(share1_pos[0]) == share1_pos[1];
+ bool erev2 = tri2.prev_pos(share2_pos[0]) == share2_pos[1];
+ if (erev1 != erev2) {
+ if (dot_must_be_positive(tri1.plane->norm, tri2.plane->norm)) {
+ return false;
+ }
+ }
+ }
+ else if (n_shared == 1) {
+ /* t1 and t2 share a vertex, but not an entire edge.
+ * If the two non-shared verts of t2 are both on the same
+ * side of tri1's plane, then they cannot non-trivially intersect.
+ * (There are some other cases that could be caught here but
+ * they are more expensive to check). */
+ Face::FacePos p = share2_pos[0];
+ const Vert *v2a = p == 0 ? tri2[1] : tri2[0];
+ const Vert *v2b = (p == 0 || p == 1) ? tri2[2] : tri2[1];
+ int o1 = filter_tri_plane_vert_orient3d(tri1, v2a);
+ int o2 = filter_tri_plane_vert_orient3d(tri1, v2b);
+ if (o1 == o2 && o1 != 0) {
+ return false;
+ }
+ p = share1_pos[0];
+ const Vert *v1a = p == 0 ? tri1[1] : tri1[0];
+ const Vert *v1b = (p == 0 || p == 1) ? tri1[2] : tri1[1];
+ o1 = filter_tri_plane_vert_orient3d(tri2, v1a);
+ o2 = filter_tri_plane_vert_orient3d(tri2, v1b);
+ if (o1 == o2 && o1 != 0) {
+ return false;
+ }
+ }
+ /* We weren't able to prove that any intersection is trivial. */
+ return true;
+}
+
+/*
+ * interesect_tri_tri and helper functions.
+ * This code uses the algorithm of Guigue and Devillers, as described
+ * in "Faster Triangle-Triangle Intersection Tests".
+ * Adapted from github code by Eric Haines:
+ * github.com/erich666/jgt-code/tree/master/Volume_08/Number_1/Guigue2003
+ */
+
+/**
+ * Return the point on ab where the plane with normal n containing point c intersects it.
+ * Assumes ab is not perpendicular to n.
+ * This works because the ratio of the projections of ab and ac onto n is the same as
+ * the ratio along the line ab of the intersection point to the whole of ab.
+ */
+static inline mpq3 tti_interp(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &n)
+{
+ mpq3 ab = a - b;
+ mpq_class den = mpq3::dot(ab, n);
+ BLI_assert(den != 0);
+ mpq_class alpha = mpq3::dot(a - c, n) / den;
+ return a - alpha * ab;
+}
+
+/**
+ * Return +1, 0, -1 as a + ad is above, on, or below the oriented plane containing a, b, c in CCW
+ * order. This is the same as -oriented(a, b, c, a + ad), but uses fewer arithmetic operations.
+ * TODO: change arguments to `const Vert *` and use floating filters.
+ */
+static inline int tti_above(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &ad)
+{
+ mpq3 n = mpq3::cross(b - a, c - a);
+ return sgn(mpq3::dot(ad, n));
+}
+
+/**
+ * Given that triangles (p1, q1, r1) and (p2, q2, r2) are in canonical order,
+ * use the classification chart in the Guigue and Devillers paper to find out
+ * how the intervals [i,j] and [k,l] overlap, where [i,j] is where p1r1 and p1q1
+ * intersect the plane-plane intersection line, L, and [k,l] is where p2q2 and p2r2
+ * intersect L. By the canonicalization, those segments intersect L exactly once.
+ * Canonicalization has made it so that for p1, q1, r1, either:
+ * (a)) p1 is off the second triangle's plane and both q1 and r1 are either
+ * on the plane or on the other side of it from p1; or
+ * (b) p1 is on the plane both q1 and r1 are on the same side
+ * of the plane and at least one of q1 and r1 are off the plane.
+ * Similarly for p2, q2, r2 with respect to the first triangle's plane.
+ */
+static ITT_value itt_canon2(const mpq3 &p1,
+ const mpq3 &q1,
+ const mpq3 &r1,
+ const mpq3 &p2,
+ const mpq3 &q2,
+ const mpq3 &r2,
+ const mpq3 &n1,
+ const mpq3 &n2)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\ntri_tri_intersect_canon:\n";
+ std::cout << "p1=" << p1 << " q1=" << q1 << " r1=" << r1 << "\n";
+ std::cout << "p2=" << p2 << " q2=" << q2 << " r2=" << r2 << "\n";
+ std::cout << "n1=" << n1 << " n2=" << n2 << "\n";
+ std::cout << "approximate values:\n";
+ std::cout << "p1=(" << p1[0].get_d() << "," << p1[1].get_d() << "," << p1[2].get_d() << ")\n";
+ std::cout << "q1=(" << q1[0].get_d() << "," << q1[1].get_d() << "," << q1[2].get_d() << ")\n";
+ std::cout << "r1=(" << r1[0].get_d() << "," << r1[1].get_d() << "," << r1[2].get_d() << ")\n";
+ std::cout << "p2=(" << p2[0].get_d() << "," << p2[1].get_d() << "," << p2[2].get_d() << ")\n";
+ std::cout << "q2=(" << q2[0].get_d() << "," << q2[1].get_d() << "," << q2[2].get_d() << ")\n";
+ std::cout << "r2=(" << r2[0].get_d() << "," << r2[1].get_d() << "," << r2[2].get_d() << ")\n";
+ std::cout << "n1=(" << n1[0].get_d() << "," << n1[1].get_d() << "," << n1[2].get_d() << ")\n";
+ std::cout << "n2=(" << n2[0].get_d() << "," << n2[1].get_d() << "," << n2[2].get_d() << ")\n";
+ }
+ mpq3 p1p2 = p2 - p1;
+ mpq3 intersect_1;
+ mpq3 intersect_2;
+ bool no_overlap = false;
+ /* Top test in classification tree. */
+ if (tti_above(p1, q1, r2, p1p2) > 0) {
+ /* Middle right test in classification tree. */
+ if (tti_above(p1, r1, r2, p1p2) <= 0) {
+ /* Bottom right test in classification tree. */
+ if (tti_above(p1, r1, q2, p1p2) > 0) {
+ /* Overlap is [k [i l] j]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [k [i l] j]\n";
+ }
+ /* i is intersect with p1r1. l is intersect with p2r2. */
+ intersect_1 = tti_interp(p1, r1, p2, n2);
+ intersect_2 = tti_interp(p2, r2, p1, n1);
+ }
+ else {
+ /* Overlap is [i [k l] j]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [i [k l] j]\n";
+ }
+ /* k is intersect with p2q2. l is intersect is p2r2. */
+ intersect_1 = tti_interp(p2, q2, p1, n1);
+ intersect_2 = tti_interp(p2, r2, p1, n1);
+ }
+ }
+ else {
+ /* No overlap: [k l] [i j]. */
+ if (dbg_level > 0) {
+ std::cout << "no overlap: [k l] [i j]\n";
+ }
+ no_overlap = true;
+ }
+ }
+ else {
+ /* Middle left test in classification tree. */
+ if (tti_above(p1, q1, q2, p1p2) < 0) {
+ /* No overlap: [i j] [k l]. */
+ if (dbg_level > 0) {
+ std::cout << "no overlap: [i j] [k l]\n";
+ }
+ no_overlap = true;
+ }
+ else {
+ /* Bottom left test in classification tree. */
+ if (tti_above(p1, r1, q2, p1p2) >= 0) {
+ /* Overlap is [k [i j] l]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [k [i j] l]\n";
+ }
+ /* i is intersect with p1r1. j is intersect with p1q1. */
+ intersect_1 = tti_interp(p1, r1, p2, n2);
+ intersect_2 = tti_interp(p1, q1, p2, n2);
+ }
+ else {
+ /* Overlap is [i [k j] l]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [i [k j] l]\n";
+ }
+ /* k is intersect with p2q2. j is intersect with p1q1. */
+ intersect_1 = tti_interp(p2, q2, p1, n1);
+ intersect_2 = tti_interp(p1, q1, p2, n2);
+ }
+ }
+ }
+ if (no_overlap) {
+ return ITT_value(INONE);
+ }
+ if (intersect_1 == intersect_2) {
+ if (dbg_level > 0) {
+ std::cout << "single intersect: " << intersect_1 << "\n";
+ }
+ return ITT_value(IPOINT, intersect_1);
+ }
+ if (dbg_level > 0) {
+ std::cout << "intersect segment: " << intersect_1 << ", " << intersect_2 << "\n";
+ }
+ return ITT_value(ISEGMENT, intersect_1, intersect_2);
+}
+
+/* Helper function for intersect_tri_tri. Args have been canonicalized for triangle 1. */
+
+static ITT_value itt_canon1(const mpq3 &p1,
+ const mpq3 &q1,
+ const mpq3 &r1,
+ const mpq3 &p2,
+ const mpq3 &q2,
+ const mpq3 &r2,
+ const mpq3 &n1,
+ const mpq3 &n2,
+ int sp2,
+ int sq2,
+ int sr2)
+{
+ constexpr int dbg_level = 0;
+ if (sp2 > 0) {
+ if (sq2 > 0) {
+ return itt_canon2(p1, r1, q1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 > 0) {
+ return itt_canon2(p1, r1, q1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, p2, q2, r2, n1, n2);
+ }
+ if (sp2 < 0) {
+ if (sq2 < 0) {
+ return itt_canon2(p1, q1, r1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 < 0) {
+ return itt_canon2(p1, q1, r1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, r1, q1, p2, q2, r2, n1, n2);
+ }
+ if (sq2 < 0) {
+ if (sr2 >= 0) {
+ return itt_canon2(p1, r1, q1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, p2, q2, r2, n1, n2);
+ }
+ if (sq2 > 0) {
+ if (sr2 > 0) {
+ return itt_canon2(p1, r1, q1, p2, q2, r2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, q2, r2, p2, n1, n2);
+ }
+ if (sr2 > 0) {
+ return itt_canon2(p1, q1, r1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 < 0) {
+ return itt_canon2(p1, r1, q1, r2, p2, q2, n1, n2);
+ }
+ if (dbg_level > 0) {
+ std::cout << "triangles are co-planar\n";
+ }
+ return ITT_value(ICOPLANAR);
+}
+
+static ITT_value intersect_tri_tri(const IMesh &tm, int t1, int t2)
+{
+ constexpr int dbg_level = 0;
+# ifdef PERFDEBUG
+ incperfcount(1); /* Intersect_tri_tri calls. */
+# endif
+ const Face &tri1 = *tm.face(t1);
+ const Face &tri2 = *tm.face(t2);
+ BLI_assert(tri1.plane_populated() && tri2.plane_populated());
+ const Vert *vp1 = tri1[0];
+ const Vert *vq1 = tri1[1];
+ const Vert *vr1 = tri1[2];
+ const Vert *vp2 = tri2[0];
+ const Vert *vq2 = tri2[1];
+ const Vert *vr2 = tri2[2];
+ if (dbg_level > 0) {
+ std::cout << "\nINTERSECT_TRI_TRI t1=" << t1 << ", t2=" << t2 << "\n";
+ std::cout << " p1 = " << vp1 << "\n";
+ std::cout << " q1 = " << vq1 << "\n";
+ std::cout << " r1 = " << vr1 << "\n";
+ std::cout << " p2 = " << vp2 << "\n";
+ std::cout << " q2 = " << vq2 << "\n";
+ std::cout << " r2 = " << vr2 << "\n";
+ }
+
+ /* Get signs of t1's vertices' distances to plane of t2 and vice versa. */
+
+ /* Try first getting signs with double arithmetic, with error bounds.
+ * If the signs calculated in this section are not 0, they are the same
+ * as what they would be using exact arithmetic. */
+ const double3 &d_p1 = vp1->co;
+ const double3 &d_q1 = vq1->co;
+ const double3 &d_r1 = vr1->co;
+ const double3 &d_p2 = vp2->co;
+ const double3 &d_q2 = vq2->co;
+ const double3 &d_r2 = vr2->co;
+ const double3 &d_n2 = tri2.plane->norm;
+
+ const double3 &abs_d_p1 = double3::abs(d_p1);
+ const double3 &abs_d_q1 = double3::abs(d_q1);
+ const double3 &abs_d_r1 = double3::abs(d_r1);
+ const double3 &abs_d_r2 = double3::abs(d_r2);
+ const double3 &abs_d_n2 = double3::abs(d_n2);
+
+ int sp1 = filter_plane_side(d_p1, d_r2, d_n2, abs_d_p1, abs_d_r2, abs_d_n2);
+ int sq1 = filter_plane_side(d_q1, d_r2, d_n2, abs_d_q1, abs_d_r2, abs_d_n2);
+ int sr1 = filter_plane_side(d_r1, d_r2, d_n2, abs_d_r1, abs_d_r2, abs_d_n2);
+ if ((sp1 > 0 && sq1 > 0 && sr1 > 0) || (sp1 < 0 && sq1 < 0 && sr1 < 0)) {
+# ifdef PERFDEBUG
+ incperfcount(2); /* Tri tri intersects decided by filter plane tests. */
+# endif
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t1's verts above or below t2\n";
+ }
+ return ITT_value(INONE);
+ }
+
+ const double3 &d_n1 = tri1.plane->norm;
+ const double3 &abs_d_p2 = double3::abs(d_p2);
+ const double3 &abs_d_q2 = double3::abs(d_q2);
+ const double3 &abs_d_n1 = double3::abs(d_n1);
+
+ int sp2 = filter_plane_side(d_p2, d_r1, d_n1, abs_d_p2, abs_d_r1, abs_d_n1);
+ int sq2 = filter_plane_side(d_q2, d_r1, d_n1, abs_d_q2, abs_d_r1, abs_d_n1);
+ int sr2 = filter_plane_side(d_r2, d_r1, d_n1, abs_d_r2, abs_d_r1, abs_d_n1);
+ if ((sp2 > 0 && sq2 > 0 && sr2 > 0) || (sp2 < 0 && sq2 < 0 && sr2 < 0)) {
+# ifdef PERFDEBUG
+ incperfcount(2); /* Tri tri intersects decided by filter plane tests. */
+# endif
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t2's verts above or below t1\n";
+ }
+ return ITT_value(INONE);
+ }
+
+ const mpq3 &p1 = vp1->co_exact;
+ const mpq3 &q1 = vq1->co_exact;
+ const mpq3 &r1 = vr1->co_exact;
+ const mpq3 &p2 = vp2->co_exact;
+ const mpq3 &q2 = vq2->co_exact;
+ const mpq3 &r2 = vr2->co_exact;
+
+ const mpq3 &n2 = tri2.plane->norm_exact;
+ if (sp1 == 0) {
+ sp1 = sgn(mpq3::dot(p1 - r2, n2));
+ }
+ if (sq1 == 0) {
+ sq1 = sgn(mpq3::dot(q1 - r2, n2));
+ }
+ if (sr1 == 0) {
+ sr1 = sgn(mpq3::dot(r1 - r2, n2));
+ }
+
+ if (dbg_level > 1) {
+ std::cout << " sp1=" << sp1 << " sq1=" << sq1 << " sr1=" << sr1 << "\n";
+ }
+
+ if ((sp1 * sq1 > 0) && (sp1 * sr1 > 0)) {
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t1's verts above or below t2 (exact)\n";
+ }
+# ifdef PERFDEBUG
+ incperfcount(3); /* Tri tri intersects decided by exact plane tests. */
+# endif
+ return ITT_value(INONE);
+ }
+
+ /* Repeat for signs of t2's vertices with respect to plane of t1. */
+ const mpq3 &n1 = tri1.plane->norm_exact;
+ if (sp2 == 0) {
+ sp2 = sgn(mpq3::dot(p2 - r1, n1));
+ }
+ if (sq2 == 0) {
+ sq2 = sgn(mpq3::dot(q2 - r1, n1));
+ }
+ if (sr2 == 0) {
+ sr2 = sgn(mpq3::dot(r2 - r1, n1));
+ }
+
+ if (dbg_level > 1) {
+ std::cout << " sp2=" << sp2 << " sq2=" << sq2 << " sr2=" << sr2 << "\n";
+ }
+
+ if ((sp2 * sq2 > 0) && (sp2 * sr2 > 0)) {
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t2's verts above or below t1 (exact)\n";
+ }
+# ifdef PERFDEBUG
+ incperfcount(3); /* Tri tri intersects decided by exact plane tests. */
+# endif
+ return ITT_value(INONE);
+ }
+
+ /* Do rest of the work with vertices in a canonical order, where p1 is on
+ * positive side of plane and q1, r1 are not, or p1 is on the plane and
+ * q1 and r1 are off the plane on the same side. */
+ ITT_value ans;
+ if (sp1 > 0) {
+ if (sq1 > 0) {
+ ans = itt_canon1(r1, p1, q1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else if (sr1 > 0) {
+ ans = itt_canon1(q1, r1, p1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else if (sp1 < 0) {
+ if (sq1 < 0) {
+ ans = itt_canon1(r1, p1, q1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else if (sr1 < 0) {
+ ans = itt_canon1(q1, r1, p1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ }
+ else {
+ if (sq1 < 0) {
+ if (sr1 >= 0) {
+ ans = itt_canon1(q1, r1, p1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else if (sq1 > 0) {
+ if (sr1 > 0) {
+ ans = itt_canon1(p1, q1, r1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(q1, r1, p1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else {
+ if (sr1 > 0) {
+ ans = itt_canon1(r1, p1, q1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else if (sr1 < 0) {
+ ans = itt_canon1(r1, p1, q1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "triangles are co-planar\n";
+ }
+ ans = ITT_value(ICOPLANAR);
+ }
+ }
+ }
+ if (ans.kind == ICOPLANAR) {
+ ans.t_source = t2;
+ }
+
+# ifdef PERFDEBUG
+ if (ans.kind != INONE) {
+ incperfcount(4);
+ }
+# endif
+ return ans;
+}
+
+struct CDT_data {
+ const Plane *t_plane;
+ Vector<mpq2> vert;
+ Vector<std::pair<int, int>> edge;
+ Vector<Vector<int>> face;
+ /** Parallels face, gives id from input #IMesh of input face. */
+ Vector<int> input_face;
+ /** Parallels face, says if input face orientation is opposite. */
+ Vector<bool> is_reversed;
+ /** Result of running CDT on input with (vert, edge, face). */
+ CDT_result<mpq_class> cdt_out;
+ int proj_axis;
+};
+
+/**
+ * We could de-duplicate verts here, but CDT routine will do that anyway.
+ */
+static int prepare_need_vert(CDT_data &cd, const mpq3 &p3d)
+{
+ mpq2 p2d = project_3d_to_2d(p3d, cd.proj_axis);
+ int v = cd.vert.append_and_get_index(p2d);
+ return v;
+}
+
+/**
+ * To un-project a 2d vert that was projected along cd.proj_axis, we copy the coordinates
+ * from the two axes not involved in the projection, and use the plane equation of the
+ * originating 3d plane, cd.t_plane, to derive the coordinate of the projected axis.
+ * The plane equation says a point p is on the plane if dot(p, plane.n()) + plane.d() == 0.
+ * Assume that the projection axis is such that plane.n()[proj_axis] != 0.
+ */
+static mpq3 unproject_cdt_vert(const CDT_data &cd, const mpq2 &p2d)
+{
+ mpq3 p3d;
+ BLI_assert(cd.t_plane->exact_populated());
+ BLI_assert(cd.t_plane->norm_exact[cd.proj_axis] != 0);
+ const mpq3 &n = cd.t_plane->norm_exact;
+ const mpq_class &d = cd.t_plane->d_exact;
+ switch (cd.proj_axis) {
+ case (0): {
+ mpq_class num = n[1] * p2d[0] + n[2] * p2d[1] + d;
+ num = -num;
+ p3d[0] = num / n[0];
+ p3d[1] = p2d[0];
+ p3d[2] = p2d[1];
+ break;
+ }
+ case (1): {
+ p3d[0] = p2d[0];
+ mpq_class num = n[0] * p2d[0] + n[2] * p2d[1] + d;
+ num = -num;
+ p3d[1] = num / n[1];
+ p3d[2] = p2d[1];
+ break;
+ }
+ case (2): {
+ p3d[0] = p2d[0];
+ p3d[1] = p2d[1];
+ mpq_class num = n[0] * p2d[0] + n[1] * p2d[1] + d;
+ num = -num;
+ p3d[2] = num / n[2];
+ break;
+ }
+ default:
+ BLI_assert(false);
+ }
+ return p3d;
+}
+
+static void prepare_need_edge(CDT_data &cd, const mpq3 &p1, const mpq3 &p2)
+{
+ int v1 = prepare_need_vert(cd, p1);
+ int v2 = prepare_need_vert(cd, p2);
+ cd.edge.append(std::pair<int, int>(v1, v2));
+}
+
+static void prepare_need_tri(CDT_data &cd, const IMesh &tm, int t)
+{
+ const Face &tri = *tm.face(t);
+ int v0 = prepare_need_vert(cd, tri[0]->co_exact);
+ int v1 = prepare_need_vert(cd, tri[1]->co_exact);
+ int v2 = prepare_need_vert(cd, tri[2]->co_exact);
+ bool rev;
+ /* How to get CCW orientation of projected triangle? Note that when look down y axis
+ * as opposed to x or z, the orientation of the other two axes is not right-and-up. */
+ BLI_assert(cd.t_plane->exact_populated());
+ if (tri.plane->norm_exact[cd.proj_axis] >= 0) {
+ rev = cd.proj_axis == 1;
+ }
+ else {
+ rev = cd.proj_axis != 1;
+ }
+ int cd_t = cd.face.append_and_get_index(Vector<int>());
+ cd.face[cd_t].append(v0);
+ if (rev) {
+ cd.face[cd_t].append(v2);
+ cd.face[cd_t].append(v1);
+ }
+ else {
+ cd.face[cd_t].append(v1);
+ cd.face[cd_t].append(v2);
+ }
+ cd.input_face.append(t);
+ cd.is_reversed.append(rev);
+}
+
+static CDT_data prepare_cdt_input(const IMesh &tm, int t, const Vector<ITT_value> itts)
+{
+ CDT_data ans;
+ BLI_assert(tm.face(t)->plane_populated());
+ ans.t_plane = tm.face(t)->plane;
+ BLI_assert(ans.t_plane->exact_populated());
+ ans.proj_axis = mpq3::dominant_axis(ans.t_plane->norm_exact);
+ prepare_need_tri(ans, tm, t);
+ for (const ITT_value &itt : itts) {
+ switch (itt.kind) {
+ case INONE:
+ break;
+ case IPOINT: {
+ prepare_need_vert(ans, itt.p1);
+ break;
+ }
+ case ISEGMENT: {
+ prepare_need_edge(ans, itt.p1, itt.p2);
+ break;
+ }
+ case ICOPLANAR: {
+ prepare_need_tri(ans, tm, itt.t_source);
+ break;
+ }
+ }
+ }
+ return ans;
+}
+
+static CDT_data prepare_cdt_input_for_cluster(const IMesh &tm,
+ const CoplanarClusterInfo &clinfo,
+ int c,
+ const Vector<ITT_value> itts)
+{
+ CDT_data ans;
+ BLI_assert(c < clinfo.tot_cluster());
+ const CoplanarCluster &cl = clinfo.cluster(c);
+ BLI_assert(cl.tot_tri() > 0);
+ int t0 = cl.tri(0);
+ BLI_assert(tm.face(t0)->plane_populated());
+ ans.t_plane = tm.face(t0)->plane;
+ BLI_assert(ans.t_plane->exact_populated());
+ ans.proj_axis = mpq3::dominant_axis(ans.t_plane->norm_exact);
+ for (const int t : cl) {
+ prepare_need_tri(ans, tm, t);
+ }
+ for (const ITT_value &itt : itts) {
+ switch (itt.kind) {
+ case IPOINT: {
+ prepare_need_vert(ans, itt.p1);
+ break;
+ }
+ case ISEGMENT: {
+ prepare_need_edge(ans, itt.p1, itt.p2);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return ans;
+}
+
+/**
+ * Fills in cd.cdt_out with result of doing the cdt calculation on (vert, edge, face).
+ */
+static void do_cdt(CDT_data &cd)
+{
+ constexpr int dbg_level = 0;
+ CDT_input<mpq_class> cdt_in;
+ cdt_in.vert = Span<mpq2>(cd.vert);
+ cdt_in.edge = Span<std::pair<int, int>>(cd.edge);
+ cdt_in.face = Span<Vector<int>>(cd.face);
+ if (dbg_level > 0) {
+ std::cout << "CDT input\nVerts:\n";
+ for (int i : cdt_in.vert.index_range()) {
+ std::cout << "v" << i << ": " << cdt_in.vert[i] << "=(" << cdt_in.vert[i][0].get_d() << ","
+ << cdt_in.vert[i][1].get_d() << ")\n";
+ }
+ std::cout << "Edges:\n";
+ for (int i : cdt_in.edge.index_range()) {
+ std::cout << "e" << i << ": (" << cdt_in.edge[i].first << ", " << cdt_in.edge[i].second
+ << ")\n";
+ }
+ std::cout << "Tris\n";
+ for (int f : cdt_in.face.index_range()) {
+ std::cout << "f" << f << ": ";
+ for (int j : cdt_in.face[f].index_range()) {
+ std::cout << cdt_in.face[f][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ }
+ cdt_in.epsilon = 0; /* TODO: needs attention for non-exact T. */
+ cd.cdt_out = blender::meshintersect::delaunay_2d_calc(cdt_in, CDT_INSIDE);
+ if (dbg_level > 0) {
+ std::cout << "\nCDT result\nVerts:\n";
+ for (int i : cd.cdt_out.vert.index_range()) {
+ std::cout << "v" << i << ": " << cd.cdt_out.vert[i] << "=(" << cd.cdt_out.vert[i][0].get_d()
+ << "," << cd.cdt_out.vert[i][1].get_d() << "\n";
+ }
+ std::cout << "Tris\n";
+ for (int f : cd.cdt_out.face.index_range()) {
+ std::cout << "f" << f << ": ";
+ for (int j : cd.cdt_out.face[f].index_range()) {
+ std::cout << cd.cdt_out.face[f][j] << " ";
+ }
+ std::cout << "orig: ";
+ for (int j : cd.cdt_out.face_orig[f].index_range()) {
+ std::cout << cd.cdt_out.face_orig[f][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ std::cout << "Edges\n";
+ for (int e : cd.cdt_out.edge.index_range()) {
+ std::cout << "e" << e << ": (" << cd.cdt_out.edge[e].first << ", "
+ << cd.cdt_out.edge[e].second << ") ";
+ std::cout << "orig: ";
+ for (int j : cd.cdt_out.edge_orig[e].index_range()) {
+ std::cout << cd.cdt_out.edge_orig[e][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ }
+}
+
+static int get_cdt_edge_orig(
+ int i0, int i1, const CDT_data &cd, const IMesh &in_tm, bool *r_is_intersect)
+{
+ int foff = cd.cdt_out.face_edge_offset;
+ *r_is_intersect = false;
+ for (int e : cd.cdt_out.edge.index_range()) {
+ std::pair<int, int> edge = cd.cdt_out.edge[e];
+ if ((edge.first == i0 && edge.second == i1) || (edge.first == i1 && edge.second == i0)) {
+ /* Pick an arbitrary orig, but not one equal to NO_INDEX, if we can help it. */
+ /* TODO: if edge has origs from more than on part of the nary input,
+ * then want to set *r_is_intersect to true. */
+ for (int orig_index : cd.cdt_out.edge_orig[e]) {
+ /* orig_index encodes the triangle and pos within the triangle of the input edge. */
+ if (orig_index >= foff) {
+ int in_face_index = (orig_index / foff) - 1;
+ int pos = orig_index % foff;
+ /* We need to retrieve the edge orig field from the Face used to populate the
+ * in_face_index'th face of the CDT, at the pos'th position of the face. */
+ int in_tm_face_index = cd.input_face[in_face_index];
+ BLI_assert(in_tm_face_index < in_tm.face_size());
+ const Face *facep = in_tm.face(in_tm_face_index);
+ BLI_assert(pos < facep->size());
+ bool is_rev = cd.is_reversed[in_face_index];
+ int eorig = is_rev ? facep->edge_orig[2 - pos] : facep->edge_orig[pos];
+ if (eorig != NO_INDEX) {
+ return eorig;
+ }
+ }
+ else {
+ /* This edge came from an edge input to the CDT problem,
+ * so it is an intersect edge. */
+ *r_is_intersect = true;
+ /* TODO: maybe there is an orig index:
+ * This happens if an input edge was formed by an input face having
+ * an edge that is co-planar with the cluster, while the face as a whole is not. */
+ return NO_INDEX;
+ }
+ }
+ return NO_INDEX;
+ }
+ }
+ return NO_INDEX;
+}
+
+/**
+ * Using the result of CDT in cd.cdt_out, extract an #IMesh representing the subdivision
+ * of input triangle t, which should be an element of cd.input_face.
+ */
+static IMesh extract_subdivided_tri(const CDT_data &cd,
+ const IMesh &in_tm,
+ int t,
+ IMeshArena *arena)
+{
+ const CDT_result<mpq_class> &cdt_out = cd.cdt_out;
+ int t_in_cdt = -1;
+ for (int i = 0; i < cd.input_face.size(); ++i) {
+ if (cd.input_face[i] == t) {
+ t_in_cdt = i;
+ }
+ }
+ if (t_in_cdt == -1) {
+ std::cout << "Could not find " << t << " in cdt input tris\n";
+ BLI_assert(false);
+ return IMesh();
+ }
+ int t_orig = in_tm.face(t)->orig;
+ constexpr int inline_buf_size = 20;
+ Vector<Face *, inline_buf_size> faces;
+ for (int f : cdt_out.face.index_range()) {
+ if (cdt_out.face_orig[f].contains(t_in_cdt)) {
+ BLI_assert(cdt_out.face[f].size() == 3);
+ int i0 = cdt_out.face[f][0];
+ int i1 = cdt_out.face[f][1];
+ int i2 = cdt_out.face[f][2];
+ mpq3 v0co = unproject_cdt_vert(cd, cdt_out.vert[i0]);
+ mpq3 v1co = unproject_cdt_vert(cd, cdt_out.vert[i1]);
+ mpq3 v2co = unproject_cdt_vert(cd, cdt_out.vert[i2]);
+ /* No need to provide an original index: if coord matches
+ * an original one, then it will already be in the arena
+ * with the correct orig field. */
+ const Vert *v0 = arena->add_or_find_vert(v0co, NO_INDEX);
+ const Vert *v1 = arena->add_or_find_vert(v1co, NO_INDEX);
+ const Vert *v2 = arena->add_or_find_vert(v2co, NO_INDEX);
+ Face *facep;
+ bool is_isect0;
+ bool is_isect1;
+ bool is_isect2;
+ if (cd.is_reversed[t_in_cdt]) {
+ int oe0 = get_cdt_edge_orig(i0, i2, cd, in_tm, &is_isect0);
+ int oe1 = get_cdt_edge_orig(i2, i1, cd, in_tm, &is_isect1);
+ int oe2 = get_cdt_edge_orig(i1, i0, cd, in_tm, &is_isect2);
+ facep = arena->add_face(
+ {v0, v2, v1}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2});
+ }
+ else {
+ int oe0 = get_cdt_edge_orig(i0, i1, cd, in_tm, &is_isect0);
+ int oe1 = get_cdt_edge_orig(i1, i2, cd, in_tm, &is_isect1);
+ int oe2 = get_cdt_edge_orig(i2, i0, cd, in_tm, &is_isect2);
+ facep = arena->add_face(
+ {v0, v1, v2}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2});
+ }
+ facep->populate_plane(false);
+ faces.append(facep);
+ }
+ }
+ return IMesh(faces);
+}
+
+static IMesh extract_single_tri(const IMesh &tm, int t)
+{
+ Face *f = tm.face(t);
+ return IMesh({f});
+}
+
+static bool bvhtreeverlap_cmp(const BVHTreeOverlap &a, const BVHTreeOverlap &b)
+{
+ if (a.indexA < b.indexA) {
+ return true;
+ }
+ if ((a.indexA == b.indexA) & (a.indexB < b.indexB)) {
+ return true;
+ }
+ return false;
+}
+class TriOverlaps {
+ BVHTree *tree_{nullptr};
+ BVHTree *tree_b_{nullptr};
+ BVHTreeOverlap *overlap_{nullptr};
+ uint overlap_tot_{0};
+
+ struct CBData {
+ const IMesh &tm;
+ std::function<int(int)> shape_fn;
+ int nshapes;
+ bool use_self;
+ };
+
+ public:
+ TriOverlaps(const IMesh &tm,
+ const Array<BoundingBox> &tri_bb,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self)
+ {
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "TriOverlaps construction\n";
+ }
+ /* Tree type is 8 => octtree; axis = 6 => using XYZ axes only. */
+ tree_ = BLI_bvhtree_new(tm.face_size(), FLT_EPSILON, 8, 6);
+ /* In the common case of a binary boolean and no self intersection in
+ * each shape, we will use two trees and simple bounding box overlap. */
+ bool two_trees_no_self = nshapes == 2 && !use_self;
+ if (two_trees_no_self) {
+ tree_b_ = BLI_bvhtree_new(tm.face_size(), FLT_EPSILON, 8, 6);
+ }
+ float bbpts[6];
+ for (int t : tm.face_index_range()) {
+ const BoundingBox &bb = tri_bb[t];
+ copy_v3_v3(bbpts, bb.min);
+ copy_v3_v3(bbpts + 3, bb.max);
+ int shape = shape_fn(tm.face(t)->orig);
+ if (two_trees_no_self) {
+ if (shape == 0) {
+ BLI_bvhtree_insert(tree_, t, bbpts, 2);
+ }
+ else if (shape == 1) {
+ BLI_bvhtree_insert(tree_b_, t, bbpts, 2);
+ }
+ }
+ else {
+ if (shape != -1) {
+ BLI_bvhtree_insert(tree_, t, bbpts, 2);
+ }
+ }
+ }
+ BLI_bvhtree_balance(tree_);
+ if (two_trees_no_self) {
+ BLI_bvhtree_balance(tree_b_);
+ /* Don't expect a lot of trivial intersects in this case. */
+ overlap_ = BLI_bvhtree_overlap(tree_, tree_b_, &overlap_tot_, NULL, NULL);
+ }
+ else {
+ CBData cbdata{tm, shape_fn, nshapes, use_self};
+ if (nshapes == 1 && use_self) {
+ /* Expect a lot of trivial intersects from quads that are triangulated
+ * and faces that share vertices.
+ * Filter them out with a callback. */
+ overlap_ = BLI_bvhtree_overlap(
+ tree_, tree_, &overlap_tot_, only_nontrivial_intersects, &cbdata);
+ }
+ else {
+ overlap_ = BLI_bvhtree_overlap(
+ tree_, tree_, &overlap_tot_, only_different_shapes, &cbdata);
+ }
+ }
+ /* The rest of the code is simpler and easier to parallelize if, in the two-trees case,
+ * we repeat the overlaps with indexA and indexB reversed. It is important that
+ * in the repeated part, sorting will then bring things with indexB together. */
+ if (two_trees_no_self) {
+ overlap_ = static_cast<BVHTreeOverlap *>(
+ MEM_reallocN(overlap_, 2 * overlap_tot_ * sizeof(overlap_[0])));
+ for (uint i = 0; i < overlap_tot_; ++i) {
+ overlap_[overlap_tot_ + i].indexA = overlap_[i].indexB;
+ overlap_[overlap_tot_ + i].indexB = overlap_[i].indexA;
+ }
+ overlap_tot_ += overlap_tot_;
+ }
+ /* Sort the overlaps to bring all the intersects with a given indexA together. */
+ std::sort(overlap_, overlap_ + overlap_tot_, bvhtreeverlap_cmp);
+ if (dbg_level > 0) {
+ std::cout << overlap_tot_ << " overlaps found:\n";
+ for (BVHTreeOverlap ov : overlap()) {
+ std::cout << "A: " << ov.indexA << ", B: " << ov.indexB << "\n";
+ }
+ }
+ }
+
+ ~TriOverlaps()
+ {
+ if (tree_) {
+ BLI_bvhtree_free(tree_);
+ }
+ if (tree_b_) {
+ BLI_bvhtree_free(tree_b_);
+ }
+ if (overlap_) {
+ MEM_freeN(overlap_);
+ }
+ }
+
+ Span<BVHTreeOverlap> overlap() const
+ {
+ return Span<BVHTreeOverlap>(overlap_, overlap_tot_);
+ }
+
+ private:
+ static bool only_nontrivial_intersects(void *userdata,
+ int index_a,
+ int index_b,
+ int UNUSED(thread))
+ {
+ CBData *cbdata = static_cast<CBData *>(userdata);
+ return may_non_trivially_intersect(cbdata->tm.face(index_a), cbdata->tm.face(index_b));
+ }
+
+ static bool only_different_shapes(void *userdata, int index_a, int index_b, int UNUSED(thread))
+ {
+ CBData *cbdata = static_cast<CBData *>(userdata);
+ return cbdata->tm.face(index_a)->orig != cbdata->tm.face(index_b)->orig;
+ }
+};
+
+/**
+ * Data needed for parallelization of #calc_overlap_itts.
+ */
+struct OverlapIttsData {
+ Vector<std::pair<int, int>> intersect_pairs;
+ Map<std::pair<int, int>, ITT_value> &itt_map;
+ const IMesh &tm;
+ IMeshArena *arena;
+
+ OverlapIttsData(Map<std::pair<int, int>, ITT_value> &itt_map, const IMesh &tm, IMeshArena *arena)
+ : itt_map(itt_map), tm(tm), arena(arena)
+ {
+ }
+};
+
+/**
+ * Return a std::pair containing a and b in canonical order:
+ * With a <= b.
+ */
+static std::pair<int, int> canon_int_pair(int a, int b)
+{
+ if (a > b) {
+ std::swap(a, b);
+ }
+ return std::pair<int, int>(a, b);
+}
+
+static void calc_overlap_itts_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ constexpr int dbg_level = 0;
+ OverlapIttsData *data = static_cast<OverlapIttsData *>(userdata);
+ std::pair<int, int> tri_pair = data->intersect_pairs[iter];
+ int a = tri_pair.first;
+ int b = tri_pair.second;
+ if (dbg_level > 0) {
+ std::cout << "calc_overlap_itts_range_func a=" << a << ", b=" << b << "\n";
+ }
+ ITT_value itt = intersect_tri_tri(data->tm, a, b);
+ if (dbg_level > 0) {
+ std::cout << "result of intersecting " << a << " and " << b << " = " << itt << "\n";
+ }
+ BLI_assert(data->itt_map.contains(tri_pair));
+ data->itt_map.add_overwrite(tri_pair, itt);
+}
+
+/**
+ * Fill in itt_map with the vector of ITT_values that result from intersecting the triangles in ov.
+ * Use a canonical order for triangles: (a,b) where a < b.
+ * Don't bother doing this if both a and b are part of the same co-planar cluster, as the
+ * intersection for those will be handled by CDT, later.
+ */
+static void calc_overlap_itts(Map<std::pair<int, int>, ITT_value> &itt_map,
+ const IMesh &tm,
+ const CoplanarClusterInfo &clinfo,
+ const TriOverlaps &ov,
+ IMeshArena *arena)
+{
+ OverlapIttsData data(itt_map, tm, arena);
+ /* Put dummy values in `itt_map` initially,
+ * so map entries will exist when doing the range function.
+ * This means we won't have to protect the `itt_map.add_overwrite` function with a lock. */
+ for (const BVHTreeOverlap &olap : ov.overlap()) {
+ std::pair<int, int> key = canon_int_pair(olap.indexA, olap.indexB);
+ if (!itt_map.contains(key)) {
+ int ca = clinfo.tri_cluster(key.first);
+ int cb = clinfo.tri_cluster(key.second);
+ if (ca == NO_INDEX || ca != cb) {
+ itt_map.add_new(key, ITT_value());
+ data.intersect_pairs.append(key);
+ }
+ }
+ }
+ int tot_intersect_pairs = data.intersect_pairs.size();
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, tot_intersect_pairs, &data, calc_overlap_itts_range_func, &settings);
+}
+
+/**
+ * Data needed for parallelization of calc_subdivided_tris.
+ */
+struct OverlapTriRange {
+ int tri_index;
+ int overlap_start;
+ int len;
+};
+struct SubdivideTrisData {
+ Array<IMesh> &r_tri_subdivided;
+ const IMesh &tm;
+ const Map<std::pair<int, int>, ITT_value> &itt_map;
+ Span<BVHTreeOverlap> overlap;
+ IMeshArena *arena;
+
+ /* This vector gives, for each triangle in tm that has an intersection
+ * we want to calculate: what the index of that triangle in tm is,
+ * where it starts in the ov structure as indexA, and how many
+ * overlap pairs have that same indexA (they will be continuous). */
+ Vector<OverlapTriRange> overlap_tri_range;
+
+ SubdivideTrisData(Array<IMesh> &r_tri_subdivided,
+ const IMesh &tm,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ Span<BVHTreeOverlap> overlap,
+ IMeshArena *arena)
+ : r_tri_subdivided(r_tri_subdivided),
+ tm(tm),
+ itt_map(itt_map),
+ overlap(overlap),
+ arena(arena),
+ overlap_tri_range{}
+ {
+ }
+};
+
+static void calc_subdivided_tri_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ constexpr int dbg_level = 0;
+ SubdivideTrisData *data = static_cast<SubdivideTrisData *>(userdata);
+ OverlapTriRange &otr = data->overlap_tri_range[iter];
+ int t = otr.tri_index;
+ if (dbg_level > 0) {
+ std::cout << "calc_subdivided_tri_range_func\nt=" << t << " start=" << otr.overlap_start
+ << " len=" << otr.len << "\n";
+ }
+ constexpr int inline_capacity = 100;
+ Vector<ITT_value, inline_capacity> itts(otr.len);
+ for (int j = otr.overlap_start; j < otr.overlap_start + otr.len; ++j) {
+ int t_other = data->overlap[j].indexB;
+ std::pair<int, int> key = canon_int_pair(t, t_other);
+ ITT_value itt;
+ if (data->itt_map.contains(key)) {
+ itt = data->itt_map.lookup(key);
+ }
+ if (itt.kind != INONE) {
+ itts.append(itt);
+ }
+ if (dbg_level > 0) {
+ std::cout << " tri t" << t_other << "; result = " << itt << "\n";
+ }
+ }
+ if (itts.size() > 0) {
+ CDT_data cd_data = prepare_cdt_input(data->tm, t, itts);
+ do_cdt(cd_data);
+ data->r_tri_subdivided[t] = extract_subdivided_tri(cd_data, data->tm, t, data->arena);
+ if (dbg_level > 0) {
+ std::cout << "subdivide output\n" << data->r_tri_subdivided[t];
+ }
+ }
+}
+
+/**
+ * For each triangle in tm, fill in the corresponding slot in
+ * r_tri_subdivided with the result of intersecting it with
+ * all the other triangles in the mesh, if it intersects any others.
+ * But don't do this for triangles that are part of a cluster.
+ * Also, do nothing here if the answer is just the triangle itself.
+ */
+static void calc_subdivided_tris(Array<IMesh> &r_tri_subdivided,
+ const IMesh &tm,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ const CoplanarClusterInfo &clinfo,
+ const TriOverlaps &ov,
+ IMeshArena *arena)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nCALC_SUBDIVIDED_TRIS\n\n";
+ }
+ Span<BVHTreeOverlap> overlap = ov.overlap();
+ SubdivideTrisData data(r_tri_subdivided, tm, itt_map, overlap, arena);
+ int overlap_tot = overlap.size();
+ data.overlap_tri_range = Vector<OverlapTriRange>();
+ data.overlap_tri_range.reserve(overlap_tot);
+ int overlap_index = 0;
+ while (overlap_index < overlap_tot) {
+ int t = overlap[overlap_index].indexA;
+ int i = overlap_index;
+ while (i + 1 < overlap_tot && overlap[i + 1].indexA == t) {
+ ++i;
+ }
+ /* Now overlap[overlap_index] to overlap[i] have indexA == t.
+ * We only record ranges for triangles that are not in clusters,
+ * because the ones in clusters are handled separately. */
+ if (clinfo.tri_cluster(t) == NO_INDEX) {
+ int len = i - overlap_index + 1;
+ if (!(len == 1 && overlap[overlap_index].indexB == t)) {
+ OverlapTriRange range = {t, overlap_index, len};
+ data.overlap_tri_range.append(range);
+# ifdef PERFDEBUG
+ bumpperfcount(0, len); /* Non-cluster overlaps. */
+# endif
+ }
+ }
+ overlap_index = i + 1;
+ }
+ int overlap_tri_range_tot = data.overlap_tri_range.size();
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 50;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(
+ 0, overlap_tri_range_tot, &data, calc_subdivided_tri_range_func, &settings);
+}
+
+/* Get first index in ov where indexA == t. Assuming sorted on indexA. */
+static int find_first_overlap_index(const TriOverlaps &ov, int t)
+{
+ Span<BVHTreeOverlap> span = ov.overlap();
+ if (span.size() == 0) {
+ return -1;
+ }
+ BVHTreeOverlap bo{t, -1};
+ const BVHTreeOverlap *p = std::lower_bound(
+ span.begin(), span.end(), bo, [](const BVHTreeOverlap &o1, const BVHTreeOverlap &o2) {
+ return o1.indexA < o2.indexA;
+ });
+ if (p != span.end()) {
+ return p - span.begin();
+ }
+ return -1;
+}
+
+static CDT_data calc_cluster_subdivided(const CoplanarClusterInfo &clinfo,
+ int c,
+ const IMesh &tm,
+ const TriOverlaps &ov,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ IMeshArena *UNUSED(arena))
+{
+ constexpr int dbg_level = 0;
+ BLI_assert(c < clinfo.tot_cluster());
+ const CoplanarCluster &cl = clinfo.cluster(c);
+ /* Make a CDT input with triangles from C and intersects from other triangles in tm. */
+ if (dbg_level > 0) {
+ std::cout << "CALC_CLUSTER_SUBDIVIDED for cluster " << c << " = " << cl << "\n";
+ }
+ /* Get vector itts of all intersections of a triangle of cl with any triangle of tm not
+ * in cl and not co-planar with it (for that latter, if there were an intersection,
+ * it should already be in cluster cl). */
+ Vector<ITT_value> itts;
+ Span<BVHTreeOverlap> ovspan = ov.overlap();
+ for (int t : cl) {
+ if (dbg_level > 0) {
+ std::cout << "find intersects with triangle " << t << " of cluster\n";
+ }
+ int first_i = find_first_overlap_index(ov, t);
+ if (first_i == -1) {
+ continue;
+ }
+ for (int i = first_i; i < ovspan.size() && ovspan[i].indexA == t; ++i) {
+ int t_other = ovspan[i].indexB;
+ if (clinfo.tri_cluster(t_other) != c) {
+ if (dbg_level > 0) {
+ std::cout << "use intersect(" << t << "," << t_other << "\n";
+ }
+ std::pair<int, int> key = canon_int_pair(t, t_other);
+ if (itt_map.contains(key)) {
+ ITT_value itt = itt_map.lookup(key);
+ if (itt.kind != INONE && itt.kind != ICOPLANAR) {
+ itts.append(itt);
+ if (dbg_level > 0) {
+ std::cout << " itt = " << itt << "\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ /* Use CDT to subdivide the cluster triangles and the points and segs in itts. */
+ CDT_data cd_data = prepare_cdt_input_for_cluster(tm, clinfo, c, itts);
+ do_cdt(cd_data);
+ return cd_data;
+}
+
+static IMesh union_tri_subdivides(const blender::Array<IMesh> &tri_subdivided)
+{
+ int tot_tri = 0;
+ for (const IMesh &m : tri_subdivided) {
+ tot_tri += m.face_size();
+ }
+ Array<Face *> faces(tot_tri);
+ int face_index = 0;
+ for (const IMesh &m : tri_subdivided) {
+ for (Face *f : m.faces()) {
+ faces[face_index++] = f;
+ }
+ }
+ return IMesh(faces);
+}
+
+static CoplanarClusterInfo find_clusters(const IMesh &tm, const Array<BoundingBox> &tri_bb)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CLUSTERS\n";
+ }
+ CoplanarClusterInfo ans(tm.face_size());
+ /* There can be more than one #CoplanarCluster per plane. Accumulate them in
+ * a Vector. We will have to merge some elements of the Vector as we discover
+ * triangles that form intersection bridges between two or more clusters. */
+ Map<Plane, Vector<CoplanarCluster>> plane_cls;
+ plane_cls.reserve(tm.face_size());
+ for (int t : tm.face_index_range()) {
+ /* Use a canonical version of the plane for map index.
+ * We can't just store the canonical version in the face
+ * since canonicalizing loses the orientation of the normal. */
+ Plane tplane = *tm.face(t)->plane;
+ BLI_assert(tplane.exact_populated());
+ tplane.make_canonical();
+ if (dbg_level > 0) {
+ std::cout << "plane for tri " << t << " = " << &tplane << "\n";
+ }
+ /* Assume all planes are in canonical from (see canon_plane()). */
+ if (plane_cls.contains(tplane)) {
+ Vector<CoplanarCluster> &curcls = plane_cls.lookup(tplane);
+ if (dbg_level > 0) {
+ std::cout << "already has " << curcls.size() << " clusters\n";
+ }
+ int proj_axis = mpq3::dominant_axis(tplane.norm_exact);
+ /* Partition `curcls` into those that intersect t non-trivially, and those that don't. */
+ Vector<CoplanarCluster *> int_cls;
+ Vector<CoplanarCluster *> no_int_cls;
+ for (CoplanarCluster &cl : curcls) {
+ if (bbs_might_intersect(tri_bb[t], cl.bounding_box()) &&
+ non_trivially_coplanar_intersects(tm, t, cl, proj_axis)) {
+ if (dbg_level > 1) {
+ std::cout << "append to int_cls\n";
+ }
+ int_cls.append(&cl);
+ }
+ else {
+ if (dbg_level > 1) {
+ std::cout << "append to no_int_cls\n";
+ }
+ no_int_cls.append(&cl);
+ }
+ }
+ if (int_cls.size() == 0) {
+ /* t doesn't intersect any existing cluster in its plane, so make one just for it. */
+ if (dbg_level > 1) {
+ std::cout << "no intersecting clusters for t, make a new one\n";
+ }
+ curcls.append(CoplanarCluster(t, tri_bb[t]));
+ }
+ else if (int_cls.size() == 1) {
+ /* t intersects exactly one existing cluster, so can add t to that cluster. */
+ if (dbg_level > 1) {
+ std::cout << "exactly one existing cluster, " << int_cls[0] << ", adding to it\n";
+ }
+ int_cls[0]->add_tri(t, tri_bb[t]);
+ }
+ else {
+ /* t intersections 2 or more existing clusters: need to merge them and replace all the
+ * originals with the merged one in `curcls`. */
+ if (dbg_level > 1) {
+ std::cout << "merging\n";
+ }
+ CoplanarCluster mergecl;
+ mergecl.add_tri(t, tri_bb[t]);
+ for (CoplanarCluster *cl : int_cls) {
+ for (int t : *cl) {
+ mergecl.add_tri(t, tri_bb[t]);
+ }
+ }
+ Vector<CoplanarCluster> newvec;
+ newvec.append(mergecl);
+ for (CoplanarCluster *cl_no_int : no_int_cls) {
+ newvec.append(*cl_no_int);
+ }
+ plane_cls.add_overwrite(tplane, newvec);
+ }
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "first cluster for its plane\n";
+ }
+ plane_cls.add_new(tplane, Vector<CoplanarCluster>{CoplanarCluster(t, tri_bb[t])});
+ }
+ }
+ /* Does this give deterministic order for cluster ids? I think so, since
+ * hash for planes is on their values, not their addresses. */
+ for (auto item : plane_cls.items()) {
+ for (const CoplanarCluster &cl : item.value) {
+ if (cl.tot_tri() > 1) {
+ ans.add_cluster(cl);
+ }
+ }
+ }
+
+ return ans;
+}
+
+static bool face_is_degenerate(const Face *f)
+{
+ const Face &face = *f;
+ const Vert *v0 = face[0];
+ const Vert *v1 = face[1];
+ const Vert *v2 = face[2];
+ if (v0 == v1 || v0 == v2 || v1 == v2) {
+ return true;
+ }
+ double3 da = v2->co - v0->co;
+ double3 db = v2->co - v1->co;
+ double3 dab = double3::cross_high_precision(da, db);
+ double dab_length_squared = dab.length_squared();
+ double err_bound = supremum_dot_cross(dab, dab) * index_dot_cross * DBL_EPSILON;
+ if (dab_length_squared > err_bound) {
+ return false;
+ }
+ mpq3 a = v2->co_exact - v0->co_exact;
+ mpq3 b = v2->co_exact - v1->co_exact;
+ mpq3 ab = mpq3::cross(a, b);
+ if (ab.x == 0 && ab.y == 0 && ab.z == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+/* Data and functions to test triangle degeneracy in parallel. */
+struct DegenData {
+ const IMesh &tm;
+};
+
+struct DegenChunkData {
+ bool has_degenerate_tri = false;
+};
+
+static void degenerate_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict tls)
+{
+ DegenData *data = static_cast<DegenData *>(userdata);
+ DegenChunkData *chunk_data = static_cast<DegenChunkData *>(tls->userdata_chunk);
+ const Face *f = data->tm.face(iter);
+ bool is_degenerate = face_is_degenerate(f);
+ chunk_data->has_degenerate_tri |= is_degenerate;
+}
+
+static void degenerate_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ DegenChunkData *degen_chunk_join = static_cast<DegenChunkData *>(chunk_join);
+ DegenChunkData *degen_chunk = static_cast<DegenChunkData *>(chunk);
+ degen_chunk_join->has_degenerate_tri |= degen_chunk->has_degenerate_tri;
+}
+
+/* Does triangle #IMesh tm have any triangles with zero area? */
+static bool has_degenerate_tris(const IMesh &tm)
+{
+ DegenData degen_data = {tm};
+ DegenChunkData degen_chunk_data;
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.userdata_chunk = &degen_chunk_data;
+ settings.userdata_chunk_size = sizeof(degen_chunk_data);
+ settings.func_reduce = degenerate_reduce;
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, tm.face_size(), &degen_data, degenerate_range_func, &settings);
+ return degen_chunk_data.has_degenerate_tri;
+}
+
+static IMesh remove_degenerate_tris(const IMesh &tm_in)
+{
+ IMesh ans;
+ Vector<Face *> new_faces;
+ new_faces.reserve(tm_in.face_size());
+ for (Face *f : tm_in.faces()) {
+ if (!face_is_degenerate(f)) {
+ new_faces.append(f);
+ }
+ }
+ ans.set_faces(new_faces);
+ return ans;
+}
+
+/* This is the main routine for calculating the self_intersection of a triangle mesh. */
+IMesh trimesh_self_intersect(const IMesh &tm_in, IMeshArena *arena)
+{
+ return trimesh_nary_intersect(
+ tm_in, 1, [](int UNUSED(t)) { return 0; }, true, arena);
+}
+
+IMesh trimesh_nary_intersect(const IMesh &tm_in,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nTRIMESH_NARY_INTERSECT nshapes=" << nshapes << " use_self=" << use_self
+ << "\n";
+ for (const Face *f : tm_in.faces()) {
+ BLI_assert(f->is_tri());
+ UNUSED_VARS_NDEBUG(f);
+ }
+ if (dbg_level > 1) {
+ std::cout << "input mesh:\n" << tm_in;
+ for (int t : tm_in.face_index_range()) {
+ std::cout << "shape(" << t << ") = " << shape_fn(tm_in.face(t)->orig) << "\n";
+ }
+ }
+ }
+# ifdef PERFDEBUG
+ perfdata_init();
+ double start_time = PIL_check_seconds_timer();
+ std::cout << "trimesh_nary_intersect start\n";
+# endif
+ /* Usually can use tm_in but if it has degenerate or illegal triangles,
+ * then need to work on a copy of it without those triangles. */
+ const IMesh *tm_clean = &tm_in;
+ IMesh tm_cleaned;
+ if (has_degenerate_tris(tm_in)) {
+ if (dbg_level > 0) {
+ std::cout << "cleaning degenerate triangles\n";
+ }
+ tm_cleaned = remove_degenerate_tris(tm_in);
+ tm_clean = &tm_cleaned;
+ if (dbg_level > 1) {
+ std::cout << "cleaned input mesh:\n" << tm_cleaned;
+ }
+ }
+ /* Temporary, while developing: populate all plane normals exactly. */
+ for (Face *f : tm_clean->faces()) {
+ f->populate_plane(true);
+ }
+# ifdef PERFDEBUG
+ double clean_time = PIL_check_seconds_timer();
+ std::cout << "cleaned, time = " << clean_time - start_time << "\n";
+# endif
+ Array<BoundingBox> tri_bb = calc_face_bounding_boxes(*tm_clean);
+# ifdef PERFDEBUG
+ double bb_calc_time = PIL_check_seconds_timer();
+ std::cout << "bbs calculated, time = " << bb_calc_time - clean_time << "\n";
+# endif
+ TriOverlaps tri_ov(*tm_clean, tri_bb, nshapes, shape_fn, use_self);
+# ifdef PERFDEBUG
+ double overlap_time = PIL_check_seconds_timer();
+ std::cout << "intersect overlaps calculated, time = " << overlap_time - bb_calc_time << "\n";
+
+# endif
+ CoplanarClusterInfo clinfo = find_clusters(*tm_clean, tri_bb);
+ if (dbg_level > 1) {
+ std::cout << clinfo;
+ }
+# ifdef PERFDEBUG
+ double find_cluster_time = PIL_check_seconds_timer();
+ std::cout << "clusters found, time = " << find_cluster_time - overlap_time << "\n";
+ doperfmax(0, tm_in.face_size());
+ doperfmax(1, clinfo.tot_cluster());
+ doperfmax(2, tri_ov.overlap().size());
+# endif
+ /* itt_map((a,b)) will hold the intersection value resulting from intersecting
+ * triangles with indices a and b, where a < b. */
+ Map<std::pair<int, int>, ITT_value> itt_map;
+ itt_map.reserve(tri_ov.overlap().size());
+ calc_overlap_itts(itt_map, *tm_clean, clinfo, tri_ov, arena);
+# ifdef PERFDEBUG
+ double itt_time = PIL_check_seconds_timer();
+ std::cout << "itts found, time = " << itt_time - find_cluster_time << "\n";
+# endif
+ Array<IMesh> tri_subdivided(tm_clean->face_size());
+ calc_subdivided_tris(tri_subdivided, *tm_clean, itt_map, clinfo, tri_ov, arena);
+# ifdef PERFDEBUG
+ double subdivided_tris_time = PIL_check_seconds_timer();
+ std::cout << "subdivided tris found, time = " << subdivided_tris_time - itt_time << "\n";
+# endif
+ Array<CDT_data> cluster_subdivided(clinfo.tot_cluster());
+ for (int c : clinfo.index_range()) {
+ cluster_subdivided[c] = calc_cluster_subdivided(clinfo, c, *tm_clean, tri_ov, itt_map, arena);
+ }
+# ifdef PERFDEBUG
+ double cluster_subdivide_time = PIL_check_seconds_timer();
+ std::cout << "subdivided clusters found, time = "
+ << cluster_subdivide_time - subdivided_tris_time << "\n";
+# endif
+ for (int t : tm_clean->face_index_range()) {
+ int c = clinfo.tri_cluster(t);
+ if (c != NO_INDEX) {
+ BLI_assert(tri_subdivided[t].face_size() == 0);
+ tri_subdivided[t] = extract_subdivided_tri(cluster_subdivided[c], *tm_clean, t, arena);
+ }
+ else if (tri_subdivided[t].face_size() == 0) {
+ tri_subdivided[t] = extract_single_tri(*tm_clean, t);
+ }
+ }
+# ifdef PERFDEBUG
+ double extract_time = PIL_check_seconds_timer();
+ std::cout << "triangles extracted, time = " << extract_time - cluster_subdivide_time << "\n";
+# endif
+ IMesh combined = union_tri_subdivides(tri_subdivided);
+ if (dbg_level > 1) {
+ std::cout << "TRIMESH_NARY_INTERSECT answer:\n";
+ std::cout << combined;
+ }
+# ifdef PERFDEBUG
+ double end_time = PIL_check_seconds_timer();
+ std::cout << "triangles combined, time = " << end_time - extract_time << "\n";
+ std::cout << "trimesh_nary_intersect done, total time = " << end_time - start_time << "\n";
+ dump_perfdata();
+# endif
+ return combined;
+}
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarCluster &cl)
+{
+ os << "cl(";
+ bool first = true;
+ for (const int t : cl) {
+ if (first) {
+ first = false;
+ }
+ else {
+ os << ",";
+ }
+ os << t;
+ }
+ os << ")";
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarClusterInfo &clinfo)
+{
+ os << "Coplanar Cluster Info:\n";
+ for (int c : clinfo.index_range()) {
+ os << c << ": " << clinfo.cluster(c) << "\n";
+ }
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const ITT_value &itt)
+{
+ switch (itt.kind) {
+ case INONE:
+ os << "none";
+ break;
+ case IPOINT:
+ os << "point " << itt.p1;
+ break;
+ case ISEGMENT:
+ os << "segment " << itt.p1 << " " << itt.p2;
+ break;
+ case ICOPLANAR:
+ os << "co-planar t" << itt.t_source;
+ break;
+ }
+ return os;
+}
+
+/**
+ * Writing the obj_mesh has the side effect of populating verts.
+ */
+void write_obj_mesh(IMesh &m, const std::string &objname)
+{
+ /* Would like to use #BKE_tempdir_base() here, but that brings in dependence on kernel library.
+ * This is just for developer debugging anyway,
+ * and should never be called in production Blender. */
+# ifdef _WIN_32
+ const char *objdir = BLI_getenv("HOME");
+# else
+ const char *objdir = "/tmp/";
+# endif
+ if (m.face_size() == 0) {
+ return;
+ }
+
+ std::string fname = std::string(objdir) + objname + std::string(".obj");
+ std::ofstream f;
+ f.open(fname);
+ if (!f) {
+ std::cout << "Could not open file " << fname << "\n";
+ return;
+ }
+
+ if (!m.has_verts()) {
+ m.populate_vert();
+ }
+ for (const Vert *v : m.vertices()) {
+ const double3 dv = v->co;
+ f << "v " << dv[0] << " " << dv[1] << " " << dv[2] << "\n";
+ }
+ int i = 0;
+ for (const Face *face : m.faces()) {
+ /* OBJ files use 1-indexing for vertices. */
+ f << "f ";
+ for (const Vert *v : *face) {
+ int i = m.lookup_vert(v);
+ BLI_assert(i != NO_INDEX);
+ /* OBJ files use 1-indexing for vertices. */
+ f << i + 1 << " ";
+ }
+ f << "\n";
+ ++i;
+ }
+ f.close();
+}
+
+# ifdef PERFDEBUG
+struct PerfCounts {
+ Vector<int> count;
+ Vector<const char *> count_name;
+ Vector<int> max;
+ Vector<const char *> max_name;
+};
+
+static PerfCounts *perfdata = nullptr;
+
+static void perfdata_init(void)
+{
+ perfdata = new PerfCounts;
+
+ /* count 0. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("Non-cluster overlaps");
+
+ /* count 1. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("intersect_tri_tri calls");
+
+ /* count 2. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("tri tri intersects decided by filter plane tests");
+
+ /* count 3. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("tri tri intersects decided by exact plane tests");
+
+ /* count 4. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("final non-NONE intersects");
+
+ /* max 0. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total faces");
+
+ /* max 1. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total clusters");
+
+ /* max 2. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total overlaps");
+}
+
+static void incperfcount(int countnum)
+{
+ perfdata->count[countnum]++;
+}
+
+static void bumpperfcount(int countnum, int amt)
+{
+ perfdata->count[countnum] += amt;
+}
+
+static void doperfmax(int maxnum, int val)
+{
+ perfdata->max[maxnum] = max_ii(perfdata->max[maxnum], val);
+}
+
+static void dump_perfdata(void)
+{
+ std::cout << "\nPERFDATA\n";
+ for (int i : perfdata->count.index_range()) {
+ std::cout << perfdata->count_name[i] << " = " << perfdata->count[i] << "\n";
+ }
+ for (int i : perfdata->max.index_range()) {
+ std::cout << perfdata->max_name[i] << " = " << perfdata->max[i] << "\n";
+ }
+ delete perfdata;
+}
+# endif
+
+}; // namespace blender::meshintersect
+
+#endif // WITH_GMP
diff --git a/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
index 752f833461d..2287389f7aa 100644
--- a/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
+++ b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
@@ -4,48 +4,30 @@
#include "MEM_guardedalloc.h"
+extern "C" {
#include "BLI_math.h"
#include "BLI_rand.h"
#include "PIL_time.h"
-
-#include "BLI_delaunay_2d.h"
+}
#include <fstream>
#include <iostream>
#include <sstream>
+#include <type_traits>
-#define DO_REGULAR_TESTS 1
+#define DO_CPP_TESTS 1
+#define DO_C_TESTS 1
#define DO_RANDOM_TESTS 0
-#define DO_FILE_TESTS 0
-static void fill_input_verts(CDT_input *r_input, float (*vcos)[2], int nverts)
-{
- r_input->verts_len = nverts;
- r_input->edges_len = 0;
- r_input->faces_len = 0;
- r_input->vert_coords = vcos;
- r_input->edges = NULL;
- r_input->faces = NULL;
- r_input->faces_start_table = NULL;
- r_input->faces_len_table = NULL;
- r_input->epsilon = 1e-5f;
- r_input->skip_input_modify = false;
-}
+#include "BLI_array.hh"
+#include "BLI_double2.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mpq2.hh"
+#include "BLI_vector.hh"
-static void add_input_edges(CDT_input *r_input, int (*edges)[2], int nedges)
-{
- r_input->edges_len = nedges;
- r_input->edges = edges;
-}
+#include "BLI_delaunay_2d.h"
-static void add_input_faces(
- CDT_input *r_input, int *faces, int *faces_start_table, int *faces_len_table, int nfaces)
-{
- r_input->faces_len = nfaces;
- r_input->faces = faces;
- r_input->faces_start_table = faces_start_table;
- r_input->faces_len_table = faces_len_table;
-}
+namespace blender::meshintersect {
/* The spec should have the form:
* #verts #edges #faces
@@ -53,177 +35,164 @@ static void add_input_faces(
* <int> <int> [#edges lines]
* <int> <int> ... <int> [#faces lines]
*/
-static void fill_input_from_string(CDT_input *r_input, const char *spec)
+template<typename T> CDT_input<T> fill_input_from_string(const char *spec)
{
- std::string line;
- std::vector<std::vector<int>> faces;
- int i, j;
- int nverts, nedges, nfaces;
- float(*p)[2];
- int(*e)[2];
- int *farr;
- int *flen;
- int *fstart;
-
std::istringstream ss(spec);
+ std::string line;
getline(ss, line);
std::istringstream hdrss(line);
+ int nverts, nedges, nfaces;
hdrss >> nverts >> nedges >> nfaces;
if (nverts == 0) {
- return;
- }
- p = (float(*)[2])MEM_malloc_arrayN(nverts, 2 * sizeof(float), __func__);
- if (nedges > 0) {
- e = (int(*)[2])MEM_malloc_arrayN(nedges, 2 * sizeof(int), __func__);
- }
- if (nfaces > 0) {
- flen = (int *)MEM_malloc_arrayN(nfaces, sizeof(int), __func__);
- fstart = (int *)MEM_malloc_arrayN(nfaces, sizeof(int), __func__);
+ return CDT_input<T>();
}
- i = 0;
+ Array<vec2<T>> verts(nverts);
+ Array<std::pair<int, int>> edges(nedges);
+ Array<Vector<int>> faces(nfaces);
+ int i = 0;
while (i < nverts && getline(ss, line)) {
std::istringstream iss(line);
- iss >> p[i][0] >> p[i][1];
+ double dp0, dp1;
+ iss >> dp0 >> dp1;
+ T p0(dp0);
+ T p1(dp1);
+ verts[i] = vec2<T>(p0, p1);
i++;
}
i = 0;
while (i < nedges && getline(ss, line)) {
std::istringstream ess(line);
- ess >> e[i][0] >> e[i][1];
+ int e0, e1;
+ ess >> e0 >> e1;
+ edges[i] = std::pair<int, int>(e0, e1);
i++;
}
i = 0;
while (i < nfaces && getline(ss, line)) {
std::istringstream fss(line);
int v;
- faces.push_back(std::vector<int>());
while (fss >> v) {
- faces[i].push_back(v);
+ faces[i].append(v);
}
i++;
}
- fill_input_verts(r_input, p, nverts);
- if (nedges > 0) {
- add_input_edges(r_input, e, nedges);
+ CDT_input<T> ans;
+ ans.vert = verts;
+ ans.edge = edges;
+ ans.face = faces;
+#ifdef WITH_GMP
+ if (std::is_same<mpq_class, T>::value) {
+ ans.epsilon = T(0);
}
- if (nfaces > 0) {
- for (i = 0; i < nfaces; i++) {
- flen[i] = (int)faces[i].size();
- if (i == 0) {
- fstart[i] = 0;
- }
- else {
- fstart[i] = fstart[i - 1] + flen[i - 1];
- }
- }
- farr = (int *)MEM_malloc_arrayN(fstart[nfaces - 1] + flen[nfaces - 1], sizeof(int), __func__);
- for (i = 0; i < nfaces; i++) {
- for (j = 0; j < (int)faces[i].size(); j++) {
- farr[fstart[i] + j] = faces[i][j];
+ else {
+ ans.epsilon = T(0.00001);
+ }
+#else
+ ans.epsilon = T(0.00001);
+#endif
+ return ans;
+}
+
+/* Find an original index in a table mapping new to original.
+ * Return -1 if not found.
+ */
+static int get_orig_index(const Array<Vector<int>> &out_to_orig, int orig_index)
+{
+ int n = static_cast<int>(out_to_orig.size());
+ for (int i = 0; i < n; ++i) {
+ for (int orig : out_to_orig[i]) {
+ if (orig == orig_index) {
+ return i;
}
}
- add_input_faces(r_input, farr, fstart, flen, nfaces);
}
+ return -1;
}
-#if DO_FILE_TESTS
-static void fill_input_from_file(CDT_input *in, const char *filename)
+template<typename T> static double math_to_double(const T UNUSED(v))
{
- std::FILE *fp = std::fopen(filename, "rb");
- if (fp) {
- std::string contents;
- std::fseek(fp, 0, SEEK_END);
- contents.resize(std::ftell(fp));
- std::rewind(fp);
- std::fread(&contents[0], 1, contents.size(), fp);
- std::fclose(fp);
- fill_input_from_string(in, contents.c_str());
- }
- else {
- printf("couldn't open file %s\n", filename);
- }
+ BLI_assert(false); /* Need implementation for other type. */
+ return 0.0;
}
-#endif
-static void free_spec_arrays(CDT_input *in)
+template<> double math_to_double<double>(const double v)
{
- if (in->vert_coords) {
- MEM_freeN(in->vert_coords);
- }
- if (in->edges) {
- MEM_freeN(in->edges);
- }
- if (in->faces_len_table) {
- MEM_freeN(in->faces_len_table);
- MEM_freeN(in->faces_start_table);
- MEM_freeN(in->faces);
- }
+ return v;
}
-/* which output vert index goes with given input vertex? -1 if not found */
-static int get_output_vert_index(const CDT_result *r, int in_index)
+#ifdef WITH_GMP
+template<> double math_to_double<mpq_class>(const mpq_class v)
{
- int i, j;
+ return v.get_d();
+}
+#endif
- for (i = 0; i < r->verts_len; i++) {
- for (j = 0; j < r->verts_orig_len_table[i]; j++) {
- if (r->verts_orig[r->verts_orig_start_table[i] + j] == in_index) {
- return i;
- }
- }
- }
- return -1;
+template<typename T> static T math_abs(const T v);
+
+#ifdef WITH_GMP
+template<> mpq_class math_abs(const mpq_class v)
+{
+ return abs(v);
}
+#endif
-/* which output edge index is for given output vert indices? */
-static int get_edge(const CDT_result *r, int out_index_1, int out_index_2)
+template<> double math_abs(const double v)
{
- int i;
+ return fabs(v);
+}
- for (i = 0; i < r->edges_len; i++) {
- if ((r->edges[i][0] == out_index_1 && r->edges[i][1] == out_index_2) ||
- (r->edges[i][0] == out_index_2 && r->edges[i][1] == out_index_1)) {
+/* Find an output index corresponding to a given coordinate (appproximately).
+ * Return -1 if not found.
+ */
+template<typename T> int get_vertex_by_coord(const CDT_result<T> &out, double x, double y)
+{
+ int nv = static_cast<int>(out.vert.size());
+ for (int i = 0; i < nv; ++i) {
+ double vx = math_to_double(out.vert[i][0]);
+ double vy = math_to_double(out.vert[i][1]);
+ if (fabs(vx - x) <= 1e-5 && fabs(vy - y) <= 1e-5) {
return i;
}
}
return -1;
}
-/* return true if given output edge has given input edge id in its originals list */
-static bool out_edge_has_input_id(const CDT_result *r, int out_edge_index, int in_edge_index)
+/* Find an edge between two given output vertex indices. -1 if not found, */
+template<typename T>
+int get_output_edge_index(const CDT_result<T> &out, int out_index_1, int out_index_2)
{
- if (r->edges_orig == NULL) {
- return false;
- }
- if (out_edge_index < 0 || out_edge_index >= r->edges_len) {
- return false;
- }
- for (int i = 0; i < r->edges_orig_len_table[out_edge_index]; i++) {
- if (r->edges_orig[r->edges_orig_start_table[out_edge_index] + i] == in_edge_index) {
- return true;
+ int ne = static_cast<int>(out.edge.size());
+ for (int i = 0; i < ne; ++i) {
+ if ((out.edge[i].first == out_index_1 && out.edge[i].second == out_index_2) ||
+ (out.edge[i].first == out_index_2 && out.edge[i].second == out_index_1)) {
+ return i;
}
}
- return false;
+ return -1;
}
-/* which face is for given output vertex ngon? */
-static int get_face(const CDT_result *r, const int *out_indices, int nverts)
+template<typename T>
+bool output_edge_has_input_id(const CDT_result<T> &out, int out_edge_index, int in_edge_index)
{
- int f, cycle_start, k, fstart;
- bool ok;
+ return out_edge_index < static_cast<int>(out.edge_orig.size()) &&
+ out.edge_orig[out_edge_index].contains(in_edge_index);
+}
- if (r->faces_len == 0) {
- return -1;
- }
- for (f = 0; f < r->faces_len; f++) {
- if (r->faces_len_table[f] != nverts) {
+/* Which out face is for a give output vertex ngon? -1 if not found.
+ * Allow for cyclic shifts vertices of one poly vs the other.
+ */
+template<typename T> int get_output_face_index(const CDT_result<T> &out, const Array<int> &poly)
+{
+ int nf = static_cast<int>(out.face.size());
+ int npolyv = static_cast<int>(poly.size());
+ for (int f = 0; f < nf; ++f) {
+ if (out.face[f].size() != poly.size()) {
continue;
}
- fstart = r->faces_start_table[f];
- for (cycle_start = 0; cycle_start < nverts; cycle_start++) {
- ok = true;
- for (k = 0; ok && k < nverts; k++) {
- if (r->faces[fstart + ((cycle_start + k) % nverts)] != out_indices[k]) {
+ for (int cycle_start = 0; cycle_start < npolyv; ++cycle_start) {
+ bool ok = true;
+ for (int k = 0; ok && k < npolyv; ++k) {
+ if (out.face[f][(cycle_start + k) % npolyv] != poly[k]) {
ok = false;
}
}
@@ -235,240 +204,286 @@ static int get_face(const CDT_result *r, const int *out_indices, int nverts)
return -1;
}
-static int get_face_tri(const CDT_result *r, int out_index_1, int out_index_2, int out_index_3)
+template<typename T>
+int get_output_tri_index(const CDT_result<T> &out,
+ int out_index_1,
+ int out_index_2,
+ int out_index_3)
{
- int tri[3];
+ Array<int> tri{out_index_1, out_index_2, out_index_3};
+ return get_output_face_index(out, tri);
+}
- tri[0] = out_index_1;
- tri[1] = out_index_2;
- tri[2] = out_index_3;
- return get_face(r, tri, 3);
+template<typename T>
+bool output_face_has_input_id(const CDT_result<T> &out, int out_face_index, int in_face_index)
+{
+ return out_face_index < static_cast<int>(out.face_orig.size()) &&
+ out.face_orig[out_face_index].contains(in_face_index);
}
-/* return true if given otuput face has given input face id in its originals list */
-static bool out_face_has_input_id(const CDT_result *r, int out_face_index, int in_face_index)
+/* For debugging. */
+template<typename T> std::ostream &operator<<(std::ostream &os, const CDT_result<T> &r)
{
- if (r->faces_orig == NULL) {
- return false;
+ os << "\nRESULT\n";
+ os << r.vert.size() << " verts, " << r.edge.size() << " edges, " << r.face.size() << " faces\n";
+ os << "\nVERTS\n";
+ for (int i : r.vert.index_range()) {
+ os << "v" << i << " = " << r.vert[i] << "\n";
+ os << " orig: ";
+ for (int j : r.vert_orig[i].index_range()) {
+ os << r.vert_orig[i][j] << " ";
+ }
+ os << "\n";
}
- if (out_face_index < 0 || out_face_index >= r->faces_len) {
- return false;
+ os << "\nEDGES\n";
+ for (int i : r.edge.index_range()) {
+ os << "e" << i << " = (" << r.edge[i].first << ", " << r.edge[i].second << ")\n";
+ os << " orig: ";
+ for (int j : r.edge_orig[i].size()) {
+ os << r.edge_orig[i][j] << " ";
+ }
+ os << "\n";
}
- for (int i = 0; i < r->faces_orig_len_table[out_face_index]; i++) {
- if (r->faces_orig[r->faces_orig_start_table[out_face_index] + i] == in_face_index) {
- return true;
+ os << "\nFACES\n";
+ for (int i : r.face.index_range()) {
+ os << "f" << i << " = ";
+ for (int j : r.face[i].index_range()) {
+ os << r.face[i][j] << " ";
}
+ os << "\n";
+ os << " orig: ";
+ for (int j : r.face_orig[i].index_range()) {
+ os << r.face_orig[i][j] << " ";
+ }
+ os << "\n";
}
- return false;
+ return os;
}
-#if DO_FILE_TESTS
-/* for debugging */
-static void dump_result(CDT_result *r)
-{
- int i, j;
+static bool draw_append = false; /* Will be set to true after first call. */
- fprintf(stderr, "\nRESULT\n");
- fprintf(stderr,
- "verts_len=%d edges_len=%d faces_len=%d\n",
- r->verts_len,
- r->edges_len,
- r->faces_len);
- fprintf(stderr, "\nvert coords:\n");
- for (i = 0; i < r->verts_len; i++) {
- fprintf(stderr, "%d: (%f,%f)\n", i, r->vert_coords[i][0], r->vert_coords[i][1]);
+template<typename T>
+void graph_draw(const std::string &label,
+ const Array<vec2<T>> &verts,
+ const Array<std::pair<int, int>> &edges,
+ const Array<Vector<int>> &UNUSED(faces))
+{
+ /* Would like to use BKE_tempdir_base() here, but that brings in dependence on kernel library.
+ * This is just for developer debugging anyway, and should never be called in production Blender.
+ */
+#ifdef WIN32
+ constexpr const char *drawfile = "./cdt_test_draw.html";
+#else
+ constexpr const char *drawfile = "/tmp/cdt_test_draw.html";
+#endif
+ constexpr int max_draw_width = 1400;
+ constexpr int max_draw_height = 1000;
+ constexpr int thin_line = 1;
+ constexpr int vert_radius = 3;
+ constexpr bool draw_vert_labels = true;
+ constexpr bool draw_edge_labels = false;
+
+ if (verts.size() == 0) {
+ return;
}
- fprintf(stderr, "vert orig:\n");
- for (i = 0; i < r->verts_len; i++) {
- fprintf(stderr, "%d:", i);
- for (j = 0; j < r->verts_orig_len_table[i]; j++) {
- fprintf(stderr, " %d", r->verts_orig[r->verts_orig_start_table[i] + j]);
+ vec2<double> vmin(1e10, 1e10);
+ vec2<double> vmax(-1e10, -1e10);
+ for (const vec2<T> &v : verts) {
+ for (int i = 0; i < 2; ++i) {
+ double dvi = math_to_double(v[i]);
+ if (dvi < vmin[i]) {
+ vmin[i] = dvi;
+ }
+ if (dvi > vmax[i]) {
+ vmax[i] = dvi;
+ }
}
- fprintf(stderr, "\n");
}
- fprintf(stderr, "\nedges:\n");
- for (i = 0; i < r->edges_len; i++) {
- fprintf(stderr, "%d: (%d,%d)\n", i, r->edges[i][0], r->edges[i][1]);
+ double draw_margin = ((vmax.x - vmin.x) + (vmax.y - vmin.y)) * 0.05;
+ double minx = vmin.x - draw_margin;
+ double maxx = vmax.x + draw_margin;
+ double miny = vmin.y - draw_margin;
+ double maxy = vmax.y + draw_margin;
+
+ double width = maxx - minx;
+ double height = maxy - miny;
+ double aspect = height / width;
+ int view_width = max_draw_width;
+ int view_height = static_cast<int>(view_width * aspect);
+ if (view_height > max_draw_height) {
+ view_height = max_draw_height;
+ view_width = static_cast<int>(view_height / aspect);
}
- if (r->edges_orig) {
- fprintf(stderr, "edge orig:\n");
- for (i = 0; i < r->edges_len; i++) {
- fprintf(stderr, "%d:", i);
- for (j = 0; j < r->edges_orig_len_table[i]; j++) {
- fprintf(stderr, " %d", r->edges_orig[r->edges_orig_start_table[i] + j]);
- }
- fprintf(stderr, "\n");
- }
+ double scale = view_width / width;
+
+#define SX(x) ((math_to_double(x) - minx) * scale)
+#define SY(y) ((maxy - math_to_double(y)) * scale)
+
+ std::ofstream f;
+ if (draw_append) {
+ f.open(drawfile, std::ios_base::app);
}
- fprintf(stderr, "\nfaces:\n");
- for (i = 0; i < r->faces_len; i++) {
- fprintf(stderr, "%d: ", i);
- for (j = 0; j < r->faces_len_table[i]; j++) {
- fprintf(stderr, " %d", r->faces[r->faces_start_table[i] + j]);
+ else {
+ f.open(drawfile);
+ }
+ if (!f) {
+ std::cout << "Could not open file " << drawfile << "\n";
+ return;
+ }
+
+ f << "<div>" << label << "</div>\n<div>\n"
+ << "<svg version=\"1.1\" "
+ "xmlns=\"http://www.w3.org/2000/svg\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
+ "xml:space=\"preserve\"\n"
+ << "width=\"" << view_width << "\" height=\"" << view_height << "\">n";
+
+ for (const std::pair<int, int> &e : edges) {
+ const vec2<T> &uco = verts[e.first];
+ const vec2<T> &vco = verts[e.second];
+ int strokew = thin_line;
+ f << "<line fill=\"none\" stroke=\"black\" stroke-width=\"" << strokew << "\" x1=\""
+ << SX(uco[0]) << "\" y1=\"" << SY(uco[1]) << "\" x2=\"" << SX(vco[0]) << "\" y2=\""
+ << SY(vco[1]) << "\">\n";
+ f << " <title>[" << e.first << "][" << e.second << "]</title>\n";
+ f << "</line>\n";
+ if (draw_edge_labels) {
+ f << "<text x=\"" << SX(0.5 * (uco[0] + vco[0])) << "\" y=\"" << SY(0.5 * (uco[1] + vco[1]))
+ << "\" font-size=\"small\">";
+ f << "[" << e.first << "][" << e.second << "]</text>\n";
}
- fprintf(stderr, "\n");
}
- if (r->faces_orig) {
- fprintf(stderr, "face orig:\n");
- for (i = 0; i < r->faces_len; i++) {
- fprintf(stderr, "%d:", i);
- for (j = 0; j < r->faces_orig_len_table[i]; j++) {
- fprintf(stderr, " %d", r->faces_orig[r->faces_orig_start_table[i] + j]);
- }
- fprintf(stderr, "\n");
+
+ int i = 0;
+ for (const vec2<T> &vco : verts) {
+ f << "<circle fill=\"black\" cx=\"" << SX(vco[0]) << "\" cy=\"" << SY(vco[1]) << "\" r=\""
+ << vert_radius << "\">\n";
+ f << " <title>[" << i << "]" << vco << "</title>\n";
+ f << "</circle>\n";
+ if (draw_vert_labels) {
+ f << "<text x=\"" << SX(vco[0]) + vert_radius << "\" y=\"" << SY(vco[1]) - vert_radius
+ << "\" font-size=\"small\">[" << i << "]</text>\n";
}
+ ++i;
}
+
+ draw_append = true;
+#undef SX
+#undef SY
+}
+
+/* Should tests draw their output to an html file? */
+constexpr bool DO_DRAW = false;
+
+template<typename T> void expect_coord_near(const vec2<T> &testco, const vec2<T> &refco);
+
+#ifdef WITH_GMP
+template<>
+void expect_coord_near<mpq_class>(const vec2<mpq_class> &testco, const vec2<mpq_class> &refco)
+{
+ EXPECT_EQ(testco[0], refco[0]);
+ EXPECT_EQ(testco[0], refco[0]);
}
#endif
-#if DO_REGULAR_TESTS
-TEST(delaunay, Empty)
+template<> void expect_coord_near<double>(const vec2<double> &testco, const vec2<double> &refco)
{
- CDT_input in;
- CDT_result *out;
+ EXPECT_NEAR(testco[0], refco[0], 1e-5);
+ EXPECT_NEAR(testco[1], refco[1], 1e-5);
+}
+
+#if DO_CPP_TESTS
- fill_input_verts(&in, NULL, 0);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_NE((CDT_result *)NULL, out);
- EXPECT_EQ(out->verts_len, 0);
- EXPECT_EQ(out->edges_len, 0);
- EXPECT_EQ(out->faces_len, 0);
- BLI_delaunay_2d_cdt_free(out);
+template<typename T> void empty_test()
+{
+ CDT_input<T> in;
+
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(0, out.vert.size());
+ EXPECT_EQ(0, out.edge.size());
+ EXPECT_EQ(0, out.face.size());
+ EXPECT_EQ(0, out.vert_orig.size());
+ EXPECT_EQ(0, out.edge_orig.size());
+ EXPECT_EQ(0, out.face_orig.size());
}
-TEST(delaunay, OnePt)
+template<typename T> void onept_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(1 0 0
0.0 0.0
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 1);
- EXPECT_EQ(out->edges_len, 0);
- EXPECT_EQ(out->faces_len, 0);
- if (out->verts_len >= 1) {
- EXPECT_EQ(out->vert_coords[0][0], 0.0f);
- EXPECT_EQ(out->vert_coords[0][1], 0.0f);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 1);
+ EXPECT_EQ(out.edge.size(), 0);
+ EXPECT_EQ(out.face.size(), 0);
+ if (out.vert.size() >= 1) {
+ expect_coord_near<T>(out.vert[0], vec2<T>(0, 0));
}
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
}
-TEST(delaunay, TwoPt)
+template<typename T> void twopt_test()
{
- CDT_input in;
- CDT_result *out;
- int v0_out, v1_out, e0_out;
const char *spec = R"(2 0 0
0.0 -0.75
0.0 0.75
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 2);
- EXPECT_EQ(out->edges_len, 1);
- EXPECT_EQ(out->faces_len, 0);
- v0_out = get_output_vert_index(out, 0);
- v1_out = get_output_vert_index(out, 1);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 2);
+ EXPECT_EQ(out.edge.size(), 1);
+ EXPECT_EQ(out.face.size(), 0);
+ int v0_out = get_orig_index(out.vert_orig, 0);
+ int v1_out = get_orig_index(out.vert_orig, 1);
EXPECT_NE(v0_out, -1);
EXPECT_NE(v1_out, -1);
EXPECT_NE(v0_out, v1_out);
- if (out->verts_len >= 2) {
- EXPECT_NEAR(out->vert_coords[v0_out][0], 0.0, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v0_out][1], -0.75, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v1_out][0], 0.0, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v1_out][1], 0.75, in.epsilon);
+ if (out.vert.size() >= 1) {
+ expect_coord_near<T>(out.vert[v0_out], vec2<T>(0.0, -0.75));
+ expect_coord_near<T>(out.vert[v1_out], vec2<T>(0.0, 0.75));
}
- e0_out = get_edge(out, v0_out, v1_out);
+ int e0_out = get_output_edge_index(out, v0_out, v1_out);
EXPECT_EQ(e0_out, 0);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("TwoPt", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, ThreePt)
+template<typename T> void threept_test()
{
- CDT_input in;
- CDT_result *out;
- int v0_out, v1_out, v2_out;
- int e0_out, e1_out, e2_out;
- int f0_out;
const char *spec = R"(3 0 0
-0.1 -0.75
0.1 0.75
0.5 0.5
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 3);
- EXPECT_EQ(out->edges_len, 3);
- EXPECT_EQ(out->faces_len, 1);
- v0_out = get_output_vert_index(out, 0);
- v1_out = get_output_vert_index(out, 1);
- v2_out = get_output_vert_index(out, 2);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 3);
+ EXPECT_EQ(out.edge.size(), 3);
+ EXPECT_EQ(out.face.size(), 1);
+ int v0_out = get_orig_index(out.vert_orig, 0);
+ int v1_out = get_orig_index(out.vert_orig, 1);
+ int v2_out = get_orig_index(out.vert_orig, 2);
EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1);
EXPECT_TRUE(v0_out != v1_out && v0_out != v2_out && v1_out != v2_out);
- e0_out = get_edge(out, v0_out, v1_out);
- e1_out = get_edge(out, v1_out, v2_out);
- e2_out = get_edge(out, v2_out, v0_out);
+ int e0_out = get_output_edge_index(out, v0_out, v1_out);
+ int e1_out = get_output_edge_index(out, v1_out, v2_out);
+ int e2_out = get_output_edge_index(out, v2_out, v0_out);
EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1);
EXPECT_TRUE(e0_out != e1_out && e0_out != e2_out && e1_out != e2_out);
- f0_out = get_face_tri(out, v0_out, v2_out, v1_out);
+ int f0_out = get_output_tri_index(out, v0_out, v2_out, v1_out);
EXPECT_EQ(f0_out, 0);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
-}
-
-TEST(delaunay, ThreePtsMerge)
-{
- CDT_input in;
- CDT_result *out;
- int v0_out, v1_out, v2_out;
- const char *spec = R"(3 0 0
- -0.05 -0.05
- 0.05 -0.05
- 0.0 0.03660254
- )";
-
- /* First with epsilon such that points are within that distance of each other */
- fill_input_from_string(&in, spec);
- in.epsilon = 0.21f;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 1);
- EXPECT_EQ(out->edges_len, 0);
- EXPECT_EQ(out->faces_len, 0);
- v0_out = get_output_vert_index(out, 0);
- v1_out = get_output_vert_index(out, 1);
- v2_out = get_output_vert_index(out, 2);
- EXPECT_EQ(v0_out, 0);
- EXPECT_EQ(v1_out, 0);
- EXPECT_EQ(v2_out, 0);
- BLI_delaunay_2d_cdt_free(out);
- /* Now with epsilon such that points are farther away than that.
- * Note that the points won't merge with each other if distance is
- * less than .01, but that they may merge with points on the Delaunay
- * triangulation lines, so make epsilon even smaller to avoid that for
- * this test.
- */
- in.epsilon = 0.05f;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 3);
- EXPECT_EQ(out->edges_len, 3);
- EXPECT_EQ(out->faces_len, 1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("ThreePt", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, MixedPts)
+template<typename T> void mixedpts_test()
{
- CDT_input in;
- CDT_result *out;
- int v0_out, v1_out, v2_out, v3_out;
- int e0_out, e1_out, e2_out;
+ /* Edges form a chain of length 3. */
const char *spec = R"(4 3 0
0.0 0.0
-0.5 -0.5
@@ -479,135 +494,129 @@ TEST(delaunay, MixedPts)
2 3
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 6);
- v0_out = get_output_vert_index(out, 0);
- v1_out = get_output_vert_index(out, 1);
- v2_out = get_output_vert_index(out, 2);
- v3_out = get_output_vert_index(out, 3);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 6);
+ int v0_out = get_orig_index(out.vert_orig, 0);
+ int v1_out = get_orig_index(out.vert_orig, 1);
+ int v2_out = get_orig_index(out.vert_orig, 2);
+ int v3_out = get_orig_index(out.vert_orig, 3);
EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1);
- e0_out = get_edge(out, v0_out, v1_out);
- e1_out = get_edge(out, v1_out, v2_out);
- e2_out = get_edge(out, v2_out, v3_out);
+ int e0_out = get_output_edge_index(out, v0_out, v1_out);
+ int e1_out = get_output_edge_index(out, v1_out, v2_out);
+ int e2_out = get_output_edge_index(out, v2_out, v3_out);
EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1);
- EXPECT_TRUE(out_edge_has_input_id(out, e0_out, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1_out, 1));
- EXPECT_TRUE(out_edge_has_input_id(out, e2_out, 2));
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ EXPECT_TRUE(output_edge_has_input_id(out, e0_out, 0));
+ EXPECT_TRUE(output_edge_has_input_id(out, e1_out, 1));
+ EXPECT_TRUE(output_edge_has_input_id(out, e2_out, 2));
+ if (DO_DRAW) {
+ graph_draw<T>("MixedPts", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, Quad0)
+template<typename T> void quad0_test()
{
- CDT_input in;
- CDT_result *out;
- int e_diag_out;
const char *spec = R"(4 0 0
0.0 1.0
1.0 0.0
2.0 0.1
2.25 0.5
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- e_diag_out = get_edge(out, 1, 3);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ int e_diag_out = get_output_edge_index(out, 1, 3);
EXPECT_NE(e_diag_out, -1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("Quad0", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, Quad1)
+template<typename T> void quad1_test()
{
- CDT_input in;
- CDT_result *out;
- int e_diag_out;
const char *spec = R"(4 0 0
0.0 0.0
0.9 -1.0
2.0 0.0
0.9 3.0
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- e_diag_out = get_edge(out, 0, 2);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ int e_diag_out = get_output_edge_index(out, 0, 2);
EXPECT_NE(e_diag_out, -1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("Quad1", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, Quad2)
+template<typename T> void quad2_test()
{
- CDT_input in;
- CDT_result *out;
- int e_diag_out;
const char *spec = R"(4 0 0
0.5 0.0
0.15 0.2
0.3 0.4
.45 0.35
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- e_diag_out = get_edge(out, 1, 3);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ int e_diag_out = get_output_edge_index(out, 1, 3);
EXPECT_NE(e_diag_out, -1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("Quad2", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, Quad3)
+template<typename T> void quad3_test()
{
- CDT_input in;
- CDT_result *out;
- int e_diag_out;
const char *spec = R"(4 0 0
0.5 0.0
0.0 0.0
0.3 0.4
.45 0.35
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- e_diag_out = get_edge(out, 0, 2);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ int e_diag_out = get_output_edge_index(out, 0, 2);
EXPECT_NE(e_diag_out, -1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("Quad3", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, Quad4)
+template<typename T> void quad4_test()
{
- CDT_input in;
- CDT_result *out;
- int e_diag_out;
const char *spec = R"(4 0 0
1.0 1.0
0.0 0.0
1.0 -3.0
0.0 1.0
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- e_diag_out = get_edge(out, 0, 1);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ int e_diag_out = get_output_edge_index(out, 0, 1);
EXPECT_NE(e_diag_out, -1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ if (DO_DRAW) {
+ graph_draw<T>("Quad4", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, LineInSquare)
+template<typename T> void lineinsquare_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(6 1 1
-0.5 -0.5
0.5 -0.5
@@ -618,20 +627,24 @@ TEST(delaunay, LineInSquare)
4 5
0 1 3 2
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 6);
- EXPECT_EQ(out->faces_len, 1);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 6);
+ EXPECT_EQ(out.face.size(), 6);
+ if (DO_DRAW) {
+ graph_draw<T>("LineInSquare - full", out.vert, out.edge, out.face);
+ }
+ CDT_result<T> out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out2.vert.size(), 6);
+ EXPECT_EQ(out2.face.size(), 1);
+ if (DO_DRAW) {
+ graph_draw<T>("LineInSquare - constraints", out2.vert, out2.edge, out2.face);
+ }
}
-TEST(delaunay, CrossSegs)
+template<typename T> void crosssegs_test()
{
- CDT_input in;
- CDT_result *out;
- int v0_out, v1_out, v2_out, v3_out, v_intersect;
- int i;
const char *spec = R"(4 2 0
-0.5 0.0
0.5 0.0
@@ -641,36 +654,37 @@ TEST(delaunay, CrossSegs)
2 3
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 5);
- EXPECT_EQ(out->edges_len, 8);
- EXPECT_EQ(out->faces_len, 4);
- v0_out = get_output_vert_index(out, 0);
- v1_out = get_output_vert_index(out, 1);
- v2_out = get_output_vert_index(out, 2);
- v3_out = get_output_vert_index(out, 3);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 5);
+ EXPECT_EQ(out.edge.size(), 8);
+ EXPECT_EQ(out.face.size(), 4);
+ int v0_out = get_orig_index(out.vert_orig, 0);
+ int v1_out = get_orig_index(out.vert_orig, 1);
+ int v2_out = get_orig_index(out.vert_orig, 2);
+ int v3_out = get_orig_index(out.vert_orig, 3);
EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1);
- v_intersect = -1;
- for (i = 0; i < out->verts_len; i++) {
- if (i != v0_out && i != v1_out && i != v2_out && i != v3_out) {
- EXPECT_EQ(v_intersect, -1);
- v_intersect = i;
+ if (out.vert.size() == 5) {
+ int v_intersect = -1;
+ for (int i = 0; i < 5; i++) {
+ if (i != v0_out && i != v1_out && i != v2_out && i != v3_out) {
+ EXPECT_EQ(v_intersect, -1);
+ v_intersect = i;
+ }
+ }
+ EXPECT_NE(v_intersect, -1);
+ if (v_intersect != -1) {
+ expect_coord_near<T>(out.vert[v_intersect], vec2<T>(0, 0));
}
}
- EXPECT_NE(v_intersect, -1);
- if (v_intersect != -1) {
- EXPECT_NEAR(out->vert_coords[v_intersect][0], 0.0f, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v_intersect][1], 0.0f, in.epsilon);
+ if (DO_DRAW) {
+ graph_draw<T>("CrossSegs", out.vert, out.edge, out.face);
}
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
}
-TEST(delaunay, DiamondCross)
+template<typename T> void diamondcross_test()
{
- CDT_input in;
- CDT_result *out;
+ /* Diamond with constraint edge from top to bottom. Some dup verts. */
const char *spec = R"(7 5 0
0.0 0.0
1.0 3.0
@@ -686,24 +700,18 @@ TEST(delaunay, DiamondCross)
5 6
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- EXPECT_EQ(out->faces_len, 2);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 4);
+ EXPECT_EQ(out.edge.size(), 5);
+ EXPECT_EQ(out.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("DiamondCross", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, TwoDiamondsCrossed)
+template<typename T> void twodiamondscross_test()
{
- CDT_input in;
- CDT_result *out;
- /* Input has some repetition of vertices, on purpose */
- int e[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7}, {7, 8}, {8, 9}, {10, 11}};
- int v_out[12];
- int e_out[9], e_cross_1, e_cross_2, e_cross_3;
- int i;
const char *spec = R"(12 9 0
0.0 0.0
1.0 2.0
@@ -728,40 +736,43 @@ TEST(delaunay, TwoDiamondsCrossed)
10 11
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 8);
- EXPECT_EQ(out->edges_len, 15);
- EXPECT_EQ(out->faces_len, 8);
- for (i = 0; i < 12; i++) {
- v_out[i] = get_output_vert_index(out, i);
- EXPECT_NE(v_out[i], -1);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 8);
+ EXPECT_EQ(out.edge.size(), 15);
+ EXPECT_EQ(out.face.size(), 8);
+ if (out.vert.size() == 8 && out.edge.size() == 15 && out.face.size() == 8) {
+ int v_out[12];
+ for (int i = 0; i < 12; ++i) {
+ v_out[i] = get_orig_index(out.vert_orig, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ EXPECT_EQ(v_out[0], v_out[4]);
+ EXPECT_EQ(v_out[0], v_out[10]);
+ EXPECT_EQ(v_out[5], v_out[9]);
+ EXPECT_EQ(v_out[7], v_out[11]);
+ int e_out[9];
+ for (int i = 0; i < 8; ++i) {
+ e_out[i] = get_output_edge_index(out, v_out[in.edge[i].first], v_out[in.edge[i].second]);
+ EXPECT_NE(e_out[i], -1);
+ }
+ /* there won't be a single edge for the input cross edge, but rather 3 */
+ EXPECT_EQ(get_output_edge_index(out, v_out[10], v_out[11]), -1);
+ int e_cross_1 = get_output_edge_index(out, v_out[0], v_out[2]);
+ int e_cross_2 = get_output_edge_index(out, v_out[2], v_out[5]);
+ int e_cross_3 = get_output_edge_index(out, v_out[5], v_out[7]);
+ EXPECT_TRUE(e_cross_1 != -1 && e_cross_2 != -1 && e_cross_3 != -1);
+ EXPECT_TRUE(output_edge_has_input_id(out, e_cross_1, 8));
+ EXPECT_TRUE(output_edge_has_input_id(out, e_cross_2, 8));
+ EXPECT_TRUE(output_edge_has_input_id(out, e_cross_3, 8));
}
- EXPECT_EQ(v_out[0], v_out[4]);
- EXPECT_EQ(v_out[0], v_out[10]);
- EXPECT_EQ(v_out[5], v_out[9]);
- EXPECT_EQ(v_out[7], v_out[11]);
- for (i = 0; i < 8; i++) {
- e_out[i] = get_edge(out, v_out[e[i][0]], v_out[e[i][1]]);
- EXPECT_NE(e_out[i], -1);
+ if (DO_DRAW) {
+ graph_draw<T>("TwoDiamondsCross", out.vert, out.edge, out.face);
}
- /* there won't be a single edge for the input cross edge, but rather 3 */
- EXPECT_EQ(get_edge(out, v_out[10], v_out[11]), -1);
- e_cross_1 = get_edge(out, v_out[0], v_out[2]);
- e_cross_2 = get_edge(out, v_out[2], v_out[5]);
- e_cross_3 = get_edge(out, v_out[5], v_out[7]);
- EXPECT_TRUE(e_cross_1 != -1 && e_cross_2 != -1 && e_cross_3 != -1);
- EXPECT_TRUE(out_edge_has_input_id(out, e_cross_1, 8));
- EXPECT_TRUE(out_edge_has_input_id(out, e_cross_2, 8));
- EXPECT_TRUE(out_edge_has_input_id(out, e_cross_3, 8));
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
-}
-
-TEST(delaunay, ManyCross)
-{
- CDT_input in;
- CDT_result *out;
+}
+
+template<typename T> void manycross_test()
+{
/* Input has some repetition of vertices, on purpose */
const char *spec = R"(27 21 0
0.0 0.0
@@ -814,21 +825,18 @@ TEST(delaunay, ManyCross)
25 26
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 19);
- EXPECT_EQ(out->edges_len, 46);
- EXPECT_EQ(out->faces_len, 28);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 19);
+ EXPECT_EQ(out.edge.size(), 46);
+ EXPECT_EQ(out.face.size(), 28);
+ if (DO_DRAW) {
+ graph_draw<T>("ManyCross", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, TwoFace)
+template<typename T> void twoface_test()
{
- CDT_input in;
- CDT_result *out;
- int v_out[6], f0_out, f1_out, e0_out, e1_out, e2_out;
- int i;
const char *spec = R"(6 0 2
0.0 0.0
1.0 0.0
@@ -840,40 +848,116 @@ TEST(delaunay, TwoFace)
3 4 5
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 6);
- EXPECT_EQ(out->edges_len, 9);
- EXPECT_EQ(out->faces_len, 4);
- for (i = 0; i < 6; i++) {
- v_out[i] = get_output_vert_index(out, i);
- EXPECT_NE(v_out[i], -1);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 6);
+ EXPECT_EQ(out.edge.size(), 9);
+ EXPECT_EQ(out.face.size(), 4);
+ if (out.vert.size() == 6 && out.edge.size() == 9 && out.face.size() == 4) {
+ int v_out[6];
+ for (int i = 0; i < 6; i++) {
+ v_out[i] = get_orig_index(out.vert_orig, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ int f0_out = get_output_tri_index(out, v_out[0], v_out[1], v_out[2]);
+ int f1_out = get_output_tri_index(out, v_out[3], v_out[4], v_out[5]);
+ EXPECT_NE(f0_out, -1);
+ EXPECT_NE(f1_out, -1);
+ int e0_out = get_output_edge_index(out, v_out[0], v_out[1]);
+ int e1_out = get_output_edge_index(out, v_out[1], v_out[2]);
+ int e2_out = get_output_edge_index(out, v_out[2], v_out[0]);
+ EXPECT_NE(e0_out, -1);
+ EXPECT_NE(e1_out, -1);
+ EXPECT_NE(e2_out, -1);
+ EXPECT_TRUE(output_edge_has_input_id(out, e0_out, out.face_edge_offset + 0));
+ EXPECT_TRUE(output_edge_has_input_id(out, e1_out, out.face_edge_offset + 1));
+ EXPECT_TRUE(output_edge_has_input_id(out, e2_out, out.face_edge_offset + 2));
+ EXPECT_TRUE(output_face_has_input_id(out, f0_out, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f1_out, 1));
+ }
+ if (DO_DRAW) {
+ graph_draw<T>("TwoFace", out.vert, out.edge, out.face);
}
- f0_out = get_face(out, &v_out[0], 3);
- f1_out = get_face(out, &v_out[3], 3);
- EXPECT_NE(f0_out, -1);
- EXPECT_NE(f1_out, -1);
- e0_out = get_edge(out, v_out[0], v_out[1]);
- e1_out = get_edge(out, v_out[1], v_out[2]);
- e2_out = get_edge(out, v_out[2], v_out[0]);
- EXPECT_NE(e0_out, -1);
- EXPECT_NE(e1_out, -1);
- EXPECT_NE(e2_out, -1);
- EXPECT_TRUE(out_edge_has_input_id(out, e0_out, out->face_edge_offset + 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1_out, out->face_edge_offset + 1));
- EXPECT_TRUE(out_edge_has_input_id(out, e2_out, out->face_edge_offset + 2));
- EXPECT_TRUE(out_face_has_input_id(out, f0_out, 0));
- EXPECT_TRUE(out_face_has_input_id(out, f1_out, 1));
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
-}
-
-TEST(delaunay, OverlapFaces)
-{
- CDT_input in;
- CDT_result *out;
- int v_out[12], v_int1, v_int2, f0_out, f1_out, f2_out;
- int i;
+}
+
+template<typename T> void twoface2_test()
+{
+ const char *spec = R"(6 0 2
+ 0.0 0.0
+ 4.0 4.0
+ -4.0 2.0
+ 3.0 0.0
+ 3.0 6.0
+ -1.0 2.0
+ 0 1 2
+ 3 4 5
+ )";
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_INSIDE);
+ EXPECT_EQ(out.vert.size(), 10);
+ EXPECT_EQ(out.edge.size(), 18);
+ EXPECT_EQ(out.face.size(), 9);
+ if (out.vert.size() == 10 && out.edge.size() == 18 && out.face.size() == 9) {
+ /* Input verts have no dups, so expect output ones match input ones. */
+ for (int i = 0; i < 6; i++) {
+ EXPECT_EQ(get_orig_index(out.vert_orig, i), i);
+ }
+ int v6 = get_vertex_by_coord(out, 3.0, 3.0);
+ EXPECT_NE(v6, -1);
+ int v7 = get_vertex_by_coord(out, 3.0, 3.75);
+ EXPECT_NE(v7, -1);
+ int v8 = get_vertex_by_coord(out, 0.0, 3.0);
+ EXPECT_NE(v8, -1);
+ int v9 = get_vertex_by_coord(out, 1.0, 1.0);
+ EXPECT_NE(v9, -1);
+ /* f0 to f3 should be triangles part of input face 0, not part of input face 1. */
+ int f0 = get_output_tri_index(out, 0, 9, 5);
+ EXPECT_NE(f0, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f0, 0));
+ EXPECT_FALSE(output_face_has_input_id(out, f0, 1));
+ int f1 = get_output_tri_index(out, 0, 5, 2);
+ EXPECT_NE(f1, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f1, 0));
+ EXPECT_FALSE(output_face_has_input_id(out, f1, 1));
+ int f2 = get_output_tri_index(out, 2, 5, 8);
+ EXPECT_NE(f2, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f2, 0));
+ EXPECT_FALSE(output_face_has_input_id(out, f2, 1));
+ int f3 = get_output_tri_index(out, 6, 1, 7);
+ EXPECT_NE(f3, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f3, 0));
+ EXPECT_FALSE(output_face_has_input_id(out, f3, 1));
+ /* f4 and f5 should be triangles part of input face 1, not part of input face 0. */
+ int f4 = get_output_tri_index(out, 8, 7, 4);
+ EXPECT_NE(f4, -1);
+ EXPECT_FALSE(output_face_has_input_id(out, f4, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f4, 1));
+ int f5 = get_output_tri_index(out, 3, 6, 9);
+ EXPECT_NE(f5, -1);
+ EXPECT_FALSE(output_face_has_input_id(out, f5, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f5, 1));
+ /* f6 to f8 should be triangles part of both input faces. */
+ int f6 = get_output_tri_index(out, 5, 9, 6);
+ EXPECT_NE(f6, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f6, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f6, 1));
+ int f7 = get_output_tri_index(out, 5, 6, 7);
+ EXPECT_NE(f7, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f7, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f7, 1));
+ int f8 = get_output_tri_index(out, 5, 7, 8);
+ EXPECT_NE(f8, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f8, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f8, 1));
+ }
+ if (DO_DRAW) {
+ graph_draw<T>("TwoFace2", out.vert, out.edge, out.face);
+ }
+}
+
+template<typename T> void overlapfaces_test()
+{
const char *spec = R"(12 0 3
0.0 0.0
1.0 0.0
@@ -892,64 +976,69 @@ TEST(delaunay, OverlapFaces)
8 9 10 11
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- EXPECT_EQ(out->verts_len, 14);
- EXPECT_EQ(out->edges_len, 33);
- EXPECT_EQ(out->faces_len, 20);
- for (i = 0; i < 12; i++) {
- v_out[i] = get_output_vert_index(out, i);
- EXPECT_NE(v_out[i], -1);
- }
- v_int1 = 12;
- v_int2 = 13;
- if (out->verts_len > 13) {
- if (fabsf(out->vert_coords[v_int1][0] - 1.0f) > in.epsilon) {
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 14);
+ EXPECT_EQ(out.edge.size(), 33);
+ EXPECT_EQ(out.face.size(), 20);
+ if (out.vert.size() == 14 && out.edge.size() == 33 && out.face.size() == 20) {
+ int v_out[12];
+ for (int i = 0; i < 12; i++) {
+ v_out[i] = get_orig_index(out.vert_orig, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ int v_int1 = 12;
+ int v_int2 = 13;
+ T x = out.vert[v_int1][0] - T(1);
+ if (math_abs(x) > in.epsilon) {
v_int1 = 13;
v_int2 = 12;
}
- EXPECT_NEAR(out->vert_coords[v_int1][0], 1.0, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v_int1][1], 0.5, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v_int2][0], 0.5, in.epsilon);
- EXPECT_NEAR(out->vert_coords[v_int2][1], 1.0, in.epsilon);
- EXPECT_EQ(out->verts_orig_len_table[v_int1], 0);
- EXPECT_EQ(out->verts_orig_len_table[v_int2], 0);
+ expect_coord_near<T>(out.vert[v_int1], vec2<T>(1, 0.5));
+ expect_coord_near<T>(out.vert[v_int2], vec2<T>(0.5, 1));
+ EXPECT_EQ(out.vert_orig[v_int1].size(), 0);
+ EXPECT_EQ(out.vert_orig[v_int2].size(), 0);
+ int f0_out = get_output_tri_index(out, v_out[1], v_int1, v_out[4]);
+ EXPECT_NE(f0_out, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f0_out, 0));
+ int f1_out = get_output_tri_index(out, v_out[4], v_int1, v_out[2]);
+ EXPECT_NE(f1_out, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f1_out, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f1_out, 1));
+ int f2_out = get_output_tri_index(out, v_out[8], v_out[9], v_out[10]);
+ if (f2_out == -1) {
+ f2_out = get_output_tri_index(out, v_out[8], v_out[9], v_out[11]);
+ }
+ EXPECT_NE(f2_out, -1);
+ EXPECT_TRUE(output_face_has_input_id(out, f2_out, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f2_out, 2));
}
- f0_out = get_face_tri(out, v_out[1], v_int1, v_out[4]);
- EXPECT_NE(f0_out, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f0_out, 0));
- f1_out = get_face_tri(out, v_out[4], v_int1, v_out[2]);
- EXPECT_NE(f1_out, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f1_out, 0));
- EXPECT_TRUE(out_face_has_input_id(out, f1_out, 1));
- f2_out = get_face_tri(out, v_out[8], v_out[9], v_out[10]);
- if (f2_out == -1) {
- f2_out = get_face_tri(out, v_out[8], v_out[9], v_out[11]);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - full", out.vert, out.edge, out.face);
}
- EXPECT_NE(f2_out, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f2_out, 0));
- EXPECT_TRUE(out_face_has_input_id(out, f2_out, 2));
- BLI_delaunay_2d_cdt_free(out);
- /* Different output types */
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_INSIDE);
- EXPECT_EQ(out->faces_len, 18);
- BLI_delaunay_2d_cdt_free(out);
+ /* Different output types. */
+ CDT_result<T> out2 = delaunay_2d_calc(in, CDT_INSIDE);
+ EXPECT_EQ(out2.face.size(), 18);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - inside", out2.vert, out2.edge, out2.face);
+ }
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->faces_len, 4);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_result<T> out3 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out3.face.size(), 4);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - constraints", out3.vert, out3.edge, out3.face);
+ }
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
- EXPECT_EQ(out->faces_len, 5);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out4.face.size(), 5);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - valid bmesh", out4.vert, out4.edge, out4.face);
+ }
}
-TEST(delaunay, TwoSquaresOverlap)
+template<typename T> void twosquaresoverlap_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(8 0 2
1.0 -1.0
-1.0 -1.0
@@ -963,22 +1052,18 @@ TEST(delaunay, TwoSquaresOverlap)
3 2 1 0
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
- EXPECT_EQ(out->verts_len, 10);
- EXPECT_EQ(out->edges_len, 12);
- EXPECT_EQ(out->faces_len, 3);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out.vert.size(), 10);
+ EXPECT_EQ(out.edge.size(), 12);
+ EXPECT_EQ(out.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("TwoSquaresOverlap", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, TwoFaceEdgeOverlap)
+template<typename T> void twofaceedgeoverlap_test()
{
- CDT_input in;
- CDT_result *out;
- int i, v_out[6], v_int;
- int e01, e1i, ei2, e20, e24, e4i, ei0;
- int f02i, f24i, f10i;
const char *spec = R"(6 0 2
5.657 0.0
-1.414 -5.831
@@ -990,56 +1075,57 @@ TEST(delaunay, TwoFaceEdgeOverlap)
5 4 3
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 5);
- EXPECT_EQ(out->edges_len, 7);
- EXPECT_EQ(out->faces_len, 3);
- if (out->verts_len == 5 && out->edges_len == 7 && out->faces_len == 3) {
- v_int = 4;
- for (i = 0; i < 6; i++) {
- v_out[i] = get_output_vert_index(out, i);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out.vert.size(), 5);
+ EXPECT_EQ(out.edge.size(), 7);
+ EXPECT_EQ(out.face.size(), 3);
+ if (out.vert.size() == 5 && out.edge.size() == 7 && out.face.size() == 3) {
+ int v_int = 4;
+ int v_out[6];
+ for (int i = 0; i < 6; i++) {
+ v_out[i] = get_orig_index(out.vert_orig, i);
EXPECT_NE(v_out[i], -1);
EXPECT_NE(v_out[i], v_int);
}
EXPECT_EQ(v_out[0], v_out[3]);
EXPECT_EQ(v_out[2], v_out[5]);
- e01 = get_edge(out, v_out[0], v_out[1]);
- EXPECT_TRUE(out_edge_has_input_id(out, e01, 1));
- e1i = get_edge(out, v_out[1], v_int);
- EXPECT_TRUE(out_edge_has_input_id(out, e1i, 0));
- ei2 = get_edge(out, v_int, v_out[2]);
- EXPECT_TRUE(out_edge_has_input_id(out, ei2, 0));
- e20 = get_edge(out, v_out[2], v_out[0]);
- EXPECT_TRUE(out_edge_has_input_id(out, e20, 2));
- EXPECT_TRUE(out_edge_has_input_id(out, e20, 5));
- e24 = get_edge(out, v_out[2], v_out[4]);
- EXPECT_TRUE(out_edge_has_input_id(out, e24, 3));
- e4i = get_edge(out, v_out[4], v_int);
- EXPECT_TRUE(out_edge_has_input_id(out, e4i, 4));
- ei0 = get_edge(out, v_int, v_out[0]);
- EXPECT_TRUE(out_edge_has_input_id(out, ei0, 4));
- f02i = get_face_tri(out, v_out[0], v_out[2], v_int);
+ int e01 = get_output_edge_index(out, v_out[0], v_out[1]);
+ int foff = out.face_edge_offset;
+ EXPECT_TRUE(output_edge_has_input_id(out, e01, foff + 1));
+ int e1i = get_output_edge_index(out, v_out[1], v_int);
+ EXPECT_TRUE(output_edge_has_input_id(out, e1i, foff + 0));
+ int ei2 = get_output_edge_index(out, v_int, v_out[2]);
+ EXPECT_TRUE(output_edge_has_input_id(out, ei2, foff + 0));
+ int e20 = get_output_edge_index(out, v_out[2], v_out[0]);
+ EXPECT_TRUE(output_edge_has_input_id(out, e20, foff + 2));
+ EXPECT_TRUE(output_edge_has_input_id(out, e20, 2 * foff + 2));
+ int e24 = get_output_edge_index(out, v_out[2], v_out[4]);
+ EXPECT_TRUE(output_edge_has_input_id(out, e24, 2 * foff + 0));
+ int e4i = get_output_edge_index(out, v_out[4], v_int);
+ EXPECT_TRUE(output_edge_has_input_id(out, e4i, 2 * foff + 1));
+ int ei0 = get_output_edge_index(out, v_int, v_out[0]);
+ EXPECT_TRUE(output_edge_has_input_id(out, ei0, 2 * foff + 1));
+ int f02i = get_output_tri_index(out, v_out[0], v_out[2], v_int);
EXPECT_NE(f02i, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f02i, 0));
- EXPECT_TRUE(out_face_has_input_id(out, f02i, 1));
- f24i = get_face_tri(out, v_out[2], v_out[4], v_int);
+ EXPECT_TRUE(output_face_has_input_id(out, f02i, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, f02i, 1));
+ int f24i = get_output_tri_index(out, v_out[2], v_out[4], v_int);
EXPECT_NE(f24i, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f24i, 1));
- EXPECT_FALSE(out_face_has_input_id(out, f24i, 0));
- f10i = get_face_tri(out, v_out[1], v_out[0], v_int);
+ EXPECT_TRUE(output_face_has_input_id(out, f24i, 1));
+ EXPECT_FALSE(output_face_has_input_id(out, f24i, 0));
+ int f10i = get_output_tri_index(out, v_out[1], v_out[0], v_int);
EXPECT_NE(f10i, -1);
- EXPECT_TRUE(out_face_has_input_id(out, f10i, 0));
- EXPECT_FALSE(out_face_has_input_id(out, f10i, 1));
+ EXPECT_TRUE(output_face_has_input_id(out, f10i, 0));
+ EXPECT_FALSE(output_face_has_input_id(out, f10i, 1));
+ }
+ if (DO_DRAW) {
+ graph_draw<T>("TwoFaceEdgeOverlap", out.vert, out.edge, out.face);
}
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
}
-TEST(delaunay, TriInTri)
+template<typename T> void triintri_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(6 0 2
-5.65685 0.0
1.41421 -5.83095
@@ -1051,19 +1137,18 @@ TEST(delaunay, TriInTri)
3 4 5
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
- EXPECT_EQ(out->verts_len, 6);
- EXPECT_EQ(out->edges_len, 8);
- EXPECT_EQ(out->faces_len, 3);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out.vert.size(), 6);
+ EXPECT_EQ(out.edge.size(), 8);
+ EXPECT_EQ(out.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("TriInTri", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, DiamondInSquare)
+template<typename T> void diamondinsquare_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(8 0 2
0.0 0.0
1.0 0.0
@@ -1076,19 +1161,19 @@ TEST(delaunay, DiamondInSquare)
0 1 2 3
4 5 6 7
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
- EXPECT_EQ(out->verts_len, 8);
- EXPECT_EQ(out->edges_len, 10);
- EXPECT_EQ(out->faces_len, 3);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out.vert.size(), 8);
+ EXPECT_EQ(out.edge.size(), 10);
+ EXPECT_EQ(out.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("DiamondInSquare", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, DiamondInSquareWire)
+template<typename T> void diamondinsquarewire_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(8 8 0
0.0 0.0
1.0 0.0
@@ -1107,67 +1192,19 @@ TEST(delaunay, DiamondInSquareWire)
6 7
7 4
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 8);
- EXPECT_EQ(out->edges_len, 8);
- EXPECT_EQ(out->faces_len, 2);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
-}
-
-TEST(delaunay, TinyEdge)
-{
- CDT_input in;
- CDT_result *out;
- /* An intersect with triangle would be at (0.8, 0.2). */
- const char *spec = R"(4 1 1
- 0.0 0.0
- 1.0 0.0
- 0.5 0.5
- 0.84 0.21
- 0 3
- 0 1 2
- )";
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 5);
- EXPECT_EQ(out->faces_len, 2);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
-}
-TEST(delaunay, TinyEdge2)
-{
- CDT_input in;
- CDT_result *out;
- /* An intersect with triangle would be at (0.8, 0.2). */
- const char *spec = R"(6 1 1
- 0.0 0.0
- 0.2 -0.2
- 1.0 0.0
- 0.5 0.5
- 0.2 0.4
- 0.84 0.21
- 0 5
- 0 1 2 3 4
- )";
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 6);
- EXPECT_EQ(out->edges_len, 7);
- EXPECT_EQ(out->faces_len, 2);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out.vert.size(), 8);
+ EXPECT_EQ(out.edge.size(), 8);
+ EXPECT_EQ(out.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("DiamondInSquareWire", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, repeatededge)
+template<typename T> void repeatedge_test()
{
- CDT_input in;
- CDT_result *out;
const char *spec = R"(5 3 0
0.0 0.0
0.0 1.0
@@ -1178,256 +1215,316 @@ TEST(delaunay, repeatededge)
2 3
2 3
)";
- fill_input_from_string(&in, spec);
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->edges_len, 2);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out.edge.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("RepeatEdge", out.vert, out.edge, out.face);
+ }
}
-TEST(delaunay, NearSeg)
+template<typename T> void repeattri_test()
{
- CDT_input in;
- CDT_result *out;
- int v[4], e0, e1, e2, i;
- const char *spec = R"(4 2 0
+ const char *spec = R"(3 0 2
0.0 0.0
1.0 0.0
- 0.25 0.09
- 0.25 1.0
- 0 1
- 2 3
+ 0.5 1.0
+ 0 1 2
+ 0 1 2
)";
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 3);
- EXPECT_EQ(out->faces_len, 0);
- if (out->edges_len == 3) {
- for (i = 0; i < 4; i++) {
- v[i] = get_output_vert_index(out, i);
- EXPECT_NE(v[i], -1);
- }
- e0 = get_edge(out, v[0], v[2]);
- e1 = get_edge(out, v[2], v[1]);
- e2 = get_edge(out, v[2], v[3]);
- EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e2, 1));
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out.edge.size(), 3);
+ EXPECT_EQ(out.face.size(), 1);
+ EXPECT_TRUE(output_face_has_input_id(out, 0, 0));
+ EXPECT_TRUE(output_face_has_input_id(out, 0, 1));
+ if (DO_DRAW) {
+ graph_draw<T>("RepeatTri", out.vert, out.edge, out.face);
}
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
}
-TEST(delaunay, OverlapSegs)
+TEST(delaunay_d, Empty)
{
- CDT_input in;
- CDT_result *out;
- int v[4], e0, e1, e2, i;
- const char *spec = R"(4 2 0
- 0.0 0.0
- 1.0 0.0
- 0.4 0.09
- 1.4 0.09
- 0 1
- 2 3
- )";
+ empty_test<double>();
+}
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 4);
- EXPECT_EQ(out->edges_len, 3);
- EXPECT_EQ(out->faces_len, 0);
- if (out->edges_len == 3) {
- for (i = 0; i < 4; i++) {
- v[i] = get_output_vert_index(out, i);
- EXPECT_NE(v[i], -1);
- }
- e0 = get_edge(out, v[0], v[2]);
- e1 = get_edge(out, v[2], v[1]);
- e2 = get_edge(out, v[1], v[3]);
- EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 1));
- EXPECT_TRUE(out_edge_has_input_id(out, e2, 1));
- }
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+TEST(delaunay_d, OnePt)
+{
+ onept_test<double>();
}
-TEST(delaunay, NearSegWithDup)
+TEST(delaunay_d, TwoPt)
{
- CDT_input in;
- CDT_result *out;
- int v[5], e0, e1, e2, e3, i;
- const char *spec = R"(5 3 0
- 0.0 0.0
- 1.0 0.0
- 0.25 0.09
- 0.25 1.0
- 0.75 0.09
- 0 1
- 2 3
- 2 4
- )";
+ twopt_test<double>();
+}
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 5);
- EXPECT_EQ(out->edges_len, 4);
- EXPECT_EQ(out->faces_len, 0);
- if (out->edges_len == 5) {
- for (i = 0; i < 5; i++) {
- v[i] = get_output_vert_index(out, i);
- EXPECT_NE(v[i], -1);
- }
- e0 = get_edge(out, v[0], v[2]);
- e1 = get_edge(out, v[2], v[4]);
- e2 = get_edge(out, v[4], v[1]);
- e3 = get_edge(out, v[3], v[2]);
- EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 2));
- EXPECT_TRUE(out_edge_has_input_id(out, e2, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e3, 1));
- }
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+TEST(delaunay_d, ThreePt)
+{
+ threept_test<double>();
}
-TEST(delaunay, TwoNearSeg)
+TEST(delaunay_d, MixedPts)
{
- CDT_input in;
- CDT_result *out;
- int v[5], e0, e1, e2, e3, e4, i;
- const char *spec = R"(5 3 0
- 0.0 0.0
- 1.0 0.0
- 0.25 0.09
- 0.25 1.0
- 0.75 0.09
- 0 1
- 3 2
- 3 4
- )";
+ mixedpts_test<double>();
+}
- fill_input_from_string(&in, spec);
- in.epsilon = 0.1;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 5);
- EXPECT_EQ(out->edges_len, 5);
- EXPECT_EQ(out->faces_len, 1);
- if (out->edges_len == 5) {
- for (i = 0; i < 5; i++) {
- v[i] = get_output_vert_index(out, i);
- EXPECT_NE(v[i], -1);
- }
- e0 = get_edge(out, v[0], v[2]);
- e1 = get_edge(out, v[2], v[4]);
- e2 = get_edge(out, v[4], v[1]);
- e3 = get_edge(out, v[3], v[2]);
- e4 = get_edge(out, v[3], v[4]);
- EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e2, 0));
- EXPECT_TRUE(out_edge_has_input_id(out, e3, 1));
- EXPECT_TRUE(out_edge_has_input_id(out, e4, 2));
- }
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+TEST(delaunay_d, Quad0)
+{
+ quad0_test<double>();
}
-TEST(delaunay, FaceNearSegs)
+TEST(delaunay_d, Quad1)
{
- CDT_input in;
- CDT_result *out;
- int v[9], e0, e1, e2, e3, i;
- const char *spec = R"(8 1 2
- 0.0 0.0
- 2.0 0.0
- 1.0 1.0
- 0.21 0.2
- 1.79 0.2
- 0.51 0.5
- 1.49 0.5
- 1.0 0.19
- 2 7
- 0 1 2
- 3 4 6 5
- )";
+ quad1_test<double>();
+}
- fill_input_from_string(&in, spec);
- in.epsilon = 0.05;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 9);
- EXPECT_EQ(out->edges_len, 13);
- EXPECT_EQ(out->faces_len, 5);
- if (out->verts_len == 9 && out->edges_len == 13) {
- for (i = 0; i < 8; i++) {
- v[i] = get_output_vert_index(out, i);
- EXPECT_NE(v[i], -1);
- }
- v[8] = 8;
- e0 = get_edge(out, v[0], v[1]);
- e1 = get_edge(out, v[4], v[6]);
- e2 = get_edge(out, v[3], v[0]);
- e3 = get_edge(out, v[2], v[8]);
-
- EXPECT_TRUE(out_edge_has_input_id(out, e0, 1));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 2));
- EXPECT_TRUE(out_edge_has_input_id(out, e1, 5));
- EXPECT_TRUE(out_edge_has_input_id(out, e2, 3));
- EXPECT_TRUE(out_edge_has_input_id(out, e3, 0));
- }
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+TEST(delaunay_d, Quad2)
+{
+ quad2_test<double>();
}
-TEST(delaunay, ChainNearIntersects)
+TEST(delaunay_d, Quad3)
{
- CDT_input in;
- CDT_result *out;
- const char *spec = R"(6 10 0
- 0.8 1.25
- 1.25 0.75
- 3.25 1.25
- 5.0 1.9
- 2.5 4.0
- 1.0 2.25
- 0 1
- 1 2
- 2 3
- 3 4
- 4 5
- 5 0
- 0 2
- 5 2
- 4 2
- 1 3
- )";
+ quad3_test<double>();
+}
+
+TEST(delaunay_d, Quad4)
+{
+ quad4_test<double>();
+}
+
+TEST(delaunay_d, LineInSquare)
+{
+ lineinsquare_test<double>();
+}
+
+TEST(delaunay_d, CrossSegs)
+{
+ crosssegs_test<double>();
+}
+
+TEST(delaunay_d, DiamondCross)
+{
+ diamondcross_test<double>();
+}
+
+TEST(delaunay_d, TwoDiamondsCross)
+{
+ twodiamondscross_test<double>();
+}
+
+TEST(delaunay_d, ManyCross)
+{
+ manycross_test<double>();
+}
+
+TEST(delaunay_d, TwoFace)
+{
+ twoface_test<double>();
+}
+
+TEST(delaunay_d, TwoFace2)
+{
+ twoface2_test<double>();
+}
+
+TEST(delaunay_d, OverlapFaces)
+{
+ overlapfaces_test<double>();
+}
- fill_input_from_string(&in, spec);
- in.epsilon = 0.05;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 9);
- EXPECT_EQ(out->edges_len, 16);
- BLI_delaunay_2d_cdt_free(out);
- in.epsilon = 0.11;
- /* The chaining we want to test happens prematurely if modify input. */
- in.skip_input_modify = true;
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- EXPECT_EQ(out->verts_len, 6);
- EXPECT_EQ(out->edges_len, 9);
- free_spec_arrays(&in);
- BLI_delaunay_2d_cdt_free(out);
+TEST(delaunay_d, TwoSquaresOverlap)
+{
+ twosquaresoverlap_test<double>();
+}
+
+TEST(delaunay_d, TwoFaceEdgeOverlap)
+{
+ twofaceedgeoverlap_test<double>();
+}
+
+TEST(delaunay_d, TriInTri)
+{
+ triintri_test<double>();
+}
+
+TEST(delaunay_d, DiamondInSquare)
+{
+ diamondinsquare_test<double>();
+}
+
+TEST(delaunay_d, DiamondInSquareWire)
+{
+ diamondinsquarewire_test<double>();
+}
+
+TEST(delaunay_d, RepeatEdge)
+{
+ repeatedge_test<double>();
+}
+
+TEST(delaunay_d, RepeatTri)
+{
+ repeattri_test<double>();
+}
+
+# ifdef WITH_GMP
+TEST(delaunay_m, Empty)
+{
+ empty_test<mpq_class>();
+}
+
+TEST(delaunay_m, OnePt)
+{
+ onept_test<mpq_class>();
+}
+TEST(delaunay_m, TwoPt)
+{
+ twopt_test<mpq_class>();
+}
+
+TEST(delaunay_m, ThreePt)
+{
+ threept_test<mpq_class>();
+}
+
+TEST(delaunay_m, MixedPts)
+{
+ mixedpts_test<mpq_class>();
+}
+
+TEST(delaunay_m, Quad0)
+{
+ quad0_test<mpq_class>();
+}
+
+TEST(delaunay_m, Quad1)
+{
+ quad1_test<mpq_class>();
+}
+
+TEST(delaunay_m, Quad2)
+{
+ quad2_test<mpq_class>();
+}
+
+TEST(delaunay_m, Quad3)
+{
+ quad3_test<mpq_class>();
+}
+
+TEST(delaunay_m, Quad4)
+{
+ quad4_test<mpq_class>();
+}
+
+TEST(delaunay_m, LineInSquare)
+{
+ lineinsquare_test<mpq_class>();
+}
+
+TEST(delaunay_m, CrossSegs)
+{
+ crosssegs_test<mpq_class>();
+}
+
+TEST(delaunay_m, DiamondCross)
+{
+ diamondcross_test<mpq_class>();
+}
+
+TEST(delaunay_m, TwoDiamondsCross)
+{
+ twodiamondscross_test<mpq_class>();
+}
+
+TEST(delaunay_m, ManyCross)
+{
+ manycross_test<mpq_class>();
+}
+
+TEST(delaunay_m, TwoFace)
+{
+ twoface_test<mpq_class>();
+}
+
+TEST(delaunay_m, TwoFace2)
+{
+ twoface2_test<mpq_class>();
+}
+
+TEST(delaunay_m, OverlapFaces)
+{
+ overlapfaces_test<mpq_class>();
+}
+
+TEST(delaunay_m, TwoSquaresOverlap)
+{
+ twosquaresoverlap_test<mpq_class>();
+}
+
+TEST(delaunay_m, TwoFaceEdgeOverlap)
+{
+ twofaceedgeoverlap_test<mpq_class>();
+}
+
+TEST(delaunay_m, TriInTri)
+{
+ triintri_test<mpq_class>();
+}
+
+TEST(delaunay_m, DiamondInSquare)
+{
+ diamondinsquare_test<mpq_class>();
+}
+
+TEST(delaunay_m, DiamondInSquareWire)
+{
+ diamondinsquarewire_test<mpq_class>();
+}
+
+TEST(delaunay_m, RepeatEdge)
+{
+ repeatedge_test<mpq_class>();
+}
+
+TEST(delaunay_m, RepeatTri)
+{
+ repeattri_test<mpq_class>();
+}
+# endif
+
+#endif
+
+#if DO_C_TESTS
+
+TEST(delaunay_d, CintTwoFace)
+{
+ float vert_coords[][2] = {
+ {0.0, 0.0}, {1.0, 0.0}, {0.5, 1.0}, {1.1, 1.0}, {1.1, 0.0}, {1.6, 1.0}};
+ int faces[] = {0, 1, 2, 3, 4, 5};
+ int faces_len[] = {3, 3};
+ int faces_start[] = {0, 3};
+
+ ::CDT_input input;
+ input.verts_len = 6;
+ input.edges_len = 0;
+ input.faces_len = 2;
+ input.vert_coords = vert_coords;
+ input.edges = NULL;
+ input.faces = faces;
+ input.faces_len_table = faces_len;
+ input.faces_start_table = faces_start;
+ input.epsilon = 1e-5f;
+ ::CDT_result *output = BLI_delaunay_2d_cdt_calc(&input, CDT_FULL);
+ BLI_delaunay_2d_cdt_free(output);
}
#endif
#if DO_RANDOM_TESTS
+
enum {
RANDOM_PTS,
RANDOM_SEGS,
@@ -1437,342 +1534,323 @@ enum {
RANDOM_TRI_BETWEEN_CIRCLES,
};
-# define DO_TIMING
-static void rand_delaunay_test(int test_kind,
- int start_lg_size,
- int max_lg_size,
- int reps_per_size,
- double param,
- CDT_output_type otype)
-{
- CDT_input in;
- CDT_result *out;
- int lg_size, size, rep, i, j, size_max, npts_max, nedges_max, nfaces_max, npts, nedges, nfaces;
- int ia, ib, ic;
- float(*p)[2];
- int(*e)[2];
- int *faces, *faces_start_table, *faces_len_table;
- double start_angle, angle_delta, angle1, angle2, angle3;
- float orient;
- double tstart;
- double *times;
- RNG *rng;
-
- rng = BLI_rng_new(0);
- e = NULL;
- faces = NULL;
- faces_start_table = NULL;
- faces_len_table = NULL;
- nedges_max = 0;
- nfaces_max = 0;
-
- /* Set up npts, nedges, nfaces, and allocate needed arrays at max length needed. */
- size_max = 1 << max_lg_size;
- switch (test_kind) {
- case RANDOM_PTS:
- case RANDOM_SEGS:
- case RANDOM_POLY:
- npts_max = size_max;
- if (test_kind == RANDOM_SEGS) {
- nedges_max = npts_max - 1;
- }
- else if (test_kind == RANDOM_POLY) {
- nedges_max = npts_max;
- }
- break;
-
- case RANDOM_TILTED_GRID:
- /* A 'size' x 'size' grid of points, tilted by angle 'param'.
- * Edges will go from left ends to right ends and tops to bottoms, so 2 x size of them.
- * Depending on epsilon, the vertical-ish edges may or may not go through the intermediate
- * vertices, but the horizontal ones always should.
- */
- npts_max = size_max * size_max;
- nedges_max = 2 * size_max;
- break;
-
- case RANDOM_CIRCLE:
- /* A circle with 'size' points, a random start angle, and equal spacing thereafter.
- * Will be input as one face.
- */
- npts_max = size_max;
- nfaces_max = 1;
- break;
-
- case RANDOM_TRI_BETWEEN_CIRCLES:
- /* A set of 'size' triangles, each has two random points on the unit circle,
- * and the third point is a random point on the circle with radius 'param'.
- * Each triangle will be input as a face.
- */
- npts_max = 3 * size_max;
- nfaces_max = size_max;
- break;
-
- default:
- fprintf(stderr, "unknown random delaunay test kind\n");
- return;
- }
- p = (float(*)[2])MEM_malloc_arrayN(npts_max, 2 * sizeof(float), __func__);
- if (nedges_max > 0) {
- e = (int(*)[2])MEM_malloc_arrayN(nedges_max, 2 * sizeof(int), __func__);
- }
- if (nfaces_max > 0) {
- faces_start_table = (int *)MEM_malloc_arrayN(nfaces_max, sizeof(int), __func__);
- faces_len_table = (int *)MEM_malloc_arrayN(nfaces_max, sizeof(int), __func__);
- faces = (int *)MEM_malloc_arrayN(npts_max, sizeof(int), __func__);
- }
-
- times = (double *)MEM_malloc_arrayN(max_lg_size + 1, sizeof(double), __func__);
+template<typename T>
+void rand_delaunay_test(int test_kind,
+ int start_lg_size,
+ int max_lg_size,
+ int reps_per_size,
+ double param,
+ CDT_output_type otype)
+{
+ constexpr bool print_timing = true;
+ RNG *rng = BLI_rng_new(0);
+ Array<double> times(max_lg_size + 1);
/* For powers of 2 sizes up to max_lg_size power of 2. */
- for (lg_size = start_lg_size; lg_size <= max_lg_size; lg_size++) {
- size = 1 << lg_size;
- nedges = 0;
- nfaces = 0;
+ for (int lg_size = start_lg_size; lg_size <= max_lg_size; ++lg_size) {
+ int size = 1 << lg_size;
times[lg_size] = 0.0;
if (size == 1 && test_kind != RANDOM_PTS) {
continue;
}
/* Do 'rep' repetitions. */
- for (rep = 0; rep < reps_per_size; rep++) {
+ for (int rep = 0; rep < reps_per_size; ++rep) {
+ /* First use test type and size to set npts, nedges, and nfaces. */
+ int npts = 0;
+ int nedges = 0;
+ int nfaces = 0;
+ std::string test_label;
+ switch (test_kind) {
+ case RANDOM_PTS: {
+ npts = size;
+ test_label = std::to_string(npts) + "Random points";
+ } break;
+ case RANDOM_SEGS: {
+ npts = size;
+ nedges = npts - 1;
+ test_label = std::to_string(nedges) + "Random edges";
+ } break;
+ case RANDOM_POLY: {
+ npts = size;
+ nedges = npts;
+ test_label = "Random poly with " + std::to_string(nedges) + " edges";
+ } break;
+ case RANDOM_TILTED_GRID: {
+ /* A 'size' x 'size' grid of points, tilted by angle 'param'.
+ * Edges will go from left ends to right ends and tops to bottoms,
+ * so 2 x size of them.
+ * Depending on epsilon, the vertical-ish edges may or may not go
+ * through the intermediate vertices, but the horizontal ones always should.
+ * 'param' is slope of tilt of vertical lines.
+ */
+ npts = size * size;
+ nedges = 2 * size;
+ test_label = "Tilted grid " + std::to_string(npts) + "x" + std::to_string(npts) +
+ " (tilt=" + std::to_string(param) + ")";
+ } break;
+ case RANDOM_CIRCLE: {
+ /* A circle with 'size' points, a random start angle,
+ * and equal spacing thereafter. Will be input as one face.
+ */
+ npts = size;
+ nfaces = 1;
+ test_label = "Circle with " + std::to_string(npts) + " points";
+ } break;
+ case RANDOM_TRI_BETWEEN_CIRCLES: {
+ /* A set of 'size' triangles, each has two random points on the unit circle,
+ * and the third point is a random point on the circle with radius 'param'.
+ * Each triangle will be input as a face.
+ */
+ npts = 3 * size;
+ nfaces = size;
+ test_label = "Random " + std::to_string(nfaces) +
+ " triangles between circles (inner radius=" + std::to_string(param) + ")";
+ } break;
+ default:
+ std::cout << "unknown delaunay test type\n";
+ return;
+ }
+ if (otype != CDT_FULL) {
+ if (otype == CDT_INSIDE) {
+ test_label += " (inside)";
+ }
+ else if (otype == CDT_CONSTRAINTS) {
+ test_label += " (constraints)";
+ }
+ else if (otype == CDT_CONSTRAINTS_VALID_BMESH) {
+ test_label += " (valid bmesh)";
+ }
+ }
+
+ CDT_input<T> in;
+ in.vert = Array<vec2<T>>(npts);
+ if (nedges > 0) {
+ in.edge = Array<std::pair<int, int>>(nedges);
+ }
+ if (nfaces > 0) {
+ in.face = Array<Vector<int>>(nfaces);
+ }
+
/* Make vertices and edges or faces. */
switch (test_kind) {
case RANDOM_PTS:
case RANDOM_SEGS:
- case RANDOM_POLY:
- npts = size;
- if (test_kind == RANDOM_SEGS) {
- nedges = npts - 1;
- }
- else if (test_kind == RANDOM_POLY) {
- nedges = npts;
- }
- for (i = 0; i < size; i++) {
- p[i][0] = (float)BLI_rng_get_double(rng); /* will be in range in [0,1) */
- p[i][1] = (float)BLI_rng_get_double(rng);
+ case RANDOM_POLY: {
+ for (int i = 0; i < size; i++) {
+ in.vert[i][0] = T(BLI_rng_get_double(rng)); /* will be in range in [0,1) */
+ in.vert[i][1] = T(BLI_rng_get_double(rng));
if (test_kind != RANDOM_PTS) {
if (i > 0) {
- e[i - 1][0] = i - 1;
- e[i - 1][1] = i;
+ in.edge[i - 1].first = i - 1;
+ in.edge[i - 1].second = i;
}
}
}
if (test_kind == RANDOM_POLY) {
- e[size - 1][0] = size - 1;
- e[size - 1][1] = 0;
+ in.edge[size - 1].first = size - 1;
+ in.edge[size - 1].second = 0;
}
- break;
+ } break;
- case RANDOM_TILTED_GRID:
- /* 'param' is slope of tilt of vertical lines. */
- npts = size * size;
- nedges = 2 * size;
- for (i = 0; i < size; i++) {
- for (j = 0; j < size; j++) {
- p[i * size + j][0] = i * param + j;
- p[i * size + j][1] = i;
+ case RANDOM_TILTED_GRID: {
+ for (int i = 0; i < size; ++i) {
+ for (int j = 0; j < size; ++j) {
+ in.vert[i * size + j][0] = T(i * param + j);
+ in.vert[i * size + j][1] = T(i);
}
}
- for (i = 0; i < size; i++) {
+ for (int i = 0; i < size; ++i) {
/* Horizontal edges: connect p(i,0) to p(i,size-1). */
- e[i][0] = i * size;
- e[i][1] = i * size + size - 1;
+ in.edge[i].first = i * size;
+ in.edge[i].second = i * size + size - 1;
/* Vertical edges: conntect p(0,i) to p(size-1,i). */
- e[size + i][0] = i;
- e[size + i][1] = (size - 1) * size + i;
+ in.edge[size + i].first = i;
+ in.edge[size + i].second = (size - 1) * size + i;
}
- break;
-
- case RANDOM_CIRCLE:
- npts = size;
- nfaces = 1;
- faces_start_table[0] = 0;
- faces_len_table[0] = npts;
- start_angle = BLI_rng_get_double(rng) * 2.0 * M_PI;
- angle_delta = 2.0 * M_PI / size;
- for (i = 0; i < size; i++) {
- p[i][0] = (float)cos(start_angle + i * angle_delta);
- p[i][1] = (float)sin(start_angle + i * angle_delta);
- faces[i] = i;
+ } break;
+
+ case RANDOM_CIRCLE: {
+ double start_angle = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ double angle_delta = 2.0 * M_PI / size;
+ for (int i = 0; i < size; i++) {
+ in.vert[i][0] = T(cos(start_angle + i * angle_delta));
+ in.vert[i][1] = T(sin(start_angle + i * angle_delta));
+ in.face[0].append(i);
}
- break;
+ } break;
- case RANDOM_TRI_BETWEEN_CIRCLES:
- npts = 3 * size;
- nfaces = size;
- for (i = 0; i < size; i++) {
+ case RANDOM_TRI_BETWEEN_CIRCLES: {
+ for (int i = 0; i < size; i++) {
/* Get three random angles in [0, 2pi). */
- angle1 = BLI_rng_get_double(rng) * 2.0 * M_PI;
- angle2 = BLI_rng_get_double(rng) * 2.0 * M_PI;
- angle3 = BLI_rng_get_double(rng) * 2.0 * M_PI;
- ia = 3 * i;
- ib = 3 * i + 1;
- ic = 3 * i + 2;
- p[ia][0] = (float)cos(angle1);
- p[ia][1] = (float)sin(angle1);
- p[ib][0] = (float)cos(angle2);
- p[ib][1] = (float)sin(angle2);
- p[ic][0] = (float)(param * cos(angle3));
- p[ic][1] = (float)(param * sin(angle3));
- faces_start_table[i] = 3 * i;
- faces_len_table[i] = 3;
+ double angle1 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ double angle2 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ double angle3 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ int ia = 3 * i;
+ int ib = 3 * i + 1;
+ int ic = 3 * i + 2;
+ in.vert[ia][0] = T(cos(angle1));
+ in.vert[ia][1] = T(sin(angle1));
+ in.vert[ib][0] = T(cos(angle2));
+ in.vert[ib][1] = T(sin(angle2));
+ in.vert[ic][0] = T((param * cos(angle3)));
+ in.vert[ic][1] = T((param * sin(angle3)));
/* Put the coordinates in ccw order. */
- faces[ia] = ia;
- orient = (p[ia][0] - p[ic][0]) * (p[ib][1] - p[ic][1]) -
- (p[ib][0] - p[ic][0]) * (p[ia][1] - p[ic][1]);
- if (orient >= 0.0f) {
- faces[ib] = ib;
- faces[ic] = ic;
+ in.face[i].append(ia);
+ int orient = vec2<T>::orient2d(in.vert[ia], in.vert[ib], in.vert[ic]);
+ if (orient >= 0) {
+ in.face[i].append(ib);
+ in.face[i].append(ic);
}
else {
- faces[ib] = ic;
- faces[ic] = ib;
+ in.face[i].append(ic);
+ in.face[i].append(ib);
}
}
- break;
- }
- fill_input_verts(&in, p, npts);
- if (nedges > 0) {
- add_input_edges(&in, e, nedges);
- }
- if (nfaces > 0) {
- add_input_faces(&in, faces, faces_start_table, faces_len_table, nfaces);
+ } break;
}
/* Run the test. */
- tstart = PIL_check_seconds_timer();
- out = BLI_delaunay_2d_cdt_calc(&in, otype);
- EXPECT_NE(out->verts_len, 0);
- BLI_delaunay_2d_cdt_free(out);
+ double tstart = PIL_check_seconds_timer();
+ CDT_result<T> out = delaunay_2d_calc(in, otype);
+ EXPECT_NE(out.vert.size(), 0);
times[lg_size] += PIL_check_seconds_timer() - tstart;
+ if (DO_DRAW) {
+ graph_draw<T>(test_label, out.vert, out.edge, out.face);
+ }
}
}
-# ifdef DO_TIMING
- fprintf(stderr, "size,time\n");
- for (lg_size = 0; lg_size <= max_lg_size; lg_size++) {
- fprintf(stderr, "%d,%f\n", 1 << lg_size, times[lg_size] / reps_per_size);
- }
-# endif
- MEM_freeN(p);
- if (e) {
- MEM_freeN(e);
- }
- if (faces) {
- MEM_freeN(faces);
- MEM_freeN(faces_start_table);
- MEM_freeN(faces_len_table);
+ if (print_timing) {
+ std::cout << "\nsize,time\n";
+ for (int lg_size = 0; lg_size <= max_lg_size; lg_size++) {
+ int size = 1 << lg_size;
+ std::cout << size << "," << times[lg_size] << "\n";
+ }
}
- MEM_freeN(times);
BLI_rng_free(rng);
}
-TEST(delaunay, randompts)
+TEST(delaunay_d, RandomPts)
{
- rand_delaunay_test(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, randomsegs)
+TEST(delaunay_d, RandomSegs)
{
- rand_delaunay_test(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, randompoly)
+TEST(delaunay_d, RandomPoly)
{
- rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, randompoly_inside)
+TEST(delaunay_d, RandomPolyConstraints)
{
- rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE);
+ rand_delaunay_test<double>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS);
}
-TEST(delaunay, randompoly_constraints)
+TEST(delaunay_d, RandomPolyValidBmesh)
{
- rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS);
+ rand_delaunay_test<double>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH);
}
-TEST(delaunay, randompoly_validbmesh)
+TEST(delaunay_d, Grid)
{
- rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH);
+ rand_delaunay_test<double>(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, grid)
+TEST(delaunay_d, TiltedGridA)
{
- rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL);
}
-TEST(delaunay, tilted_grid_a)
+TEST(delaunay_d, TiltedGridB)
{
- rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL);
}
-TEST(delaunay, tilted_grid_b)
+TEST(delaunay_d, RandomCircle)
{
- rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, randomcircle)
+TEST(delaunay_d, RandomTrisCircle)
{
- rand_delaunay_test(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL);
}
-TEST(delaunay, random_tris_circle)
+TEST(delaunay_d, RandomTrisCircleB)
{
- rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL);
+ rand_delaunay_test<double>(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL);
}
-TEST(delaunay, random_tris_circle_b)
+# ifdef WITH_GMP
+TEST(delaunay_m, RandomPts)
{
- rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL);
+ rand_delaunay_test<mpq_class>(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL);
}
-#endif
-#if DO_FILE_TESTS
-/* For manually testing performance by timing a large number of points from a
- * file. See fill_input_from_file for file format.
- */
-static void points_from_file_test(const char *filename)
+TEST(delaunay_m, RandomSegs)
{
- CDT_input in;
- CDT_result *out;
- double tstart;
+ rand_delaunay_test<mpq_class>(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL);
+}
- fill_input_from_file(&in, filename);
- tstart = PIL_check_seconds_timer();
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
- fprintf(stderr, "time to triangulate=%f seconds\n", PIL_check_seconds_timer() - tstart);
- BLI_delaunay_2d_cdt_free(out);
- free_spec_arrays(&in);
+TEST(delaunay_m, RandomPoly)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL);
}
-# if 0
-TEST(delaunay, debug)
+TEST(delaunay_d, RandomPolyInside)
{
- CDT_input in;
- CDT_result *out;
- fill_input_from_file(&in, "/tmp/cdtinput.txt");
- out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
- BLI_delaunay_2d_cdt_free(out);
- free_spec_arrays(&in);
+ rand_delaunay_test<double>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE);
+}
+
+TEST(delaunay_m, RandomPolyInside)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE);
+}
+
+TEST(delaunay_m, RandomPolyConstraints)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS);
}
-# endif
-# if 1
-# define POINTFILEROOT "/tmp/"
+TEST(delaunay_m, RandomPolyValidBmesh)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH);
+}
-TEST(delaunay, terrain1)
+TEST(delaunay_m, Grid)
{
- points_from_file_test(POINTFILEROOT "points1.txt");
+ rand_delaunay_test<mpq_class>(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL);
}
-TEST(delaunay, terrain2)
+TEST(delaunay_m, TiltedGridA)
{
- points_from_file_test(POINTFILEROOT "points2.txt");
+ rand_delaunay_test<mpq_class>(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL);
}
-TEST(delaunay, terrain3)
+TEST(delaunay_m, TiltedGridB)
{
- points_from_file_test(POINTFILEROOT "points3.txt");
+ rand_delaunay_test<mpq_class>(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL);
+}
+
+TEST(delaunay_m, RandomCircle)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay_m, RandomTrisCircle)
+{
+ rand_delaunay_test<mpq_class>(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL);
+}
+
+TEST(delaunay_m, RandomTrisCircleB)
+{
+ rand_delaunay_test<double>(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL);
}
# endif
+
#endif
+
+} // namespace blender::meshintersect
diff --git a/source/blender/blenlib/tests/BLI_mesh_boolean_test.cc b/source/blender/blenlib/tests/BLI_mesh_boolean_test.cc
new file mode 100644
index 00000000000..af8856a2e15
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_mesh_boolean_test.cc
@@ -0,0 +1,908 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array.hh"
+#include "BLI_map.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mesh_boolean.hh"
+#include "BLI_mpq3.hh"
+#include "BLI_vector.hh"
+
+namespace blender::meshintersect::tests {
+
+constexpr bool DO_OBJ = false;
+
+/* Build and hold an IMesh from a string spec. Also hold and own resources used by IMesh. */
+class IMeshBuilder {
+ public:
+ IMesh imesh;
+ IMeshArena arena;
+
+ /* "Edge orig" indices are an encoding of <input face#, position in face of start of edge>. */
+ static constexpr int MAX_FACE_LEN = 1000; /* Used for forming "orig edge" indices only. */
+
+ static int edge_index(int face_index, int facepos)
+ {
+ return face_index * MAX_FACE_LEN + facepos;
+ }
+
+ static std::pair<int, int> face_and_pos_for_edge_index(int e_index)
+ {
+ return std::pair<int, int>(e_index / MAX_FACE_LEN, e_index % MAX_FACE_LEN);
+ }
+
+ /*
+ * Spec should have form:
+ * #verts #faces
+ * mpq_class mpq_class mpq_clas [#verts lines]
+ * int int int ... [#faces lines; indices into verts for given face]
+ */
+ IMeshBuilder(const char *spec)
+ {
+ std::istringstream ss(spec);
+ std::string line;
+ getline(ss, line);
+ std::istringstream hdrss(line);
+ int nv, nf;
+ hdrss >> nv >> nf;
+ if (nv == 0 || nf == 0) {
+ return;
+ }
+ arena.reserve(nv, nf);
+ Vector<const Vert *> verts;
+ Vector<Face *> faces;
+ bool spec_ok = true;
+ int v_index = 0;
+ while (v_index < nv && spec_ok && getline(ss, line)) {
+ std::istringstream iss(line);
+ mpq_class p0;
+ mpq_class p1;
+ mpq_class p2;
+ iss >> p0 >> p1 >> p2;
+ spec_ok = !iss.fail();
+ verts.append(arena.add_or_find_vert(mpq3(p0, p1, p2), v_index));
+ ++v_index;
+ }
+ if (v_index != nv) {
+ spec_ok = false;
+ }
+ int f_index = 0;
+ while (f_index < nf && spec_ok && getline(ss, line)) {
+ std::istringstream fss(line);
+ Vector<const Vert *> face_verts;
+ Vector<int> edge_orig;
+ int fpos = 0;
+ while (spec_ok && fss >> v_index) {
+ if (v_index < 0 || v_index >= nv) {
+ spec_ok = false;
+ continue;
+ }
+ face_verts.append(verts[v_index]);
+ edge_orig.append(edge_index(f_index, fpos));
+ ++fpos;
+ }
+ Face *facep = arena.add_face(face_verts, f_index, edge_orig);
+ faces.append(facep);
+ ++f_index;
+ }
+ if (f_index != nf) {
+ spec_ok = false;
+ }
+ if (!spec_ok) {
+ std::cout << "Bad spec: " << spec;
+ return;
+ }
+ imesh = IMesh(faces);
+ }
+};
+
+static int all_shape_zero(int UNUSED(t))
+{
+ return 0;
+}
+
+TEST(boolean_trimesh, Empty)
+{
+ IMeshArena arena;
+ IMesh in;
+ IMesh out = boolean_trimesh(in, BoolOpType::None, 1, all_shape_zero, true, &arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 0);
+ EXPECT_EQ(out.face_size(), 0);
+}
+
+TEST(boolean_trimesh, TetTetTrimesh)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 0 0 1
+ 2 0 1
+ 1 2 1
+ 1 1 3
+ 0 2 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::None, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 11);
+ EXPECT_EQ(out.face_size(), 20);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet_tm");
+ }
+
+ IMeshBuilder mb2(spec);
+ IMesh out2 = boolean_trimesh(mb2.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb2.arena);
+ out2.populate_vert();
+ EXPECT_EQ(out2.vert_size(), 10);
+ EXPECT_EQ(out2.face_size(), 16);
+ if (DO_OBJ) {
+ write_obj_mesh(out2, "tettet_union_tm");
+ }
+
+ IMeshBuilder mb3(spec);
+ IMesh out3 = boolean_trimesh(
+ mb3.imesh, BoolOpType::Union, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb3.arena);
+ out3.populate_vert();
+ EXPECT_EQ(out3.vert_size(), 10);
+ EXPECT_EQ(out3.face_size(), 16);
+ if (DO_OBJ) {
+ write_obj_mesh(out3, "tettet_union_binary_tm");
+ }
+
+ IMeshBuilder mb4(spec);
+ IMesh out4 = boolean_trimesh(
+ mb4.imesh, BoolOpType::Union, 2, [](int t) { return t < 4 ? 0 : 1; }, true, &mb4.arena);
+ out4.populate_vert();
+ EXPECT_EQ(out4.vert_size(), 10);
+ EXPECT_EQ(out4.face_size(), 16);
+ if (DO_OBJ) {
+ write_obj_mesh(out4, "tettet_union_binary_self_tm");
+ }
+
+ IMeshBuilder mb5(spec);
+ IMesh out5 = boolean_trimesh(
+ mb5.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb5.arena);
+ out5.populate_vert();
+ EXPECT_EQ(out5.vert_size(), 4);
+ EXPECT_EQ(out5.face_size(), 4);
+ if (DO_OBJ) {
+ write_obj_mesh(out5, "tettet_intersect_binary_tm");
+ }
+
+ IMeshBuilder mb6(spec);
+ IMesh out6 = boolean_trimesh(
+ mb6.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 4 ? 0 : 1; },
+ false,
+ &mb6.arena);
+ out6.populate_vert();
+ EXPECT_EQ(out6.vert_size(), 6);
+ EXPECT_EQ(out6.face_size(), 8);
+ if (DO_OBJ) {
+ write_obj_mesh(out6, "tettet_difference_binary_tm");
+ }
+
+ IMeshBuilder mb7(spec);
+ IMesh out7 = boolean_trimesh(
+ mb7.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 4 ? 1 : 0; },
+ false,
+ &mb7.arena);
+ out7.populate_vert();
+ EXPECT_EQ(out7.vert_size(), 8);
+ EXPECT_EQ(out7.face_size(), 12);
+ if (DO_OBJ) {
+ write_obj_mesh(out7, "tettet_difference_rev_binary_tm");
+ }
+}
+
+TEST(boolean_trimesh, TetTet2Trimesh)
+{
+ const char *spec = R"(8 8
+ 0 1 -1
+ 7/8 -1/2 -1
+ -7/8 -1/2 -1
+ 0 0 1
+ 0 1 0
+ 7/8 -1/2 0
+ -7/8 -1/2 0
+ 0 0 2
+ 0 3 1
+ 0 1 2
+ 1 3 2
+ 2 3 0
+ 4 7 5
+ 4 5 6
+ 5 7 6
+ 6 7 4
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 10);
+ EXPECT_EQ(out.face_size(), 16);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet2_union_tm");
+ }
+}
+
+TEST(boolean_trimesh, CubeTetTrimesh)
+{
+ const char *spec = R"(12 16
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1/2 1/2
+ 1/2 -1/4 1/2
+ -1/2 -1/4 1/2
+ 0 0 3/2
+ 0 1 3
+ 0 3 2
+ 2 3 7
+ 2 7 6
+ 6 7 5
+ 6 5 4
+ 4 5 1
+ 4 1 0
+ 2 6 4
+ 2 4 0
+ 7 3 1
+ 7 1 5
+ 8 11 9
+ 8 9 10
+ 9 11 10
+ 10 11 8
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 14);
+ EXPECT_EQ(out.face_size(), 24);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubetet_union_tm");
+ }
+}
+
+TEST(boolean_trimesh, BinaryTetTetTrimesh)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 0 0 1
+ 2 0 1
+ 1 2 1
+ 1 1 3
+ 0 2 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(
+ mb.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 4 ? 0 : 1; }, false, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 4);
+ EXPECT_EQ(out.face_size(), 4);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "binary_tettet_isect_tm");
+ }
+}
+
+TEST(boolean_trimesh, TetTetCoplanarTrimesh)
+{
+ const char *spec = R"(8 8
+ 0 1 0
+ 7/8 -1/2 0
+ -7/8 -1/2 0
+ 0 0 1
+ 0 1 0
+ 7/8 -1/2 0
+ -7/8 -1/2 0
+ 0 0 -1
+ 0 3 1
+ 0 1 2
+ 1 3 2
+ 2 3 0
+ 4 5 7
+ 4 6 5
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 5);
+ EXPECT_EQ(out.face_size(), 6);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet_coplanar_tm");
+ }
+}
+
+TEST(boolean_trimesh, TetInsideTetTrimesh)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ -1 -3/4 -1/2
+ 3 -3/4 -1/2
+ 1 13/4 -1/2
+ 1 5/4 7/2
+ 0 2 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 4);
+ EXPECT_EQ(out.face_size(), 4);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tetinsidetet_tm");
+ }
+}
+
+TEST(boolean_trimesh, TetBesideTetTrimesh)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 3 0 0
+ 5 0 0
+ 4 2 0
+ 4 1 2
+ 0 2 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 8);
+ EXPECT_EQ(out.face_size(), 8);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tetbesidetet_tm");
+ }
+}
+
+TEST(boolean_trimesh, DegenerateTris)
+{
+ const char *spec = R"(10 10
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 0 0 1
+ 2 0 1
+ 1 2 1
+ 1 1 3
+ 0 0 0
+ 1 0 0
+ 0 2 1
+ 0 8 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ 0 1 9
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_trimesh(
+ mb.imesh, BoolOpType::Intersect, 2, [](int t) { return t < 5 ? 0 : 1; }, false, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 4);
+ EXPECT_EQ(out.face_size(), 4);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "degenerate_tris_tm");
+ }
+}
+
+TEST(boolean_polymesh, TetTet)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 0 0 1
+ 2 0 1
+ 1 2 1
+ 1 1 3
+ 0 2 1
+ 0 1 3
+ 1 2 3
+ 2 0 3
+ 4 6 5
+ 4 5 7
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh, BoolOpType::None, 1, all_shape_zero, true, nullptr, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 11);
+ EXPECT_EQ(out.face_size(), 13);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet");
+ }
+
+ IMeshBuilder mb2(spec);
+ IMesh out2 = boolean_mesh(
+ mb2.imesh,
+ BoolOpType::None,
+ 2,
+ [](int t) { return t < 4 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb2.arena);
+ out2.populate_vert();
+ EXPECT_EQ(out2.vert_size(), 11);
+ EXPECT_EQ(out2.face_size(), 13);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet2");
+ }
+}
+
+TEST(boolean_polymesh, CubeCube)
+{
+ const char *spec = R"(16 12
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 1/2 1/2 1/2
+ 1/2 1/2 5/2
+ 1/2 5/2 1/2
+ 1/2 5/2 5/2
+ 5/2 1/2 1/2
+ 5/2 1/2 5/2
+ 5/2 5/2 1/2
+ 5/2 5/2 5/2
+ 0 1 3 2
+ 6 2 3 7
+ 4 6 7 5
+ 0 4 5 1
+ 0 2 6 4
+ 3 1 5 7
+ 8 9 11 10
+ 14 10 11 15
+ 12 14 15 13
+ 8 12 13 9
+ 8 10 14 12
+ 11 9 13 15
+ )";
+
+ IMeshBuilder mb(spec);
+ if (DO_OBJ) {
+ write_obj_mesh(mb.imesh, "cube_cube_in");
+ }
+ IMesh out = boolean_mesh(
+ mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, nullptr, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 20);
+ EXPECT_EQ(out.face_size(), 12);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubecube_union");
+ }
+
+ IMeshBuilder mb2(spec);
+ IMesh out2 = boolean_mesh(
+ mb2.imesh,
+ BoolOpType::None,
+ 2,
+ [](int t) { return t < 6 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb2.arena);
+ out2.populate_vert();
+ EXPECT_EQ(out2.vert_size(), 22);
+ EXPECT_EQ(out2.face_size(), 18);
+ if (DO_OBJ) {
+ write_obj_mesh(out2, "cubecube_none");
+ }
+}
+
+TEST(boolean_polymesh, CubeCone)
+{
+ const char *spec = R"(14 12
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1/2 3/4
+ 119/250 31/200 3/4
+ 147/500 -81/200 3/4
+ 0 0 7/4
+ -147/500 -81/200 3/4
+ -119/250 31/200 3/4
+ 0 1 3 2
+ 2 3 7 6
+ 6 7 5 4
+ 4 5 1 0
+ 2 6 4 0
+ 7 3 1 5
+ 8 11 9
+ 9 11 10
+ 10 11 12
+ 12 11 13
+ 13 11 8
+ 8 9 10 12 13)";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh, BoolOpType::Union, 1, all_shape_zero, true, nullptr, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 14);
+ EXPECT_EQ(out.face_size(), 12);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubeccone");
+ }
+}
+
+TEST(boolean_polymesh, CubeCubeCoplanar)
+{
+ const char *spec = R"(16 12
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ -1/2 -1/2 1
+ -1/2 -1/2 2
+ -1/2 1/2 1
+ -1/2 1/2 2
+ 1/2 -1/2 1
+ 1/2 -1/2 2
+ 1/2 1/2 1
+ 1/2 1/2 2
+ 0 1 3 2
+ 2 3 7 6
+ 6 7 5 4
+ 4 5 1 0
+ 2 6 4 0
+ 7 3 1 5
+ 8 9 11 10
+ 10 11 15 14
+ 14 15 13 12
+ 12 13 9 8
+ 10 14 12 8
+ 15 11 9 13
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Union,
+ 2,
+ [](int t) { return t < 6 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 16);
+ EXPECT_EQ(out.face_size(), 12);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubecube_coplanar");
+ }
+}
+
+TEST(boolean_polymesh, TetTeTCoplanarDiff)
+{
+ const char *spec = R"(8 8
+ 0 1 0
+ 7/8 -1/2 0
+ -7/8 -1/2 0
+ 0 0 1
+ 0 1 0
+ 7/8 -1/2 0
+ -7/8 -1/2 0
+ 0 0 -1
+ 0 3 1
+ 0 1 2
+ 1 3 2
+ 2 3 0
+ 4 5 7
+ 4 6 5
+ 5 6 7
+ 6 4 7
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 4 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 4);
+ EXPECT_EQ(out.face_size(), 4);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tettet_coplanar_diff");
+ }
+}
+
+TEST(boolean_polymesh, CubeCubeStep)
+{
+ const char *spec = R"(16 12
+ 0 -1 0
+ 0 -1 2
+ 0 1 0
+ 0 1 2
+ 2 -1 0
+ 2 -1 2
+ 2 1 0
+ 2 1 2
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1 3 2
+ 2 3 7 6
+ 6 7 5 4
+ 4 5 1 0
+ 2 6 4 0
+ 7 3 1 5
+ 8 9 11 10
+ 10 11 15 14
+ 14 15 13 12
+ 12 13 9 8
+ 10 14 12 8
+ 15 11 9 13
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 6 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 12);
+ EXPECT_EQ(out.face_size(), 8);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubecubestep");
+ }
+}
+
+TEST(boolean_polymesh, CubeCyl4)
+{
+ const char *spec = R"(16 12
+ 0 1 -1
+ 0 1 1
+ 1 0 -1
+ 1 0 1
+ 0 -1 -1
+ 0 -1 1
+ -1 0 -1
+ -1 0 1
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1 3 2
+ 2 3 5 4
+ 3 1 7 5
+ 4 5 7 6
+ 6 7 1 0
+ 0 2 4 6
+ 8 9 11 10
+ 10 11 15 14
+ 14 15 13 12
+ 12 13 9 8
+ 10 14 12 8
+ 15 11 9 13
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 6 ? 1 : 0; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 16);
+ EXPECT_EQ(out.face_size(), 20);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubecyl4");
+ }
+}
+
+TEST(boolean_polymesh, CubeCubesubdivDiff)
+{
+ /* A cube intersected by a subdivided cube that intersects first cubes edges exactly. */
+ const char *spec = R"(26 22
+ 2 1/3 2
+ 2 -1/3 2
+ 2 -1/3 0
+ 2 1/3 0
+ 0 -1/3 2
+ 0 1/3 2
+ 0 1/3 0
+ 0 -1/3 0
+ 1 1/3 2
+ 1 -1/3 2
+ 1 1/3 0
+ 1 -1/3 0
+ 0 -1/3 1
+ 0 1/3 1
+ 2 1/3 1
+ 2 -1/3 1
+ 1 1/3 1
+ 1 -1/3 1
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 17 9 4 12
+ 13 6 7 12
+ 15 2 3 14
+ 11 7 6 10
+ 16 13 5 8
+ 9 1 0 8
+ 4 9 8 5
+ 14 16 8 0
+ 2 11 10 3
+ 15 1 9 17
+ 2 15 17 11
+ 3 10 16 14
+ 10 6 13 16
+ 1 15 14 0
+ 5 13 12 4
+ 11 17 12 7
+ 19 21 20 18
+ 21 25 24 20
+ 25 23 22 24
+ 23 19 18 22
+ 18 20 24 22
+ 23 25 21 19
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t < 16 ? 1 : 0; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 16);
+ EXPECT_EQ(out.face_size(), 10);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubecubesubdivdiff");
+ }
+}
+
+TEST(boolean_polymesh, CubePlane)
+{
+ const char *spec = R"(12 7
+ -2 -2 0
+ 2 -2 0
+ -2 2 0
+ 2 2 0
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1 3 2
+ 4 5 7 6
+ 6 7 11 10
+ 10 11 9 8
+ 8 9 5 4
+ 6 10 8 4
+ 11 7 5 9
+)";
+
+ IMeshBuilder mb(spec);
+ IMesh out = boolean_mesh(
+ mb.imesh,
+ BoolOpType::Difference,
+ 2,
+ [](int t) { return t >= 1 ? 0 : 1; },
+ false,
+ nullptr,
+ &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 8);
+ EXPECT_EQ(out.face_size(), 6);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "cubeplane");
+ }
+}
+
+} // namespace blender::meshintersect::tests
diff --git a/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc
new file mode 100644
index 00000000000..6d24c4e6c03
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc
@@ -0,0 +1,1072 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+
+#include "PIL_time.h"
+
+#include "BLI_array.hh"
+#include "BLI_math_mpq.hh"
+#include "BLI_mesh_intersect.hh"
+#include "BLI_mpq3.hh"
+#include "BLI_task.h"
+#include "BLI_vector.hh"
+
+#define DO_REGULAR_TESTS 1
+#define DO_PERF_TESTS 0
+
+namespace blender::meshintersect::tests {
+
+constexpr bool DO_OBJ = false;
+
+/* Build and hold an IMesh from a string spec. Also hold and own resources used by IMesh. */
+class IMeshBuilder {
+ public:
+ IMesh imesh;
+ IMeshArena arena;
+
+ /* "Edge orig" indices are an encoding of <input face#, position in face of start of edge>. */
+ static constexpr int MAX_FACE_LEN = 1000; /* Used for forming "orig edge" indices only. */
+
+ static int edge_index(int face_index, int facepos)
+ {
+ return face_index * MAX_FACE_LEN + facepos;
+ }
+
+ static std::pair<int, int> face_and_pos_for_edge_index(int e_index)
+ {
+ return std::pair<int, int>(e_index / MAX_FACE_LEN, e_index % MAX_FACE_LEN);
+ }
+
+ /*
+ * Spec should have form:
+ * #verts #faces
+ * mpq_class mpq_class mpq_clas [#verts lines]
+ * int int int ... [#faces lines; indices into verts for given face]
+ */
+ IMeshBuilder(const char *spec)
+ {
+ std::istringstream ss(spec);
+ std::string line;
+ getline(ss, line);
+ std::istringstream hdrss(line);
+ int nv, nf;
+ hdrss >> nv >> nf;
+ if (nv == 0 || nf == 0) {
+ return;
+ }
+ arena.reserve(nv, nf);
+ Vector<const Vert *> verts;
+ Vector<Face *> faces;
+ bool spec_ok = true;
+ int v_index = 0;
+ while (v_index < nv && spec_ok && getline(ss, line)) {
+ std::istringstream iss(line);
+ mpq_class p0;
+ mpq_class p1;
+ mpq_class p2;
+ iss >> p0 >> p1 >> p2;
+ spec_ok = !iss.fail();
+ if (spec_ok) {
+ verts.append(arena.add_or_find_vert(mpq3(p0, p1, p2), v_index));
+ }
+ ++v_index;
+ }
+ if (v_index != nv) {
+ spec_ok = false;
+ }
+ int f_index = 0;
+ while (f_index < nf && spec_ok && getline(ss, line)) {
+ std::istringstream fss(line);
+ Vector<const Vert *> face_verts;
+ Vector<int> edge_orig;
+ int fpos = 0;
+ while (spec_ok && fss >> v_index) {
+ if (v_index < 0 || v_index >= nv) {
+ spec_ok = false;
+ continue;
+ }
+ face_verts.append(verts[v_index]);
+ edge_orig.append(edge_index(f_index, fpos));
+ ++fpos;
+ }
+ if (fpos < 3) {
+ spec_ok = false;
+ }
+ if (spec_ok) {
+ Face *facep = arena.add_face(face_verts, f_index, edge_orig);
+ faces.append(facep);
+ }
+ ++f_index;
+ }
+ if (f_index != nf) {
+ spec_ok = false;
+ }
+ if (!spec_ok) {
+ std::cout << "Bad spec: " << spec;
+ return;
+ }
+ imesh = IMesh(faces);
+ }
+};
+
+/* Return a const Face * in mesh with verts equal to v0, v1, and v2, in
+ * some cyclic order; return nullptr if not found.
+ */
+static const Face *find_tri_with_verts(const IMesh &mesh,
+ const Vert *v0,
+ const Vert *v1,
+ const Vert *v2)
+{
+ Face f_arg({v0, v1, v2}, 0, NO_INDEX);
+ for (const Face *f : mesh.faces()) {
+ if (f->cyclic_equal(f_arg)) {
+ return f;
+ }
+ }
+ return nullptr;
+}
+
+/* How many instances of a triangle with v0, v1, v2 are in the mesh? */
+static int count_tris_with_verts(const IMesh &mesh, const Vert *v0, const Vert *v1, const Vert *v2)
+{
+ Face f_arg({v0, v1, v2}, 0, NO_INDEX);
+ int ans = 0;
+ for (const Face *f : mesh.faces()) {
+ if (f->cyclic_equal(f_arg)) {
+ ++ans;
+ }
+ }
+ return ans;
+}
+
+/* What is the starting position, if any, of the edge (v0, v1), in either order, in f? -1 if none.
+ */
+static int find_edge_pos_in_tri(const Vert *v0, const Vert *v1, const Face *f)
+{
+ for (int pos : f->index_range()) {
+ int nextpos = f->next_pos(pos);
+ if (((*f)[pos] == v0 && (*f)[nextpos] == v1) || ((*f)[pos] == v1 && (*f)[nextpos] == v0)) {
+ return static_cast<int>(pos);
+ }
+ }
+ return -1;
+}
+
+#if DO_REGULAR_TESTS
+TEST(mesh_intersect, Mesh)
+{
+ Vector<const Vert *> verts;
+ Vector<Face *> faces;
+ IMeshArena arena;
+
+ verts.append(arena.add_or_find_vert(mpq3(0, 0, 1), 0));
+ verts.append(arena.add_or_find_vert(mpq3(1, 0, 1), 1));
+ verts.append(arena.add_or_find_vert(mpq3(0.5, 1, 1), 2));
+ faces.append(arena.add_face(verts, 0, {10, 11, 12}));
+
+ IMesh mesh(faces);
+ const Face *f = mesh.face(0);
+ EXPECT_TRUE(f->is_tri());
+}
+
+TEST(mesh_intersect, OneTri)
+{
+ const char *spec = R"(3 1
+ 0 0 0
+ 1 0 0
+ 1/2 1 0
+ 0 1 2
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh imesh = trimesh_self_intersect(mb.imesh, &mb.arena);
+ imesh.populate_vert();
+ EXPECT_EQ(imesh.vert_size(), 3);
+ EXPECT_EQ(imesh.face_size(), 1);
+ const Face &f_in = *mb.imesh.face(0);
+ const Face &f_out = *imesh.face(0);
+ EXPECT_EQ(f_in.orig, f_out.orig);
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(f_in[i], f_out[i]);
+ EXPECT_EQ(f_in.edge_orig[i], f_out.edge_orig[i]);
+ }
+}
+
+TEST(mesh_intersect, TriTri)
+{
+ const char *spec = R"(6 2
+ 0 0 0
+ 4 0 0
+ 0 4 0
+ 1 0 0
+ 2 0 0
+ 1 1 0
+ 0 1 2
+ 3 4 5
+ )";
+
+ /* Second triangle is smaller and congruent to first, resting on same base, partway along. */
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 6);
+ EXPECT_EQ(out.face_size(), 6);
+ if (out.vert_size() == 6 && out.face_size() == 6) {
+ const Vert *v0 = mb.arena.find_vert(mpq3(0, 0, 0));
+ const Vert *v1 = mb.arena.find_vert(mpq3(4, 0, 0));
+ const Vert *v2 = mb.arena.find_vert(mpq3(0, 4, 0));
+ const Vert *v3 = mb.arena.find_vert(mpq3(1, 0, 0));
+ const Vert *v4 = mb.arena.find_vert(mpq3(2, 0, 0));
+ const Vert *v5 = mb.arena.find_vert(mpq3(1, 1, 0));
+ EXPECT_TRUE(v0 != nullptr && v1 != nullptr && v2 != nullptr);
+ EXPECT_TRUE(v3 != nullptr && v4 != nullptr && v5 != nullptr);
+ if (v0 != nullptr && v1 != nullptr && v2 != nullptr && v3 != nullptr && v4 != nullptr &&
+ v5 != nullptr) {
+ EXPECT_EQ(v0->orig, 0);
+ EXPECT_EQ(v1->orig, 1);
+ const Face *f0 = find_tri_with_verts(out, v4, v1, v5);
+ const Face *f1 = find_tri_with_verts(out, v3, v4, v5);
+ const Face *f2 = find_tri_with_verts(out, v0, v3, v5);
+ const Face *f3 = find_tri_with_verts(out, v0, v5, v2);
+ const Face *f4 = find_tri_with_verts(out, v5, v1, v2);
+ EXPECT_TRUE(f0 != nullptr && f1 != nullptr && f2 != nullptr && f3 != nullptr &&
+ f4 != nullptr);
+ /* For boolean to work right, there need to be two copies of the smaller triangle in the
+ * output. */
+ EXPECT_EQ(count_tris_with_verts(out, v3, v4, v5), 2);
+ if (f0 != nullptr && f1 != nullptr && f2 != nullptr && f3 != nullptr && f4 != nullptr) {
+ EXPECT_EQ(f0->orig, 0);
+ EXPECT_TRUE(f1->orig == 0 || f1->orig == 1);
+ EXPECT_EQ(f2->orig, 0);
+ EXPECT_EQ(f3->orig, 0);
+ EXPECT_EQ(f4->orig, 0);
+ }
+ int e03 = find_edge_pos_in_tri(v0, v3, f2);
+ int e34 = find_edge_pos_in_tri(v3, v4, f1);
+ int e45 = find_edge_pos_in_tri(v4, v5, f1);
+ int e05 = find_edge_pos_in_tri(v0, v5, f3);
+ int e15 = find_edge_pos_in_tri(v1, v5, f0);
+ EXPECT_TRUE(e03 != -1 && e34 != -1 && e45 != -1 && e05 != -1 && e15 != -1);
+ if (e03 != -1 && e34 != -1 && e45 != -1 && e05 != -1 && e15 != -1) {
+ EXPECT_EQ(f2->edge_orig[e03], 0);
+ EXPECT_TRUE(f1->edge_orig[e34] == 0 ||
+ f1->edge_orig[e34] == 1 * IMeshBuilder::MAX_FACE_LEN);
+ EXPECT_EQ(f1->edge_orig[e45], 1 * IMeshBuilder::MAX_FACE_LEN + 1);
+ EXPECT_EQ(f3->edge_orig[e05], NO_INDEX);
+ EXPECT_EQ(f0->edge_orig[e15], NO_INDEX);
+ }
+ }
+ }
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tritri");
+ }
+}
+
+TEST(mesh_intersect, TriTriReversed)
+{
+ /* Like TriTri but with triangles of opposite orientation.
+ * This matters because projection to 2D will now need reversed triangles. */
+ const char *spec = R"(6 2
+ 0 0 0
+ 4 0 0
+ 0 4 0
+ 1 0 0
+ 2 0 0
+ 1 1 0
+ 0 2 1
+ 3 5 4
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 6);
+ EXPECT_EQ(out.face_size(), 6);
+ if (out.vert_size() == 6 && out.face_size() == 6) {
+ const Vert *v0 = mb.arena.find_vert(mpq3(0, 0, 0));
+ const Vert *v1 = mb.arena.find_vert(mpq3(4, 0, 0));
+ const Vert *v2 = mb.arena.find_vert(mpq3(0, 4, 0));
+ const Vert *v3 = mb.arena.find_vert(mpq3(1, 0, 0));
+ const Vert *v4 = mb.arena.find_vert(mpq3(2, 0, 0));
+ const Vert *v5 = mb.arena.find_vert(mpq3(1, 1, 0));
+ EXPECT_TRUE(v0 != nullptr && v1 != nullptr && v2 != nullptr);
+ EXPECT_TRUE(v3 != nullptr && v4 != nullptr && v5 != nullptr);
+ if (v0 != nullptr && v1 != nullptr && v2 != nullptr && v3 != nullptr && v4 != nullptr &&
+ v5 != nullptr) {
+ EXPECT_EQ(v0->orig, 0);
+ EXPECT_EQ(v1->orig, 1);
+ const Face *f0 = find_tri_with_verts(out, v4, v5, v1);
+ const Face *f1 = find_tri_with_verts(out, v3, v5, v4);
+ const Face *f2 = find_tri_with_verts(out, v0, v5, v3);
+ const Face *f3 = find_tri_with_verts(out, v0, v2, v5);
+ const Face *f4 = find_tri_with_verts(out, v5, v2, v1);
+ EXPECT_TRUE(f0 != nullptr && f1 != nullptr && f2 != nullptr && f3 != nullptr &&
+ f4 != nullptr);
+ /* For boolean to work right, there need to be two copies of the smaller triangle in the
+ * output. */
+ EXPECT_EQ(count_tris_with_verts(out, v3, v5, v4), 2);
+ if (f0 != nullptr && f1 != nullptr && f2 != nullptr && f3 != nullptr && f4 != nullptr) {
+ EXPECT_EQ(f0->orig, 0);
+ EXPECT_TRUE(f1->orig == 0 || f1->orig == 1);
+ EXPECT_EQ(f2->orig, 0);
+ EXPECT_EQ(f3->orig, 0);
+ EXPECT_EQ(f4->orig, 0);
+ }
+ int e03 = find_edge_pos_in_tri(v0, v3, f2);
+ int e34 = find_edge_pos_in_tri(v3, v4, f1);
+ int e45 = find_edge_pos_in_tri(v4, v5, f1);
+ int e05 = find_edge_pos_in_tri(v0, v5, f3);
+ int e15 = find_edge_pos_in_tri(v1, v5, f0);
+ EXPECT_TRUE(e03 != -1 && e34 != -1 && e45 != -1 && e05 != -1 && e15 != -1);
+ if (e03 != -1 && e34 != -1 && e45 != -1 && e05 != -1 && e15 != -1) {
+ EXPECT_EQ(f2->edge_orig[e03], 2);
+ EXPECT_TRUE(f1->edge_orig[e34] == 2 ||
+ f1->edge_orig[e34] == 1 * IMeshBuilder::MAX_FACE_LEN + 2);
+ EXPECT_EQ(f1->edge_orig[e45], 1 * IMeshBuilder::MAX_FACE_LEN + 1);
+ EXPECT_EQ(f3->edge_orig[e05], NO_INDEX);
+ EXPECT_EQ(f0->edge_orig[e15], NO_INDEX);
+ }
+ }
+ }
+ if (DO_OBJ) {
+ write_obj_mesh(out, "tritrirev");
+ }
+}
+
+TEST(mesh_intersect, TwoTris)
+{
+ Array<mpq3> verts = {
+ mpq3(1, 1, 1), mpq3(1, 4, 1), mpq3(1, 1, 4), /* T0 */
+ mpq3(2, 2, 2), mpq3(-3, 3, 2), mpq3(-4, 1, 3), /* T1 */
+ mpq3(2, 2, 2), mpq3(-3, 3, 2), mpq3(0, 3, 5), /* T2 */
+ mpq3(2, 2, 2), mpq3(-3, 3, 2), mpq3(0, 3, 3), /* T3 */
+ mpq3(1, 0, 0), mpq3(2, 4, 1), mpq3(-3, 2, 2), /* T4 */
+ mpq3(0, 2, 1), mpq3(-2, 3, 3), mpq3(0, 1, 3), /* T5 */
+ mpq3(1.5, 2, 0.5), mpq3(-2, 3, 3), mpq3(0, 1, 3), /* T6 */
+ mpq3(1, 0, 0), mpq3(-2, 3, 3), mpq3(0, 1, 3), /* T7 */
+ mpq3(1, 0, 0), mpq3(-3, 2, 2), mpq3(0, 1, 3), /* T8 */
+ mpq3(1, 0, 0), mpq3(-1, 1, 1), mpq3(0, 1, 3), /* T9 */
+ mpq3(3, -1, -1), mpq3(-1, 1, 1), mpq3(0, 1, 3), /* T10 */
+ mpq3(0, 0.5, 0.5), mpq3(-1, 1, 1), mpq3(0, 1, 3), /* T11 */
+ mpq3(2, 1, 1), mpq3(3, 5, 2), mpq3(-2, 3, 3), /* T12 */
+ mpq3(2, 1, 1), mpq3(3, 5, 2), mpq3(-2, 3, 4), /* T13 */
+ mpq3(2, 2, 5), mpq3(-3, 3, 5), mpq3(0, 3, 10), /* T14 */
+ mpq3(0, 0, 0), mpq3(4, 4, 0), mpq3(-4, 2, 4), /* T15 */
+ mpq3(0, 1.5, 1), mpq3(1, 2.5, 1), mpq3(-1, 2, 2), /* T16 */
+ mpq3(3, 0, -2), mpq3(7, 4, -2), mpq3(-1, 2, 2), /* T17 */
+ mpq3(3, 0, -2), mpq3(3, 6, 2), mpq3(-1, 2, 2), /* T18 */
+ mpq3(7, 4, -2), mpq3(3, 6, 2), mpq3(-1, 2, 2), /* T19 */
+ mpq3(5, 2, -2), mpq3(1, 4, 2), mpq3(-3, 0, 2), /* T20 */
+ mpq3(2, 2, 0), mpq3(1, 4, 2), mpq3(-3, 0, 2), /* T21 */
+ mpq3(0, 0, 0), mpq3(4, 4, 0), mpq3(-3, 0, 2), /* T22 */
+ mpq3(0, 0, 0), mpq3(4, 4, 0), mpq3(-1, 2, 2), /* T23 */
+ mpq3(2, 2, 0), mpq3(4, 4, 0), mpq3(0, 3, 2), /* T24 */
+ mpq3(0, 0, 0), mpq3(-4, 2, 4), mpq3(4, 4, 0), /* T25 */
+ };
+ struct two_tri_test_spec {
+ int t0;
+ int t1;
+ int nv_out;
+ int nf_out;
+ };
+ Array<two_tri_test_spec> test_tris = Span<two_tri_test_spec>{
+ {0, 1, 8, 8}, /* 0: T1 pierces T0 inside at (1,11/6,13/6) and (1,11/5,2). */
+ {0, 2, 8, 8}, /* 1: T2 intersects T0 inside (1,11/5,2) and edge (1,7/3,8/3). */
+ {0, 3, 8, 7}, /* 2: T3 intersects T0 (1,11/5,2) and edge-edge (1,5/2,5/2). */
+ {4, 5, 6, 4}, /* 3: T5 touches T4 inside (0,2,1). */
+ {4, 6, 6, 3}, /* 4: T6 touches T4 on edge (3/2,2/1/2). */
+ {4, 7, 5, 2}, /* 5: T7 touches T4 on vert (1,0,0). */
+ {4, 8, 4, 2}, /* 6: T8 shared edge with T4 (1,0,0)(-3,2,2). */
+ {4, 9, 5, 3}, /* 7: T9 edge (1,0,0)(-1,1,1) is subset of T4 edge. */
+ {4, 10, 6, 4}, /* 8: T10 edge overlaps T4 edge with seg (-1,1,0)(1,0,0). */
+ {4, 11, 6, 4}, /* 9: T11 edge (-1,1,1)(0,1/2,1/2) inside T4 edge. */
+ {4, 12, 6, 2}, /* 10: parallel planes, not intersecting. */
+ {4, 13, 6, 2}, /* 11: non-parallel planes, not intersecting, all one side. */
+ {0, 14, 6, 2}, /* 12: non-paralel planes, not intersecting, alternate sides. */
+ /* Following are all coplanar cases. */
+ {15, 16, 6, 8}, /* 13: T16 inside T15. Note: dup'd tri is expected. */
+ {15, 17, 8, 8}, /* 14: T17 intersects one edge of T15 at (1,1,0)(3,3,0). */
+ {15, 18, 10, 12}, /* 15: T18 intersects T15 at (1,1,0)(3,3,0)(3,15/4,1/2)(0,3,2). */
+ {15, 19, 8, 10}, /* 16: T19 intersects T15 at (3,3,0)(0,3,2). */
+ {15, 20, 12, 14}, /* 17: T20 intersects T15 on three edges, six intersects. */
+ {15, 21, 10, 11}, /* 18: T21 intersects T15 on three edges, touching one. */
+ {15, 22, 5, 4}, /* 19: T22 shares edge T15, one other outside. */
+ {15, 23, 4, 4}, /* 20: T23 shares edge T15, one other outside. */
+ {15, 24, 5, 4}, /* 21: T24 shares two edges with T15. */
+ {15, 25, 3, 2}, /* 22: T25 same T15, reverse orientation. */
+ };
+ static int perms[6][3] = {{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}};
+
+ const int do_only_test = -1; /* Make this negative to do all tests. */
+ for (int test = 0; test < test_tris.size(); ++test) {
+ if (do_only_test >= 0 && test != do_only_test) {
+ continue;
+ }
+ int tri1_index = test_tris[test].t0;
+ int tri2_index = test_tris[test].t1;
+ int co1_i = 3 * tri1_index;
+ int co2_i = 3 * tri2_index;
+
+ const bool verbose = false;
+
+ if (verbose) {
+ std::cout << "\nTest " << test << ": T" << tri1_index << " intersect T" << tri2_index
+ << "\n";
+ }
+
+ const bool do_all_perms = true;
+ const int perm_limit = do_all_perms ? 3 : 1;
+
+ for (int i = 0; i < perm_limit; ++i) {
+ for (int j = 0; j < perm_limit; ++j) {
+ if (do_all_perms && verbose) {
+ std::cout << "\nperms " << i << " " << j << "\n";
+ }
+ IMeshArena arena;
+ arena.reserve(2 * 3, 2);
+ Array<const Vert *> f0_verts(3);
+ Array<const Vert *> f1_verts(3);
+ for (int k = 0; k < 3; ++k) {
+ f0_verts[k] = arena.add_or_find_vert(verts[co1_i + perms[i][k]], k);
+ }
+ for (int k = 0; k < 3; ++k) {
+ f1_verts[k] = arena.add_or_find_vert(verts[co2_i + perms[i][k]], k + 3);
+ }
+ Face *f0 = arena.add_face(f0_verts, 0, {0, 1, 2});
+ Face *f1 = arena.add_face(f1_verts, 1, {3, 4, 5});
+ IMesh in_mesh({f0, f1});
+ IMesh out_mesh = trimesh_self_intersect(in_mesh, &arena);
+ out_mesh.populate_vert();
+ EXPECT_EQ(out_mesh.vert_size(), test_tris[test].nv_out);
+ EXPECT_EQ(out_mesh.face_size(), test_tris[test].nf_out);
+ bool constexpr dump_input = true;
+ if (DO_OBJ && i == 0 && j == 0) {
+ if (dump_input) {
+ std::string name = "test_tt_in" + std::to_string(test);
+ write_obj_mesh(in_mesh, name);
+ }
+ std::string name = "test_tt" + std::to_string(test);
+ write_obj_mesh(out_mesh, name);
+ }
+ }
+ }
+ }
+}
+
+TEST(mesh_intersect, OverlapCluster)
+{
+ /* Chain of 5 overlapping coplanar tris.
+ * Ordered so that clustering will make two separate clusters
+ * that it will have to merge into one cluster with everything. */
+ const char *spec = R"(15 5
+ 0 0 0
+ 1 0 0
+ 1/2 1 0
+ 1/2 0 0
+ 3/2 0 0
+ 1 1 0
+ 1 0 0
+ 2 0 0
+ 3/2 1 0
+ 3/2 0 0
+ 5/2 0 0
+ 2 1 0
+ 2 0 0
+ 3 0 0
+ 5/2 1 0
+ 0 1 2
+ 3 4 5
+ 9 10 11
+ 12 13 14
+ 6 7 8
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 16);
+ EXPECT_EQ(out.face_size(), 18);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "overlapcluster");
+ }
+}
+
+TEST(mesh_intersect, TriCornerCross1)
+{
+ /* A corner formed by 3 tris, and a 4th crossing two of them. */
+ const char *spec = R"(12 4
+ 0 0 0
+ 1 0 0
+ 0 0 1
+ 0 0 0
+ 0 1 0
+ 0 0 1
+ 0 0 0
+ 1 0 0
+ 0 1 0
+ 1 1 1/2
+ 1 -2 1/2
+ -2 1 1/2
+ 0 1 2
+ 3 4 5
+ 6 7 8
+ 9 10 11
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 10);
+ EXPECT_EQ(out.face_size(), 14);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "test_tc_1");
+ }
+}
+
+TEST(mesh_intersect, TriCornerCross2)
+{
+ /* A corner formed by 3 tris, and a 4th coplanar with base. */
+ const char *spec = R"(12 4
+ 0 0 0
+ 1 0 0
+ 0 0 1
+ 0 0 0
+ 0 1 0
+ 0 0 1
+ 0 0 0
+ 1 0 0
+ 0 1 0
+ 1 1 0
+ 1 -2 0
+ -2 1 0
+ 0 1 2
+ 3 4 5
+ 6 7 8
+ 9 10 11
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 7);
+ EXPECT_EQ(out.face_size(), 8);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "test_tc_2");
+ }
+}
+
+TEST(mesh_intersect, TriCornerCross3)
+{
+ /* A corner formed by 3 tris, and a 4th crossing all 3. */
+ const char *spec = R"(12 4
+ 0 0 0
+ 1 0 0
+ 0 0 1
+ 0 0 0
+ 0 1 0
+ 0 0 1
+ 0 0 0
+ 1 0 0
+ 0 1 0
+ 3/2 -1/2 -1/4
+ -1/2 3/2 -1/4
+ -1/2 -1/2 3/4
+ 0 1 2
+ 3 4 5
+ 6 7 8
+ 9 10 11
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 10);
+ EXPECT_EQ(out.face_size(), 16);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "test_tc_3");
+ }
+}
+
+TEST(mesh_intersect, TetTet)
+{
+ const char *spec = R"(8 8
+ 0 0 0
+ 2 0 0
+ 1 2 0
+ 1 1 2
+ 0 0 1
+ 2 0 1
+ 1 2 1
+ 1 1 3
+ 0 1 2
+ 0 3 1
+ 1 3 2
+ 2 3 0
+ 4 5 6
+ 4 7 5
+ 5 7 6
+ 6 7 4
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 11);
+ EXPECT_EQ(out.face_size(), 20);
+ /* Expect there to be a triangle with these three verts, oriented this way, with original face 1.
+ */
+ const Vert *v1 = mb.arena.find_vert(mpq3(2, 0, 0));
+ const Vert *v8 = mb.arena.find_vert(mpq3(0.5, 0.5, 1));
+ const Vert *v9 = mb.arena.find_vert(mpq3(1.5, 0.5, 1));
+ EXPECT_TRUE(v1 != nullptr && v8 != nullptr && v9 != nullptr);
+ const Face *f = mb.arena.find_face({v1, v8, v9});
+ EXPECT_NE(f, nullptr);
+ EXPECT_EQ(f->orig, 1);
+ int v1pos = f->vert[0] == v1 ? 0 : (f->vert[1] == v1 ? 1 : 2);
+ EXPECT_EQ(f->edge_orig[v1pos], NO_INDEX);
+ EXPECT_EQ(f->edge_orig[(v1pos + 1) % 3], NO_INDEX);
+ EXPECT_EQ(f->edge_orig[(v1pos + 2) % 3], 1001);
+ EXPECT_EQ(f->is_intersect[v1pos], false);
+ EXPECT_EQ(f->is_intersect[(v1pos + 1) % 3], true);
+ EXPECT_EQ(f->is_intersect[(v1pos + 2) % 3], false);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "test_tc_3");
+ }
+}
+
+TEST(mesh_intersect, CubeCubeStep)
+{
+ const char *spec = R"(16 24
+ 0 -1 0
+ 0 -1 2
+ 0 1 0
+ 0 1 2
+ 2 -1 0
+ 2 -1 2
+ 2 1 0
+ 2 1 2
+ -1 -1 -1
+ -1 -1 1
+ -1 1 -1
+ -1 1 1
+ 1 -1 -1
+ 1 -1 1
+ 1 1 -1
+ 1 1 1
+ 0 1 3
+ 0 3 2
+ 2 3 7
+ 2 7 6
+ 6 7 5
+ 6 5 4
+ 4 5 1
+ 4 1 0
+ 2 6 4
+ 2 4 0
+ 7 3 1
+ 7 1 5
+ 8 9 11
+ 8 11 10
+ 10 11 15
+ 10 15 14
+ 14 15 13
+ 14 13 12
+ 12 13 9
+ 12 9 8
+ 10 14 12
+ 10 12 8
+ 15 11 9
+ 15 9 13
+ )";
+
+ IMeshBuilder mb(spec);
+ IMesh out = trimesh_self_intersect(mb.imesh, &mb.arena);
+ out.populate_vert();
+ EXPECT_EQ(out.vert_size(), 22);
+ EXPECT_EQ(out.face_size(), 56);
+ if (DO_OBJ) {
+ write_obj_mesh(out, "test_cubecubestep");
+ }
+
+ IMeshBuilder mb2(spec);
+ IMesh out2 = trimesh_nary_intersect(
+ mb2.imesh, 2, [](int t) { return t < 12 ? 0 : 1; }, false, &mb2.arena);
+ out2.populate_vert();
+ EXPECT_EQ(out2.vert_size(), 22);
+ EXPECT_EQ(out2.face_size(), 56);
+ if (DO_OBJ) {
+ write_obj_mesh(out2, "test_cubecubestep_nary");
+ }
+}
+#endif
+
+#if DO_PERF_TESTS
+
+static void get_sphere_params(
+ int nrings, int nsegs, bool triangulate, int *r_num_verts, int *r_num_faces)
+{
+ *r_num_verts = nsegs * (nrings - 1) + 2;
+ if (triangulate) {
+ *r_num_faces = 2 * nsegs + 2 * nsegs * (nrings - 2);
+ }
+ else {
+ *r_num_faces = nsegs * nrings;
+ }
+}
+
+static void fill_sphere_data(int nrings,
+ int nsegs,
+ const double3 &center,
+ double radius,
+ bool triangulate,
+ MutableSpan<Face *> face,
+ int vid_start,
+ int fid_start,
+ IMeshArena *arena)
+{
+ int num_verts;
+ int num_faces;
+ get_sphere_params(nrings, nsegs, triangulate, &num_verts, &num_faces);
+ BLI_assert(num_faces == face.size());
+ Array<const Vert *> vert(num_verts);
+ const bool nrings_even = (nrings % 2 == 0);
+ int half_nrings = nrings / 2;
+ const bool nsegs_even = (nsegs % 2) == 0;
+ const bool nsegs_four_divisible = (nsegs % 4 == 0);
+ int half_nsegs = nrings;
+ int quarter_nsegs = half_nsegs / 2;
+ double delta_phi = 2 * M_PI / nsegs;
+ double delta_theta = M_PI / nrings;
+ int fid = fid_start;
+ int vid = vid_start;
+ auto vert_index_fn = [nrings, num_verts](int seg, int ring) {
+ if (ring == 0) { /* Top vert. */
+ return num_verts - 2;
+ }
+ if (ring == nrings) { /* Bottom vert. */
+ return num_verts - 1;
+ }
+ return seg * (nrings - 1) + (ring - 1);
+ };
+ auto face_index_fn = [nrings](int seg, int ring) { return seg * nrings + ring; };
+ auto tri_index_fn = [nrings, nsegs](int seg, int ring, int tri) {
+ if (ring == 0) {
+ return seg;
+ }
+ if (ring < nrings - 1) {
+ return nsegs + 2 * (ring - 1) * nsegs + 2 * seg + tri;
+ }
+ return nsegs + 2 * (nrings - 2) * nsegs + seg;
+ };
+ Array<int> eid = {0, 0, 0, 0}; /* Don't care about edge ids. */
+ /*
+ * (x, y , z) is given from inclination theta and azimuth phi,
+ * where 0 <= theta <= pi; 0 <= phi <= 2pi.
+ * x = radius * sin(theta) cos(phi)
+ * y = radius * sin(theta) sin(phi)
+ * z = radius * cos(theta)
+ */
+ for (int s = 0; s < nsegs; ++s) {
+ double phi = s * delta_phi;
+ double sin_phi;
+ double cos_phi;
+ /* Avoid use of trig functions for pi/2 divisible angles. */
+ if (s == 0) {
+ /* phi = 0. */
+ sin_phi = 0.0;
+ cos_phi = 1.0;
+ }
+ else if (nsegs_even && s == half_nsegs) {
+ /* phi = pi. */
+ sin_phi = 0.0;
+ cos_phi = -1.0;
+ }
+ else if (nsegs_four_divisible && s == quarter_nsegs) {
+ /* phi = pi/2. */
+ sin_phi = 1.0;
+ cos_phi = 0.0;
+ }
+ else if (nsegs_four_divisible && s == 3 * quarter_nsegs) {
+ /* phi = 3pi/2. */
+ sin_phi = -1.0;
+ cos_phi = 0.0;
+ }
+ else {
+ sin_phi = sin(phi);
+ cos_phi = cos(phi);
+ }
+ for (int r = 1; r < nrings; ++r) {
+ double theta = r * delta_theta;
+ double r_sin_theta;
+ double r_cos_theta;
+ if (nrings_even && r == half_nrings) {
+ /* theta = pi/2. */
+ r_sin_theta = radius;
+ r_cos_theta = 0.0;
+ }
+ else {
+ r_sin_theta = radius * sin(theta);
+ r_cos_theta = radius * cos(theta);
+ }
+ double x = r_sin_theta * cos_phi + center[0];
+ double y = r_sin_theta * sin_phi + center[1];
+ double z = r_cos_theta + center[2];
+ const Vert *v = arena->add_or_find_vert(mpq3(x, y, z), vid++);
+ vert[vert_index_fn(s, r)] = v;
+ }
+ }
+ const Vert *vtop = arena->add_or_find_vert(mpq3(center[0], center[1], center[2] + radius),
+ vid++);
+ const Vert *vbot = arena->add_or_find_vert(mpq3(center[0], center[1], center[2] - radius),
+ vid++);
+ vert[vert_index_fn(0, 0)] = vtop;
+ vert[vert_index_fn(0, nrings)] = vbot;
+ for (int s = 0; s < nsegs; ++s) {
+ int snext = (s + 1) % nsegs;
+ for (int r = 0; r < nrings; ++r) {
+ int rnext = r + 1;
+ int i0 = vert_index_fn(s, r);
+ int i1 = vert_index_fn(s, rnext);
+ int i2 = vert_index_fn(snext, rnext);
+ int i3 = vert_index_fn(snext, r);
+ Face *f;
+ Face *f2 = nullptr;
+ if (r == 0) {
+ f = arena->add_face({vert[i0], vert[i1], vert[i2]}, fid++, eid);
+ }
+ else if (r == nrings - 1) {
+ f = arena->add_face({vert[i0], vert[i1], vert[i3]}, fid++, eid);
+ }
+ else {
+ if (triangulate) {
+ f = arena->add_face({vert[i0], vert[i1], vert[i2]}, fid++, eid);
+ f2 = arena->add_face({vert[i2], vert[i3], vert[i0]}, fid++, eid);
+ }
+ else {
+ f = arena->add_face({vert[i0], vert[i1], vert[i2], vert[i3]}, fid++, eid);
+ }
+ }
+ if (triangulate) {
+ int f_index = tri_index_fn(s, r, 0);
+ face[f_index] = f;
+ if (r != 0 && r != nrings - 1) {
+ int f_index2 = tri_index_fn(s, r, 1);
+ face[f_index2] = f2;
+ }
+ }
+ else {
+ int f_index = face_index_fn(s, r);
+ face[f_index] = f;
+ }
+ }
+ }
+}
+
+static void spheresphere_test(int nrings, double y_offset, bool use_self)
+{
+ /* Make two uvspheres with nrings rings ad 2*nrings segments. */
+ if (nrings < 2) {
+ return;
+ }
+ BLI_task_scheduler_init(); /* Without this, no parallelism. */
+ double time_start = PIL_check_seconds_timer();
+ IMeshArena arena;
+ int nsegs = 2 * nrings;
+ int num_sphere_verts;
+ int num_sphere_tris;
+ get_sphere_params(nrings, nsegs, true, &num_sphere_verts, &num_sphere_tris);
+ Array<Face *> tris(2 * num_sphere_tris);
+ arena.reserve(2 * num_sphere_verts, 2 * num_sphere_tris);
+ double3 center1(0.0, 0.0, 0.0);
+ fill_sphere_data(nrings,
+ nsegs,
+ center1,
+ 1.0,
+ true,
+ MutableSpan<Face *>(tris.begin(), num_sphere_tris),
+ 0,
+ 0,
+ &arena);
+ double3 center2(0.0, y_offset, 0.0);
+ fill_sphere_data(nrings,
+ nsegs,
+ center2,
+ 1.0,
+ true,
+ MutableSpan<Face *>(tris.begin() + num_sphere_tris, num_sphere_tris),
+ num_sphere_verts,
+ num_sphere_verts,
+ &arena);
+ IMesh mesh(tris);
+ double time_create = PIL_check_seconds_timer();
+ // write_obj_mesh(mesh, "spheresphere_in");
+ IMesh out;
+ if (use_self) {
+ out = trimesh_self_intersect(mesh, &arena);
+ }
+ else {
+ int nf = num_sphere_tris;
+ out = trimesh_nary_intersect(
+ mesh, 2, [nf](int t) { return t < nf ? 0 : 1; }, false, &arena);
+ }
+ double time_intersect = PIL_check_seconds_timer();
+ std::cout << "Create time: " << time_create - time_start << "\n";
+ std::cout << "Intersect time: " << time_intersect - time_create << "\n";
+ std::cout << "Total time: " << time_intersect - time_start << "\n";
+ if (DO_OBJ) {
+ write_obj_mesh(out, "spheresphere");
+ }
+ BLI_task_scheduler_exit();
+}
+
+static void get_grid_params(
+ int x_subdiv, int y_subdiv, bool triangulate, int *r_num_verts, int *r_num_faces)
+{
+ *r_num_verts = x_subdiv * y_subdiv;
+ if (triangulate) {
+ *r_num_faces = 2 * (x_subdiv - 1) * (y_subdiv - 1);
+ }
+ else {
+ *r_num_faces = (x_subdiv - 1) * (y_subdiv - 1);
+ }
+}
+
+static void fill_grid_data(int x_subdiv,
+ int y_subdiv,
+ bool triangulate,
+ double size,
+ const double3 &center,
+ MutableSpan<Face *> face,
+ int vid_start,
+ int fid_start,
+ IMeshArena *arena)
+{
+ if (x_subdiv <= 1 || y_subdiv <= 1) {
+ return;
+ }
+ int num_verts;
+ int num_faces;
+ get_grid_params(x_subdiv, y_subdiv, triangulate, &num_verts, &num_faces);
+ BLI_assert(face.size() == num_faces);
+ Array<const Vert *> vert(num_verts);
+ auto vert_index_fn = [x_subdiv](int ix, int iy) { return iy * x_subdiv + ix; };
+ auto face_index_fn = [x_subdiv](int ix, int iy) { return iy * (x_subdiv - 1) + ix; };
+ auto tri_index_fn = [x_subdiv](int ix, int iy, int tri) {
+ return 2 * iy * (x_subdiv - 1) + 2 * ix + tri;
+ };
+ Array<int> eid = {0, 0, 0, 0}; /* Don't care about edge ids. */
+ double r = size / 2.0;
+ double delta_x = size / (x_subdiv - 1);
+ double delta_y = size / (y_subdiv - 1);
+ int vid = vid_start;
+ for (int iy = 0; iy < y_subdiv; ++iy) {
+ for (int ix = 0; ix < x_subdiv; ++ix) {
+ double x = center[0] - r + ix * delta_x;
+ double y = center[1] - r + iy * delta_y;
+ double z = center[2];
+ const Vert *v = arena->add_or_find_vert(mpq3(x, y, z), vid++);
+ vert[vert_index_fn(ix, iy)] = v;
+ }
+ }
+ int fid = fid_start;
+ for (int iy = 0; iy < y_subdiv - 1; ++iy) {
+ for (int ix = 0; ix < x_subdiv - 1; ++ix) {
+ int i0 = vert_index_fn(ix, iy);
+ int i1 = vert_index_fn(ix, iy + 1);
+ int i2 = vert_index_fn(ix + 1, iy + 1);
+ int i3 = vert_index_fn(ix + 1, iy);
+ if (triangulate) {
+ Face *f = arena->add_face({vert[i0], vert[i1], vert[i2]}, fid++, eid);
+ Face *f2 = arena->add_face({vert[i2], vert[i3], vert[i0]}, fid++, eid);
+ face[tri_index_fn(ix, iy, 0)] = f;
+ face[tri_index_fn(ix, iy, 1)] = f2;
+ }
+ else {
+ Face *f = arena->add_face({vert[i0], vert[i1], vert[i2], vert[i3]}, fid++, eid);
+ face[face_index_fn(ix, iy)] = f;
+ }
+ }
+ }
+}
+
+static void spheregrid_test(int nrings, int grid_level, double z_offset, bool use_self)
+{
+ /* Make a uvsphere and a grid.
+ * The sphere is radius 1, has nrings rings and 2 * nrings segs,
+ * and is centered at (0,0,z_offset).
+ * The plane is 4x4, has 2**grid_level subdivisions x and y,
+ * and is centered at the origin. */
+ if (nrings < 2 || grid_level < 1) {
+ return;
+ }
+ BLI_task_scheduler_init(); /* Without this, no parallelism. */
+ double time_start = PIL_check_seconds_timer();
+ IMeshArena arena;
+ int num_sphere_verts;
+ int num_sphere_tris;
+ int nsegs = 2 * nrings;
+ int num_grid_verts;
+ int num_grid_tris;
+ int subdivs = 1 << grid_level;
+ get_sphere_params(nrings, nsegs, true, &num_sphere_verts, &num_sphere_tris);
+ get_grid_params(subdivs, subdivs, true, &num_grid_verts, &num_grid_tris);
+ Array<Face *> tris(num_sphere_tris + num_grid_tris);
+ arena.reserve(num_sphere_verts + num_grid_verts, num_sphere_tris + num_grid_tris);
+ double3 center(0.0, 0.0, z_offset);
+ fill_sphere_data(nrings,
+ nsegs,
+ center,
+ 1.0,
+ true,
+ MutableSpan<Face *>(tris.begin(), num_sphere_tris),
+ 0,
+ 0,
+ &arena);
+ fill_grid_data(subdivs,
+ subdivs,
+ true,
+ 4.0,
+ double3(0, 0, 0),
+ MutableSpan<Face *>(tris.begin() + num_sphere_tris, num_grid_tris),
+ num_sphere_verts,
+ num_sphere_tris,
+ &arena);
+ IMesh mesh(tris);
+ double time_create = PIL_check_seconds_timer();
+ // write_obj_mesh(mesh, "spheregrid_in");
+ IMesh out;
+ if (use_self) {
+ out = trimesh_self_intersect(mesh, &arena);
+ }
+ else {
+ int nf = num_sphere_tris;
+ out = trimesh_nary_intersect(
+ mesh, 2, [nf](int t) { return t < nf ? 0 : 1; }, false, &arena);
+ }
+ double time_intersect = PIL_check_seconds_timer();
+ std::cout << "Create time: " << time_create - time_start << "\n";
+ std::cout << "Intersect time: " << time_intersect - time_create << "\n";
+ std::cout << "Total time: " << time_intersect - time_start << "\n";
+ if (DO_OBJ) {
+ write_obj_mesh(out, "spheregrid");
+ }
+ BLI_task_scheduler_exit();
+}
+
+TEST(mesh_intersect_perf, SphereSphere)
+{
+ spheresphere_test(64, 0.5, false);
+}
+
+TEST(mesh_intersect_perf, SphereGrid)
+{
+ spheregrid_test(64, 4, 0.1, false);
+}
+
+#endif
+
+} // namespace blender::meshintersect::tests