From f3da6201f8f57177150326997655fddc244c7932 Mon Sep 17 00:00:00 2001 From: Jakub Marcowski Date: Thu, 15 May 2025 23:06:54 +0200 Subject: [PATCH] manifold: Update to 3.1.1 --- COPYRIGHT.txt | 2 +- modules/csg/SCsub | 2 + thirdparty/README.md | 4 +- thirdparty/manifold/include/manifold/common.h | 59 +- thirdparty/manifold/include/manifold/linalg.h | 741 +++++++++--------- .../manifold/include/manifold/manifold.h | 148 +++- .../include/manifold/optional_assert.h | 4 + .../manifold/include/manifold/polygon.h | 9 +- .../manifold/include/manifold/vec_view.h | 36 +- thirdparty/manifold/src/boolean3.cpp | 443 ++++------- thirdparty/manifold/src/boolean3.h | 7 +- thirdparty/manifold/src/boolean_result.cpp | 239 +++--- thirdparty/manifold/src/collider.h | 121 ++- thirdparty/manifold/src/constructors.cpp | 68 +- .../src/cross_section/cross_section.cpp | 23 +- thirdparty/manifold/src/csg_tree.cpp | 217 +++-- thirdparty/manifold/src/csg_tree.h | 20 +- thirdparty/manifold/src/edge_op.cpp | 639 ++++++++++----- thirdparty/manifold/src/face_op.cpp | 217 ++--- thirdparty/manifold/src/hashtable.h | 40 +- thirdparty/manifold/src/impl.cpp | 706 +++++++++-------- thirdparty/manifold/src/impl.h | 230 +++--- thirdparty/manifold/src/iters.h | 27 +- thirdparty/manifold/src/manifold.cpp | 169 ++-- thirdparty/manifold/src/mesh_fixes.h | 2 +- thirdparty/manifold/src/parallel.h | 214 ++--- thirdparty/manifold/src/polygon.cpp | 250 +++--- thirdparty/manifold/src/properties.cpp | 235 +++--- thirdparty/manifold/src/quickhull.cpp | 9 +- thirdparty/manifold/src/quickhull.h | 4 +- thirdparty/manifold/src/sdf.cpp | 38 +- thirdparty/manifold/src/shared.h | 34 +- thirdparty/manifold/src/smoothing.cpp | 321 ++++---- thirdparty/manifold/src/sort.cpp | 191 +++-- thirdparty/manifold/src/subdivision.cpp | 146 ++-- thirdparty/manifold/src/svd.h | 110 ++- thirdparty/manifold/src/tree2d.cpp | 59 ++ thirdparty/manifold/src/tree2d.h | 85 ++ thirdparty/manifold/src/tri_dist.h | 1 + thirdparty/manifold/src/utils.h | 21 +- thirdparty/manifold/src/vec.h | 58 +- 41 files changed, 3224 insertions(+), 2725 deletions(-) create mode 100644 thirdparty/manifold/src/tree2d.cpp create mode 100644 thirdparty/manifold/src/tree2d.h diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index cdc49bb9ed..5a7e274f2b 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -391,7 +391,7 @@ License: BSD-3-clause Files: thirdparty/manifold/* Comment: Manifold -Copyright: 2020-2024, The Manifold Authors +Copyright: 2020-2025, The Manifold Authors License: Apache-2.0 Files: thirdparty/mbedtls/* diff --git a/modules/csg/SCsub b/modules/csg/SCsub index 32bc240a82..a6515b8c92 100644 --- a/modules/csg/SCsub +++ b/modules/csg/SCsub @@ -25,9 +25,11 @@ thirdparty_sources = [ "src/polygon.cpp", "src/properties.cpp", "src/quickhull.cpp", + "src/sdf.cpp", "src/smoothing.cpp", "src/sort.cpp", "src/subdivision.cpp", + "src/tree2d.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/thirdparty/README.md b/thirdparty/README.md index cd2232166a..ab4e306498 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -624,12 +624,12 @@ See `linuxbsd_headers/README.md`. ## manifold - Upstream: https://github.com/elalish/manifold -- Version: 3.0.1 (98b8142519d35c13e0e25cfa9fd6e3a271403be6, 2024) +- Version: 3.1.1 (2f4741e0b1de44d6d461b869e481351335340b44, 2025) - License: Apache 2.0 File extracted from upstream source: -- `src/` and `include/`, except from `CMakeLists.txt`, `cross_section.cpp` and `meshIO.{cpp,h}` +- `src/` and `include/`, except from `CMakeLists.txt`, `cross_section.h` and `meshIO.{cpp,h}` - `AUTHORS`, `LICENSE` diff --git a/thirdparty/manifold/include/manifold/common.h b/thirdparty/manifold/include/manifold/common.h index f1200f4654..6975cc3de5 100644 --- a/thirdparty/manifold/include/manifold/common.h +++ b/thirdparty/manifold/include/manifold/common.h @@ -20,7 +20,8 @@ #include #endif -#include "manifold/linalg.h" +#include "linalg.h" +#include "optional_assert.h" namespace manifold { /** @addtogroup Math @@ -548,7 +549,7 @@ class Quality { int nSegL = 2.0 * radius * kPi / circularEdgeLength_; int nSeg = fmin(nSegA, nSegL) + 3; nSeg -= nSeg % 4; - return std::max(nSeg, 3); + return std::max(nSeg, 4); } /** @@ -577,21 +578,62 @@ struct ExecutionParams { /// Perform extra sanity checks and assertions on the intermediate data /// structures. bool intermediateChecks = false; - /// Verbose output primarily of the Boolean, including timing info and vector - /// sizes. - bool verbose = false; + /// Perform 3D mesh self-intersection test on intermediate boolean results to + /// test for ϵ-validity. For debug purposes only. + bool selfIntersectionChecks = false; /// If processOverlaps is false, a geometric check will be performed to assert /// all triangles are CCW. bool processOverlaps = true; /// Suppresses printed errors regarding CW triangles. Has no effect if /// processOverlaps is true. bool suppressErrors = false; - /// Perform optional but recommended triangle cleanups in SimplifyTopology() + /// Deprecated! This value no longer has any effect, as cleanup now only + /// occurs on intersected triangles. bool cleanupTriangles = true; + /// Verbose level: + /// - 0 for no verbose output + /// - 1 for verbose output for the Boolean, including timing info and vector + /// sizes. + /// - 2 for verbose output with triangulator action as well. + int verbose = 0; }; /** @} */ #ifdef MANIFOLD_DEBUG +template +std::ostream& operator<<(std::ostream& out, const la::vec& v) { + return out << '{' << v[0] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::vec& v) { + return out << '{' << v[0] << ',' << v[1] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::vec& v) { + return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::vec& v) { + return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}'; +} + +template +std::ostream& operator<<(std::ostream& out, const la::mat& m) { + return out << '{' << m[0] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::mat& m) { + return out << '{' << m[0] << ',' << m[1] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::mat& m) { + return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const la::mat& m) { + return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}'; +} + inline std::ostream& operator<<(std::ostream& stream, const Box& box) { return stream << "min: " << box.min << ", " << "max: " << box.max; @@ -602,6 +644,11 @@ inline std::ostream& operator<<(std::ostream& stream, const Rect& box) { << "max: " << box.max; } +inline std::ostream& operator<<(std::ostream& stream, const Smoothness& s) { + return stream << "halfedge: " << s.halfedge << ", " + << "smoothness: " << s.smoothness; +} + /** * Print the contents of this vector to standard output. Only exists if compiled * with MANIFOLD_DEBUG flag. diff --git a/thirdparty/manifold/include/manifold/linalg.h b/thirdparty/manifold/include/manifold/linalg.h index dfcd305ba8..12f484fa1c 100644 --- a/thirdparty/manifold/include/manifold/linalg.h +++ b/thirdparty/manifold/include/manifold/linalg.h @@ -37,17 +37,10 @@ #include // For std::array #include // For various unary math functions, such as std::sqrt -#include // For implementing namespace linalg::aliases #include // To resolve std::abs ambiguity on clang #include // For std::hash declaration -#include // For forward definitions of std::ostream #include // For std::enable_if, std::is_same, std::declval -#ifdef MANIFOLD_DEBUG -#include -#include -#endif - // In Visual Studio 2015, `constexpr` applied to a member function implies // `const`, which causes ambiguous overload resolution #if defined(_MSC_VER) && (_MSC_VER <= 1900) @@ -96,27 +89,27 @@ struct ord { T a, b; }; template -constexpr bool operator==(const ord &o, std::nullptr_t) { +constexpr bool operator==(const ord& o, std::nullptr_t) { return o.a == o.b; } template -constexpr bool operator!=(const ord &o, std::nullptr_t) { +constexpr bool operator!=(const ord& o, std::nullptr_t) { return !(o.a == o.b); } template -constexpr bool operator<(const ord &o, std::nullptr_t) { +constexpr bool operator<(const ord& o, std::nullptr_t) { return o.a < o.b; } template -constexpr bool operator>(const ord &o, std::nullptr_t) { +constexpr bool operator>(const ord& o, std::nullptr_t) { return o.b < o.a; } template -constexpr bool operator<=(const ord &o, std::nullptr_t) { +constexpr bool operator<=(const ord& o, std::nullptr_t) { return !(o.b < o.a); } template -constexpr bool operator>=(const ord &o, std::nullptr_t) { +constexpr bool operator>=(const ord& o, std::nullptr_t) { return !(o.a < o.b); } @@ -126,21 +119,21 @@ struct any_compare {}; template struct any_compare, vec> { using type = ord; - constexpr ord operator()(const vec &a, const vec &b) const { + constexpr ord operator()(const vec& a, const vec& b) const { return ord{a.x, b.x}; } }; template struct any_compare, vec> { using type = ord; - constexpr ord operator()(const vec &a, const vec &b) const { + constexpr ord operator()(const vec& a, const vec& b) const { return !(a.x == b.x) ? ord{a.x, b.x} : ord{a.y, b.y}; } }; template struct any_compare, vec> { using type = ord; - constexpr ord operator()(const vec &a, const vec &b) const { + constexpr ord operator()(const vec& a, const vec& b) const { return !(a.x == b.x) ? ord{a.x, b.x} : !(a.y == b.y) ? ord{a.y, b.y} : ord{a.z, b.z}; @@ -149,7 +142,7 @@ struct any_compare, vec> { template struct any_compare, vec> { using type = ord; - constexpr ord operator()(const vec &a, const vec &b) const { + constexpr ord operator()(const vec& a, const vec& b) const { return !(a.x == b.x) ? ord{a.x, b.x} : !(a.y == b.y) ? ord{a.y, b.y} : !(a.z == b.z) ? ord{a.z, b.z} @@ -159,24 +152,24 @@ struct any_compare, vec> { template struct any_compare, mat> { using type = ord; - constexpr ord operator()(const mat &a, - const mat &b) const { + constexpr ord operator()(const mat& a, + const mat& b) const { return compare(a.x, b.x); } }; template struct any_compare, mat> { using type = ord; - constexpr ord operator()(const mat &a, - const mat &b) const { + constexpr ord operator()(const mat& a, + const mat& b) const { return a.x != b.x ? compare(a.x, b.x) : compare(a.y, b.y); } }; template struct any_compare, mat> { using type = ord; - constexpr ord operator()(const mat &a, - const mat &b) const { + constexpr ord operator()(const mat& a, + const mat& b) const { return a.x != b.x ? compare(a.x, b.x) : a.y != b.y ? compare(a.y, b.y) : compare(a.z, b.z); @@ -185,8 +178,8 @@ struct any_compare, mat> { template struct any_compare, mat> { using type = ord; - constexpr ord operator()(const mat &a, - const mat &b) const { + constexpr ord operator()(const mat& a, + const mat& b) const { return a.x != b.x ? compare(a.x, b.x) : a.y != b.y ? compare(a.y, b.y) : a.z != b.z ? compare(a.z, b.z) @@ -201,28 +194,28 @@ struct getter; template <> struct getter<0> { template - constexpr auto operator()(A &a) const -> decltype(a.x) { + constexpr auto operator()(A& a) const -> decltype(a.x) { return a.x; } }; template <> struct getter<1> { template - constexpr auto operator()(A &a) const -> decltype(a.y) { + constexpr auto operator()(A& a) const -> decltype(a.y) { return a.y; } }; template <> struct getter<2> { template - constexpr auto operator()(A &a) const -> decltype(a.z) { + constexpr auto operator()(A& a) const -> decltype(a.z) { return a.z; } }; template <> struct getter<3> { template - constexpr auto operator()(A &a) const -> decltype(a.w) { + constexpr auto operator()(A& a) const -> decltype(a.w) { return a.w; } }; @@ -255,12 +248,12 @@ struct make_seq_impl { template using make_seq = typename make_seq_impl::type; template -vec constexpr swizzle(const vec &v, seq i) { +vec constexpr swizzle(const vec& v, seq) { return {getter{}(v)...}; } template -mat constexpr swizzle(const mat &m, - seq i, seq j) { +mat constexpr swizzle(const mat& m, + seq i, seq) { return {swizzle(getter{}(m), i)...}; } @@ -291,7 +284,7 @@ struct apply, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a) { + static constexpr type impl(seq, F f, const vec& a) { return {f(getter{}(a))...}; } }; @@ -300,8 +293,8 @@ struct apply, vec, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, - const vec &b) { + static constexpr type impl(seq, F f, const vec& a, + const vec& b) { return {f(getter{}(a), getter{}(b))...}; } }; @@ -310,7 +303,7 @@ struct apply, vec, B> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, B b) { + static constexpr type impl(seq, F f, const vec& a, B b) { return {f(getter{}(a), b)...}; } }; @@ -319,7 +312,7 @@ struct apply, A, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, A a, const vec &b) { + static constexpr type impl(seq, F f, A a, const vec& b) { return {f(a, getter{}(b))...}; } }; @@ -328,8 +321,8 @@ struct apply, vec, vec, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, - const vec &b, const vec &c) { + static constexpr type impl(seq, F f, const vec& a, + const vec& b, const vec& c) { return {f(getter{}(a), getter{}(b), getter{}(c))...}; } }; @@ -338,8 +331,8 @@ struct apply, vec, vec, C> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, - const vec &b, C c) { + static constexpr type impl(seq, F f, const vec& a, + const vec& b, C c) { return {f(getter{}(a), getter{}(b), c)...}; } }; @@ -348,8 +341,8 @@ struct apply, vec, B, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, B b, - const vec &c) { + static constexpr type impl(seq, F f, const vec& a, B b, + const vec& c) { return {f(getter{}(a), b, getter{}(c))...}; } }; @@ -358,7 +351,7 @@ struct apply, vec, B, C> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, const vec &a, B b, C c) { + static constexpr type impl(seq, F f, const vec& a, B b, C c) { return {f(getter{}(a), b, c)...}; } }; @@ -367,8 +360,8 @@ struct apply, A, vec, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, A a, const vec &b, - const vec &c) { + static constexpr type impl(seq, F f, A a, const vec& b, + const vec& c) { return {f(a, getter{}(b), getter{}(c))...}; } }; @@ -377,7 +370,7 @@ struct apply, A, vec, C> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, A a, const vec &b, C c) { + static constexpr type impl(seq, F f, A a, const vec& b, C c) { return {f(a, getter{}(b), c)...}; } }; @@ -386,7 +379,7 @@ struct apply, A, B, vec> { using type = vec, M>; enum { size = M, mm = 0 }; template - static constexpr type impl(seq, F f, A a, B b, const vec &c) { + static constexpr type impl(seq, F f, A a, B b, const vec& c) { return {f(a, b, getter{}(c))...}; } }; @@ -395,7 +388,7 @@ struct apply, mat> { using type = mat, M, N>; enum { size = N, mm = 0 }; template - static constexpr type impl(seq, F f, const mat &a) { + static constexpr type impl(seq, F f, const mat& a) { return {apply>::impl(make_seq<0, M>{}, f, getter{}(a))...}; } @@ -405,8 +398,8 @@ struct apply, mat, mat> { using type = mat, M, N>; enum { size = N, mm = 1 }; template - static constexpr type impl(seq, F f, const mat &a, - const mat &b) { + static constexpr type impl(seq, F f, const mat& a, + const mat& b) { return {apply, vec>::impl( make_seq<0, M>{}, f, getter{}(a), getter{}(b))...}; } @@ -416,7 +409,7 @@ struct apply, mat, B> { using type = mat, M, N>; enum { size = N, mm = 0 }; template - static constexpr type impl(seq, F f, const mat &a, B b) { + static constexpr type impl(seq, F f, const mat& a, B b) { return {apply, B>::impl(make_seq<0, M>{}, f, getter{}(a), b)...}; } @@ -426,7 +419,7 @@ struct apply, A, mat> { using type = mat, M, N>; enum { size = N, mm = 0 }; template - static constexpr type impl(seq, F f, A a, const mat &b) { + static constexpr type impl(seq, F f, A a, const mat& b) { return {apply>::impl(make_seq<0, M>{}, f, a, getter{}(b))...}; } @@ -471,8 +464,8 @@ struct select { }; struct lerp { template - constexpr auto operator()(A a, B b, - C c) const -> decltype(a * (1 - c) + b * c) { + constexpr auto operator()(A a, B b, C c) const + -> decltype(a * (1 - c) + b * c) { return a * (1 - c) + b * c; } }; @@ -855,16 +848,16 @@ template struct vec { T x; constexpr vec() : x() {} - constexpr vec(const T &x_) : x(x_) {} + constexpr vec(const T& x_) : x(x_) {} // NOTE: vec does NOT have a constructor from pointer, this can conflict // with initializing its single element from zero template - constexpr explicit vec(const vec &v) : vec(static_cast(v.x)) {} - constexpr const T &operator[](int i) const { return x; } - LINALG_CONSTEXPR14 T &operator[](int i) { return x; } + constexpr explicit vec(const vec& v) : vec(static_cast(v.x)) {} + constexpr const T& operator[](int) const { return x; } + LINALG_CONSTEXPR14 T& operator[](int) { return x; } template > - constexpr vec(const U &u) : vec(converter{}(u)) {} + constexpr vec(const U& u) : vec(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -874,21 +867,21 @@ template struct vec { T x, y; constexpr vec() : x(), y() {} - constexpr vec(const T &x_, const T &y_) : x(x_), y(y_) {} - constexpr explicit vec(const T &s) : vec(s, s) {} - constexpr explicit vec(const T *p) : vec(p[0], p[1]) {} + constexpr vec(const T& x_, const T& y_) : x(x_), y(y_) {} + constexpr explicit vec(const T& s) : vec(s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1]) {} template - constexpr explicit vec(const vec &v) + constexpr explicit vec(const vec& v) : vec(static_cast(v.x), static_cast(v.y)) { static_assert( N >= 2, "You must give extra arguments if your input vector is shorter."); } - constexpr const T &operator[](int i) const { return i == 0 ? x : y; } - LINALG_CONSTEXPR14 T &operator[](int i) { return i == 0 ? x : y; } + constexpr const T& operator[](int i) const { return i == 0 ? x : y; } + LINALG_CONSTEXPR14 T& operator[](int i) { return i == 0 ? x : y; } template > - constexpr vec(const U &u) : vec(converter{}(u)) {} + constexpr vec(const U& u) : vec(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -898,30 +891,30 @@ template struct vec { T x, y, z; constexpr vec() : x(), y(), z() {} - constexpr vec(const T &x_, const T &y_, const T &z_) : x(x_), y(y_), z(z_) {} - constexpr vec(const vec &xy, const T &z_) : vec(xy.x, xy.y, z_) {} - constexpr explicit vec(const T &s) : vec(s, s, s) {} - constexpr explicit vec(const T *p) : vec(p[0], p[1], p[2]) {} + constexpr vec(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} + constexpr vec(const vec& xy, const T& z_) : vec(xy.x, xy.y, z_) {} + constexpr explicit vec(const T& s) : vec(s, s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1], p[2]) {} template - constexpr explicit vec(const vec &v) + constexpr explicit vec(const vec& v) : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z)) { static_assert( N >= 3, "You must give extra arguments if your input vector is shorter."); } - constexpr const T &operator[](int i) const { + constexpr const T& operator[](int i) const { return i == 0 ? x : i == 1 ? y : z; } - LINALG_CONSTEXPR14 T &operator[](int i) { + LINALG_CONSTEXPR14 T& operator[](int i) { return i == 0 ? x : i == 1 ? y : z; } - constexpr const vec &xy() const { - return *reinterpret_cast *>(this); + constexpr const vec& xy() const { + return *reinterpret_cast*>(this); } - vec &xy() { return *reinterpret_cast *>(this); } + vec& xy() { return *reinterpret_cast*>(this); } template > - constexpr vec(const U &u) : vec(converter{}(u)) {} + constexpr vec(const U& u) : vec(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -931,39 +924,39 @@ template struct vec { T x, y, z, w; constexpr vec() : x(), y(), z(), w() {} - constexpr vec(const T &x_, const T &y_, const T &z_, const T &w_) + constexpr vec(const T& x_, const T& y_, const T& z_, const T& w_) : x(x_), y(y_), z(z_), w(w_) {} - constexpr vec(const vec &xy, const T &z_, const T &w_) + constexpr vec(const vec& xy, const T& z_, const T& w_) : vec(xy.x, xy.y, z_, w_) {} - constexpr vec(const vec &xyz, const T &w_) + constexpr vec(const vec& xyz, const T& w_) : vec(xyz.x, xyz.y, xyz.z, w_) {} - constexpr explicit vec(const T &s) : vec(s, s, s, s) {} - constexpr explicit vec(const T *p) : vec(p[0], p[1], p[2], p[3]) {} + constexpr explicit vec(const T& s) : vec(s, s, s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1], p[2], p[3]) {} template - constexpr explicit vec(const vec &v) + constexpr explicit vec(const vec& v) : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z), static_cast(v.w)) { static_assert( N >= 4, "You must give extra arguments if your input vector is shorter."); } - constexpr const T &operator[](int i) const { + constexpr const T& operator[](int i) const { return i == 0 ? x : i == 1 ? y : i == 2 ? z : w; } - LINALG_CONSTEXPR14 T &operator[](int i) { + LINALG_CONSTEXPR14 T& operator[](int i) { return i == 0 ? x : i == 1 ? y : i == 2 ? z : w; } - constexpr const vec &xy() const { - return *reinterpret_cast *>(this); + constexpr const vec& xy() const { + return *reinterpret_cast*>(this); } - constexpr const vec &xyz() const { - return *reinterpret_cast *>(this); + constexpr const vec& xyz() const { + return *reinterpret_cast*>(this); } - vec &xy() { return *reinterpret_cast *>(this); } - vec &xyz() { return *reinterpret_cast *>(this); } + vec& xy() { return *reinterpret_cast*>(this); } + vec& xyz() { return *reinterpret_cast*>(this); } template > - constexpr vec(const U &u) : vec(converter{}(u)) {} + constexpr vec(const U& u) : vec(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -1067,20 +1060,20 @@ functions, as well as a set of standard reductions. */ template struct mat { - typedef vec V; + using V = vec; V x; constexpr mat() : x() {} - constexpr mat(const V &x_) : x(x_) {} - constexpr explicit mat(const T &s) : x(s) {} - constexpr explicit mat(const T *p) : x(p + M * 0) {} + constexpr mat(const V& x_) : x(x_) {} + constexpr explicit mat(const T& s) : x(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0) {} template - constexpr explicit mat(const mat &m) : mat(V(m.x)) {} + constexpr explicit mat(const mat& m) : mat(V(m.x)) {} constexpr vec row(int i) const { return {x[i]}; } - constexpr const V &operator[](int j) const { return x; } - LINALG_CONSTEXPR14 V &operator[](int j) { return x; } + constexpr const V& operator[](int) const { return x; } + LINALG_CONSTEXPR14 V& operator[](int) { return x; } template > - constexpr mat(const U &u) : mat(converter{}(u)) {} + constexpr mat(const U& u) : mat(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -1088,22 +1081,22 @@ struct mat { }; template struct mat { - typedef vec V; + using V = vec; V x, y; constexpr mat() : x(), y() {} - constexpr mat(const V &x_, const V &y_) : x(x_), y(y_) {} - constexpr explicit mat(const T &s) : x(s), y(s) {} - constexpr explicit mat(const T *p) : x(p + M * 0), y(p + M * 1) {} + constexpr mat(const V& x_, const V& y_) : x(x_), y(y_) {} + constexpr explicit mat(const T& s) : x(s), y(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0), y(p + M * 1) {} template - constexpr explicit mat(const mat &m) : mat(V(m.x), V(m.y)) { + constexpr explicit mat(const mat& m) : mat(V(m.x), V(m.y)) { static_assert(P >= 2, "Input matrix dimensions must be at least as big."); } constexpr vec row(int i) const { return {x[i], y[i]}; } - constexpr const V &operator[](int j) const { return j == 0 ? x : y; } - LINALG_CONSTEXPR14 V &operator[](int j) { return j == 0 ? x : y; } + constexpr const V& operator[](int j) const { return j == 0 ? x : y; } + LINALG_CONSTEXPR14 V& operator[](int j) { return j == 0 ? x : y; } template > - constexpr mat(const U &u) : mat(converter{}(u)) {} + constexpr mat(const U& u) : mat(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -1111,29 +1104,29 @@ struct mat { }; template struct mat { - typedef vec V; + using V = vec; V x, y, z; constexpr mat() : x(), y(), z() {} - constexpr mat(const V &x_, const V &y_, const V &z_) : x(x_), y(y_), z(z_) {} - constexpr mat(const mat &m_, const V &z_) + constexpr mat(const V& x_, const V& y_, const V& z_) : x(x_), y(y_), z(z_) {} + constexpr mat(const mat& m_, const V& z_) : x(m_.x), y(m_.y), z(z_) {} - constexpr explicit mat(const T &s) : x(s), y(s), z(s) {} - constexpr explicit mat(const T *p) + constexpr explicit mat(const T& s) : x(s), y(s), z(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0), y(p + M * 1), z(p + M * 2) {} template - constexpr explicit mat(const mat &m) : mat(V(m.x), V(m.y), V(m.z)) { + constexpr explicit mat(const mat& m) : mat(V(m.x), V(m.y), V(m.z)) { static_assert(P >= 3, "Input matrix dimensions must be at least as big."); } constexpr vec row(int i) const { return {x[i], y[i], z[i]}; } - constexpr const V &operator[](int j) const { + constexpr const V& operator[](int j) const { return j == 0 ? x : j == 1 ? y : z; } - LINALG_CONSTEXPR14 V &operator[](int j) { + LINALG_CONSTEXPR14 V& operator[](int j) { return j == 0 ? x : j == 1 ? y : z; } template > - constexpr mat(const U &u) : mat(converter{}(u)) {} + constexpr mat(const U& u) : mat(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -1141,32 +1134,32 @@ struct mat { }; template struct mat { - typedef vec V; + using V = vec; V x, y, z, w; constexpr mat() : x(), y(), z(), w() {} - constexpr mat(const V &x_, const V &y_, const V &z_, const V &w_) + constexpr mat(const V& x_, const V& y_, const V& z_, const V& w_) : x(x_), y(y_), z(z_), w(w_) {} - constexpr mat(const mat &m_, const V &w_) + constexpr mat(const mat& m_, const V& w_) : x(m_.x), y(m_.y), z(m_.z), w(w_) {} - constexpr explicit mat(const T &s) : x(s), y(s), z(s), w(s) {} - constexpr explicit mat(const T *p) + constexpr explicit mat(const T& s) : x(s), y(s), z(s), w(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0), y(p + M * 1), z(p + M * 2), w(p + M * 3) {} template - constexpr explicit mat(const mat &m) + constexpr explicit mat(const mat& m) : mat(V(m.x), V(m.y), V(m.z), V(m.w)) { static_assert(P >= 4, "Input matrix dimensions must be at least as big."); } constexpr vec row(int i) const { return {x[i], y[i], z[i], w[i]}; } - constexpr const V &operator[](int j) const { + constexpr const V& operator[](int j) const { return j == 0 ? x : j == 1 ? y : j == 2 ? z : w; } - LINALG_CONSTEXPR14 V &operator[](int j) { + LINALG_CONSTEXPR14 V& operator[](int j) { return j == 0 ? x : j == 1 ? y : j == 2 ? z : w; } template > - constexpr mat(const U &u) : mat(converter{}(u)) {} + constexpr mat(const U& u) : mat(converter{}(u)) {} template > constexpr operator U() const { return converter{}(*this); @@ -1228,35 +1221,35 @@ constexpr identity_t identity{1}; * @{ */ template -constexpr A fold(F f, A a, const vec &b) { +constexpr A fold(F f, A a, const vec& b) { return f(a, b.x); } template -constexpr A fold(F f, A a, const vec &b) { +constexpr A fold(F f, A a, const vec& b) { return f(f(a, b.x), b.y); } template -constexpr A fold(F f, A a, const vec &b) { +constexpr A fold(F f, A a, const vec& b) { return f(f(f(a, b.x), b.y), b.z); } template -constexpr A fold(F f, A a, const vec &b) { +constexpr A fold(F f, A a, const vec& b) { return f(f(f(f(a, b.x), b.y), b.z), b.w); } template -constexpr A fold(F f, A a, const mat &b) { +constexpr A fold(F f, A a, const mat& b) { return fold(f, a, b.x); } template -constexpr A fold(F f, A a, const mat &b) { +constexpr A fold(F f, A a, const mat& b) { return fold(f, fold(f, a, b.x), b.y); } template -constexpr A fold(F f, A a, const mat &b) { +constexpr A fold(F f, A a, const mat& b) { return fold(f, fold(f, fold(f, a, b.x), b.y), b.z); } template -constexpr A fold(F f, A a, const mat &b) { +constexpr A fold(F f, A a, const mat& b) { return fold(f, fold(f, fold(f, fold(f, a, b.x), b.y), b.z), b.w); } /** @} */ @@ -1286,20 +1279,20 @@ using scalar_t = // apply(f,...) applies the provided function in an elementwise fashion to its // arguments, producing an object of the same dimensions template -constexpr apply_t apply(F func, const A &...args) { +constexpr apply_t apply(F func, const A&... args) { return detail::apply::impl( detail::make_seq<0, detail::apply::size>{}, func, args...); } // map(a,f) is equivalent to apply(f,a) template -constexpr apply_t map(const A &a, F func) { +constexpr apply_t map(const A& a, F func) { return apply(func, a); } // zip(a,b,f) is equivalent to apply(f,a,b) template -constexpr apply_t zip(const A &a, const B &b, F func) { +constexpr apply_t zip(const A& a, const B& b, F func) { return apply(func, a, b); } /** @} */ @@ -1311,38 +1304,38 @@ constexpr apply_t zip(const A &a, const B &b, F func) { * @{ */ template -constexpr typename detail::any_compare::type compare(const A &a, - const B &b) { +constexpr typename detail::any_compare::type compare(const A& a, + const B& b) { return detail::any_compare()(a, b); } template -constexpr auto operator==(const A &a, - const B &b) -> decltype(compare(a, b) == 0) { +constexpr auto operator==(const A& a, const B& b) + -> decltype(compare(a, b) == 0) { return compare(a, b) == 0; } template -constexpr auto operator!=(const A &a, - const B &b) -> decltype(compare(a, b) != 0) { +constexpr auto operator!=(const A& a, const B& b) + -> decltype(compare(a, b) != 0) { return compare(a, b) != 0; } template -constexpr auto operator<(const A &a, - const B &b) -> decltype(compare(a, b) < 0) { +constexpr auto operator<(const A& a, const B& b) + -> decltype(compare(a, b) < 0) { return compare(a, b) < 0; } template -constexpr auto operator>(const A &a, - const B &b) -> decltype(compare(a, b) > 0) { +constexpr auto operator>(const A& a, const B& b) + -> decltype(compare(a, b) > 0) { return compare(a, b) > 0; } template -constexpr auto operator<=(const A &a, - const B &b) -> decltype(compare(a, b) <= 0) { +constexpr auto operator<=(const A& a, const B& b) + -> decltype(compare(a, b) <= 0) { return compare(a, b) <= 0; } template -constexpr auto operator>=(const A &a, - const B &b) -> decltype(compare(a, b) >= 0) { +constexpr auto operator>=(const A& a, const B& b) + -> decltype(compare(a, b) >= 0) { return compare(a, b) >= 0; } /** @} */ @@ -1353,38 +1346,38 @@ constexpr auto operator>=(const A &a, * @{ */ template -constexpr bool any(const A &a) { +constexpr bool any(const A& a) { return fold(detail::op_or{}, false, a); } template -constexpr bool all(const A &a) { +constexpr bool all(const A& a) { return fold(detail::op_and{}, true, a); } template -constexpr scalar_t sum(const A &a) { +constexpr scalar_t sum(const A& a) { return fold(detail::op_add{}, scalar_t(0), a); } template -constexpr scalar_t product(const A &a) { +constexpr scalar_t product(const A& a) { return fold(detail::op_mul{}, scalar_t(1), a); } template -constexpr scalar_t minelem(const A &a) { +constexpr scalar_t minelem(const A& a) { return fold(detail::min{}, a.x, a); } template -constexpr scalar_t maxelem(const A &a) { +constexpr scalar_t maxelem(const A& a) { return fold(detail::max{}, a.x, a); } template -int argmin(const vec &a) { +int argmin(const vec& a) { int j = 0; for (int i = 1; i < M; ++i) if (a[i] < a[j]) j = i; return j; } template -int argmax(const vec &a) { +int argmax(const vec& a) { int j = 0; for (int i = 1; i < M; ++i) if (a[i] > a[j]) j = i; @@ -1398,19 +1391,19 @@ int argmax(const vec &a) { * @{ */ template -constexpr apply_t operator+(const A &a) { +constexpr apply_t operator+(const A& a) { return apply(detail::op_pos{}, a); } template -constexpr apply_t operator-(const A &a) { +constexpr apply_t operator-(const A& a) { return apply(detail::op_neg{}, a); } template -constexpr apply_t operator~(const A &a) { +constexpr apply_t operator~(const A& a) { return apply(detail::op_cmp{}, a); } template -constexpr apply_t operator!(const A &a) { +constexpr apply_t operator!(const A& a) { return apply(detail::op_not{}, a); } /** @} */ @@ -1424,108 +1417,108 @@ constexpr apply_t operator!(const A &a) { * @{ */ template -constexpr apply_t operator+(const A &a, const B &b) { +constexpr apply_t operator+(const A& a, const B& b) { return apply(detail::op_add{}, a, b); } template -constexpr apply_t operator-(const A &a, const B &b) { +constexpr apply_t operator-(const A& a, const B& b) { return apply(detail::op_sub{}, a, b); } template -constexpr apply_t cmul(const A &a, const B &b) { +constexpr apply_t cmul(const A& a, const B& b) { return apply(detail::op_mul{}, a, b); } template -constexpr apply_t operator/(const A &a, const B &b) { +constexpr apply_t operator/(const A& a, const B& b) { return apply(detail::op_div{}, a, b); } template -constexpr apply_t operator%(const A &a, const B &b) { +constexpr apply_t operator%(const A& a, const B& b) { return apply(detail::op_mod{}, a, b); } template -constexpr apply_t operator|(const A &a, const B &b) { +constexpr apply_t operator|(const A& a, const B& b) { return apply(detail::op_un{}, a, b); } template -constexpr apply_t operator^(const A &a, const B &b) { +constexpr apply_t operator^(const A& a, const B& b) { return apply(detail::op_xor{}, a, b); } template -constexpr apply_t operator&(const A &a, const B &b) { +constexpr apply_t operator&(const A& a, const B& b) { return apply(detail::op_int{}, a, b); } template -constexpr apply_t operator<<(const A &a, const B &b) { +constexpr apply_t operator<<(const A& a, const B& b) { return apply(detail::op_lsh{}, a, b); } template -constexpr apply_t operator>>(const A &a, const B &b) { +constexpr apply_t operator>>(const A& a, const B& b) { return apply(detail::op_rsh{}, a, b); } // Binary `operator *` represents the algebraic matrix product - use cmul(a, b) // for the Hadamard (component-wise) product. template -constexpr auto operator*(const A &a, const B &b) { +constexpr auto operator*(const A& a, const B& b) { return mul(a, b); } // Binary assignment operators a $= b is always defined as though it were // explicitly written a = a $ b template -constexpr auto operator+=(A &a, const B &b) -> decltype(a = a + b) { +constexpr auto operator+=(A& a, const B& b) -> decltype(a = a + b) { return a = a + b; } template -constexpr auto operator-=(A &a, const B &b) -> decltype(a = a - b) { +constexpr auto operator-=(A& a, const B& b) -> decltype(a = a - b) { return a = a - b; } template -constexpr auto operator*=(A &a, const B &b) -> decltype(a = a * b) { +constexpr auto operator*=(A& a, const B& b) -> decltype(a = a * b) { return a = a * b; } template -constexpr auto operator/=(A &a, const B &b) -> decltype(a = a / b) { +constexpr auto operator/=(A& a, const B& b) -> decltype(a = a / b) { return a = a / b; } template -constexpr auto operator%=(A &a, const B &b) -> decltype(a = a % b) { +constexpr auto operator%=(A& a, const B& b) -> decltype(a = a % b) { return a = a % b; } template -constexpr auto operator|=(A &a, const B &b) -> decltype(a = a | b) { +constexpr auto operator|=(A& a, const B& b) -> decltype(a = a | b) { return a = a | b; } template -constexpr auto operator^=(A &a, const B &b) -> decltype(a = a ^ b) { +constexpr auto operator^=(A& a, const B& b) -> decltype(a = a ^ b) { return a = a ^ b; } template -constexpr auto operator&=(A &a, const B &b) -> decltype(a = a & b) { +constexpr auto operator&=(A& a, const B& b) -> decltype(a = a & b) { return a = a & b; } template -constexpr auto operator<<=(A &a, const B &b) -> decltype(a = a << b) { +constexpr auto operator<<=(A& a, const B& b) -> decltype(a = a << b) { return a = a << b; } template -constexpr auto operator>>=(A &a, const B &b) -> decltype(a = a >> b) { +constexpr auto operator>>=(A& a, const B& b) -> decltype(a = a >> b) { return a = a >> b; } /** @} */ -/** @addtogroup swizzles +/** @defgroup swizzles Swizzles + * Swizzles and subobjects. * @ingroup LinAlg - * @brief Swizzles and subobjects. - * @{ + * @{ */ /** * @brief Returns a vector containing the specified ordered indices, e.g. * linalg::swizzle<1, 2, 0>(vec4(4, 5, 6, 7)) == vec3(5, 6, 4) */ template -constexpr vec swizzle(const vec &a) { +constexpr vec swizzle(const vec& a) { return {detail::getter{}(a)...}; } /** @@ -1533,7 +1526,7 @@ constexpr vec swizzle(const vec &a) { * linalg::subvec<1, 4>(vec4(4, 5, 6, 7)) == vec3(5, 6, 7) */ template -constexpr vec subvec(const vec &a) { +constexpr vec subvec(const vec& a) { return detail::swizzle(a, detail::make_seq{}); } /** @@ -1541,7 +1534,7 @@ constexpr vec subvec(const vec &a) { * linalg::submat */ template -constexpr mat submat(const mat &a) { +constexpr mat submat(const mat& a) { return detail::swizzle(a, detail::make_seq{}, detail::make_seq{}); } @@ -1553,79 +1546,79 @@ constexpr mat submat(const mat &a) { * @{ */ template -constexpr apply_t isfinite(const A &a) { +constexpr apply_t isfinite(const A& a) { return apply(detail::std_isfinite{}, a); } template -constexpr apply_t abs(const A &a) { +constexpr apply_t abs(const A& a) { return apply(detail::std_abs{}, a); } template -constexpr apply_t floor(const A &a) { +constexpr apply_t floor(const A& a) { return apply(detail::std_floor{}, a); } template -constexpr apply_t ceil(const A &a) { +constexpr apply_t ceil(const A& a) { return apply(detail::std_ceil{}, a); } template -constexpr apply_t exp(const A &a) { +constexpr apply_t exp(const A& a) { return apply(detail::std_exp{}, a); } template -constexpr apply_t log(const A &a) { +constexpr apply_t log(const A& a) { return apply(detail::std_log{}, a); } template -constexpr apply_t log2(const A &a) { +constexpr apply_t log2(const A& a) { return apply(detail::std_log2{}, a); } template -constexpr apply_t log10(const A &a) { +constexpr apply_t log10(const A& a) { return apply(detail::std_log10{}, a); } template -constexpr apply_t sqrt(const A &a) { +constexpr apply_t sqrt(const A& a) { return apply(detail::std_sqrt{}, a); } template -constexpr apply_t sin(const A &a) { +constexpr apply_t sin(const A& a) { return apply(detail::std_sin{}, a); } template -constexpr apply_t cos(const A &a) { +constexpr apply_t cos(const A& a) { return apply(detail::std_cos{}, a); } template -constexpr apply_t tan(const A &a) { +constexpr apply_t tan(const A& a) { return apply(detail::std_tan{}, a); } template -constexpr apply_t asin(const A &a) { +constexpr apply_t asin(const A& a) { return apply(detail::std_asin{}, a); } template -constexpr apply_t acos(const A &a) { +constexpr apply_t acos(const A& a) { return apply(detail::std_acos{}, a); } template -constexpr apply_t atan(const A &a) { +constexpr apply_t atan(const A& a) { return apply(detail::std_atan{}, a); } template -constexpr apply_t sinh(const A &a) { +constexpr apply_t sinh(const A& a) { return apply(detail::std_sinh{}, a); } template -constexpr apply_t cosh(const A &a) { +constexpr apply_t cosh(const A& a) { return apply(detail::std_cosh{}, a); } template -constexpr apply_t tanh(const A &a) { +constexpr apply_t tanh(const A& a) { return apply(detail::std_tanh{}, a); } template -constexpr apply_t round(const A &a) { +constexpr apply_t round(const A& a) { return apply(detail::std_round{}, a); } /** @} */ @@ -1637,19 +1630,19 @@ constexpr apply_t round(const A &a) { * @{ */ template -constexpr apply_t fmod(const A &a, const B &b) { +constexpr apply_t fmod(const A& a, const B& b) { return apply(detail::std_fmod{}, a, b); } template -constexpr apply_t pow(const A &a, const B &b) { +constexpr apply_t pow(const A& a, const B& b) { return apply(detail::std_pow{}, a, b); } template -constexpr apply_t atan2(const A &a, const B &b) { +constexpr apply_t atan2(const A& a, const B& b) { return apply(detail::std_atan2{}, a, b); } template -constexpr apply_t copysign(const A &a, const B &b) { +constexpr apply_t copysign(const A& a, const B& b) { return apply(detail::std_copysign{}, a, b); } /** @} */ @@ -1661,27 +1654,27 @@ constexpr apply_t copysign(const A &a, const B &b) { * @{ */ template -constexpr apply_t equal(const A &a, const B &b) { +constexpr apply_t equal(const A& a, const B& b) { return apply(detail::op_eq{}, a, b); } template -constexpr apply_t nequal(const A &a, const B &b) { +constexpr apply_t nequal(const A& a, const B& b) { return apply(detail::op_ne{}, a, b); } template -constexpr apply_t less(const A &a, const B &b) { +constexpr apply_t less(const A& a, const B& b) { return apply(detail::op_lt{}, a, b); } template -constexpr apply_t greater(const A &a, const B &b) { +constexpr apply_t greater(const A& a, const B& b) { return apply(detail::op_gt{}, a, b); } template -constexpr apply_t lequal(const A &a, const B &b) { +constexpr apply_t lequal(const A& a, const B& b) { return apply(detail::op_le{}, a, b); } template -constexpr apply_t gequal(const A &a, const B &b) { +constexpr apply_t gequal(const A& a, const B& b) { return apply(detail::op_ge{}, a, b); } /** @} */ @@ -1693,19 +1686,19 @@ constexpr apply_t gequal(const A &a, const B &b) { * @{ */ template -constexpr apply_t min(const A &a, const B &b) { +constexpr apply_t min(const A& a, const B& b) { return apply(detail::min{}, a, b); } template -constexpr apply_t max(const A &a, const B &b) { +constexpr apply_t max(const A& a, const B& b) { return apply(detail::max{}, a, b); } /** * @brief Clamps the components of x between l and h, provided l[i] < h[i]. */ template -constexpr apply_t clamp(const X &x, const L &l, - const H &h) { +constexpr apply_t clamp(const X& x, const L& l, + const H& h) { return apply(detail::clamp{}, x, l, h); } /** @@ -1713,8 +1706,8 @@ constexpr apply_t clamp(const X &x, const L &l, * true and from b otherwise. */ template -constexpr apply_t select(const P &p, const A &a, - const B &b) { +constexpr apply_t select(const P& p, const A& a, + const B& b) { return apply(detail::select{}, p, a, b); } /** @@ -1722,36 +1715,36 @@ constexpr apply_t select(const P &p, const A &a, * [a, b] will result if t is outside [0, 1]. */ template -constexpr apply_t lerp(const A &a, const B &b, - const T &t) { +constexpr apply_t lerp(const A& a, const B& b, + const T& t) { return apply(detail::lerp{}, a, b, t); } /** @} */ -/** @addtogroup vec_algebra +/** @defgroup vec_algebra Vector Algebra + * Support for vector algebra. * @ingroup LinAlg - * @brief Support for vector algebra. * @{ */ /** * @brief shorthand for `cross({a.x,a.y,0}, {b.x,b.y,0}).z` */ template -constexpr T cross(const vec &a, const vec &b) { +constexpr T cross(const vec& a, const vec& b) { return a.x * b.y - a.y * b.x; } /** * @brief shorthand for `cross({0,0,a.z}, {b.x,b.y,0}).xy()` */ template -constexpr vec cross(T a, const vec &b) { +constexpr vec cross(T a, const vec& b) { return {-a * b.y, a * b.x}; } /** * @brief shorthand for `cross({a.x,a.y,0}, {0,0,b.z}).xy()` */ template -constexpr vec cross(const vec &a, T b) { +constexpr vec cross(const vec& a, T b) { return {a.y * b, -a.x * b}; } /** @@ -1759,7 +1752,7 @@ constexpr vec cross(const vec &a, T b) { * product](https://en.wikipedia.org/wiki/Cross_product) of vectors `a` and `b` */ template -constexpr vec cross(const vec &a, const vec &b) { +constexpr vec cross(const vec& a, const vec& b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } /** @@ -1767,21 +1760,21 @@ constexpr vec cross(const vec &a, const vec &b) { * vectors `a` and `b` */ template -constexpr T dot(const vec &a, const vec &b) { +constexpr T dot(const vec& a, const vec& b) { return sum(a * b); } /** * @brief *square* of the length or magnitude of vector `a` */ template -constexpr T length2(const vec &a) { +constexpr T length2(const vec& a) { return dot(a, a); } /** * @brief length or magnitude of a vector `a` */ template -T length(const vec &a) { +T length(const vec& a) { return std::sqrt(length2(a)); } /** @@ -1790,7 +1783,7 @@ T length(const vec &a) { */ template -vec normalize(const vec &a) { +vec normalize(const vec& a) { return a / length(a); } /** @@ -1799,7 +1792,7 @@ vec normalize(const vec &a) { * `a` and `b` */ template -constexpr T distance2(const vec &a, const vec &b) { +constexpr T distance2(const vec& a, const vec& b) { return length2(b - a); } /** @@ -1807,14 +1800,14 @@ constexpr T distance2(const vec &a, const vec &b) { * between points `a` and `b` */ template -T distance(const vec &a, const vec &b) { +T distance(const vec& a, const vec& b) { return length(b - a); } /** * @brief Return the angle in radians between two unit vectors. */ template -T uangle(const vec &a, const vec &b) { +T uangle(const vec& a, const vec& b) { T d = dot(a, b); return d > 1 ? 0 : std::acos(d < -1 ? -1 : d); } @@ -1822,7 +1815,7 @@ T uangle(const vec &a, const vec &b) { * @brief Return the angle in radians between two non-unit vectors. */ template -T angle(const vec &a, const vec &b) { +T angle(const vec& a, const vec& b) { return uangle(normalize(a), normalize(b)); } /** @@ -1830,7 +1823,7 @@ T angle(const vec &a, const vec &b) { * [radians](https://en.wikipedia.org/wiki/Radian) */ template -vec rot(T a, const vec &v) { +vec rot(T a, const vec& v) { const T s = std::sin(a), c = std::cos(a); return {v.x * c - v.y * s, v.x * s + v.y * c}; } @@ -1839,7 +1832,7 @@ vec rot(T a, const vec &v) { * [radians](https://en.wikipedia.org/wiki/Radian) around the X axis */ template -vec rotx(T a, const vec &v) { +vec rotx(T a, const vec& v) { const T s = std::sin(a), c = std::cos(a); return {v.x, v.y * c - v.z * s, v.y * s + v.z * c}; } @@ -1848,7 +1841,7 @@ vec rotx(T a, const vec &v) { * [radians](https://en.wikipedia.org/wiki/Radian) around the Y axis */ template -vec roty(T a, const vec &v) { +vec roty(T a, const vec& v) { const T s = std::sin(a), c = std::cos(a); return {v.x * c + v.z * s, v.y, -v.x * s + v.z * c}; } @@ -1857,7 +1850,7 @@ vec roty(T a, const vec &v) { * [radians](https://en.wikipedia.org/wiki/Radian) around the Z axis */ template -vec rotz(T a, const vec &v) { +vec rotz(T a, const vec& v) { const T s = std::sin(a), c = std::cos(a); return {v.x * c - v.y * s, v.x * s + v.y * c, v.z}; } @@ -1865,7 +1858,7 @@ vec rotz(T a, const vec &v) { * @brief shorthand for `normalize(lerp(a,b,t))` */ template -vec nlerp(const vec &a, const vec &b, T t) { +vec nlerp(const vec& a, const vec& b, T t) { return normalize(lerp(a, b, t)); } /** @@ -1874,7 +1867,7 @@ vec nlerp(const vec &a, const vec &b, T t) { * parameter `t` */ template -vec slerp(const vec &a, const vec &b, T t) { +vec slerp(const vec& a, const vec& b, T t) { T th = uangle(a, b); return th == 0 ? a : a * (std::sin(th * (1 - t)) / std::sin(th)) + @@ -1882,10 +1875,10 @@ vec slerp(const vec &a, const vec &b, T t) { } /** @} */ -/** @addtogroup quaternions +/** @defgroup quaternions Quaternions + * Support for quaternion algebra using 4D vectors of + * arbitrary length, representing xi + yj + zk + w. * @ingroup LinAlg - * @brief Support for quaternion algebra using 4D vectors of arbitrary length, - * representing xi + yj + zk + w. * @{ */ /** @@ -1894,7 +1887,7 @@ vec slerp(const vec &a, const vec &b, T t) { * of quaternion `q` */ template -constexpr vec qconj(const vec &q) { +constexpr vec qconj(const vec& q) { return {-q.x, -q.y, -q.z, q.w}; } /** @@ -1903,7 +1896,7 @@ constexpr vec qconj(const vec &q) { * of quaternion `q` (undefined for zero-length quaternions) */ template -vec qinv(const vec &q) { +vec qinv(const vec& q) { return qconj(q) / length2(q); } /** @@ -1912,7 +1905,7 @@ vec qinv(const vec &q) { * of quaternion `q` */ template -vec qexp(const vec &q) { +vec qexp(const vec& q) { const auto v = q.xyz(); const auto vv = length(v); return std::exp(q.w) * @@ -1924,7 +1917,7 @@ vec qexp(const vec &q) { * of quaternion `q` */ template -vec qlog(const vec &q) { +vec qlog(const vec& q) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q); return {v * (vv > 0 ? std::acos(q.w / qq) / vv : 0), std::log(qq)}; @@ -1933,7 +1926,7 @@ vec qlog(const vec &q) { * @brief quaternion `q` raised to the exponent `p` */ template -vec qpow(const vec &q, const T &p) { +vec qpow(const vec& q, const T& p) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q), th = std::acos(q.w / qq); return std::pow(qq, p) * @@ -1945,7 +1938,7 @@ vec qpow(const vec &q, const T &p) { * quaternions `a` and `b` */ template -constexpr vec qmul(const vec &a, const vec &b) { +constexpr vec qmul(const vec& a, const vec& b) { return {a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y, a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z, a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x, @@ -1955,21 +1948,21 @@ constexpr vec qmul(const vec &a, const vec &b) { * @brief Multiply as many input quaternions together as desired. */ template -constexpr vec qmul(const vec &a, R... r) { +constexpr vec qmul(const vec& a, R... r) { return qmul(a, qmul(r...)); } /** @} */ -/** @addtogroup quaternion_rotation +/** @defgroup quaternion_rotation Quaternion Rotations + * Support for 3D spatial rotations using normalized quaternions. * @ingroup LinAlg - * @brief Support for 3D spatial rotations using normalized quaternions. * @{ */ /** * @brief efficient shorthand for `qrot(q, {1,0,0})` */ template -constexpr vec qxdir(const vec &q) { +constexpr vec qxdir(const vec& q) { return {q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, (q.x * q.y + q.z * q.w) * 2, (q.z * q.x - q.y * q.w) * 2}; } @@ -1977,7 +1970,7 @@ constexpr vec qxdir(const vec &q) { * @brief efficient shorthand for `qrot(q, {0,1,0})` */ template -constexpr vec qydir(const vec &q) { +constexpr vec qydir(const vec& q) { return {(q.x * q.y - q.z * q.w) * 2, q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, (q.y * q.z + q.x * q.w) * 2}; @@ -1986,7 +1979,7 @@ constexpr vec qydir(const vec &q) { * @brief efficient shorthand for `qrot(q, {0,0,1})` */ template -constexpr vec qzdir(const vec &q) { +constexpr vec qzdir(const vec& q) { return {(q.z * q.x + q.y * q.w) * 2, (q.y * q.z - q.x * q.w) * 2, q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z}; } @@ -1994,14 +1987,14 @@ constexpr vec qzdir(const vec &q) { * @brief Create an equivalent mat3 rotation matrix from the input quaternion. */ template -constexpr mat qmat(const vec &q) { +constexpr mat qmat(const vec& q) { return {qxdir(q), qydir(q), qzdir(q)}; } /** * @brief Rotate a vector by a quaternion. */ template -constexpr vec qrot(const vec &q, const vec &v) { +constexpr vec qrot(const vec& q, const vec& v) { return qxdir(q) * v.x + qydir(q) * v.y + qzdir(q) * v.z; } /** @@ -2009,7 +2002,7 @@ constexpr vec qrot(const vec &q, const vec &v) { * input normalized quaternion. */ template -T qangle(const vec &q) { +T qangle(const vec& q) { return std::atan2(length(q.xyz()), q.w) * 2; } /** @@ -2017,7 +2010,7 @@ T qangle(const vec &q) { * input normalized quaternion. */ template -vec qaxis(const vec &q) { +vec qaxis(const vec& q) { return normalize(q.xyz()); } /** @@ -2025,14 +2018,14 @@ vec qaxis(const vec &q) { * geometrically sensible, consider qslerp instead. */ template -vec qnlerp(const vec &a, const vec &b, T t) { +vec qnlerp(const vec& a, const vec& b, T t) { return nlerp(a, dot(a, b) < 0 ? -b : b, t); } /** * @brief Spherical linear interpolation that takes the shortest path. */ template -vec qslerp(const vec &a, const vec &b, T t) { +vec qslerp(const vec& a, const vec& b, T t) { return slerp(a, dot(a, b) < 0 ? -b : b, t); } /** @@ -2040,7 +2033,7 @@ vec qslerp(const vec &a, const vec &b, T t) { * radians about the provided axis. */ template -vec constexpr rotation_quat(const vec &axis, T angle) { +vec constexpr rotation_quat(const vec& axis, T angle) { return {axis * std::sin(angle / 2), std::cos(angle / 2)}; } /** @@ -2048,13 +2041,13 @@ vec constexpr rotation_quat(const vec &axis, T angle) { * from orig vector to dest vector. */ template -vec rotation_quat(const vec &orig, const vec &dest); +vec rotation_quat(const vec& orig, const vec& dest); /** * @brief Returns a normalized quaternion representing the input rotation * matrix, which should be orthonormal. */ template -vec rotation_quat(const mat &m); +vec rotation_quat(const mat& m); /** @} */ /** @addtogroup mat_algebra @@ -2063,167 +2056,167 @@ vec rotation_quat(const mat &m); * @{ */ template -constexpr vec mul(const vec &a, const T &b) { +constexpr vec mul(const vec& a, const T& b) { return cmul(a, b); } template -constexpr vec mul(const T &b, const vec &a) { +constexpr vec mul(const T& b, const vec& a) { return cmul(b, a); } template -constexpr mat mul(const mat &a, const T &b) { +constexpr mat mul(const mat& a, const T& b) { return cmul(a, b); } template -constexpr mat mul(const T &b, const mat &a) { +constexpr mat mul(const T& b, const mat& a) { return cmul(b, a); } template -constexpr vec mul(const vec &a, const vec &b) { +constexpr vec mul(const vec& a, const vec& b) { return cmul(a, b); } template -constexpr vec mul(const mat &a, const vec &b) { +constexpr vec mul(const mat& a, const vec& b) { return a.x * b.x; } template -constexpr vec mul(const mat &a, const vec &b) { +constexpr vec mul(const mat& a, const vec& b) { return a.x * b.x + a.y * b.y; } template -constexpr vec mul(const mat &a, const vec &b) { +constexpr vec mul(const mat& a, const vec& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } template -constexpr vec mul(const mat &a, const vec &b) { +constexpr vec mul(const mat& a, const vec& b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } template -constexpr mat mul(const mat &a, const mat &b) { +constexpr mat mul(const mat& a, const mat& b) { return {mul(a, b.x)}; } template -constexpr mat mul(const mat &a, const mat &b) { +constexpr mat mul(const mat& a, const mat& b) { return {mul(a, b.x), mul(a, b.y)}; } template -constexpr mat mul(const mat &a, const mat &b) { +constexpr mat mul(const mat& a, const mat& b) { return {mul(a, b.x), mul(a, b.y), mul(a, b.z)}; } template -constexpr mat mul(const mat &a, const mat &b) { +constexpr mat mul(const mat& a, const mat& b) { return {mul(a, b.x), mul(a, b.y), mul(a, b.z), mul(a, b.w)}; } template -constexpr vec mul(const mat &a, const mat &b, - const vec &c) { +constexpr vec mul(const mat& a, const mat& b, + const vec& c) { return mul(mul(a, b), c); } template -constexpr mat mul(const mat &a, const mat &b, - const mat &c) { +constexpr mat mul(const mat& a, const mat& b, + const mat& c) { return mul(mul(a, b), c); } template -constexpr vec mul(const mat &a, const mat &b, - const mat &c, const vec &d) { +constexpr vec mul(const mat& a, const mat& b, + const mat& c, const vec& d) { return mul(mul(a, b, c), d); } template -constexpr mat mul(const mat &a, const mat &b, - const mat &c, const mat &d) { +constexpr mat mul(const mat& a, const mat& b, + const mat& c, const mat& d) { return mul(mul(a, b, c), d); } template -constexpr mat outerprod(const vec &a, const vec &b) { +constexpr mat outerprod(const vec& a, const vec& b) { return {a * b.x}; } template -constexpr mat outerprod(const vec &a, const vec &b) { +constexpr mat outerprod(const vec& a, const vec& b) { return {a * b.x, a * b.y}; } template -constexpr mat outerprod(const vec &a, const vec &b) { +constexpr mat outerprod(const vec& a, const vec& b) { return {a * b.x, a * b.y, a * b.z}; } template -constexpr mat outerprod(const vec &a, const vec &b) { +constexpr mat outerprod(const vec& a, const vec& b) { return {a * b.x, a * b.y, a * b.z, a * b.w}; } template -constexpr vec diagonal(const mat &a) { +constexpr vec diagonal(const mat& a) { return {a.x.x}; } template -constexpr vec diagonal(const mat &a) { +constexpr vec diagonal(const mat& a) { return {a.x.x, a.y.y}; } template -constexpr vec diagonal(const mat &a) { +constexpr vec diagonal(const mat& a) { return {a.x.x, a.y.y, a.z.z}; } template -constexpr vec diagonal(const mat &a) { +constexpr vec diagonal(const mat& a) { return {a.x.x, a.y.y, a.z.z, a.w.w}; } template -constexpr T trace(const mat &a) { +constexpr T trace(const mat& a) { return sum(diagonal(a)); } template -constexpr mat transpose(const mat &m) { +constexpr mat transpose(const mat& m) { return {m.row(0)}; } template -constexpr mat transpose(const mat &m) { +constexpr mat transpose(const mat& m) { return {m.row(0), m.row(1)}; } template -constexpr mat transpose(const mat &m) { +constexpr mat transpose(const mat& m) { return {m.row(0), m.row(1), m.row(2)}; } template -constexpr mat transpose(const mat &m) { +constexpr mat transpose(const mat& m) { return {m.row(0), m.row(1), m.row(2), m.row(3)}; } template -constexpr mat transpose(const vec &m) { +constexpr mat transpose(const vec& m) { return transpose(mat(m)); } template -constexpr mat adjugate(const mat &a) { +constexpr mat adjugate(const mat&) { return {vec{1}}; } template -constexpr mat adjugate(const mat &a) { +constexpr mat adjugate(const mat& a) { return {{a.y.y, -a.x.y}, {-a.y.x, a.x.x}}; } template -constexpr mat adjugate(const mat &a); +constexpr mat adjugate(const mat& a); template -constexpr mat adjugate(const mat &a); +constexpr mat adjugate(const mat& a); template -constexpr mat comatrix(const mat &a) { +constexpr mat comatrix(const mat& a) { return transpose(adjugate(a)); } template -constexpr T determinant(const mat &a) { +constexpr T determinant(const mat& a) { return a.x.x; } template -constexpr T determinant(const mat &a) { +constexpr T determinant(const mat& a) { return a.x.x * a.y.y - a.x.y * a.y.x; } template -constexpr T determinant(const mat &a) { +constexpr T determinant(const mat& a) { return a.x.x * (a.y.y * a.z.z - a.z.y * a.y.z) + a.x.y * (a.y.z * a.z.x - a.z.z * a.y.x) + a.x.z * (a.y.x * a.z.y - a.z.x * a.y.y); } template -constexpr T determinant(const mat &a); +constexpr T determinant(const mat& a); template -constexpr mat inverse(const mat &a) { +constexpr mat inverse(const mat& a) { return adjugate(a) / determinant(a); } /** @} */ @@ -2234,35 +2227,35 @@ constexpr mat inverse(const mat &a) { * @{ */ template -T *begin(vec &a) { +T* begin(vec& a) { return &a.x; } template -const T *begin(const vec &a) { +const T* begin(const vec& a) { return &a.x; } template -T *end(vec &a) { +T* end(vec& a) { return begin(a) + M; } template -const T *end(const vec &a) { +const T* end(const vec& a) { return begin(a) + M; } template -vec *begin(mat &a) { +vec* begin(mat& a) { return &a.x; } template -const vec *begin(const mat &a) { +const vec* begin(const mat& a) { return &a.x; } template -vec *end(mat &a) { +vec* end(mat& a) { return begin(a) + N; } template -const vec *end(const mat &a) { +const vec* end(const mat& a) { return begin(a) + N; } /** @} */ @@ -2282,30 +2275,30 @@ enum z_range { zero_to_one }; // Should projection matrices map z into the range of [-1,1] or [0,1]? template -mat translation_matrix(const vec &translation) { +mat translation_matrix(const vec& translation) { return {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {translation, 1}}; } template -mat rotation_matrix(const vec &rotation) { +mat rotation_matrix(const vec& rotation) { return {{qxdir(rotation), 0}, {qydir(rotation), 0}, {qzdir(rotation), 0}, {0, 0, 0, 1}}; } template -mat scaling_matrix(const vec &scaling) { +mat scaling_matrix(const vec& scaling) { return {{scaling.x, 0, 0, 0}, {0, scaling.y, 0, 0}, {0, 0, scaling.z, 0}, {0, 0, 0, 1}}; } template -mat pose_matrix(const vec &q, const vec &p) { +mat pose_matrix(const vec& q, const vec& p) { return {{qxdir(q), 0}, {qydir(q), 0}, {qzdir(q), 0}, {p, 1}}; } template -mat lookat_matrix(const vec &eye, const vec ¢er, - const vec &view_y_dir, fwd_axis fwd = neg_z); +mat lookat_matrix(const vec& eye, const vec& center, + const vec& view_y_dir, fwd_axis fwd = neg_z); template mat frustum_matrix(T x0, T x1, T y0, T y1, T n, T f, fwd_axis a = neg_z, z_range z = neg_one_to_one); @@ -2325,82 +2318,46 @@ mat perspective_matrix(T fovy, T aspect, T n, T f, fwd_axis a = neg_z, */ template struct converter, std::array> { - vec operator()(const std::array &a) const { return {a[0]}; } + vec operator()(const std::array& a) const { return {a[0]}; } }; template struct converter, std::array> { - vec operator()(const std::array &a) const { return {a[0], a[1]}; } + vec operator()(const std::array& a) const { return {a[0], a[1]}; } }; template struct converter, std::array> { - vec operator()(const std::array &a) const { + vec operator()(const std::array& a) const { return {a[0], a[1], a[2]}; } }; template struct converter, std::array> { - vec operator()(const std::array &a) const { + vec operator()(const std::array& a) const { return {a[0], a[1], a[2], a[3]}; } }; template struct converter, vec> { - std::array operator()(const vec &a) const { return {a[0]}; } + std::array operator()(const vec& a) const { return {a[0]}; } }; template struct converter, vec> { - std::array operator()(const vec &a) const { return {a[0], a[1]}; } + std::array operator()(const vec& a) const { return {a[0], a[1]}; } }; template struct converter, vec> { - std::array operator()(const vec &a) const { + std::array operator()(const vec& a) const { return {a[0], a[1], a[2]}; } }; template struct converter, vec> { - std::array operator()(const vec &a) const { + std::array operator()(const vec& a) const { return {a[0], a[1], a[2], a[3]}; } }; /** @} */ - -#ifdef MANIFOLD_DEBUG -template -std::ostream &operator<<(std::ostream &out, const vec &v) { - return out << '{' << v[0] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const vec &v) { - return out << '{' << v[0] << ',' << v[1] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const vec &v) { - return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const vec &v) { - return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}'; -} - -template -std::ostream &operator<<(std::ostream &out, const mat &m) { - return out << '{' << m[0] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const mat &m) { - return out << '{' << m[0] << ',' << m[1] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const mat &m) { - return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}'; -} -template -std::ostream &operator<<(std::ostream &out, const mat &m) { - return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}'; -} -#endif } // namespace linalg namespace std { @@ -2411,28 +2368,28 @@ namespace std { */ template struct hash> { - std::size_t operator()(const linalg::vec &v) const { + std::size_t operator()(const linalg::vec& v) const { std::hash h; return h(v.x); } }; template struct hash> { - std::size_t operator()(const linalg::vec &v) const { + std::size_t operator()(const linalg::vec& v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1); } }; template struct hash> { - std::size_t operator()(const linalg::vec &v) const { + std::size_t operator()(const linalg::vec& v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2); } }; template struct hash> { - std::size_t operator()(const linalg::vec &v) const { + std::size_t operator()(const linalg::vec& v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2) ^ (h(v.w) << 3); } @@ -2440,28 +2397,28 @@ struct hash> { template struct hash> { - std::size_t operator()(const linalg::mat &v) const { + std::size_t operator()(const linalg::mat& v) const { std::hash> h; return h(v.x); } }; template struct hash> { - std::size_t operator()(const linalg::mat &v) const { + std::size_t operator()(const linalg::mat& v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M); } }; template struct hash> { - std::size_t operator()(const linalg::mat &v) const { + std::size_t operator()(const linalg::mat& v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M * 2)); } }; template struct hash> { - std::size_t operator()(const linalg::mat &v) const { + std::size_t operator()(const linalg::mat& v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M * 2)) ^ (h(v.w) << (M * 3)); } @@ -2471,7 +2428,7 @@ struct hash> { // Definitions of functions too long to be defined inline template -constexpr linalg::mat linalg::adjugate(const mat &a) { +constexpr linalg::mat linalg::adjugate(const mat& a) { return {{a.y.y * a.z.z - a.z.y * a.y.z, a.z.y * a.x.z - a.x.y * a.z.z, a.x.y * a.y.z - a.y.y * a.x.z}, {a.y.z * a.z.x - a.z.z * a.y.x, a.z.z * a.x.x - a.x.z * a.z.x, @@ -2481,7 +2438,7 @@ constexpr linalg::mat linalg::adjugate(const mat &a) { } template -constexpr linalg::mat linalg::adjugate(const mat &a) { +constexpr linalg::mat linalg::adjugate(const mat& a) { return {{a.y.y * a.z.z * a.w.w + a.w.y * a.y.z * a.z.w + a.z.y * a.w.z * a.y.w - a.y.y * a.w.z * a.z.w - a.z.y * a.y.z * a.w.w - a.w.y * a.z.z * a.y.w, @@ -2533,7 +2490,7 @@ constexpr linalg::mat linalg::adjugate(const mat &a) { } template -constexpr T linalg::determinant(const mat &a) { +constexpr T linalg::determinant(const mat& a) { return a.x.x * (a.y.y * a.z.z * a.w.w + a.w.y * a.y.z * a.z.w + a.z.y * a.w.z * a.y.w - a.y.y * a.w.z * a.z.w - a.z.y * a.y.z * a.w.w - a.w.y * a.z.z * a.y.w) + @@ -2549,8 +2506,8 @@ constexpr T linalg::determinant(const mat &a) { } template -linalg::vec linalg::rotation_quat(const vec &orig, - const vec &dest) { +linalg::vec linalg::rotation_quat(const vec& orig, + const vec& dest) { T cosTheta = dot(orig, dest); if (cosTheta >= 1 - std::numeric_limits::epsilon()) { return {0, 0, 0, 1}; @@ -2568,7 +2525,7 @@ linalg::vec linalg::rotation_quat(const vec &orig, } template -linalg::vec linalg::rotation_quat(const mat &m) { +linalg::vec linalg::rotation_quat(const mat& m) { const vec q{m.x.x - m.y.y - m.z.z, m.y.y - m.x.x - m.z.z, m.z.z - m.x.x - m.y.y, m.x.x + m.y.y + m.z.z}, s[]{{1, m.x.y + m.y.x, m.z.x + m.x.z, m.y.z - m.z.y}, @@ -2579,9 +2536,9 @@ linalg::vec linalg::rotation_quat(const mat &m) { } template -linalg::mat linalg::lookat_matrix(const vec &eye, - const vec ¢er, - const vec &view_y_dir, +linalg::mat linalg::lookat_matrix(const vec& eye, + const vec& center, + const vec& view_y_dir, fwd_axis a) { const vec f = normalize(center - eye), z = a == pos_z ? f : -f, x = normalize(cross(view_y_dir, z)), y = cross(z, x); diff --git a/thirdparty/manifold/include/manifold/manifold.h b/thirdparty/manifold/include/manifold/manifold.h index 9c59d7367a..8764771a01 100644 --- a/thirdparty/manifold/include/manifold/manifold.h +++ b/thirdparty/manifold/include/manifold/manifold.h @@ -13,12 +13,9 @@ // limitations under the License. #pragma once +#include // uint32_t, uint64_t #include -#include - -#ifdef MANIFOLD_EXPORT -#include -#endif +#include // needed for shared_ptr #include "manifold/common.h" #include "manifold/vec_view.h" @@ -46,11 +43,71 @@ class CsgLeafNode; * @brief Mesh input/output suitable for pushing directly into graphics * libraries. * - * This may not be manifold since the verts are duplicated along property - * boundaries that do not match. The additional merge vectors store this missing - * information, allowing the manifold to be reconstructed. MeshGL is an alias - * for the standard single-precision version. Use MeshGL64 to output the full - * double precision that Manifold uses internally. + * The core (non-optional) parts of MeshGL are the triVerts indices buffer and + * the vertProperties interleaved vertex buffer, which follow the conventions of + * OpenGL (and other graphic libraries') buffers and are therefore generally + * easy to map directly to other applications' data structures. + * + * The triVerts vector has a stride of 3 and specifies triangles as + * vertex indices. For triVerts = [2, 4, 5, 3, 1, 6, ...], the triangles are [2, + * 4, 5], [3, 1, 6], etc. and likewise the halfedges are [2, 4], [4, 5], [5, 2], + * [3, 1], [1, 6], [6, 3], etc. + * + * The triVerts indices should form a manifold mesh: each of the 3 halfedges of + * each triangle should have exactly one paired halfedge in the list, defined as + * having the first index of one equal to the second index of the other and + * vice-versa. However, this is not always possible - consider e.g. a cube with + * normal-vector properties. Shared vertices would turn the cube into a ball by + * interpolating normals - the common solution is to duplicate each corner + * vertex into 3, each with the same position, but different normals + * corresponding to each face. This is exactly what should be done in MeshGL, + * however we request two additional vectors in this case: mergeFromVert and + * mergeToVert. Each vertex mergeFromVert[i] is merged into vertex + * mergeToVert[i], avoiding unreliable floating-point comparisons to recover the + * manifold topology. These merges are simply a union, so which is from and to + * doesn't matter. + * + * If you don't have merge vectors, you can create them with the Merge() method, + * however this will fail if the mesh is not already manifold within the set + * tolerance. For maximum reliablility, always store the merge vectors with the + * mesh, e.g. using the EXT_mesh_manifold extension in glTF. + * + * You can have any number of arbitrary floating-point properties per vertex, + * and they will all be interpolated as necessary during operations. It is up to + * you to keep track of which channel represents what type of data. A few of + * Manifold's methods allow you to specify the channel where normals data + * starts, in order to update it automatically for transforms and such. This + * will be easier if your meshes all use the same channels for properties, but + * this is not a requirement. Operations between meshes with different numbers + * of peroperties will simply use the larger numProp and pad the smaller one + * with zeroes. + * + * On output, the triangles are sorted into runs (runIndex, runOriginalID, + * runTransform) that correspond to different mesh inputs. Other 3D libraries + * may refer to these runs as primitives of a mesh (as in glTF) or draw calls, + * as they often represent different materials on different parts of the mesh. + * It is generally a good idea to maintain a map of OriginalIDs to materials to + * make it easy to reapply them after a set of Boolean operations. These runs + * can also be used as input, and thus also ensure a lossless roundtrip of data + * through MeshGL. + * + * As an example, with runIndex = [0, 6, 18, 21] and runOriginalID = [1, 3, 3], + * there are 7 triangles, where the first two are from the input mesh with ID 1, + * the next 4 are from an input mesh with ID 3, and the last triangle is from a + * different copy (instance) of the input mesh with ID 3. These two instances + * can be distinguished by their different runTransform matrices. + * + * You can reconstruct polygonal faces by assembling all the triangles that are + * from the same run and share the same faceID. These faces will be planar + * within the output tolerance. + * + * The halfedgeTangent vector is used to specify the weighted tangent vectors of + * each halfedge for the purpose of using the Refine methods to create a + * smoothly-interpolated surface. They can also be output when calculated + * automatically by the Smooth functions. + * + * MeshGL is an alias for the standard single-precision version. Use MeshGL64 to + * output the full double precision that Manifold uses internally. */ template struct MeshGLP { @@ -62,7 +119,7 @@ struct MeshGLP { I numProp = 3; /// Flat, GL-style interleaved list of all vertex properties: propVal = /// vertProperties[vert * numProp + propIdx]. The first three properties are - /// always the position x, y, z. + /// always the position x, y, z. The stride of the array is numProp. std::vector vertProperties; /// The vertex indices of the three triangle corners in CCW (from the outside) /// order, for each triangle. @@ -93,10 +150,11 @@ struct MeshGLP { /// This matrix is stored in column-major order and the length of the overall /// vector is 12 * runOriginalID.size(). std::vector runTransform; - /// Optional: Length NumTri, contains the source face ID this - /// triangle comes from. When auto-generated, this ID will be a triangle index - /// into the original mesh. This index/ID is purely for external use (e.g. - /// recreating polygonal faces) and will not affect Manifold's algorithms. + /// Optional: Length NumTri, contains the source face ID this triangle comes + /// from. Simplification will maintain all edges between triangles with + /// different faceIDs. Input faceIDs will be maintained to the outputs, but if + /// none are given, they will be filled in with Manifold's coplanar face + /// calculation based on mesh tolerance. std::vector faceID; /// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If /// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed @@ -263,6 +321,7 @@ class Manifold { RunIndexWrongLength, FaceIDWrongLength, InvalidConstruction, + ResultTooLarge, }; /** @name Information @@ -311,6 +370,7 @@ class Manifold { Manifold Warp(std::function) const; Manifold WarpBatch(std::function)>) const; Manifold SetTolerance(double) const; + Manifold Simplify(double tolerance = 0) const; ///@} /** @name Boolean @@ -368,22 +428,68 @@ class Manifold { static Manifold Hull(const std::vector& pts); ///@} + /** @name Debugging I/O + * Self-contained mechanism for reading and writing high precision Manifold + * data. Write function creates special-purpose OBJ files, and Read function + * reads them in. Be warned these are not (and not intended to be) + * full-featured OBJ importers/exporters. Their primary use is to extract + * accurate Manifold data for debugging purposes - writing out any info + * needed to accurately reproduce a problem case's state. Consequently, they + * may store and process additional data in comments that other OBJ parsing + * programs won't understand. + * + * The "format" read and written by these functions is not guaranteed to be + * stable from release to release - it will be modified as needed to ensure + * it captures information needed for debugging. The only API guarantee is + * that the ReadOBJ method in a given build/release will read in the output + * of the WriteOBJ method produced by that release. + * + * To work with a file, the caller should prepare the ifstream/ostream + * themselves, as follows: + * + * Reading: + * @code + * std::ifstream ifile; + * ifile.open(filename); + * if (ifile.is_open()) { + * Manifold obj_m = Manifold::ReadOBJ(ifile); + * ifile.close(); + * if (obj_m.Status() != Manifold::Error::NoError) { + * std::cerr << "Failed reading " << filename << ":\n"; + * std::cerr << Manifold::ToString(ob_m.Status()) << "\n"; + * } + * ifile.close(); + * } + * @endcode + * + * Writing: + * @code + * std::ofstream ofile; + * ofile.open(filename); + * if (ofile.is_open()) { + * if (!m.WriteOBJ(ofile)) { + * std::cerr << "Failed writing to " << filename << "\n"; + * } + * } + * ofile.close(); + * @endcode + */ +#ifdef MANIFOLD_DEBUG + static Manifold ReadOBJ(std::istream& stream); + bool WriteOBJ(std::ostream& stream) const; +#endif + /** @name Testing Hooks * These are just for internal testing. */ ///@{ bool MatchesTriNormals() const; size_t NumDegenerateTris() const; - size_t NumOverlaps(const Manifold& second) const; double GetEpsilon() const; ///@} struct Impl; -#ifdef MANIFOLD_EXPORT - static Manifold ImportMeshGL64(std::istream& stream); -#endif - private: Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); @@ -429,6 +535,8 @@ inline std::string ToString(const Manifold::Error& error) { return "Face ID Wrong Length"; case Manifold::Error::InvalidConstruction: return "Invalid Construction"; + case Manifold::Error::ResultTooLarge: + return "Result Too Large"; default: return "Unknown Error"; }; diff --git a/thirdparty/manifold/include/manifold/optional_assert.h b/thirdparty/manifold/include/manifold/optional_assert.h index 905746cca3..d08a6318fc 100644 --- a/thirdparty/manifold/include/manifold/optional_assert.h +++ b/thirdparty/manifold/include/manifold/optional_assert.h @@ -15,6 +15,7 @@ #pragma once #ifdef MANIFOLD_DEBUG +#include #include #include #include @@ -33,6 +34,9 @@ struct geometryErr : public virtual std::runtime_error { using std::runtime_error::runtime_error; }; using logicErr = std::logic_error; +#endif + +#if defined(MANIFOLD_ASSERT) && defined(MANIFOLD_DEBUG) template void AssertFail(const char* file, int line, const char* cond, const char* msg) { diff --git a/thirdparty/manifold/include/manifold/polygon.h b/thirdparty/manifold/include/manifold/polygon.h index 2902524ce4..35968ad045 100644 --- a/thirdparty/manifold/include/manifold/polygon.h +++ b/thirdparty/manifold/include/manifold/polygon.h @@ -52,11 +52,10 @@ using PolygonsIdx = std::vector; * @brief Polygon triangulation * @{ */ -std::vector TriangulateIdx(const PolygonsIdx &polys, - double epsilon = -1); +std::vector TriangulateIdx(const PolygonsIdx& polys, double epsilon = -1, + bool allowConvex = true); -std::vector Triangulate(const Polygons &polygons, double epsilon = -1); - -ExecutionParams &PolygonParams(); +std::vector Triangulate(const Polygons& polygons, double epsilon = -1, + bool allowConvex = true); /** @} */ } // namespace manifold diff --git a/thirdparty/manifold/include/manifold/vec_view.h b/thirdparty/manifold/include/manifold/vec_view.h index 4dc5f73445..b5fcc8288e 100644 --- a/thirdparty/manifold/include/manifold/vec_view.h +++ b/thirdparty/manifold/include/manifold/vec_view.h @@ -31,22 +31,22 @@ namespace manifold { template class VecView { public: - using Iter = T *; - using IterC = const T *; + using Iter = T*; + using IterC = const T*; VecView() : ptr_(nullptr), size_(0) {} - VecView(T *ptr, size_t size) : ptr_(ptr), size_(size) {} + VecView(T* ptr, size_t size) : ptr_(ptr), size_(size) {} - VecView(const std::vector> &v) + VecView(const std::vector>& v) : ptr_(v.data()), size_(v.size()) {} - VecView(const VecView &other) { + VecView(const VecView& other) { ptr_ = other.ptr_; size_ = other.size_; } - VecView &operator=(const VecView &other) { + VecView& operator=(const VecView& other) { ptr_ = other.ptr_; size_ = other.size_; return *this; @@ -55,12 +55,12 @@ class VecView { // allows conversion to a const VecView operator VecView() const { return {ptr_, size_}; } - inline const T &operator[](size_t i) const { + inline const T& operator[](size_t i) const { ASSERT(i < size_, std::out_of_range("Vec out of range")); return ptr_[i]; } - inline T &operator[](size_t i) { + inline T& operator[](size_t i) { ASSERT(i < size_, std::out_of_range("Vec out of range")); return ptr_[i]; } @@ -74,25 +74,25 @@ class VecView { Iter begin() { return ptr_; } Iter end() { return ptr_ + size_; } - const T &front() const { + const T& front() const { ASSERT(size_ != 0, std::out_of_range("Attempt to take the front of an empty vector")); return ptr_[0]; } - const T &back() const { + const T& back() const { ASSERT(size_ != 0, std::out_of_range("Attempt to take the back of an empty vector")); return ptr_[size_ - 1]; } - T &front() { + T& front() { ASSERT(size_ != 0, std::out_of_range("Attempt to take the front of an empty vector")); return ptr_[0]; } - T &back() { + T& back() { ASSERT(size_ != 0, std::out_of_range("Attempt to take the back of an empty vector")); return ptr_[size_ - 1]; @@ -106,8 +106,7 @@ class VecView { size_t length = std::numeric_limits::max()) { if (length == std::numeric_limits::max()) length = this->size_ - offset; - ASSERT(length >= 0, std::out_of_range("Vec::view out of range")); - ASSERT(offset + length <= this->size_ && offset >= 0, + ASSERT(offset + length <= this->size_, std::out_of_range("Vec::view out of range")); return VecView(this->ptr_ + offset, length); } @@ -117,8 +116,7 @@ class VecView { size_t length = std::numeric_limits::max()) const { if (length == std::numeric_limits::max()) length = this->size_ - offset; - ASSERT(length >= 0, std::out_of_range("Vec::cview out of range")); - ASSERT(offset + length <= this->size_ && offset >= 0, + ASSERT(offset + length <= this->size_, std::out_of_range("Vec::cview out of range")); return VecView(this->ptr_ + offset, length); } @@ -129,9 +127,9 @@ class VecView { return cview(offset, length); } - T *data() { return this->ptr_; } + T* data() { return this->ptr_; } - const T *data() const { return this->ptr_; } + const T* data() const { return this->ptr_; } #ifdef MANIFOLD_DEBUG void Dump() const { @@ -144,7 +142,7 @@ class VecView { #endif protected: - T *ptr_ = nullptr; + T* ptr_ = nullptr; size_t size_ = 0; }; diff --git a/thirdparty/manifold/src/boolean3.cpp b/thirdparty/manifold/src/boolean3.cpp index 1fca99db45..ba92f77ae4 100644 --- a/thirdparty/manifold/src/boolean3.cpp +++ b/thirdparty/manifold/src/boolean3.cpp @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./boolean3.h" +#include "boolean3.h" #include -#include "./parallel.h" +#include "parallel.h" + +#if (MANIFOLD_PAR == 1) +#include +#endif using namespace manifold; @@ -38,12 +42,12 @@ vec2 Interpolate(vec3 pL, vec3 pR, double x) { if (!std::isfinite(lambda) || !std::isfinite(dLR.y) || !std::isfinite(dLR.z)) return vec2(pL.y, pL.z); vec2 yz; - yz[0] = fma(lambda, dLR.y, useL ? pL.y : pR.y); - yz[1] = fma(lambda, dLR.z, useL ? pL.z : pR.z); + yz[0] = lambda * dLR.y + (useL ? pL.y : pR.y); + yz[1] = lambda * dLR.z + (useL ? pL.z : pR.z); return yz; } -vec4 Intersect(const vec3 &pL, const vec3 &pR, const vec3 &qL, const vec3 &qR) { +vec4 Intersect(const vec3& pL, const vec3& pR, const vec3& qL, const vec3& qR) { const double dyL = qL.y - pL.y; const double dyR = qR.y - pR.y; DEBUG_ASSERT(dyL * dyR <= 0, logicErr, @@ -53,53 +57,17 @@ vec4 Intersect(const vec3 &pL, const vec3 &pR, const vec3 &qL, const vec3 &qR) { double lambda = (useL ? dyL : dyR) / (dyL - dyR); if (!std::isfinite(lambda)) lambda = 0.0; vec4 xyzz; - xyzz.x = fma(lambda, dx, useL ? pL.x : pR.x); + xyzz.x = lambda * dx + (useL ? pL.x : pR.x); const double pDy = pR.y - pL.y; const double qDy = qR.y - qL.y; const bool useP = fabs(pDy) < fabs(qDy); - xyzz.y = fma(lambda, useP ? pDy : qDy, - useL ? (useP ? pL.y : qL.y) : (useP ? pR.y : qR.y)); - xyzz.z = fma(lambda, pR.z - pL.z, useL ? pL.z : pR.z); - xyzz.w = fma(lambda, qR.z - qL.z, useL ? qL.z : qR.z); + xyzz.y = lambda * (useP ? pDy : qDy) + + (useL ? (useP ? pL.y : qL.y) : (useP ? pR.y : qR.y)); + xyzz.z = lambda * (pR.z - pL.z) + (useL ? pL.z : pR.z); + xyzz.w = lambda * (qR.z - qL.z) + (useL ? qL.z : qR.z); return xyzz; } -template -struct CopyFaceEdges { - const SparseIndices &p1q1; - // x can be either vert or edge (0 or 1). - SparseIndices &pXq1; - VecView halfedgesQ; - const size_t offset; - - void operator()(const size_t i) { - int idx = 3 * (i + offset); - int pX = p1q1.Get(i, inverted); - int q2 = p1q1.Get(i, !inverted); - - for (const int j : {0, 1, 2}) { - const int q1 = 3 * q2 + j; - const Halfedge edge = halfedgesQ[q1]; - int a = pX; - int b = edge.IsForward() ? q1 : edge.pairedHalfedge; - if (inverted) std::swap(a, b); - pXq1.Set(idx + static_cast(j), a, b); - } - } -}; - -SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ, - const SparseIndices &p1q2, const SparseIndices &p2q1) { - ZoneScoped; - SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size()); - for_each_n(autoPolicy(p1q2.size(), 1e5), countAt(0_uz), p1q2.size(), - CopyFaceEdges({p1q2, p1q1, inQ.halfedge_, 0_uz})); - for_each_n(autoPolicy(p2q1.size(), 1e5), countAt(0_uz), p2q1.size(), - CopyFaceEdges({p2q1, p1q1, inP.halfedge_, p1q2.size()})); - p1q1.Unique(); - return p1q1; -} - inline bool Shadows(double p, double q, double dir) { return p == q ? dir < 0 : p < q; } @@ -107,16 +75,16 @@ inline bool Shadows(double p, double q, double dir) { inline std::pair Shadow01( const int p0, const int q1, VecView vertPosP, VecView vertPosQ, VecView halfedgeQ, - const double expandP, VecView normalP, const bool reverse) { + const double expandP, VecView normal, const bool reverse) { const int q1s = halfedgeQ[q1].startVert; const int q1e = halfedgeQ[q1].endVert; const double p0x = vertPosP[p0].x; const double q1sx = vertPosQ[q1s].x; const double q1ex = vertPosQ[q1e].x; - int s01 = reverse ? Shadows(q1sx, p0x, expandP * normalP[q1s].x) - - Shadows(q1ex, p0x, expandP * normalP[q1e].x) - : Shadows(p0x, q1ex, expandP * normalP[p0].x) - - Shadows(p0x, q1sx, expandP * normalP[p0].x); + int s01 = reverse ? Shadows(q1sx, p0x, expandP * normal[q1s].x) - + Shadows(q1ex, p0x, expandP * normal[q1e].x) + : Shadows(p0x, q1ex, expandP * normal[p0].x) - + Shadows(p0x, q1sx, expandP * normal[p0].x); vec2 yz01(NAN); if (s01 != 0) { @@ -126,70 +94,26 @@ inline std::pair Shadow01( const double start2 = la::dot(diff, diff); diff = vertPosQ[q1e] - vertPosP[p0]; const double end2 = la::dot(diff, diff); - const double dir = start2 < end2 ? normalP[q1s].y : normalP[q1e].y; + const double dir = start2 < end2 ? normal[q1s].y : normal[q1e].y; if (!Shadows(yz01[0], vertPosP[p0].y, expandP * dir)) s01 = 0; } else { - if (!Shadows(vertPosP[p0].y, yz01[0], expandP * normalP[p0].y)) s01 = 0; + if (!Shadows(vertPosP[p0].y, yz01[0], expandP * normal[p0].y)) s01 = 0; } } return std::make_pair(s01, yz01); } -// https://github.com/scandum/binary_search/blob/master/README.md -// much faster than standard binary search on large arrays -size_t monobound_quaternary_search(VecView array, int64_t key) { - if (array.size() == 0) { - return std::numeric_limits::max(); - } - size_t bot = 0; - size_t top = array.size(); - while (top >= 65536) { - size_t mid = top / 4; - top -= mid * 3; - if (key < array[bot + mid * 2]) { - if (key >= array[bot + mid]) { - bot += mid; - } - } else { - bot += mid * 2; - if (key >= array[bot + mid]) { - bot += mid; - } - } - } - - while (top > 3) { - size_t mid = top / 2; - if (key >= array[bot + mid]) { - bot += mid; - } - top -= mid; - } - - while (top--) { - if (key == array[bot + top]) { - return bot + top; - } - } - return -1; -} - struct Kernel11 { - VecView xyzz; - VecView s; VecView vertPosP; VecView vertPosQ; VecView halfedgeP; VecView halfedgeQ; const double expandP; VecView normalP; - const SparseIndices &p1q1; - void operator()(const size_t idx) { - const int p1 = p1q1.Get(idx, false); - const int q1 = p1q1.Get(idx, true); - vec4 &xyzz11 = xyzz[idx]; - int &s11 = s[idx]; + std::pair operator()(int p1, int q1) { + vec4 xyzz11 = vec4(NAN); + int s11 = 0; // For pRL[k], qRL[k], k==0 is the left and k==1 is the right. int k = 0; @@ -201,10 +125,8 @@ struct Kernel11 { const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert}; for (int i : {0, 1}) { - const auto syz01 = Shadow01(p0[i], q1, vertPosP, vertPosQ, halfedgeQ, - expandP, normalP, false); - const int s01 = syz01.first; - const vec2 yz01 = syz01.second; + const auto [s01, yz01] = Shadow01(p0[i], q1, vertPosP, vertPosQ, + halfedgeQ, expandP, normalP, false); // If the value is NaN, then these do not overlap. if (std::isfinite(yz01[0])) { s11 += s01 * (i == 0 ? -1 : 1); @@ -219,10 +141,8 @@ struct Kernel11 { const int q0[2] = {halfedgeQ[q1].startVert, halfedgeQ[q1].endVert}; for (int i : {0, 1}) { - const auto syz10 = Shadow01(q0[i], p1, vertPosQ, vertPosP, halfedgeP, - expandP, normalP, true); - const int s10 = syz10.first; - const vec2 yz10 = syz10.second; + const auto [s10, yz10] = Shadow01(q0[i], p1, vertPosQ, vertPosP, + halfedgeP, expandP, normalP, true); // If the value is NaN, then these do not overlap. if (std::isfinite(yz10[0])) { s11 += s10 * (i == 0 ? -1 : 1); @@ -251,42 +171,22 @@ struct Kernel11 { if (!Shadows(xyzz11.z, xyzz11.w, expandP * dir)) s11 = 0; } + + return std::make_pair(s11, xyzz11); } }; -std::tuple, Vec> Shadow11(SparseIndices &p1q1, - const Manifold::Impl &inP, - const Manifold::Impl &inQ, - double expandP) { - ZoneScoped; - Vec s11(p1q1.size()); - Vec xyzz11(p1q1.size()); - - for_each_n(autoPolicy(p1q1.size(), 1e4), countAt(0_uz), p1q1.size(), - Kernel11({xyzz11, s11, inP.vertPos_, inQ.vertPos_, inP.halfedge_, - inQ.halfedge_, expandP, inP.vertNormal_, p1q1})); - - p1q1.KeepFinite(xyzz11, s11); - - return std::make_tuple(s11, xyzz11); -}; - struct Kernel02 { - VecView s; - VecView z; VecView vertPosP; VecView halfedgeQ; VecView vertPosQ; const double expandP; VecView vertNormalP; - const SparseIndices &p0q2; const bool forward; - void operator()(const size_t idx) { - const int p0 = p0q2.Get(idx, !forward); - const int q2 = p0q2.Get(idx, forward); - int &s02 = s[idx]; - double &z02 = z[idx]; + std::pair operator()(int p0, int q2) { + int s02 = 0; + double z02 = 0.0; // For yzzLR[k], k==0 is the left and k==1 is the right. int k = 0; @@ -342,47 +242,21 @@ struct Kernel02 { s02 = 0; } } + return std::make_pair(s02, z02); } }; -std::tuple, Vec> Shadow02(const Manifold::Impl &inP, - const Manifold::Impl &inQ, - SparseIndices &p0q2, bool forward, - double expandP) { - ZoneScoped; - Vec s02(p0q2.size()); - Vec z02(p0q2.size()); - - auto vertNormalP = forward ? inP.vertNormal_ : inQ.vertNormal_; - for_each_n(autoPolicy(p0q2.size(), 1e4), countAt(0_uz), p0q2.size(), - Kernel02({s02, z02, inP.vertPos_, inQ.halfedge_, inQ.vertPos_, - expandP, vertNormalP, p0q2, forward})); - - p0q2.KeepFinite(z02, s02); - - return std::make_tuple(s02, z02); -}; - struct Kernel12 { - VecView x; - VecView v; - VecView p0q2; - VecView s02; - VecView z02; - VecView p1q1; - VecView s11; - VecView xyzz11; VecView halfedgesP; VecView halfedgesQ; VecView vertPosP; const bool forward; - const SparseIndices &p1q2; + Kernel02 k02; + Kernel11 k11; - void operator()(const size_t idx) { - int p1 = p1q2.Get(idx, !forward); - int q2 = p1q2.Get(idx, forward); - int &x12 = x[idx]; - vec3 &v12 = v[idx]; + std::pair operator()(int p1, int q2) { + int x12 = 0; + vec3 v12 = vec3(NAN); // For xzyLR-[k], k==0 is the left and k==1 is the right. int k = 0; @@ -396,18 +270,15 @@ struct Kernel12 { const Halfedge edge = halfedgesP[p1]; for (int vert : {edge.startVert, edge.endVert}) { - const int64_t key = forward ? SparseIndices::EncodePQ(vert, q2) - : SparseIndices::EncodePQ(q2, vert); - const size_t idx = monobound_quaternary_search(p0q2, key); - if (idx != std::numeric_limits::max()) { - const int s = s02[idx]; + const auto [s, z] = k02(vert, q2); + if (std::isfinite(z)) { x12 += s * ((vert == edge.startVert) == forward ? 1 : -1); if (k < 2 && (k == 0 || (s != 0) != shadows)) { shadows = s != 0; xzyLR0[k] = vertPosP[vert]; std::swap(xzyLR0[k].y, xzyLR0[k].z); xzyLR1[k] = xzyLR0[k]; - xzyLR1[k][1] = z02[idx]; + xzyLR1[k][1] = z; k++; } } @@ -417,17 +288,11 @@ struct Kernel12 { const int q1 = 3 * q2 + i; const Halfedge edge = halfedgesQ[q1]; const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge; - const int64_t key = forward ? SparseIndices::EncodePQ(p1, q1F) - : SparseIndices::EncodePQ(q1F, p1); - const size_t idx = monobound_quaternary_search(p1q1, key); - if (idx != - std::numeric_limits::max()) { // s is implicitly zero for - // anything not found - const int s = s11[idx]; + const auto [s, xyzz] = forward ? k11(p1, q1F) : k11(q1F, p1); + if (std::isfinite(xyzz[0])) { x12 -= s * (edge.IsForward() ? 1 : -1); if (k < 2 && (k == 0 || (s != 0) != shadows)) { shadows = s != 0; - const vec4 xyzz = xyzz11[idx]; xzyLR0[k][0] = xyzz.x; xzyLR0[k][1] = xyzz.z; xzyLR0[k][2] = xyzz.y; @@ -448,60 +313,146 @@ struct Kernel12 { v12.y = xzyy[2]; v12.z = xzyy[1]; } + return std::make_pair(x12, v12); } }; -std::tuple, Vec> Intersect12( - const Manifold::Impl &inP, const Manifold::Impl &inQ, const Vec &s02, - const SparseIndices &p0q2, const Vec &s11, const SparseIndices &p1q1, - const Vec &z02, const Vec &xyzz11, SparseIndices &p1q2, - bool forward) { +struct Kernel12Tmp { + Vec> p1q2_; + Vec x12_; + Vec v12_; +}; + +struct Kernel12Recorder { + using Local = Kernel12Tmp; + Kernel12& k12; + VecView tmpedges; + bool forward; + +#if MANIFOLD_PAR == 1 + tbb::combinable store; + Local& local() { return store.local(); } +#else + Kernel12Tmp localStore; + Local& local() { return localStore; } +#endif + + void record(int queryIdx, int leafIdx, Local& tmp) { + queryIdx = tmpedges[queryIdx].halfedgeIdx; + const auto [x12, v12] = k12(queryIdx, leafIdx); + if (std::isfinite(v12[0])) { + if (forward) + tmp.p1q2_.push_back({queryIdx, leafIdx}); + else + tmp.p1q2_.push_back({leafIdx, queryIdx}); + tmp.x12_.push_back(x12); + tmp.v12_.push_back(v12); + } + } + + Kernel12Tmp get() { +#if MANIFOLD_PAR == 1 + Kernel12Tmp result; + std::vector tmps; + store.combine_each( + [&](Kernel12Tmp& data) { tmps.emplace_back(std::move(data)); }); + std::vector sizes; + size_t total_size = 0; + for (const auto& tmp : tmps) { + sizes.push_back(total_size); + total_size += tmp.x12_.size(); + } + result.p1q2_.resize(total_size); + result.x12_.resize(total_size); + result.v12_.resize(total_size); + for_each_n(ExecutionPolicy::Seq, countAt(0), tmps.size(), [&](size_t i) { + std::copy(tmps[i].p1q2_.begin(), tmps[i].p1q2_.end(), + result.p1q2_.begin() + sizes[i]); + std::copy(tmps[i].x12_.begin(), tmps[i].x12_.end(), + result.x12_.begin() + sizes[i]); + std::copy(tmps[i].v12_.begin(), tmps[i].v12_.end(), + result.v12_.begin() + sizes[i]); + }); + return result; +#else + return localStore; +#endif + } +}; + +std::tuple, Vec> Intersect12(const Manifold::Impl& inP, + const Manifold::Impl& inQ, + Vec>& p1q2, + double expandP, bool forward) { ZoneScoped; - Vec x12(p1q2.size()); - Vec v12(p1q2.size()); + // a: 1 (edge), b: 2 (face) + const Manifold::Impl& a = forward ? inP : inQ; + const Manifold::Impl& b = forward ? inQ : inP; - for_each_n( - autoPolicy(p1q2.size(), 1e4), countAt(0_uz), p1q2.size(), - Kernel12({x12, v12, p0q2.AsVec64(), s02, z02, p1q1.AsVec64(), s11, xyzz11, - inP.halfedge_, inQ.halfedge_, inP.vertPos_, forward, p1q2})); + Kernel02 k02{a.vertPos_, b.halfedge_, b.vertPos_, + expandP, inP.vertNormal_, forward}; + Kernel11 k11{inP.vertPos_, inQ.vertPos_, inP.halfedge_, + inQ.halfedge_, expandP, inP.vertNormal_}; - p1q2.KeepFinite(v12, x12); + Vec tmpedges = CreateTmpEdges(a.halfedge_); + Vec AEdgeBB(tmpedges.size()); + for_each_n(autoPolicy(tmpedges.size(), 1e5), countAt(0), tmpedges.size(), + [&](const int e) { + AEdgeBB[e] = Box(a.vertPos_[tmpedges[e].first], + a.vertPos_[tmpedges[e].second]); + }); + Kernel12 k12{a.halfedge_, b.halfedge_, a.vertPos_, forward, k02, k11}; + Kernel12Recorder recorder{k12, tmpedges, forward, {}}; + b.collider_.Collisions(AEdgeBB.cview(), + recorder); + + Kernel12Tmp result = recorder.get(); + p1q2 = std::move(result.p1q2_); + auto x12 = std::move(result.x12_); + auto v12 = std::move(result.v12_); + // sort p1q2 + Vec i12(p1q2.size()); + sequence(i12.begin(), i12.end()); + stable_sort(i12.begin(), i12.end(), [&](int a, int b) { + return p1q2[a][0] < p1q2[b][0] || + (p1q2[a][0] == p1q2[b][0] && p1q2[a][1] < p1q2[b][1]); + }); + Permute(p1q2, i12); + Permute(x12, i12); + Permute(v12, i12); return std::make_tuple(x12, v12); }; -Vec Winding03(const Manifold::Impl &inP, Vec &vertices, Vec &s02, - bool reverse) { +Vec Winding03(const Manifold::Impl& inP, const Manifold::Impl& inQ, + double expandP, bool forward) { ZoneScoped; // verts that are not shadowed (not in p0q2) have winding number zero. - Vec w03(inP.NumVert(), 0); - if (vertices.size() <= 1e5) { - for_each_n(ExecutionPolicy::Seq, countAt(0), s02.size(), - [&w03, &vertices, &s02, reverse](const int i) { - w03[vertices[i]] += s02[i] * (reverse ? -1 : 1); - }); - } else { - for_each_n(ExecutionPolicy::Par, countAt(0), s02.size(), - [&w03, &vertices, &s02, reverse](const int i) { - AtomicAdd(w03[vertices[i]], s02[i] * (reverse ? -1 : 1)); - }); - } + // a: 0 (vertex), b: 2 (face) + const Manifold::Impl& a = forward ? inP : inQ; + const Manifold::Impl& b = forward ? inQ : inP; + Vec w03(a.NumVert(), 0); + Kernel02 k02{a.vertPos_, b.halfedge_, b.vertPos_, + expandP, inP.vertNormal_, forward}; + auto f = [&](int a, int b) { + const auto [s02, z02] = k02(a, b); + if (std::isfinite(z02)) AtomicAdd(w03[a], s02 * (!forward ? -1 : 1)); + }; + auto recorder = MakeSimpleRecorder(f); + b.collider_.Collisions(a.vertPos_.cview(), recorder); return w03; }; } // namespace namespace manifold { -Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ, +Boolean3::Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ, OpType op) : inP_(inP), inQ_(inQ), expandP_(op == OpType::Add ? 1.0 : -1.0) { // Symbolic perturbation: // Union -> expand inP // Difference, Intersection -> contract inP - -#ifdef MANIFOLD_DEBUG - Timer broad; - broad.Start(); -#endif + constexpr size_t INT_MAX_SZ = + static_cast(std::numeric_limits::max()); if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) { PRINT("No overlap, early out"); @@ -510,88 +461,34 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ, return; } - // Level 3 - // Find edge-triangle overlaps (broad phase) - p1q2_ = inQ_.EdgeCollisions(inP_); - p2q1_ = inP_.EdgeCollisions(inQ_, true); // inverted - - p1q2_.Sort(); - PRINT("p1q2 size = " << p1q2_.size()); - - p2q1_.Sort(); - PRINT("p2q1 size = " << p2q1_.size()); - - // Level 2 - // Find vertices that overlap faces in XY-projection - SparseIndices p0q2 = inQ.VertexCollisionsZ(inP.vertPos_); - p0q2.Sort(); - PRINT("p0q2 size = " << p0q2.size()); - - SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_, true); // inverted - p2q0.Sort(); - PRINT("p2q0 size = " << p2q0.size()); - - // Find involved edge pairs from Level 3 - SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_); - PRINT("p1q1 size = " << p1q1.size()); - #ifdef MANIFOLD_DEBUG - broad.Stop(); Timer intersections; intersections.Start(); #endif - // Level 2 - // Build up XY-projection intersection of two edges, including the z-value for - // each edge, keeping only those whose intersection exists. - Vec s11; - Vec xyzz11; - std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_); - PRINT("s11 size = " << s11.size()); - - // Build up Z-projection of vertices onto triangles, keeping only those that - // fall inside the triangle. - Vec s02; - Vec z02; - std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_); - PRINT("s02 size = " << s02.size()); - - Vec s20; - Vec z20; - std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_); - PRINT("s20 size = " << s20.size()); - // Level 3 // Build up the intersection of the edges and triangles, keeping only those // that intersect, and record the direction the edge is passing through the // triangle. - std::tie(x12_, v12_) = - Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true); + std::tie(x12_, v12_) = Intersect12(inP, inQ, p1q2_, expandP_, true); PRINT("x12 size = " << x12_.size()); - std::tie(x21_, v21_) = - Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false); + std::tie(x21_, v21_) = Intersect12(inP, inQ, p2q1_, expandP_, false); PRINT("x21 size = " << x21_.size()); - s11.clear(); - xyzz11.clear(); - z02.clear(); - z20.clear(); + if (x12_.size() > INT_MAX_SZ || x21_.size() > INT_MAX_SZ) { + valid = false; + return; + } - Vec p0 = p0q2.Copy(false); - p0q2.Resize(0); - Vec q0 = p2q0.Copy(true); - p2q0.Resize(0); // Sum up the winding numbers of all vertices. - w03_ = Winding03(inP, p0, s02, false); - - w30_ = Winding03(inQ, q0, s20, true); + w03_ = Winding03(inP, inQ, expandP_, true); + w30_ = Winding03(inP, inQ, expandP_, false); #ifdef MANIFOLD_DEBUG intersections.Stop(); if (ManifoldParams().verbose) { - broad.Print("Broad phase"); intersections.Print("Intersections"); } #endif diff --git a/thirdparty/manifold/src/boolean3.h b/thirdparty/manifold/src/boolean3.h index 7ee2ee1230..745403f78d 100644 --- a/thirdparty/manifold/src/boolean3.h +++ b/thirdparty/manifold/src/boolean3.h @@ -13,11 +13,11 @@ // limitations under the License. #pragma once -#include "./impl.h" +#include "impl.h" #ifdef MANIFOLD_DEBUG #define PRINT(msg) \ - if (ManifoldParams().verbose) std::cout << msg << std::endl; + if (ManifoldParams().verbose > 0) std::cout << msg << std::endl; #else #define PRINT(msg) #endif @@ -53,8 +53,9 @@ class Boolean3 { private: const Manifold::Impl &inP_, &inQ_; const double expandP_; - SparseIndices p1q2_, p2q1_; + Vec> p1q2_, p2q1_; Vec x12_, x21_, w03_, w30_; Vec v12_, v21_; + bool valid = true; }; } // namespace manifold diff --git a/thirdparty/manifold/src/boolean_result.cpp b/thirdparty/manifold/src/boolean_result.cpp index 2921dde906..4ea3303741 100644 --- a/thirdparty/manifold/src/boolean_result.cpp +++ b/thirdparty/manifold/src/boolean_result.cpp @@ -16,9 +16,9 @@ #include #include -#include "./boolean3.h" -#include "./parallel.h" -#include "./utils.h" +#include "boolean3.h" +#include "parallel.h" +#include "utils.h" #if (MANIFOLD_PAR == 1) && __has_include() #define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 @@ -37,7 +37,7 @@ using namespace manifold; template <> struct std::hash> { - size_t operator()(const std::pair &p) const { + size_t operator()(const std::pair& p) const { return std::hash()(p.first) ^ std::hash()(p.second); } }; @@ -83,12 +83,12 @@ struct CountNewVerts { VecView countP; VecView countQ; VecView i12; - const SparseIndices &pq; + const Vec>& pq; VecView halfedges; void operator()(const int idx) { - int edgeP = pq.Get(idx, inverted); - int faceQ = pq.Get(idx, !inverted); + int edgeP = pq[idx][inverted ? 1 : 0]; + int faceQ = pq[idx][inverted ? 0 : 1]; int inclusion = std::abs(i12[idx]); if (atomic) { @@ -106,10 +106,10 @@ struct CountNewVerts { }; std::tuple, Vec> SizeOutput( - Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ, - const Vec &i03, const Vec &i30, const Vec &i12, - const Vec &i21, const SparseIndices &p1q2, const SparseIndices &p2q1, - bool invertQ) { + Manifold::Impl& outR, const Manifold::Impl& inP, const Manifold::Impl& inQ, + const Vec& i03, const Vec& i30, const Vec& i12, + const Vec& i21, const Vec>& p1q2, + const Vec>& p2q1, bool invertQ) { ZoneScoped; Vec sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0); // note: numFaceR <= facePQ2R.size() = sidesPerFacePQ.size() + 1 @@ -154,7 +154,7 @@ std::tuple, Vec> SizeOutput( int numFaceR = facePQ2R.back(); facePQ2R.resize(inP.NumTri() + inQ.NumTri()); - outR.faceNormal_.resize(numFaceR); + outR.faceNormal_.resize_nofill(numFaceR); Vec tmpBuffer(outR.faceNormal_.size()); auto faceIds = TransformIterator(countAt(0_uz), [&sidesPerFacePQ](size_t i) { @@ -196,8 +196,9 @@ std::tuple, Vec> SizeOutput( } struct EdgePos { - int vert; double edgePos; + int vert; + int collisionId; bool isStart; }; @@ -212,8 +213,8 @@ void AddNewEdgeVerts( // we need concurrent_map because we will be adding things concurrently concurrent_map> &edgesP, concurrent_map, std::vector> &edgesNew, - const SparseIndices &p1q2, const Vec &i12, const Vec &v12R, - const Vec &halfedgeP, bool forward) { + const Vec> &p1q2, const Vec &i12, const Vec &v12R, + const Vec &halfedgeP, bool forward, size_t offset) { ZoneScoped; // For each edge of P that intersects a face of Q (p1q2), add this vertex to // P's corresponding edge vector and to the two new edges, which are @@ -222,8 +223,8 @@ void AddNewEdgeVerts( // the output vert index. When forward is false, all is reversed. auto process = [&](std::function lock, std::function unlock, size_t i) { - const int edgeP = p1q2.Get(i, !forward); - const int faceQ = p1q2.Get(i, forward); + const int edgeP = p1q2[i][forward ? 0 : 1]; + const int faceQ = p1q2[i][forward ? 1 : 0]; const int vert = v12R[i]; const int inclusion = i12[i]; @@ -236,21 +237,22 @@ void AddNewEdgeVerts( bool direction = inclusion < 0; std::hash> pairHasher; - std::array *>, 3> edges = { + std::array*>, 3> edges = { std::make_tuple(direction, std::hash{}(edgeP), &edgesP[edgeP]), std::make_tuple(direction ^ !forward, // revert if not forward pairHasher(keyRight), &edgesNew[keyRight]), std::make_tuple(direction ^ forward, // revert if forward pairHasher(keyLeft), &edgesNew[keyLeft])}; - for (const auto &tuple : edges) { + for (const auto& tuple : edges) { lock(std::get<1>(tuple)); for (int j = 0; j < std::abs(inclusion); ++j) - std::get<2>(tuple)->push_back({vert + j, 0.0, std::get<0>(tuple)}); + std::get<2>(tuple)->push_back( + {0.0, vert + j, static_cast(i + offset), std::get<0>(tuple)}); unlock(std::get<1>(tuple)); direction = !direction; } }; -#if (MANIFOLD_PAR == 1) && __has_include() +#if (MANIFOLD_PAR == 1) && __has_include() // parallelize operations, requires concurrent_map so we can only enable this // with tbb if (p1q2.size() > kParallelThreshold) { @@ -264,19 +266,19 @@ void AddNewEdgeVerts( std::placeholders::_1); tbb::parallel_for( tbb::blocked_range(0_uz, p1q2.size(), 32), - [&](const tbb::blocked_range &range) { + [&](const tbb::blocked_range& range) { for (size_t i = range.begin(); i != range.end(); i++) processFun(i); }, ap); return; } #endif - auto processFun = std::bind( - process, [](size_t _) {}, [](size_t _) {}, std::placeholders::_1); + auto processFun = + std::bind(process, [](size_t) {}, [](size_t) {}, std::placeholders::_1); for (size_t i = 0; i < p1q2.size(); ++i) processFun(i); } -std::vector PairUp(std::vector &edgePos) { +std::vector PairUp(std::vector& edgePos) { // Pair start vertices with end vertices to form edges. The choice of pairing // is arbitrary for the manifoldness guarantee, but must be ordered to be // geometrically valid. If the order does not go start-end-start-end... then @@ -289,7 +291,11 @@ std::vector PairUp(std::vector &edgePos) { [](EdgePos x) { return x.isStart; }); DEBUG_ASSERT(static_cast(middle - edgePos.begin()) == nEdges, topologyErr, "Non-manifold edge!"); - auto cmp = [](EdgePos a, EdgePos b) { return a.edgePos < b.edgePos; }; + auto cmp = [](EdgePos a, EdgePos b) { + return a.edgePos < b.edgePos || + // we also sort by collisionId to make things deterministic + (a.edgePos == b.edgePos && a.collisionId < b.collisionId); + }; std::stable_sort(edgePos.begin(), middle, cmp); std::stable_sort(middle, edgePos.end(), cmp); std::vector edges; @@ -298,11 +304,11 @@ std::vector PairUp(std::vector &edgePos) { return edges; } -void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, - Vec &facePtrR, - concurrent_map> &edgesP, - Vec &halfedgeRef, const Manifold::Impl &inP, - const Vec &i03, const Vec &vP2R, +void AppendPartialEdges(Manifold::Impl& outR, Vec& wholeHalfedgeP, + Vec& facePtrR, + concurrent_map>& edgesP, + Vec& halfedgeRef, const Manifold::Impl& inP, + const Vec& i03, const Vec& vP2R, const Vec::IterC faceP2R, bool forward) { ZoneScoped; // Each edge in the map is partially retained; for each of these, look up @@ -310,15 +316,15 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, // while remapping them to the output using vP2R. Use the verts position // projected along the edge vector to pair them up, then distribute these // edges to their faces. - Vec &halfedgeR = outR.halfedge_; - const Vec &vertPosP = inP.vertPos_; - const Vec &halfedgeP = inP.halfedge_; + Vec& halfedgeR = outR.halfedge_; + const Vec& vertPosP = inP.vertPos_; + const Vec& halfedgeP = inP.halfedge_; - for (auto &value : edgesP) { + for (auto& value : edgesP) { const int edgeP = value.first; - std::vector &edgePosP = value.second; + std::vector& edgePosP = value.second; - const Halfedge &halfedge = halfedgeP[edgeP]; + const Halfedge& halfedge = halfedgeP[edgeP]; wholeHalfedgeP[edgeP] = false; wholeHalfedgeP[halfedge.pairedHalfedge] = false; @@ -326,13 +332,13 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, const int vEnd = halfedge.endVert; const vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart]; // Fill in the edge positions of the old points. - for (EdgePos &edge : edgePosP) { + for (EdgePos& edge : edgePosP) { edge.edgePos = la::dot(outR.vertPos_[edge.vert], edgeVec); } int inclusion = i03[vStart]; - EdgePos edgePos = {vP2R[vStart], - la::dot(outR.vertPos_[vP2R[vStart]], edgeVec), + EdgePos edgePos = {la::dot(outR.vertPos_[vP2R[vStart]], edgeVec), + vP2R[vStart], std::numeric_limits::max(), inclusion > 0}; for (int j = 0; j < std::abs(inclusion); ++j) { edgePosP.push_back(edgePos); @@ -340,8 +346,8 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, } inclusion = i03[vEnd]; - edgePos = {vP2R[vEnd], la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec), - inclusion < 0}; + edgePos = {la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec), vP2R[vEnd], + std::numeric_limits::max(), inclusion < 0}; for (int j = 0; j < std::abs(inclusion); ++j) { edgePosP.push_back(edgePos); ++edgePos.vert; @@ -359,8 +365,8 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, // reference is now to the endVert instead of the startVert, which is one // position advanced CCW. This is only valid if this is a retained vert; it // will be ignored later if the vert is new. - const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP}; - const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP}; + const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1}; + const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1}; for (Halfedge e : edges) { const int forwardEdge = facePtrR[faceLeft]++; @@ -379,18 +385,18 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec &wholeHalfedgeP, } void AppendNewEdges( - Manifold::Impl &outR, Vec &facePtrR, - concurrent_map, std::vector> &edgesNew, - Vec &halfedgeRef, const Vec &facePQ2R, const int numFaceP) { + Manifold::Impl& outR, Vec& facePtrR, + concurrent_map, std::vector>& edgesNew, + Vec& halfedgeRef, const Vec& facePQ2R, const int numFaceP) { ZoneScoped; // Pair up each edge's verts and distribute to faces based on indices in key. - Vec &halfedgeR = outR.halfedge_; - Vec &vertPosR = outR.vertPos_; + Vec& halfedgeR = outR.halfedge_; + Vec& vertPosR = outR.vertPos_; - for (auto &value : edgesNew) { + for (auto& value : edgesNew) { const int faceP = value.first.first; const int faceQ = value.first.second; - std::vector &edgePos = value.second; + std::vector& edgePos = value.second; Box bbox; for (auto edge : edgePos) { @@ -401,7 +407,7 @@ void AppendNewEdges( const int i = (size.x > size.y && size.x > size.z) ? 0 : size.y > size.z ? 1 : 2; - for (auto &edge : edgePos) { + for (auto& edge : edgePos) { edge.edgePos = vertPosR[edge.vert][i]; } @@ -411,8 +417,8 @@ void AppendNewEdges( // add halfedges to result const int faceLeft = facePQ2R[faceP]; const int faceRight = facePQ2R[numFaceP + faceQ]; - const TriRef forwardRef = {0, -1, faceP}; - const TriRef backwardRef = {1, -1, faceQ}; + const TriRef forwardRef = {0, -1, faceP, -1}; + const TriRef backwardRef = {1, -1, faceQ, -1}; for (Halfedge e : edges) { const int forwardEdge = facePtrR[faceLeft]++; const int backwardEdge = facePtrR[faceRight]++; @@ -459,8 +465,8 @@ struct DuplicateHalfedges { // Negative inclusion means the halfedges are reversed, which means our // reference is now to the endVert instead of the startVert, which is one // position advanced CCW. - const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP}; - const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP}; + const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1}; + const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1}; for (int i = 0; i < std::abs(inclusion); ++i) { int forwardEdge = AtomicAdd(facePtr[newFace], 1); @@ -479,10 +485,10 @@ struct DuplicateHalfedges { } }; -void AppendWholeEdges(Manifold::Impl &outR, Vec &facePtrR, - Vec &halfedgeRef, const Manifold::Impl &inP, - const Vec wholeHalfedgeP, const Vec &i03, - const Vec &vP2R, VecView faceP2R, +void AppendWholeEdges(Manifold::Impl& outR, Vec& facePtrR, + Vec& halfedgeRef, const Manifold::Impl& inP, + const Vec wholeHalfedgeP, const Vec& i03, + const Vec& vP2R, VecView faceP2R, bool forward) { ZoneScoped; for_each_n( @@ -496,26 +502,26 @@ struct MapTriRef { VecView triRefQ; const int offsetQ; - void operator()(TriRef &triRef) { - const int tri = triRef.tri; + void operator()(TriRef& triRef) { + const int tri = triRef.faceID; const bool PQ = triRef.meshID == 0; triRef = PQ ? triRefP[tri] : triRefQ[tri]; if (!PQ) triRef.meshID += offsetQ; } }; -void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP, - const Manifold::Impl &inQ, bool invertQ) { +void UpdateReference(Manifold::Impl& outR, const Manifold::Impl& inP, + const Manifold::Impl& inQ, bool invertQ) { const int offsetQ = Manifold::Impl::meshIDCounter_; for_each_n( autoPolicy(outR.NumTri(), 1e5), outR.meshRelation_.triRef.begin(), outR.NumTri(), MapTriRef({inP.meshRelation_.triRef, inQ.meshRelation_.triRef, offsetQ})); - for (const auto &pair : inP.meshRelation_.meshIDtransform) { + for (const auto& pair : inP.meshRelation_.meshIDtransform) { outR.meshRelation_.meshIDtransform[pair.first] = pair.second; } - for (const auto &pair : inQ.meshRelation_.meshIDtransform) { + for (const auto& pair : inQ.meshRelation_.meshIDtransform) { outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second; outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^= invertQ; @@ -537,10 +543,10 @@ struct Barycentric { const TriRef refPQ = ref[tri]; if (halfedgeR[3 * tri].startVert < 0) return; - const int triPQ = refPQ.tri; + const int triPQ = refPQ.faceID; const bool PQ = refPQ.meshID == 0; - const auto &vertPos = PQ ? vertPosP : vertPosQ; - const auto &halfedge = PQ ? halfedgeP : halfedgeQ; + const auto& vertPos = PQ ? vertPosP : vertPosQ; + const auto& halfedge = PQ ? halfedgeP : halfedgeQ; mat3 triPos; for (const int j : {0, 1, 2}) @@ -553,18 +559,16 @@ struct Barycentric { } }; -void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, - const Manifold::Impl &inQ) { +void CreateProperties(Manifold::Impl& outR, const Manifold::Impl& inP, + const Manifold::Impl& inQ) { ZoneScoped; const int numPropP = inP.NumProp(); const int numPropQ = inQ.NumProp(); const int numProp = std::max(numPropP, numPropQ); - outR.meshRelation_.numProp = numProp; + outR.numProp_ = numProp; if (numProp == 0) return; const int numTri = outR.NumTri(); - outR.meshRelation_.triProperties.resize(numTri); - Vec bary(outR.halfedge_.size()); for_each_n(autoPolicy(numTri, 1e4), countAt(0), numTri, Barycentric({bary, outR.meshRelation_.triRef, inP.vertPos_, @@ -578,7 +582,7 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, propMissIdx[0].resize(inQ.NumPropVert(), -1); propMissIdx[1].resize(inP.NumPropVert(), -1); - outR.meshRelation_.properties.reserve(outR.NumVert() * numProp); + outR.properties_.reserve(outR.NumVert() * numProp); int idx = 0; for (int tri = 0; tri < numTri; ++tri) { @@ -588,15 +592,12 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, const TriRef ref = outR.meshRelation_.triRef[tri]; const bool PQ = ref.meshID == 0; const int oldNumProp = PQ ? numPropP : numPropQ; - const auto &properties = - PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties; - const ivec3 &triProp = oldNumProp == 0 ? ivec3(-1) - : PQ ? inP.meshRelation_.triProperties[ref.tri] - : inQ.meshRelation_.triProperties[ref.tri]; + const auto& properties = PQ ? inP.properties_ : inQ.properties_; + const auto& halfedge = PQ ? inP.halfedge_ : inQ.halfedge_; for (const int i : {0, 1, 2}) { const int vert = outR.halfedge_[3 * tri + i].startVert; - const vec3 &uvw = bary[3 * tri + i]; + const vec3& uvw = bary[3 * tri + i]; ivec4 key(PQ, idMissProp, -1, -1); if (oldNumProp > 0) { @@ -604,7 +605,7 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, for (const int j : {0, 1, 2}) { if (uvw[j] == 1) { // On a retained vert, the propVert must also match - key[2] = triProp[j]; + key[2] = halfedge[3 * ref.faceID + j].propVert; edge = -1; break; } @@ -612,8 +613,8 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, } if (edge >= 0) { // On an edge, both propVerts must match - const int p0 = triProp[Next3(edge)]; - const int p1 = triProp[Prev3(edge)]; + const int p0 = halfedge[3 * ref.faceID + Next3(edge)].propVert; + const int p1 = halfedge[3 * ref.faceID + Prev3(edge)].propVert; key[1] = vert; key[2] = std::min(p0, p1); key[3] = std::max(p0, p1); @@ -624,19 +625,19 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, if (key.y == idMissProp && key.z >= 0) { // only key.x/key.z matters - auto &entry = propMissIdx[key.x][key.z]; + auto& entry = propMissIdx[key.x][key.z]; if (entry >= 0) { - outR.meshRelation_.triProperties[tri][i] = entry; + outR.halfedge_[3 * tri + i].propVert = entry; continue; } entry = idx; } else { - auto &bin = propIdx[key.y]; + auto& bin = propIdx[key.y]; bool bFound = false; - for (const auto &b : bin) { + for (const auto& b : bin) { if (b.first == ivec3(key.x, key.z, key.w)) { bFound = true; - outR.meshRelation_.triProperties[tri][i] = b.second; + outR.halfedge_[3 * tri + i].propVert = b.second; break; } } @@ -644,20 +645,55 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, bin.push_back(std::make_pair(ivec3(key.x, key.z, key.w), idx)); } - outR.meshRelation_.triProperties[tri][i] = idx++; + outR.halfedge_[3 * tri + i].propVert = idx++; for (int p = 0; p < numProp; ++p) { if (p < oldNumProp) { vec3 oldProps; for (const int j : {0, 1, 2}) - oldProps[j] = properties[oldNumProp * triProp[j] + p]; - outR.meshRelation_.properties.push_back(la::dot(uvw, oldProps)); + oldProps[j] = + properties[oldNumProp * halfedge[3 * ref.faceID + j].propVert + + p]; + outR.properties_.push_back(la::dot(uvw, oldProps)); } else { - outR.meshRelation_.properties.push_back(0); + outR.properties_.push_back(0); } } } } } + +void ReorderHalfedges(VecView& halfedges) { + // halfedges in the same face are added in non-deterministic order, so we have + // to reorder them for determinism + + // step 1: reorder within the same face, such that the halfedge with the + // smallest starting vertex is placed first + for_each(autoPolicy(halfedges.size() / 3), countAt(0), + countAt(halfedges.size() / 3), [&halfedges](size_t tri) { + std::array face = {halfedges[tri * 3], + halfedges[tri * 3 + 1], + halfedges[tri * 3 + 2]}; + int index = 0; + for (int i : {1, 2}) + if (face[i].startVert < face[index].startVert) index = i; + for (int i : {0, 1, 2}) + halfedges[tri * 3 + i] = face[(index + i) % 3]; + }); + // step 2: fix paired halfedge + for_each(autoPolicy(halfedges.size() / 3), countAt(0), + countAt(halfedges.size() / 3), [&halfedges](size_t tri) { + for (int i : {0, 1, 2}) { + Halfedge& curr = halfedges[tri * 3 + i]; + int oppositeFace = curr.pairedHalfedge / 3; + int index = -1; + for (int j : {0, 1, 2}) + if (curr.startVert == halfedges[oppositeFace * 3 + j].endVert) + index = j; + curr.pairedHalfedge = oppositeFace * 3 + index; + } + }); +} + } // namespace namespace manifold { @@ -697,6 +733,12 @@ Manifold::Impl Boolean3::Result(OpType op) const { return inP_; } + if (!valid) { + auto impl = Manifold::Impl(); + impl.status_ = Manifold::Error::ResultTooLarge; + return impl; + } + const bool invertQ = op == OpType::Subtract; // Convert winding numbers to inclusion values based on operation type. @@ -746,7 +788,7 @@ Manifold::Impl Boolean3::Result(OpType op) const { outR.epsilon_ = std::max(inP_.epsilon_, inQ_.epsilon_); outR.tolerance_ = std::max(inP_.tolerance_, inQ_.tolerance_); - outR.vertPos_.resize(numVertR); + outR.vertPos_.resize_nofill(numVertR); // Add vertices, duplicating for inclusion numbers not in [-1, 1]. // Retained vertices from P and Q: for_each_n(autoPolicy(inP_.NumVert(), 1e4), countAt(0), inP_.NumVert(), @@ -775,8 +817,9 @@ Manifold::Impl Boolean3::Result(OpType op) const { // This key is the face index of concurrent_map, std::vector> edgesNew; - AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true); - AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false); + AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true, 0); + AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false, + p1q2_.size()); v12R.clear(); v21R.clear(); @@ -842,6 +885,8 @@ Manifold::Impl Boolean3::Result(OpType op) const { halfedgeRef.clear(); faceEdge.clear(); + ReorderHalfedges(outR.halfedge_); + #ifdef MANIFOLD_DEBUG triangulate.Stop(); Timer simplify; @@ -856,7 +901,7 @@ Manifold::Impl Boolean3::Result(OpType op) const { UpdateReference(outR, inP_, inQ_, invertQ); - outR.SimplifyTopology(); + outR.SimplifyTopology(nPv + nQv); outR.RemoveUnreferencedVerts(); if (ManifoldParams().intermediateChecks) diff --git a/thirdparty/manifold/src/collider.h b/thirdparty/manifold/src/collider.h index 80de94b7c2..d266669905 100644 --- a/thirdparty/manifold/src/collider.h +++ b/thirdparty/manifold/src/collider.h @@ -13,11 +13,10 @@ // limitations under the License. #pragma once -#include "./parallel.h" -#include "./sparse.h" -#include "./utils.h" -#include "./vec.h" #include "manifold/common.h" +#include "parallel.h" +#include "utils.h" +#include "vec.h" #ifdef _MSC_VER #include @@ -40,7 +39,7 @@ constexpr int kRoot = 1; #ifdef _MSC_VER #ifndef _WINDEF_ -typedef unsigned long DWORD; +using DWORD = unsigned long; #endif uint32_t inline ctz(uint32_t value) { @@ -163,14 +162,16 @@ struct FindCollision { VecView queries; VecView nodeBBox_; VecView> internalChildren_; - Recorder recorder; + Recorder& recorder; - inline int RecordCollision(int node, const int queryIdx, SparseIndices& ind) { + using Local = typename Recorder::Local; + + inline int RecordCollision(int node, const int queryIdx, Local& local) { bool overlaps = nodeBBox_[node].DoesOverlap(queries[queryIdx]); if (overlaps && IsLeaf(node)) { int leafIdx = Node2Leaf(node); if (!selfCollision || leafIdx != queryIdx) { - recorder.record(queryIdx, leafIdx, ind); + recorder.record(queryIdx, leafIdx, local); } } return overlaps && IsInternal(node); // Should traverse into node @@ -183,14 +184,14 @@ struct FindCollision { int top = -1; // Depth-first search int node = kRoot; - SparseIndices& ind = recorder.local(); + Local& local = recorder.local(); while (1) { int internal = Node2Internal(node); int child1 = internalChildren_[internal].first; int child2 = internalChildren_[internal].second; - int traverse1 = RecordCollision(child1, queryIdx, ind); - int traverse2 = RecordCollision(child2, queryIdx, ind); + int traverse1 = RecordCollision(child1, queryIdx, local); + int traverse2 = RecordCollision(child2, queryIdx, local); if (!traverse1 && !traverse2) { if (top < 0) break; // done @@ -205,35 +206,6 @@ struct FindCollision { } }; -template -struct SeqCollisionRecorder { - SparseIndices& queryTri_; - inline void record(int queryIdx, int leafIdx, SparseIndices& ind) const { - if (inverted) - ind.Add(leafIdx, queryIdx); - else - ind.Add(queryIdx, leafIdx); - } - SparseIndices& local() { return queryTri_; } -}; - -#if (MANIFOLD_PAR == 1) -template -struct ParCollisionRecorder { - tbb::combinable& store; - inline void record(int queryIdx, int leafIdx, SparseIndices& ind) const { - // Add may invoke something in parallel, and it may return in - // another thread, making thread local unsafe - // we need to explicitly forbid parallelization by passing a flag - if (inverted) - ind.Add(leafIdx, queryIdx, true); - else - ind.Add(queryIdx, leafIdx, true); - } - SparseIndices& local() { return store.local(); } -}; -#endif - struct BuildInternalBoxes { VecView nodeBBox_; VecView counter_; @@ -266,6 +238,22 @@ constexpr inline uint32_t SpreadBits3(uint32_t v) { } } // namespace collider_internal +template +struct SimpleRecorder { + using Local = F; + F& f; + + inline void record(int queryIdx, int leafIdx, F& f) const { + f(queryIdx, leafIdx); + } + Local& local() { return f; } +}; + +template +inline SimpleRecorder MakeSimpleRecorder(F& f) { + return SimpleRecorder{f}; +} + /** @ingroup Private */ class Collider { public: @@ -278,7 +266,7 @@ class Collider { "vectors must be the same length"); int num_nodes = 2 * leafBB.size() - 1; // assign and allocate members - nodeBBox_.resize(num_nodes); + nodeBBox_.resize_nofill(num_nodes); nodeParent_.resize(num_nodes, -1); internalChildren_.resize(leafBB.size() - 1, std::make_pair(-1, -1)); // organize tree @@ -321,40 +309,27 @@ class Collider { {nodeBBox_, counter, nodeParent_, internalChildren_})); } - template - void Collisions(const VecView& queriesIn, - SparseIndices& queryTri) const { + // This function iterates over queriesIn and calls recorder.record(queryIdx, + // leafIdx, local) for each collision it found. + // If selfCollisionl is true, it will skip the case where queryIdx == leafIdx. + // The recorder should provide a local() method that returns a Recorder::Local + // type, representing thread local storage. By default, recorder.record can + // run in parallel and the thread local storage can be combined at the end. + // If parallel is false, the function will run in sequential mode. + // + // If thread local storage is not needed, use SimpleRecorder. + template + void Collisions(const VecView& queriesIn, Recorder& recorder, + bool parallel = true) const { ZoneScoped; using collider_internal::FindCollision; -#if (MANIFOLD_PAR == 1) - if (queriesIn.size() > collider_internal::kSequentialThreshold) { - tbb::combinable store; - for_each_n( - ExecutionPolicy::Par, countAt(0), queriesIn.size(), - FindCollision>{ - queriesIn, nodeBBox_, internalChildren_, {store}}); - - std::vector tmp; - store.combine_each( - [&](SparseIndices& ind) { tmp.emplace_back(std::move(ind)); }); - queryTri.FromIndices(tmp); - return; - } -#endif - for_each_n(ExecutionPolicy::Seq, countAt(0), queriesIn.size(), - FindCollision>{ - queriesIn, nodeBBox_, internalChildren_, {queryTri}}); - } - - template - SparseIndices Collisions(const VecView& queriesIn) const { - SparseIndices result; - Collisions(queriesIn, result); - return result; + if (internalChildren_.empty()) return; + for_each_n(parallel ? autoPolicy(queriesIn.size(), + collider_internal::kSequentialThreshold) + : ExecutionPolicy::Seq, + countAt(0), queriesIn.size(), + FindCollision{ + queriesIn, nodeBBox_, internalChildren_, recorder}); } static uint32_t MortonCode(vec3 position, Box bBox) { diff --git a/thirdparty/manifold/src/constructors.cpp b/thirdparty/manifold/src/constructors.cpp index 2b3e528c6b..86acc2d1ab 100644 --- a/thirdparty/manifold/src/constructors.cpp +++ b/thirdparty/manifold/src/constructors.cpp @@ -12,10 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./csg_tree.h" -#include "./impl.h" -#include "./parallel.h" +#include "csg_tree.h" +#include "impl.h" +#include "manifold/manifold.h" #include "manifold/polygon.h" +#include "parallel.h" + +namespace { +using namespace manifold; + +template +std::shared_ptr SmoothImpl( + const MeshGLP& meshGL, + const std::vector& sharpenedEdges) { + DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error, + "when supplying tangents, the normal constructor should be used " + "rather than Smooth()."); + + MeshGLP meshTmp = meshGL; + meshTmp.faceID.resize(meshGL.NumTri()); + std::iota(meshTmp.faceID.begin(), meshTmp.faceID.end(), 0); + + std::shared_ptr impl = + std::make_shared(meshTmp); + impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges)); + // Restore the original faceID + const size_t numTri = impl->NumTri(); + for (size_t i = 0; i < numTri; ++i) { + if (meshGL.faceID.size() == numTri) { + impl->meshRelation_.triRef[i].faceID = + meshGL.faceID[impl->meshRelation_.triRef[i].faceID]; + } else { + impl->meshRelation_.triRef[i].faceID = -1; + } + } + return impl; +} +} // namespace namespace manifold { /** @@ -48,13 +81,7 @@ namespace manifold { */ Manifold Manifold::Smooth(const MeshGL& meshGL, const std::vector& sharpenedEdges) { - DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error, - "when supplying tangents, the normal constructor should be used " - "rather than Smooth()."); - - std::shared_ptr impl = std::make_shared(meshGL); - impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges)); - return Manifold(impl); + return Manifold(SmoothImpl(meshGL, sharpenedEdges)); } /** @@ -87,13 +114,7 @@ Manifold Manifold::Smooth(const MeshGL& meshGL, */ Manifold Manifold::Smooth(const MeshGL64& meshGL64, const std::vector& sharpenedEdges) { - DEBUG_ASSERT(meshGL64.halfedgeTangent.empty(), std::runtime_error, - "when supplying tangents, the normal constructor should be used " - "rather than Smooth()."); - - std::shared_ptr impl = std::make_shared(meshGL64); - impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges)); - return Manifold(impl); + return Manifold(SmoothImpl(meshGL64, sharpenedEdges)); } /** @@ -173,8 +194,7 @@ Manifold Manifold::Sphere(double radius, int circularSegments) { int n = circularSegments > 0 ? (circularSegments + 3) / 4 : Quality::GetCircularSegments(radius) / 4; auto pImpl_ = std::make_shared(Impl::Shape::Octahedron); - pImpl_->Subdivide( - [n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; }); + pImpl_->Subdivide([n](vec3, vec4, vec4) { return n - 1; }); for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(), pImpl_->NumVert(), [radius](vec3& v) { v = la::cos(kHalfPi * (1.0 - v)); @@ -277,7 +297,7 @@ Manifold Manifold::Extrude(const Polygons& crossSection, double height, pImpl_->CreateHalfedges(triVertsDH); pImpl_->Finish(); pImpl_->InitializeOriginal(); - pImpl_->CreateFaces(); + pImpl_->MarkCoplanar(); return Manifold(pImpl_); } @@ -316,7 +336,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments, } const size_t next = i + 1 == poly.size() ? 0 : i + 1; if ((poly[next].x < 0) != (poly[i].x < 0)) { - const double y = poly[next].y + poly[next].x * + const double y = poly[next].y - poly[next].x * (poly[i].y - poly[next].y) / (poly[i].x - poly[next].x); polygons.back().push_back({0, y}); @@ -352,8 +372,8 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments, const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1; for (const auto& poly : polygons) { - std::size_t nPosVerts = 0; - std::size_t nRevolveAxisVerts = 0; + size_t nPosVerts = 0; + size_t nRevolveAxisVerts = 0; for (auto& pt : poly) { if (pt.x > 0) { nPosVerts++; @@ -420,7 +440,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments, pImpl_->CreateHalfedges(triVertsDH); pImpl_->Finish(); pImpl_->InitializeOriginal(); - pImpl_->CreateFaces(); + pImpl_->MarkCoplanar(); return Manifold(pImpl_); } diff --git a/thirdparty/manifold/src/cross_section/cross_section.cpp b/thirdparty/manifold/src/cross_section/cross_section.cpp index bacdfd10e6..84d285cca7 100644 --- a/thirdparty/manifold/src/cross_section/cross_section.cpp +++ b/thirdparty/manifold/src/cross_section/cross_section.cpp @@ -376,6 +376,16 @@ CrossSection CrossSection::BatchBoolean( return crossSections[0]; auto subjs = crossSections[0].GetPaths(); + + if (op == OpType::Intersect) { + auto res = subjs->paths_; + for (size_t i = 1; i < crossSections.size(); ++i) { + res = C2::BooleanOp(C2::ClipType::Intersection, C2::FillRule::Positive, + res, crossSections[i].GetPaths()->paths_, precision_); + } + return CrossSection(shared_paths(res)); + } + int n_clips = 0; for (size_t i = 1; i < crossSections.size(); ++i) { n_clips += crossSections[i].GetPaths()->paths_.size(); @@ -446,7 +456,8 @@ CrossSection& CrossSection::operator^=(const CrossSection& Q) { * Construct a CrossSection from a vector of other CrossSections (batch * boolean union). */ -CrossSection CrossSection::Compose(std::vector& crossSections) { +CrossSection CrossSection::Compose( + const std::vector& crossSections) { return BatchBoolean(crossSections, OpType::Add); } @@ -518,9 +529,9 @@ CrossSection CrossSection::Scale(const vec2 scale) const { } /** - * Mirror this CrossSection over the arbitrary axis described by the unit form - * of the given vector. If the length of the vector is zero, an empty - * CrossSection is returned. This operation can be chained. Transforms are + * Mirror this CrossSection over the arbitrary axis whose normal is described by + * the unit form of the given vector. If the length of the vector is zero, an + * empty CrossSection is returned. This operation can be chained. Transforms are * combined and applied lazily. * * @param ax the axis to be mirrored over @@ -529,7 +540,7 @@ CrossSection CrossSection::Mirror(const vec2 ax) const { if (la::length(ax) == 0.) { return CrossSection(); } - auto n = la::normalize(la::abs(ax)); + auto n = la::normalize(ax); auto m = mat2x3(mat2(la::identity) - 2.0 * la::outerprod(n, n), vec2(0.0)); return Transform(m); } @@ -641,7 +652,7 @@ CrossSection CrossSection::Simplify(double epsilon) const { * to expand, and retraction of inner (hole) contours. Negative deltas will * have the opposite effect. * @param jointype The join type specifying the treatment of contour joins - * (corners). + * (corners). Defaults to Round. * @param miter_limit The maximum distance in multiples of delta that vertices * can be offset from their original positions with before squaring is * applied, when the join type is Miter (default is 2, which is the diff --git a/thirdparty/manifold/src/csg_tree.cpp b/thirdparty/manifold/src/csg_tree.cpp index e126485ece..c41cb8f0c5 100644 --- a/thirdparty/manifold/src/csg_tree.cpp +++ b/thirdparty/manifold/src/csg_tree.cpp @@ -12,27 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if (MANIFOLD_PAR == 1) && __has_include() +#if MANIFOLD_PAR == 1 #include -#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 -#include #endif #include -#include "./boolean3.h" -#include "./csg_tree.h" -#include "./impl.h" -#include "./mesh_fixes.h" -#include "./parallel.h" - -constexpr int kParallelThreshold = 4096; +#include "boolean3.h" +#include "csg_tree.h" +#include "impl.h" +#include "mesh_fixes.h" +#include "parallel.h" namespace { using namespace manifold; struct MeshCompare { - bool operator()(const std::shared_ptr &a, - const std::shared_ptr &b) { + bool operator()(const std::shared_ptr& a, + const std::shared_ptr& b) { return a->GetImpl()->NumVert() < b->GetImpl()->NumVert(); } }; @@ -41,7 +37,7 @@ struct MeshCompare { namespace manifold { std::shared_ptr CsgNode::Boolean( - const std::shared_ptr &second, OpType op) { + const std::shared_ptr& second, OpType op) { if (second->GetNodeType() != CsgNodeType::Leaf) { // "this" is not a CsgOpNode (which overrides Boolean), but if "second" is // and the operation is commutative, we let it built the tree. @@ -54,13 +50,13 @@ std::shared_ptr CsgNode::Boolean( return std::make_shared(children, op); } -std::shared_ptr CsgNode::Translate(const vec3 &t) const { +std::shared_ptr CsgNode::Translate(const vec3& t) const { mat3x4 transform = la::identity; transform[3] += t; return Transform(transform); } -std::shared_ptr CsgNode::Scale(const vec3 &v) const { +std::shared_ptr CsgNode::Scale(const vec3& v) const { mat3x4 transform; for (int i : {0, 1, 2}) transform[i][i] = v[i]; return Transform(transform); @@ -102,18 +98,18 @@ std::shared_ptr CsgLeafNode::ToLeafNode() const { return std::make_shared(*this); } -std::shared_ptr CsgLeafNode::Transform(const mat3x4 &m) const { +std::shared_ptr CsgLeafNode::Transform(const mat3x4& m) const { return std::make_shared(pImpl_, m * Mat4(transform_)); } CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; } -std::shared_ptr ImplToLeaf(Manifold::Impl &&impl) { +std::shared_ptr ImplToLeaf(Manifold::Impl&& impl) { return std::make_shared(std::make_shared(impl)); } -std::shared_ptr SimpleBoolean(const Manifold::Impl &a, - const Manifold::Impl &b, OpType op) { +std::shared_ptr SimpleBoolean(const Manifold::Impl& a, + const Manifold::Impl& b, OpType op) { #ifdef MANIFOLD_DEBUG auto dump = [&]() { dump_lock.lock(); @@ -121,6 +117,7 @@ std::shared_ptr SimpleBoolean(const Manifold::Impl &a, << std::endl; std::cout << "RHS self-intersecting: " << b.IsSelfIntersecting() << std::endl; +#ifdef MANIFOLD_EXPORT if (ManifoldParams().verbose) { if (op == OpType::Add) std::cout << "Add"; @@ -132,22 +129,23 @@ std::shared_ptr SimpleBoolean(const Manifold::Impl &a, std::cout << a; std::cout << b; } +#endif dump_lock.unlock(); }; try { Boolean3 boolean(a, b, op); auto impl = boolean.Result(op); - if (ManifoldParams().intermediateChecks && impl.IsSelfIntersecting()) { + if (ManifoldParams().selfIntersectionChecks && impl.IsSelfIntersecting()) { dump_lock.lock(); std::cout << "self intersections detected" << std::endl; dump_lock.unlock(); throw logicErr("self intersection detected"); } return ImplToLeaf(std::move(impl)); - } catch (logicErr &err) { + } catch (logicErr& err) { dump(); throw err; - } catch (geometryErr &err) { + } catch (geometryErr& err) { dump(); throw err; } @@ -160,7 +158,7 @@ std::shared_ptr SimpleBoolean(const Manifold::Impl &a, * Efficient union of a set of pairwise disjoint meshes. */ std::shared_ptr CsgLeafNode::Compose( - const std::vector> &nodes) { + const std::vector>& nodes) { ZoneScoped; double epsilon = -1; double tolerance = -1; @@ -173,7 +171,7 @@ std::shared_ptr CsgLeafNode::Compose( std::vector triIndices; std::vector propVertIndices; int numPropOut = 0; - for (auto &node : nodes) { + for (auto& node : nodes) { if (node->pImpl_->status_ != Manifold::Error::NoError) { Manifold::Impl impl; impl.status_ = node->pImpl_->status_; @@ -199,22 +197,20 @@ std::shared_ptr CsgLeafNode::Compose( const int numProp = node->pImpl_->NumProp(); numPropOut = std::max(numPropOut, numProp); numPropVert += - numProp == 0 ? 1 - : node->pImpl_->meshRelation_.properties.size() / numProp; + numProp == 0 ? 1 : node->pImpl_->properties_.size() / numProp; } Manifold::Impl combined; combined.epsilon_ = epsilon; combined.tolerance_ = tolerance; - combined.vertPos_.resize(numVert); - combined.halfedge_.resize(2 * numEdge); - combined.faceNormal_.resize(numTri); + combined.vertPos_.resize_nofill(numVert); + combined.halfedge_.resize_nofill(2 * numEdge); + combined.faceNormal_.resize_nofill(numTri); combined.halfedgeTangent_.resize(2 * numEdge); - combined.meshRelation_.triRef.resize(numTri); + combined.meshRelation_.triRef.resize_nofill(numTri); if (numPropOut > 0) { - combined.meshRelation_.numProp = numPropOut; - combined.meshRelation_.properties.resize(numPropOut * numPropVert, 0); - combined.meshRelation_.triProperties.resize(numTri); + combined.numProp_ = numPropOut; + combined.properties_.resize(numPropOut * numPropVert, 0); } auto policy = autoPolicy(numTri); @@ -228,50 +224,35 @@ std::shared_ptr CsgLeafNode::Compose( countAt(0), nodes.size(), [&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices, numPropOut, &combined, policy](int i) { - auto &node = nodes[i]; + auto& node = nodes[i]; copy(node->pImpl_->halfedgeTangent_.begin(), node->pImpl_->halfedgeTangent_.end(), combined.halfedgeTangent_.begin() + edgeIndices[i]); const int nextVert = vertIndices[i]; const int nextEdge = edgeIndices[i]; - const int nextFace = triIndices[i]; + const int nextProp = propVertIndices[i]; transform(node->pImpl_->halfedge_.begin(), node->pImpl_->halfedge_.end(), combined.halfedge_.begin() + edgeIndices[i], - [nextVert, nextEdge, nextFace](Halfedge edge) { + [nextVert, nextEdge, nextProp](Halfedge edge) { edge.startVert += nextVert; edge.endVert += nextVert; edge.pairedHalfedge += nextEdge; + edge.propVert += nextProp; return edge; }); - if (numPropOut > 0) { - auto start = - combined.meshRelation_.triProperties.begin() + triIndices[i]; - if (node->pImpl_->NumProp() > 0) { - auto &triProp = node->pImpl_->meshRelation_.triProperties; - const int nextProp = propVertIndices[i]; - transform(triProp.begin(), triProp.end(), start, - [nextProp](ivec3 tri) { - tri += nextProp; - return tri; - }); - - const int numProp = node->pImpl_->NumProp(); - auto &oldProp = node->pImpl_->meshRelation_.properties; - auto &newProp = combined.meshRelation_.properties; - for (int p = 0; p < numProp; ++p) { - auto oldRange = - StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp); - auto newRange = StridedRange( - newProp.begin() + numPropOut * propVertIndices[i] + p, - newProp.end(), numPropOut); - copy(oldRange.begin(), oldRange.end(), newRange.begin()); - } - } else { - // point all triangles at single new property of zeros. - fill(start, start + node->pImpl_->NumTri(), - ivec3(propVertIndices[i])); + if (node->pImpl_->NumProp() > 0) { + const int numProp = node->pImpl_->NumProp(); + auto& oldProp = node->pImpl_->properties_; + auto& newProp = combined.properties_; + for (int p = 0; p < numProp; ++p) { + auto oldRange = + StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp); + auto newRange = StridedRange( + newProp.begin() + numPropOut * propVertIndices[i] + p, + newProp.end(), numPropOut); + copy(oldRange.begin(), oldRange.end(), newRange.begin()); } } @@ -323,16 +304,16 @@ std::shared_ptr CsgLeafNode::Compose( }); for (size_t i = 0; i < nodes.size(); i++) { - auto &node = nodes[i]; + auto& node = nodes[i]; const int offset = i * Manifold::Impl::meshIDCounter_; - for (const auto &pair : node->pImpl_->meshRelation_.meshIDtransform) { + for (const auto& pair : node->pImpl_->meshRelation_.meshIDtransform) { combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second; } } // required to remove parts that are smaller than the tolerance - combined.SimplifyTopology(); + combined.RemoveDegenerates(); combined.Finish(); combined.IncrementMeshIDs(); return ImplToLeaf(std::move(combined)); @@ -343,7 +324,7 @@ std::shared_ptr CsgLeafNode::Compose( * operation. Only supports union and intersection. */ std::shared_ptr BatchBoolean( - OpType operation, std::vector> &results) { + OpType operation, std::vector>& results) { ZoneScoped; DEBUG_ASSERT(operation != OpType::Subtract, logicErr, "BatchBoolean doesn't support Difference."); @@ -353,50 +334,44 @@ std::shared_ptr BatchBoolean( if (results.size() == 2) return SimpleBoolean(*results[0]->GetImpl(), *results[1]->GetImpl(), operation); -#if (MANIFOLD_PAR == 1) && __has_include() - tbb::task_group group; - tbb::concurrent_priority_queue, MeshCompare> - queue(results.size()); - for (auto result : results) { - queue.emplace(result); - } - results.clear(); - std::function process = [&]() { - while (queue.size() > 1) { - std::shared_ptr a, b; - if (!queue.try_pop(a)) continue; - if (!queue.try_pop(b)) { - queue.push(a); - continue; - } - group.run([&, a, b]() { - queue.emplace(SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation)); - return group.run(process); - }); - } - }; - group.run_and_wait(process); - std::shared_ptr r; - queue.try_pop(r); - return r; -#endif // apply boolean operations starting from smaller meshes // the assumption is that boolean operations on smaller meshes is faster, // due to less data being copied and processed auto cmpFn = MeshCompare(); std::make_heap(results.begin(), results.end(), cmpFn); + std::vector> tmp; +#if MANIFOLD_PAR == 1 + tbb::task_group group; + std::mutex mutex; +#endif while (results.size() > 1) { - std::pop_heap(results.begin(), results.end(), cmpFn); - auto a = std::move(results.back()); - results.pop_back(); - std::pop_heap(results.begin(), results.end(), cmpFn); - auto b = std::move(results.back()); - results.pop_back(); - // boolean operation - auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation); - if (results.size() == 0) return result; - results.push_back(result); - std::push_heap(results.begin(), results.end(), cmpFn); + for (size_t i = 0; i < 4 && results.size() > 1; i++) { + std::pop_heap(results.begin(), results.end(), cmpFn); + auto a = std::move(results.back()); + results.pop_back(); + std::pop_heap(results.begin(), results.end(), cmpFn); + auto b = std::move(results.back()); + results.pop_back(); +#if MANIFOLD_PAR == 1 + group.run([&, a, b]() { + auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation); + mutex.lock(); + tmp.push_back(result); + mutex.unlock(); + }); +#else + auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation); + tmp.push_back(result); +#endif + } +#if MANIFOLD_PAR == 1 + group.wait(); +#endif + for (auto result : tmp) { + results.push_back(result); + std::push_heap(results.begin(), results.end(), cmpFn); + } + tmp.clear(); } return results.front(); } @@ -406,7 +381,7 @@ std::shared_ptr BatchBoolean( * possible. */ std::shared_ptr BatchUnion( - std::vector> &children) { + std::vector>& children) { ZoneScoped; // INVARIANT: children_ is a vector of leaf nodes // this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check @@ -429,7 +404,7 @@ std::shared_ptr BatchUnion( // each set contains a set of children that are pairwise disjoint std::vector> disjointSets; for (size_t i = 0; i < boxes.size(); i++) { - auto lambda = [&boxes, i](const Vec &set) { + auto lambda = [&boxes, i](const Vec& set) { return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) { return boxes[i].DoesOverlap(boxes[j]); }) == set.end(); @@ -443,7 +418,7 @@ std::shared_ptr BatchUnion( } // compose each set of disjoint children std::vector> impls; - for (auto &set : disjointSets) { + for (auto& set : disjointSets) { if (set.size() == 1) { impls.push_back(children[start + set[0]]); } else { @@ -466,7 +441,7 @@ std::shared_ptr BatchUnion( CsgOpNode::CsgOpNode() {} -CsgOpNode::CsgOpNode(const std::vector> &children, +CsgOpNode::CsgOpNode(const std::vector>& children, OpType op) : impl_(children), op_(op) {} @@ -475,7 +450,7 @@ CsgOpNode::~CsgOpNode() { auto impl = impl_.GetGuard(); std::vector> toProcess; auto handleChildren = - [&toProcess](std::vector> &children) { + [&toProcess](std::vector>& children) { while (!children.empty()) { // move out so shrinking the vector will not trigger recursive drop auto movedChild = std::move(children.back()); @@ -498,7 +473,7 @@ CsgOpNode::~CsgOpNode() { } std::shared_ptr CsgOpNode::Boolean( - const std::shared_ptr &second, OpType op) { + const std::shared_ptr& second, OpType op) { std::vector> children; children.push_back(shared_from_this()); children.push_back(second); @@ -506,7 +481,7 @@ std::shared_ptr CsgOpNode::Boolean( return std::make_shared(children, op); } -std::shared_ptr CsgOpNode::Transform(const mat3x4 &m) const { +std::shared_ptr CsgOpNode::Transform(const mat3x4& m) const { auto node = std::make_shared(); node->impl_ = impl_; node->transform_ = m * Mat4(transform_); @@ -520,14 +495,14 @@ struct CsgStackFrame { bool finalize; OpType parent_op; mat3x4 transform; - Nodes *positive_dest; - Nodes *negative_dest; + Nodes* positive_dest; + Nodes* negative_dest; std::shared_ptr op_node; Nodes positive_children; Nodes negative_children; CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform, - Nodes *positive_dest, Nodes *negative_dest, + Nodes* positive_dest, Nodes* negative_dest, std::shared_ptr op_node) : finalize(finalize), parent_op(parent_op), @@ -675,8 +650,8 @@ std::shared_ptr CsgOpNode::ToLeafNode() const { stack.pop_back(); } else { auto add_children = - [&stack](std::shared_ptr &node, OpType op, mat3x4 transform, - CsgStackFrame::Nodes *dest1, CsgStackFrame::Nodes *dest2) { + [&stack](std::shared_ptr& node, OpType op, mat3x4 transform, + CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) { if (node->GetNodeType() == CsgNodeType::Leaf) dest1->push_back(std::static_pointer_cast( node->Transform(transform))); @@ -702,14 +677,14 @@ std::shared_ptr CsgOpNode::ToLeafNode() const { const mat3x4 transform = canCollapse ? (frame->transform * Mat4(frame->op_node->transform_)) : la::identity; - CsgStackFrame::Nodes *pos_dest = + CsgStackFrame::Nodes* pos_dest = canCollapse ? frame->positive_dest : &frame->positive_children; - CsgStackFrame::Nodes *neg_dest = + CsgStackFrame::Nodes* neg_dest = canCollapse ? frame->negative_dest : &frame->negative_children; for (size_t i = 0; i < impl->size(); i++) { const bool negative = op == OpType::Subtract && i != 0; - CsgStackFrame::Nodes *dest1 = negative ? neg_dest : pos_dest; - CsgStackFrame::Nodes *dest2 = + CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest; + CsgStackFrame::Nodes* dest2 = (op == OpType::Subtract && i == 0) ? neg_dest : nullptr; add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1, dest2); diff --git a/thirdparty/manifold/src/csg_tree.h b/thirdparty/manifold/src/csg_tree.h index fb664df00c..fa5d22382d 100644 --- a/thirdparty/manifold/src/csg_tree.h +++ b/thirdparty/manifold/src/csg_tree.h @@ -13,8 +13,8 @@ // limitations under the License. #pragma once -#include "./utils.h" #include "manifold/manifold.h" +#include "utils.h" namespace manifold { @@ -25,14 +25,14 @@ class CsgLeafNode; class CsgNode : public std::enable_shared_from_this { public: virtual std::shared_ptr ToLeafNode() const = 0; - virtual std::shared_ptr Transform(const mat3x4 &m) const = 0; + virtual std::shared_ptr Transform(const mat3x4& m) const = 0; virtual CsgNodeType GetNodeType() const = 0; virtual std::shared_ptr Boolean( - const std::shared_ptr &second, OpType op); + const std::shared_ptr& second, OpType op); - std::shared_ptr Translate(const vec3 &t) const; - std::shared_ptr Scale(const vec3 &s) const; + std::shared_ptr Translate(const vec3& t) const; + std::shared_ptr Scale(const vec3& s) const; std::shared_ptr Rotate(double xDegrees = 0, double yDegrees = 0, double zDegrees = 0) const; }; @@ -47,12 +47,12 @@ class CsgLeafNode final : public CsgNode { std::shared_ptr ToLeafNode() const override; - std::shared_ptr Transform(const mat3x4 &m) const override; + std::shared_ptr Transform(const mat3x4& m) const override; CsgNodeType GetNodeType() const override; static std::shared_ptr Compose( - const std::vector> &nodes); + const std::vector>& nodes); private: mutable std::shared_ptr pImpl_; @@ -63,12 +63,12 @@ class CsgOpNode final : public CsgNode { public: CsgOpNode(); - CsgOpNode(const std::vector> &children, OpType op); + CsgOpNode(const std::vector>& children, OpType op); - std::shared_ptr Boolean(const std::shared_ptr &second, + std::shared_ptr Boolean(const std::shared_ptr& second, OpType op) override; - std::shared_ptr Transform(const mat3x4 &m) const override; + std::shared_ptr Transform(const mat3x4& m) const override; std::shared_ptr ToLeafNode() const override; diff --git a/thirdparty/manifold/src/edge_op.cpp b/thirdparty/manifold/src/edge_op.cpp index d22577a2a2..81567ffeba 100644 --- a/thirdparty/manifold/src/edge_op.cpp +++ b/thirdparty/manifold/src/edge_op.cpp @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./impl.h" -#include "./parallel.h" +#include + +#include "impl.h" +#include "parallel.h" +#include "shared.h" namespace { using namespace manifold; @@ -47,33 +50,46 @@ struct DuplicateEdge { struct ShortEdge { VecView halfedge; VecView vertPos; - const double tolerance; + const double epsilon; + const int firstNewVert; bool operator()(int edge) const { - if (halfedge[edge].pairedHalfedge < 0) return false; + const Halfedge& half = halfedge[edge]; + if (half.pairedHalfedge < 0 || + (half.startVert < firstNewVert && half.endVert < firstNewVert)) + return false; // Flag short edges - const vec3 delta = - vertPos[halfedge[edge].endVert] - vertPos[halfedge[edge].startVert]; - return la::dot(delta, delta) < tolerance * tolerance; + const vec3 delta = vertPos[half.endVert] - vertPos[half.startVert]; + return la::dot(delta, delta) < epsilon * epsilon; } }; struct FlagEdge { VecView halfedge; VecView triRef; + const int firstNewVert; bool operator()(int edge) const { - if (halfedge[edge].pairedHalfedge < 0) return false; + const Halfedge& half = halfedge[edge]; + if (half.pairedHalfedge < 0 || half.startVert < firstNewVert) return false; // Flag redundant edges - those where the startVert is surrounded by only // two original triangles. const TriRef ref0 = triRef[edge / 3]; - int current = NextHalfedge(halfedge[edge].pairedHalfedge); - const TriRef ref1 = triRef[current / 3]; + int current = NextHalfedge(half.pairedHalfedge); + TriRef ref1 = triRef[current / 3]; + bool ref1Updated = !ref0.SameFace(ref1); while (current != edge) { current = NextHalfedge(halfedge[current].pairedHalfedge); int tri = current / 3; const TriRef ref = triRef[tri]; - if (!ref.SameFace(ref0) && !ref.SameFace(ref1)) return false; + if (!ref.SameFace(ref0) && !ref.SameFace(ref1)) { + if (!ref1Updated) { + ref1 = ref; + ref1Updated = true; + } else { + return false; + } + } } return true; } @@ -84,9 +100,15 @@ struct SwappableEdge { VecView vertPos; VecView triNormal; const double tolerance; + const int firstNewVert; bool operator()(int edge) const { - if (halfedge[edge].pairedHalfedge < 0) return false; + const Halfedge& half = halfedge[edge]; + if (half.pairedHalfedge < 0) return false; + if (half.startVert < firstNewVert && half.endVert < firstNewVert && + halfedge[NextHalfedge(edge)].endVert < firstNewVert && + halfedge[NextHalfedge(half.pairedHalfedge)].endVert < firstNewVert) + return false; int tri = edge / 3; ivec3 triEdge = TriOf(edge); @@ -98,7 +120,7 @@ struct SwappableEdge { return false; // Switch to neighbor's projection. - edge = halfedge[edge].pairedHalfedge; + edge = half.pairedHalfedge; tri = edge / 3; triEdge = TriOf(edge); projection = GetAxisAlignedProjection(triNormal[tri]); @@ -109,14 +131,67 @@ struct SwappableEdge { } }; -struct SortEntry { - int start; - int end; - size_t index; - inline bool operator<(const SortEntry& other) const { - return start == other.start ? end < other.end : start < other.start; +struct FlagStore { +#if MANIFOLD_PAR == 1 + tbb::combinable> store; +#endif + std::vector s; + + template + void run_seq(size_t n, Pred pred, F f) { + for (size_t i = 0; i < n; ++i) + if (pred(i)) s.push_back(i); + for (size_t i : s) f(i); + s.clear(); + } + +#if MANIFOLD_PAR == 1 + template + void run_par(size_t n, Pred pred, F f) { + // Test pred in parallel, store i into thread-local vectors when pred(i) is + // true. After testing pred, iterate and call f over the indices in + // ascending order by using a heap in a single thread + auto& store = this->store; + tbb::parallel_for(tbb::blocked_range(0, n), + [&store, &pred](const auto& r) { + auto& local = store.local(); + for (auto i = r.begin(); i < r.end(); ++i) { + if (pred(i)) local.push_back(i); + } + }); + + std::vector> stores; + std::vector result; + store.combine_each( + [&](auto& data) { stores.emplace_back(std::move(data)); }); + std::vector sizes; + size_t total_size = 0; + for (const auto& tmp : stores) { + sizes.push_back(total_size); + total_size += tmp.size(); + } + result.resize(total_size); + for_each_n(ExecutionPolicy::Seq, countAt(0), stores.size(), [&](size_t i) { + std::copy(stores[i].begin(), stores[i].end(), result.begin() + sizes[i]); + }); + stable_sort(autoPolicy(result.size()), result.begin(), result.end()); + for (size_t x : result) f(x); + } +#endif + + template + void run(size_t n, Pred pred, F f) { +#if MANIFOLD_PAR == 1 + if (n > 1e5) { + run_par(n, pred, f); + } else +#endif + { + run_seq(n, pred, f); + } } }; + } // namespace namespace manifold { @@ -131,41 +206,7 @@ void Manifold::Impl::CleanupTopology() { // In the case of a very bad triangulation, it is possible to create pinched // verts. They must be removed before edge collapse. SplitPinchedVerts(); - - while (1) { - ZoneScopedN("DedupeEdge"); - - const size_t nbEdges = halfedge_.size(); - size_t numFlagged = 0; - - Vec entries; - entries.reserve(nbEdges / 2); - for (size_t i = 0; i < nbEdges; ++i) { - if (halfedge_[i].IsForward()) { - entries.push_back({halfedge_[i].startVert, halfedge_[i].endVert, i}); - } - } - - stable_sort(entries.begin(), entries.end()); - for (size_t i = 0; i < entries.size() - 1; ++i) { - const int h0 = entries[i].index; - const int h1 = entries[i + 1].index; - if (halfedge_[h0].startVert == halfedge_[h1].startVert && - halfedge_[h0].endVert == halfedge_[h1].endVert) { - DedupeEdge(entries[i].index); - numFlagged++; - } - } - - if (numFlagged == 0) break; - -#ifdef MANIFOLD_DEBUG - if (ManifoldParams().verbose) { - std::cout << "found " << numFlagged << " duplicate edges to split" - << std::endl; - } -#endif - } + DedupeEdges(); } /** @@ -184,95 +225,112 @@ void Manifold::Impl::CleanupTopology() { * meshes, thus decreasing the Genus(). It only increases when meshes that have * collapsed to just a pair of triangles are removed entirely. * + * Verts with index less than firstNewVert will be left uncollapsed. This is + * zero by default so that everything can be collapsed. + * * Rather than actually removing the edges, this step merely marks them for * removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}. */ -void Manifold::Impl::SimplifyTopology() { +void Manifold::Impl::SimplifyTopology(int firstNewVert) { if (!halfedge_.size()) return; CleanupTopology(); + CollapseShortEdges(firstNewVert); + CollapseColinearEdges(firstNewVert); + SwapDegenerates(firstNewVert); +} - if (!ManifoldParams().cleanupTriangles) { - return; - } +void Manifold::Impl::RemoveDegenerates(int firstNewVert) { + if (!halfedge_.size()) return; - const size_t nbEdges = halfedge_.size(); - auto policy = autoPolicy(nbEdges, 1e5); + CleanupTopology(); + CollapseShortEdges(firstNewVert); + SwapDegenerates(firstNewVert); +} + +void Manifold::Impl::CollapseShortEdges(int firstNewVert) { + ZoneScopedN("CollapseShortEdge"); + FlagStore s; size_t numFlagged = 0; - Vec bFlags(nbEdges); + const size_t nbEdges = halfedge_.size(); std::vector scratchBuffer; scratchBuffer.reserve(10); - { - ZoneScopedN("CollapseShortEdge"); - numFlagged = 0; - ShortEdge se{halfedge_, vertPos_, epsilon_}; - for_each_n(policy, countAt(0_uz), nbEdges, - [&](size_t i) { bFlags[i] = se(i); }); - for (size_t i = 0; i < nbEdges; ++i) { - if (bFlags[i]) { - CollapseEdge(i, scratchBuffer); - scratchBuffer.resize(0); - numFlagged++; - } - } - } + // Short edges get to skip several checks and hence remove more classes of + // degenerate triangles than flagged edges do, but this could in theory lead + // to error stacking where a vertex moves too far. For this reason this is + // restricted to epsilon, rather than tolerance. + ShortEdge se{halfedge_, vertPos_, epsilon_, firstNewVert}; + s.run(nbEdges, se, [&](size_t i) { + const bool didCollapse = CollapseEdge(i, scratchBuffer); + if (didCollapse) numFlagged++; + scratchBuffer.resize(0); + }); #ifdef MANIFOLD_DEBUG - if (ManifoldParams().verbose && numFlagged > 0) { - std::cout << "found " << numFlagged << " short edges to collapse" - << std::endl; + if (ManifoldParams().verbose > 0 && numFlagged > 0) { + std::cout << "collapsed " << numFlagged << " short edges" << std::endl; } #endif +} - { +void Manifold::Impl::CollapseColinearEdges(int firstNewVert) { + FlagStore s; + size_t numFlagged = 0; + const size_t nbEdges = halfedge_.size(); + std::vector scratchBuffer; + scratchBuffer.reserve(10); + while (1) { ZoneScopedN("CollapseFlaggedEdge"); numFlagged = 0; - FlagEdge se{halfedge_, meshRelation_.triRef}; - for_each_n(policy, countAt(0_uz), nbEdges, - [&](size_t i) { bFlags[i] = se(i); }); - for (size_t i = 0; i < nbEdges; ++i) { - if (bFlags[i]) { - CollapseEdge(i, scratchBuffer); - scratchBuffer.resize(0); - numFlagged++; - } - } - } + // Collapse colinear edges, but only remove new verts, i.e. verts with + // index + // >= firstNewVert. This is used to keep the Boolean from changing the + // non-intersecting parts of the input meshes. Colinear is defined not by a + // local check, but by the global MarkCoplanar function, which keeps this + // from being vulnerable to error stacking. + FlagEdge se{halfedge_, meshRelation_.triRef, firstNewVert}; + s.run(nbEdges, se, [&](size_t i) { + const bool didCollapse = CollapseEdge(i, scratchBuffer); + if (didCollapse) numFlagged++; + scratchBuffer.resize(0); + }); + if (numFlagged == 0) break; #ifdef MANIFOLD_DEBUG - if (ManifoldParams().verbose && numFlagged > 0) { - std::cout << "found " << numFlagged << " colinear edges to collapse" - << std::endl; - } + if (ManifoldParams().verbose > 0 && numFlagged > 0) { + std::cout << "collapsed " << numFlagged << " colinear edges" << std::endl; + } #endif - - { - ZoneScopedN("RecursiveEdgeSwap"); - numFlagged = 0; - SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_}; - for_each_n(policy, countAt(0_uz), nbEdges, - [&](size_t i) { bFlags[i] = se(i); }); - std::vector edgeSwapStack; - std::vector visited(halfedge_.size(), -1); - int tag = 0; - for (size_t i = 0; i < nbEdges; ++i) { - if (bFlags[i]) { - numFlagged++; - tag++; - RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer); - while (!edgeSwapStack.empty()) { - int last = edgeSwapStack.back(); - edgeSwapStack.pop_back(); - RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer); - } - } - } } +} + +void Manifold::Impl::SwapDegenerates(int firstNewVert) { + ZoneScopedN("RecursiveEdgeSwap"); + FlagStore s; + size_t numFlagged = 0; + const size_t nbEdges = halfedge_.size(); + std::vector scratchBuffer; + scratchBuffer.reserve(10); + + SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_, firstNewVert}; + std::vector edgeSwapStack; + std::vector visited(halfedge_.size(), -1); + int tag = 0; + s.run(nbEdges, se, [&](size_t i) { + numFlagged++; + tag++; + RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer); + while (!edgeSwapStack.empty()) { + int last = edgeSwapStack.back(); + edgeSwapStack.pop_back(); + RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer); + } + }); #ifdef MANIFOLD_DEBUG - if (ManifoldParams().verbose && numFlagged > 0) { - std::cout << "found " << numFlagged << " edges to swap" << std::endl; + if (ManifoldParams().verbose > 0 && numFlagged > 0) { + std::cout << "swapped " << numFlagged << " edges" << std::endl; } #endif } @@ -283,6 +341,7 @@ void Manifold::Impl::DedupeEdge(const int edge) { // Orbit endVert const int startVert = halfedge_[edge].startVert; const int endVert = halfedge_[edge].endVert; + const int endProp = halfedge_[NextHalfedge(edge)].propVert; int current = halfedge_[NextHalfedge(edge)].pairedHalfedge; while (current != edge) { const int vert = halfedge_[current].startVert; @@ -297,36 +356,30 @@ void Manifold::Impl::DedupeEdge(const int edge) { UpdateVert(newVert, current, opposite); int newHalfedge = halfedge_.size(); - int newFace = newHalfedge / 3; int oldFace = current / 3; int outsideVert = halfedge_[current].startVert; - halfedge_.push_back({endVert, newVert, -1}); - halfedge_.push_back({newVert, outsideVert, -1}); - halfedge_.push_back({outsideVert, endVert, -1}); + halfedge_.push_back({endVert, newVert, -1, endProp}); + halfedge_.push_back({newVert, outsideVert, -1, endProp}); + halfedge_.push_back( + {outsideVert, endVert, -1, halfedge_[current].propVert}); PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge); PairUp(newHalfedge + 1, current); if (meshRelation_.triRef.size() > 0) meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]); - if (meshRelation_.triProperties.size() > 0) - meshRelation_.triProperties.push_back( - meshRelation_.triProperties[oldFace]); if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]); newHalfedge += 3; - ++newFace; oldFace = opposite / 3; outsideVert = halfedge_[opposite].startVert; - halfedge_.push_back({newVert, endVert, -1}); - halfedge_.push_back({endVert, outsideVert, -1}); - halfedge_.push_back({outsideVert, newVert, -1}); + halfedge_.push_back({newVert, endVert, -1, endProp}); // fix prop + halfedge_.push_back({endVert, outsideVert, -1, endProp}); + halfedge_.push_back( + {outsideVert, newVert, -1, halfedge_[opposite].propVert}); PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge); PairUp(newHalfedge + 1, opposite); PairUp(newHalfedge, newHalfedge - 3); if (meshRelation_.triRef.size() > 0) meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]); - if (meshRelation_.triProperties.size() > 0) - meshRelation_.triProperties.push_back( - meshRelation_.triProperties[oldFace]); if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]); break; @@ -420,7 +473,7 @@ void Manifold::Impl::CollapseTri(const ivec3& triEdge) { halfedge_[pair1].pairedHalfedge = pair2; halfedge_[pair2].pairedHalfedge = pair1; for (int i : {0, 1, 2}) { - halfedge_[triEdge[i]] = {-1, -1, -1}; + halfedge_[triEdge[i]] = {-1, -1, -1, halfedge_[triEdge[i]].propVert}; } } @@ -452,16 +505,16 @@ void Manifold::Impl::RemoveIfFolded(int edge) { } } -// Collapses the given edge by removing startVert. May split the mesh -// topologically if the collapse would have resulted in a 4-manifold edge. Do -// not collapse an edge if startVert is pinched - the vert will be marked NaN, -// but other edges may still be pointing to it. -void Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { +// Collapses the given edge by removing startVert - returns false if the edge +// cannot be collapsed. May split the mesh topologically if the collapse would +// have resulted in a 4-manifold edge. Do not collapse an edge if startVert is +// pinched - the vert would be marked NaN, but other edges could still be +// pointing to it. +bool Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { Vec& triRef = meshRelation_.triRef; - Vec& triProp = meshRelation_.triProperties; const Halfedge toRemove = halfedge_[edge]; - if (toRemove.pairedHalfedge < 0) return; + if (toRemove.pairedHalfedge < 0) return false; const int endVert = toRemove.endVert; const ivec3 tri0edge = TriOf(edge); @@ -470,23 +523,16 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { const vec3 pNew = vertPos_[endVert]; const vec3 pOld = vertPos_[toRemove.startVert]; const vec3 delta = pNew - pOld; - const bool shortEdge = la::dot(delta, delta) < tolerance_ * tolerance_; - - // Orbit endVert - int current = halfedge_[tri0edge[1]].pairedHalfedge; - while (current != tri1edge[2]) { - current = NextHalfedge(current); - edges.push_back(current); - current = halfedge_[current].pairedHalfedge; - } + const bool shortEdge = la::dot(delta, delta) < epsilon_ * epsilon_; // Orbit startVert int start = halfedge_[tri1edge[1]].pairedHalfedge; + int current = tri1edge[2]; if (!shortEdge) { current = start; TriRef refCheck = triRef[toRemove.pairedHalfedge / 3]; vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert]; - while (current != tri0edge[2]) { + while (current != tri1edge[0]) { current = NextHalfedge(current); vec3 pNext = vertPos_[halfedge_[current].endVert]; const int tri = current / 3; @@ -495,28 +541,43 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { // Don't collapse if the edge is not redundant (this may have changed due // to the collapse of neighbors). if (!ref.SameFace(refCheck)) { + const TriRef oldRef = refCheck; refCheck = triRef[edge / 3]; if (!ref.SameFace(refCheck)) { - return; - } else { - // Don't collapse if the edges separating the faces are not colinear - // (can happen when the two faces are coplanar). - if (CCW(projection * pOld, projection * pLast, projection * pNew, + return false; + } + if (ref.meshID != oldRef.meshID || ref.faceID != oldRef.faceID || + la::dot(faceNormal_[toRemove.pairedHalfedge / 3], + faceNormal_[tri]) < -0.5) { + // Restrict collapse to colinear edges when the edge separates faces + // or the edge is sharp. This ensures large shifts are not introduced + // parallel to the tangent plane. + if (CCW(projection * pLast, projection * pOld, projection * pNew, epsilon_) != 0) - return; + return false; } } // Don't collapse edge if it would cause a triangle to invert. if (CCW(projection * pNext, projection * pLast, projection * pNew, epsilon_) < 0) - return; + return false; pLast = pNext; current = halfedge_[current].pairedHalfedge; } } + // Orbit endVert + { + int current = halfedge_[tri0edge[1]].pairedHalfedge; + while (current != tri1edge[2]) { + current = NextHalfedge(current); + edges.push_back(current); + current = halfedge_[current].pairedHalfedge; + } + } + // Remove toRemove.startVert and replace with endVert. vertPos_[toRemove.startVert] = vec3(NAN); CollapseTri(tri1edge); @@ -524,20 +585,18 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { // Orbit startVert const int tri0 = edge / 3; const int tri1 = toRemove.pairedHalfedge / 3; - const int triVert0 = (edge + 1) % 3; - const int triVert1 = toRemove.pairedHalfedge % 3; current = start; while (current != tri0edge[2]) { current = NextHalfedge(current); - if (triProp.size() > 0) { + if (NumProp() > 0) { // Update the shifted triangles to the vertBary of endVert const int tri = current / 3; - const int vIdx = current - 3 * tri; if (triRef[tri].SameFace(triRef[tri0])) { - triProp[tri][vIdx] = triProp[tri0][triVert0]; + halfedge_[current].propVert = halfedge_[NextHalfedge(edge)].propVert; } else if (triRef[tri].SameFace(triRef[tri1])) { - triProp[tri][vIdx] = triProp[tri1][triVert1]; + halfedge_[current].propVert = + halfedge_[toRemove.pairedHalfedge].propVert; } } @@ -557,6 +616,7 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector& edges) { UpdateVert(endVert, start, tri0edge[2]); CollapseTri(tri0edge); RemoveIfFolded(start); + return true; } void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag, @@ -574,8 +634,6 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag, const ivec3 tri0edge = TriOf(edge); const ivec3 tri1edge = TriOf(pair); - const ivec3 perm0 = TriOf(edge % 3); - const ivec3 perm1 = TriOf(pair % 3); mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]); vec2 v[4]; @@ -611,22 +669,21 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag, const double l02 = la::length(v[2] - v[0]); const double a = std::max(0.0, std::min(1.0, l02 / l01)); // Update properties if applicable - if (meshRelation_.properties.size() > 0) { - Vec& triProp = meshRelation_.triProperties; - Vec& prop = meshRelation_.properties; - triProp[tri0] = triProp[tri1]; - triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]]; - triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]]; + if (properties_.size() > 0) { + Vec& prop = properties_; + halfedge_[tri0edge[1]].propVert = halfedge_[tri1edge[0]].propVert; + halfedge_[tri0edge[0]].propVert = halfedge_[tri1edge[2]].propVert; + halfedge_[tri0edge[2]].propVert = halfedge_[tri1edge[2]].propVert; const int numProp = NumProp(); const int newProp = prop.size() / numProp; - const int propIdx0 = triProp[tri1][perm1[0]]; - const int propIdx1 = triProp[tri1][perm1[1]]; + const int propIdx0 = halfedge_[tri1edge[0]].propVert; + const int propIdx1 = halfedge_[tri1edge[1]].propVert; for (int p = 0; p < numProp; ++p) { prop.push_back(a * prop[numProp * propIdx0 + p] + (1 - a) * prop[numProp * propIdx1 + p]); } - triProp[tri1][perm1[0]] = newProp; - triProp[tri0][perm0[2]] = newProp; + halfedge_[tri1edge[0]].propVert = newProp; + halfedge_[tri0edge[2]].propVert = newProp; } // if the new edge already exists, duplicate the verts and split the mesh. @@ -675,22 +732,224 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag, void Manifold::Impl::SplitPinchedVerts() { ZoneScoped; - std::vector vertProcessed(NumVert(), false); - std::vector halfedgeProcessed(halfedge_.size(), false); - for (size_t i = 0; i < halfedge_.size(); ++i) { - if (halfedgeProcessed[i]) continue; - int vert = halfedge_[i].startVert; - if (vertProcessed[vert]) { - vertPos_.push_back(vertPos_[vert]); - vert = NumVert() - 1; - } else { - vertProcessed[vert] = true; + + auto nbEdges = halfedge_.size(); +#if MANIFOLD_PAR == 1 + if (nbEdges > 1e4) { + std::mutex mutex; + std::vector pinched; + // This parallelized version is non-trivial so we can't reuse the code + // + // The idea here is to identify cycles of halfedges that can be iterated + // through using ForVert. Pinched verts are vertices where there are + // multiple cycles associated with the vertex. Each cycle is identified with + // the largest halfedge index within the cycle, and when there are multiple + // cycles associated with the same starting vertex but with different ids, + // it means we have a pinched vertex. This check is done by using a single + // atomic cas operation, the expected case is either invalid id (the vertex + // was not processed) or with the same id. + // + // The local store is to store the processed halfedges, so to avoid + // repetitive processing. Note that it only approximates the processed + // halfedges because it is thread local. This is why we need a vector to + // deduplicate the probematic halfedges we found. + std::vector> largestEdge(NumVert()); + for_each(ExecutionPolicy::Par, countAt(0), countAt(NumVert()), + [&largestEdge](size_t i) { + largestEdge[i].store(std::numeric_limits::max()); + }); + tbb::combinable> store( + [nbEdges]() { return std::vector(nbEdges, false); }); + tbb::parallel_for( + tbb::blocked_range(0, nbEdges), + [&store, &mutex, &pinched, &largestEdge, this](const auto& r) { + auto& local = store.local(); + std::vector pinchedLocal; + for (auto i = r.begin(); i < r.end(); ++i) { + if (local[i]) continue; + local[i] = true; + const int vert = halfedge_[i].startVert; + if (vert == -1) continue; + size_t largest = i; + ForVert(i, [&local, &largest](int current) { + local[current] = true; + largest = std::max(largest, static_cast(current)); + }); + size_t expected = std::numeric_limits::max(); + if (!largestEdge[vert].compare_exchange_strong(expected, largest) && + expected != largest) { + // we know that there is another loop... + pinchedLocal.push_back(largest); + } + } + if (!pinchedLocal.empty()) { + std::lock_guard lock(mutex); + pinched.insert(pinched.end(), pinchedLocal.begin(), + pinchedLocal.end()); + } + }); + + manifold::stable_sort(pinched.begin(), pinched.end()); + std::vector halfedgeProcessed(nbEdges, false); + for (size_t i : pinched) { + if (halfedgeProcessed[i]) continue; + vertPos_.push_back(vertPos_[halfedge_[i].startVert]); + const int vert = NumVert() - 1; + ForVert(i, [this, vert, &halfedgeProcessed](int current) { + halfedgeProcessed[current] = true; + halfedge_[current].startVert = vert; + halfedge_[halfedge_[current].pairedHalfedge].endVert = vert; + }); } - ForVert(i, [this, &halfedgeProcessed, vert](int current) { - halfedgeProcessed[current] = true; - halfedge_[current].startVert = vert; - halfedge_[halfedge_[current].pairedHalfedge].endVert = vert; - }); + } else +#endif + { + std::vector vertProcessed(NumVert(), false); + std::vector halfedgeProcessed(nbEdges, false); + for (size_t i = 0; i < nbEdges; ++i) { + if (halfedgeProcessed[i]) continue; + int vert = halfedge_[i].startVert; + if (vert == -1) continue; + if (vertProcessed[vert]) { + vertPos_.push_back(vertPos_[vert]); + vert = NumVert() - 1; + } else { + vertProcessed[vert] = true; + } + ForVert(i, [this, &halfedgeProcessed, vert](int current) { + halfedgeProcessed[current] = true; + halfedge_[current].startVert = vert; + halfedge_[halfedge_[current].pairedHalfedge].endVert = vert; + }); + } + } +} + +void Manifold::Impl::DedupeEdges() { + while (1) { + ZoneScopedN("DedupeEdge"); + + const size_t nbEdges = halfedge_.size(); + std::vector duplicates; + auto localLoop = [&](size_t start, size_t end, std::vector& local, + std::vector& results) { + // Iterate over all halfedges that start with the same vertex, and check + // for halfedges with the same ending vertex. + // Note: we use Vec and linear search when the number of neighbor is + // small because unordered_set requires allocations and is expensive. + // We switch to unordered_set when the number of neighbor is + // larger to avoid making things quadratic. + // We do it in two pass, the first pass to find the minimal halfedges with + // the target start and end verts, the second pass flag all the duplicated + // halfedges that are not having the minimal index as duplicates. + // This ensures deterministic result. + // + // The local store is to store the processed halfedges, so to avoid + // repetitive processing. Note that it only approximates the processed + // halfedges because it is thread local. + Vec> endVerts; + std::unordered_map endVertSet; + for (auto i = start; i < end; ++i) { + if (local[i] || halfedge_[i].startVert == -1 || + halfedge_[i].endVert == -1) + continue; + // we want to keep the allocation + endVerts.clear(false); + endVertSet.clear(); + + // first iteration, populate entries + // this makes sure we always report the same set of entries + ForVert(i, [&local, &endVerts, &endVertSet, this](int current) { + local[current] = true; + if (halfedge_[current].startVert == -1 || + halfedge_[current].endVert == -1) { + return; + } + int endV = halfedge_[current].endVert; + if (endVertSet.empty()) { + auto iter = std::find_if(endVerts.begin(), endVerts.end(), + [endV](const std::pair& pair) { + return pair.first == endV; + }); + if (iter != endVerts.end()) { + iter->second = std::min(iter->second, current); + } else { + endVerts.push_back({endV, current}); + if (endVerts.size() > 32) { + endVertSet.insert(endVerts.begin(), endVerts.end()); + endVerts.clear(false); + } + } + } else { + auto pair = endVertSet.insert({endV, current}); + if (!pair.second) + pair.first->second = std::min(pair.first->second, current); + } + }); + // second iteration, actually check for duplicates + // we always report the same set of duplicates, excluding the smallest + // halfedge in the set of duplicates + ForVert(i, [&endVerts, &endVertSet, &results, this](int current) { + if (halfedge_[current].startVert == -1 || + halfedge_[current].endVert == -1) { + return; + } + int endV = halfedge_[current].endVert; + if (endVertSet.empty()) { + auto iter = std::find_if(endVerts.begin(), endVerts.end(), + [endV](const std::pair& pair) { + return pair.first == endV; + }); + if (iter->second != current) results.push_back(current); + } else { + auto iter = endVertSet.find(endV); + if (iter->second != current) results.push_back(current); + } + }); + } + }; +#if MANIFOLD_PAR == 1 + if (nbEdges > 1e4) { + std::mutex mutex; + tbb::combinable> store( + [nbEdges]() { return std::vector(nbEdges, false); }); + tbb::parallel_for( + tbb::blocked_range(0, nbEdges), + [&store, &mutex, &duplicates, &localLoop](const auto& r) { + auto& local = store.local(); + std::vector duplicatesLocal; + localLoop(r.begin(), r.end(), local, duplicatesLocal); + if (!duplicatesLocal.empty()) { + std::lock_guard lock(mutex); + duplicates.insert(duplicates.end(), duplicatesLocal.begin(), + duplicatesLocal.end()); + } + }); + manifold::stable_sort(duplicates.begin(), duplicates.end()); + duplicates.resize( + std::distance(duplicates.begin(), + std::unique(duplicates.begin(), duplicates.end()))); + } else +#endif + { + std::vector local(nbEdges, false); + localLoop(0, nbEdges, local, duplicates); + } + + size_t numFlagged = 0; + for (size_t i : duplicates) { + DedupeEdge(i); + numFlagged++; + } + + if (numFlagged == 0) break; + +#ifdef MANIFOLD_DEBUG + if (ManifoldParams().verbose > 0) { + std::cout << "found " << numFlagged << " duplicate edges to split" + << std::endl; + } +#endif } } } // namespace manifold diff --git a/thirdparty/manifold/src/face_op.cpp b/thirdparty/manifold/src/face_op.cpp index b1a6b16c73..5e826dd73b 100644 --- a/thirdparty/manifold/src/face_op.cpp +++ b/thirdparty/manifold/src/face_op.cpp @@ -12,16 +12,75 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +#include "impl.h" +#include "manifold/common.h" +#include "manifold/polygon.h" +#include "parallel.h" +#include "shared.h" + #if (MANIFOLD_PAR == 1) && __has_include() #include #define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 #include #endif -#include -#include "./impl.h" -#include "./parallel.h" -#include "manifold/polygon.h" +namespace { +using namespace manifold; + +/** + * Returns an assembled set of vertex index loops of the input list of + * Halfedges, where each vert must be referenced the same number of times as a + * startVert and endVert. If startHalfedgeIdx is given, instead of putting + * vertex indices into the returned polygons structure, it will use the halfedge + * indices instead. + */ +std::vector> AssembleHalfedges(VecView::IterC start, + VecView::IterC end, + const int startHalfedgeIdx) { + std::multimap vert_edge; + for (auto edge = start; edge != end; ++edge) { + vert_edge.emplace( + std::make_pair(edge->startVert, static_cast(edge - start))); + } + + std::vector> polys; + int startEdge = 0; + int thisEdge = startEdge; + while (1) { + if (thisEdge == startEdge) { + if (vert_edge.empty()) break; + startEdge = vert_edge.begin()->second; + thisEdge = startEdge; + polys.push_back({}); + } + polys.back().push_back(startHalfedgeIdx + thisEdge); + const auto result = vert_edge.find((start + thisEdge)->endVert); + DEBUG_ASSERT(result != vert_edge.end(), topologyErr, "non-manifold edge"); + thisEdge = result->second; + vert_edge.erase(result); + } + return polys; +} + +/** + * Add the vertex position projection to the indexed polygons. + */ +PolygonsIdx ProjectPolygons(const std::vector>& polys, + const Vec& halfedge, + const Vec& vertPos, mat2x3 projection) { + PolygonsIdx polygons; + for (const auto& poly : polys) { + polygons.push_back({}); + for (const auto& edge : poly) { + polygons.back().push_back( + {projection * vertPos[halfedge[edge].startVert], edge}); + } // for vert + } // for poly + return polygons; +} +} // namespace namespace manifold { @@ -39,83 +98,71 @@ using AddTriangle = std::function; * faceNormal_ values are retained, repeated as necessary. */ void Manifold::Impl::Face2Tri(const Vec& faceEdge, - const Vec& halfedgeRef) { + const Vec& halfedgeRef, + bool allowConvex) { ZoneScoped; Vec triVerts; Vec triNormal; + Vec triProp; Vec& triRef = meshRelation_.triRef; - triRef.resize(0); + triRef.clear(); auto processFace = [&](GeneralTriangulation general, AddTriangle addTri, int face) { const int firstEdge = faceEdge[face]; const int lastEdge = faceEdge[face + 1]; const int numEdge = lastEdge - firstEdge; + if (numEdge == 0) return; DEBUG_ASSERT(numEdge >= 3, topologyErr, "face has less than three edges."); const vec3 normal = faceNormal_[face]; if (numEdge == 3) { // Single triangle - int mapping[3] = {halfedge_[firstEdge].startVert, - halfedge_[firstEdge + 1].startVert, - halfedge_[firstEdge + 2].startVert}; + ivec3 triEdge(firstEdge, firstEdge + 1, firstEdge + 2); ivec3 tri(halfedge_[firstEdge].startVert, halfedge_[firstEdge + 1].startVert, halfedge_[firstEdge + 2].startVert); ivec3 ends(halfedge_[firstEdge].endVert, halfedge_[firstEdge + 1].endVert, halfedge_[firstEdge + 2].endVert); if (ends[0] == tri[2]) { + std::swap(triEdge[1], triEdge[2]); std::swap(tri[1], tri[2]); std::swap(ends[1], ends[2]); } DEBUG_ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0], topologyErr, "These 3 edges do not form a triangle!"); - addTri(face, tri, normal, halfedgeRef[firstEdge]); + addTri(face, triEdge, normal, halfedgeRef[firstEdge]); } else if (numEdge == 4) { // Pair of triangles - int mapping[4] = {halfedge_[firstEdge].startVert, - halfedge_[firstEdge + 1].startVert, - halfedge_[firstEdge + 2].startVert, - halfedge_[firstEdge + 3].startVert}; const mat2x3 projection = GetAxisAlignedProjection(normal); auto triCCW = [&projection, this](const ivec3 tri) { - return CCW(projection * this->vertPos_[tri[0]], - projection * this->vertPos_[tri[1]], - projection * this->vertPos_[tri[2]], epsilon_) >= 0; + return CCW(projection * this->vertPos_[halfedge_[tri[0]].startVert], + projection * this->vertPos_[halfedge_[tri[1]].startVert], + projection * this->vertPos_[halfedge_[tri[2]].startVert], + epsilon_) >= 0; }; - ivec3 tri0(halfedge_[firstEdge].startVert, halfedge_[firstEdge].endVert, - -1); - ivec3 tri1(-1, -1, tri0[0]); - for (const int i : {1, 2, 3}) { - if (halfedge_[firstEdge + i].startVert == tri0[1]) { - tri0[2] = halfedge_[firstEdge + i].endVert; - tri1[0] = tri0[2]; - } - if (halfedge_[firstEdge + i].endVert == tri0[0]) { - tri1[1] = halfedge_[firstEdge + i].startVert; - } - } - DEBUG_ASSERT(la::all(la::gequal(tri0, ivec3(0))) && - la::all(la::gequal(tri1, ivec3(0))), - topologyErr, "non-manifold quad!"); - bool firstValid = triCCW(tri0) && triCCW(tri1); - tri0[2] = tri1[1]; - tri1[2] = tri0[1]; - bool secondValid = triCCW(tri0) && triCCW(tri1); + std::vector quad = AssembleHalfedges( + halfedge_.cbegin() + faceEdge[face], + halfedge_.cbegin() + faceEdge[face + 1], faceEdge[face])[0]; - if (!secondValid) { - tri0[2] = tri1[0]; - tri1[2] = tri0[0]; - } else if (firstValid) { - vec3 firstCross = vertPos_[tri0[0]] - vertPos_[tri1[0]]; - vec3 secondCross = vertPos_[tri0[1]] - vertPos_[tri1[1]]; - if (la::dot(firstCross, firstCross) < - la::dot(secondCross, secondCross)) { - tri0[2] = tri1[0]; - tri1[2] = tri0[0]; + const la::mat tris[2] = { + {{quad[0], quad[1], quad[2]}, {quad[0], quad[2], quad[3]}}, + {{quad[1], quad[2], quad[3]}, {quad[0], quad[1], quad[3]}}}; + + int choice = 0; + + if (!(triCCW(tris[0][0]) && triCCW(tris[0][1]))) { + choice = 1; + } else if (triCCW(tris[1][0]) && triCCW(tris[1][1])) { + vec3 diag0 = vertPos_[halfedge_[quad[0]].startVert] - + vertPos_[halfedge_[quad[2]].startVert]; + vec3 diag1 = vertPos_[halfedge_[quad[1]].startVert] - + vertPos_[halfedge_[quad[3]].startVert]; + if (la::length2(diag0) > la::length2(diag1)) { + choice = 1; } } - for (const auto& tri : {tri0, tri1}) { + for (const auto& tri : tris[choice]) { addTri(face, tri, normal, halfedgeRef[firstEdge]); } } else { // General triangulation @@ -127,10 +174,12 @@ void Manifold::Impl::Face2Tri(const Vec& faceEdge, auto generalTriangulation = [&](int face) { const vec3 normal = faceNormal_[face]; const mat2x3 projection = GetAxisAlignedProjection(normal); - const PolygonsIdx polys = - Face2Polygons(halfedge_.cbegin() + faceEdge[face], - halfedge_.cbegin() + faceEdge[face + 1], projection); - return TriangulateIdx(polys, epsilon_); + const PolygonsIdx polys = ProjectPolygons( + AssembleHalfedges(halfedge_.cbegin() + faceEdge[face], + halfedge_.cbegin() + faceEdge[face + 1], + faceEdge[face]), + halfedge_, vertPos_, projection); + return TriangulateIdx(polys, epsilon_, allowConvex); }; #if (MANIFOLD_PAR == 1) && __has_include() tbb::task_group group; @@ -156,13 +205,17 @@ void Manifold::Impl::Face2Tri(const Vec& faceEdge, // prefix sum computation (assign unique index to each face) and preallocation exclusive_scan(triCount.begin(), triCount.end(), triCount.begin(), 0_uz); triVerts.resize(triCount.back()); + triProp.resize(triCount.back()); triNormal.resize(triCount.back()); triRef.resize(triCount.back()); auto processFace2 = std::bind( processFace, [&](size_t face) { return std::move(results[face]); }, [&](size_t face, ivec3 tri, vec3 normal, TriRef r) { - triVerts[triCount[face]] = tri; + for (const int i : {0, 1, 2}) { + triVerts[triCount[face]][i] = halfedge_[tri[i]].startVert; + triProp[triCount[face]][i] = halfedge_[tri[i]].propVert; + } triNormal[triCount[face]] = normal; triRef[triCount[face]] = r; triCount[face]++; @@ -177,8 +230,15 @@ void Manifold::Impl::Face2Tri(const Vec& faceEdge, triRef.reserve(faceEdge.size()); auto processFace2 = std::bind( processFace, generalTriangulation, - [&](size_t _face, ivec3 tri, vec3 normal, TriRef r) { - triVerts.push_back(tri); + [&](size_t, ivec3 tri, vec3 normal, TriRef r) { + ivec3 verts; + ivec3 props; + for (const int i : {0, 1, 2}) { + verts[i] = halfedge_[tri[i]].startVert; + props[i] = halfedge_[tri[i]].propVert; + } + triVerts.push_back(verts); + triProp.push_back(props); triNormal.push_back(normal); triRef.push_back(r); }, @@ -189,41 +249,7 @@ void Manifold::Impl::Face2Tri(const Vec& faceEdge, #endif faceNormal_ = std::move(triNormal); - CreateHalfedges(triVerts); -} - -/** - * Returns a set of 2D polygons formed by the input projection of the vertices - * of the list of Halfedges, which must be an even-manifold, meaning each vert - * must be referenced the same number of times as a startVert and endVert. - */ -PolygonsIdx Manifold::Impl::Face2Polygons(VecView::IterC start, - VecView::IterC end, - mat2x3 projection) const { - std::multimap vert_edge; - for (auto edge = start; edge != end; ++edge) { - vert_edge.emplace( - std::make_pair(edge->startVert, static_cast(edge - start))); - } - - PolygonsIdx polys; - int startEdge = 0; - int thisEdge = startEdge; - while (1) { - if (thisEdge == startEdge) { - if (vert_edge.empty()) break; - startEdge = vert_edge.begin()->second; - thisEdge = startEdge; - polys.push_back({}); - } - int vert = (start + thisEdge)->startVert; - polys.back().push_back({projection * vertPos_[vert], vert}); - const auto result = vert_edge.find((start + thisEdge)->endVert); - DEBUG_ASSERT(result != vert_edge.end(), topologyErr, "non-manifold edge"); - thisEdge = result->second; - vert_edge.erase(result); - } - return polys; + CreateHalfedges(triProp, triVerts); } Polygons Manifold::Impl::Slice(double height) const { @@ -231,12 +257,9 @@ Polygons Manifold::Impl::Slice(double height) const { plane.min.z = plane.max.z = height; Vec query; query.push_back(plane); - const SparseIndices collisions = - collider_.Collisions(query.cview()); std::unordered_set tris; - for (size_t i = 0; i < collisions.size(); ++i) { - const int tri = collisions.Get(i, 1); + auto recordCollision = [&](int, int tri) { double min = std::numeric_limits::infinity(); double max = -std::numeric_limits::infinity(); for (const int j : {0, 1, 2}) { @@ -248,7 +271,10 @@ Polygons Manifold::Impl::Slice(double height) const { if (min <= height && max > height) { tris.insert(tri); } - } + }; + + auto recorder = MakeSimpleRecorder(recordCollision); + collider_.Collisions(query.cview(), recorder, false); Polygons polys; while (!tris.empty()) { @@ -303,7 +329,8 @@ Polygons Manifold::Impl::Project() const { cusps.begin()); PolygonsIdx polysIndexed = - Face2Polygons(cusps.cbegin(), cusps.cend(), projection); + ProjectPolygons(AssembleHalfedges(cusps.cbegin(), cusps.cend(), 0), cusps, + vertPos_, projection); Polygons polys; for (const auto& poly : polysIndexed) { diff --git a/thirdparty/manifold/src/hashtable.h b/thirdparty/manifold/src/hashtable.h index 052c34e09c..96107c357f 100644 --- a/thirdparty/manifold/src/hashtable.h +++ b/thirdparty/manifold/src/hashtable.h @@ -16,13 +16,12 @@ #include -#include "./utils.h" -#include "./vec.h" +#include "utils.h" +#include "vec.h" namespace { -typedef unsigned long long int Uint64; -typedef Uint64 (*hash_fun_t)(Uint64); -inline constexpr Uint64 kOpen = std::numeric_limits::max(); +using hash_fun_t = uint64_t(uint64_t); +inline constexpr uint64_t kOpen = std::numeric_limits::max(); template T AtomicCAS(T& target, T compare, T val) { @@ -45,13 +44,6 @@ T AtomicLoad(const T& target) { return tar.load(std::memory_order_acquire); } -// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key -inline Uint64 hash64bit(Uint64 x) { - x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; - x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; - x = x ^ (x >> 31); - return x; -} } // namespace namespace manifold { @@ -59,7 +51,7 @@ namespace manifold { template class HashTableD { public: - HashTableD(Vec& keys, Vec& values, std::atomic& used, + HashTableD(Vec& keys, Vec& values, std::atomic& used, uint32_t step = 1) : step_{step}, keys_{keys}, values_{values}, used_{used} {} @@ -70,12 +62,12 @@ class HashTableD { static_cast(Size()); } - void Insert(Uint64 key, const V& val) { + void Insert(uint64_t key, const V& val) { uint32_t idx = H(key) & (Size() - 1); while (1) { if (Full()) return; - Uint64& k = keys_[idx]; - const Uint64 found = AtomicCAS(k, kOpen, key); + uint64_t& k = keys_[idx]; + const uint64_t found = AtomicCAS(k, kOpen, key); if (found == kOpen) { used_.fetch_add(1, std::memory_order_relaxed); values_[idx] = val; @@ -86,10 +78,10 @@ class HashTableD { } } - V& operator[](Uint64 key) { + V& operator[](uint64_t key) { uint32_t idx = H(key) & (Size() - 1); while (1) { - const Uint64 k = AtomicLoad(keys_[idx]); + const uint64_t k = AtomicLoad(keys_[idx]); if (k == key || k == kOpen) { return values_[idx]; } @@ -97,10 +89,10 @@ class HashTableD { } } - const V& operator[](Uint64 key) const { + const V& operator[](uint64_t key) const { uint32_t idx = H(key) & (Size() - 1); while (1) { - const Uint64 k = AtomicLoad(keys_[idx]); + const uint64_t k = AtomicLoad(keys_[idx]); if (k == key || k == kOpen) { return values_[idx]; } @@ -108,13 +100,13 @@ class HashTableD { } } - Uint64 KeyAt(int idx) const { return AtomicLoad(keys_[idx]); } + uint64_t KeyAt(int idx) const { return AtomicLoad(keys_[idx]); } V& At(int idx) { return values_[idx]; } const V& At(int idx) const { return values_[idx]; } private: uint32_t step_; - VecView keys_; + VecView keys_; VecView values_; std::atomic& used_; }; @@ -157,10 +149,10 @@ class HashTable { Vec& GetValueStore() { return values_; } - static Uint64 Open() { return kOpen; } + static uint64_t Open() { return kOpen; } private: - Vec keys_; + Vec keys_; Vec values_; std::atomic used_ = 0; uint32_t step_; diff --git a/thirdparty/manifold/src/impl.cpp b/thirdparty/manifold/src/impl.cpp index afa45dff30..ba9a5f71f3 100644 --- a/thirdparty/manifold/src/impl.cpp +++ b/thirdparty/manifold/src/impl.cpp @@ -12,38 +12,113 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./impl.h" +#include "impl.h" #include #include +#include #include #include -#include "./hashtable.h" -#include "./mesh_fixes.h" -#include "./parallel.h" -#include "./svd.h" - -#ifdef MANIFOLD_EXPORT -#include - -#include -#endif +#include "csg_tree.h" +#include "hashtable.h" +#include "manifold/optional_assert.h" +#include "mesh_fixes.h" +#include "parallel.h" +#include "shared.h" +#include "svd.h" namespace { using namespace manifold; -constexpr uint64_t kRemove = std::numeric_limits::max(); - -void AtomicAddVec3(vec3& target, const vec3& add) { - for (int i : {0, 1, 2}) { - std::atomic& tar = - reinterpret_cast&>(target[i]); - double old_val = tar.load(std::memory_order_relaxed); - while (!tar.compare_exchange_weak(old_val, old_val + add[i], - std::memory_order_relaxed)) { +/** + * Returns arc cosine of 𝑥. + * + * @return value in range [0,M_PI] + * @return NAN if 𝑥 ∈ {NAN,+INFINITY,-INFINITY} + * @return NAN if 𝑥 ∉ [-1,1] + */ +double sun_acos(double x) { + /* + * Origin of acos function: FreeBSD /usr/src/lib/msun/src/e_acos.c + * Changed the use of union to memcpy to avoid undefined behavior. + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + constexpr double pio2_hi = + 1.57079632679489655800e+00; /* 0x3FF921FB, 0x54442D18 */ + constexpr double pio2_lo = + 6.12323399573676603587e-17; /* 0x3C91A626, 0x33145C07 */ + constexpr double pS0 = + 1.66666666666666657415e-01; /* 0x3FC55555, 0x55555555 */ + constexpr double pS1 = + -3.25565818622400915405e-01; /* 0xBFD4D612, 0x03EB6F7D */ + constexpr double pS2 = + 2.01212532134862925881e-01; /* 0x3FC9C155, 0x0E884455 */ + constexpr double pS3 = + -4.00555345006794114027e-02; /* 0xBFA48228, 0xB5688F3B */ + constexpr double pS4 = + 7.91534994289814532176e-04; /* 0x3F49EFE0, 0x7501B288 */ + constexpr double pS5 = + 3.47933107596021167570e-05; /* 0x3F023DE1, 0x0DFDF709 */ + constexpr double qS1 = + -2.40339491173441421878e+00; /* 0xC0033A27, 0x1C8A2D4B */ + constexpr double qS2 = + 2.02094576023350569471e+00; /* 0x40002AE5, 0x9C598AC8 */ + constexpr double qS3 = + -6.88283971605453293030e-01; /* 0xBFE6066C, 0x1B8D0159 */ + constexpr double qS4 = + 7.70381505559019352791e-02; /* 0x3FB3B8C5, 0xB12E9282 */ + auto R = [=](double z) { + double p, q; + p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5))))); + q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4))); + return p / q; + }; + double z, w, s, c, df; + uint64_t xx; + uint32_t hx, lx, ix; + memcpy(&xx, &x, sizeof(xx)); + hx = xx >> 32; + ix = hx & 0x7fffffff; + /* |x| >= 1 or nan */ + if (ix >= 0x3ff00000) { + lx = xx; + if (((ix - 0x3ff00000) | lx) == 0) { + /* acos(1)=0, acos(-1)=pi */ + if (hx >> 31) return 2 * pio2_hi + 0x1p-120f; + return 0; } + return 0 / (x - x); } + /* |x| < 0.5 */ + if (ix < 0x3fe00000) { + if (ix <= 0x3c600000) /* |x| < 2**-57 */ + return pio2_hi + 0x1p-120f; + return pio2_hi - (x - (pio2_lo - x * R(x * x))); + } + /* x < -0.5 */ + if (hx >> 31) { + z = (1.0 + x) * 0.5; + s = sqrt(z); + w = R(z) * s - pio2_lo; + return 2 * (pio2_hi - (s + w)); + } + /* x > 0.5 */ + z = (1.0 - x) * 0.5; + s = sqrt(z); + memcpy(&xx, &s, sizeof(xx)); + xx &= 0xffffffff00000000; + memcpy(&df, &xx, sizeof(xx)); + c = (z - df * df) / (s + df); + w = R(z) * s + c; + return 2 * (df + w); } struct Transform4x3 { @@ -52,143 +127,12 @@ struct Transform4x3 { vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); } }; -template -struct AssignNormals { - VecView faceNormal; - VecView vertNormal; - VecView vertPos; - VecView halfedges; - - void operator()(const int face) { - vec3& triNormal = faceNormal[face]; - - ivec3 triVerts; - for (int i : {0, 1, 2}) triVerts[i] = halfedges[3 * face + i].startVert; - - vec3 edge[3]; - for (int i : {0, 1, 2}) { - const int j = (i + 1) % 3; - edge[i] = la::normalize(vertPos[triVerts[j]] - vertPos[triVerts[i]]); - } - - if (calculateTriNormal) { - triNormal = la::normalize(la::cross(edge[0], edge[1])); - if (std::isnan(triNormal.x)) triNormal = vec3(0, 0, 1); - } - - // corner angles - vec3 phi; - double dot = -la::dot(edge[2], edge[0]); - phi[0] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot)); - dot = -la::dot(edge[0], edge[1]); - phi[1] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot)); - phi[2] = kPi - phi[0] - phi[1]; - - // assign weighted sum - for (int i : {0, 1, 2}) { - AtomicAddVec3(vertNormal[triVerts[i]], phi[i] * triNormal); - } - } -}; - struct UpdateMeshID { const HashTableD meshIDold2new; void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; } }; -struct CoplanarEdge { - VecView> face2face; - VecView triArea; - VecView halfedge; - VecView vertPos; - VecView triRef; - VecView triProp; - const int numProp; - const double epsilon; - const double tolerance; - - void operator()(const int edgeIdx) { - const Halfedge edge = halfedge[edgeIdx]; - const Halfedge pair = halfedge[edge.pairedHalfedge]; - const int edgeFace = edgeIdx / 3; - const int pairFace = edge.pairedHalfedge / 3; - - if (triRef[edgeFace].meshID != triRef[pairFace].meshID) return; - - const vec3 base = vertPos[edge.startVert]; - const int baseNum = edgeIdx - 3 * edgeFace; - const int jointNum = edge.pairedHalfedge - 3 * pairFace; - - if (numProp > 0) { - if (triProp[edgeFace][baseNum] != triProp[pairFace][Next3(jointNum)] || - triProp[edgeFace][Next3(baseNum)] != triProp[pairFace][jointNum]) - return; - } - - if (!edge.IsForward()) return; - - const int edgeNum = baseNum == 0 ? 2 : baseNum - 1; - const int pairNum = jointNum == 0 ? 2 : jointNum - 1; - const vec3 jointVec = vertPos[pair.startVert] - base; - const vec3 edgeVec = - vertPos[halfedge[3 * edgeFace + edgeNum].startVert] - base; - const vec3 pairVec = - vertPos[halfedge[3 * pairFace + pairNum].startVert] - base; - - const double length = std::max(la::length(jointVec), la::length(edgeVec)); - const double lengthPair = - std::max(la::length(jointVec), la::length(pairVec)); - vec3 normal = la::cross(jointVec, edgeVec); - const double area = la::length(normal); - const double areaPair = la::length(la::cross(pairVec, jointVec)); - - // make sure we only write this once - if (edgeIdx % 3 == 0) triArea[edgeFace] = area; - // Don't link degenerate triangles - if (area < length * epsilon || areaPair < lengthPair * epsilon) return; - - const double volume = std::abs(la::dot(normal, pairVec)); - // Only operate on coplanar triangles - if (volume > std::max(area, areaPair) * tolerance) return; - - face2face[edgeIdx] = std::make_pair(edgeFace, pairFace); - } -}; - -struct CheckCoplanarity { - VecView comp2tri; - VecView halfedge; - VecView vertPos; - std::vector* components; - const double tolerance; - - void operator()(int tri) { - const int component = (*components)[tri]; - const int referenceTri = - reinterpret_cast*>(&comp2tri[component]) - ->load(std::memory_order_relaxed); - if (referenceTri < 0 || referenceTri == tri) return; - - const vec3 origin = vertPos[halfedge[3 * referenceTri].startVert]; - const vec3 normal = la::normalize( - la::cross(vertPos[halfedge[3 * referenceTri + 1].startVert] - origin, - vertPos[halfedge[3 * referenceTri + 2].startVert] - origin)); - - for (const int i : {0, 1, 2}) { - const vec3 vert = vertPos[halfedge[3 * tri + i].startVert]; - // If any component vertex is not coplanar with the component's reference - // triangle, unmark the entire component so that none of its triangles are - // marked coplanar. - if (std::abs(la::dot(normal, vert - origin)) > tolerance) { - reinterpret_cast*>(&comp2tri[component]) - ->store(-1, std::memory_order_relaxed); - break; - } - } - } -}; - int GetLabels(std::vector& components, const Vec>& edges, int numNodes) { UnionFind<> uf(numNodes); @@ -199,19 +143,6 @@ int GetLabels(std::vector& components, return uf.connectedComponents(components); } - -void DedupePropVerts(manifold::Vec& triProp, - const Vec>& vert2vert, - size_t numPropVert) { - ZoneScoped; - std::vector vertLabels; - const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert); - - std::vector label2vert(numLabels); - for (size_t v = 0; v < numPropVert; ++v) label2vert[vertLabels[v]] = v; - for (auto& prop : triProp) - for (int i : {0, 1, 2}) prop[i] = label2vert[vertLabels[prop[i]]]; -} } // namespace namespace manifold { @@ -271,7 +202,7 @@ Manifold::Impl::Impl(Shape shape, const mat3x4 m) { CreateHalfedges(triVerts); Finish(); InitializeOriginal(); - CreateFaces(); + MarkCoplanar(); } void Manifold::Impl::RemoveUnreferencedVerts() { @@ -297,124 +228,165 @@ void Manifold::Impl::InitializeOriginal(bool keepFaceID) { const int meshID = ReserveIDs(1); meshRelation_.originalID = meshID; auto& triRef = meshRelation_.triRef; - triRef.resize(NumTri()); + triRef.resize_nofill(NumTri()); for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(), [meshID, keepFaceID, &triRef](const int tri) { - triRef[tri] = {meshID, meshID, tri, - keepFaceID ? triRef[tri].faceID : tri}; + triRef[tri] = {meshID, meshID, -1, + keepFaceID ? triRef[tri].coplanarID : tri}; }); meshRelation_.meshIDtransform.clear(); meshRelation_.meshIDtransform[meshID] = {meshID}; } -void Manifold::Impl::CreateFaces() { +void Manifold::Impl::MarkCoplanar() { ZoneScoped; - Vec> face2face(halfedge_.size(), {-1, -1}); - Vec> vert2vert(halfedge_.size(), {-1, -1}); - Vec triArea(NumTri()); + const int numTri = NumTri(); + struct TriPriority { + double area2; + int tri; + }; + Vec triPriority(numTri); + for_each_n(autoPolicy(numTri), countAt(0), numTri, + [&triPriority, this](int tri) { + meshRelation_.triRef[tri].coplanarID = -1; + if (halfedge_[3 * tri].startVert < 0) { + triPriority[tri] = {0, tri}; + return; + } + const vec3 v = vertPos_[halfedge_[3 * tri].startVert]; + triPriority[tri] = { + length2(cross(vertPos_[halfedge_[3 * tri].endVert] - v, + vertPos_[halfedge_[3 * tri + 1].endVert] - v)), + tri}; + }); - const size_t numProp = NumProp(); - if (numProp > 0) { - for_each_n( - autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(), - [&vert2vert, numProp, this](const int edgeIdx) { - const Halfedge edge = halfedge_[edgeIdx]; - const Halfedge pair = halfedge_[edge.pairedHalfedge]; - const int edgeFace = edgeIdx / 3; - const int pairFace = edge.pairedHalfedge / 3; + stable_sort(triPriority.begin(), triPriority.end(), + [](auto a, auto b) { return a.area2 > b.area2; }); - if (meshRelation_.triRef[edgeFace].meshID != - meshRelation_.triRef[pairFace].meshID) - return; + Vec interiorHalfedges; + for (const auto tp : triPriority) { + if (meshRelation_.triRef[tp.tri].coplanarID >= 0) continue; - const int baseNum = edgeIdx - 3 * edgeFace; - const int jointNum = edge.pairedHalfedge - 3 * pairFace; + meshRelation_.triRef[tp.tri].coplanarID = tp.tri; + if (halfedge_[3 * tp.tri].startVert < 0) continue; + const vec3 base = vertPos_[halfedge_[3 * tp.tri].startVert]; + const vec3 normal = faceNormal_[tp.tri]; + interiorHalfedges.resize(3); + interiorHalfedges[0] = 3 * tp.tri; + interiorHalfedges[1] = 3 * tp.tri + 1; + interiorHalfedges[2] = 3 * tp.tri + 2; + while (!interiorHalfedges.empty()) { + const int h = + NextHalfedge(halfedge_[interiorHalfedges.back()].pairedHalfedge); + interiorHalfedges.pop_back(); + if (meshRelation_.triRef[h / 3].coplanarID >= 0) continue; - const int prop0 = meshRelation_.triProperties[edgeFace][baseNum]; - const int prop1 = - meshRelation_ - .triProperties[pairFace][jointNum == 2 ? 0 : jointNum + 1]; - if (prop0 == prop1) return; + const vec3 v = vertPos_[halfedge_[h].endVert]; + if (std::abs(dot(v - base, normal)) < tolerance_) { + meshRelation_.triRef[h / 3].coplanarID = tp.tri; - bool propEqual = true; - for (size_t p = 0; p < numProp; ++p) { - if (meshRelation_.properties[numProp * prop0 + p] != - meshRelation_.properties[numProp * prop1 + p]) { - propEqual = false; - break; - } - } - if (propEqual) { - vert2vert[edgeIdx] = std::make_pair(prop0, prop1); - } - }); - DedupePropVerts(meshRelation_.triProperties, vert2vert, NumPropVert()); - } - - for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(), - CoplanarEdge({face2face, triArea, halfedge_, vertPos_, - meshRelation_.triRef, meshRelation_.triProperties, - meshRelation_.numProp, epsilon_, tolerance_})); - - std::vector components; - const int numComponent = GetLabels(components, face2face, NumTri()); - - Vec comp2tri(numComponent, -1); - for (size_t tri = 0; tri < NumTri(); ++tri) { - const int comp = components[tri]; - const int current = comp2tri[comp]; - if (current < 0 || triArea[tri] > triArea[current]) { - comp2tri[comp] = tri; - triArea[comp] = triArea[tri]; - } - } - - for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), NumTri(), - CheckCoplanarity( - {comp2tri, halfedge_, vertPos_, &components, tolerance_})); - - Vec& triRef = meshRelation_.triRef; - for (size_t tri = 0; tri < NumTri(); ++tri) { - const int referenceTri = comp2tri[components[tri]]; - if (referenceTri >= 0) { - triRef[tri].faceID = referenceTri; + if (interiorHalfedges.empty() || + h != halfedge_[interiorHalfedges.back()].pairedHalfedge) { + interiorHalfedges.push_back(h); + } else { + interiorHalfedges.pop_back(); + } + const int hNext = NextHalfedge(h); + interiorHalfedges.push_back(hNext); + } } } } /** - * Create the halfedge_ data structure from an input triVerts array like Mesh. + * Dereference duplicate property vertices if they are exactly floating-point + * equal. These unreferenced properties are then removed by CompactProps. */ -void Manifold::Impl::CreateHalfedges(const Vec& triVerts) { +void Manifold::Impl::DedupePropVerts() { ZoneScoped; - const size_t numTri = triVerts.size(); + const size_t numProp = NumProp(); + if (numProp == 0) return; + + Vec> vert2vert(halfedge_.size(), {-1, -1}); + for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(), + [&vert2vert, numProp, this](const int edgeIdx) { + const Halfedge edge = halfedge_[edgeIdx]; + const int edgeFace = edgeIdx / 3; + const int pairFace = edge.pairedHalfedge / 3; + + if (meshRelation_.triRef[edgeFace].meshID != + meshRelation_.triRef[pairFace].meshID) + return; + + const int prop0 = halfedge_[edgeIdx].propVert; + const int prop1 = + halfedge_[NextHalfedge(edge.pairedHalfedge)].propVert; + bool propEqual = true; + for (size_t p = 0; p < numProp; ++p) { + if (properties_[numProp * prop0 + p] != + properties_[numProp * prop1 + p]) { + propEqual = false; + break; + } + } + if (propEqual) { + vert2vert[edgeIdx] = std::make_pair(prop0, prop1); + } + }); + + std::vector vertLabels; + const size_t numPropVert = NumPropVert(); + const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert); + + std::vector label2vert(numLabels); + for (size_t v = 0; v < numPropVert; ++v) label2vert[vertLabels[v]] = v; + for (Halfedge& edge : halfedge_) + edge.propVert = label2vert[vertLabels[edge.propVert]]; +} + +constexpr int kRemovedHalfedge = -2; + +/** + * Create the halfedge_ data structure from a list of triangles. If the optional + * prop2vert array is missing, it's assumed these triangles are are pointing to + * both vert and propVert indices. If prop2vert is present, the triangles are + * assumed to be pointing to propVert indices only. The prop2vert array is used + * to map the propVert indices to vert indices. + */ +void Manifold::Impl::CreateHalfedges(const Vec& triProp, + const Vec& triVert) { + ZoneScoped; + const size_t numTri = triProp.size(); const int numHalfedge = 3 * numTri; // drop the old value first to avoid copy - halfedge_.resize(0); - halfedge_.resize(numHalfedge); + halfedge_.clear(true); + halfedge_.resize_nofill(numHalfedge); Vec edge(numHalfedge); Vec ids(numHalfedge); auto policy = autoPolicy(numTri, 1e5); sequence(ids.begin(), ids.end()); for_each_n(policy, countAt(0), numTri, - [this, &edge, &triVerts](const int tri) { - const ivec3& verts = triVerts[tri]; + [this, &edge, &triProp, &triVert](const int tri) { + const ivec3& props = triProp[tri]; for (const int i : {0, 1, 2}) { const int j = (i + 1) % 3; const int e = 3 * tri + i; - halfedge_[e] = {verts[i], verts[j], -1}; + const int v0 = triVert.empty() ? props[i] : triVert[tri][i]; + const int v1 = triVert.empty() ? props[j] : triVert[tri][j]; + DEBUG_ASSERT(v0 != v1, logicErr, "topological degeneracy"); + halfedge_[e] = {v0, v1, -1, props[i]}; // Sort the forward halfedges in front of the backward ones // by setting the highest-order bit. - edge[e] = uint64_t(verts[i] < verts[j] ? 1 : 0) << 63 | - ((uint64_t)std::min(verts[i], verts[j])) << 32 | - std::max(verts[i], verts[j]); + edge[e] = uint64_t(v0 < v1 ? 1 : 0) << 63 | + ((uint64_t)std::min(v0, v1)) << 32 | + std::max(v0, v1); } }); // Stable sort is required here so that halfedges from the same face are // paired together (the triangles were created in face order). In some // degenerate situations the triangulator can add the same internal edge in // two different faces, causing this edge to not be 2-manifold. These are - // fixed by duplicating verts in SimplifyTopology. + // fixed by duplicating verts in CleanupTopology. stable_sort(ids.begin(), ids.end(), [&edge](const int& a, const int& b) { return edge[a] < edge[b]; }); @@ -422,26 +394,62 @@ void Manifold::Impl::CreateHalfedges(const Vec& triVerts) { // Mark opposed triangles for removal - this may strand unreferenced verts // which are removed later by RemoveUnreferencedVerts() and Finish(). const int numEdge = numHalfedge / 2; - for (int i = 0; i < numEdge; ++i) { + + const auto body = [&](int i, int consecutiveStart, int segmentEnd) { const int pair0 = ids[i]; - Halfedge h0 = halfedge_[pair0]; - int k = i + numEdge; + Halfedge& h0 = halfedge_[pair0]; + int k = consecutiveStart + numEdge; while (1) { const int pair1 = ids[k]; - Halfedge h1 = halfedge_[pair1]; + Halfedge& h1 = halfedge_[pair1]; if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break; if (halfedge_[NextHalfedge(pair0)].endVert == halfedge_[NextHalfedge(pair1)].endVert) { - h0 = {-1, -1, -1}; - h1 = {-1, -1, -1}; + h0.pairedHalfedge = h1.pairedHalfedge = kRemovedHalfedge; // Reorder so that remaining edges pair up if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]); break; } ++k; - if (k >= numHalfedge) break; + if (k >= segmentEnd + numEdge) break; } + if (i + 1 == segmentEnd) return consecutiveStart; + Halfedge& h1 = halfedge_[ids[i + 1]]; + if (h0.startVert == h1.startVert && h0.endVert == h1.endVert) + return consecutiveStart; + return i + 1; + }; + +#if MANIFOLD_PAR == 1 + Vec> ranges; + const int increment = std::min( + std::max(numEdge / tbb::this_task_arena::max_concurrency() / 2, 1024), + numEdge); + const auto duplicated = [&](int a, int b) { + const Halfedge& h0 = halfedge_[ids[a]]; + const Halfedge& h1 = halfedge_[ids[b]]; + return h0.startVert == h1.startVert && h0.endVert == h1.endVert; + }; + int end = 0; + while (end < numEdge) { + const int start = end; + end = std::min(end + increment, numEdge); + // make sure duplicated halfedges are in the same partition + while (end < numEdge && duplicated(end - 1, end)) end++; + ranges.push_back(std::make_pair(start, end)); } + for_each(ExecutionPolicy::Par, ranges.begin(), ranges.end(), + [&](const std::pair& range) { + const auto [start, end] = range; + int consecutiveStart = start; + for (int i = start; i < end; ++i) + consecutiveStart = body(i, consecutiveStart, end); + }); +#else + int consecutiveStart = 0; + for (int i = 0; i < numEdge; ++i) + consecutiveStart = body(i, consecutiveStart, numEdge); +#endif // Once sorted, the first half of the range is the forward halfedges, which // correspond to their backward pair at the same offset in the second half @@ -449,9 +457,11 @@ void Manifold::Impl::CreateHalfedges(const Vec& triVerts) { for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) { const int pair0 = ids[i]; const int pair1 = ids[i + numEdge]; - if (halfedge_[pair0].startVert >= 0) { + if (halfedge_[pair0].pairedHalfedge != kRemovedHalfedge) { halfedge_[pair0].pairedHalfedge = pair1; halfedge_[pair1].pairedHalfedge = pair0; + } else { + halfedge_[pair0] = halfedge_[pair1] = {-1, -1, -1}; } }); } @@ -468,13 +478,13 @@ void Manifold::Impl::Update() { collider_.UpdateBoxes(faceBox); } -void Manifold::Impl::MarkFailure(Error status) { +void Manifold::Impl::MakeEmpty(Error status) { bBox_ = Box(); - vertPos_.resize(0); - halfedge_.resize(0); - vertNormal_.resize(0); - faceNormal_.resize(0); - halfedgeTangent_.resize(0); + vertPos_.clear(); + halfedge_.clear(); + vertNormal_.clear(); + faceNormal_.clear(); + halfedgeTangent_.clear(); meshRelation_ = MeshRelationD(); status_ = status; } @@ -489,15 +499,14 @@ void Manifold::Impl::WarpBatch(std::function)> warpFunc) { warpFunc(vertPos_.view()); CalculateBBox(); if (!IsFinite()) { - MarkFailure(Error::NonFiniteVertex); + MakeEmpty(Error::NonFiniteVertex); return; } Update(); - faceNormal_.resize(0); // force recalculation of triNormal - CalculateNormals(); + faceNormal_.clear(); // force recalculation of triNormal SetEpsilon(); Finish(); - CreateFaces(); + MarkCoplanar(); meshRelation_.originalID = -1; } @@ -511,13 +520,15 @@ Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const { return result; } if (!all(la::isfinite(transform_))) { - result.MarkFailure(Error::NonFiniteVertex); + result.MakeEmpty(Error::NonFiniteVertex); return result; } result.collider_ = collider_; result.meshRelation_ = meshRelation_; result.epsilon_ = epsilon_; result.tolerance_ = tolerance_; + result.numProp_ = numProp_; + result.properties_ = properties_; result.bBox_ = bBox_; result.halfedge_ = halfedge_; result.halfedgeTangent_.resize(halfedgeTangent_.size()); @@ -592,23 +603,73 @@ void Manifold::Impl::SetEpsilon(double minEpsilon, bool useSingle) { void Manifold::Impl::CalculateNormals() { ZoneScoped; vertNormal_.resize(NumVert()); - auto policy = autoPolicy(NumTri(), 1e4); - fill(vertNormal_.begin(), vertNormal_.end(), vec3(0.0)); - bool calculateTriNormal = false; + auto policy = autoPolicy(NumTri()); + + std::vector> vertHalfedgeMap(NumVert()); + for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) { + vertHalfedgeMap[vert] = std::numeric_limits::max(); + }); + + auto atomicMin = [&vertHalfedgeMap](int value, int vert) { + if (vert < 0) return; + int old = std::numeric_limits::max(); + while (!vertHalfedgeMap[vert].compare_exchange_strong(old, value)) + if (old < value) break; + }; if (faceNormal_.size() != NumTri()) { faceNormal_.resize(NumTri()); - calculateTriNormal = true; + for_each_n(policy, countAt(0), NumTri(), [&](const int face) { + vec3& triNormal = faceNormal_[face]; + if (halfedge_[3 * face].startVert < 0) { + triNormal = vec3(0, 0, 1); + return; + } + + ivec3 triVerts; + for (int i : {0, 1, 2}) { + int v = halfedge_[3 * face + i].startVert; + triVerts[i] = v; + atomicMin(3 * face + i, v); + } + + vec3 edge[3]; + for (int i : {0, 1, 2}) { + const int j = (i + 1) % 3; + edge[i] = la::normalize(vertPos_[triVerts[j]] - vertPos_[triVerts[i]]); + } + triNormal = la::normalize(la::cross(edge[0], edge[1])); + if (std::isnan(triNormal.x)) triNormal = vec3(0, 0, 1); + }); + } else { + for_each_n(policy, countAt(0), halfedge_.size(), + [&](const int i) { atomicMin(i, halfedge_[i].startVert); }); } - if (calculateTriNormal) - for_each_n( - policy, countAt(0), NumTri(), - AssignNormals({faceNormal_, vertNormal_, vertPos_, halfedge_})); - else - for_each_n( - policy, countAt(0), NumTri(), - AssignNormals({faceNormal_, vertNormal_, vertPos_, halfedge_})); - for_each(policy, vertNormal_.begin(), vertNormal_.end(), - [](vec3& v) { v = SafeNormalize(v); }); + + for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) { + int firstEdge = vertHalfedgeMap[vert].load(); + // not referenced + if (firstEdge == std::numeric_limits::max()) { + vertNormal_[vert] = vec3(0.0); + return; + } + vec3 normal = vec3(0.0); + ForVert(firstEdge, [&](int edge) { + ivec3 triVerts = {halfedge_[edge].startVert, halfedge_[edge].endVert, + halfedge_[NextHalfedge(edge)].endVert}; + vec3 currEdge = + la::normalize(vertPos_[triVerts[1]] - vertPos_[triVerts[0]]); + vec3 prevEdge = + la::normalize(vertPos_[triVerts[0]] - vertPos_[triVerts[2]]); + + // if it is not finite, this means that the triangle is degenerate, and we + // should just exclude it from the normal calculation... + if (!la::isfinite(currEdge[0]) || !la::isfinite(prevEdge[0])) return; + double dot = -la::dot(prevEdge, currEdge); + double phi = dot >= 1 ? 0 : (dot <= -1 ? kPi : sun_acos(dot)); + normal += phi * faceNormal_[edge / 3]; + }); + vertNormal_[vert] = SafeNormalize(normal); + }); } /** @@ -632,55 +693,13 @@ void Manifold::Impl::IncrementMeshIDs() { UpdateMeshID({meshIDold2new.D()})); } -/** - * Returns a sparse array of the bounding box overlaps between the edges of - * the input manifold, Q and the faces of this manifold. Returned indices only - * point to forward halfedges. - */ -SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q, - bool inverted) const { - ZoneScoped; - Vec edges = CreateTmpEdges(Q.halfedge_); - const size_t numEdge = edges.size(); - Vec QedgeBB(numEdge); - const auto& vertPos = Q.vertPos_; - auto policy = autoPolicy(numEdge, 1e5); - for_each_n( - policy, countAt(0), numEdge, [&QedgeBB, &edges, &vertPos](const int e) { - QedgeBB[e] = Box(vertPos[edges[e].first], vertPos[edges[e].second]); - }); - - SparseIndices q1p2(0); - if (inverted) - q1p2 = collider_.Collisions(QedgeBB.cview()); - else - q1p2 = collider_.Collisions(QedgeBB.cview()); - - if (inverted) - for_each(policy, countAt(0_uz), countAt(q1p2.size()), - ReindexEdge({edges, q1p2})); - else - for_each(policy, countAt(0_uz), countAt(q1p2.size()), - ReindexEdge({edges, q1p2})); - return q1p2; -} - -/** - * Returns a sparse array of the input vertices that project inside the XY - * bounding boxes of the faces of this manifold. - */ -SparseIndices Manifold::Impl::VertexCollisionsZ(VecView vertsIn, - bool inverted) const { - ZoneScoped; - if (inverted) - return collider_.Collisions(vertsIn); - else - return collider_.Collisions(vertsIn); -} - #ifdef MANIFOLD_DEBUG +/** + * Debugging output using high precision OBJ files with specialized comments + */ std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) { - stream << std::setprecision(17); // for double precision + stream << std::setprecision(19); // for double precision + stream << std::fixed; // for uniformity in output numbers stream << "# ======= begin mesh ======" << std::endl; stream << "# tolerance = " << impl.tolerance_ << std::endl; stream << "# epsilon = " << impl.epsilon_ << std::endl; @@ -699,13 +718,19 @@ std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) { stream << "# ======== end mesh =======" << std::endl; return stream; } -#endif -#ifdef MANIFOLD_EXPORT -Manifold Manifold::ImportMeshGL64(std::istream& stream) { +/** + * Import a mesh from a Wavefront OBJ file that was exported with Write. This + * function is the counterpart to Write and should be used with it. This + * function is not guaranteed to be able to import OBJ files not written by the + * Write function. + */ +Manifold Manifold::ReadOBJ(std::istream& stream) { + if (!stream.good()) return Invalid(); + MeshGL64 mesh; std::optional epsilon; - stream.precision(17); + stream >> std::setprecision(19); while (true) { char c = stream.get(); if (stream.eof()) break; @@ -718,7 +743,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) { stream.get(tmp.data(), SIZE, '\n'); if (strncmp(tmp.data(), "tolerance", SIZE) == 0) { // skip 3 letters - for (int i : {0, 1, 2}) stream.get(); + for (int _ : {0, 1, 2}) stream.get(); stream >> mesh.tolerance; } else if (strncmp(tmp.data(), "epsilon =", SIZE) == 0) { double tmp; @@ -727,7 +752,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) { } else { // add it back because it is not what we want int end = 0; - while (tmp[end] != 0 && end < SIZE) end++; + while (end < SIZE && tmp[end] != 0) end++; while (--end > -1) stream.putback(tmp[end]); } c = stream.get(); @@ -739,29 +764,42 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) { break; } case 'v': - for (int i : {0, 1, 2}) { + for (int _ : {0, 1, 2}) { double x; stream >> x; mesh.vertProperties.push_back(x); } break; case 'f': - for (int i : {0, 1, 2}) { + for (int _ : {0, 1, 2}) { uint64_t x; stream >> x; mesh.triVerts.push_back(x - 1); } break; + case '\r': case '\n': break; default: - DEBUG_ASSERT(false, userErr, "unexpected character in MeshGL64 import"); + DEBUG_ASSERT(false, userErr, "unexpected character in Manifold import"); } } auto m = std::make_shared(mesh); if (epsilon) m->SetEpsilon(*epsilon); return Manifold(m); } + +/** + * Export the mesh to a Wavefront OBJ file in a way that preserves the full + * 64-bit precision of the vertex positions, as well as storing metadata such as + * the tolerance and epsilon. Useful for debugging and testing. Files written + * by WriteOBJ should be read back in with ReadOBJ. + */ +bool Manifold::WriteOBJ(std::ostream& stream) const { + if (!stream.good()) return false; + stream << *this->GetCsgLeafNode().GetImpl(); + return true; +} #endif } // namespace manifold diff --git a/thirdparty/manifold/src/impl.h b/thirdparty/manifold/src/impl.h index d37a9a7d27..7af45bb257 100644 --- a/thirdparty/manifold/src/impl.h +++ b/thirdparty/manifold/src/impl.h @@ -15,12 +15,11 @@ #pragma once #include -#include "./collider.h" -#include "./shared.h" -#include "./sparse.h" -#include "./vec.h" +#include "collider.h" +#include "manifold/common.h" #include "manifold/manifold.h" -#include "manifold/polygon.h" +#include "shared.h" +#include "vec.h" namespace manifold { @@ -34,11 +33,8 @@ struct Manifold::Impl { struct MeshRelationD { /// The originalID of this Manifold if it is an original; -1 otherwise. int originalID = -1; - int numProp = 0; - Vec properties; std::map meshIDtransform; Vec triRef; - Vec triProperties; }; struct BaryIndices { int tri, start4, end4; @@ -47,9 +43,13 @@ struct Manifold::Impl { Box bBox_; double epsilon_ = -1; double tolerance_ = -1; + int numProp_ = 0; Error status_ = Error::NoError; Vec vertPos_; Vec halfedge_; + Vec properties_; + // Note that vertNormal_ is not precise due to the use of an approximated acos + // function Vec vertNormal_; Vec faceNormal_; Vec halfedgeTangent_; @@ -69,153 +69,185 @@ struct Manifold::Impl { const uint32_t numTri = meshGL.NumTri(); if (meshGL.numProp < 3) { - MarkFailure(Error::MissingPositionProperties); + MakeEmpty(Error::MissingPositionProperties); return; } if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) { - MarkFailure(Error::MergeVectorsDifferentLengths); + MakeEmpty(Error::MergeVectorsDifferentLengths); return; } if (!meshGL.runTransform.empty() && 12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) { - MarkFailure(Error::TransformWrongLength); + MakeEmpty(Error::TransformWrongLength); return; } if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() && meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size() && meshGL.runOriginalID.size() != meshGL.runIndex.size()) { - MarkFailure(Error::RunIndexWrongLength); + MakeEmpty(Error::RunIndexWrongLength); return; } if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) { - MarkFailure(Error::FaceIDWrongLength); + MakeEmpty(Error::FaceIDWrongLength); return; } - std::vector prop2vert(numVert); - std::iota(prop2vert.begin(), prop2vert.end(), 0); - for (size_t i = 0; i < meshGL.mergeFromVert.size(); ++i) { - const uint32_t from = meshGL.mergeFromVert[i]; - const uint32_t to = meshGL.mergeToVert[i]; - if (from >= numVert || to >= numVert) { - MarkFailure(Error::MergeIndexOutOfBounds); - return; + if (!manifold::all_of(meshGL.vertProperties.begin(), + meshGL.vertProperties.end(), + [](Precision x) { return std::isfinite(x); })) { + MakeEmpty(Error::NonFiniteVertex); + return; + } + + if (!manifold::all_of(meshGL.runTransform.begin(), + meshGL.runTransform.end(), + [](Precision x) { return std::isfinite(x); })) { + MakeEmpty(Error::InvalidConstruction); + return; + } + + if (!manifold::all_of(meshGL.halfedgeTangent.begin(), + meshGL.halfedgeTangent.end(), + [](Precision x) { return std::isfinite(x); })) { + MakeEmpty(Error::InvalidConstruction); + return; + } + + std::vector prop2vert; + if (!meshGL.mergeFromVert.empty()) { + prop2vert.resize(numVert); + std::iota(prop2vert.begin(), prop2vert.end(), 0); + for (size_t i = 0; i < meshGL.mergeFromVert.size(); ++i) { + const uint32_t from = meshGL.mergeFromVert[i]; + const uint32_t to = meshGL.mergeToVert[i]; + if (from >= numVert || to >= numVert) { + MakeEmpty(Error::MergeIndexOutOfBounds); + return; + } + prop2vert[from] = to; } - prop2vert[from] = to; } const auto numProp = meshGL.numProp - 3; - meshRelation_.numProp = numProp; - meshRelation_.properties.resize(meshGL.NumVert() * numProp); + numProp_ = numProp; + properties_.resize_nofill(meshGL.NumVert() * numProp); tolerance_ = meshGL.tolerance; // This will have unreferenced duplicate positions that will be removed by // Impl::RemoveUnreferencedVerts(). - vertPos_.resize(meshGL.NumVert()); + vertPos_.resize_nofill(meshGL.NumVert()); for (size_t i = 0; i < meshGL.NumVert(); ++i) { for (const int j : {0, 1, 2}) vertPos_[i][j] = meshGL.vertProperties[meshGL.numProp * i + j]; for (size_t j = 0; j < numProp; ++j) - meshRelation_.properties[i * numProp + j] = + properties_[i * numProp + j] = meshGL.vertProperties[meshGL.numProp * i + 3 + j]; } - halfedgeTangent_.resize(meshGL.halfedgeTangent.size() / 4); + halfedgeTangent_.resize_nofill(meshGL.halfedgeTangent.size() / 4); for (size_t i = 0; i < halfedgeTangent_.size(); ++i) { for (const int j : {0, 1, 2, 3}) halfedgeTangent_[i][j] = meshGL.halfedgeTangent[4 * i + j]; } Vec triRef; - if (!meshGL.runOriginalID.empty()) { - auto runIndex = meshGL.runIndex; - const auto runEnd = meshGL.triVerts.size(); - if (runIndex.empty()) { - runIndex = {0, static_cast(runEnd)}; - } else if (runIndex.size() == meshGL.runOriginalID.size()) { - runIndex.push_back(runEnd); - } - triRef.resize(meshGL.NumTri()); - const auto startID = Impl::ReserveIDs(meshGL.runOriginalID.size()); - for (size_t i = 0; i < meshGL.runOriginalID.size(); ++i) { - const int meshID = startID + i; - const int originalID = meshGL.runOriginalID[i]; - for (size_t tri = runIndex[i] / 3; tri < runIndex[i + 1] / 3; ++tri) { - TriRef& ref = triRef[tri]; - ref.meshID = meshID; - ref.originalID = originalID; - ref.tri = meshGL.faceID.empty() ? tri : meshGL.faceID[tri]; - ref.faceID = tri; - } + triRef.resize_nofill(meshGL.NumTri()); - if (meshGL.runTransform.empty()) { - meshRelation_.meshIDtransform[meshID] = {originalID}; - } else { - const Precision* m = meshGL.runTransform.data() + 12 * i; - meshRelation_.meshIDtransform[meshID] = {originalID, - {{m[0], m[1], m[2]}, - {m[3], m[4], m[5]}, - {m[6], m[7], m[8]}, - {m[9], m[10], m[11]}}}; - } + auto runIndex = meshGL.runIndex; + const auto runEnd = meshGL.triVerts.size(); + if (runIndex.empty()) { + runIndex = {0, static_cast(runEnd)}; + } else if (runIndex.size() == meshGL.runOriginalID.size()) { + runIndex.push_back(runEnd); + } else if (runIndex.size() == 1) { + runIndex.push_back(runEnd); + } + + const auto startID = Impl::ReserveIDs(meshGL.runOriginalID.size()); + auto runOriginalID = meshGL.runOriginalID; + if (runOriginalID.empty()) { + runOriginalID.push_back(startID); + } + for (size_t i = 0; i < runOriginalID.size(); ++i) { + const int meshID = startID + i; + const int originalID = runOriginalID[i]; + for (size_t tri = runIndex[i] / 3; tri < runIndex[i + 1] / 3; ++tri) { + TriRef& ref = triRef[tri]; + ref.meshID = meshID; + ref.originalID = originalID; + ref.faceID = meshGL.faceID.empty() ? -1 : meshGL.faceID[tri]; + ref.coplanarID = tri; + } + + if (meshGL.runTransform.empty()) { + meshRelation_.meshIDtransform[meshID] = {originalID}; + } else { + const Precision* m = meshGL.runTransform.data() + 12 * i; + meshRelation_.meshIDtransform[meshID] = {originalID, + {{m[0], m[1], m[2]}, + {m[3], m[4], m[5]}, + {m[6], m[7], m[8]}, + {m[9], m[10], m[11]}}}; } } - Vec triVerts; - triVerts.reserve(numTri); + Vec triProp; + triProp.reserve(numTri); + Vec triVert; + const bool needsPropMap = numProp > 0 && !prop2vert.empty(); + if (needsPropMap) triVert.reserve(numTri); + if (triRef.size() > 0) meshRelation_.triRef.reserve(numTri); for (size_t i = 0; i < numTri; ++i) { - ivec3 tri; + ivec3 triP, triV; for (const size_t j : {0, 1, 2}) { uint32_t vert = (uint32_t)meshGL.triVerts[3 * i + j]; if (vert >= numVert) { - MarkFailure(Error::VertexOutOfBounds); + MakeEmpty(Error::VertexOutOfBounds); return; } - tri[j] = prop2vert[vert]; + triP[j] = vert; + triV[j] = prop2vert.empty() ? vert : prop2vert[vert]; } - if (tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0]) { - triVerts.push_back(tri); + if (triV[0] != triV[1] && triV[1] != triV[2] && triV[2] != triV[0]) { + if (needsPropMap) { + triProp.push_back(triP); + triVert.push_back(triV); + } else { + triProp.push_back(triV); + } if (triRef.size() > 0) { meshRelation_.triRef.push_back(triRef[i]); } - if (numProp > 0) { - meshRelation_.triProperties.push_back( - ivec3(static_cast(meshGL.triVerts[3 * i]), - static_cast(meshGL.triVerts[3 * i + 1]), - static_cast(meshGL.triVerts[3 * i + 2]))); - } } } - CreateHalfedges(triVerts); + CreateHalfedges(triProp, triVert); if (!IsManifold()) { - MarkFailure(Error::NotManifold); + MakeEmpty(Error::NotManifold); return; } CalculateBBox(); SetEpsilon(-1, std::is_same::value); - SplitPinchedVerts(); - + // we need to split pinched verts before calculating vertex normals, because + // the algorithm doesn't work with pinched verts + CleanupTopology(); CalculateNormals(); - if (meshGL.runOriginalID.empty()) { - InitializeOriginal(); - } + DedupePropVerts(); + MarkCoplanar(); - CreateFaces(); - - SimplifyTopology(); + RemoveDegenerates(); RemoveUnreferencedVerts(); Finish(); if (!IsFinite()) { - MarkFailure(Error::NonFiniteVertex); + MakeEmpty(Error::NonFiniteVertex); return; } @@ -224,7 +256,8 @@ struct Manifold::Impl { meshRelation_.originalID = -1; } - inline void ForVert(int halfedge, std::function func) { + template + inline void ForVert(int halfedge, F func) { int current = halfedge; do { current = NextHalfedge(halfedge_[current].pairedHalfedge); @@ -247,30 +280,28 @@ struct Manifold::Impl { } while (current != halfedge); } - void CreateFaces(); + void MarkCoplanar(); + void DedupePropVerts(); void RemoveUnreferencedVerts(); void InitializeOriginal(bool keepFaceID = false); - void CreateHalfedges(const Vec& triVerts); + void CreateHalfedges(const Vec& triProp, + const Vec& triVert = {}); void CalculateNormals(); void IncrementMeshIDs(); void Update(); - void MarkFailure(Error status); + void MakeEmpty(Error status); void Warp(std::function warpFunc); void WarpBatch(std::function)> warpFunc); Impl Transform(const mat3x4& transform) const; - SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const; - SparseIndices VertexCollisionsZ(VecView vertsIn, - bool inverted = false) const; bool IsEmpty() const { return NumTri() == 0; } size_t NumVert() const { return vertPos_.size(); } size_t NumEdge() const { return halfedge_.size() / 2; } size_t NumTri() const { return halfedge_.size() / 3; } - size_t NumProp() const { return meshRelation_.numProp; } + size_t NumProp() const { return numProp_; } size_t NumPropVert() const { - return NumProp() == 0 ? NumVert() - : meshRelation_.properties.size() / NumProp(); + return NumProp() == 0 ? NumVert() : properties_.size() / NumProp(); } // properties.cpp @@ -299,18 +330,20 @@ struct Manifold::Impl { void GatherFaces(const Impl& old, const Vec& faceNew2Old); // face_op.cpp - void Face2Tri(const Vec& faceEdge, const Vec& halfedgeRef); - PolygonsIdx Face2Polygons(VecView::IterC start, - VecView::IterC end, - mat2x3 projection) const; + void Face2Tri(const Vec& faceEdge, const Vec& halfedgeRef, + bool allowConvex = false); Polygons Slice(double height) const; Polygons Project() const; // edge_op.cpp void CleanupTopology(); - void SimplifyTopology(); + void SimplifyTopology(int firstNewVert = 0); + void RemoveDegenerates(int firstNewVert = 0); + void CollapseShortEdges(int firstNewVert = 0); + void CollapseColinearEdges(int firstNewVert = 0); + void SwapDegenerates(int firstNewVert = 0); void DedupeEdge(int edge); - void CollapseEdge(int edge, std::vector& edges); + bool CollapseEdge(int edge, std::vector& edges); void RecursiveEdgeSwap(int edge, int& tag, std::vector& visited, std::vector& edgeSwapStack, std::vector& edges); @@ -320,6 +353,7 @@ struct Manifold::Impl { void FormLoop(int current, int end); void CollapseTri(const ivec3& triEdge); void SplitPinchedVerts(); + void DedupeEdges(); // subdivision.cpp int GetNeighbor(int tri) const; @@ -353,8 +387,6 @@ struct Manifold::Impl { void Hull(VecView vertPos); }; -#ifdef MANIFOLD_DEBUG extern std::mutex dump_lock; std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl); -#endif } // namespace manifold diff --git a/thirdparty/manifold/src/iters.h b/thirdparty/manifold/src/iters.h index 8b5110ae14..65a1000ffc 100644 --- a/thirdparty/manifold/src/iters.h +++ b/thirdparty/manifold/src/iters.h @@ -14,6 +14,7 @@ #pragma once #include +#include #include namespace manifold { @@ -22,7 +23,7 @@ template struct TransformIterator { private: Iter iter; - F f; + std::optional f; public: using pointer = void; @@ -36,16 +37,28 @@ struct TransformIterator { constexpr TransformIterator(Iter iter, F f) : iter(iter), f(f) {} + TransformIterator(const TransformIterator& other) + : iter(other.iter), f(other.f) {} + + TransformIterator(TransformIterator&& other) : iter(other.iter), f(other.f) {} + TransformIterator& operator=(const TransformIterator& other) { if (this == &other) return *this; - // don't copy function, should be the same iter = other.iter; + f.emplace(*other.f); return *this; } - constexpr reference operator*() const { return f(*iter); } + TransformIterator& operator=(TransformIterator&& other) { + if (this == &other) return *this; + iter = other.iter; + f.emplace(*other.f); + return *this; + } - constexpr reference operator[](size_t i) const { return f(iter[i]); } + constexpr reference operator*() const { return (*f)(*iter); } + + constexpr reference operator[](size_t i) const { return (*f)(iter[i]); } // prefix increment TransformIterator& operator++() { @@ -74,7 +87,7 @@ struct TransformIterator { } constexpr TransformIterator operator+(size_t n) const { - return TransformIterator(iter + n, f); + return TransformIterator(iter + n, *f); } TransformIterator& operator+=(size_t n) { @@ -83,7 +96,7 @@ struct TransformIterator { } constexpr TransformIterator operator-(size_t n) const { - return TransformIterator(iter - n, f); + return TransformIterator(iter - n, *f); } TransformIterator& operator-=(size_t n) { @@ -108,7 +121,7 @@ struct TransformIterator { } constexpr operator TransformIterator() const { - return TransformIterator(f, iter); + return TransformIterator(*f, iter); } }; diff --git a/thirdparty/manifold/src/manifold.cpp b/thirdparty/manifold/src/manifold.cpp index a36dd460b9..627ef5ea38 100644 --- a/thirdparty/manifold/src/manifold.cpp +++ b/thirdparty/manifold/src/manifold.cpp @@ -16,36 +16,17 @@ #include #include -#include "./boolean3.h" -#include "./csg_tree.h" -#include "./impl.h" -#include "./parallel.h" +#include "boolean3.h" +#include "csg_tree.h" +#include "impl.h" +#include "parallel.h" +#include "shared.h" namespace { using namespace manifold; ExecutionParams manifoldParams; -struct UpdateProperties { - double* properties; - const int numProp; - const double* oldProperties; - const int numOldProp; - const vec3* vertPos; - const ivec3* triProperties; - const Halfedge* halfedges; - std::function propFunc; - - void operator()(int tri) { - for (int i : {0, 1, 2}) { - const int vert = halfedges[3 * tri + i].startVert; - const int propVert = triProperties[tri][i]; - propFunc(properties + numProp * propVert, vertPos[vert], - oldProperties + numOldProp * propVert); - } - } -}; - Manifold Halfspace(Box bBox, vec3 normal, double originOffset) { normal = la::normalize(normal); Manifold cutter = Manifold::Cube(vec3(2.0), true).Translate({1.0, 0.0, 0.0}); @@ -94,11 +75,12 @@ MeshGLP GetMeshGLImpl(const manifold::Manifold::Impl& impl, VecView triRef = impl.meshRelation_.triRef; // Don't sort originals - keep them in order if (!isOriginal) { - std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) { - return triRef[a].originalID == triRef[b].originalID - ? triRef[a].meshID < triRef[b].meshID - : triRef[a].originalID < triRef[b].originalID; - }); + std::stable_sort(triNew2Old.begin(), triNew2Old.end(), + [triRef](int a, int b) { + return triRef[a].originalID == triRef[b].originalID + ? triRef[a].meshID < triRef[b].meshID + : triRef[a].originalID < triRef[b].originalID; + }); } std::vector runNormalTransform; @@ -128,7 +110,7 @@ MeshGLP GetMeshGLImpl(const manifold::Manifold::Impl& impl, const auto ref = triRef[oldTri]; const int meshID = ref.meshID; - out.faceID[tri] = ref.tri; + out.faceID[tri] = ref.faceID >= 0 ? ref.faceID : ref.coplanarID; for (const int i : {0, 1, 2}) out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert; @@ -166,9 +148,8 @@ MeshGLP GetMeshGLImpl(const manifold::Manifold::Impl& impl, for (size_t run = 0; run < out.runOriginalID.size(); ++run) { for (size_t tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3; ++tri) { - const ivec3 triProp = impl.meshRelation_.triProperties[triNew2Old[tri]]; for (const int i : {0, 1, 2}) { - const int prop = triProp[i]; + const int prop = impl.halfedge_[3 * triNew2Old[tri] + i].propVert; const int vert = out.triVerts[3 * tri + i]; auto& bin = vertPropPair[vert]; @@ -189,8 +170,7 @@ MeshGLP GetMeshGLImpl(const manifold::Manifold::Impl& impl, out.vertProperties.push_back(impl.vertPos_[vert][p]); } for (int p = 0; p < numProp; ++p) { - out.vertProperties.push_back( - impl.meshRelation_.properties[prop * numProp + p]); + out.vertProperties.push_back(impl.properties_[prop * numProp + p]); } if (updateNormals) { @@ -332,11 +312,9 @@ MeshGL64 Manifold::GetMeshGL64(int normalIdx) const { bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); } /** * Returns the reason for an input Mesh producing an empty Manifold. This Status - * only applies to Manifolds newly-created from an input Mesh - once they are - * combined into a new Manifold via operations, the status reverts to NoError, - * simply processing the problem mesh as empty. Likewise, empty meshes may still - * show NoError, for instance if they are small enough relative to their - * tolerance to be collapsed to nothing. + * will carry on through operations like NaN propogation, ensuring an errored + * mesh doesn't get mysteriously lost. Empty meshes may still show + * NoError, for instance the intersection of non-overlapping meshes. */ Manifold::Error Manifold::Status() const { return GetCsgLeafNode().GetImpl()->status_; @@ -404,7 +382,7 @@ Manifold Manifold::SetTolerance(double tolerance) const { auto impl = std::make_shared(*GetCsgLeafNode().GetImpl()); if (tolerance > impl->tolerance_) { impl->tolerance_ = tolerance; - impl->CreateFaces(); + impl->MarkCoplanar(); impl->SimplifyTopology(); impl->Finish(); } else { @@ -415,6 +393,27 @@ Manifold Manifold::SetTolerance(double tolerance) const { return Manifold(impl); } +/** + * Return a copy of the manifold simplified to the given tolerance, but with its + * actual tolerance value unchanged. If the tolerance is not given or is less + * than the current tolerance, the current tolerance is used for simplification. + * The result will contain a subset of the original verts and all surfaces will + * have moved by less than tolerance. + */ +Manifold Manifold::Simplify(double tolerance) const { + auto impl = std::make_shared(*GetCsgLeafNode().GetImpl()); + const double oldTolerance = impl->tolerance_; + if (tolerance == 0) tolerance = oldTolerance; + if (tolerance > oldTolerance) { + impl->tolerance_ = tolerance; + impl->MarkCoplanar(); + } + impl->SimplifyTopology(); + impl->Finish(); + impl->tolerance_ = oldTolerance; + return Manifold(impl); +} + /** * The genus is a topological property of the manifold, representing the number * of "handles". A sphere is 0, torus 1, etc. It is only meaningful for a single @@ -450,9 +449,10 @@ int Manifold::OriginalID() const { /** * This removes all relations (originalID, faceID, transform) to ancestor meshes - * and this new Manifold is marked an original. It also collapses colinear edges - * - these don't get collapsed at boundaries where originalID changes, so the - * reset may allow flat faces to be further simplified. + * and this new Manifold is marked an original. It also recreates faces + * - these don't get joined at boundaries where originalID changes, so the + * reset may allow triangles of flat faces to be further collapsed with + * Simplify(). */ Manifold Manifold::AsOriginal() const { auto oldImpl = GetCsgLeafNode().GetImpl(); @@ -463,9 +463,7 @@ Manifold Manifold::AsOriginal() const { } auto newImpl = std::make_shared(*oldImpl); newImpl->InitializeOriginal(); - newImpl->CreateFaces(); - newImpl->SimplifyTopology(); - newImpl->Finish(); + newImpl->MarkCoplanar(); newImpl->InitializeOriginal(true); return Manifold(std::make_shared(newImpl)); } @@ -497,22 +495,6 @@ size_t Manifold::NumDegenerateTris() const { return GetCsgLeafNode().GetImpl()->NumDegenerateTris(); } -/** - * This is a checksum-style verification of the collider, simply returning the - * total number of edge-face bounding box overlaps between this and other. - * - * @param other A Manifold to overlap with. - */ -size_t Manifold::NumOverlaps(const Manifold& other) const { - SparseIndices overlaps = GetCsgLeafNode().GetImpl()->EdgeCollisions( - *other.GetCsgLeafNode().GetImpl()); - int num_overlaps = overlaps.size(); - - overlaps = other.GetCsgLeafNode().GetImpl()->EdgeCollisions( - *GetCsgLeafNode().GetImpl()); - return num_overlaps + overlaps.size(); -} - /** * Move this Manifold in space. This operation can be chained. Transforms are * combined and applied lazily. @@ -635,38 +617,33 @@ Manifold Manifold::SetProperties( propFunc) const { auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); const int oldNumProp = NumProp(); - const Vec oldProperties = pImpl->meshRelation_.properties; + const Vec oldProperties = pImpl->properties_; - auto& triProperties = pImpl->meshRelation_.triProperties; if (numProp == 0) { - triProperties.resize(0); - pImpl->meshRelation_.properties.resize(0); + pImpl->properties_.clear(); } else { - if (triProperties.size() == 0) { - const int numTri = NumTri(); - triProperties.resize(numTri); - for (int i = 0; i < numTri; ++i) { - for (const int j : {0, 1, 2}) { - triProperties[i][j] = pImpl->halfedge_[3 * i + j].startVert; - } - } - pImpl->meshRelation_.properties = Vec(numProp * NumVert(), 0); - } else { - pImpl->meshRelation_.properties = Vec(numProp * NumPropVert(), 0); - } + pImpl->properties_ = Vec(numProp * NumPropVert(), 0); for_each_n( propFunc == nullptr ? ExecutionPolicy::Par : ExecutionPolicy::Seq, - countAt(0), NumTri(), - UpdateProperties( - {pImpl->meshRelation_.properties.data(), numProp, - oldProperties.data(), oldNumProp, pImpl->vertPos_.data(), - triProperties.data(), pImpl->halfedge_.data(), - propFunc == nullptr ? [](double* newProp, vec3 position, - const double* oldProp) { *newProp = 0; } - : propFunc})); + countAt(0), NumTri(), [&](int tri) { + for (int i : {0, 1, 2}) { + const Halfedge& edge = pImpl->halfedge_[3 * tri + i]; + const int vert = edge.startVert; + const int propVert = edge.propVert; + if (propFunc == nullptr) { + for (int p = 0; p < numProp; ++p) { + pImpl->properties_[numProp * propVert + p] = 0; + } + } else { + propFunc(&pImpl->properties_[numProp * propVert], + pImpl->vertPos_[vert], + oldProperties.data() + oldNumProp * propVert); + } + } + }); } - pImpl->meshRelation_.numProp = numProp; + pImpl->numProp_ = numProp; return Manifold(std::make_shared(pImpl)); } @@ -754,14 +731,15 @@ Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const { auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); if (!IsEmpty()) { if (minSmoothness == 0) { - const int numProp = pImpl->meshRelation_.numProp; - Vec properties = pImpl->meshRelation_.properties; - Vec triProperties = pImpl->meshRelation_.triProperties; + const int numProp = pImpl->numProp_; + Vec properties = pImpl->properties_; + Vec halfedge = pImpl->halfedge_; pImpl->SetNormals(0, minSharpAngle); pImpl->CreateTangents(0); - pImpl->meshRelation_.numProp = numProp; - pImpl->meshRelation_.properties.swap(properties); - pImpl->meshRelation_.triProperties.swap(triProperties); + // Reset the properties to the original values, removing temporary normals + pImpl->numProp_ = numProp; + pImpl->properties_.swap(properties); + pImpl->halfedge_.swap(halfedge); } else { pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness)); } @@ -783,8 +761,7 @@ Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const { Manifold Manifold::Refine(int n) const { auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); if (n > 1) { - pImpl->Refine( - [n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; }); + pImpl->Refine([n](vec3, vec4, vec4) { return n - 1; }); } return Manifold(std::make_shared(pImpl)); } @@ -802,7 +779,7 @@ Manifold Manifold::Refine(int n) const { Manifold Manifold::RefineToLength(double length) const { length = std::abs(length); auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); - pImpl->Refine([length](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { + pImpl->Refine([length](vec3 edge, vec4, vec4) { return static_cast(la::length(edge) / length); }); return Manifold(std::make_shared(pImpl)); diff --git a/thirdparty/manifold/src/mesh_fixes.h b/thirdparty/manifold/src/mesh_fixes.h index d4aa800b4a..fc11486a6b 100644 --- a/thirdparty/manifold/src/mesh_fixes.h +++ b/thirdparty/manifold/src/mesh_fixes.h @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #pragma once -#include "./shared.h" +#include "shared.h" namespace { using namespace manifold; diff --git a/thirdparty/manifold/src/parallel.h b/thirdparty/manifold/src/parallel.h index a434939494..05d75be959 100644 --- a/thirdparty/manifold/src/parallel.h +++ b/thirdparty/manifold/src/parallel.h @@ -17,7 +17,7 @@ #pragma once -#include "./iters.h" +#include "iters.h" #if (MANIFOLD_PAR == 1) #include #include @@ -114,20 +114,20 @@ template struct ScanBody { T sum; T identity; - BinOp &f; + BinOp& f; InputIter input; OutputIter output; - ScanBody(T sum, T identity, BinOp &f, InputIter input, OutputIter output) + ScanBody(T sum, T identity, BinOp& f, InputIter input, OutputIter output) : sum(sum), identity(identity), f(f), input(input), output(output) {} - ScanBody(ScanBody &b, tbb::split) + ScanBody(ScanBody& b, tbb::split) : sum(b.identity), identity(b.identity), f(b.f), input(b.input), output(b.output) {} template - void operator()(const tbb::blocked_range &r, Tag) { + void operator()(const tbb::blocked_range& r, Tag) { T temp = sum; for (size_t i = r.begin(); i < r.end(); ++i) { T inputTmp = input[i]; @@ -137,23 +137,23 @@ struct ScanBody { sum = temp; } T get_sum() const { return sum; } - void reverse_join(ScanBody &a) { sum = f(a.sum, sum); } - void assign(ScanBody &b) { sum = b.sum; } + void reverse_join(ScanBody& a) { sum = f(a.sum, sum); } + void assign(ScanBody& b) { sum = b.sum; } }; template struct CopyIfScanBody { size_t sum; - P &pred; + P& pred; InputIter input; OutputIter output; - CopyIfScanBody(P &pred, InputIter input, OutputIter output) + CopyIfScanBody(P& pred, InputIter input, OutputIter output) : sum(0), pred(pred), input(input), output(output) {} - CopyIfScanBody(CopyIfScanBody &b, tbb::split) + CopyIfScanBody(CopyIfScanBody& b, tbb::split) : sum(0), pred(b.pred), input(b.input), output(b.output) {} template - void operator()(const tbb::blocked_range &r, Tag) { + void operator()(const tbb::blocked_range& r, Tag) { size_t temp = sum; for (size_t i = r.begin(); i < r.end(); ++i) { if (pred(i)) { @@ -164,8 +164,8 @@ struct CopyIfScanBody { sum = temp; } size_t get_sum() const { return sum; } - void reverse_join(CopyIfScanBody &a) { sum = a.sum + sum; } - void assign(CopyIfScanBody &b) { sum = b.sum; } + void reverse_join(CopyIfScanBody& a) { sum = a.sum + sum; } + void assign(CopyIfScanBody& b) { sum = b.sum; } }; template @@ -173,11 +173,11 @@ struct Hist { using SizeType = N; static constexpr int k = K; N hist[k][256] = {{0}}; - void merge(const Hist &other) { + void merge(const Hist& other) { for (int i = 0; i < k; ++i) for (int j = 0; j < 256; ++j) hist[i][j] += other.hist[i][j]; } - void prefixSum(N total, bool *canSkip) { + void prefixSum(N total, bool* canSkip) { for (int i = 0; i < k; ++i) { size_t count = 0; for (int j = 0; j < 256; ++j) { @@ -191,8 +191,8 @@ struct Hist { }; template -void histogram(T *ptr, typename H::SizeType n, H &hist) { - auto worker = [](T *ptr, typename H::SizeType n, H &hist) { +void histogram(T* ptr, typename H::SizeType n, H& hist) { + auto worker = [](T* ptr, typename H::SizeType n, H& hist) { for (typename H::SizeType i = 0; i < n; ++i) for (int k = 0; k < hist.k; ++k) ++hist.hist[k][(ptr[i] >> (8 * k)) & 0xFF]; @@ -203,21 +203,21 @@ void histogram(T *ptr, typename H::SizeType n, H &hist) { tbb::combinable store; tbb::parallel_for( tbb::blocked_range(0, n, kSeqThreshold), - [&worker, &store, ptr](const auto &r) { + [&worker, &store, ptr](const auto& r) { worker(ptr + r.begin(), r.end() - r.begin(), store.local()); }); - store.combine_each([&hist](const H &h) { hist.merge(h); }); + store.combine_each([&hist](const H& h) { hist.merge(h); }); } } template -void shuffle(T *src, T *target, typename H::SizeType n, H &hist, int k) { +void shuffle(T* src, T* target, typename H::SizeType n, H& hist, int k) { for (typename H::SizeType i = 0; i < n; ++i) target[hist.hist[k][(src[i] >> (8 * k)) & 0xFF]++] = src[i]; } template -bool LSB_radix_sort(T *input, T *tmp, SizeType n) { +bool LSB_radix_sort(T* input, T* tmp, SizeType n) { Hist hist; if (std::is_sorted(input, input + n)) return false; histogram(input, n, hist); @@ -240,9 +240,9 @@ struct SortedRange { SizeType offset = 0, length = 0; bool inTmp = false; - SortedRange(T *input, T *tmp, SizeType offset = 0, SizeType length = 0) + SortedRange(T* input, T* tmp, SizeType offset = 0, SizeType length = 0) : input(input), tmp(tmp), offset(offset), length(length) {} - SortedRange(SortedRange &r, tbb::split) + SortedRange(SortedRange& r, tbb::split) : input(r.input), tmp(r.tmp) {} // FIXME: no idea why thread sanitizer reports data race here #if defined(__has_feature) @@ -251,7 +251,7 @@ struct SortedRange { #endif #endif void - operator()(const tbb::blocked_range &range) { + operator()(const tbb::blocked_range& range) { SortedRange rhs(input, tmp, range.begin(), range.end() - range.begin()); rhs.inTmp = @@ -267,7 +267,7 @@ struct SortedRange { copy(src + offset, src + offset + length, target + offset); return !inTmp; } - void join(const SortedRange &rhs) { + void join(const SortedRange& rhs) { if (inTmp != rhs.inTmp) { if (length < rhs.length) inTmp = swapBuffer(); @@ -286,8 +286,8 @@ struct SortedRange { }; template -void radix_sort(T *input, SizeTy n) { - T *aux = new T[n]; +void radix_sort(T* input, SizeTy n) { + T* aux = new T[n]; SizeTy blockSize = std::max(n / tbb::this_task_arena::max_concurrency() / 4, static_cast(kSeqThreshold / sizeof(T))); SortedRange result(input, aux); @@ -306,7 +306,7 @@ void mergeSort(ExecutionPolicy policy, Iterator first, Iterator last, // apparently this prioritizes threads inside here? tbb::this_task_arena::isolate([&] { size_t length = std::distance(first, last); - T *tmp = new T[length]; + T* tmp = new T[length]; copy(policy, first, last, tmp); details::mergeSortRec(tmp, first, 0, length, comp); delete[] tmp; @@ -376,13 +376,16 @@ void for_each(ExecutionPolicy policy, Iter first, Iter last, F f) { typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - tbb::parallel_for(tbb::blocked_range(first, last), - [&f](const tbb::blocked_range &range) { - for (Iter i = range.begin(); i != range.end(); i++) - f(*i); - }); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_for(tbb::blocked_range(first, last), + [&f](const tbb::blocked_range& range) { + for (Iter i = range.begin(); i != range.end(); i++) + f(*i); + }); + }); return; } #endif @@ -412,16 +415,19 @@ T reduce(ExecutionPolicy policy, InputIter first, InputIter last, T init, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { // should we use deterministic reduce here? - return tbb::parallel_reduce( - tbb::blocked_range(first, last, details::kSeqThreshold), - init, - [&f](const tbb::blocked_range &range, T value) { - return std::reduce(range.begin(), range.end(), value, f); - }, - f); + return tbb::this_task_arena::isolate([&]() { + return tbb::parallel_reduce( + tbb::blocked_range(first, last, details::kSeqThreshold), + init, + [&f](const tbb::blocked_range& range, T value) { + return std::reduce(range.begin(), range.end(), value, f); + }, + f); + }); } #endif return std::reduce(first, last, init, f); @@ -488,21 +494,24 @@ void inclusive_scan(ExecutionPolicy policy, InputIter first, InputIter last, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - tbb::parallel_scan( - tbb::blocked_range(0, std::distance(first, last)), - static_cast(0), - [&](const tbb::blocked_range &range, T sum, - bool is_final_scan) { - T temp = sum; - for (size_t i = range.begin(); i < range.end(); ++i) { - temp = temp + first[i]; - if (is_final_scan) d_first[i] = temp; - } - return temp; - }, - std::plus()); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_scan( + tbb::blocked_range(0, std::distance(first, last)), + static_cast(0), + [&](const tbb::blocked_range& range, T sum, + bool is_final_scan) { + T temp = sum; + for (size_t i = range.begin(); i < range.end(); ++i) { + temp = temp + first[i]; + if (is_final_scan) d_first[i] = temp; + } + return temp; + }, + std::plus()); + }); return; } #endif @@ -550,12 +559,16 @@ void exclusive_scan(ExecutionPolicy policy, InputIter first, InputIter last, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; + (void)identity; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { details::ScanBody body(init, identity, f, first, d_first); - tbb::parallel_scan( - tbb::blocked_range(0, std::distance(first, last)), body); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_scan( + tbb::blocked_range(0, std::distance(first, last)), body); + }); return; } #endif @@ -603,15 +616,18 @@ void transform(ExecutionPolicy policy, InputIter first, InputIter last, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - tbb::parallel_for(tbb::blocked_range( - 0, static_cast(std::distance(first, last))), - [&](const tbb::blocked_range &range) { - std::transform(first + range.begin(), - first + range.end(), - d_first + range.begin(), f); - }); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_for(tbb::blocked_range( + 0, static_cast(std::distance(first, last))), + [&](const tbb::blocked_range& range) { + std::transform(first + range.begin(), + first + range.end(), + d_first + range.begin(), f); + }); + }); return; } #endif @@ -647,15 +663,18 @@ void copy(ExecutionPolicy policy, InputIter first, InputIter last, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - tbb::parallel_for(tbb::blocked_range( - 0, static_cast(std::distance(first, last)), - details::kSeqThreshold), - [&](const tbb::blocked_range &range) { - std::copy(first + range.begin(), first + range.end(), - d_first + range.begin()); - }); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_for(tbb::blocked_range( + 0, static_cast(std::distance(first, last)), + details::kSeqThreshold), + [&](const tbb::blocked_range& range) { + std::copy(first + range.begin(), first + range.end(), + d_first + range.begin()); + }); + }); return; } #endif @@ -704,12 +723,15 @@ void fill(ExecutionPolicy policy, OutputIter first, OutputIter last, T value) { typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - tbb::parallel_for(tbb::blocked_range(first, last), - [&](const tbb::blocked_range &range) { - std::fill(range.begin(), range.end(), value); - }); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_for(tbb::blocked_range(first, last), + [&](const tbb::blocked_range& range) { + std::fill(range.begin(), range.end(), value); + }); + }); return; } #endif @@ -727,6 +749,7 @@ void fill(OutputIter first, OutputIter last, T value) { template size_t count_if(ExecutionPolicy policy, InputIter first, InputIter last, P pred) { + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { return reduce(policy, TransformIterator(first, pred), @@ -751,18 +774,21 @@ bool all_of(ExecutionPolicy policy, InputIter first, InputIter last, P pred) { typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { // should we use deterministic reduce here? - return tbb::parallel_reduce( - tbb::blocked_range(first, last), true, - [&](const tbb::blocked_range &range, bool value) { - if (!value) return false; - for (InputIter i = range.begin(); i != range.end(); i++) - if (!pred(*i)) return false; - return true; - }, - [](bool a, bool b) { return a && b; }); + return tbb::this_task_arena::isolate([&]() { + return tbb::parallel_reduce( + tbb::blocked_range(first, last), true, + [&](const tbb::blocked_range& range, bool value) { + if (!value) return false; + for (InputIter i = range.begin(); i != range.end(); i++) + if (!pred(*i)) return false; + return true; + }, + [](bool a, bool b) { return a && b; }); + }); } #endif return std::all_of(first, last, pred); @@ -798,13 +824,16 @@ OutputIter copy_if(ExecutionPolicy policy, InputIter first, InputIter last, typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>, "You can only parallelize RandomAccessIterator."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { auto pred2 = [&](size_t i) { return pred(first[i]); }; details::CopyIfScanBody body(pred2, first, d_first); - tbb::parallel_scan( - tbb::blocked_range(0, std::distance(first, last)), body); - return d_first + body.get_sum(); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_scan( + tbb::blocked_range(0, std::distance(first, last)), body); + return d_first + body.get_sum(); + }); } #endif return std::copy_if(first, last, d_first, pred); @@ -845,9 +874,10 @@ Iter remove_if(ExecutionPolicy policy, Iter first, Iter last, P pred) { static_assert(std::is_trivially_destructible_v, "Our simple implementation does not support types that are " "not trivially destructable."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - T *tmp = new T[std::distance(first, last)]; + T* tmp = new T[std::distance(first, last)]; auto back = copy_if(policy, first, last, tmp, [&](T v) { return !pred(v); }); copy(policy, tmp, back, first); @@ -892,9 +922,10 @@ Iter remove(ExecutionPolicy policy, Iter first, Iter last, T value) { static_assert(std::is_trivially_destructible_v, "Our simple implementation does not support types that are " "not trivially destructable."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par) { - T *tmp = new T[std::distance(first, last)]; + T* tmp = new T[std::distance(first, last)]; auto back = copy_if(policy, first, last, tmp, [&](T v) { return v != value; }); copy(policy, tmp, back, first); @@ -939,13 +970,14 @@ Iter unique(ExecutionPolicy policy, Iter first, Iter last) { static_assert(std::is_trivially_destructible_v, "Our simple implementation does not support types that are " "not trivially destructable."); + (void)policy; #if (MANIFOLD_PAR == 1) if (policy == ExecutionPolicy::Par && first != last) { Iter newSrcStart = first; // cap the maximum buffer size, proved to be beneficial for unique with huge // array size constexpr size_t MAX_BUFFER_SIZE = 1 << 16; - T *tmp = new T[std::min(MAX_BUFFER_SIZE, + T* tmp = new T[std::min(MAX_BUFFER_SIZE, static_cast(std::distance(first, last)))]; auto pred = [&](size_t i) { return tmp[i] != tmp[i + 1]; }; do { @@ -957,7 +989,9 @@ Iter unique(ExecutionPolicy policy, Iter first, Iter last) { // this is not a typo, the index i is offset by 1, so to compare an // element with its predecessor we need to compare i and i + 1. details::CopyIfScanBody body(pred, tmp + 1, first + 1); - tbb::parallel_scan(tbb::blocked_range(0, length - 1), body); + tbb::this_task_arena::isolate([&]() { + tbb::parallel_scan(tbb::blocked_range(0, length - 1), body); + }); first += body.get_sum() + 1; newSrcStart += length; } while (newSrcStart != last); @@ -992,6 +1026,7 @@ Iter unique(Iter first, Iter last) { template ::value_type> void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last) { + (void)policy; #if (MANIFOLD_PAR == 1) details::SortFunctor()(policy, first, last); #else @@ -1023,6 +1058,7 @@ template ())> void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last, Comp comp) { + (void)policy; #if (MANIFOLD_PAR == 1) details::mergeSort(policy, first, last, comp); #else diff --git a/thirdparty/manifold/src/polygon.cpp b/thirdparty/manifold/src/polygon.cpp index 60f510a98b..db7c58e71a 100644 --- a/thirdparty/manifold/src/polygon.cpp +++ b/thirdparty/manifold/src/polygon.cpp @@ -18,15 +18,20 @@ #include #include -#include "./collider.h" -#include "./parallel.h" -#include "./utils.h" +#include "manifold/manifold.h" #include "manifold/optional_assert.h" +#include "parallel.h" +#include "tree2d.h" +#include "utils.h" + +#ifdef MANIFOLD_DEBUG +#include +#endif namespace { using namespace manifold; -static ExecutionParams params; +constexpr int TRIANGULATOR_VERBOSE_LEVEL = 2; constexpr double kBest = -std::numeric_limits::infinity(); @@ -40,9 +45,9 @@ struct PolyEdge { int startVert, endVert; }; -std::vector Polygons2Edges(const PolygonsIdx &polys) { +std::vector Polygons2Edges(const PolygonsIdx& polys) { std::vector halfedges; - for (const auto &poly : polys) { + for (const auto& poly : polys) { for (size_t i = 1; i < poly.size(); ++i) { halfedges.push_back({poly[i - 1].idx, poly[i].idx}); } @@ -51,10 +56,10 @@ std::vector Polygons2Edges(const PolygonsIdx &polys) { return halfedges; } -std::vector Triangles2Edges(const std::vector &triangles) { +std::vector Triangles2Edges(const std::vector& triangles) { std::vector halfedges; halfedges.reserve(triangles.size() * 3); - for (const ivec3 &tri : triangles) { + for (const ivec3& tri : triangles) { halfedges.push_back({tri[0], tri[1]}); halfedges.push_back({tri[1], tri[2]}); halfedges.push_back({tri[2], tri[0]}); @@ -62,7 +67,7 @@ std::vector Triangles2Edges(const std::vector &triangles) { return halfedges; } -void CheckTopology(const std::vector &halfedges) { +void CheckTopology(const std::vector& halfedges) { DEBUG_ASSERT(halfedges.size() % 2 == 0, topologyErr, "Odd number of halfedges."); size_t n_edges = halfedges.size() / 2; @@ -83,8 +88,8 @@ void CheckTopology(const std::vector &halfedges) { backward.resize(n_edges); std::for_each(backward.begin(), backward.end(), - [](PolyEdge &e) { std::swap(e.startVert, e.endVert); }); - auto cmp = [](const PolyEdge &a, const PolyEdge &b) { + [](PolyEdge& e) { std::swap(e.startVert, e.endVert); }); + auto cmp = [](const PolyEdge& a, const PolyEdge& b) { return a.startVert < b.startVert || (a.startVert == b.startVert && a.endVert < b.endVert); }; @@ -97,8 +102,8 @@ void CheckTopology(const std::vector &halfedges) { } } -void CheckTopology(const std::vector &triangles, - const PolygonsIdx &polys) { +void CheckTopology(const std::vector& triangles, + const PolygonsIdx& polys) { std::vector halfedges = Triangles2Edges(triangles); std::vector openEdges = Polygons2Edges(polys); for (PolyEdge e : openEdges) { @@ -107,23 +112,24 @@ void CheckTopology(const std::vector &triangles, CheckTopology(halfedges); } -void CheckGeometry(const std::vector &triangles, - const PolygonsIdx &polys, double epsilon) { +void CheckGeometry(const std::vector& triangles, + const PolygonsIdx& polys, double epsilon) { std::unordered_map vertPos; - for (const auto &poly : polys) { + for (const auto& poly : polys) { for (size_t i = 0; i < poly.size(); ++i) { vertPos[poly[i].idx] = poly[i].pos; } } DEBUG_ASSERT(std::all_of(triangles.begin(), triangles.end(), - [&vertPos, epsilon](const ivec3 &tri) { + [&vertPos, epsilon](const ivec3& tri) { return CCW(vertPos[tri[0]], vertPos[tri[1]], vertPos[tri[2]], epsilon) >= 0; }), geometryErr, "triangulation is not entirely CCW!"); } -void Dump(const PolygonsIdx &polys, double epsilon) { +void Dump(const PolygonsIdx& polys, double epsilon) { + std::cout << std::setprecision(19); std::cout << "Polygon 0 " << epsilon << " " << polys.size() << std::endl; for (auto poly : polys) { std::cout << poly.size() << std::endl; @@ -141,12 +147,18 @@ void Dump(const PolygonsIdx &polys, double epsilon) { } } -void PrintFailure(const std::exception &e, const PolygonsIdx &polys, - std::vector &triangles, double epsilon) { +std::atomic numFailures(0); + +void PrintFailure(const std::exception& e, const PolygonsIdx& polys, + std::vector& triangles, double epsilon) { + // only print the first triangulation failure + if (numFailures.fetch_add(1) != 0) return; + std::cout << std::setprecision(19); std::cout << "-----------------------------------" << std::endl; std::cout << "Triangulation failed! Precision = " << epsilon << std::endl; std::cout << e.what() << std::endl; - if (triangles.size() > 1000 && !PolygonParams().verbose) { + if (triangles.size() > 1000 && + ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) { std::cout << "Output truncated due to producing " << triangles.size() << " triangles." << std::endl; return; @@ -159,8 +171,9 @@ void PrintFailure(const std::exception &e, const PolygonsIdx &polys, } } -#define PRINT(msg) \ - if (params.verbose) std::cout << msg << std::endl; +#define PRINT(msg) \ + if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) \ + std::cout << msg << std::endl; #else #define PRINT(msg) #endif @@ -170,8 +183,8 @@ void PrintFailure(const std::exception &e, const PolygonsIdx &polys, * Exactly colinear edges and zero-length edges are treated conservatively as * reflex. Does not check for overlaps. */ -bool IsConvex(const PolygonsIdx &polys, double epsilon) { - for (const SimplePolygonIdx &poly : polys) { +bool IsConvex(const PolygonsIdx& polys, double epsilon) { + for (const SimplePolygonIdx& poly : polys) { const vec2 firstEdge = poly[0].pos - poly[poly.size() - 1].pos; // Zero-length edges comes out NaN, which won't trip the early return, but // it's okay because that zero-length edge will also get tested @@ -193,14 +206,14 @@ bool IsConvex(const PolygonsIdx &polys, double epsilon) { * Triangulates a set of convex polygons by alternating instead of a fan, to * avoid creating high-degree vertices. */ -std::vector TriangulateConvex(const PolygonsIdx &polys) { +std::vector TriangulateConvex(const PolygonsIdx& polys) { const size_t numTri = manifold::transform_reduce( polys.begin(), polys.end(), 0_uz, [](size_t a, size_t b) { return a + b; }, - [](const SimplePolygonIdx &poly) { return poly.size() - 2; }); + [](const SimplePolygonIdx& poly) { return poly.size() - 2; }); std::vector triangles; triangles.reserve(numTri); - for (const SimplePolygonIdx &poly : polys) { + for (const SimplePolygonIdx& poly : polys) { size_t i = 0; size_t k = poly.size() - 1; bool right = true; @@ -222,9 +235,7 @@ std::vector TriangulateConvex(const PolygonsIdx &polys) { * Ear-clipping triangulator based on David Eberly's approach from Geometric * Tools, but adjusted to handle epsilon-valid polygons, and including a * fallback that ensures a manifold triangulation even for overlapping polygons. - * This is an O(n^2) algorithm, but hopefully this is not a big problem as the - * number of edges in a given polygon is generally much less than the number of - * triangles in a mesh, and relatively few faces even need triangulation. + * This is reduced from an O(n^2) algorithm by means of our BVH Collider. * * The main adjustments for robustness involve clipping the sharpest ears first * (a known technique to get higher triangle quality), and doing an exhaustive @@ -234,11 +245,11 @@ std::vector TriangulateConvex(const PolygonsIdx &polys) { class EarClip { public: - EarClip(const PolygonsIdx &polys, double epsilon) : epsilon_(epsilon) { + EarClip(const PolygonsIdx& polys, double epsilon) : epsilon_(epsilon) { ZoneScoped; size_t numVert = 0; - for (const SimplePolygonIdx &poly : polys) { + for (const SimplePolygonIdx& poly : polys) { numVert += poly.size(); } polygon_.reserve(numVert + 2 * polys.size()); @@ -272,19 +283,19 @@ class EarClip { private: struct Vert; - typedef std::vector::iterator VertItr; - typedef std::vector::const_iterator VertItrC; + using VertItr = std::vector::iterator; + using VertItrC = std::vector::const_iterator; struct MaxX { - bool operator()(const VertItr &a, const VertItr &b) const { + bool operator()(const VertItr& a, const VertItr& b) const { return a->pos.x > b->pos.x; } }; struct MinCost { - bool operator()(const VertItr &a, const VertItr &b) const { + bool operator()(const VertItr& a, const VertItr& b) const { return a->cost < b->cost; } }; - typedef std::set::iterator qItr; + using qItr = std::set::iterator; // The flat list where all the Verts are stored. Not used much for traversal. std::vector polygon_; @@ -306,9 +317,8 @@ class EarClip { double epsilon_; struct IdxCollider { - Collider collider; + Vec points; std::vector itr; - SparseIndices ind; }; // A circularly-linked list representing the polygon(s) that still need to be @@ -399,20 +409,9 @@ class EarClip { return true; } - // A major key to robustness is to only clip convex ears, but this is - // difficult to determine when an edge is folded back on itself. This - // function walks down the kinks in a degenerate portion of a polygon until - // it finds a clear geometric result. In the vast majority of cases the loop - // will only need one or two iterations. + // Returns true for convex or colinear ears. bool IsConvex(double epsilon) const { - const int convexity = CCW(left->pos, pos, right->pos, epsilon); - if (convexity != 0) { - return convexity > 0; - } - if (la::dot(left->pos - pos, right->pos - pos) <= 0) { - return true; - } - return left->InsideEdge(left->right, epsilon, true); + return CCW(left->pos, pos, right->pos, epsilon) >= 0; } // Subtly different from !IsConvex because IsConvex will return true for @@ -489,7 +488,7 @@ class EarClip { // values < -epsilon so they will never affect validity. The first // totalCost is designed to give priority to sharper angles. Any cost < (-1 // - epsilon) has satisfied the Delaunay condition. - double EarCost(double epsilon, IdxCollider &collider) const { + double EarCost(double epsilon, IdxCollider& collider) const { vec2 openSide = left->pos - right->pos; const vec2 center = 0.5 * (left->pos + right->pos); const double scale = 4 / la::dot(openSide, openSide); @@ -502,38 +501,32 @@ class EarClip { return totalCost; } - Box earBox = Box{vec3(center.x - radius, center.y - radius, 0), - vec3(center.x + radius, center.y + radius, 0)}; - earBox.Union(vec3(pos, 0)); - collider.collider.Collisions(VecView(&earBox, 1), - collider.ind); + Rect earBox = Rect(vec2(center.x - radius, center.y - radius), + vec2(center.x + radius, center.y + radius)); + earBox.Union(pos); + earBox.min -= epsilon; + earBox.max += epsilon; const int lid = left->mesh_idx; const int rid = right->mesh_idx; - - totalCost = transform_reduce( - countAt(0), countAt(collider.ind.size()), totalCost, - [](double a, double b) { return std::max(a, b); }, - [&](size_t i) { - const VertItr test = collider.itr[collider.ind.Get(i, true)]; - if (!Clipped(test) && test->mesh_idx != mesh_idx && - test->mesh_idx != lid && - test->mesh_idx != rid) { // Skip duplicated verts - double cost = Cost(test, openSide, epsilon); - if (cost < -epsilon) { - cost = DelaunayCost(test->pos - center, scale, epsilon); - } - return cost; - } - return std::numeric_limits::lowest(); - }); - collider.ind.Clear(); + QueryTwoDTree(collider.points, earBox, [&](PolyVert point) { + const VertItr test = collider.itr[point.idx]; + if (!Clipped(test) && test->mesh_idx != mesh_idx && + test->mesh_idx != lid && + test->mesh_idx != rid) { // Skip duplicated verts + double cost = Cost(test, openSide, epsilon); + if (cost < -epsilon) { + cost = DelaunayCost(test->pos - center, scale, epsilon); + } + if (cost > totalCost) totalCost = cost; + } + }); return totalCost; } void PrintVert() const { #ifdef MANIFOLD_DEBUG - if (!params.verbose) return; + if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return; std::cout << "vert: " << mesh_idx << ", left: " << left->mesh_idx << ", right: " << right->mesh_idx << ", cost: " << cost << std::endl; @@ -603,8 +596,7 @@ class EarClip { // If an ear will make a degenerate triangle, clip it early to avoid // difficulty in key-holing. This function is recursive, as the process of - // clipping may cause the neighbors to degenerate. Reflex degenerates *must - // not* be clipped, unless they have a short edge. + // clipping may cause the neighbors to degenerate. void ClipIfDegenerate(VertItr ear) { if (Clipped(ear)) { return; @@ -614,8 +606,7 @@ class EarClip { } if (ear->IsShort(epsilon_) || (CCW(ear->left->pos, ear->pos, ear->right->pos, epsilon_) == 0 && - la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0 && - ear->IsConvex(epsilon_))) { + la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0)) { ClipEar(ear); ClipIfDegenerate(ear->left); ClipIfDegenerate(ear->right); @@ -623,11 +614,18 @@ class EarClip { } // Build the circular list polygon structures. - std::vector Initialize(const PolygonsIdx &polys) { + std::vector Initialize(const PolygonsIdx& polys) { std::vector starts; - for (const SimplePolygonIdx &poly : polys) { + const auto invalidItr = polygon_.begin(); + for (const SimplePolygonIdx& poly : polys) { auto vert = poly.begin(); - polygon_.push_back({vert->idx, 0.0, earsQueue_.end(), vert->pos}); + polygon_.push_back({vert->idx, + 0.0, + earsQueue_.end(), + vert->pos, + {0, 0}, + invalidItr, + invalidItr}); const VertItr first = std::prev(polygon_.end()); bBox_.Union(first->pos); @@ -639,7 +637,13 @@ class EarClip { for (++vert; vert != poly.end(); ++vert) { bBox_.Union(vert->pos); - polygon_.push_back({vert->idx, 0.0, earsQueue_.end(), vert->pos}); + polygon_.push_back({vert->idx, + 0.0, + earsQueue_.end(), + vert->pos, + {0, 0}, + invalidItr, + invalidItr}); VertItr next = std::prev(polygon_.end()); Link(last, next); @@ -740,7 +744,7 @@ class EarClip { JoinPolygons(start, connector); #ifdef MANIFOLD_DEBUG - if (params.verbose) { + if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) { std::cout << "connected " << start->mesh_idx << " to " << connector->mesh_idx << std::endl; } @@ -767,7 +771,8 @@ class EarClip { above * CCW(start->pos, vert->pos, connector->pos, epsilon_); if (vert->pos.x > start->pos.x - epsilon_ && vert->pos.y * above > start->pos.y * above - epsilon_ && - (inside > 0 || (inside == 0 && vert->pos.x < connector->pos.x)) && + (inside > 0 || (inside == 0 && vert->pos.x < connector->pos.x && + vert->pos.y * above < connector->pos.y * above)) && vert->InsideEdge(edge, epsilon_, true) && vert->IsReflex(epsilon_)) { connector = vert; } @@ -803,7 +808,7 @@ class EarClip { // Recalculate the cost of the Vert v ear, updating it in the queue by // removing and reinserting it. - void ProcessEar(VertItr v, IdxCollider &collider) { + void ProcessEar(VertItr v, IdxCollider& collider) { if (v->ear != earsQueue_.end()) { earsQueue_.erase(v->ear); v->ear = earsQueue_.end(); @@ -823,35 +828,16 @@ class EarClip { // epsilon_. Each ear uses this BVH to quickly find a subset of vertices to // check for cost. IdxCollider VertCollider(VertItr start) const { - Vec vertBox; - Vec vertMorton; + ZoneScoped; std::vector itr; - const Box box(vec3(bBox_.min, 0), vec3(bBox_.max, 0)); - - Loop(start, [&vertBox, &vertMorton, &itr, &box, this](VertItr v) { + Vec points; + Loop(start, [&itr, &points](VertItr v) { + points.push_back({v->pos, static_cast(itr.size())}); itr.push_back(v); - const vec3 pos(v->pos, 0); - vertBox.push_back({pos - epsilon_, pos + epsilon_}); - vertMorton.push_back(Collider::MortonCode(pos, box)); }); - if (itr.empty()) { - return {Collider(), itr}; - } - - const int numVert = itr.size(); - Vec vertNew2Old(numVert); - sequence(vertNew2Old.begin(), vertNew2Old.end()); - - stable_sort(vertNew2Old.begin(), vertNew2Old.end(), - [&vertMorton](const int a, const int b) { - return vertMorton[a] < vertMorton[b]; - }); - Permute(vertMorton, vertNew2Old); - Permute(vertBox, vertNew2Old); - Permute(itr, vertNew2Old); - - return {Collider(vertBox, vertMorton), itr}; + BuildTwoDTree(points); + return {std::move(points), std::move(itr)}; } // The main ear-clipping loop. This is called once for each simple polygon - @@ -907,7 +893,7 @@ class EarClip { void Dump(VertItrC start) const { #ifdef MANIFOLD_DEBUG - if (!params.verbose) return; + if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return; VertItrC v = start; std::cout << "show(array([" << std::setprecision(15) << std::endl; do { @@ -944,16 +930,21 @@ namespace manifold { * references back to the original vertices. * @param epsilon The value of ε, bounding the uncertainty of the * input. + * @param allowConvex If true (default), the triangulator will use a fast + * triangulation if the input is convex, falling back to ear-clipping if not. + * The triangle quality may be lower, so set to false to disable this + * optimization. * @return std::vector The triangles, referencing the original * vertex indicies. */ -std::vector TriangulateIdx(const PolygonsIdx &polys, double epsilon) { +std::vector TriangulateIdx(const PolygonsIdx& polys, double epsilon, + bool allowConvex) { std::vector triangles; double updatedEpsilon = epsilon; #ifdef MANIFOLD_DEBUG try { #endif - if (IsConvex(polys, epsilon)) { // fast path + if (allowConvex && IsConvex(polys, epsilon)) { // fast path triangles = TriangulateConvex(polys); } else { EarClip triangulator(polys, epsilon); @@ -961,18 +952,18 @@ std::vector TriangulateIdx(const PolygonsIdx &polys, double epsilon) { updatedEpsilon = triangulator.GetPrecision(); } #ifdef MANIFOLD_DEBUG - if (params.intermediateChecks) { + if (ManifoldParams().intermediateChecks) { CheckTopology(triangles, polys); - if (!params.processOverlaps) { + if (!ManifoldParams().processOverlaps) { CheckGeometry(triangles, polys, 2 * updatedEpsilon); } } - } catch (const geometryErr &e) { - if (!params.suppressErrors) { + } catch (const geometryErr& e) { + if (!ManifoldParams().suppressErrors) { PrintFailure(e, polys, triangles, updatedEpsilon); } throw; - } catch (const std::exception &e) { + } catch (const std::exception& e) { PrintFailure(e, polys, triangles, updatedEpsilon); throw; } @@ -989,22 +980,25 @@ std::vector TriangulateIdx(const PolygonsIdx &polys, double epsilon) { * polygons and/or holes. * @param epsilon The value of ε, bounding the uncertainty of the * input. + * @param allowConvex If true (default), the triangulator will use a fast + * triangulation if the input is convex, falling back to ear-clipping if not. + * The triangle quality may be lower, so set to false to disable this + * optimization. * @return std::vector The triangles, referencing the original * polygon points in order. */ -std::vector Triangulate(const Polygons &polygons, double epsilon) { +std::vector Triangulate(const Polygons& polygons, double epsilon, + bool allowConvex) { int idx = 0; PolygonsIdx polygonsIndexed; - for (const auto &poly : polygons) { + for (const auto& poly : polygons) { SimplePolygonIdx simpleIndexed; - for (const vec2 &polyVert : poly) { + for (const vec2& polyVert : poly) { simpleIndexed.push_back({polyVert, idx++}); } polygonsIndexed.push_back(simpleIndexed); } - return TriangulateIdx(polygonsIndexed, epsilon); + return TriangulateIdx(polygonsIndexed, epsilon, allowConvex); } -ExecutionParams &PolygonParams() { return params; } - } // namespace manifold diff --git a/thirdparty/manifold/src/properties.cpp b/thirdparty/manifold/src/properties.cpp index 5b6403a6c1..d66b05595d 100644 --- a/thirdparty/manifold/src/properties.cpp +++ b/thirdparty/manifold/src/properties.cpp @@ -14,9 +14,13 @@ #include -#include "./impl.h" -#include "./parallel.h" -#include "./tri_dist.h" +#if MANIFOLD_PAR == 1 +#include +#endif + +#include "impl.h" +#include "parallel.h" +#include "tri_dist.h" namespace { using namespace manifold; @@ -64,48 +68,6 @@ struct CurvatureAngles { } }; -struct UpdateProperties { - VecView triProp; - VecView properties; - VecView counters; - - VecView oldProperties; - VecView halfedge; - VecView meanCurvature; - VecView gaussianCurvature; - const int oldNumProp; - const int numProp; - const int gaussianIdx; - const int meanIdx; - - void operator()(const size_t tri) { - for (const int i : {0, 1, 2}) { - const int vert = halfedge[3 * tri + i].startVert; - if (oldNumProp == 0) { - triProp[tri][i] = vert; - } - const int propVert = triProp[tri][i]; - - auto old = std::atomic_exchange( - reinterpret_cast*>(&counters[propVert]), - static_cast(1)); - if (old == 1) continue; - - for (int p = 0; p < oldNumProp; ++p) { - properties[numProp * propVert + p] = - oldProperties[oldNumProp * propVert + p]; - } - - if (gaussianIdx >= 0) { - properties[numProp * propVert + gaussianIdx] = gaussianCurvature[vert]; - } - if (meanIdx >= 0) { - properties[numProp * propVert + meanIdx] = meanCurvature[vert]; - } - } - } -}; - struct CheckHalfedges { VecView halfedges; @@ -138,33 +100,8 @@ struct CheckCCW { for (int i : {0, 1, 2}) v[i] = projection * vertPos[halfedges[3 * face + i].startVert]; - int ccw = CCW(v[0], v[1], v[2], std::abs(tol)); - bool check = tol > 0 ? ccw >= 0 : ccw == 0; - -#ifdef MANIFOLD_DEBUG - if (tol > 0 && !check) { - vec2 v1 = v[1] - v[0]; - vec2 v2 = v[2] - v[0]; - double area = v1.x * v2.y - v1.y * v2.x; - double base2 = std::max(la::dot(v1, v1), la::dot(v2, v2)); - double base = std::sqrt(base2); - vec3 V0 = vertPos[halfedges[3 * face].startVert]; - vec3 V1 = vertPos[halfedges[3 * face + 1].startVert]; - vec3 V2 = vertPos[halfedges[3 * face + 2].startVert]; - vec3 norm = la::cross(V1 - V0, V2 - V0); - printf( - "Tri %ld does not match normal, approx height = %g, base = %g\n" - "tol = %g, area2 = %g, base2*tol2 = %g\n" - "normal = %g, %g, %g\n" - "norm = %g, %g, %g\nverts: %d, %d, %d\n", - static_cast(face), area / base, base, tol, area * area, - base2 * tol * tol, triNormal[face].x, triNormal[face].y, - triNormal[face].z, norm.x, norm.y, norm.z, - halfedges[3 * face].startVert, halfedges[3 * face + 1].startVert, - halfedges[3 * face + 2].startVert); - } -#endif - return check; + const int ccw = CCW(v[0], v[1], v[2], std::abs(tol)); + return tol > 0 ? ccw >= 0 : ccw == 0; } }; } // namespace @@ -211,55 +148,58 @@ std::mutex dump_lock; * Note that this is not checking for epsilon-validity. */ bool Manifold::Impl::IsSelfIntersecting() const { - const double epsilonSq = epsilon_ * epsilon_; + const double ep = 2 * epsilon_; + const double epsilonSq = ep * ep; Vec faceBox; Vec faceMorton; GetFaceBoxMorton(faceBox, faceMorton); - SparseIndices collisions = collider_.Collisions(faceBox.cview()); - const bool verbose = ManifoldParams().verbose; - return !all_of(countAt(0), countAt(collisions.size()), [&](size_t i) { - size_t x = collisions.Get(i, false); - size_t y = collisions.Get(i, true); - std::array tri_x, tri_y; + std::atomic intersecting(false); + + auto f = [&](int tri0, int tri1) { + std::array triVerts0, triVerts1; for (int i : {0, 1, 2}) { - tri_x[i] = vertPos_[halfedge_[3 * x + i].startVert]; - tri_y[i] = vertPos_[halfedge_[3 * y + i].startVert]; + triVerts0[i] = vertPos_[halfedge_[3 * tri0 + i].startVert]; + triVerts1[i] = vertPos_[halfedge_[3 * tri1 + i].startVert]; } - // if triangles x and y share a vertex, return true to skip the + // if triangles tri0 and tri1 share a vertex, return true to skip the // check. we relax the sharing criteria a bit to allow for at most // distance epsilon squared for (int i : {0, 1, 2}) for (int j : {0, 1, 2}) - if (distance2(tri_x[i], tri_y[j]) <= epsilonSq) return true; + if (distance2(triVerts0[i], triVerts1[j]) <= epsilonSq) return; - if (DistanceTriangleTriangleSquared(tri_x, tri_y) == 0.0) { + if (DistanceTriangleTriangleSquared(triVerts0, triVerts1) == 0.0) { // try to move the triangles around the normal of the other face - std::array tmp_x, tmp_y; - for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] + epsilon_ * faceNormal_[y]; - if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true; - for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] - epsilon_ * faceNormal_[y]; - if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true; - for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] + epsilon_ * faceNormal_[x]; - if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true; - for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] - epsilon_ * faceNormal_[x]; - if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true; + std::array tmp0, tmp1; + for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] + ep * faceNormal_[tri1]; + if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return; + for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] - ep * faceNormal_[tri1]; + if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return; + for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] + ep * faceNormal_[tri0]; + if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return; + for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] - ep * faceNormal_[tri0]; + if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return; #ifdef MANIFOLD_DEBUG - if (verbose) { + if (ManifoldParams().verbose > 0) { dump_lock.lock(); std::cout << "intersecting:" << std::endl; - for (int i : {0, 1, 2}) std::cout << tri_x[i] << " "; + for (int i : {0, 1, 2}) std::cout << triVerts0[i] << " "; std::cout << std::endl; - for (int i : {0, 1, 2}) std::cout << tri_y[i] << " "; + for (int i : {0, 1, 2}) std::cout << triVerts1[i] << " "; std::cout << std::endl; dump_lock.unlock(); } #endif - return false; + intersecting.store(true); } - return true; - }); + }; + + auto recorder = MakeSimpleRecorder(f); + collider_.Collisions(faceBox.cview(), recorder); + + return intersecting.load(); } /** @@ -335,20 +275,36 @@ void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) { const int oldNumProp = NumProp(); const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1); - const Vec oldProperties = meshRelation_.properties; - meshRelation_.properties = Vec(numProp * NumPropVert(), 0); - meshRelation_.numProp = numProp; - if (meshRelation_.triProperties.size() == 0) { - meshRelation_.triProperties.resize(NumTri()); - } + const Vec oldProperties = properties_; + properties_ = Vec(numProp * NumPropVert(), 0); + numProp_ = numProp; - const Vec counters(NumPropVert(), 0); - for_each_n( - policy, countAt(0_uz), NumTri(), - UpdateProperties({meshRelation_.triProperties, meshRelation_.properties, - counters, oldProperties, halfedge_, vertMeanCurvature, - vertGaussianCurvature, oldNumProp, numProp, gaussianIdx, - meanIdx})); + Vec counters(NumPropVert(), 0); + for_each_n(policy, countAt(0_uz), NumTri(), [&](const size_t tri) { + for (const int i : {0, 1, 2}) { + const Halfedge& edge = halfedge_[3 * tri + i]; + const int vert = edge.startVert; + const int propVert = edge.propVert; + + auto old = std::atomic_exchange( + reinterpret_cast*>(&counters[propVert]), + static_cast(1)); + if (old == 1) continue; + + for (int p = 0; p < oldNumProp; ++p) { + properties_[numProp * propVert + p] = + oldProperties[oldNumProp * propVert + p]; + } + + if (gaussianIdx >= 0) { + properties_[numProp * propVert + gaussianIdx] = + vertGaussianCurvature[vert]; + } + if (meanIdx >= 0) { + properties_[numProp * propVert + meanIdx] = vertMeanCurvature[vert]; + } + } + }); } /** @@ -404,6 +360,36 @@ bool Manifold::Impl::IsIndexInBounds(VecView triVerts) const { return minmax[0] >= 0 && minmax[1] < static_cast(NumVert()); } +struct MinDistanceRecorder { + using Local = double; + const Manifold::Impl &self, &other; +#if MANIFOLD_PAR == 1 + tbb::combinable store = tbb::combinable( + []() { return std::numeric_limits::infinity(); }); + Local& local() { return store.local(); } + double get() { + double result = std::numeric_limits::infinity(); + store.combine_each([&](double& val) { result = std::min(result, val); }); + return result; + } +#else + double result = std::numeric_limits::infinity(); + Local& local() { return result; } + double get() { return result; } +#endif + + void record(int triOther, int tri, double& minDistance) { + std::array p; + std::array q; + + for (const int j : {0, 1, 2}) { + p[j] = self.vertPos_[self.halfedge_[3 * tri + j].startVert]; + q[j] = other.vertPos_[other.halfedge_[3 * triOther + j].startVert]; + } + minDistance = std::min(minDistance, DistanceTriangleTriangleSquared(p, q)); + } +}; + /* * Returns the minimum gap between two manifolds. Returns a double between * 0 and searchLength. @@ -422,26 +408,11 @@ double Manifold::Impl::MinGap(const Manifold::Impl& other, box.max + vec3(searchLength)); }); - SparseIndices collisions = collider_.Collisions(faceBoxOther.cview()); - - double minDistanceSquared = transform_reduce( - countAt(0_uz), countAt(collisions.size()), searchLength * searchLength, - [](double a, double b) { return std::min(a, b); }, - [&collisions, this, &other](int i) { - const int tri = collisions.Get(i, 1); - const int triOther = collisions.Get(i, 0); - - std::array p; - std::array q; - - for (const int j : {0, 1, 2}) { - p[j] = vertPos_[halfedge_[3 * tri + j].startVert]; - q[j] = other.vertPos_[other.halfedge_[3 * triOther + j].startVert]; - } - - return DistanceTriangleTriangleSquared(p, q); - }); - + MinDistanceRecorder recorder{*this, other}; + collider_.Collisions(faceBoxOther.cview(), + recorder, false); + double minDistanceSquared = + std::min(recorder.get(), searchLength * searchLength); return sqrt(minDistanceSquared); }; diff --git a/thirdparty/manifold/src/quickhull.cpp b/thirdparty/manifold/src/quickhull.cpp index be2b89e1ca..54733d6807 100644 --- a/thirdparty/manifold/src/quickhull.cpp +++ b/thirdparty/manifold/src/quickhull.cpp @@ -20,7 +20,7 @@ #include #include -#include "./impl.h" +#include "impl.h" namespace manifold { @@ -289,7 +289,7 @@ std::pair, Vec> QuickHull::buildMesh(double epsilon) { for_each( autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(), [&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; }); - counts.resize(originalVertexData.size() + 1); + counts.resize_nofill(originalVertexData.size() + 1); fill(counts.begin(), counts.end(), 0); // remove unused vertices @@ -804,7 +804,7 @@ void QuickHull::setupInitialTetrahedron() { std::unique_ptr> QuickHull::getIndexVectorFromPool() { auto r = indexVectorPool.get(); - r->resize(0); + r->clear(); return r; } @@ -851,10 +851,9 @@ void Manifold::Impl::Hull(VecView vertPos) { std::tie(halfedge_, vertPos_) = qh.buildMesh(); CalculateBBox(); SetEpsilon(); - CalculateNormals(); InitializeOriginal(); Finish(); - CreateFaces(); + MarkCoplanar(); } } // namespace manifold diff --git a/thirdparty/manifold/src/quickhull.h b/thirdparty/manifold/src/quickhull.h index 3f90575097..1167143c6a 100644 --- a/thirdparty/manifold/src/quickhull.h +++ b/thirdparty/manifold/src/quickhull.h @@ -58,8 +58,8 @@ #include #include -#include "./shared.h" -#include "./vec.h" +#include "shared.h" +#include "vec.h" namespace manifold { diff --git a/thirdparty/manifold/src/sdf.cpp b/thirdparty/manifold/src/sdf.cpp index ad95b9b6fe..00bd40f81c 100644 --- a/thirdparty/manifold/src/sdf.cpp +++ b/thirdparty/manifold/src/sdf.cpp @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./hashtable.h" -#include "./impl.h" -#include "./parallel.h" -#include "./utils.h" -#include "./vec.h" +#include "hashtable.h" +#include "impl.h" #include "manifold/manifold.h" +#include "parallel.h" +#include "utils.h" +#include "vec.h" namespace { using namespace manifold; @@ -95,13 +95,14 @@ ivec4 Neighbor(ivec4 base, int i) { return neighborIndex; } -Uint64 EncodeIndex(ivec4 gridPos, ivec3 gridPow) { - return static_cast(gridPos.w) | static_cast(gridPos.z) << 1 | - static_cast(gridPos.y) << (1 + gridPow.z) | - static_cast(gridPos.x) << (1 + gridPow.z + gridPow.y); +uint64_t EncodeIndex(ivec4 gridPos, ivec3 gridPow) { + return static_cast(gridPos.w) | + static_cast(gridPos.z) << 1 | + static_cast(gridPos.y) << (1 + gridPow.z) | + static_cast(gridPos.x) << (1 + gridPow.z + gridPow.y); } -ivec4 DecodeIndex(Uint64 idx, ivec3 gridPow) { +ivec4 DecodeIndex(uint64_t idx, ivec3 gridPow) { ivec4 gridPos; gridPos.w = idx & 1; idx = idx >> 1; @@ -211,7 +212,7 @@ struct NearSurface { const double level; const double tol; - inline void operator()(Uint64 index) { + inline void operator()(uint64_t index) { ZoneScoped; if (gridVerts.Full()) return; @@ -296,7 +297,7 @@ struct ComputeVerts { void operator()(int idx) { ZoneScoped; - Uint64 baseKey = gridVerts.KeyAt(idx); + uint64_t baseKey = gridVerts.KeyAt(idx); if (baseKey == kOpen) return; GridVert& gridVert = gridVerts.At(idx); @@ -358,7 +359,7 @@ struct BuildTris { void operator()(int idx) { ZoneScoped; - Uint64 baseKey = gridVerts.KeyAt(idx); + uint64_t baseKey = gridVerts.KeyAt(idx); if (baseKey == kOpen) return; const GridVert& base = gridVerts.At(idx); @@ -467,7 +468,7 @@ Manifold Manifold::LevelSet(std::function sdf, Box bounds, const vec3 spacing = dim / (vec3(gridSize - 1)); const ivec3 gridPow(la::log2(gridSize + 2) + 1); - const Uint64 maxIndex = EncodeIndex(ivec4(gridSize + 2, 1), gridPow); + const uint64_t maxIndex = EncodeIndex(ivec4(gridSize + 2, 1), gridPow); // Parallel policies violate will crash language runtimes with runtime locks // that expect to not be called back by unregistered threads. This allows @@ -479,15 +480,15 @@ Manifold Manifold::LevelSet(std::function sdf, Box bounds, Vec voxels(maxIndex); for_each_n( pol, countAt(0_uz), maxIndex, - [&voxels, sdf, level, origin, spacing, gridSize, gridPow](Uint64 idx) { + [&voxels, sdf, level, origin, spacing, gridSize, gridPow](uint64_t idx) { voxels[idx] = BoundedSDF(DecodeIndex(idx, gridPow) - kVoxelOffset, origin, spacing, gridSize, level, sdf); }); size_t tableSize = std::min( - 2 * maxIndex, static_cast(10 * la::pow(maxIndex, 0.667))); + 2 * maxIndex, static_cast(10 * la::pow(maxIndex, 0.667))); HashTable gridVerts(tableSize); - vertPos.resize(gridVerts.Size() * 7); + vertPos.resize_nofill(gridVerts.Size() * 7); while (1) { Vec index(1, 0); @@ -497,7 +498,7 @@ Manifold Manifold::LevelSet(std::function sdf, Box bounds, if (gridVerts.Full()) { // Resize HashTable const vec3 lastVert = vertPos[index[0] - 1]; - const Uint64 lastIndex = + const uint64_t lastIndex = EncodeIndex(ivec4(ivec3((lastVert - origin) / spacing), 1), gridPow); const double ratio = static_cast(maxIndex) / lastIndex; @@ -529,6 +530,7 @@ Manifold Manifold::LevelSet(std::function sdf, Box bounds, pImpl_->RemoveUnreferencedVerts(); pImpl_->Finish(); pImpl_->InitializeOriginal(); + pImpl_->MarkCoplanar(); return Manifold(pImpl_); } } // namespace manifold diff --git a/thirdparty/manifold/src/shared.h b/thirdparty/manifold/src/shared.h index 3f3336141d..7a28c41421 100644 --- a/thirdparty/manifold/src/shared.h +++ b/thirdparty/manifold/src/shared.h @@ -14,10 +14,9 @@ #pragma once -#include "./parallel.h" -#include "./sparse.h" -#include "./utils.h" -#include "./vec.h" +#include "parallel.h" +#include "utils.h" +#include "vec.h" namespace manifold { @@ -120,6 +119,7 @@ inline vec3 GetBarycentric(const vec3& v, const mat3& triPos, struct Halfedge { int startVert, endVert; int pairedHalfedge; + int propVert; bool IsForward() const { return startVert < endVert; } bool operator<(const Halfedge& other) const { return startVert == other.startVert ? endVert < other.endVert @@ -142,12 +142,13 @@ struct TriRef { int originalID; /// Probably the triangle index of the original triangle this was part of: /// Mesh.triVerts[tri], but it's an input, so just pass it along unchanged. - int tri; - /// Triangles with the same face ID are coplanar. int faceID; + /// Triangles with the same coplanar ID are coplanar. + int coplanarID; bool SameFace(const TriRef& other) const { - return meshID == other.meshID && faceID == other.faceID; + return meshID == other.meshID && coplanarID == other.coplanarID && + faceID == other.faceID; } }; @@ -188,22 +189,12 @@ Vec inline CreateTmpEdges(const Vec& halfedge) { return edges; } -template -struct ReindexEdge { - VecView edges; - SparseIndices& indices; - - void operator()(size_t i) { - int& edge = indices.Get(i, inverted); - edge = edges[edge].halfedgeIdx; - } -}; - #ifdef MANIFOLD_DEBUG inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) { return stream << "startVert = " << edge.startVert << ", endVert = " << edge.endVert - << ", pairedHalfedge = " << edge.pairedHalfedge; + << ", pairedHalfedge = " << edge.pairedHalfedge + << ", propVert = " << edge.propVert; } inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) { @@ -212,8 +203,9 @@ inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) { inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) { return stream << "meshID: " << ref.meshID - << ", originalID: " << ref.originalID << ", tri: " << ref.tri - << ", faceID: " << ref.faceID; + << ", originalID: " << ref.originalID + << ", faceID: " << ref.faceID + << ", coplanarID: " << ref.coplanarID; } #endif } // namespace manifold diff --git a/thirdparty/manifold/src/smoothing.cpp b/thirdparty/manifold/src/smoothing.cpp index 57d81b4623..bf8903dfb3 100644 --- a/thirdparty/manifold/src/smoothing.cpp +++ b/thirdparty/manifold/src/smoothing.cpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./impl.h" -#include "./parallel.h" +#include "impl.h" +#include "parallel.h" namespace { using namespace manifold; @@ -254,13 +254,10 @@ namespace manifold { * normalIdx shows the beginning of where normals are stored in the properties. */ vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const { - const int tri = halfedge / 3; - const int j = halfedge % 3; - const int prop = meshRelation_.triProperties[tri][j]; + const int prop = halfedge_[halfedge].propVert; vec3 normal; for (const int i : {0, 1, 2}) { - normal[i] = - meshRelation_.properties[prop * meshRelation_.numProp + normalIdx + i]; + normal[i] = properties_[prop * numProp_ + normalIdx + i]; } return normal; } @@ -320,12 +317,12 @@ bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const { // sharpenedEdges are referenced to the input Mesh, but the triangles have // been sorted in creating the Manifold, so the indices are converted using -// meshRelation_. +// meshRelation_.faceID, which temporarily holds the mapping. std::vector Manifold::Impl::UpdateSharpenedEdges( const std::vector& sharpenedEdges) const { std::unordered_map oldHalfedge2New; for (size_t tri = 0; tri < NumTri(); ++tri) { - int oldTri = meshRelation_.triRef[tri].tri; + int oldTri = meshRelation_.triRef[tri].faceID; for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i; } std::vector newSharp = sharpenedEdges; @@ -371,7 +368,7 @@ Vec Manifold::Impl::FlatFaces() const { // gets -1, and if there are more than one it gets -2. Vec Manifold::Impl::VertFlatFace(const Vec& flatFaces) const { Vec vertFlatFace(NumVert(), -1); - Vec vertRef(NumVert(), {-1, -1, -1}); + Vec vertRef(NumVert(), {-1, -1, -1, -1}); for (size_t tri = 0; tri < NumTri(); ++tri) { if (flatFaces[tri]) { for (const int j : {0, 1, 2}) { @@ -439,7 +436,6 @@ void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) { if (normalIdx < 0) return; const int oldNumProp = NumProp(); - const int numTri = NumTri(); Vec triIsFlatFace = FlatFaces(); Vec vertFlatFace = VertFlatFace(triIsFlatFace); @@ -470,164 +466,153 @@ void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) { const int numProp = std::max(oldNumProp, normalIdx + 3); Vec oldProperties(numProp * NumPropVert(), 0); - meshRelation_.properties.swap(oldProperties); - meshRelation_.numProp = numProp; - if (meshRelation_.triProperties.size() == 0) { - meshRelation_.triProperties.resize(numTri); - for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri, [this](int tri) { - for (const int j : {0, 1, 2}) - meshRelation_.triProperties[tri][j] = halfedge_[3 * tri + j].startVert; - }); - } - Vec oldTriProp(numTri, {-1, -1, -1}); - meshRelation_.triProperties.swap(oldTriProp); + properties_.swap(oldProperties); + numProp_ = numProp; - for (int tri = 0; tri < numTri; ++tri) { - for (const int i : {0, 1, 2}) { - if (meshRelation_.triProperties[tri][i] >= 0) continue; - int startEdge = 3 * tri + i; - const int vert = halfedge_[startEdge].startVert; + Vec oldHalfedgeProp(halfedge_.size()); + for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(), + [this, &oldHalfedgeProp](int i) { + oldHalfedgeProp[i] = halfedge_[i].propVert; + halfedge_[i].propVert = -1; + }); - if (vertNumSharp[vert] < 2) { // vertex has single normal - const vec3 normal = vertFlatFace[vert] >= 0 - ? faceNormal_[vertFlatFace[vert]] - : vertNormal_[vert]; - int lastProp = -1; - ForVert(startEdge, [&](int current) { - const int thisTri = current / 3; - const int j = current - 3 * thisTri; - const int prop = oldTriProp[thisTri][j]; - meshRelation_.triProperties[thisTri][j] = prop; - if (prop == lastProp) return; - lastProp = prop; - // update property vertex - auto start = oldProperties.begin() + prop * oldNumProp; + const int numEdge = halfedge_.size(); + for (int startEdge = 0; startEdge < numEdge; ++startEdge) { + if (halfedge_[startEdge].propVert >= 0) continue; + const int vert = halfedge_[startEdge].startVert; + + if (vertNumSharp[vert] < 2) { // vertex has single normal + const vec3 normal = vertFlatFace[vert] >= 0 + ? faceNormal_[vertFlatFace[vert]] + : vertNormal_[vert]; + int lastProp = -1; + ForVert(startEdge, [&](int current) { + const int prop = oldHalfedgeProp[current]; + halfedge_[current].propVert = prop; + if (prop == lastProp) return; + lastProp = prop; + // update property vertex + auto start = oldProperties.begin() + prop * oldNumProp; + std::copy(start, start + oldNumProp, + properties_.begin() + prop * numProp); + for (const int i : {0, 1, 2}) + properties_[prop * numProp + normalIdx + i] = normal[i]; + }); + } else { // vertex has multiple normals + const vec3 centerPos = vertPos_[vert]; + // Length degree + std::vector group; + // Length number of normals + std::vector normals; + int current = startEdge; + int prevFace = current / 3; + + do { // find a sharp edge to start on + int next = NextHalfedge(halfedge_[current].pairedHalfedge); + const int face = next / 3; + + const double dihedral = degrees( + std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace]))); + if (dihedral > minSharpAngle || + triIsFlatFace[face] != triIsFlatFace[prevFace] || + (triIsFlatFace[face] && triIsFlatFace[prevFace] && + !meshRelation_.triRef[face].SameFace( + meshRelation_.triRef[prevFace]))) { + break; + } + current = next; + prevFace = face; + } while (current != startEdge); + + const int endEdge = current; + + struct FaceEdge { + int face; + vec3 edgeVec; + }; + + // calculate pseudo-normals between each sharp edge + ForVert( + endEdge, + [this, centerPos, &vertNumSharp, &vertFlatFace](int current) { + if (IsInsideQuad(current)) { + return FaceEdge({current / 3, vec3(NAN)}); + } + const int vert = halfedge_[current].endVert; + vec3 pos = vertPos_[vert]; + if (vertNumSharp[vert] < 2) { + // opposite vert has fixed normal + const vec3 normal = vertFlatFace[vert] >= 0 + ? faceNormal_[vertFlatFace[vert]] + : vertNormal_[vert]; + // Flair out the normal we're calculating to give the edge a + // more constant curvature to meet the opposite normal. Achieve + // this by pointing the tangent toward the opposite bezier + // control point instead of the vert itself. + pos += vec3( + TangentFromNormal(normal, halfedge_[current].pairedHalfedge)); + } + return FaceEdge({current / 3, SafeNormalize(pos - centerPos)}); + }, + [this, &triIsFlatFace, &normals, &group, minSharpAngle]( + int, const FaceEdge& here, FaceEdge& next) { + const double dihedral = degrees(std::acos( + la::dot(faceNormal_[here.face], faceNormal_[next.face]))); + if (dihedral > minSharpAngle || + triIsFlatFace[here.face] != triIsFlatFace[next.face] || + (triIsFlatFace[here.face] && triIsFlatFace[next.face] && + !meshRelation_.triRef[here.face].SameFace( + meshRelation_.triRef[next.face]))) { + normals.push_back(vec3(0.0)); + } + group.push_back(normals.size() - 1); + if (std::isfinite(next.edgeVec.x)) { + normals.back() += + SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) * + AngleBetween(here.edgeVec, next.edgeVec); + } else { + next.edgeVec = here.edgeVec; + } + }); + + for (auto& normal : normals) { + normal = SafeNormalize(normal); + } + + int lastGroup = 0; + int lastProp = -1; + int newProp = -1; + int idx = 0; + ForVert(endEdge, [&](int current1) { + const int prop = oldHalfedgeProp[current1]; + auto start = oldProperties.begin() + prop * oldNumProp; + + if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) { + // split property vertex, duplicating but with an updated normal + lastGroup = group[idx]; + newProp = NumPropVert(); + properties_.resize(properties_.size() + numProp); std::copy(start, start + oldNumProp, - meshRelation_.properties.begin() + prop * numProp); - for (const int i : {0, 1, 2}) - meshRelation_.properties[prop * numProp + normalIdx + i] = - normal[i]; - }); - } else { // vertex has multiple normals - const vec3 centerPos = vertPos_[vert]; - // Length degree - std::vector group; - // Length number of normals - std::vector normals; - int current = startEdge; - int prevFace = current / 3; - - do { // find a sharp edge to start on - int next = NextHalfedge(halfedge_[current].pairedHalfedge); - const int face = next / 3; - - const double dihedral = degrees( - std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace]))); - if (dihedral > minSharpAngle || - triIsFlatFace[face] != triIsFlatFace[prevFace] || - (triIsFlatFace[face] && triIsFlatFace[prevFace] && - !meshRelation_.triRef[face].SameFace( - meshRelation_.triRef[prevFace]))) { - break; + properties_.begin() + newProp * numProp); + for (const int i : {0, 1, 2}) { + properties_[newProp * numProp + normalIdx + i] = + normals[group[idx]][i]; } - current = next; - prevFace = face; - } while (current != startEdge); - - const int endEdge = current; - - struct FaceEdge { - int face; - vec3 edgeVec; - }; - - // calculate pseudo-normals between each sharp edge - ForVert( - endEdge, - [this, centerPos, &vertNumSharp, &vertFlatFace](int current) { - if (IsInsideQuad(current)) { - return FaceEdge({current / 3, vec3(NAN)}); - } - const int vert = halfedge_[current].endVert; - vec3 pos = vertPos_[vert]; - const vec3 edgeVec = centerPos - pos; - if (vertNumSharp[vert] < 2) { - // opposite vert has fixed normal - const vec3 normal = vertFlatFace[vert] >= 0 - ? faceNormal_[vertFlatFace[vert]] - : vertNormal_[vert]; - // Flair out the normal we're calculating to give the edge a - // more constant curvature to meet the opposite normal. Achieve - // this by pointing the tangent toward the opposite bezier - // control point instead of the vert itself. - pos += vec3(TangentFromNormal( - normal, halfedge_[current].pairedHalfedge)); - } - return FaceEdge({current / 3, SafeNormalize(pos - centerPos)}); - }, - [this, &triIsFlatFace, &normals, &group, minSharpAngle]( - int current, const FaceEdge& here, FaceEdge& next) { - const double dihedral = degrees(std::acos( - la::dot(faceNormal_[here.face], faceNormal_[next.face]))); - if (dihedral > minSharpAngle || - triIsFlatFace[here.face] != triIsFlatFace[next.face] || - (triIsFlatFace[here.face] && triIsFlatFace[next.face] && - !meshRelation_.triRef[here.face].SameFace( - meshRelation_.triRef[next.face]))) { - normals.push_back(vec3(0.0)); - } - group.push_back(normals.size() - 1); - if (std::isfinite(next.edgeVec.x)) { - normals.back() += - SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) * - AngleBetween(here.edgeVec, next.edgeVec); - } else { - next.edgeVec = here.edgeVec; - } - }); - - for (auto& normal : normals) { - normal = SafeNormalize(normal); + } else if (prop != lastProp) { + // update property vertex + lastProp = prop; + newProp = prop; + std::copy(start, start + oldNumProp, + properties_.begin() + prop * numProp); + for (const int i : {0, 1, 2}) + properties_[prop * numProp + normalIdx + i] = + normals[group[idx]][i]; } - int lastGroup = 0; - int lastProp = -1; - int newProp = -1; - int idx = 0; - ForVert(endEdge, [&](int current1) { - const int thisTri = current1 / 3; - const int j = current1 - 3 * thisTri; - const int prop = oldTriProp[thisTri][j]; - auto start = oldProperties.begin() + prop * oldNumProp; - - if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) { - // split property vertex, duplicating but with an updated normal - lastGroup = group[idx]; - newProp = NumPropVert(); - meshRelation_.properties.resize(meshRelation_.properties.size() + - numProp); - std::copy(start, start + oldNumProp, - meshRelation_.properties.begin() + newProp * numProp); - for (const int i : {0, 1, 2}) { - meshRelation_.properties[newProp * numProp + normalIdx + i] = - normals[group[idx]][i]; - } - } else if (prop != lastProp) { - // update property vertex - lastProp = prop; - newProp = prop; - std::copy(start, start + oldNumProp, - meshRelation_.properties.begin() + prop * numProp); - for (const int i : {0, 1, 2}) - meshRelation_.properties[prop * numProp + normalIdx + i] = - normals[group[idx]][i]; - } - - // point to updated property vertex - meshRelation_.triProperties[thisTri][j] = newProp; - ++idx; - }); - } + // point to updated property vertex + halfedge_[current1].propVert = newProp; + ++idx; + }); } } } @@ -770,7 +755,7 @@ void Manifold::Impl::CreateTangents(int normalIdx) { ZoneScoped; const int numVert = NumVert(); const int numHalfedge = halfedge_.size(); - halfedgeTangent_.resize(0); + halfedgeTangent_.clear(); Vec tangent(numHalfedge); Vec fixedHalfedge(numHalfedge, false); @@ -854,7 +839,7 @@ void Manifold::Impl::CreateTangents(int normalIdx) { void Manifold::Impl::CreateTangents(std::vector sharpenedEdges) { ZoneScoped; const int numHalfedge = halfedge_.size(); - halfedgeTangent_.resize(0); + halfedgeTangent_.clear(); Vec tangent(numHalfedge); Vec fixedHalfedge(numHalfedge, false); @@ -994,9 +979,11 @@ void Manifold::Impl::Refine(std::function edgeDivisions, InterpTri({vertPos_, vertBary, &old})); } - halfedgeTangent_.resize(0); + halfedgeTangent_.clear(); Finish(); - CreateFaces(); + if (old.halfedgeTangent_.size() == old.halfedge_.size()) { + MarkCoplanar(); + } meshRelation_.originalID = -1; } diff --git a/thirdparty/manifold/src/sort.cpp b/thirdparty/manifold/src/sort.cpp index f394aa5266..691e21cc9e 100644 --- a/thirdparty/manifold/src/sort.cpp +++ b/thirdparty/manifold/src/sort.cpp @@ -15,8 +15,9 @@ #include #include -#include "./impl.h" -#include "./parallel.h" +#include "impl.h" +#include "parallel.h" +#include "shared.h" namespace { using namespace manifold; @@ -31,37 +32,6 @@ uint32_t MortonCode(vec3 position, Box bBox) { return Collider::MortonCode(position, bBox); } -struct Reindex { - VecView indexInv; - - void operator()(Halfedge& edge) { - if (edge.startVert < 0) return; - edge.startVert = indexInv[edge.startVert]; - edge.endVert = indexInv[edge.endVert]; - } -}; - -struct MarkProp { - VecView keep; - - void operator()(ivec3 triProp) { - for (const int i : {0, 1, 2}) { - reinterpret_cast*>(&keep[triProp[i]]) - ->store(1, std::memory_order_relaxed); - } - } -}; - -struct ReindexProps { - VecView old2new; - - void operator()(ivec3& triProp) { - for (const int i : {0, 1, 2}) { - triProp[i] = old2new[triProp[i]]; - } - } -}; - struct ReindexFace { VecView halfedge; VecView halfedgeTangent; @@ -180,18 +150,20 @@ bool MergeMeshGLP(MeshGLP& mesh) { Permute(vertMorton, vertNew2Old); Permute(vertBox, vertNew2Old); Permute(openVerts, vertNew2Old); - Collider collider(vertBox, vertMorton); - SparseIndices toMerge = collider.Collisions(vertBox.cview()); + Collider collider(vertBox, vertMorton); UnionFind<> uf(numVert); + + auto f = [&uf, &openVerts](int a, int b) { + return uf.unionXY(openVerts[a], openVerts[b]); + }; + auto recorder = MakeSimpleRecorder(f); + collider.Collisions(vertBox.cview(), recorder, false); + for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) { uf.unionXY(static_cast(mesh.mergeFromVert[i]), static_cast(mesh.mergeToVert[i])); } - for (size_t i = 0; i < toMerge.size(); ++i) { - uf.unionXY(openVerts[toMerge.Get(i, false)], - openVerts[toMerge.Get(i, true)]); - } mesh.mergeToVert.clear(); mesh.mergeFromVert.clear(); @@ -221,7 +193,7 @@ void Manifold::Impl::Finish() { SetEpsilon(epsilon_); if (!bBox_.IsFinite()) { // Decimated out of existence - early out. - MarkFailure(Error::NoError); + MakeEmpty(Error::NoError); return; } @@ -237,31 +209,33 @@ void Manifold::Impl::Finish() { "Not an even number of faces after sorting faces!"); #ifdef MANIFOLD_DEBUG - auto MaxOrMinus = [](int a, int b) { - return std::min(a, b) < 0 ? -1 : std::max(a, b); - }; - int face = 0; - Halfedge extrema = {0, 0, 0}; - for (size_t i = 0; i < halfedge_.size(); i++) { - Halfedge e = halfedge_[i]; - if (!e.IsForward()) std::swap(e.startVert, e.endVert); - extrema.startVert = std::min(extrema.startVert, e.startVert); - extrema.endVert = std::min(extrema.endVert, e.endVert); - extrema.pairedHalfedge = - MaxOrMinus(extrema.pairedHalfedge, e.pairedHalfedge); - face = MaxOrMinus(face, i / 3); + if (ManifoldParams().intermediateChecks) { + auto MaxOrMinus = [](int a, int b) { + return std::min(a, b) < 0 ? -1 : std::max(a, b); + }; + int face = 0; + Halfedge extrema = {0, 0, 0}; + for (size_t i = 0; i < halfedge_.size(); i++) { + Halfedge e = halfedge_[i]; + if (!e.IsForward()) std::swap(e.startVert, e.endVert); + extrema.startVert = std::min(extrema.startVert, e.startVert); + extrema.endVert = std::min(extrema.endVert, e.endVert); + extrema.pairedHalfedge = + MaxOrMinus(extrema.pairedHalfedge, e.pairedHalfedge); + face = MaxOrMinus(face, i / 3); + } + DEBUG_ASSERT(extrema.startVert >= 0, topologyErr, + "Vertex index is negative!"); + DEBUG_ASSERT(extrema.endVert < static_cast(NumVert()), topologyErr, + "Vertex index exceeds number of verts!"); + DEBUG_ASSERT(extrema.pairedHalfedge >= 0, topologyErr, + "Halfedge index is negative!"); + DEBUG_ASSERT(extrema.pairedHalfedge < 2 * static_cast(NumEdge()), + topologyErr, "Halfedge index exceeds number of halfedges!"); + DEBUG_ASSERT(face >= 0, topologyErr, "Face index is negative!"); + DEBUG_ASSERT(face < static_cast(NumTri()), topologyErr, + "Face index exceeds number of faces!"); } - DEBUG_ASSERT(extrema.startVert >= 0, topologyErr, - "Vertex index is negative!"); - DEBUG_ASSERT(extrema.endVert < static_cast(NumVert()), topologyErr, - "Vertex index exceeds number of verts!"); - DEBUG_ASSERT(extrema.pairedHalfedge >= 0, topologyErr, - "Halfedge index is negative!"); - DEBUG_ASSERT(extrema.pairedHalfedge < 2 * static_cast(NumEdge()), - topologyErr, "Halfedge index exceeds number of halfedges!"); - DEBUG_ASSERT(face >= 0, topologyErr, "Face index is negative!"); - DEBUG_ASSERT(face < static_cast(NumTri()), topologyErr, - "Face index exceeds number of faces!"); #endif DEBUG_ASSERT(meshRelation_.triRef.size() == NumTri() || @@ -301,11 +275,12 @@ void Manifold::Impl::SortVerts() { // Verts were flagged for removal with NaNs and assigned kNoCode to sort // them to the end, which allows them to be removed. - const auto newNumVert = std::find_if(vertNew2Old.begin(), vertNew2Old.end(), - [&vertMorton](const int vert) { - return vertMorton[vert] == kNoCode; - }) - - vertNew2Old.begin(); + const auto newNumVert = + std::lower_bound(vertNew2Old.begin(), vertNew2Old.end(), kNoCode, + [&vertMorton](const int vert, const uint32_t val) { + return vertMorton[vert] < val; + }) - + vertNew2Old.begin(); vertNew2Old.resize(newNumVert); Permute(vertPos_, vertNew2Old); @@ -326,31 +301,41 @@ void Manifold::Impl::ReindexVerts(const Vec& vertNew2Old, Vec vertOld2New(oldNumVert); scatter(countAt(0), countAt(static_cast(NumVert())), vertNew2Old.begin(), vertOld2New.begin()); + const bool hasProp = NumProp() > 0; for_each(autoPolicy(oldNumVert, 1e5), halfedge_.begin(), halfedge_.end(), - Reindex({vertOld2New})); + [&vertOld2New, hasProp](Halfedge& edge) { + if (edge.startVert < 0) return; + edge.startVert = vertOld2New[edge.startVert]; + edge.endVert = vertOld2New[edge.endVert]; + if (!hasProp) { + edge.propVert = edge.startVert; + } + }); } /** - * Removes unreferenced property verts and reindexes triProperties. + * Removes unreferenced property verts and reindexes propVerts. */ void Manifold::Impl::CompactProps() { ZoneScoped; - if (meshRelation_.numProp == 0) return; + if (numProp_ == 0) return; - const auto numVerts = meshRelation_.properties.size() / meshRelation_.numProp; + const int numProp = NumProp(); + const auto numVerts = properties_.size() / numProp; Vec keep(numVerts, 0); auto policy = autoPolicy(numVerts, 1e5); - for_each(policy, meshRelation_.triProperties.cbegin(), - meshRelation_.triProperties.cend(), MarkProp({keep})); + for_each(policy, halfedge_.cbegin(), halfedge_.cend(), [&keep](Halfedge h) { + reinterpret_cast*>(&keep[h.propVert]) + ->store(1, std::memory_order_relaxed); + }); Vec propOld2New(numVerts + 1, 0); inclusive_scan(keep.begin(), keep.end(), propOld2New.begin() + 1); - Vec oldProp = meshRelation_.properties; + Vec oldProp = properties_; const int numVertsNew = propOld2New[numVerts]; - const int numProp = meshRelation_.numProp; - auto& properties = meshRelation_.properties; - properties.resize(numProp * numVertsNew); + auto& properties = properties_; + properties.resize_nofill(numProp * numVertsNew); for_each_n( policy, countAt(0), numVerts, [&properties, &oldProp, &propOld2New, &keep, &numProp](const int oldIdx) { @@ -360,8 +345,10 @@ void Manifold::Impl::CompactProps() { oldProp[oldIdx * numProp + p]; } }); - for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(), - ReindexProps({propOld2New})); + for_each(policy, halfedge_.begin(), halfedge_.end(), + [&propOld2New](Halfedge& edge) { + edge.propVert = propOld2New[edge.propVert]; + }); } /** @@ -372,8 +359,9 @@ void Manifold::Impl::CompactProps() { void Manifold::Impl::GetFaceBoxMorton(Vec& faceBox, Vec& faceMorton) const { ZoneScoped; - faceBox.resize(NumTri()); - faceMorton.resize(NumTri()); + // faceBox should be initialized + faceBox.resize(NumTri(), Box()); + faceMorton.resize_nofill(NumTri()); for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(), [this, &faceBox, &faceMorton](const int face) { // Removed tris are marked by all halfedges having pairedHalfedge @@ -413,11 +401,12 @@ void Manifold::Impl::SortFaces(Vec& faceBox, Vec& faceMorton) { // Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode // to sort them to the end, which allows them to be removed. - const int newNumTri = std::find_if(faceNew2Old.begin(), faceNew2Old.end(), - [&faceMorton](const int face) { - return faceMorton[face] == kNoCode; - }) - - faceNew2Old.begin(); + const int newNumTri = + std::lower_bound(faceNew2Old.begin(), faceNew2Old.end(), kNoCode, + [&faceMorton](const int face, const uint32_t val) { + return faceMorton[face] < val; + }) - + faceNew2Old.begin(); faceNew2Old.resize(newNumTri); Permute(faceMorton, faceNew2Old); @@ -435,8 +424,6 @@ void Manifold::Impl::GatherFaces(const Vec& faceNew2Old) { const auto numTri = faceNew2Old.size(); if (meshRelation_.triRef.size() == NumTri()) Permute(meshRelation_.triRef, faceNew2Old); - if (meshRelation_.triProperties.size() == NumTri()) - Permute(meshRelation_.triProperties, faceNew2Old); if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old); Vec oldHalfedge(std::move(halfedge_)); @@ -446,8 +433,9 @@ void Manifold::Impl::GatherFaces(const Vec& faceNew2Old) { scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(), faceOld2New.begin()); - halfedge_.resize(3 * numTri); - if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri); + halfedge_.resize_nofill(3 * numTri); + if (oldHalfedgeTangent.size() != 0) + halfedgeTangent_.resize_nofill(3 * numTri); for_each_n(policy, countAt(0), numTri, ReindexFace({halfedge_, halfedgeTangent_, oldHalfedge, oldHalfedgeTangent, faceNew2Old, faceOld2New})); @@ -457,7 +445,7 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec& faceNew2Old) { ZoneScoped; const auto numTri = faceNew2Old.size(); - meshRelation_.triRef.resize(numTri); + meshRelation_.triRef.resize_nofill(numTri); gather(faceNew2Old.begin(), faceNew2Old.end(), old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin()); @@ -465,17 +453,13 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec& faceNew2Old) { meshRelation_.meshIDtransform[pair.first] = pair.second; } - if (old.meshRelation_.triProperties.size() > 0) { - meshRelation_.triProperties.resize(numTri); - gather(faceNew2Old.begin(), faceNew2Old.end(), - old.meshRelation_.triProperties.begin(), - meshRelation_.triProperties.begin()); - meshRelation_.numProp = old.meshRelation_.numProp; - meshRelation_.properties = old.meshRelation_.properties; + if (old.NumProp() > 0) { + numProp_ = old.numProp_; + properties_ = old.properties_; } if (old.faceNormal_.size() == old.NumTri()) { - faceNormal_.resize(numTri); + faceNormal_.resize_nofill(numTri); gather(faceNew2Old.begin(), faceNew2Old.end(), old.faceNormal_.begin(), faceNormal_.begin()); } @@ -484,8 +468,9 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec& faceNew2Old) { scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(), faceOld2New.begin()); - halfedge_.resize(3 * numTri); - if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri); + halfedge_.resize_nofill(3 * numTri); + if (old.halfedgeTangent_.size() != 0) + halfedgeTangent_.resize_nofill(3 * numTri); for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri, ReindexFace({halfedge_, halfedgeTangent_, old.halfedge_, old.halfedgeTangent_, faceNew2Old, faceOld2New})); diff --git a/thirdparty/manifold/src/subdivision.cpp b/thirdparty/manifold/src/subdivision.cpp index c8631d79c8..71fbd36338 100644 --- a/thirdparty/manifold/src/subdivision.cpp +++ b/thirdparty/manifold/src/subdivision.cpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./impl.h" -#include "./parallel.h" +#include "impl.h" +#include "parallel.h" template <> struct std::hash { @@ -113,7 +113,7 @@ class Partition { } const int offset = interiorOffset - newVerts.size(); size_t old = newVerts.size(); - newVerts.resize(vertBary.size()); + newVerts.resize_nofill(vertBary.size()); std::iota(newVerts.begin() + old, newVerts.end(), old + offset); const int numTri = triVert.size(); @@ -535,7 +535,7 @@ Vec Manifold::Impl::Subdivide( auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) { int longest = 0; int total = 0; - for (int j : {0, 1, 2}) { + for (int _ : {0, 1, 2}) { const int added = edgeAdded[half2Edge[hIdx]]; longest = la::max(longest, added); total += added; @@ -586,7 +586,7 @@ Vec Manifold::Impl::Subdivide( std::vector subTris(numTri); for_each_n(policy, countAt(0), numTri, - [this, &subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) { + [&subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) { const ivec4 halfedges = faceHalfedges[tri]; ivec4 divisions(0); for (const int i : {0, 1, 2, 3}) { @@ -684,18 +684,19 @@ Vec Manifold::Impl::Subdivide( }); vertPos_ = newVertPos; - faceNormal_.resize(0); + faceNormal_.clear(); - if (meshRelation_.numProp > 0) { + if (numProp_ > 0) { const int numPropVert = NumPropVert(); const int addedVerts = NumVert() - numVert; const int propOffset = numPropVert - numVert; - Vec prop(meshRelation_.numProp * - (numPropVert + addedVerts + totalEdgeAdded)); + // duplicate the prop verts along all new edges even though this is + // unnecessary for edges that share the same prop verts. The duplicates will + // be removed by CompactProps. + Vec prop(numProp_ * (numPropVert + addedVerts + totalEdgeAdded)); // copy retained prop verts - copy(meshRelation_.properties.begin(), meshRelation_.properties.end(), - prop.begin()); + copy(properties_.begin(), properties_.end(), prop.begin()); // copy interior prop verts and forward edge prop verts for_each_n( @@ -705,104 +706,99 @@ Vec Manifold::Impl::Subdivide( const int vert = numPropVert + i; const Barycentric bary = vertBary[numVert + i]; const ivec4 halfedges = faceHalfedges[bary.tri]; - auto& rel = meshRelation_; + const int numProp = NumProp(); - for (int p = 0; p < rel.numProp; ++p) { + for (int p = 0; p < numProp; ++p) { if (halfedges[3] < 0) { vec3 triProp; for (const int i : {0, 1, 2}) { - triProp[i] = rel.properties[rel.triProperties[bary.tri][i] * - rel.numProp + - p]; + triProp[i] = + properties_[halfedge_[3 * bary.tri + i].propVert * numProp + + p]; } - prop[vert * rel.numProp + p] = la::dot(triProp, vec3(bary.uvw)); + prop[vert * numProp + p] = la::dot(triProp, vec3(bary.uvw)); } else { vec4 quadProp; for (const int i : {0, 1, 2, 3}) { - const int tri = halfedges[i] / 3; - const int j = halfedges[i] % 3; quadProp[i] = - rel.properties[rel.triProperties[tri][j] * rel.numProp + p]; + properties_[halfedge_[halfedges[i]].propVert * numProp + p]; } - prop[vert * rel.numProp + p] = la::dot(quadProp, bary.uvw); + prop[vert * numProp + p] = la::dot(quadProp, bary.uvw); } } }); - // copy backward edge prop verts + // copy backward edge prop verts, some of which will be unreferenced + // duplicates. for_each_n(policy, countAt(0), numEdge, [this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset, addedVerts](const int i) { const int n = edgeAdded[i]; const int offset = edgeOffset[i] + propOffset + addedVerts; - auto& rel = meshRelation_; + const int numProp = NumProp(); const double frac = 1.0 / (n + 1); const int halfedgeIdx = halfedge_[edges[i].halfedgeIdx].pairedHalfedge; - const int v0 = halfedgeIdx % 3; - const int tri = halfedgeIdx / 3; - const int prop0 = rel.triProperties[tri][v0]; - const int prop1 = rel.triProperties[tri][Next3(v0)]; + const int prop0 = halfedge_[halfedgeIdx].propVert; + const int prop1 = + halfedge_[NextHalfedge(halfedgeIdx)].propVert; for (int i = 0; i < n; ++i) { - for (int p = 0; p < rel.numProp; ++p) { - prop[(offset + i) * rel.numProp + p] = - la::lerp(rel.properties[prop0 * rel.numProp + p], - rel.properties[prop1 * rel.numProp + p], - (i + 1) * frac); + for (int p = 0; p < numProp; ++p) { + prop[(offset + i) * numProp + p] = la::lerp( + properties_[prop0 * numProp + p], + properties_[prop1 * numProp + p], (i + 1) * frac); } } }); Vec triProp(triVerts.size()); - for_each_n(policy, countAt(0), numTri, - [this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset, - &interiorOffset, &faceHalfedges, propOffset, - addedVerts](const int tri) { - const ivec4 halfedges = faceHalfedges[tri]; - if (halfedges[0] < 0) return; + for_each_n( + policy, countAt(0), numTri, + [this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset, + &interiorOffset, &faceHalfedges, propOffset, + addedVerts](const int tri) { + const ivec4 halfedges = faceHalfedges[tri]; + if (halfedges[0] < 0) return; - auto& rel = meshRelation_; - ivec4 tri3; - ivec4 edgeOffsets; - bvec4 edgeFwd(true); - for (const int i : {0, 1, 2, 3}) { - if (halfedges[i] < 0) { - tri3[i] = -1; - continue; - } - const int thisTri = halfedges[i] / 3; - const int j = halfedges[i] % 3; - const Halfedge& halfedge = halfedge_[halfedges[i]]; - tri3[i] = rel.triProperties[thisTri][j]; - edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]]; - if (!halfedge.IsForward()) { - const int pairTri = halfedge.pairedHalfedge / 3; - const int k = halfedge.pairedHalfedge % 3; - if (rel.triProperties[pairTri][k] != - rel.triProperties[thisTri][Next3(j)] || - rel.triProperties[pairTri][Next3(k)] != - rel.triProperties[thisTri][j]) { - edgeOffsets[i] += addedVerts; - } else { - edgeFwd[i] = false; - } - } - } + ivec4 tri3; + ivec4 edgeOffsets; + bvec4 edgeFwd(true); + for (const int i : {0, 1, 2, 3}) { + if (halfedges[i] < 0) { + tri3[i] = -1; + continue; + } + const Halfedge& halfedge = halfedge_[halfedges[i]]; + tri3[i] = halfedge.propVert; + edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]]; + if (!halfedge.IsForward()) { + if (halfedge_[halfedge.pairedHalfedge].propVert != + halfedge_[NextHalfedge(halfedges[i])].propVert || + halfedge_[NextHalfedge(halfedge.pairedHalfedge)].propVert != + halfedge.propVert) { + // if the edge doesn't match, point to the backward edge + // propverts. + edgeOffsets[i] += addedVerts; + } else { + edgeFwd[i] = false; + } + } + } - Vec newTris = subTris[tri].Reindex( - tri3, edgeOffsets + propOffset, edgeFwd, - interiorOffset[tri] + propOffset); - copy(newTris.begin(), newTris.end(), - triProp.begin() + triOffset[tri]); - }); + Vec newTris = + subTris[tri].Reindex(tri3, edgeOffsets + propOffset, edgeFwd, + interiorOffset[tri] + propOffset); + copy(newTris.begin(), newTris.end(), + triProp.begin() + triOffset[tri]); + }); - meshRelation_.properties = prop; - meshRelation_.triProperties = triProp; + properties_ = prop; + CreateHalfedges(triProp, triVerts); + } else { + CreateHalfedges(triVerts); } - CreateHalfedges(triVerts); - return vertBary; } diff --git a/thirdparty/manifold/src/svd.h b/thirdparty/manifold/src/svd.h index cc1a82035f..3978a17415 100644 --- a/thirdparty/manifold/src/svd.h +++ b/thirdparty/manifold/src/svd.h @@ -82,7 +82,7 @@ struct QR { mat3 Q, R; }; // Calculates the squared norm of the vector. -inline double Dist2(vec3 v) { return la::dot(v, v); } +inline double Dist2(vec3 v) { return manifold::la::dot(v, v); } // For an explanation of the math see // http://pages.cs.wisc.edu/~sifakis/papers/SVD_TR1690.pdf Computing the // Singular Value Decomposition of 3 x 3 matrices with minimal branching and @@ -101,29 +101,26 @@ inline Givens ApproximateGivensQuaternion(Symmetric3x3& A) { inline void JacobiConjugation(const int32_t x, const int32_t y, const int32_t z, Symmetric3x3& S, vec4& q) { auto g = ApproximateGivensQuaternion(S); - double scale = 1.0 / fma(g.ch, g.ch, g.sh * g.sh); - double a = fma(g.ch, g.ch, -g.sh * g.sh) * scale; + double scale = 1.0 / (g.ch * g.ch + g.sh * g.sh); + double a = (g.ch * g.ch - g.sh * g.sh) * scale; double b = 2.0 * g.sh * g.ch * scale; Symmetric3x3 _S = S; // perform conjugation S = Q'*S*Q - S.m_00 = - fma(a, fma(a, _S.m_00, b * _S.m_10), b * (fma(a, _S.m_10, b * _S.m_11))); - S.m_10 = fma(a, fma(-b, _S.m_00, a * _S.m_10), - b * (fma(-b, _S.m_10, a * _S.m_11))); - S.m_11 = fma(-b, fma(-b, _S.m_00, a * _S.m_10), - a * (fma(-b, _S.m_10, a * _S.m_11))); - S.m_20 = fma(a, _S.m_20, b * _S.m_21); - S.m_21 = fma(-b, _S.m_20, a * _S.m_21); + S.m_00 = a * (a * _S.m_00 + b * _S.m_10) + b * (a * _S.m_10 + b * _S.m_11); + S.m_10 = a * (-b * _S.m_00 + a * _S.m_10) + b * (-b * _S.m_10 + a * _S.m_11); + S.m_11 = -b * (-b * _S.m_00 + a * _S.m_10) + a * (-b * _S.m_10 + a * _S.m_11); + S.m_20 = a * _S.m_20 + b * _S.m_21; + S.m_21 = -b * _S.m_20 + a * _S.m_21; S.m_22 = _S.m_22; // update cumulative rotation qV vec3 tmp = g.sh * vec3(q); g.sh *= q[3]; // (x,y,z) corresponds to ((0,1,2),(1,2,0),(2,0,1)) for (p,q) = // ((0,1),(1,2),(0,2)) - q[z] = fma(q[z], g.ch, g.sh); - q[3] = fma(q[3], g.ch, -tmp[z]); // w - q[x] = fma(q[x], g.ch, tmp[y]); - q[y] = fma(q[y], g.ch, -tmp[x]); + q[z] = q[z] * g.ch + g.sh; + q[3] = q[3] * g.ch + -tmp[z]; // w + q[x] = q[x] * g.ch + tmp[y]; + q[y] = q[y] * g.ch + -tmp[x]; // re-arrange matrix for next iteration _S.m_00 = S.m_11; _S.m_10 = S.m_21; @@ -148,15 +145,15 @@ inline mat3 JacobiEigenAnalysis(Symmetric3x3 S) { JacobiConjugation(1, 2, 0, S, q); JacobiConjugation(2, 0, 1, S, q); } - return mat3({1.0 - 2.0 * (fma(q.y, q.y, q.z * q.z)), // - 2.0 * fma(q.x, q.y, +q.w * q.z), // - 2.0 * fma(q.x, q.z, -q.w * q.y)}, // - {2 * fma(q.x, q.y, -q.w * q.z), // - 1 - 2 * fma(q.x, q.x, q.z * q.z), // - 2 * fma(q.y, q.z, q.w * q.x)}, // - {2 * fma(q.x, q.z, q.w * q.y), // - 2 * fma(q.y, q.z, -q.w * q.x), // - 1 - 2 * fma(q.x, q.x, q.y * q.y)}); + return mat3({1.0 - 2.0 * (q.y * q.y + q.z * q.z), // + 2.0 * (q.x * q.y + +q.w * q.z), // + 2.0 * (q.x * q.z + -q.w * q.y)}, // + {2 * (q.x * q.y + -q.w * q.z), // + 1 - 2 * (q.x * q.x + q.z * q.z), // + 2 * (q.y * q.z + q.w * q.x)}, // + {2 * (q.x * q.z + q.w * q.y), // + 2 * (q.y * q.z + -q.w * q.x), // + 1 - 2 * (q.x * q.x + q.y * q.y)}); } // Implementation of Algorithm 3 inline void SortSingularValues(mat3& B, mat3& V) { @@ -207,65 +204,64 @@ inline QR QRDecomposition(mat3& B) { mat3 Q, R; // first Givens rotation (ch,0,0,sh) auto g1 = QRGivensQuaternion(B[0][0], B[0][1]); - auto a = fma(-2.0, g1.sh * g1.sh, 1.0); + auto a = -2.0 * g1.sh * g1.sh + 1.0; auto b = 2.0 * g1.ch * g1.sh; // apply B = Q' * B - R[0][0] = fma(a, B[0][0], b * B[0][1]); - R[1][0] = fma(a, B[1][0], b * B[1][1]); - R[2][0] = fma(a, B[2][0], b * B[2][1]); - R[0][1] = fma(-b, B[0][0], a * B[0][1]); - R[1][1] = fma(-b, B[1][0], a * B[1][1]); - R[2][1] = fma(-b, B[2][0], a * B[2][1]); + R[0][0] = a * B[0][0] + b * B[0][1]; + R[1][0] = a * B[1][0] + b * B[1][1]; + R[2][0] = a * B[2][0] + b * B[2][1]; + R[0][1] = -b * B[0][0] + a * B[0][1]; + R[1][1] = -b * B[1][0] + a * B[1][1]; + R[2][1] = -b * B[2][0] + a * B[2][1]; R[0][2] = B[0][2]; R[1][2] = B[1][2]; R[2][2] = B[2][2]; // second Givens rotation (ch,0,-sh,0) auto g2 = QRGivensQuaternion(R[0][0], R[0][2]); - a = fma(-2.0, g2.sh * g2.sh, 1.0); + a = -2.0 * g2.sh * g2.sh + 1.0; b = 2.0 * g2.ch * g2.sh; // apply B = Q' * B; - B[0][0] = fma(a, R[0][0], b * R[0][2]); - B[1][0] = fma(a, R[1][0], b * R[1][2]); - B[2][0] = fma(a, R[2][0], b * R[2][2]); + B[0][0] = a * R[0][0] + b * R[0][2]; + B[1][0] = a * R[1][0] + b * R[1][2]; + B[2][0] = a * R[2][0] + b * R[2][2]; B[0][1] = R[0][1]; B[1][1] = R[1][1]; B[2][1] = R[2][1]; - B[0][2] = fma(-b, R[0][0], a * R[0][2]); - B[1][2] = fma(-b, R[1][0], a * R[1][2]); - B[2][2] = fma(-b, R[2][0], a * R[2][2]); + B[0][2] = -b * R[0][0] + a * R[0][2]; + B[1][2] = -b * R[1][0] + a * R[1][2]; + B[2][2] = -b * R[2][0] + a * R[2][2]; // third Givens rotation (ch,sh,0,0) auto g3 = QRGivensQuaternion(B[1][1], B[1][2]); - a = fma(-2.0, g3.sh * g3.sh, 1.0); + a = -2.0 * g3.sh * g3.sh + 1.0; b = 2.0 * g3.ch * g3.sh; // R is now set to desired value R[0][0] = B[0][0]; R[1][0] = B[1][0]; R[2][0] = B[2][0]; - R[0][1] = fma(a, B[0][1], b * B[0][2]); - R[1][1] = fma(a, B[1][1], b * B[1][2]); - R[2][1] = fma(a, B[2][1], b * B[2][2]); - R[0][2] = fma(-b, B[0][1], a * B[0][2]); - R[1][2] = fma(-b, B[1][1], a * B[1][2]); - R[2][2] = fma(-b, B[2][1], a * B[2][2]); + R[0][1] = a * B[0][1] + b * B[0][2]; + R[1][1] = a * B[1][1] + b * B[1][2]; + R[2][1] = a * B[2][1] + b * B[2][2]; + R[0][2] = -b * B[0][1] + a * B[0][2]; + R[1][2] = -b * B[1][1] + a * B[1][2]; + R[2][2] = -b * B[2][1] + a * B[2][2]; // construct the cumulative rotation Q=Q1 * Q2 * Q3 // the number of floating point operations for three quaternion // multiplications is more or less comparable to the explicit form of the // joined matrix. certainly more memory-efficient! - auto sh12 = 2.0 * fma(g1.sh, g1.sh, -0.5); - auto sh22 = 2.0 * fma(g2.sh, g2.sh, -0.5); - auto sh32 = 2.0 * fma(g3.sh, g3.sh, -0.5); + auto sh12 = 2.0 * (g1.sh * g1.sh + -0.5); + auto sh22 = 2.0 * (g2.sh * g2.sh + -0.5); + auto sh32 = 2.0 * (g3.sh * g3.sh + -0.5); Q[0][0] = sh12 * sh22; - Q[1][0] = fma(4.0 * g2.ch * g3.ch, sh12 * g2.sh * g3.sh, - 2.0 * g1.ch * g1.sh * sh32); - Q[2][0] = fma(4.0 * g1.ch * g3.ch, g1.sh * g3.sh, - -2.0 * g2.ch * sh12 * g2.sh * sh32); + Q[1][0] = + 4.0 * g2.ch * g3.ch * sh12 * g2.sh * g3.sh + 2.0 * g1.ch * g1.sh * sh32; + Q[2][0] = + 4.0 * g1.ch * g3.ch * g1.sh * g3.sh + -2.0 * g2.ch * sh12 * g2.sh * sh32; Q[0][1] = -2.0 * g1.ch * g1.sh * sh22; - Q[1][1] = - fma(-8.0 * g1.ch * g2.ch * g3.ch, g1.sh * g2.sh * g3.sh, sh12 * sh32); - Q[2][1] = fma( - -2.0 * g3.ch, g3.sh, - 4.0 * g1.sh * fma(g3.ch * g1.sh, g3.sh, g1.ch * g2.ch * g2.sh * sh32)); + Q[1][1] = -8.0 * g1.ch * g2.ch * g3.ch * g1.sh * g2.sh * g3.sh + sh12 * sh32; + Q[2][1] = + -2.0 * g3.ch * g3.sh + + 4.0 * g1.sh * (g3.ch * g1.sh * g3.sh + g1.ch * g2.ch * g2.sh * sh32); Q[0][2] = 2.0 * g2.ch * g2.sh; Q[1][2] = -2.0 * g3.ch * sh22 * g3.sh; diff --git a/thirdparty/manifold/src/tree2d.cpp b/thirdparty/manifold/src/tree2d.cpp new file mode 100644 index 0000000000..b47c7a2f78 --- /dev/null +++ b/thirdparty/manifold/src/tree2d.cpp @@ -0,0 +1,59 @@ +// Copyright 2025 The Manifold Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tree2d.h" + +#include "parallel.h" + +#ifndef ZoneScoped +#if __has_include() +#include +#else +#define FrameMarkStart(x) +#define FrameMarkEnd(x) +// putting ZoneScoped in a function will instrument the function execution when +// TRACY_ENABLE is set, which allows the profiler to record more accurate +// timing. +#define ZoneScoped +#define ZoneScopedN(name) +#endif +#endif + +namespace manifold { + +// Not really a proper KD-tree, but a kd tree with k = 2 and alternating x/y +// partition. +// Recursive sorting is not the most efficient, but simple and guaranteed to +// result in a balanced tree. +void BuildTwoDTreeImpl(VecView points, bool sortX) { + using CmpFn = std::function; + CmpFn cmpx = [](const PolyVert& a, const PolyVert& b) { + return a.pos.x < b.pos.x; + }; + CmpFn cmpy = [](const PolyVert& a, const PolyVert& b) { + return a.pos.y < b.pos.y; + }; + manifold::stable_sort(points.begin(), points.end(), sortX ? cmpx : cmpy); + if (points.size() < 2) return; + BuildTwoDTreeImpl(points.view(0, points.size() / 2), !sortX); + BuildTwoDTreeImpl(points.view(points.size() / 2 + 1), !sortX); +} + +void BuildTwoDTree(VecView points) { + ZoneScoped; + // don't even bother... + if (points.size() <= 8) return; + BuildTwoDTreeImpl(points, true); +} +} // namespace manifold diff --git a/thirdparty/manifold/src/tree2d.h b/thirdparty/manifold/src/tree2d.h new file mode 100644 index 0000000000..d16b2ab3e3 --- /dev/null +++ b/thirdparty/manifold/src/tree2d.h @@ -0,0 +1,85 @@ +// Copyright 2025 The Manifold Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "manifold/common.h" +#include "manifold/optional_assert.h" +#include "manifold/polygon.h" +#include "manifold/vec_view.h" + +namespace manifold { + +void BuildTwoDTreeImpl(VecView points, bool sortX); + +void BuildTwoDTree(VecView points); + +template +void QueryTwoDTree(VecView points, Rect r, F f) { + if (points.size() <= 8) { + for (const auto& p : points) + if (r.Contains(p.pos)) f(p); + return; + } + Rect current; + current.min = vec2(-std::numeric_limits::infinity()); + current.max = vec2(std::numeric_limits::infinity()); + + int level = 0; + VecView currentView = points; + std::array rectStack; + std::array, 64> viewStack; + std::array levelStack; + int stackPointer = 0; + + while (1) { + if (currentView.size() <= 2) { + for (const auto& p : currentView) + if (r.Contains(p.pos)) f(p); + if (--stackPointer < 0) break; + level = levelStack[stackPointer]; + currentView = viewStack[stackPointer]; + current = rectStack[stackPointer]; + continue; + } + + // these are conceptual left/right trees + Rect left = current; + Rect right = current; + const PolyVert middle = currentView[currentView.size() / 2]; + if (level % 2 == 0) + left.max.x = right.min.x = middle.pos.x; + else + left.max.y = right.min.y = middle.pos.y; + + if (r.Contains(middle.pos)) f(middle); + if (left.DoesOverlap(r)) { + if (right.DoesOverlap(r)) { + DEBUG_ASSERT(stackPointer < 64, logicErr, "Stack overflow"); + rectStack[stackPointer] = right; + viewStack[stackPointer] = currentView.view(currentView.size() / 2 + 1); + levelStack[stackPointer] = level + 1; + stackPointer++; + } + current = left; + currentView = currentView.view(0, currentView.size() / 2); + level++; + } else { + current = right; + currentView = currentView.view(currentView.size() / 2 + 1); + level++; + } + } +} +} // namespace manifold diff --git a/thirdparty/manifold/src/tri_dist.h b/thirdparty/manifold/src/tri_dist.h index 4e83ca5253..123d71f805 100644 --- a/thirdparty/manifold/src/tri_dist.h +++ b/thirdparty/manifold/src/tri_dist.h @@ -15,6 +15,7 @@ #pragma once #include +#include #include "manifold/common.h" diff --git a/thirdparty/manifold/src/utils.h b/thirdparty/manifold/src/utils.h index 12d6a58477..48e8218b85 100644 --- a/thirdparty/manifold/src/utils.h +++ b/thirdparty/manifold/src/utils.h @@ -19,8 +19,8 @@ #include #include -#include "./vec.h" #include "manifold/common.h" +#include "vec.h" #ifndef MANIFOLD_PAR #error "MANIFOLD_PAR must be defined to either 1 (parallel) or -1 (series)" @@ -33,7 +33,7 @@ #endif #endif -#include "./parallel.h" +#include "parallel.h" #if __has_include() #include @@ -72,7 +72,7 @@ inline int Prev3(int i) { template void Permute(Vec& inOut, const Vec& new2Old) { Vec tmp(std::move(inOut)); - inOut.resize(new2Old.size()); + inOut.resize_nofill(new2Old.size()); gather(new2Old.begin(), new2Old.end(), tmp.begin(), inOut.begin()); } @@ -106,6 +106,12 @@ class ConcurrentSharedPtr { ConcurrentSharedPtr(T value) : impl(std::make_shared(value)) {} ConcurrentSharedPtr(const ConcurrentSharedPtr& other) : impl(other.impl), mutex(other.mutex) {} + ConcurrentSharedPtr& operator=(const ConcurrentSharedPtr& other) { + if (this == &other) return *this; + impl = other.impl; + mutex = other.mutex; + return *this; + } class SharedPtrGuard { public: SharedPtrGuard(std::recursive_mutex* mutex, T* content) @@ -211,7 +217,7 @@ struct Negate { inline int CCW(vec2 p0, vec2 p1, vec2 p2, double tol) { vec2 v1 = p1 - p0; vec2 v2 = p2 - p0; - double area = fma(v1.x, v2.y, -v1.y * v2.x); + double area = v1.x * v2.y - v1.y * v2.x; double base2 = la::max(la::dot(v1, v1), la::dot(v2, v2)); if (area * area * 4 <= base2 * tol * tol) return 0; @@ -224,4 +230,11 @@ inline mat4 Mat4(mat3x4 a) { } inline mat3 Mat3(mat2x3 a) { return mat3({a[0], 0}, {a[1], 0}, {a[2], 1}); } +// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key +constexpr uint64_t hash64bit(uint64_t x) { + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; + x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; + x = x ^ (x >> 31); + return x; +} } // namespace manifold diff --git a/thirdparty/manifold/src/vec.h b/thirdparty/manifold/src/vec.h index b59b4b6ad5..a37ad188be 100644 --- a/thirdparty/manifold/src/vec.h +++ b/thirdparty/manifold/src/vec.h @@ -21,8 +21,8 @@ #endif #include -#include "./parallel.h" #include "manifold/vec_view.h" +#include "parallel.h" namespace manifold { @@ -45,40 +45,40 @@ class Vec : public VecView { // Note that the vector constructed with this constructor will contain // uninitialized memory. Please specify `val` if you need to make sure that // the data is initialized. - Vec(size_t size) { + Vec(size_t size) : VecView() { reserve(size); this->size_ = size; } - Vec(size_t size, T val) { resize(size, val); } + Vec(size_t size, T val) : VecView() { resize(size, val); } - Vec(const Vec &vec) { *this = Vec(vec.view()); } + Vec(const Vec& vec) : VecView() { *this = Vec(vec.view()); } - Vec(const VecView &vec) { + Vec(const VecView& vec) : VecView() { this->size_ = vec.size(); this->capacity_ = this->size_; auto policy = autoPolicy(this->size_); if (this->size_ != 0) { - this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); + this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); ASSERT(this->ptr_ != nullptr, std::bad_alloc()); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); copy(policy, vec.begin(), vec.end(), this->ptr_); } } - Vec(const std::vector &vec) { + Vec(const std::vector& vec) : VecView() { this->size_ = vec.size(); this->capacity_ = this->size_; auto policy = autoPolicy(this->size_); if (this->size_ != 0) { - this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); + this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); ASSERT(this->ptr_ != nullptr, std::bad_alloc()); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); copy(policy, vec.begin(), vec.end(), this->ptr_); } } - Vec(Vec &&vec) { + Vec(Vec&& vec) : VecView() { this->ptr_ = vec.ptr_; this->size_ = vec.size_; capacity_ = vec.capacity_; @@ -100,7 +100,7 @@ class Vec : public VecView { capacity_ = 0; } - Vec &operator=(const Vec &other) { + Vec& operator=(const Vec& other) { if (&other == this) return *this; if (this->ptr_ != nullptr) { TracyFreeS(this->ptr_, 3); @@ -109,7 +109,7 @@ class Vec : public VecView { this->size_ = other.size_; capacity_ = other.size_; if (this->size_ != 0) { - this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); + this->ptr_ = reinterpret_cast(malloc(this->size_ * sizeof(T))); ASSERT(this->ptr_ != nullptr, std::bad_alloc()); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); manifold::copy(other.begin(), other.end(), this->ptr_); @@ -117,7 +117,7 @@ class Vec : public VecView { return *this; } - Vec &operator=(Vec &&other) { + Vec& operator=(Vec&& other) { if (&other == this) return *this; if (this->ptr_ != nullptr) { TracyFreeS(this->ptr_, 3); @@ -134,38 +134,37 @@ class Vec : public VecView { operator VecView() const { return {this->ptr_, this->size_}; } - void swap(Vec &other) { + void swap(Vec& other) { std::swap(this->ptr_, other.ptr_); std::swap(this->size_, other.size_); std::swap(capacity_, other.capacity_); } - inline void push_back(const T &val, bool seq = false) { + inline void push_back(const T& val) { if (this->size_ >= capacity_) { // avoid dangling pointer in case val is a reference of our array T val_copy = val; - reserve(capacity_ == 0 ? 128 : capacity_ * 2, seq); + reserve(capacity_ == 0 ? 128 : capacity_ * 2); this->ptr_[this->size_++] = val_copy; return; } this->ptr_[this->size_++] = val; } - inline void extend(size_t n, bool seq = false) { + inline void extend(size_t n) { if (this->size_ + n >= capacity_) - reserve(capacity_ == 0 ? 128 : std::max(capacity_ * 2, this->size_ + n), - seq); + reserve(capacity_ == 0 ? 128 : std::max(capacity_ * 2, this->size_ + n)); this->size_ += n; } - void reserve(size_t n, bool seq = false) { + void reserve(size_t n) { if (n > capacity_) { - T *newBuffer = reinterpret_cast(malloc(n * sizeof(T))); + T* newBuffer = reinterpret_cast(malloc(n * sizeof(T))); ASSERT(newBuffer != nullptr, std::bad_alloc()); TracyAllocS(newBuffer, n * sizeof(T), 3); if (this->size_ > 0) - manifold::copy(seq ? ExecutionPolicy::Seq : autoPolicy(this->size_), - this->ptr_, this->ptr_ + this->size_, newBuffer); + manifold::copy(autoPolicy(this->size_), this->ptr_, + this->ptr_ + this->size_, newBuffer); if (this->ptr_ != nullptr) { TracyFreeS(this->ptr_, 3); free(this->ptr_); @@ -176,7 +175,7 @@ class Vec : public VecView { } void resize(size_t newSize, T val = T()) { - bool shrink = this->size_ > 2 * newSize; + bool shrink = this->size_ > 2 * newSize && this->size_ > 16; reserve(newSize); if (this->size_ < newSize) { fill(autoPolicy(newSize - this->size_), this->ptr_ + this->size_, @@ -186,7 +185,14 @@ class Vec : public VecView { if (shrink) shrink_to_fit(); } - void pop_back() { resize(this->size_ - 1); } + void resize_nofill(size_t newSize) { + bool shrink = this->size_ > 2 * newSize && this->size_ > 16; + reserve(newSize); + this->size_ = newSize; + if (shrink) shrink_to_fit(); + } + + void pop_back() { resize_nofill(this->size_ - 1); } void clear(bool shrink = true) { this->size_ = 0; @@ -194,9 +200,9 @@ class Vec : public VecView { } void shrink_to_fit() { - T *newBuffer = nullptr; + T* newBuffer = nullptr; if (this->size_ > 0) { - newBuffer = reinterpret_cast(malloc(this->size_ * sizeof(T))); + newBuffer = reinterpret_cast(malloc(this->size_ * sizeof(T))); ASSERT(newBuffer != nullptr, std::bad_alloc()); TracyAllocS(newBuffer, this->size_ * sizeof(T), 3); manifold::copy(this->ptr_, this->ptr_ + this->size_, newBuffer);