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:
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r--source/blender/blenlib/BLI_array.h2
-rw-r--r--source/blender/blenlib/BLI_array.hh144
-rw-r--r--source/blender/blenlib/BLI_compiler_compat.h1
-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_listbase.h9
-rw-r--r--source/blender/blenlib/BLI_map.hh277
-rw-r--r--source/blender/blenlib/BLI_map_slots.hh95
-rw-r--r--source/blender/blenlib/BLI_math_base.h8
-rw-r--r--source/blender/blenlib/BLI_math_boolean.hh62
-rw-r--r--source/blender/blenlib/BLI_math_color.h2
-rw-r--r--source/blender/blenlib/BLI_math_matrix.h3
-rw-r--r--source/blender/blenlib/BLI_math_mpq.hh36
-rw-r--r--source/blender/blenlib/BLI_math_vector.h12
-rw-r--r--source/blender/blenlib/BLI_memory_utils.hh56
-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/BLI_set.hh114
-rw-r--r--source/blender/blenlib/BLI_set_slots.hh47
-rw-r--r--source/blender/blenlib/BLI_span.hh20
-rw-r--r--source/blender/blenlib/BLI_stack.hh95
-rw-r--r--source/blender/blenlib/BLI_utildefines.h6
-rw-r--r--source/blender/blenlib/BLI_vector.hh226
-rw-r--r--source/blender/blenlib/BLI_winstuff.h12
-rw-r--r--source/blender/blenlib/CMakeLists.txt31
-rw-r--r--source/blender/blenlib/intern/BLI_kdopbvh.c39
-rw-r--r--source/blender/blenlib/intern/delaunay_2d.c5170
-rw-r--r--source/blender/blenlib/intern/delaunay_2d.cc2500
-rw-r--r--source/blender/blenlib/intern/freetypefont.c3
-rw-r--r--source/blender/blenlib/intern/math_boolean.cc2533
-rw-r--r--source/blender/blenlib/intern/math_color.c2
-rw-r--r--source/blender/blenlib/intern/math_matrix.c109
-rw-r--r--source/blender/blenlib/intern/math_vec.cc195
-rw-r--r--source/blender/blenlib/intern/math_vector.c20
-rw-r--r--source/blender/blenlib/intern/math_vector_inline.c50
-rw-r--r--source/blender/blenlib/intern/mesh_boolean.cc3382
-rw-r--r--source/blender/blenlib/intern/mesh_intersect.cc3322
-rw-r--r--source/blender/blenlib/intern/path_util.c6
-rw-r--r--source/blender/blenlib/intern/winstuff.c52
-rw-r--r--source/blender/blenlib/tests/BLI_array_test.cc77
-rw-r--r--source/blender/blenlib/tests/BLI_delaunay_2d_test.cc2378
-rw-r--r--source/blender/blenlib/tests/BLI_exception_safety_test_utils.hh102
-rw-r--r--source/blender/blenlib/tests/BLI_map_test.cc67
-rw-r--r--source/blender/blenlib/tests/BLI_memory_utils_test.cc2
-rw-r--r--source/blender/blenlib/tests/BLI_mesh_boolean_test.cc910
-rw-r--r--source/blender/blenlib/tests/BLI_mesh_intersect_test.cc1074
-rw-r--r--source/blender/blenlib/tests/BLI_set_test.cc50
-rw-r--r--source/blender/blenlib/tests/BLI_span_test.cc31
-rw-r--r--source/blender/blenlib/tests/BLI_stack_cxx_test.cc56
-rw-r--r--source/blender/blenlib/tests/BLI_vector_test.cc193
55 files changed, 18187 insertions, 6821 deletions
diff --git a/source/blender/blenlib/BLI_array.h b/source/blender/blenlib/BLI_array.h
index 6ea01d45f79..4e37314ed3e 100644
--- a/source/blender/blenlib/BLI_array.h
+++ b/source/blender/blenlib/BLI_array.h
@@ -117,7 +117,7 @@ void _bli_array_grow_func(void **arr_p,
{ \
if (arr && (char *)arr != _##arr##_static) { \
BLI_array_fake_user(arr); \
- MEM_freeN(arr); \
+ MEM_freeN((void *)arr); \
} \
} \
((void)0)
diff --git a/source/blender/blenlib/BLI_array.hh b/source/blender/blenlib/BLI_array.hh
index 9b307dc6a04..dddf4f64ff5 100644
--- a/source/blender/blenlib/BLI_array.hh
+++ b/source/blender/blenlib/BLI_array.hh
@@ -77,32 +77,39 @@ class Array {
/**
* By default an empty array is created.
*/
- Array()
+ Array(Allocator allocator = {}) noexcept : allocator_(allocator)
{
data_ = inline_buffer_;
size_ = 0;
}
+ Array(NoExceptConstructor, Allocator allocator = {}) noexcept : Array(allocator)
+ {
+ }
+
/**
* Create a new array that contains copies of all values.
*/
template<typename U, typename std::enable_if_t<std::is_convertible_v<U, T>> * = nullptr>
- Array(Span<U> values, Allocator allocator = {}) : allocator_(allocator)
+ Array(Span<U> values, Allocator allocator = {}) : Array(NoExceptConstructor(), allocator)
{
- size_ = values.size();
- data_ = this->get_buffer_for_size(values.size());
- uninitialized_convert_n<U, T>(values.data(), size_, data_);
+ const int64_t size = values.size();
+ data_ = this->get_buffer_for_size(size);
+ uninitialized_convert_n<U, T>(values.data(), size, data_);
+ size_ = size;
}
/**
* Create a new array that contains copies of all values.
*/
template<typename U, typename std::enable_if_t<std::is_convertible_v<U, T>> * = nullptr>
- Array(const std::initializer_list<U> &values) : Array(Span<U>(values))
+ Array(const std::initializer_list<U> &values, Allocator allocator = {})
+ : Array(Span<U>(values), allocator)
{
}
- Array(const std::initializer_list<T> &values) : Array(Span<T>(values))
+ Array(const std::initializer_list<T> &values, Allocator allocator = {})
+ : Array(Span<T>(values), allocator)
{
}
@@ -114,23 +121,24 @@ class Array {
* even for non-trivial types. This should not be the default though, because one can easily mess
* up when dealing with uninitialized memory.
*/
- explicit Array(int64_t size)
+ explicit Array(int64_t size, Allocator allocator = {}) : Array(NoExceptConstructor(), allocator)
{
- size_ = size;
data_ = this->get_buffer_for_size(size);
default_construct_n(data_, size);
+ size_ = size;
}
/**
* Create a new array with the given size. All values will be initialized by copying the given
* default.
*/
- Array(int64_t size, const T &value)
+ Array(int64_t size, const T &value, Allocator allocator = {})
+ : Array(NoExceptConstructor(), allocator)
{
BLI_assert(size >= 0);
- size_ = size;
data_ = this->get_buffer_for_size(size);
- uninitialized_fill_n(data_, size_, value);
+ uninitialized_fill_n(data_, size, value);
+ size_ = size;
}
/**
@@ -145,28 +153,28 @@ class Array {
* Usage:
* Array<std::string> my_strings(10, NoInitialization());
*/
- Array(int64_t size, NoInitialization)
+ Array(int64_t size, NoInitialization, Allocator allocator = {})
+ : Array(NoExceptConstructor(), allocator)
{
BLI_assert(size >= 0);
- size_ = size;
data_ = this->get_buffer_for_size(size);
+ size_ = size;
}
Array(const Array &other) : Array(other.as_span(), other.allocator_)
{
}
- Array(Array &&other) noexcept : allocator_(other.allocator_)
+ Array(Array &&other) noexcept(std::is_nothrow_move_constructible_v<T>)
+ : Array(NoExceptConstructor(), other.allocator_)
{
- size_ = other.size_;
-
- if (!other.uses_inline_buffer()) {
- data_ = other.data_;
+ if (other.data_ == other.inline_buffer_) {
+ uninitialized_relocate_n(other.data_, other.size_, data_);
}
else {
- data_ = this->get_buffer_for_size(size_);
- uninitialized_relocate_n(other.data_, size_, data_);
+ data_ = other.data_;
}
+ size_ = other.size_;
other.data_ = other.inline_buffer_;
other.size_ = 0;
@@ -175,31 +183,17 @@ class Array {
~Array()
{
destruct_n(data_, size_);
- if (!this->uses_inline_buffer()) {
- allocator_.deallocate(static_cast<void *>(data_));
- }
+ this->deallocate_if_not_inline(data_);
}
Array &operator=(const Array &other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Array();
- new (this) Array(other);
- return *this;
+ return copy_assign_container(*this, other);
}
- Array &operator=(Array &&other)
+ Array &operator=(Array &&other) noexcept(std::is_nothrow_move_constructible_v<T>)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Array();
- new (this) Array(std::move(other));
- return *this;
+ return move_assign_container(*this, std::move(other));
}
T &operator[](int64_t index)
@@ -273,6 +267,21 @@ class Array {
}
/**
+ * Return a reference to the last element in the array.
+ * This invokes undefined behavior when the array is empty.
+ */
+ const T &last() const
+ {
+ BLI_assert(size_ > 0);
+ return *(data_ + size_ - 1);
+ }
+ T &last()
+ {
+ BLI_assert(size_ > 0);
+ return *(data_ + size_ - 1);
+ }
+
+ /**
* Get a pointer to the beginning of the array.
*/
const T *data() const
@@ -288,7 +297,6 @@ class Array {
{
return data_;
}
-
const T *end() const
{
return data_ + size_;
@@ -298,12 +306,29 @@ class Array {
{
return data_;
}
-
T *end()
{
return data_ + size_;
}
+ std::reverse_iterator<T *> rbegin()
+ {
+ return std::reverse_iterator<T *>(this->end());
+ }
+ std::reverse_iterator<T *> rend()
+ {
+ return std::reverse_iterator<T *>(this->begin());
+ }
+
+ std::reverse_iterator<const T *> rbegin() const
+ {
+ return std::reverse_iterator<T *>(this->end());
+ }
+ std::reverse_iterator<const T *> rend() const
+ {
+ return std::reverse_iterator<T *>(this->begin());
+ }
+
/**
* Get an index range containing all valid indices for this array.
*/
@@ -338,6 +363,37 @@ class Array {
return InlineBufferCapacity;
}
+ /**
+ * Destruct values and create a new array of the given size. The values in the new array are
+ * default constructed.
+ */
+ void reinitialize(const int64_t new_size)
+ {
+ BLI_assert(new_size >= 0);
+ int64_t old_size = size_;
+
+ destruct_n(data_, size_);
+ size_ = 0;
+
+ if (new_size <= old_size) {
+ default_construct_n(data_, new_size);
+ }
+ else {
+ T *new_data = this->get_buffer_for_size(new_size);
+ try {
+ default_construct_n(new_data, new_size);
+ }
+ catch (...) {
+ this->deallocate_if_not_inline(new_data);
+ throw;
+ }
+ this->deallocate_if_not_inline(data_);
+ data_ = new_data;
+ }
+
+ size_ = new_size;
+ }
+
private:
T *get_buffer_for_size(int64_t size)
{
@@ -355,9 +411,11 @@ class Array {
allocator_.allocate(static_cast<size_t>(size) * sizeof(T), alignof(T), AT));
}
- bool uses_inline_buffer() const
+ void deallocate_if_not_inline(T *ptr)
{
- return data_ == inline_buffer_;
+ if (ptr != inline_buffer_) {
+ allocator_.deallocate(ptr);
+ }
}
};
diff --git a/source/blender/blenlib/BLI_compiler_compat.h b/source/blender/blenlib/BLI_compiler_compat.h
index 0870d01872a..bd8f84cedd6 100644
--- a/source/blender/blenlib/BLI_compiler_compat.h
+++ b/source/blender/blenlib/BLI_compiler_compat.h
@@ -55,4 +55,3 @@ template<typename T> static inline T decltype_helper(T x)
#else
# define BLI_NOINLINE
#endif
-
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_listbase.h b/source/blender/blenlib/BLI_listbase.h
index fa7cf7a1847..aff80a2bd86 100644
--- a/source/blender/blenlib/BLI_listbase.h
+++ b/source/blender/blenlib/BLI_listbase.h
@@ -171,6 +171,15 @@ struct LinkData *BLI_genericNodeN(void *data);
#define LISTBASE_FOREACH(type, var, list) \
for (type var = (type)((list)->first); var != NULL; var = (type)(((Link *)(var))->next))
+/**
+ * A version of #LISTBASE_FOREACH that supports incrementing an index variable at every step.
+ * Including this in the macro helps prevent mistakes where "continue" mistakenly skips the
+ * incrementation.
+ */
+#define LISTBASE_FOREACH_INDEX(type, var, list, index_var) \
+ for (type var = (((void)(index_var = 0)), (type)((list)->first)); var != NULL; \
+ var = (type)(((Link *)(var))->next), index_var++)
+
#define LISTBASE_FOREACH_BACKWARD(type, var, list) \
for (type var = (type)((list)->last); var != NULL; var = (type)(((Link *)(var))->prev))
diff --git a/source/blender/blenlib/BLI_map.hh b/source/blender/blenlib/BLI_map.hh
index 229bbfad0e4..08fe1a3cdbc 100644
--- a/source/blender/blenlib/BLI_map.hh
+++ b/source/blender/blenlib/BLI_map.hh
@@ -171,14 +171,18 @@ class Map {
* This is necessary to avoid a high cost when no elements are added at all. An optimized grow
* operation is performed on the first insertion.
*/
- Map()
+ Map(Allocator allocator = {}) noexcept
: removed_slots_(0),
occupied_and_removed_slots_(0),
usable_slots_(0),
slot_mask_(0),
hash_(),
is_equal_(),
- slots_(1)
+ slots_(1, allocator)
+ {
+ }
+
+ Map(NoExceptConstructor, Allocator allocator = {}) noexcept : Map(allocator)
{
}
@@ -186,41 +190,38 @@ class Map {
Map(const Map &other) = default;
- Map(Map &&other) noexcept
- : removed_slots_(other.removed_slots_),
- occupied_and_removed_slots_(other.occupied_and_removed_slots_),
- usable_slots_(other.usable_slots_),
- slot_mask_(other.slot_mask_),
- hash_(std::move(other.hash_)),
- is_equal_(std::move(other.is_equal_)),
- slots_(std::move(other.slots_))
+ Map(Map &&other) noexcept(std::is_nothrow_move_constructible_v<SlotArray>)
+ : Map(NoExceptConstructor(), other.slots_.allocator())
{
- other.~Map();
- new (&other) Map();
+ if constexpr (std::is_nothrow_move_constructible_v<SlotArray>) {
+ slots_ = std::move(other.slots_);
+ }
+ else {
+ try {
+ slots_ = std::move(other.slots_);
+ }
+ catch (...) {
+ other.noexcept_reset();
+ throw;
+ }
+ }
+ removed_slots_ = other.removed_slots_;
+ occupied_and_removed_slots_ = other.occupied_and_removed_slots_;
+ usable_slots_ = other.usable_slots_;
+ slot_mask_ = other.slot_mask_;
+ hash_ = std::move(other.hash_);
+ is_equal_ = std::move(other.is_equal_);
+ other.noexcept_reset();
}
Map &operator=(const Map &other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Map();
- new (this) Map(other);
-
- return *this;
+ return copy_assign_container(*this, other);
}
Map &operator=(Map &&other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Map();
- new (this) Map(std::move(other));
-
- return *this;
+ return move_assign_container(*this, std::move(other));
}
/**
@@ -315,7 +316,7 @@ class Map {
}
template<typename ForwardKey> bool contains_as(const ForwardKey &key) const
{
- return this->contains__impl(key, hash_(key));
+ return this->lookup_slot_ptr(key, hash_(key)) != nullptr;
}
/**
@@ -330,7 +331,13 @@ class Map {
}
template<typename ForwardKey> bool remove_as(const ForwardKey &key)
{
- return this->remove__impl(key, hash_(key));
+ Slot *slot = this->lookup_slot_ptr(key, hash_(key));
+ if (slot == nullptr) {
+ return false;
+ }
+ slot->remove();
+ removed_slots_++;
+ return true;
}
/**
@@ -343,7 +350,9 @@ class Map {
}
template<typename ForwardKey> void remove_contained_as(const ForwardKey &key)
{
- this->remove_contained__impl(key, hash_(key));
+ Slot &slot = this->lookup_slot(key, hash_(key));
+ slot.remove();
+ removed_slots_++;
}
/**
@@ -356,7 +365,11 @@ class Map {
}
template<typename ForwardKey> Value pop_as(const ForwardKey &key)
{
- return this->pop__impl(key, hash_(key));
+ Slot &slot = this->lookup_slot(key, hash_(key));
+ Value value = std::move(*slot.value());
+ slot.remove();
+ removed_slots_++;
+ return value;
}
/**
@@ -369,7 +382,14 @@ class Map {
}
template<typename ForwardKey> std::optional<Value> pop_try_as(const ForwardKey &key)
{
- return this->pop_try__impl(key, hash_(key));
+ Slot *slot = this->lookup_slot_ptr(key, hash_(key));
+ if (slot == nullptr) {
+ return {};
+ }
+ std::optional<Value> value = std::move(*slot->value());
+ slot->remove();
+ removed_slots_++;
+ return value;
}
/**
@@ -387,7 +407,14 @@ class Map {
template<typename ForwardKey, typename ForwardValue>
Value pop_default_as(const ForwardKey &key, ForwardValue &&default_value)
{
- return this->pop_default__impl(key, std::forward<ForwardValue>(default_value), hash_(key));
+ Slot *slot = this->lookup_slot_ptr(key, hash_(key));
+ if (slot == nullptr) {
+ return std::forward<ForwardValue>(default_value);
+ }
+ Value value = std::move(*slot->value());
+ slot->remove();
+ removed_slots_++;
+ return value;
}
/**
@@ -448,11 +475,12 @@ class Map {
}
template<typename ForwardKey> const Value *lookup_ptr_as(const ForwardKey &key) const
{
- return this->lookup_ptr__impl(key, hash_(key));
+ const Slot *slot = this->lookup_slot_ptr(key, hash_(key));
+ return (slot != nullptr) ? slot->value() : nullptr;
}
template<typename ForwardKey> Value *lookup_ptr_as(const ForwardKey &key)
{
- return const_cast<Value *>(this->lookup_ptr__impl(key, hash_(key)));
+ return const_cast<Value *>(const_cast<const Map *>(this)->lookup_ptr_as(key));
}
/**
@@ -843,8 +871,7 @@ class Map {
*/
void clear()
{
- this->~Map();
- new (this) Map();
+ this->noexcept_reset();
}
/**
@@ -869,8 +896,13 @@ class Map {
* Optimize the case when the map was empty beforehand. We can avoid some copies here.
*/
if (this->size() == 0) {
- slots_.~Array();
- new (&slots_) SlotArray(total_slots);
+ try {
+ slots_.reinitialize(total_slots);
+ }
+ catch (...) {
+ this->noexcept_reset();
+ throw;
+ }
removed_slots_ = 0;
occupied_and_removed_slots_ = 0;
usable_slots_ = usable_slots;
@@ -880,48 +912,44 @@ class Map {
SlotArray new_slots(total_slots);
- for (Slot &slot : slots_) {
- if (slot.is_occupied()) {
- this->add_after_grow_and_destruct_old(slot, new_slots, new_slot_mask);
+ try {
+ for (Slot &slot : slots_) {
+ if (slot.is_occupied()) {
+ this->add_after_grow(slot, new_slots, new_slot_mask);
+ slot.remove();
+ }
}
+ slots_ = std::move(new_slots);
+ }
+ catch (...) {
+ this->noexcept_reset();
+ throw;
}
- /* All occupied slots have been destructed already and empty/removed slots are assumed to be
- * trivially destructible. */
- slots_.clear_without_destruct();
- slots_ = std::move(new_slots);
occupied_and_removed_slots_ -= removed_slots_;
usable_slots_ = usable_slots;
removed_slots_ = 0;
slot_mask_ = new_slot_mask;
}
- void add_after_grow_and_destruct_old(Slot &old_slot,
- SlotArray &new_slots,
- uint64_t new_slot_mask)
+ void add_after_grow(Slot &old_slot, SlotArray &new_slots, uint64_t new_slot_mask)
{
uint64_t hash = old_slot.get_hash(Hash());
SLOT_PROBING_BEGIN (ProbingStrategy, hash, new_slot_mask, slot_index) {
Slot &slot = new_slots[slot_index];
if (slot.is_empty()) {
- slot.relocate_occupied_here(old_slot, hash);
+ slot.occupy(std::move(*old_slot.key()), std::move(*old_slot.value()), hash);
return;
}
}
SLOT_PROBING_END();
}
- template<typename ForwardKey> bool contains__impl(const ForwardKey &key, uint64_t hash) const
+ void noexcept_reset() noexcept
{
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.is_empty()) {
- return false;
- }
- if (slot.contains(key, is_equal_, hash)) {
- return true;
- }
- }
- MAP_SLOT_PROBING_END();
+ Allocator allocator = slots_.allocator();
+ this->~Map();
+ new (this) Map(NoExceptConstructor(), allocator);
}
template<typename ForwardKey, typename ForwardValue>
@@ -930,11 +958,11 @@ class Map {
BLI_assert(!this->contains_as(key));
this->ensure_can_add();
- occupied_and_removed_slots_++;
MAP_SLOT_PROBING_BEGIN (hash, slot) {
if (slot.is_empty()) {
slot.occupy(std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash);
+ occupied_and_removed_slots_++;
return;
}
}
@@ -959,86 +987,6 @@ class Map {
MAP_SLOT_PROBING_END();
}
- template<typename ForwardKey> bool remove__impl(const ForwardKey &key, uint64_t hash)
- {
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.contains(key, is_equal_, hash)) {
- slot.remove();
- removed_slots_++;
- return true;
- }
- if (slot.is_empty()) {
- return false;
- }
- }
- MAP_SLOT_PROBING_END();
- }
-
- template<typename ForwardKey> void remove_contained__impl(const ForwardKey &key, uint64_t hash)
- {
- BLI_assert(this->contains_as(key));
-
- removed_slots_++;
-
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.contains(key, is_equal_, hash)) {
- slot.remove();
- return;
- }
- }
- MAP_SLOT_PROBING_END();
- }
-
- template<typename ForwardKey> Value pop__impl(const ForwardKey &key, uint64_t hash)
- {
- BLI_assert(this->contains_as(key));
-
- removed_slots_++;
-
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.contains(key, is_equal_, hash)) {
- Value value = std::move(*slot.value());
- slot.remove();
- return value;
- }
- }
- MAP_SLOT_PROBING_END();
- }
-
- template<typename ForwardKey>
- std::optional<Value> pop_try__impl(const ForwardKey &key, uint64_t hash)
- {
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.contains(key, is_equal_, hash)) {
- std::optional<Value> value = std::move(*slot.value());
- slot.remove();
- removed_slots_++;
- return value;
- }
- if (slot.is_empty()) {
- return {};
- }
- }
- MAP_SLOT_PROBING_END();
- }
-
- template<typename ForwardKey, typename ForwardValue>
- Value pop_default__impl(const ForwardKey &key, ForwardValue &&default_value, uint64_t hash)
- {
- MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.contains(key, is_equal_, hash)) {
- Value value = std::move(*slot.value());
- slot.remove();
- removed_slots_++;
- return value;
- }
- if (slot.is_empty()) {
- return std::forward<ForwardValue>(default_value);
- }
- }
- MAP_SLOT_PROBING_END();
- }
-
template<typename ForwardKey, typename CreateValueF, typename ModifyValueF>
auto add_or_modify__impl(ForwardKey &&key,
const CreateValueF &create_value,
@@ -1054,10 +1002,19 @@ class Map {
MAP_SLOT_PROBING_BEGIN (hash, slot) {
if (slot.is_empty()) {
- occupied_and_removed_slots_++;
- slot.occupy_without_value(std::forward<ForwardKey>(key), hash);
Value *value_ptr = slot.value();
- return create_value(value_ptr);
+ if constexpr (std::is_void_v<CreateReturnT>) {
+ create_value(value_ptr);
+ slot.occupy_no_value(std::forward<ForwardKey>(key), hash);
+ occupied_and_removed_slots_++;
+ return;
+ }
+ else {
+ auto return_value = create_value(value_ptr);
+ slot.occupy_no_value(std::forward<ForwardKey>(key), hash);
+ occupied_and_removed_slots_++;
+ return return_value;
+ }
}
if (slot.contains(key, is_equal_, hash)) {
Value *value_ptr = slot.value();
@@ -1119,19 +1076,41 @@ class Map {
}
template<typename ForwardKey>
- const Value *lookup_ptr__impl(const ForwardKey &key, uint64_t hash) const
+ const Slot &lookup_slot(const ForwardKey &key, const uint64_t hash) const
{
+ BLI_assert(this->contains_as(key));
MAP_SLOT_PROBING_BEGIN (hash, slot) {
- if (slot.is_empty()) {
- return nullptr;
+ if (slot.contains(key, is_equal_, hash)) {
+ return slot;
}
+ }
+ MAP_SLOT_PROBING_END();
+ }
+
+ template<typename ForwardKey> Slot &lookup_slot(const ForwardKey &key, const uint64_t hash)
+ {
+ return const_cast<Slot &>(const_cast<const Map *>(this)->lookup_slot(key, hash));
+ }
+
+ template<typename ForwardKey>
+ const Slot *lookup_slot_ptr(const ForwardKey &key, const uint64_t hash) const
+ {
+ MAP_SLOT_PROBING_BEGIN (hash, slot) {
if (slot.contains(key, is_equal_, hash)) {
- return slot.value();
+ return &slot;
+ }
+ if (slot.is_empty()) {
+ return nullptr;
}
}
MAP_SLOT_PROBING_END();
}
+ template<typename ForwardKey> Slot *lookup_slot_ptr(const ForwardKey &key, const uint64_t hash)
+ {
+ return const_cast<Slot *>(const_cast<const Map *>(this)->lookup_slot_ptr(key, hash));
+ }
+
template<typename ForwardKey>
int64_t count_collisions__impl(const ForwardKey &key, uint64_t hash) const
{
diff --git a/source/blender/blenlib/BLI_map_slots.hh b/source/blender/blenlib/BLI_map_slots.hh
index 25fb92d61a3..c0cb3091a8e 100644
--- a/source/blender/blenlib/BLI_map_slots.hh
+++ b/source/blender/blenlib/BLI_map_slots.hh
@@ -38,6 +38,19 @@
namespace blender {
+template<typename Src1, typename Src2, typename Dst1, typename Dst2>
+void initialize_pointer_pair(Src1 &&src1, Src2 &&src2, Dst1 *dst1, Dst2 *dst2)
+{
+ new ((void *)dst1) Dst1(std::forward<Src1>(src1));
+ try {
+ new ((void *)dst2) Dst2(std::forward<Src2>(src2));
+ }
+ catch (...) {
+ dst1->~Dst1();
+ throw;
+ }
+}
+
/**
* The simplest possible map slot. It stores the slot state and the optional key and value
* instances in separate variables. Depending on the alignment requirement of the key and value,
@@ -83,8 +96,10 @@ template<typename Key, typename Value> class SimpleMapSlot {
{
state_ = other.state_;
if (other.state_ == Occupied) {
- new (&key_buffer_) Key(*other.key_buffer_);
- new (&value_buffer_) Value(*other.value_buffer_);
+ initialize_pointer_pair(other.key_buffer_.ref(),
+ other.value_buffer_.ref(),
+ key_buffer_.ptr(),
+ value_buffer_.ptr());
}
}
@@ -93,12 +108,15 @@ template<typename Key, typename Value> class SimpleMapSlot {
* from the other have to moved as well. The other slot stays in the state it was in before. Its
* optionally stored key and value remain in a moved-from state.
*/
- SimpleMapSlot(SimpleMapSlot &&other) noexcept
+ SimpleMapSlot(SimpleMapSlot &&other) noexcept(
+ std::is_nothrow_move_constructible_v<Key> &&std::is_nothrow_move_constructible_v<Value>)
{
state_ = other.state_;
if (other.state_ == Occupied) {
- new (&key_buffer_) Key(std::move(*other.key_buffer_));
- new (&value_buffer_) Value(std::move(*other.value_buffer_));
+ initialize_pointer_pair(std::move(other.key_buffer_.ref()),
+ std::move(other.value_buffer_.ref()),
+ key_buffer_.ptr(),
+ value_buffer_.ptr());
}
}
@@ -161,21 +179,6 @@ template<typename Key, typename Value> class SimpleMapSlot {
}
/**
- * Move the other slot into this slot and destruct it. We do destruction here, because this way
- * we can avoid a comparison with the state, since we know the slot is occupied.
- */
- void relocate_occupied_here(SimpleMapSlot &other, uint64_t UNUSED(hash))
- {
- BLI_assert(!this->is_occupied());
- BLI_assert(other.is_occupied());
- state_ = Occupied;
- new (&key_buffer_) Key(std::move(*other.key_buffer_));
- new (&value_buffer_) Value(std::move(*other.value_buffer_));
- other.key_buffer_.ref().~Key();
- other.value_buffer_.ref().~Value();
- }
-
- /**
* Returns true, when this slot is occupied and contains a key that compares equal to the given
* key. The hash can be used by other slot implementations to determine inequality faster.
*/
@@ -196,19 +199,27 @@ template<typename Key, typename Value> class SimpleMapSlot {
void occupy(ForwardKey &&key, ForwardValue &&value, uint64_t hash)
{
BLI_assert(!this->is_occupied());
- this->occupy_without_value(std::forward<ForwardKey>(key), hash);
new (&value_buffer_) Value(std::forward<ForwardValue>(value));
+ this->occupy_no_value(std::forward<ForwardKey>(key), hash);
+ state_ = Occupied;
}
/**
- * Change the state of this slot from empty/removed to occupied, but leave the value
- * uninitialized. The caller is responsible to construct the value afterwards.
+ * Change the state of this slot from empty/removed to occupied. The value is assumed to be
+ * constructed already.
*/
- template<typename ForwardKey> void occupy_without_value(ForwardKey &&key, uint64_t UNUSED(hash))
+ template<typename ForwardKey> void occupy_no_value(ForwardKey &&key, uint64_t UNUSED(hash))
{
BLI_assert(!this->is_occupied());
+ try {
+ new (&key_buffer_) Key(std::forward<ForwardKey>(key));
+ }
+ catch (...) {
+ /* The value is assumed to be constructed already, so it has to be destructed as well. */
+ value_buffer_.ref().~Value();
+ throw;
+ }
state_ = Occupied;
- new (&key_buffer_) Key(std::forward<ForwardKey>(key));
}
/**
@@ -218,17 +229,17 @@ template<typename Key, typename Value> class SimpleMapSlot {
void remove()
{
BLI_assert(this->is_occupied());
- state_ = Removed;
key_buffer_.ref().~Key();
value_buffer_.ref().~Value();
+ state_ = Removed;
}
};
/**
- * An IntrusiveMapSlot uses two special values of the key to indicate whether the slot is empty or
- * removed. This saves some memory in all cases and is more efficient in many cases. The KeyInfo
- * type indicates which specific values are used. An example for a KeyInfo implementation is
- * PointerKeyInfo.
+ * An IntrusiveMapSlot uses two special values of the key to indicate whether the slot is empty
+ * or removed. This saves some memory in all cases and is more efficient in many cases. The
+ * KeyInfo type indicates which specific values are used. An example for a KeyInfo
+ * implementation is PointerKeyInfo.
*
* The special key values are expected to be trivially destructible.
*/
@@ -297,16 +308,6 @@ template<typename Key, typename Value, typename KeyInfo> class IntrusiveMapSlot
return hash(key_);
}
- void relocate_occupied_here(IntrusiveMapSlot &other, uint64_t UNUSED(hash))
- {
- BLI_assert(!this->is_occupied());
- BLI_assert(other.is_occupied());
- key_ = std::move(other.key_);
- new (&value_buffer_) Value(std::move(*other.value_buffer_));
- other.key_.~Key();
- other.value_buffer_.ref().~Value();
- }
-
template<typename ForwardKey, typename IsEqual>
bool contains(const ForwardKey &key, const IsEqual &is_equal, uint64_t UNUSED(hash)) const
{
@@ -319,22 +320,28 @@ template<typename Key, typename Value, typename KeyInfo> class IntrusiveMapSlot
{
BLI_assert(!this->is_occupied());
BLI_assert(KeyInfo::is_not_empty_or_removed(key));
- this->occupy_without_value(std::forward<ForwardKey>(key), hash);
new (&value_buffer_) Value(std::forward<ForwardValue>(value));
+ this->occupy_no_value(std::forward<ForwardKey>(key), hash);
}
- template<typename ForwardKey> void occupy_without_value(ForwardKey &&key, uint64_t UNUSED(hash))
+ template<typename ForwardKey> void occupy_no_value(ForwardKey &&key, uint64_t UNUSED(hash))
{
BLI_assert(!this->is_occupied());
BLI_assert(KeyInfo::is_not_empty_or_removed(key));
- key_ = std::forward<ForwardKey>(key);
+ try {
+ key_ = std::forward<ForwardKey>(key);
+ }
+ catch (...) {
+ value_buffer_.ref().~Value();
+ throw;
+ }
}
void remove()
{
BLI_assert(this->is_occupied());
- KeyInfo::remove(key_);
value_buffer_.ref().~Value();
+ KeyInfo::remove(key_);
}
};
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..79b1483bfb8
--- /dev/null
+++ b/source/blender/blenlib/BLI_math_boolean.hh
@@ -0,0 +1,62 @@
+/*
+ * 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_color.h b/source/blender/blenlib/BLI_math_color.h
index 943f0fc764f..7b48b62b6e7 100644
--- a/source/blender/blenlib/BLI_math_color.h
+++ b/source/blender/blenlib/BLI_math_color.h
@@ -47,7 +47,7 @@ void hsv_to_rgb(float h, float s, float v, float *r_r, float *r_g, float *r_b);
void hsv_to_rgb_v(const float hsv[3], float r_rgb[3]);
void hsl_to_rgb(float h, float c, float l, float *r_r, float *r_g, float *r_b);
void hsl_to_rgb_v(const float hcl[3], float r_rgb[3]);
-void hex_to_rgb(char *hexcol, float *r_r, float *r_g, float *r_b);
+void hex_to_rgb(const char *hexcol, float *r_r, float *r_g, float *r_b);
void yuv_to_rgb(float y, float u, float v, float *r_r, float *r_g, float *r_b, int colorspace);
void ycc_to_rgb(float y, float cb, float cr, float *r_r, float *r_g, float *r_b, int colorspace);
void cpack_to_rgb(unsigned int col, float *r_r, float *r_g, float *r_b);
diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h
index a00fdaa0ae9..09f2d5a7cdc 100644
--- a/source/blender/blenlib/BLI_math_matrix.h
+++ b/source/blender/blenlib/BLI_math_matrix.h
@@ -299,6 +299,9 @@ bool has_zero_axis_m4(const float matrix[4][4]);
void invert_m4_m4_safe(float Ainv[4][4], const float A[4][4]);
+void invert_m3_m3_safe_ortho(float Ainv[3][3], const float A[3][3]);
+void invert_m4_m4_safe_ortho(float Ainv[4][4], const float A[4][4]);
+
/****************************** Transformations ******************************/
void scale_m3_fl(float R[3][3], float scale);
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..3399287dd46 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 *******************************/
@@ -333,6 +338,7 @@ MINLINE bool equals_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RE
MINLINE bool equals_v4v4(const float a[4], const float b[4]) ATTR_WARN_UNUSED_RESULT;
MINLINE bool equals_v2v2_int(const int v1[2], const int v2[2]) ATTR_WARN_UNUSED_RESULT;
+MINLINE bool equals_v4v4_int(const int v1[4], const int v2[4]) ATTR_WARN_UNUSED_RESULT;
MINLINE bool compare_v2v2(const float a[2],
const float b[2],
@@ -402,6 +408,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 +417,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_memory_utils.hh b/source/blender/blenlib/BLI_memory_utils.hh
index 7216536a884..49076bb1aae 100644
--- a/source/blender/blenlib/BLI_memory_utils.hh
+++ b/source/blender/blenlib/BLI_memory_utils.hh
@@ -162,7 +162,7 @@ void uninitialized_convert_n(const From *src, int64_t n, To *dst)
int64_t current = 0;
try {
for (; current < n; current++) {
- new (static_cast<void *>(dst + current)) To((To)src[current]);
+ new (static_cast<void *>(dst + current)) To(static_cast<To>(src[current]));
}
}
catch (...) {
@@ -410,6 +410,15 @@ class NoInitialization {
};
/**
+ * This can be used to mark a constructor of an object that does not throw exceptions. Other
+ * constructors can delegate to this constructor to make sure that the object lifetime starts.
+ * With this, the destructor of the object will be called, even when the remaining constructor
+ * throws.
+ */
+class NoExceptConstructor {
+};
+
+/**
* Helper variable that checks if a pointer type can be converted into another pointer type without
* issues. Possible issues are casting away const and casting a pointer to a child class.
* Adding const or casting to a parent class is fine.
@@ -427,4 +436,49 @@ inline constexpr int64_t default_inline_buffer_capacity(size_t element_size)
return (static_cast<int64_t>(element_size) < 100) ? 4 : 0;
}
+/**
+ * This can be used by containers to implement an exception-safe copy-assignment-operator.
+ * It assumes that the container has an exception safe copy constructor and an exception-safe
+ * move-assignment-operator.
+ */
+template<typename Container> Container &copy_assign_container(Container &dst, const Container &src)
+{
+ if (&src == &dst) {
+ return dst;
+ }
+
+ Container container_copy{src};
+ dst = std::move(container_copy);
+ return dst;
+}
+
+/**
+ * This can be used by containers to implement an exception-safe move-assignment-operator.
+ * It assumes that the container has an exception-safe move-constructor and a noexcept constructor
+ * tagged with the NoExceptConstructor tag.
+ */
+template<typename Container>
+Container &move_assign_container(Container &dst, Container &&src) noexcept(
+ std::is_nothrow_move_constructible_v<Container>)
+{
+ if (&dst == &src) {
+ return dst;
+ }
+
+ dst.~Container();
+ if constexpr (std::is_nothrow_move_constructible_v<Container>) {
+ new (&dst) Container(std::move(src));
+ }
+ else {
+ try {
+ new (&dst) Container(std::move(src));
+ }
+ catch (...) {
+ new (&dst) Container(NoExceptConstructor());
+ throw;
+ }
+ }
+ return dst;
+}
+
} // namespace blender
diff --git a/source/blender/blenlib/BLI_mesh_boolean.hh b/source/blender/blenlib/BLI_mesh_boolean.hh
new file mode 100644
index 00000000000..cb6fc203dc7
--- /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 \a 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 argument 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 argument isn't `const` because we may populate its verts (for debugging).
+ * Same goes for the pm_triangulated argument.
+ * 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/BLI_set.hh b/source/blender/blenlib/BLI_set.hh
index 477a03cf623..9684f372db7 100644
--- a/source/blender/blenlib/BLI_set.hh
+++ b/source/blender/blenlib/BLI_set.hh
@@ -170,62 +170,68 @@ class Set {
* is. This is necessary to avoid a high cost when no elements are added at all. An optimized
* grow operation is performed on the first insertion.
*/
- Set()
+ Set(Allocator allocator = {}) noexcept
: removed_slots_(0),
occupied_and_removed_slots_(0),
usable_slots_(0),
slot_mask_(0),
- slots_(1)
+ slots_(1, allocator)
{
}
- ~Set() = default;
+ Set(NoExceptConstructor, Allocator allocator = {}) noexcept : Set(allocator)
+ {
+ }
+
+ Set(Span<Key> values, Allocator allocator = {}) : Set(NoExceptConstructor(), allocator)
+ {
+ this->add_multiple(values);
+ }
/**
* Construct a set that contains the given keys. Duplicates will be removed automatically.
*/
- Set(const std::initializer_list<Key> &list) : Set()
+ Set(const std::initializer_list<Key> &values) : Set(Span<Key>(values))
{
- this->add_multiple(list);
}
+ ~Set() = default;
+
Set(const Set &other) = default;
- Set(Set &&other) noexcept
- : removed_slots_(other.removed_slots_),
- occupied_and_removed_slots_(other.occupied_and_removed_slots_),
- usable_slots_(other.usable_slots_),
- slot_mask_(other.slot_mask_),
- hash_(std::move(other.hash_)),
- is_equal_(std::move(other.is_equal_)),
- slots_(std::move(other.slots_))
+ Set(Set &&other) noexcept(std::is_nothrow_move_constructible_v<SlotArray>)
+ : Set(NoExceptConstructor(), other.slots_.allocator())
+
{
- other.~Set();
- new (&other) Set();
+ if constexpr (std::is_nothrow_move_constructible_v<SlotArray>) {
+ slots_ = std::move(other.slots_);
+ }
+ else {
+ try {
+ slots_ = std::move(other.slots_);
+ }
+ catch (...) {
+ other.noexcept_reset();
+ throw;
+ }
+ }
+ removed_slots_ = other.removed_slots_;
+ occupied_and_removed_slots_ = other.occupied_and_removed_slots_;
+ usable_slots_ = other.usable_slots_;
+ slot_mask_ = other.slot_mask_;
+ hash_ = std::move(other.hash_);
+ is_equal_ = std::move(other.is_equal_);
+ other.noexcept_reset();
}
Set &operator=(const Set &other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Set();
- new (this) Set(other);
-
- return *this;
+ return copy_assign_container(*this, other);
}
Set &operator=(Set &&other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Set();
- new (this) Set(std::move(other));
-
- return *this;
+ return move_assign_container(*this, std::move(other));
}
/**
@@ -562,8 +568,13 @@ class Set {
* Optimize the case when the set was empty beforehand. We can avoid some copies here.
*/
if (this->size() == 0) {
- slots_.~Array();
- new (&slots_) SlotArray(total_slots);
+ try {
+ slots_.reinitialize(total_slots);
+ }
+ catch (...) {
+ this->noexcept_reset();
+ throw;
+ }
removed_slots_ = 0;
occupied_and_removed_slots_ = 0;
usable_slots_ = usable_slots;
@@ -574,38 +585,51 @@ class Set {
/* The grown array that we insert the keys into. */
SlotArray new_slots(total_slots);
- for (Slot &slot : slots_) {
- if (slot.is_occupied()) {
- this->add_after_grow_and_destruct_old(slot, new_slots, new_slot_mask);
+ try {
+ for (Slot &slot : slots_) {
+ if (slot.is_occupied()) {
+ this->add_after_grow(slot, new_slots, new_slot_mask);
+ slot.remove();
+ }
}
+ slots_ = std::move(new_slots);
+ }
+ catch (...) {
+ this->noexcept_reset();
+ throw;
}
- /* All occupied slots have been destructed already and empty/removed slots are assumed to be
- * trivially destructible. */
- slots_.clear_without_destruct();
- slots_ = std::move(new_slots);
occupied_and_removed_slots_ -= removed_slots_;
usable_slots_ = usable_slots;
removed_slots_ = 0;
slot_mask_ = new_slot_mask;
}
- void add_after_grow_and_destruct_old(Slot &old_slot,
- SlotArray &new_slots,
- const uint64_t new_slot_mask)
+ void add_after_grow(Slot &old_slot, SlotArray &new_slots, const uint64_t new_slot_mask)
{
const uint64_t hash = old_slot.get_hash(Hash());
SLOT_PROBING_BEGIN (ProbingStrategy, hash, new_slot_mask, slot_index) {
Slot &slot = new_slots[slot_index];
if (slot.is_empty()) {
- slot.relocate_occupied_here(old_slot, hash);
+ slot.occupy(std::move(*old_slot.key()), hash);
return;
}
}
SLOT_PROBING_END();
}
+ /**
+ * In some cases when exceptions are thrown, it's best to just reset the entire container to make
+ * sure that invariants are maintained. This should happen very rarely in practice.
+ */
+ void noexcept_reset() noexcept
+ {
+ Allocator allocator = slots_.allocator();
+ this->~Set();
+ new (this) Set(NoExceptConstructor(), allocator);
+ }
+
template<typename ForwardKey>
bool contains__impl(const ForwardKey &key, const uint64_t hash) const
{
@@ -699,11 +723,11 @@ class Set {
void remove_contained__impl(const ForwardKey &key, const uint64_t hash)
{
BLI_assert(this->contains_as(key));
- removed_slots_++;
SET_SLOT_PROBING_BEGIN (hash, slot) {
if (slot.contains(key, is_equal_, hash)) {
slot.remove();
+ removed_slots_++;
return;
}
}
diff --git a/source/blender/blenlib/BLI_set_slots.hh b/source/blender/blenlib/BLI_set_slots.hh
index ee5da17fcaf..a4d01dfdb68 100644
--- a/source/blender/blenlib/BLI_set_slots.hh
+++ b/source/blender/blenlib/BLI_set_slots.hh
@@ -88,7 +88,7 @@ template<typename Key> class SimpleSetSlot {
* other slot has to be moved as well. The other slot stays in the state it was in before. Its
* optionally stored key remains in a moved-from state.
*/
- SimpleSetSlot(SimpleSetSlot &&other) noexcept
+ SimpleSetSlot(SimpleSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>)
{
state_ = other.state_;
if (other.state_ == Occupied) {
@@ -139,19 +139,6 @@ template<typename Key> class SimpleSetSlot {
}
/**
- * Move the other slot into this slot and destruct it. We do destruction here, because this way
- * we can avoid a comparison with the state, since we know the slot is occupied.
- */
- void relocate_occupied_here(SimpleSetSlot &other, uint64_t UNUSED(hash))
- {
- BLI_assert(!this->is_occupied());
- BLI_assert(other.is_occupied());
- state_ = Occupied;
- new (&key_buffer_) Key(std::move(*other.key_buffer_));
- other.key_buffer_.ref().~Key();
- }
-
- /**
* Return true, when this slot is occupied and contains a key that compares equal to the given
* key. The hash is used by other slot implementations to determine inequality faster.
*/
@@ -171,8 +158,8 @@ template<typename Key> class SimpleSetSlot {
template<typename ForwardKey> void occupy(ForwardKey &&key, uint64_t UNUSED(hash))
{
BLI_assert(!this->is_occupied());
- state_ = Occupied;
new (&key_buffer_) Key(std::forward<ForwardKey>(key));
+ state_ = Occupied;
}
/**
@@ -181,8 +168,8 @@ template<typename Key> class SimpleSetSlot {
void remove()
{
BLI_assert(this->is_occupied());
- state_ = Removed;
key_buffer_.ref().~Key();
+ state_ = Removed;
}
};
@@ -224,7 +211,7 @@ template<typename Key> class HashedSetSlot {
}
}
- HashedSetSlot(HashedSetSlot &&other) noexcept
+ HashedSetSlot(HashedSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>)
{
state_ = other.state_;
if (other.state_ == Occupied) {
@@ -259,16 +246,6 @@ template<typename Key> class HashedSetSlot {
return hash_;
}
- void relocate_occupied_here(HashedSetSlot &other, const uint64_t hash)
- {
- BLI_assert(!this->is_occupied());
- BLI_assert(other.is_occupied());
- state_ = Occupied;
- hash_ = hash;
- new (&key_buffer_) Key(std::move(*other.key_buffer_));
- key_buffer_.ref().~Key();
- }
-
template<typename ForwardKey, typename IsEqual>
bool contains(const ForwardKey &key, const IsEqual &is_equal, const uint64_t hash) const
{
@@ -284,16 +261,16 @@ template<typename Key> class HashedSetSlot {
template<typename ForwardKey> void occupy(ForwardKey &&key, const uint64_t hash)
{
BLI_assert(!this->is_occupied());
+ new (&key_buffer_) Key(std::forward<ForwardKey>(key));
state_ = Occupied;
hash_ = hash;
- new (&key_buffer_) Key(std::forward<ForwardKey>(key));
}
void remove()
{
BLI_assert(this->is_occupied());
- state_ = Removed;
key_buffer_.ref().~Key();
+ state_ = Removed;
}
};
@@ -313,7 +290,8 @@ template<typename Key, typename KeyInfo> class IntrusiveSetSlot {
IntrusiveSetSlot() = default;
~IntrusiveSetSlot() = default;
IntrusiveSetSlot(const IntrusiveSetSlot &other) = default;
- IntrusiveSetSlot(IntrusiveSetSlot &&other) noexcept = default;
+ IntrusiveSetSlot(IntrusiveSetSlot &&other) noexcept(std::is_nothrow_move_constructible_v<Key>) =
+ default;
Key *key()
{
@@ -341,14 +319,6 @@ template<typename Key, typename KeyInfo> class IntrusiveSetSlot {
return hash(key_);
}
- void relocate_occupied_here(IntrusiveSetSlot &other, const uint64_t UNUSED(hash))
- {
- BLI_assert(!this->is_occupied());
- BLI_assert(other.is_occupied());
- key_ = std::move(other.key_);
- other.key_.~Key();
- }
-
template<typename ForwardKey, typename IsEqual>
bool contains(const ForwardKey &key, const IsEqual &is_equal, const uint64_t UNUSED(hash)) const
{
@@ -360,7 +330,6 @@ template<typename Key, typename KeyInfo> class IntrusiveSetSlot {
{
BLI_assert(!this->is_occupied());
BLI_assert(KeyInfo::is_not_empty_or_removed(key));
-
key_ = std::forward<ForwardKey>(key);
}
diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh
index 165814cf23c..5b4d2769f57 100644
--- a/source/blender/blenlib/BLI_span.hh
+++ b/source/blender/blenlib/BLI_span.hh
@@ -213,12 +213,20 @@ template<typename T> class Span {
{
return data_;
}
-
const T *end() const
{
return data_ + size_;
}
+ std::reverse_iterator<const T *> rbegin() const
+ {
+ return std::reverse_iterator<const T *>(this->end());
+ }
+ std::reverse_iterator<const T *> rend() const
+ {
+ return std::reverse_iterator<const T *>(this->begin());
+ }
+
/**
* Access an element in the array. This invokes undefined behavior when the index is out of
* bounds.
@@ -502,12 +510,20 @@ template<typename T> class MutableSpan {
{
return data_;
}
-
T *end() const
{
return data_ + size_;
}
+ std::reverse_iterator<T *> rbegin() const
+ {
+ return std::reverse_iterator<T *>(this->end());
+ }
+ std::reverse_iterator<T *> rend() const
+ {
+ return std::reverse_iterator<T *>(this->begin());
+ }
+
T &operator[](const int64_t index) const
{
BLI_assert(index < this->size());
diff --git a/source/blender/blenlib/BLI_stack.hh b/source/blender/blenlib/BLI_stack.hh
index 8eca356ec54..a463ac102f1 100644
--- a/source/blender/blenlib/BLI_stack.hh
+++ b/source/blender/blenlib/BLI_stack.hh
@@ -117,7 +117,7 @@ class Stack {
/**
* Initialize an empty stack. No heap allocation is done.
*/
- Stack(Allocator allocator = {}) : allocator_(allocator)
+ Stack(Allocator allocator = {}) noexcept : allocator_(allocator)
{
inline_chunk_.below = nullptr;
inline_chunk_.above = nullptr;
@@ -129,11 +129,15 @@ class Stack {
size_ = 0;
}
+ Stack(NoExceptConstructor, Allocator allocator = {}) noexcept : Stack(allocator)
+ {
+ }
+
/**
* Create a new stack that contains the given elements. The values are pushed to the stack in
* the order they are in the array.
*/
- Stack(Span<T> values) : Stack()
+ Stack(Span<T> values, Allocator allocator = {}) : Stack(NoExceptConstructor(), allocator)
{
this->push_multiple(values);
}
@@ -147,11 +151,12 @@ class Stack {
* assert(stack.pop() == 6);
* assert(stack.pop() == 5);
*/
- Stack(const std::initializer_list<T> &values) : Stack(Span<T>(values))
+ Stack(const std::initializer_list<T> &values, Allocator allocator = {})
+ : Stack(Span<T>(values), allocator)
{
}
- Stack(const Stack &other) : Stack(other.allocator_)
+ Stack(const Stack &other) : Stack(NoExceptConstructor(), other.allocator_)
{
for (const Chunk *chunk = &other.inline_chunk_; chunk; chunk = chunk->above) {
const T *begin = chunk->begin;
@@ -160,7 +165,8 @@ class Stack {
}
}
- Stack(Stack &&other) noexcept : Stack(other.allocator_)
+ Stack(Stack &&other) noexcept(std::is_nothrow_move_constructible_v<T>)
+ : Stack(NoExceptConstructor(), other.allocator_)
{
uninitialized_relocate_n<T>(
other.inline_buffer_, std::min(other.size_, InlineBufferCapacity), inline_buffer_);
@@ -197,28 +203,14 @@ class Stack {
}
}
- Stack &operator=(const Stack &stack)
+ Stack &operator=(const Stack &other)
{
- if (this == &stack) {
- return *this;
- }
-
- this->~Stack();
- new (this) Stack(stack);
-
- return *this;
+ return copy_assign_container(*this, other);
}
- Stack &operator=(Stack &&stack)
+ Stack &operator=(Stack &&other)
{
- if (this == &stack) {
- return *this;
- }
-
- this->~Stack();
- new (this) Stack(std::move(stack));
-
- return *this;
+ return move_assign_container(*this, std::move(other));
}
/**
@@ -226,21 +218,26 @@ class Stack {
*/
void push(const T &value)
{
- if (top_ == top_chunk_->capacity_end) {
- this->activate_next_chunk(1);
- }
- new (top_) T(value);
- top_++;
- size_++;
+ this->push_as(value);
}
void push(T &&value)
{
+ this->push_as(std::move(value));
+ }
+ template<typename ForwardT> void push_as(ForwardT &&value)
+ {
if (top_ == top_chunk_->capacity_end) {
this->activate_next_chunk(1);
}
- new (top_) T(std::move(value));
- top_++;
- size_++;
+ try {
+ new (top_) T(std::forward<ForwardT>(value));
+ top_++;
+ size_++;
+ }
+ catch (...) {
+ this->move_top_pointer_back_to_below_chunk();
+ throw;
+ }
}
/**
@@ -250,8 +247,8 @@ class Stack {
T pop()
{
BLI_assert(size_ > 0);
+ T value = std::move(*(top_ - 1));
top_--;
- T value = std::move(*top_);
top_->~T();
size_--;
@@ -296,13 +293,18 @@ class Stack {
const int64_t remaining_capacity = top_chunk_->capacity_end - top_;
const int64_t amount = std::min(remaining_values.size(), remaining_capacity);
- uninitialized_copy_n(remaining_values.data(), amount, top_);
+ try {
+ uninitialized_copy_n(remaining_values.data(), amount, top_);
+ }
+ catch (...) {
+ this->move_top_pointer_back_to_below_chunk();
+ throw;
+ }
top_ += amount;
+ size_ += amount;
remaining_values = remaining_values.drop_front(amount);
}
-
- size_ += values.size();
}
/**
@@ -332,6 +334,15 @@ class Stack {
top_ = top_chunk_->begin;
}
+ /* This should only be called by unit tests. */
+ bool is_invariant_maintained() const
+ {
+ if (size_ == 0) {
+ return top_ == inline_chunk_.begin;
+ }
+ return top_ > top_chunk_->begin;
+ }
+
private:
/**
* Changes top_chunk_ to point to a new chunk that is above the current one. The new chunk might
@@ -365,6 +376,18 @@ class Stack {
top_ = top_chunk_->begin;
}
+ void move_top_pointer_back_to_below_chunk()
+ {
+ /* This makes sure that the invariant stays intact after a failed push. */
+ if (size_ == 0) {
+ top_ = inline_chunk_.begin;
+ }
+ else if (top_ == top_chunk_->begin) {
+ top_chunk_ = top_chunk_->below;
+ top_ = top_chunk_->capacity_end;
+ }
+ }
+
void destruct_all_elements()
{
for (T *value = top_chunk_->begin; value != top_; value++) {
diff --git a/source/blender/blenlib/BLI_utildefines.h b/source/blender/blenlib/BLI_utildefines.h
index 2699f2498ac..acfa77ecf31 100644
--- a/source/blender/blenlib/BLI_utildefines.h
+++ b/source/blender/blenlib/BLI_utildefines.h
@@ -765,15 +765,15 @@ extern bool BLI_memory_is_zero(const void *arr, const size_t arr_size);
# define ENUM_OPERATORS(_enum_type) \
inline constexpr _enum_type operator|(_enum_type a, _enum_type b) \
{ \
- return a = static_cast<_enum_type>(static_cast<int>(a) | b); \
+ return static_cast<_enum_type>(static_cast<int>(a) | b); \
} \
inline constexpr _enum_type operator&(_enum_type a, _enum_type b) \
{ \
- return a = static_cast<_enum_type>(static_cast<int>(a) & b); \
+ return static_cast<_enum_type>(static_cast<int>(a) & b); \
} \
inline constexpr _enum_type operator~(_enum_type a) \
{ \
- return a = static_cast<_enum_type>(~static_cast<int>(a)); \
+ return static_cast<_enum_type>(~static_cast<int>(a)); \
} \
inline _enum_type &operator|=(_enum_type &a, _enum_type b) \
{ \
diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh
index 48110ef2814..3c90e1ab755 100644
--- a/source/blender/blenlib/BLI_vector.hh
+++ b/source/blender/blenlib/BLI_vector.hh
@@ -118,7 +118,7 @@ class Vector {
* Create an empty vector.
* This does not do any memory allocation.
*/
- Vector(Allocator allocator = {}) : allocator_(allocator)
+ Vector(Allocator allocator = {}) noexcept : allocator_(allocator)
{
begin_ = inline_buffer_;
end_ = begin_;
@@ -126,12 +126,17 @@ class Vector {
UPDATE_VECTOR_SIZE(this);
}
+ Vector(NoExceptConstructor, Allocator allocator = {}) noexcept : Vector(allocator)
+ {
+ }
+
/**
* Create a vector with a specific size.
* The elements will be default constructed.
* If T is trivially constructible, the elements in the vector are not touched.
*/
- explicit Vector(int64_t size) : Vector()
+ explicit Vector(int64_t size, Allocator allocator = {})
+ : Vector(NoExceptConstructor(), allocator)
{
this->resize(size);
}
@@ -139,7 +144,8 @@ class Vector {
/**
* Create a vector filled with a specific value.
*/
- Vector(int64_t size, const T &value) : Vector()
+ Vector(int64_t size, const T &value, Allocator allocator = {})
+ : Vector(NoExceptConstructor(), allocator)
{
this->resize(size, value);
}
@@ -148,12 +154,12 @@ class Vector {
* Create a vector from an array ref. The values in the vector are copy constructed.
*/
template<typename U, typename std::enable_if_t<std::is_convertible_v<U, T>> * = nullptr>
- Vector(Span<U> values, Allocator allocator = {}) : Vector(allocator)
+ Vector(Span<U> values, Allocator allocator = {}) : Vector(NoExceptConstructor(), allocator)
{
const int64_t size = values.size();
this->reserve(size);
- this->increase_size_by_unchecked(size);
uninitialized_convert_n<U, T>(values.data(), size, begin_);
+ this->increase_size_by_unchecked(size);
}
/**
@@ -178,17 +184,16 @@ class Vector {
{
}
- /**
- * Create a vector from any container. It must be possible to use the container in a
- * range-for loop.
- */
- template<typename ContainerT> static Vector FromContainer(const ContainerT &container)
+ template<typename InputIt,
+ /* This constructor should not be called with e.g. Vector(3, 10), because that is
+ expected to produce the vector (10, 10, 10). */
+ typename std::enable_if_t<!std::is_convertible_v<InputIt, int>> * = nullptr>
+ Vector(InputIt first, InputIt last, Allocator allocator = {})
+ : Vector(NoExceptConstructor(), allocator)
{
- Vector vector;
- for (const auto &value : container) {
- vector.append(value);
+ for (InputIt current = first; current != last; ++current) {
+ this->append(*current);
}
- return vector;
}
/**
@@ -198,7 +203,7 @@ class Vector {
* Example Usage:
* Vector<ModifierData *> modifiers(ob->modifiers);
*/
- Vector(ListBase &values) : Vector()
+ Vector(ListBase &values, Allocator allocator = {}) : Vector(NoExceptConstructor(), allocator)
{
LISTBASE_FOREACH (T, value, &values) {
this->append(value);
@@ -228,27 +233,26 @@ class Vector {
* have zero elements afterwards.
*/
template<int64_t OtherInlineBufferCapacity>
- Vector(Vector<T, OtherInlineBufferCapacity, Allocator> &&other) noexcept
- : allocator_(other.allocator_)
+ Vector(Vector<T, OtherInlineBufferCapacity, Allocator> &&other) noexcept(
+ std::is_nothrow_move_constructible_v<T>)
+ : Vector(NoExceptConstructor(), other.allocator_)
{
const int64_t size = other.size();
if (other.is_inline()) {
if (size <= InlineBufferCapacity) {
/* Copy between inline buffers. */
- begin_ = inline_buffer_;
- end_ = begin_ + size;
- capacity_end_ = begin_ + InlineBufferCapacity;
uninitialized_relocate_n(other.begin_, size, begin_);
+ end_ = begin_ + size;
}
else {
/* Copy from inline buffer to newly allocated buffer. */
const int64_t capacity = size;
begin_ = static_cast<T *>(
allocator_.allocate(sizeof(T) * static_cast<size_t>(capacity), alignof(T), AT));
- end_ = begin_ + size;
capacity_end_ = begin_ + capacity;
uninitialized_relocate_n(other.begin_, size, begin_);
+ end_ = begin_ + size;
}
}
else {
@@ -275,28 +279,12 @@ class Vector {
Vector &operator=(const Vector &other)
{
- if (this == &other) {
- return *this;
- }
-
- this->~Vector();
- new (this) Vector(other);
-
- return *this;
+ return copy_assign_container(*this, other);
}
Vector &operator=(Vector &&other)
{
- if (this == &other) {
- return *this;
- }
-
- /* This can be incorrect, when the vector is used to build a recursive data structure. However,
- we don't take care of it at this low level. See https://youtu.be/7Qgd9B1KuMQ?t=840. */
- this->~Vector();
- new (this) Vector(std::move(other));
-
- return *this;
+ return move_assign_container(*this, std::move(other));
}
/**
@@ -476,17 +464,10 @@ class Vector {
* behavior when not enough capacity has been reserved beforehand. Only use this in performance
* critical code.
*/
- void append_unchecked(const T &value)
+ template<typename ForwardT> void append_unchecked(ForwardT &&value)
{
BLI_assert(end_ < capacity_end_);
- new (end_) T(value);
- end_++;
- UPDATE_VECTOR_SIZE(this);
- }
- void append_unchecked(T &&value)
- {
- BLI_assert(end_ < capacity_end_);
- new (end_) T(std::move(value));
+ new (end_) T(std::forward<ForwardT>(value));
end_++;
UPDATE_VECTOR_SIZE(this);
}
@@ -499,7 +480,7 @@ class Vector {
{
BLI_assert(n >= 0);
this->reserve(this->size() + n);
- blender::uninitialized_fill_n(end_, n, value);
+ uninitialized_fill_n(end_, n, value);
this->increase_size_by_unchecked(n);
}
@@ -509,7 +490,7 @@ class Vector {
* useful when you want to call constructors in the vector yourself. This should only be done in
* very rare cases and has to be justified every time.
*/
- void increase_size_by_unchecked(const int64_t n)
+ void increase_size_by_unchecked(const int64_t n) noexcept
{
BLI_assert(end_ + n <= capacity_end_);
end_ += n;
@@ -555,11 +536,101 @@ class Vector {
{
BLI_assert(amount >= 0);
BLI_assert(begin_ + amount <= capacity_end_);
- blender::uninitialized_copy_n(start, amount, end_);
+ uninitialized_copy_n(start, amount, end_);
end_ += amount;
UPDATE_VECTOR_SIZE(this);
}
+ template<typename InputIt> void extend(InputIt first, InputIt last)
+ {
+ this->insert(this->end(), first, last);
+ }
+
+ /**
+ * Insert elements into the vector at the specified position. This has a running time of O(n)
+ * where n is the number of values that have to be moved. Undefined behavior is invoked when the
+ * insert position is out of bounds.
+ */
+ void insert(const int64_t insert_index, const T &value)
+ {
+ this->insert(insert_index, Span<T>(&value, 1));
+ }
+ void insert(const int64_t insert_index, T &&value)
+ {
+ this->insert(
+ insert_index, std::make_move_iterator(&value), std::make_move_iterator(&value + 1));
+ }
+ void insert(const int64_t insert_index, Span<T> array)
+ {
+ this->insert(begin_ + insert_index, array.begin(), array.end());
+ }
+ template<typename InputIt> void insert(const T *insert_position, InputIt first, InputIt last)
+ {
+ const int64_t insert_index = insert_position - begin_;
+ this->insert(insert_index, first, last);
+ }
+ template<typename InputIt> void insert(const int64_t insert_index, InputIt first, InputIt last)
+ {
+ BLI_assert(insert_index >= 0);
+ BLI_assert(insert_index <= this->size());
+
+ const int64_t insert_amount = std::distance(first, last);
+ const int64_t old_size = this->size();
+ const int64_t new_size = old_size + insert_amount;
+ const int64_t move_amount = old_size - insert_index;
+
+ this->reserve(new_size);
+ for (int64_t i = 0; i < move_amount; i++) {
+ const int64_t src_index = insert_index + move_amount - i - 1;
+ const int64_t dst_index = new_size - i - 1;
+ try {
+ new (static_cast<void *>(begin_ + dst_index)) T(std::move(begin_[src_index]));
+ }
+ catch (...) {
+ /* Destruct all values that have been moved already. */
+ destruct_n(begin_ + dst_index + 1, i);
+ end_ = begin_ + src_index + 1;
+ UPDATE_VECTOR_SIZE(this);
+ throw;
+ }
+ begin_[src_index].~T();
+ }
+
+ try {
+ std::uninitialized_copy_n(first, insert_amount, begin_ + insert_index);
+ }
+ catch (...) {
+ /* Destruct all values that have been moved. */
+ destruct_n(begin_ + new_size - move_amount, move_amount);
+ end_ = begin_ + insert_index;
+ UPDATE_VECTOR_SIZE(this);
+ throw;
+ }
+ end_ = begin_ + new_size;
+ UPDATE_VECTOR_SIZE(this);
+ }
+
+ /**
+ * Insert values at the beginning of the vector. The has to move all the other elements, so it
+ * has a linear running time.
+ */
+ void prepend(const T &&value)
+ {
+ this->insert(0, value);
+ }
+ void prepend(T &&value)
+ {
+ this->insert(0, std::move(value));
+ }
+ void prepend(Span<T> values)
+ {
+ this->insert(0, values);
+ }
+ template<typename InputIt> void prepend(InputIt first, InputIt last)
+ {
+ this->insert(0, first, last);
+ }
+
/**
* Return a reference to the last element in the vector.
* This invokes undefined behavior when the vector is empty.
@@ -616,8 +687,8 @@ class Vector {
T pop_last()
{
BLI_assert(!this->is_empty());
+ T value = std::move(*(end_ - 1));
end_--;
- T value = std::move(*end_);
end_->~T();
UPDATE_VECTOR_SIZE(this);
return value;
@@ -632,10 +703,10 @@ class Vector {
BLI_assert(index >= 0);
BLI_assert(index < this->size());
T *element_to_remove = begin_ + index;
- end_--;
if (element_to_remove < end_) {
- *element_to_remove = std::move(*end_);
+ *element_to_remove = std::move(*(end_ - 1));
}
+ end_--;
end_->~T();
UPDATE_VECTOR_SIZE(this);
}
@@ -671,6 +742,27 @@ class Vector {
}
/**
+ * Remove a contiguous chunk of elements and move all values coming after it towards the front.
+ * This takes O(n) time.
+ *
+ * This is similar to std::vector::erase.
+ */
+ void remove(const int64_t start_index, const int64_t amount)
+ {
+ const int64_t old_size = this->size();
+ BLI_assert(start_index >= 0);
+ BLI_assert(amount >= 0);
+ BLI_assert(start_index + amount <= old_size);
+ const int64_t move_amount = old_size - start_index - amount;
+ for (int64_t i = 0; i < move_amount; i++) {
+ begin_[start_index + i] = std::move(begin_[start_index + amount + i]);
+ }
+ destruct_n(end_ - amount, amount);
+ end_ -= amount;
+ UPDATE_VECTOR_SIZE(this);
+ }
+
+ /**
* Do a linear search to find the value in the vector.
* When found, return the first index, otherwise return -1.
*/
@@ -746,6 +838,24 @@ class Vector {
return end_;
}
+ std::reverse_iterator<T *> rbegin()
+ {
+ return std::reverse_iterator<T *>(this->end());
+ }
+ std::reverse_iterator<T *> rend()
+ {
+ return std::reverse_iterator<T *>(this->begin());
+ }
+
+ std::reverse_iterator<const T *> rbegin() const
+ {
+ return std::reverse_iterator<T *>(this->end());
+ }
+ std::reverse_iterator<const T *> rend() const
+ {
+ return std::reverse_iterator<T *>(this->begin());
+ }
+
/**
* Get the current capacity of the vector, i.e. the maximum number of elements the vector can
* hold, before it has to reallocate.
@@ -813,7 +923,13 @@ class Vector {
T *new_array = static_cast<T *>(
allocator_.allocate(static_cast<size_t>(new_capacity) * sizeof(T), alignof(T), AT));
- uninitialized_relocate_n(begin_, size, new_array);
+ try {
+ uninitialized_relocate_n(begin_, size, new_array);
+ }
+ catch (...) {
+ allocator_.deallocate(new_array);
+ throw;
+ }
if (!this->is_inline()) {
allocator_.deallocate(begin_);
diff --git a/source/blender/blenlib/BLI_winstuff.h b/source/blender/blenlib/BLI_winstuff.h
index 2de6098f6be..8076724a214 100644
--- a/source/blender/blenlib/BLI_winstuff.h
+++ b/source/blender/blenlib/BLI_winstuff.h
@@ -28,6 +28,8 @@
# error "This include is for Windows only!"
#endif
+#include "BLI_sys_types.h"
+
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -86,6 +88,7 @@ typedef long ssize_t;
# endif
#endif
+/* Directory reading compatibility with UNIX. */
struct dirent {
int d_ino;
int d_off;
@@ -99,13 +102,12 @@ typedef struct __dirstream DIR;
DIR *opendir(const char *path);
struct dirent *readdir(DIR *dp);
int closedir(DIR *dp);
-
-void RegisterBlendExtension(void);
-void get_default_root(char *root);
-int check_file_chars(char *filename);
const char *dirname(char *path);
-int BLI_getInstallationDir(char *str);
+/* Windows utility functions. */
+void BLI_windows_register_blend_extension(const bool background);
+void BLI_windows_get_default_root_dir(char *root_dir);
+int BLI_windows_get_executable_dir(char *str);
#ifdef __cplusplus
}
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 819c74b6946..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
@@ -390,6 +417,8 @@ if(WITH_GTESTS)
tests/BLI_task_test.cc
tests/BLI_vector_set_test.cc
tests/BLI_vector_test.cc
+
+ tests/BLI_exception_safety_test_utils.hh
)
set(TEST_INC
../imbuf
diff --git a/source/blender/blenlib/intern/BLI_kdopbvh.c b/source/blender/blenlib/intern/BLI_kdopbvh.c
index f63a523ca60..f030a733752 100644
--- a/source/blender/blenlib/intern/BLI_kdopbvh.c
+++ b/source/blender/blenlib/intern/BLI_kdopbvh.c
@@ -201,6 +201,23 @@ const float bvhtree_kdop_axes[13][3] = {
{0, 1.0, -1.0},
};
+/* Used to correct the epsilon and thus match the overlap distance. */
+const float bvhtree_kdop_axes_length[13] = {
+ 1.0f,
+ 1.0f,
+ 1.0f,
+ 1.7320508075688772f,
+ 1.7320508075688772f,
+ 1.7320508075688772f,
+ 1.7320508075688772f,
+ 1.4142135623730951f,
+ 1.4142135623730951f,
+ 1.4142135623730951f,
+ 1.4142135623730951f,
+ 1.4142135623730951f,
+ 1.4142135623730951f,
+};
+
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
@@ -970,9 +987,18 @@ void BLI_bvhtree_balance(BVHTree *tree)
#endif
}
-void BLI_bvhtree_insert(BVHTree *tree, int index, const float co[3], int numpoints)
+static void bvhtree_node_inflate(const BVHTree *tree, BVHNode *node, const float dist)
{
axis_t axis_iter;
+ for (axis_iter = tree->start_axis; axis_iter < tree->stop_axis; axis_iter++) {
+ float dist_corrected = dist * bvhtree_kdop_axes_length[axis_iter];
+ node->bv[(2 * axis_iter)] -= dist_corrected; /* minimum */
+ node->bv[(2 * axis_iter) + 1] += dist_corrected; /* maximum */
+ }
+}
+
+void BLI_bvhtree_insert(BVHTree *tree, int index, const float co[3], int numpoints)
+{
BVHNode *node = NULL;
/* insert should only possible as long as tree->totbranch is 0 */
@@ -986,10 +1012,7 @@ void BLI_bvhtree_insert(BVHTree *tree, int index, const float co[3], int numpoin
node->index = index;
/* inflate the bv with some epsilon */
- for (axis_iter = tree->start_axis; axis_iter < tree->stop_axis; axis_iter++) {
- node->bv[(2 * axis_iter)] -= tree->epsilon; /* minimum */
- node->bv[(2 * axis_iter) + 1] += tree->epsilon; /* maximum */
- }
+ bvhtree_node_inflate(tree, node, tree->epsilon);
}
/* call before BLI_bvhtree_update_tree() */
@@ -997,7 +1020,6 @@ bool BLI_bvhtree_update_node(
BVHTree *tree, int index, const float co[3], const float co_moving[3], int numpoints)
{
BVHNode *node = NULL;
- axis_t axis_iter;
/* check if index exists */
if (index > tree->totleaf) {
@@ -1013,10 +1035,7 @@ bool BLI_bvhtree_update_node(
}
/* inflate the bv with some epsilon */
- for (axis_iter = tree->start_axis; axis_iter < tree->stop_axis; axis_iter++) {
- node->bv[(2 * axis_iter)] -= tree->epsilon; /* minimum */
- node->bv[(2 * axis_iter) + 1] += tree->epsilon; /* maximum */
- }
+ bvhtree_node_inflate(tree, node, tree->epsilon);
return true;
}
diff --git a/source/blender/blenlib/intern/delaunay_2d.c b/source/blender/blenlib/intern/delaunay_2d.c
deleted file mode 100644
index baf49ddaffd..00000000000
--- a/source/blender/blenlib/intern/delaunay_2d.c
+++ /dev/null
@@ -1,5170 +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);
- }
- 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/freetypefont.c b/source/blender/blenlib/intern/freetypefont.c
index 34886054cb0..301d9dc2296 100644
--- a/source/blender/blenlib/intern/freetypefont.c
+++ b/source/blender/blenlib/intern/freetypefont.c
@@ -15,7 +15,8 @@
*
* The Original Code is written by Rob Haarsma (phase)
* All rights reserved.
- * This code parses the Freetype font outline data to chains of Blender's beziertriples.
+ *
+ * This code parses the Freetype font outline data to chains of Blender's bezier-triples.
* Additional information can be found at the bottom of this file.
*
* Code that uses exotic character maps is present but commented out.
diff --git a/source/blender/blenlib/intern/math_boolean.cc b/source/blender/blenlib/intern/math_boolean.cc
new file mode 100644
index 00000000000..0c3b4ab8395
--- /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
+ * significant 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. Non-robust.
+ * 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_color.c b/source/blender/blenlib/intern/math_color.c
index 09bb7ea5711..4b62d6b9b5b 100644
--- a/source/blender/blenlib/intern/math_color.c
+++ b/source/blender/blenlib/intern/math_color.c
@@ -198,7 +198,7 @@ void ycc_to_rgb(float y, float cb, float cr, float *r_r, float *r_g, float *r_b,
*r_b = b / 255.0f;
}
-void hex_to_rgb(char *hexcol, float *r_r, float *r_g, float *r_b)
+void hex_to_rgb(const char *hexcol, float *r_r, float *r_g, float *r_b)
{
unsigned int ri, gi, bi;
diff --git a/source/blender/blenlib/intern/math_matrix.c b/source/blender/blenlib/intern/math_matrix.c
index fadd7d83444..f523bd07c09 100644
--- a/source/blender/blenlib/intern/math_matrix.c
+++ b/source/blender/blenlib/intern/math_matrix.c
@@ -3118,6 +3118,115 @@ void invert_m4_m4_safe(float Ainv[4][4], const float A[4][4])
}
}
+/* -------------------------------------------------------------------- */
+/** \name Invert (Safe Orthographic)
+ *
+ * Invert the matrix, filling in zeroed axes using the valid ones where possible.
+ *
+ * Unlike #invert_m4_m4_safe set degenerate axis unit length instead of adding a small value,
+ * which has the results in:
+ *
+ * - Scaling by a large value on the resulting matrix.
+ * - Changing axis which aren't degenerate.
+ *
+ * \note We could support passing in a length value if there is a good use-case
+ * where we want to specify the length of the degenerate axes.
+ * \{ */
+
+/**
+ * Return true if invert should be attempted again.
+ *
+ * \note Takes an array of points to be usable from 3x3 and 4x4 matrices.
+ */
+static bool invert_m3_m3_safe_ortho_prepare(float *mat[3])
+{
+ enum { X = 1 << 0, Y = 1 << 1, Z = 1 << 2 };
+ int flag = 0;
+ for (int i = 0; i < 3; i++) {
+ flag |= (len_squared_v3(mat[i]) == 0.0f) ? (1 << i) : 0;
+ }
+
+ /* Either all or none are zero, either way we can't properly resolve this
+ * since we need to fill invalid axes from valid ones. */
+ if (ELEM(flag, 0, X | Y | Z)) {
+ return false;
+ }
+
+ switch (flag) {
+ case X | Y: {
+ ortho_v3_v3(mat[1], mat[2]);
+ ATTR_FALLTHROUGH;
+ }
+ case X: {
+ cross_v3_v3v3(mat[0], mat[1], mat[2]);
+ break;
+ }
+
+ case Y | Z: {
+ ortho_v3_v3(mat[2], mat[0]);
+ ATTR_FALLTHROUGH;
+ }
+ case Y: {
+ cross_v3_v3v3(mat[1], mat[0], mat[2]);
+ break;
+ }
+
+ case Z | X: {
+ ortho_v3_v3(mat[0], mat[1]);
+ ATTR_FALLTHROUGH;
+ }
+ case Z: {
+ cross_v3_v3v3(mat[2], mat[0], mat[1]);
+ break;
+ }
+ default: {
+ BLI_assert(0); /* Unreachable! */
+ }
+ }
+
+ for (int i = 0; i < 3; i++) {
+ if (flag & (1 << i)) {
+ if (UNLIKELY(normalize_v3(mat[i]) == 0.0f)) {
+ mat[i][i] = 1.0f;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * A safe version of invert that uses valid axes, calculating the zero'd axis
+ * based on the non-zero ones.
+ *
+ * This works well for transformation matrices, when a single axis is zerod.
+ */
+void invert_m4_m4_safe_ortho(float Ainv[4][4], const float A[4][4])
+{
+ if (UNLIKELY(!invert_m4_m4(Ainv, A))) {
+ float Atemp[4][4];
+ copy_m4_m4(Atemp, A);
+ if (UNLIKELY(!(invert_m3_m3_safe_ortho_prepare((float *[3]){UNPACK3(Atemp)}) &&
+ invert_m4_m4(Ainv, Atemp)))) {
+ unit_m4(Ainv);
+ }
+ }
+}
+
+void invert_m3_m3_safe_ortho(float Ainv[3][3], const float A[3][3])
+{
+ if (UNLIKELY(!invert_m3_m3(Ainv, A))) {
+ float Atemp[3][3];
+ copy_m3_m3(Atemp, A);
+ if (UNLIKELY(!(invert_m3_m3_safe_ortho_prepare((float *[3]){UNPACK3(Atemp)}) &&
+ invert_m3_m3(Ainv, Atemp)))) {
+ unit_m3(Ainv);
+ }
+ }
+}
+
+/** \} */
+
/**
* #SpaceTransform struct encapsulates all needed data to convert between two coordinate spaces
* (where conversion can be represented by a matrix multiplication).
diff --git a/source/blender/blenlib/intern/math_vec.cc b/source/blender/blenlib/intern/math_vec.cc
new file mode 100644
index 00000000000..54926f84eb9
--- /dev/null
+++ b/source/blender/blenlib/intern/math_vec.cc
@@ -0,0 +1,195 @@
+/*
+ * 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;
+}
+
+#ifdef WITH_GMP
+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);
+}
+#endif
+
+} // 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..13a87b9ec6a 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)
@@ -1274,6 +1313,11 @@ MINLINE bool equals_v2v2_int(const int v1[2], const int v2[2])
return ((v1[0] == v2[0]) && (v1[1] == v2[1]));
}
+MINLINE bool equals_v4v4_int(const int v1[4], const int v2[4])
+{
+ return ((v1[0] == v2[0]) && (v1[1] == v2[1]) && (v1[2] == v2[2]) && (v1[3] == v2[3]));
+}
+
MINLINE bool compare_v2v2(const float v1[2], const float v2[2], const float limit)
{
return (compare_ff(v1[0], v2[0], limit) && compare_ff(v1[1], v2[1], limit));
diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc
new file mode 100644
index 00000000000..387d879c5af
--- /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 closestp 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..5bd25404674
--- /dev/null
+++ b/source/blender/blenlib/intern/mesh_intersect.cc
@@ -0,0 +1,3322 @@
+/*
+ * 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()
+{
+ 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_.combine(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.
+ * In addition, it must not be the case that all of the points of one triangle
+ * are totally to one side of one edge of the other triangle, 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;
+ }
+ int s = orients[ab][0][i] + orients[ab][1][i] + orients[ab][2][i];
+ if (s == 3 || s == -3) {
+ 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 Map<std::pair<int, int>, ITT_value> &itt_map)
+{
+ 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) {
+ if (!itt_map.contains(std::pair<int, int>(t, cl_t)) &&
+ !itt_map.contains(std::pair<int, int>(cl_t, t))) {
+ continue;
+ }
+ 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);
+}
+
+/* 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;
+
+/**
+ * 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;
+
+/* Not using this at the moment. Leaving it for a bit in case we want it again. */
+# if 0
+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);
+}
+
+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;
+}
+# endif
+
+/**
+ * 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;
+}
+
+/* Not using this at the moment. Leave it here for a while in case we want it again. */
+# if 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;
+}
+# endif
+
+/*
+ * 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};
+ Array<int> first_overlap_;
+ 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) {
+ overlap_ = BLI_bvhtree_overlap(tree_, tree_, &overlap_tot_, NULL, NULL);
+ }
+ 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";
+ }
+ }
+ first_overlap_ = Array<int>(tm.face_size(), -1);
+ for (int i = 0; i < static_cast<int>(overlap_tot_); ++i) {
+ int t = overlap_[i].indexA;
+ if (first_overlap_[t] == -1) {
+ first_overlap_[t] = i;
+ }
+ }
+ }
+
+ ~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_);
+ }
+
+ int first_overlap_index(int t) const
+ {
+ return first_overlap_[t];
+ }
+
+ private:
+ 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.
+ */
+static void calc_overlap_itts(Map<std::pair<int, int>, ITT_value> &itt_map,
+ const IMesh &tm,
+ 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)) {
+ 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);
+}
+
+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 = ov.first_overlap_index(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,
+ const Map<std::pair<int, int>, ITT_value> &itt_map)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CLUSTERS\n";
+ }
+ CoplanarClusterInfo ans(tm.face_size());
+ /* Use a VectorSet to get stable order from run to run. */
+ VectorSet<int> maybe_coplanar_tris;
+ maybe_coplanar_tris.reserve(2 * itt_map.size());
+ for (auto item : itt_map.items()) {
+ if (item.value.kind == ICOPLANAR) {
+ int t1 = item.key.first;
+ int t2 = item.key.second;
+ maybe_coplanar_tris.add_multiple({t1, t2});
+ }
+ }
+ if (dbg_level > 0) {
+ std::cout << "found " << maybe_coplanar_tris.size() << " possible coplanar tris\n";
+ }
+ if (maybe_coplanar_tris.size() == 0) {
+ if (dbg_level > 0) {
+ std::cout << "No possible coplanar tris, so no clusters\n";
+ }
+ return ans;
+ }
+ /* 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(maybe_coplanar_tris.size());
+ for (int t : maybe_coplanar_tris) {
+ /* 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 (dbg_level > 1) {
+ std::cout << "consider intersecting with cluster " << cl << "\n";
+ }
+ if (bbs_might_intersect(tri_bb[t], cl.bounding_box()) &&
+ non_trivially_coplanar_intersects(tm, t, cl, proj_axis, itt_map)) {
+ 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";
+ }
+ write_obj_mesh(const_cast<IMesh &>(tm_in), "trimesh_input");
+ }
+ }
+# 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;
+ }
+ }
+# 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
+ for (int t : tm_clean->face_index_range()) {
+ if (tri_ov.first_overlap_index(t) != -1) {
+ tm_clean->face(t)->populate_plane(true);
+ }
+ }
+# ifdef PERFDEBUG
+ double plane_populate = PIL_check_seconds_timer();
+ std::cout << "planes populated, time = " << plane_populate - overlap_time << "\n";
+# 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, tri_ov, arena);
+# ifdef PERFDEBUG
+ double itt_time = PIL_check_seconds_timer();
+ std::cout << "itts found, time = " << itt_time - plane_populate << "\n";
+# endif
+ CoplanarClusterInfo clinfo = find_clusters(*tm_clean, tri_bb, itt_map);
+ 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 - itt_time << "\n";
+ doperfmax(0, tm_in.face_size());
+ doperfmax(1, clinfo.tot_cluster());
+ doperfmax(2, tri_ov.overlap().size());
+# 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/intern/path_util.c b/source/blender/blenlib/intern/path_util.c
index 67d41ffb779..cde4394a8c3 100644
--- a/source/blender/blenlib/intern/path_util.c
+++ b/source/blender/blenlib/intern/path_util.c
@@ -532,7 +532,7 @@ void BLI_path_rel(char *file, const char *relfile)
char *ptemp;
/* fix missing volume name in relative base,
* can happen with old recent-files.txt files */
- get_default_root(temp);
+ BLI_windows_get_default_root_dir(temp);
ptemp = &temp[2];
if (relfile[0] != '\\' && relfile[0] != '/') {
ptemp++;
@@ -1026,7 +1026,7 @@ bool BLI_path_abs(char *path, const char *basepath)
*/
if (!wasrelative && !BLI_path_is_abs(path)) {
char *p = path;
- get_default_root(tmp);
+ BLI_windows_get_default_root_dir(tmp);
// get rid of the slashes at the beginning of the path
while (ELEM(*p, '\\', '/')) {
p++;
@@ -1385,7 +1385,7 @@ void BLI_make_file_string(const char *relabase, char *string, const char *dir, c
string[3] = '\0';
}
else { /* we're out of luck here, guessing the first valid drive, usually c:\ */
- get_default_root(string);
+ BLI_windows_get_default_root_dir(string);
}
/* ignore leading slashes */
diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c
index df7e7107d11..333b6783087 100644
--- a/source/blender/blenlib/intern/winstuff.c
+++ b/source/blender/blenlib/intern/winstuff.c
@@ -36,14 +36,12 @@
# include "BLI_utildefines.h"
# include "BLI_winstuff.h"
-# include "../blenkernel/BKE_global.h" /* G.background, bad level include (no function calls) */
-
# include "utf_winfunc.h"
# include "utfconv.h"
/* FILE_MAXDIR + FILE_MAXFILE */
-int BLI_getInstallationDir(char *str)
+int BLI_windows_get_executable_dir(char *str)
{
char dir[FILE_MAXDIR];
int a;
@@ -60,19 +58,19 @@ int BLI_getInstallationDir(char *str)
return 1;
}
-static void RegisterBlendExtension_Fail(HKEY root)
+static void register_blend_extension_failed(HKEY root, const bool background)
{
printf("failed\n");
if (root) {
RegCloseKey(root);
}
- if (!G.background) {
+ if (!background) {
MessageBox(0, "Could not register file extension.", "Blender error", MB_OK | MB_ICONERROR);
}
TerminateProcess(GetCurrentProcess(), 1);
}
-void RegisterBlendExtension(void)
+void BLI_windows_register_blend_extension(const bool background)
{
LONG lresult;
HKEY hkey = 0;
@@ -108,7 +106,7 @@ void RegisterBlendExtension(void)
usr_mode = true;
lresult = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Classes", 0, KEY_ALL_ACCESS, &root);
if (lresult != ERROR_SUCCESS) {
- RegisterBlendExtension_Fail(0);
+ register_blend_extension_failed(0, background);
}
}
@@ -120,7 +118,7 @@ void RegisterBlendExtension(void)
RegCloseKey(hkey);
}
if (lresult != ERROR_SUCCESS) {
- RegisterBlendExtension_Fail(root);
+ register_blend_extension_failed(root, background);
}
lresult = RegCreateKeyEx(root,
@@ -138,7 +136,7 @@ void RegisterBlendExtension(void)
RegCloseKey(hkey);
}
if (lresult != ERROR_SUCCESS) {
- RegisterBlendExtension_Fail(root);
+ register_blend_extension_failed(root, background);
}
lresult = RegCreateKeyEx(root,
@@ -156,7 +154,7 @@ void RegisterBlendExtension(void)
RegCloseKey(hkey);
}
if (lresult != ERROR_SUCCESS) {
- RegisterBlendExtension_Fail(root);
+ register_blend_extension_failed(root, background);
}
lresult = RegCreateKeyEx(
@@ -167,10 +165,10 @@ void RegisterBlendExtension(void)
RegCloseKey(hkey);
}
if (lresult != ERROR_SUCCESS) {
- RegisterBlendExtension_Fail(root);
+ register_blend_extension_failed(root, background);
}
- BLI_getInstallationDir(InstallDir);
+ BLI_windows_get_executable_dir(InstallDir);
GetSystemDirectory(SysDir, FILE_MAXDIR);
ThumbHandlerDLL = "BlendThumb.dll";
snprintf(
@@ -179,7 +177,7 @@ void RegisterBlendExtension(void)
RegCloseKey(root);
printf("success (%s)\n", usr_mode ? "user" : "system");
- if (!G.background) {
+ if (!background) {
sprintf(MBox,
"File extension registered for %s.",
usr_mode ? "the current user. To register for all users, run as an administrator" :
@@ -189,7 +187,7 @@ void RegisterBlendExtension(void)
TerminateProcess(GetCurrentProcess(), 0);
}
-void get_default_root(char *root)
+void BLI_windows_get_default_root_dir(char *root)
{
char str[MAX_PATH + 1];
@@ -236,7 +234,7 @@ void get_default_root(char *root)
}
}
if (0 == rc) {
- printf("ERROR in 'get_default_root': can't find a valid drive!\n");
+ printf("ERROR in 'BLI_windows_get_default_root_dir': can't find a valid drive!\n");
root[0] = 'C';
root[1] = ':';
root[2] = '\\';
@@ -246,30 +244,6 @@ void get_default_root(char *root)
}
}
-/* UNUSED */
-# if 0
-int check_file_chars(char *filename)
-{
- char *p = filename;
- while (*p) {
- switch (*p) {
- case ':':
- case '?':
- case '*':
- case '|':
- case '\\':
- case '/':
- case '\"':
- return 0;
- break;
- }
-
- p++;
- }
- return 1;
-}
-# endif
-
#else
/* intentionally empty for UNIX */
diff --git a/source/blender/blenlib/tests/BLI_array_test.cc b/source/blender/blenlib/tests/BLI_array_test.cc
index 7348a6f93f3..7d967eca87e 100644
--- a/source/blender/blenlib/tests/BLI_array_test.cc
+++ b/source/blender/blenlib/tests/BLI_array_test.cc
@@ -1,7 +1,9 @@
/* Apache License, Version 2.0 */
#include "BLI_array.hh"
+#include "BLI_exception_safety_test_utils.hh"
#include "BLI_strict_flags.h"
+#include "BLI_vector.hh"
#include "testing/testing.h"
namespace blender::tests {
@@ -173,4 +175,79 @@ TEST(array, Fill)
EXPECT_EQ(array[4], 3);
}
+TEST(array, ReverseIterator)
+{
+ Array<int> array = {3, 4, 5, 6};
+ Vector<int> reversed_vec;
+
+ for (auto it = array.rbegin(); it != array.rend(); ++it) {
+ reversed_vec.append(*it);
+ *it += 10;
+ }
+
+ EXPECT_EQ_ARRAY(reversed_vec.data(), Span({6, 5, 4, 3}).data(), 4);
+ EXPECT_EQ_ARRAY(array.data(), Span({13, 14, 15, 16}).data(), 4);
+}
+
+TEST(array, SpanConstructorExceptions)
+{
+ std::array<ExceptionThrower, 4> values;
+ values[2].throw_during_copy = true;
+ Span<ExceptionThrower> span{values};
+ EXPECT_ANY_THROW({ Array<ExceptionThrower> array(span); });
+}
+
+TEST(array, SizeValueConstructorExceptions)
+{
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ Array<ExceptionThrower> array(5, value); });
+}
+
+TEST(array, MoveConstructorExceptions)
+{
+ Array<ExceptionThrower, 4> array(3);
+ array[1].throw_during_move = true;
+ EXPECT_ANY_THROW({ Array<ExceptionThrower> array_copy(std::move(array)); });
+}
+
+TEST(array, CopyAssignmentExceptions)
+{
+ Array<ExceptionThrower> array(5);
+ array[3].throw_during_copy = true;
+ Array<ExceptionThrower> array_copy(10);
+ EXPECT_ANY_THROW({ array_copy = array; });
+}
+
+TEST(array, MoveAssignmentExceptions)
+{
+ Array<ExceptionThrower, 4> array(4);
+ array[2].throw_during_move = true;
+ Array<ExceptionThrower> array_moved(10);
+ EXPECT_ANY_THROW({ array_moved = std::move(array); });
+}
+
+TEST(array, Last)
+{
+ Array<int> array = {5, 7, 8, 9};
+ EXPECT_EQ(array.last(), 9);
+ array.last() = 1;
+ EXPECT_EQ(array[3], 1);
+ EXPECT_EQ(const_cast<const Array<int> &>(array).last(), 1);
+}
+
+TEST(array, Reinitialize)
+{
+ Array<std::string> array = {"hello", "world"};
+ EXPECT_EQ(array.size(), 2);
+ EXPECT_EQ(array[1], "world");
+ array.reinitialize(3);
+ EXPECT_EQ(array.size(), 3);
+ EXPECT_EQ(array[0], "");
+ EXPECT_EQ(array[1], "");
+ EXPECT_EQ(array[2], "");
+ array.reinitialize(0);
+ EXPECT_EQ(array.size(), 0);
+}
+
} // namespace blender::tests
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_exception_safety_test_utils.hh b/source/blender/blenlib/tests/BLI_exception_safety_test_utils.hh
new file mode 100644
index 00000000000..91270767a25
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_exception_safety_test_utils.hh
@@ -0,0 +1,102 @@
+#include "BLI_hash.hh"
+#include "BLI_utildefines.h"
+#include "MEM_guardedalloc.h"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+class ExceptionThrower {
+ private:
+ /* Use some random values that are unlikely to exist at the memory location already. */
+ static constexpr uint32_t is_alive_state = 0x21254634;
+ static constexpr uint32_t is_destructed_state = 0xFA4BC327;
+
+ uint32_t state_;
+
+ /* Make use of leak detector to check if this value has been destructed. */
+ void *my_memory_;
+
+ public:
+ mutable bool throw_during_copy;
+ mutable bool throw_during_move;
+ /* Used for hashing and comparing. */
+ int value;
+
+ ExceptionThrower(int value = 0)
+ : state_(is_alive_state),
+ my_memory_(MEM_mallocN(1, AT)),
+ throw_during_copy(false),
+ throw_during_move(false),
+ value(value)
+ {
+ }
+
+ ExceptionThrower(const ExceptionThrower &other) : ExceptionThrower(other.value)
+ {
+ EXPECT_EQ(other.state_, is_alive_state);
+ if (other.throw_during_copy) {
+ throw std::runtime_error("throwing during copy, as requested");
+ }
+ }
+
+ ExceptionThrower(ExceptionThrower &&other) : ExceptionThrower(other.value)
+ {
+ EXPECT_EQ(other.state_, is_alive_state);
+ if (other.throw_during_move) {
+ throw std::runtime_error("throwing during move, as requested");
+ }
+ }
+
+ ExceptionThrower &operator=(const ExceptionThrower &other)
+ {
+ EXPECT_EQ(other.state_, is_alive_state);
+ if (throw_during_copy || other.throw_during_copy) {
+ throw std::runtime_error("throwing during copy, as requested");
+ }
+ value = other.value;
+ return *this;
+ }
+
+ ExceptionThrower &operator=(ExceptionThrower &&other)
+ {
+ EXPECT_EQ(other.state_, is_alive_state);
+ if (throw_during_move || other.throw_during_move) {
+ throw std::runtime_error("throwing during move, as requested");
+ }
+ value = other.value;
+ return *this;
+ }
+
+ ~ExceptionThrower()
+ {
+ const char *message = "";
+ if (state_ != is_alive_state) {
+ if (state_ == is_destructed_state) {
+ message = "Trying to destruct an already destructed instance.";
+ }
+ else {
+ message = "Trying to destruct an uninitialized instance.";
+ }
+ }
+ EXPECT_EQ(state_, is_alive_state) << message;
+ state_ = is_destructed_state;
+ MEM_freeN(my_memory_);
+ }
+
+ uint64_t hash() const
+ {
+ return static_cast<uint64_t>(value);
+ }
+
+ friend bool operator==(const ExceptionThrower &a, const ExceptionThrower &b)
+ {
+ return a.value == b.value;
+ }
+
+ friend bool operator!=(const ExceptionThrower &a, const ExceptionThrower &b)
+ {
+ return !(a == b);
+ }
+};
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_map_test.cc b/source/blender/blenlib/tests/BLI_map_test.cc
index fe7b0f01279..7b4a484e736 100644
--- a/source/blender/blenlib/tests/BLI_map_test.cc
+++ b/source/blender/blenlib/tests/BLI_map_test.cc
@@ -1,5 +1,6 @@
/* Apache License, Version 2.0 */
+#include "BLI_exception_safety_test_utils.hh"
#include "BLI_map.hh"
#include "BLI_rand.h"
#include "BLI_set.hh"
@@ -479,6 +480,72 @@ TEST(map, ForeachItem)
EXPECT_EQ(keys.first_index_of(1), values.first_index_of(8));
}
+TEST(map, CopyConstructorExceptions)
+{
+ using MapType = Map<ExceptionThrower, ExceptionThrower>;
+ MapType map;
+ map.add(2, 2);
+ map.add(4, 4);
+ map.lookup(2).throw_during_copy = true;
+ EXPECT_ANY_THROW({ MapType map_copy(map); });
+}
+
+TEST(map, MoveConstructorExceptions)
+{
+ using MapType = Map<ExceptionThrower, ExceptionThrower>;
+ MapType map;
+ map.add(1, 1);
+ map.add(2, 2);
+ map.lookup(1).throw_during_move = true;
+ EXPECT_ANY_THROW({ MapType map_moved(std::move(map)); });
+ map.add(5, 5);
+}
+
+TEST(map, AddNewExceptions)
+{
+ Map<ExceptionThrower, ExceptionThrower> map;
+ ExceptionThrower key1 = 1;
+ key1.throw_during_copy = true;
+ ExceptionThrower value1;
+ EXPECT_ANY_THROW({ map.add_new(key1, value1); });
+ EXPECT_EQ(map.size(), 0);
+ ExceptionThrower key2 = 2;
+ ExceptionThrower value2;
+ value2.throw_during_copy = true;
+ EXPECT_ANY_THROW({ map.add_new(key2, value2); });
+}
+
+TEST(map, ReserveExceptions)
+{
+ Map<ExceptionThrower, ExceptionThrower> map;
+ map.add(3, 3);
+ map.add(5, 5);
+ map.add(2, 2);
+ map.lookup(2).throw_during_move = true;
+ EXPECT_ANY_THROW({ map.reserve(100); });
+ map.add(1, 1);
+ map.add(5, 5);
+}
+
+TEST(map, PopExceptions)
+{
+ Map<ExceptionThrower, ExceptionThrower> map;
+ map.add(3, 3);
+ map.lookup(3).throw_during_move = true;
+ EXPECT_ANY_THROW({ map.pop(3); });
+ EXPECT_EQ(map.size(), 1);
+ map.add(1, 1);
+ EXPECT_EQ(map.size(), 2);
+}
+
+TEST(map, AddOrModifyExceptions)
+{
+ Map<ExceptionThrower, ExceptionThrower> map;
+ auto create_fn = [](ExceptionThrower *UNUSED(v)) { throw std::runtime_error(""); };
+ auto modify_fn = [](ExceptionThrower *UNUSED(v)) {};
+ EXPECT_ANY_THROW({ map.add_or_modify(3, create_fn, modify_fn); });
+}
+
/**
* Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
*/
diff --git a/source/blender/blenlib/tests/BLI_memory_utils_test.cc b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
index f3cb02b63d7..fcef2f8688a 100644
--- a/source/blender/blenlib/tests/BLI_memory_utils_test.cc
+++ b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
@@ -7,6 +7,7 @@
namespace blender::tests {
+namespace {
struct MyValue {
static inline int alive = 0;
@@ -33,6 +34,7 @@ struct MyValue {
alive--;
}
};
+} // namespace
TEST(memory_utils, DefaultConstructN_ActuallyCallsConstructor)
{
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..b212ddb8e63
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_mesh_boolean_test.cc
@@ -0,0 +1,910 @@
+/* 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"
+
+#ifdef WITH_GMP
+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
+#endif
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..237905a1bf6
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc
@@ -0,0 +1,1074 @@
+/* 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
+
+#ifdef WITH_GMP
+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(512, 0.5, false);
+}
+
+TEST(mesh_intersect_perf, SphereGrid)
+{
+ spheregrid_test(512, 4, 0.1, false);
+}
+
+# endif
+
+} // namespace blender::meshintersect::tests
+#endif
diff --git a/source/blender/blenlib/tests/BLI_set_test.cc b/source/blender/blenlib/tests/BLI_set_test.cc
index df3f7ab544c..3ea9a59b3db 100644
--- a/source/blender/blenlib/tests/BLI_set_test.cc
+++ b/source/blender/blenlib/tests/BLI_set_test.cc
@@ -3,6 +3,7 @@
#include <set>
#include <unordered_set>
+#include "BLI_exception_safety_test_utils.hh"
#include "BLI_ghash.h"
#include "BLI_rand.h"
#include "BLI_set.hh"
@@ -462,6 +463,55 @@ TEST(set, StringViewKeys)
EXPECT_TRUE(set.contains("hello"));
}
+TEST(set, SpanConstructorExceptions)
+{
+ std::array<ExceptionThrower, 5> array = {1, 2, 3, 4, 5};
+ array[3].throw_during_copy = true;
+ Span<ExceptionThrower> span = array;
+
+ EXPECT_ANY_THROW({ Set<ExceptionThrower> set(span); });
+}
+
+TEST(set, CopyConstructorExceptions)
+{
+ Set<ExceptionThrower> set = {1, 2, 3, 4, 5};
+ set.lookup_key(3).throw_during_copy = true;
+ EXPECT_ANY_THROW({ Set<ExceptionThrower> set_copy(set); });
+}
+
+TEST(set, MoveConstructorExceptions)
+{
+ using SetType = Set<ExceptionThrower, 4>;
+ SetType set = {1, 2, 3};
+ set.lookup_key(2).throw_during_move = true;
+ EXPECT_ANY_THROW({ SetType set_moved(std::move(set)); });
+ EXPECT_EQ(set.size(), 0);
+ set.add_multiple({3, 6, 7});
+ EXPECT_EQ(set.size(), 3);
+}
+
+TEST(set, AddNewExceptions)
+{
+ Set<ExceptionThrower> set;
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ set.add_new(value); });
+ EXPECT_EQ(set.size(), 0);
+ EXPECT_ANY_THROW({ set.add_new(value); });
+ EXPECT_EQ(set.size(), 0);
+}
+
+TEST(set, AddExceptions)
+{
+ Set<ExceptionThrower> set;
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ set.add(value); });
+ EXPECT_EQ(set.size(), 0);
+ EXPECT_ANY_THROW({ set.add(value); });
+ EXPECT_EQ(set.size(), 0);
+}
+
/**
* Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
*/
diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc
index 6ad2a5633ad..9a8d9df7873 100644
--- a/source/blender/blenlib/tests/BLI_span_test.cc
+++ b/source/blender/blenlib/tests/BLI_span_test.cc
@@ -237,7 +237,8 @@ TEST(span, ContainsPtr)
EXPECT_TRUE(a_span.contains_ptr(&a[0] + 1));
EXPECT_TRUE(a_span.contains_ptr(&a[0] + 2));
EXPECT_FALSE(a_span.contains_ptr(&a[0] + 3));
- EXPECT_FALSE(a_span.contains_ptr(&a[0] - 1));
+ int *ptr_before = reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(a.data()) - 1);
+ EXPECT_FALSE(a_span.contains_ptr(ptr_before));
EXPECT_FALSE(a_span.contains_ptr(&other));
}
@@ -308,4 +309,32 @@ TEST(span, CopyFrom)
EXPECT_EQ(dst[3], 8);
}
+TEST(span, ReverseIterator)
+{
+ std::array<int, 4> src = {4, 5, 6, 7};
+ Span<int> span = src;
+ Vector<int> reversed_vec;
+
+ for (auto it = span.rbegin(); it != span.rend(); ++it) {
+ reversed_vec.append(*it);
+ }
+ EXPECT_EQ(reversed_vec.size(), 4);
+ EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4);
+}
+
+TEST(span, MutableReverseIterator)
+{
+ std::array<int, 4> src = {4, 5, 6, 7};
+ MutableSpan<int> span = src;
+ Vector<int> reversed_vec;
+
+ for (auto it = span.rbegin(); it != span.rend(); ++it) {
+ reversed_vec.append(*it);
+ *it += 10;
+ }
+ EXPECT_EQ(reversed_vec.size(), 4);
+ EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4);
+ EXPECT_EQ_ARRAY(src.data(), Span({14, 15, 16, 17}).data(), 4);
+}
+
} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_stack_cxx_test.cc b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc
index 3572e751b88..c03893c5596 100644
--- a/source/blender/blenlib/tests/BLI_stack_cxx_test.cc
+++ b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc
@@ -1,5 +1,6 @@
/* Apache License, Version 2.0 */
+#include "BLI_exception_safety_test_utils.hh"
#include "BLI_stack.hh"
#include "BLI_strict_flags.h"
#include "BLI_vector.hh"
@@ -185,4 +186,59 @@ TEST(stack, OveralignedValues)
}
}
+TEST(stack, SpanConstructorExceptions)
+{
+ std::array<ExceptionThrower, 5> values;
+ values[3].throw_during_copy = true;
+ EXPECT_ANY_THROW({ Stack<ExceptionThrower> stack(values); });
+}
+
+TEST(stack, MoveConstructorExceptions)
+{
+ Stack<ExceptionThrower, 4> stack;
+ stack.push({});
+ stack.push({});
+ stack.peek().throw_during_move = true;
+ EXPECT_ANY_THROW({ Stack<ExceptionThrower> moved_stack{std::move(stack)}; });
+}
+
+TEST(stack, PushExceptions)
+{
+ Stack<ExceptionThrower, 2> stack;
+ stack.push({});
+ stack.push({});
+ ExceptionThrower *ptr1 = &stack.peek();
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ stack.push(value); });
+ EXPECT_EQ(stack.size(), 2);
+ ExceptionThrower *ptr2 = &stack.peek();
+ EXPECT_EQ(ptr1, ptr2);
+ EXPECT_TRUE(stack.is_invariant_maintained());
+}
+
+TEST(stack, PopExceptions)
+{
+ Stack<ExceptionThrower> stack;
+ stack.push({});
+ stack.peek().throw_during_move = true;
+ stack.push({});
+ stack.pop(); /* NOLINT: bugprone-throw-keyword-missing */
+ EXPECT_ANY_THROW({ stack.pop(); }); /* NOLINT: bugprone-throw-keyword-missing */
+ EXPECT_EQ(stack.size(), 1);
+ EXPECT_TRUE(stack.is_invariant_maintained());
+}
+
+TEST(stack, PushMultipleExceptions)
+{
+ Stack<ExceptionThrower> stack;
+ stack.push({});
+ std::array<ExceptionThrower, 100> values;
+ values[6].throw_during_copy = true;
+ EXPECT_ANY_THROW({ stack.push_multiple(values); });
+ EXPECT_TRUE(stack.is_invariant_maintained());
+ EXPECT_ANY_THROW({ stack.push_multiple(values); });
+ EXPECT_TRUE(stack.is_invariant_maintained());
+}
+
} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_vector_test.cc b/source/blender/blenlib/tests/BLI_vector_test.cc
index f72dfc5deb8..e6b2e7c6365 100644
--- a/source/blender/blenlib/tests/BLI_vector_test.cc
+++ b/source/blender/blenlib/tests/BLI_vector_test.cc
@@ -1,5 +1,6 @@
/* Apache License, Version 2.0 */
+#include "BLI_exception_safety_test_utils.hh"
#include "BLI_strict_flags.h"
#include "BLI_vector.hh"
#include "testing/testing.h"
@@ -98,14 +99,14 @@ TEST(vector, ListBaseConstructor)
delete value3;
}
-TEST(vector, ContainerConstructor)
+TEST(vector, IteratorConstructor)
{
std::forward_list<int> list;
list.push_front(3);
list.push_front(1);
list.push_front(5);
- Vector<int> vec = Vector<int>::FromContainer(list);
+ Vector<int> vec = Vector<int>(list.begin(), list.end());
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec[0], 5);
EXPECT_EQ(vec[1], 1);
@@ -279,6 +280,15 @@ TEST(vector, ExtendNonDuplicates)
EXPECT_EQ(vec.size(), 5);
}
+TEST(vector, ExtendIterator)
+{
+ Vector<int> vec = {3, 4, 5};
+ std::forward_list<int> list = {8, 9};
+ vec.extend(list.begin(), list.end());
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span({3, 4, 5, 8, 9}).data(), 5);
+}
+
TEST(vector, Iterator)
{
Vector<int> vec({1, 4, 9, 16});
@@ -636,4 +646,183 @@ TEST(vector, Fill)
EXPECT_EQ(vec[4], 3);
}
+TEST(vector, InsertAtBeginning)
+{
+ Vector<int> vec = {1, 2, 3};
+ vec.insert(0, {6, 7});
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span({6, 7, 1, 2, 3}).data(), 5);
+}
+
+TEST(vector, InsertAtEnd)
+{
+ Vector<int> vec = {1, 2, 3};
+ vec.insert(3, {6, 7});
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span({1, 2, 3, 6, 7}).data(), 5);
+}
+
+TEST(vector, InsertInMiddle)
+{
+ Vector<int> vec = {1, 2, 3};
+ vec.insert(1, {6, 7});
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span({1, 6, 7, 2, 3}).data(), 5);
+}
+
+TEST(vector, InsertAtIterator)
+{
+ Vector<std::string> vec = {"1", "2", "3"};
+ Vector<std::string> other_vec = {"hello", "world"};
+ vec.insert(vec.begin() + 1, other_vec.begin(), other_vec.end());
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span<std::string>({"1", "hello", "world", "2", "3"}).data(), 5);
+}
+
+TEST(vector, InsertMoveOnlyType)
+{
+ Vector<std::unique_ptr<int>> vec;
+ vec.append(std::make_unique<int>(1));
+ vec.append(std::make_unique<int>(2));
+ vec.insert(1, std::make_unique<int>(30));
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(*vec[0], 1);
+ EXPECT_EQ(*vec[1], 30);
+ EXPECT_EQ(*vec[2], 2);
+}
+
+TEST(vector, Prepend)
+{
+ Vector<int> vec = {1, 2, 3};
+ vec.prepend({7, 8});
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ_ARRAY(vec.data(), Span({7, 8, 1, 2, 3}).data(), 5);
+}
+
+TEST(vector, ReverseIterator)
+{
+ Vector<int> vec = {4, 5, 6, 7};
+ Vector<int> reversed_vec;
+ for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
+ reversed_vec.append(*it);
+ }
+ EXPECT_EQ(reversed_vec.size(), 4);
+ EXPECT_EQ_ARRAY(reversed_vec.data(), Span({7, 6, 5, 4}).data(), 4);
+}
+
+TEST(vector, SizeValueConstructorExceptions)
+{
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ Vector<ExceptionThrower> vec(5, value); });
+}
+
+TEST(vector, SpanConstructorExceptions)
+{
+ std::array<ExceptionThrower, 5> values;
+ values[3].throw_during_copy = true;
+ EXPECT_ANY_THROW({ Vector<ExceptionThrower> vec(values); });
+}
+
+TEST(vector, MoveConstructorExceptions)
+{
+ Vector<ExceptionThrower, 4> vec(3);
+ vec[2].throw_during_move = true;
+ EXPECT_ANY_THROW({ Vector<ExceptionThrower> moved_vector{std::move(vec)}; });
+}
+
+TEST(vector, AppendExceptions)
+{
+ Vector<ExceptionThrower, 4> vec(2);
+ ExceptionThrower *ptr1 = &vec.last();
+ ExceptionThrower value;
+ value.throw_during_copy = true;
+ EXPECT_ANY_THROW({ vec.append(value); });
+ EXPECT_EQ(vec.size(), 2);
+ ExceptionThrower *ptr2 = &vec.last();
+ EXPECT_EQ(ptr1, ptr2);
+}
+
+TEST(vector, ExtendExceptions)
+{
+ Vector<ExceptionThrower> vec(5);
+ std::array<ExceptionThrower, 10> values;
+ values[6].throw_during_copy = true;
+ EXPECT_ANY_THROW({ vec.extend(values); });
+ EXPECT_EQ(vec.size(), 5);
+}
+
+TEST(vector, Insert1Exceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ std::array<ExceptionThrower, 5> values;
+ values[3].throw_during_copy = true;
+ EXPECT_ANY_THROW({ vec.insert(7, values); });
+}
+
+TEST(vector, Insert2Exceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ vec.reserve(100);
+ vec[8].throw_during_move = true;
+ std::array<ExceptionThrower, 5> values;
+ EXPECT_ANY_THROW({ vec.insert(3, values); });
+}
+
+TEST(vector, PopLastExceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ vec.last().throw_during_move = true;
+ EXPECT_ANY_THROW({ vec.pop_last(); }); /* NOLINT: bugprone-throw-keyword-missing */
+ EXPECT_EQ(vec.size(), 10);
+}
+
+TEST(vector, RemoveAndReorderExceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ vec.last().throw_during_move = true;
+ EXPECT_ANY_THROW({ vec.remove_and_reorder(3); });
+ EXPECT_EQ(vec.size(), 10);
+}
+
+TEST(vector, RemoveExceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ vec[8].throw_during_move = true;
+ EXPECT_ANY_THROW({ vec.remove(2); });
+ EXPECT_EQ(vec.size(), 10);
+}
+
+TEST(vector, RemoveChunk)
+{
+ Vector<int> vec = {2, 3, 4, 5, 6, 7, 8};
+ EXPECT_EQ(vec.size(), 7);
+ vec.remove(2, 4);
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(vec[0], 2);
+ EXPECT_EQ(vec[1], 3);
+ EXPECT_EQ(vec[2], 8);
+ vec.remove(0, 1);
+ EXPECT_EQ(vec.size(), 2);
+ EXPECT_EQ(vec[0], 3);
+ EXPECT_EQ(vec[1], 8);
+ vec.remove(1, 1);
+ EXPECT_EQ(vec.size(), 1);
+ EXPECT_EQ(vec[0], 3);
+ vec.remove(0, 1);
+ EXPECT_EQ(vec.size(), 0);
+ vec.remove(0, 0);
+ EXPECT_EQ(vec.size(), 0);
+}
+
+TEST(vector, RemoveChunkExceptions)
+{
+ Vector<ExceptionThrower> vec(10);
+ vec.remove(1, 3);
+ EXPECT_EQ(vec.size(), 7);
+ vec[5].throw_during_move = true;
+ EXPECT_ANY_THROW({ vec.remove(2, 3); });
+ EXPECT_EQ(vec.size(), 7);
+}
+
} // namespace blender::tests