manifold: Update to 3.1.1

This commit is contained in:
Jakub Marcowski
2025-05-15 23:06:54 +02:00
parent 64b09905c7
commit f3da6201f8
41 changed files with 3224 additions and 2725 deletions

View File

@ -391,7 +391,7 @@ License: BSD-3-clause
Files: thirdparty/manifold/* Files: thirdparty/manifold/*
Comment: Manifold Comment: Manifold
Copyright: 2020-2024, The Manifold Authors Copyright: 2020-2025, The Manifold Authors
License: Apache-2.0 License: Apache-2.0
Files: thirdparty/mbedtls/* Files: thirdparty/mbedtls/*

View File

@ -25,9 +25,11 @@ thirdparty_sources = [
"src/polygon.cpp", "src/polygon.cpp",
"src/properties.cpp", "src/properties.cpp",
"src/quickhull.cpp", "src/quickhull.cpp",
"src/sdf.cpp",
"src/smoothing.cpp", "src/smoothing.cpp",
"src/sort.cpp", "src/sort.cpp",
"src/subdivision.cpp", "src/subdivision.cpp",
"src/tree2d.cpp",
] ]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]

View File

@ -624,12 +624,12 @@ See `linuxbsd_headers/README.md`.
## manifold ## manifold
- Upstream: https://github.com/elalish/manifold - Upstream: https://github.com/elalish/manifold
- Version: 3.0.1 (98b8142519d35c13e0e25cfa9fd6e3a271403be6, 2024) - Version: 3.1.1 (2f4741e0b1de44d6d461b869e481351335340b44, 2025)
- License: Apache 2.0 - License: Apache 2.0
File extracted from upstream source: 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` - `AUTHORS`, `LICENSE`

View File

@ -20,7 +20,8 @@
#include <chrono> #include <chrono>
#endif #endif
#include "manifold/linalg.h" #include "linalg.h"
#include "optional_assert.h"
namespace manifold { namespace manifold {
/** @addtogroup Math /** @addtogroup Math
@ -548,7 +549,7 @@ class Quality {
int nSegL = 2.0 * radius * kPi / circularEdgeLength_; int nSegL = 2.0 * radius * kPi / circularEdgeLength_;
int nSeg = fmin(nSegA, nSegL) + 3; int nSeg = fmin(nSegA, nSegL) + 3;
nSeg -= nSeg % 4; 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 /// Perform extra sanity checks and assertions on the intermediate data
/// structures. /// structures.
bool intermediateChecks = false; bool intermediateChecks = false;
/// Verbose output primarily of the Boolean, including timing info and vector /// Perform 3D mesh self-intersection test on intermediate boolean results to
/// sizes. /// test for ϵ-validity. For debug purposes only.
bool verbose = false; bool selfIntersectionChecks = false;
/// If processOverlaps is false, a geometric check will be performed to assert /// If processOverlaps is false, a geometric check will be performed to assert
/// all triangles are CCW. /// all triangles are CCW.
bool processOverlaps = true; bool processOverlaps = true;
/// Suppresses printed errors regarding CW triangles. Has no effect if /// Suppresses printed errors regarding CW triangles. Has no effect if
/// processOverlaps is true. /// processOverlaps is true.
bool suppressErrors = false; 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; 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 #ifdef MANIFOLD_DEBUG
template <class T>
std::ostream& operator<<(std::ostream& out, const la::vec<T, 1>& v) {
return out << '{' << v[0] << '}';
}
template <class T>
std::ostream& operator<<(std::ostream& out, const la::vec<T, 2>& v) {
return out << '{' << v[0] << ',' << v[1] << '}';
}
template <class T>
std::ostream& operator<<(std::ostream& out, const la::vec<T, 3>& v) {
return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}';
}
template <class T>
std::ostream& operator<<(std::ostream& out, const la::vec<T, 4>& v) {
return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}';
}
template <class T, int M>
std::ostream& operator<<(std::ostream& out, const la::mat<T, M, 1>& m) {
return out << '{' << m[0] << '}';
}
template <class T, int M>
std::ostream& operator<<(std::ostream& out, const la::mat<T, M, 2>& m) {
return out << '{' << m[0] << ',' << m[1] << '}';
}
template <class T, int M>
std::ostream& operator<<(std::ostream& out, const la::mat<T, M, 3>& m) {
return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}';
}
template <class T, int M>
std::ostream& operator<<(std::ostream& out, const la::mat<T, M, 4>& m) {
return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}';
}
inline std::ostream& operator<<(std::ostream& stream, const Box& box) { inline std::ostream& operator<<(std::ostream& stream, const Box& box) {
return stream << "min: " << box.min << ", " return stream << "min: " << box.min << ", "
<< "max: " << box.max; << "max: " << box.max;
@ -602,6 +644,11 @@ inline std::ostream& operator<<(std::ostream& stream, const Rect& box) {
<< "max: " << box.max; << "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 * Print the contents of this vector to standard output. Only exists if compiled
* with MANIFOLD_DEBUG flag. * with MANIFOLD_DEBUG flag.

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,9 @@
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include <cstdint> // uint32_t, uint64_t
#include <functional> #include <functional>
#include <memory> #include <memory> // needed for shared_ptr
#ifdef MANIFOLD_EXPORT
#include <iostream>
#endif
#include "manifold/common.h" #include "manifold/common.h"
#include "manifold/vec_view.h" #include "manifold/vec_view.h"
@ -46,11 +43,71 @@ class CsgLeafNode;
* @brief Mesh input/output suitable for pushing directly into graphics * @brief Mesh input/output suitable for pushing directly into graphics
* libraries. * libraries.
* *
* This may not be manifold since the verts are duplicated along property * The core (non-optional) parts of MeshGL are the triVerts indices buffer and
* boundaries that do not match. The additional merge vectors store this missing * the vertProperties interleaved vertex buffer, which follow the conventions of
* information, allowing the manifold to be reconstructed. MeshGL is an alias * OpenGL (and other graphic libraries') buffers and are therefore generally
* for the standard single-precision version. Use MeshGL64 to output the full * easy to map directly to other applications' data structures.
* double precision that Manifold uses internally. *
* 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 <typename Precision, typename I = uint32_t> template <typename Precision, typename I = uint32_t>
struct MeshGLP { struct MeshGLP {
@ -62,7 +119,7 @@ struct MeshGLP {
I numProp = 3; I numProp = 3;
/// Flat, GL-style interleaved list of all vertex properties: propVal = /// Flat, GL-style interleaved list of all vertex properties: propVal =
/// vertProperties[vert * numProp + propIdx]. The first three properties are /// 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<Precision> vertProperties; std::vector<Precision> vertProperties;
/// The vertex indices of the three triangle corners in CCW (from the outside) /// The vertex indices of the three triangle corners in CCW (from the outside)
/// order, for each triangle. /// order, for each triangle.
@ -93,10 +150,11 @@ struct MeshGLP {
/// This matrix is stored in column-major order and the length of the overall /// This matrix is stored in column-major order and the length of the overall
/// vector is 12 * runOriginalID.size(). /// vector is 12 * runOriginalID.size().
std::vector<Precision> runTransform; std::vector<Precision> runTransform;
/// Optional: Length NumTri, contains the source face ID this /// Optional: Length NumTri, contains the source face ID this triangle comes
/// triangle comes from. When auto-generated, this ID will be a triangle index /// from. Simplification will maintain all edges between triangles with
/// into the original mesh. This index/ID is purely for external use (e.g. /// different faceIDs. Input faceIDs will be maintained to the outputs, but if
/// recreating polygonal faces) and will not affect Manifold's algorithms. /// none are given, they will be filled in with Manifold's coplanar face
/// calculation based on mesh tolerance.
std::vector<I> faceID; std::vector<I> faceID;
/// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If /// 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 /// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
@ -263,6 +321,7 @@ class Manifold {
RunIndexWrongLength, RunIndexWrongLength,
FaceIDWrongLength, FaceIDWrongLength,
InvalidConstruction, InvalidConstruction,
ResultTooLarge,
}; };
/** @name Information /** @name Information
@ -311,6 +370,7 @@ class Manifold {
Manifold Warp(std::function<void(vec3&)>) const; Manifold Warp(std::function<void(vec3&)>) const;
Manifold WarpBatch(std::function<void(VecView<vec3>)>) const; Manifold WarpBatch(std::function<void(VecView<vec3>)>) const;
Manifold SetTolerance(double) const; Manifold SetTolerance(double) const;
Manifold Simplify(double tolerance = 0) const;
///@} ///@}
/** @name Boolean /** @name Boolean
@ -368,22 +428,68 @@ class Manifold {
static Manifold Hull(const std::vector<vec3>& pts); static Manifold Hull(const std::vector<vec3>& 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 /** @name Testing Hooks
* These are just for internal testing. * These are just for internal testing.
*/ */
///@{ ///@{
bool MatchesTriNormals() const; bool MatchesTriNormals() const;
size_t NumDegenerateTris() const; size_t NumDegenerateTris() const;
size_t NumOverlaps(const Manifold& second) const;
double GetEpsilon() const; double GetEpsilon() const;
///@} ///@}
struct Impl; struct Impl;
#ifdef MANIFOLD_EXPORT
static Manifold ImportMeshGL64(std::istream& stream);
#endif
private: private:
Manifold(std::shared_ptr<CsgNode> pNode_); Manifold(std::shared_ptr<CsgNode> pNode_);
Manifold(std::shared_ptr<Impl> pImpl_); Manifold(std::shared_ptr<Impl> pImpl_);
@ -429,6 +535,8 @@ inline std::string ToString(const Manifold::Error& error) {
return "Face ID Wrong Length"; return "Face ID Wrong Length";
case Manifold::Error::InvalidConstruction: case Manifold::Error::InvalidConstruction:
return "Invalid Construction"; return "Invalid Construction";
case Manifold::Error::ResultTooLarge:
return "Result Too Large";
default: default:
return "Unknown Error"; return "Unknown Error";
}; };

View File

@ -15,6 +15,7 @@
#pragma once #pragma once
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
#include <iomanip>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
@ -33,6 +34,9 @@ struct geometryErr : public virtual std::runtime_error {
using std::runtime_error::runtime_error; using std::runtime_error::runtime_error;
}; };
using logicErr = std::logic_error; using logicErr = std::logic_error;
#endif
#if defined(MANIFOLD_ASSERT) && defined(MANIFOLD_DEBUG)
template <typename Ex> template <typename Ex>
void AssertFail(const char* file, int line, const char* cond, const char* msg) { void AssertFail(const char* file, int line, const char* cond, const char* msg) {

View File

@ -52,11 +52,10 @@ using PolygonsIdx = std::vector<SimplePolygonIdx>;
* @brief Polygon triangulation * @brief Polygon triangulation
* @{ * @{
*/ */
std::vector<ivec3> TriangulateIdx(const PolygonsIdx &polys, std::vector<ivec3> TriangulateIdx(const PolygonsIdx& polys, double epsilon = -1,
double epsilon = -1); bool allowConvex = true);
std::vector<ivec3> Triangulate(const Polygons &polygons, double epsilon = -1); std::vector<ivec3> Triangulate(const Polygons& polygons, double epsilon = -1,
bool allowConvex = true);
ExecutionParams &PolygonParams();
/** @} */ /** @} */
} // namespace manifold } // namespace manifold

View File

@ -31,22 +31,22 @@ namespace manifold {
template <typename T> template <typename T>
class VecView { class VecView {
public: public:
using Iter = T *; using Iter = T*;
using IterC = const T *; using IterC = const T*;
VecView() : ptr_(nullptr), size_(0) {} 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<std::remove_cv_t<T>> &v) VecView(const std::vector<std::remove_cv_t<T>>& v)
: ptr_(v.data()), size_(v.size()) {} : ptr_(v.data()), size_(v.size()) {}
VecView(const VecView &other) { VecView(const VecView& other) {
ptr_ = other.ptr_; ptr_ = other.ptr_;
size_ = other.size_; size_ = other.size_;
} }
VecView &operator=(const VecView &other) { VecView& operator=(const VecView& other) {
ptr_ = other.ptr_; ptr_ = other.ptr_;
size_ = other.size_; size_ = other.size_;
return *this; return *this;
@ -55,12 +55,12 @@ class VecView {
// allows conversion to a const VecView // allows conversion to a const VecView
operator VecView<const T>() const { return {ptr_, size_}; } operator VecView<const T>() 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")); ASSERT(i < size_, std::out_of_range("Vec out of range"));
return ptr_[i]; 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")); ASSERT(i < size_, std::out_of_range("Vec out of range"));
return ptr_[i]; return ptr_[i];
} }
@ -74,25 +74,25 @@ class VecView {
Iter begin() { return ptr_; } Iter begin() { return ptr_; }
Iter end() { return ptr_ + size_; } Iter end() { return ptr_ + size_; }
const T &front() const { const T& front() const {
ASSERT(size_ != 0, ASSERT(size_ != 0,
std::out_of_range("Attempt to take the front of an empty vector")); std::out_of_range("Attempt to take the front of an empty vector"));
return ptr_[0]; return ptr_[0];
} }
const T &back() const { const T& back() const {
ASSERT(size_ != 0, ASSERT(size_ != 0,
std::out_of_range("Attempt to take the back of an empty vector")); std::out_of_range("Attempt to take the back of an empty vector"));
return ptr_[size_ - 1]; return ptr_[size_ - 1];
} }
T &front() { T& front() {
ASSERT(size_ != 0, ASSERT(size_ != 0,
std::out_of_range("Attempt to take the front of an empty vector")); std::out_of_range("Attempt to take the front of an empty vector"));
return ptr_[0]; return ptr_[0];
} }
T &back() { T& back() {
ASSERT(size_ != 0, ASSERT(size_ != 0,
std::out_of_range("Attempt to take the back of an empty vector")); std::out_of_range("Attempt to take the back of an empty vector"));
return ptr_[size_ - 1]; return ptr_[size_ - 1];
@ -106,8 +106,7 @@ class VecView {
size_t length = std::numeric_limits<size_t>::max()) { size_t length = std::numeric_limits<size_t>::max()) {
if (length == std::numeric_limits<size_t>::max()) if (length == std::numeric_limits<size_t>::max())
length = this->size_ - offset; length = this->size_ - offset;
ASSERT(length >= 0, std::out_of_range("Vec::view out of range")); ASSERT(offset + length <= this->size_,
ASSERT(offset + length <= this->size_ && offset >= 0,
std::out_of_range("Vec::view out of range")); std::out_of_range("Vec::view out of range"));
return VecView<T>(this->ptr_ + offset, length); return VecView<T>(this->ptr_ + offset, length);
} }
@ -117,8 +116,7 @@ class VecView {
size_t length = std::numeric_limits<size_t>::max()) const { size_t length = std::numeric_limits<size_t>::max()) const {
if (length == std::numeric_limits<size_t>::max()) if (length == std::numeric_limits<size_t>::max())
length = this->size_ - offset; length = this->size_ - offset;
ASSERT(length >= 0, std::out_of_range("Vec::cview out of range")); ASSERT(offset + length <= this->size_,
ASSERT(offset + length <= this->size_ && offset >= 0,
std::out_of_range("Vec::cview out of range")); std::out_of_range("Vec::cview out of range"));
return VecView<const T>(this->ptr_ + offset, length); return VecView<const T>(this->ptr_ + offset, length);
} }
@ -129,9 +127,9 @@ class VecView {
return cview(offset, length); 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 #ifdef MANIFOLD_DEBUG
void Dump() const { void Dump() const {
@ -144,7 +142,7 @@ class VecView {
#endif #endif
protected: protected:
T *ptr_ = nullptr; T* ptr_ = nullptr;
size_t size_ = 0; size_t size_ = 0;
}; };

View File

@ -12,11 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./boolean3.h" #include "boolean3.h"
#include <limits> #include <limits>
#include "./parallel.h" #include "parallel.h"
#if (MANIFOLD_PAR == 1)
#include <tbb/combinable.h>
#endif
using namespace manifold; 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)) if (!std::isfinite(lambda) || !std::isfinite(dLR.y) || !std::isfinite(dLR.z))
return vec2(pL.y, pL.z); return vec2(pL.y, pL.z);
vec2 yz; vec2 yz;
yz[0] = fma(lambda, dLR.y, useL ? pL.y : pR.y); yz[0] = lambda * dLR.y + (useL ? pL.y : pR.y);
yz[1] = fma(lambda, dLR.z, useL ? pL.z : pR.z); yz[1] = lambda * dLR.z + (useL ? pL.z : pR.z);
return yz; 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 dyL = qL.y - pL.y;
const double dyR = qR.y - pR.y; const double dyR = qR.y - pR.y;
DEBUG_ASSERT(dyL * dyR <= 0, logicErr, 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); double lambda = (useL ? dyL : dyR) / (dyL - dyR);
if (!std::isfinite(lambda)) lambda = 0.0; if (!std::isfinite(lambda)) lambda = 0.0;
vec4 xyzz; 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 pDy = pR.y - pL.y;
const double qDy = qR.y - qL.y; const double qDy = qR.y - qL.y;
const bool useP = fabs(pDy) < fabs(qDy); const bool useP = fabs(pDy) < fabs(qDy);
xyzz.y = fma(lambda, useP ? pDy : qDy, xyzz.y = lambda * (useP ? pDy : qDy) +
useL ? (useP ? pL.y : qL.y) : (useP ? pR.y : qR.y)); (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.z = lambda * (pR.z - pL.z) + (useL ? pL.z : pR.z);
xyzz.w = fma(lambda, qR.z - qL.z, useL ? qL.z : qR.z); xyzz.w = lambda * (qR.z - qL.z) + (useL ? qL.z : qR.z);
return xyzz; return xyzz;
} }
template <const bool inverted>
struct CopyFaceEdges {
const SparseIndices &p1q1;
// x can be either vert or edge (0 or 1).
SparseIndices &pXq1;
VecView<const Halfedge> 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<size_t>(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<false>({p1q2, p1q1, inQ.halfedge_, 0_uz}));
for_each_n(autoPolicy(p2q1.size(), 1e5), countAt(0_uz), p2q1.size(),
CopyFaceEdges<true>({p2q1, p1q1, inP.halfedge_, p1q2.size()}));
p1q1.Unique();
return p1q1;
}
inline bool Shadows(double p, double q, double dir) { inline bool Shadows(double p, double q, double dir) {
return p == q ? dir < 0 : p < q; return p == q ? dir < 0 : p < q;
} }
@ -107,16 +75,16 @@ inline bool Shadows(double p, double q, double dir) {
inline std::pair<int, vec2> Shadow01( inline std::pair<int, vec2> Shadow01(
const int p0, const int q1, VecView<const vec3> vertPosP, const int p0, const int q1, VecView<const vec3> vertPosP,
VecView<const vec3> vertPosQ, VecView<const Halfedge> halfedgeQ, VecView<const vec3> vertPosQ, VecView<const Halfedge> halfedgeQ,
const double expandP, VecView<const vec3> normalP, const bool reverse) { const double expandP, VecView<const vec3> normal, const bool reverse) {
const int q1s = halfedgeQ[q1].startVert; const int q1s = halfedgeQ[q1].startVert;
const int q1e = halfedgeQ[q1].endVert; const int q1e = halfedgeQ[q1].endVert;
const double p0x = vertPosP[p0].x; const double p0x = vertPosP[p0].x;
const double q1sx = vertPosQ[q1s].x; const double q1sx = vertPosQ[q1s].x;
const double q1ex = vertPosQ[q1e].x; const double q1ex = vertPosQ[q1e].x;
int s01 = reverse ? Shadows(q1sx, p0x, expandP * normalP[q1s].x) - int s01 = reverse ? Shadows(q1sx, p0x, expandP * normal[q1s].x) -
Shadows(q1ex, p0x, expandP * normalP[q1e].x) Shadows(q1ex, p0x, expandP * normal[q1e].x)
: Shadows(p0x, q1ex, expandP * normalP[p0].x) - : Shadows(p0x, q1ex, expandP * normal[p0].x) -
Shadows(p0x, q1sx, expandP * normalP[p0].x); Shadows(p0x, q1sx, expandP * normal[p0].x);
vec2 yz01(NAN); vec2 yz01(NAN);
if (s01 != 0) { if (s01 != 0) {
@ -126,70 +94,26 @@ inline std::pair<int, vec2> Shadow01(
const double start2 = la::dot(diff, diff); const double start2 = la::dot(diff, diff);
diff = vertPosQ[q1e] - vertPosP[p0]; diff = vertPosQ[q1e] - vertPosP[p0];
const double end2 = la::dot(diff, diff); 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; if (!Shadows(yz01[0], vertPosP[p0].y, expandP * dir)) s01 = 0;
} else { } 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); 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<const int64_t> array, int64_t key) {
if (array.size() == 0) {
return std::numeric_limits<size_t>::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 { struct Kernel11 {
VecView<vec4> xyzz;
VecView<int> s;
VecView<const vec3> vertPosP; VecView<const vec3> vertPosP;
VecView<const vec3> vertPosQ; VecView<const vec3> vertPosQ;
VecView<const Halfedge> halfedgeP; VecView<const Halfedge> halfedgeP;
VecView<const Halfedge> halfedgeQ; VecView<const Halfedge> halfedgeQ;
const double expandP; const double expandP;
VecView<const vec3> normalP; VecView<const vec3> normalP;
const SparseIndices &p1q1;
void operator()(const size_t idx) { std::pair<int, vec4> operator()(int p1, int q1) {
const int p1 = p1q1.Get(idx, false); vec4 xyzz11 = vec4(NAN);
const int q1 = p1q1.Get(idx, true); int s11 = 0;
vec4 &xyzz11 = xyzz[idx];
int &s11 = s[idx];
// For pRL[k], qRL[k], k==0 is the left and k==1 is the right. // For pRL[k], qRL[k], k==0 is the left and k==1 is the right.
int k = 0; int k = 0;
@ -201,10 +125,8 @@ struct Kernel11 {
const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert}; const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert};
for (int i : {0, 1}) { for (int i : {0, 1}) {
const auto syz01 = Shadow01(p0[i], q1, vertPosP, vertPosQ, halfedgeQ, const auto [s01, yz01] = Shadow01(p0[i], q1, vertPosP, vertPosQ,
expandP, normalP, false); halfedgeQ, expandP, normalP, false);
const int s01 = syz01.first;
const vec2 yz01 = syz01.second;
// If the value is NaN, then these do not overlap. // If the value is NaN, then these do not overlap.
if (std::isfinite(yz01[0])) { if (std::isfinite(yz01[0])) {
s11 += s01 * (i == 0 ? -1 : 1); s11 += s01 * (i == 0 ? -1 : 1);
@ -219,10 +141,8 @@ struct Kernel11 {
const int q0[2] = {halfedgeQ[q1].startVert, halfedgeQ[q1].endVert}; const int q0[2] = {halfedgeQ[q1].startVert, halfedgeQ[q1].endVert};
for (int i : {0, 1}) { for (int i : {0, 1}) {
const auto syz10 = Shadow01(q0[i], p1, vertPosQ, vertPosP, halfedgeP, const auto [s10, yz10] = Shadow01(q0[i], p1, vertPosQ, vertPosP,
expandP, normalP, true); halfedgeP, expandP, normalP, true);
const int s10 = syz10.first;
const vec2 yz10 = syz10.second;
// If the value is NaN, then these do not overlap. // If the value is NaN, then these do not overlap.
if (std::isfinite(yz10[0])) { if (std::isfinite(yz10[0])) {
s11 += s10 * (i == 0 ? -1 : 1); s11 += s10 * (i == 0 ? -1 : 1);
@ -251,42 +171,22 @@ struct Kernel11 {
if (!Shadows(xyzz11.z, xyzz11.w, expandP * dir)) s11 = 0; if (!Shadows(xyzz11.z, xyzz11.w, expandP * dir)) s11 = 0;
} }
return std::make_pair(s11, xyzz11);
} }
}; };
std::tuple<Vec<int>, Vec<vec4>> Shadow11(SparseIndices &p1q1,
const Manifold::Impl &inP,
const Manifold::Impl &inQ,
double expandP) {
ZoneScoped;
Vec<int> s11(p1q1.size());
Vec<vec4> 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 { struct Kernel02 {
VecView<int> s;
VecView<double> z;
VecView<const vec3> vertPosP; VecView<const vec3> vertPosP;
VecView<const Halfedge> halfedgeQ; VecView<const Halfedge> halfedgeQ;
VecView<const vec3> vertPosQ; VecView<const vec3> vertPosQ;
const double expandP; const double expandP;
VecView<const vec3> vertNormalP; VecView<const vec3> vertNormalP;
const SparseIndices &p0q2;
const bool forward; const bool forward;
void operator()(const size_t idx) { std::pair<int, double> operator()(int p0, int q2) {
const int p0 = p0q2.Get(idx, !forward); int s02 = 0;
const int q2 = p0q2.Get(idx, forward); double z02 = 0.0;
int &s02 = s[idx];
double &z02 = z[idx];
// For yzzLR[k], k==0 is the left and k==1 is the right. // For yzzLR[k], k==0 is the left and k==1 is the right.
int k = 0; int k = 0;
@ -342,47 +242,21 @@ struct Kernel02 {
s02 = 0; s02 = 0;
} }
} }
return std::make_pair(s02, z02);
} }
}; };
std::tuple<Vec<int>, Vec<double>> Shadow02(const Manifold::Impl &inP,
const Manifold::Impl &inQ,
SparseIndices &p0q2, bool forward,
double expandP) {
ZoneScoped;
Vec<int> s02(p0q2.size());
Vec<double> 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 { struct Kernel12 {
VecView<int> x;
VecView<vec3> v;
VecView<const int64_t> p0q2;
VecView<const int> s02;
VecView<const double> z02;
VecView<const int64_t> p1q1;
VecView<const int> s11;
VecView<const vec4> xyzz11;
VecView<const Halfedge> halfedgesP; VecView<const Halfedge> halfedgesP;
VecView<const Halfedge> halfedgesQ; VecView<const Halfedge> halfedgesQ;
VecView<const vec3> vertPosP; VecView<const vec3> vertPosP;
const bool forward; const bool forward;
const SparseIndices &p1q2; Kernel02 k02;
Kernel11 k11;
void operator()(const size_t idx) { std::pair<int, vec3> operator()(int p1, int q2) {
int p1 = p1q2.Get(idx, !forward); int x12 = 0;
int q2 = p1q2.Get(idx, forward); vec3 v12 = vec3(NAN);
int &x12 = x[idx];
vec3 &v12 = v[idx];
// For xzyLR-[k], k==0 is the left and k==1 is the right. // For xzyLR-[k], k==0 is the left and k==1 is the right.
int k = 0; int k = 0;
@ -396,18 +270,15 @@ struct Kernel12 {
const Halfedge edge = halfedgesP[p1]; const Halfedge edge = halfedgesP[p1];
for (int vert : {edge.startVert, edge.endVert}) { for (int vert : {edge.startVert, edge.endVert}) {
const int64_t key = forward ? SparseIndices::EncodePQ(vert, q2) const auto [s, z] = k02(vert, q2);
: SparseIndices::EncodePQ(q2, vert); if (std::isfinite(z)) {
const size_t idx = monobound_quaternary_search(p0q2, key);
if (idx != std::numeric_limits<size_t>::max()) {
const int s = s02[idx];
x12 += s * ((vert == edge.startVert) == forward ? 1 : -1); x12 += s * ((vert == edge.startVert) == forward ? 1 : -1);
if (k < 2 && (k == 0 || (s != 0) != shadows)) { if (k < 2 && (k == 0 || (s != 0) != shadows)) {
shadows = s != 0; shadows = s != 0;
xzyLR0[k] = vertPosP[vert]; xzyLR0[k] = vertPosP[vert];
std::swap(xzyLR0[k].y, xzyLR0[k].z); std::swap(xzyLR0[k].y, xzyLR0[k].z);
xzyLR1[k] = xzyLR0[k]; xzyLR1[k] = xzyLR0[k];
xzyLR1[k][1] = z02[idx]; xzyLR1[k][1] = z;
k++; k++;
} }
} }
@ -417,17 +288,11 @@ struct Kernel12 {
const int q1 = 3 * q2 + i; const int q1 = 3 * q2 + i;
const Halfedge edge = halfedgesQ[q1]; const Halfedge edge = halfedgesQ[q1];
const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge; const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge;
const int64_t key = forward ? SparseIndices::EncodePQ(p1, q1F) const auto [s, xyzz] = forward ? k11(p1, q1F) : k11(q1F, p1);
: SparseIndices::EncodePQ(q1F, p1); if (std::isfinite(xyzz[0])) {
const size_t idx = monobound_quaternary_search(p1q1, key);
if (idx !=
std::numeric_limits<size_t>::max()) { // s is implicitly zero for
// anything not found
const int s = s11[idx];
x12 -= s * (edge.IsForward() ? 1 : -1); x12 -= s * (edge.IsForward() ? 1 : -1);
if (k < 2 && (k == 0 || (s != 0) != shadows)) { if (k < 2 && (k == 0 || (s != 0) != shadows)) {
shadows = s != 0; shadows = s != 0;
const vec4 xyzz = xyzz11[idx];
xzyLR0[k][0] = xyzz.x; xzyLR0[k][0] = xyzz.x;
xzyLR0[k][1] = xyzz.z; xzyLR0[k][1] = xyzz.z;
xzyLR0[k][2] = xyzz.y; xzyLR0[k][2] = xyzz.y;
@ -448,60 +313,146 @@ struct Kernel12 {
v12.y = xzyy[2]; v12.y = xzyy[2];
v12.z = xzyy[1]; v12.z = xzyy[1];
} }
return std::make_pair(x12, v12);
} }
}; };
std::tuple<Vec<int>, Vec<vec3>> Intersect12( struct Kernel12Tmp {
const Manifold::Impl &inP, const Manifold::Impl &inQ, const Vec<int> &s02, Vec<std::array<int, 2>> p1q2_;
const SparseIndices &p0q2, const Vec<int> &s11, const SparseIndices &p1q1, Vec<int> x12_;
const Vec<double> &z02, const Vec<vec4> &xyzz11, SparseIndices &p1q2, Vec<vec3> v12_;
bool forward) { };
struct Kernel12Recorder {
using Local = Kernel12Tmp;
Kernel12& k12;
VecView<const TmpEdge> tmpedges;
bool forward;
#if MANIFOLD_PAR == 1
tbb::combinable<Kernel12Tmp> 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<Kernel12Tmp> tmps;
store.combine_each(
[&](Kernel12Tmp& data) { tmps.emplace_back(std::move(data)); });
std::vector<size_t> 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<int>, Vec<vec3>> Intersect12(const Manifold::Impl& inP,
const Manifold::Impl& inQ,
Vec<std::array<int, 2>>& p1q2,
double expandP, bool forward) {
ZoneScoped; ZoneScoped;
Vec<int> x12(p1q2.size()); // a: 1 (edge), b: 2 (face)
Vec<vec3> v12(p1q2.size()); const Manifold::Impl& a = forward ? inP : inQ;
const Manifold::Impl& b = forward ? inQ : inP;
for_each_n( Kernel02 k02{a.vertPos_, b.halfedge_, b.vertPos_,
autoPolicy(p1q2.size(), 1e4), countAt(0_uz), p1q2.size(), expandP, inP.vertNormal_, forward};
Kernel12({x12, v12, p0q2.AsVec64(), s02, z02, p1q1.AsVec64(), s11, xyzz11, Kernel11 k11{inP.vertPos_, inQ.vertPos_, inP.halfedge_,
inP.halfedge_, inQ.halfedge_, inP.vertPos_, forward, p1q2})); inQ.halfedge_, expandP, inP.vertNormal_};
p1q2.KeepFinite(v12, x12); Vec<TmpEdge> tmpedges = CreateTmpEdges(a.halfedge_);
Vec<Box> 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<false, Box, Kernel12Recorder>(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<size_t> 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); return std::make_tuple(x12, v12);
}; };
Vec<int> Winding03(const Manifold::Impl &inP, Vec<int> &vertices, Vec<int> &s02, Vec<int> Winding03(const Manifold::Impl& inP, const Manifold::Impl& inQ,
bool reverse) { double expandP, bool forward) {
ZoneScoped; ZoneScoped;
// verts that are not shadowed (not in p0q2) have winding number zero. // verts that are not shadowed (not in p0q2) have winding number zero.
Vec<int> w03(inP.NumVert(), 0); // a: 0 (vertex), b: 2 (face)
if (vertices.size() <= 1e5) { const Manifold::Impl& a = forward ? inP : inQ;
for_each_n(ExecutionPolicy::Seq, countAt(0), s02.size(), const Manifold::Impl& b = forward ? inQ : inP;
[&w03, &vertices, &s02, reverse](const int i) { Vec<int> w03(a.NumVert(), 0);
w03[vertices[i]] += s02[i] * (reverse ? -1 : 1); Kernel02 k02{a.vertPos_, b.halfedge_, b.vertPos_,
}); expandP, inP.vertNormal_, forward};
} else { auto f = [&](int a, int b) {
for_each_n(ExecutionPolicy::Par, countAt(0), s02.size(), const auto [s02, z02] = k02(a, b);
[&w03, &vertices, &s02, reverse](const int i) { if (std::isfinite(z02)) AtomicAdd(w03[a], s02 * (!forward ? -1 : 1));
AtomicAdd(w03[vertices[i]], s02[i] * (reverse ? -1 : 1)); };
}); auto recorder = MakeSimpleRecorder(f);
} b.collider_.Collisions<false>(a.vertPos_.cview(), recorder);
return w03; return w03;
}; };
} // namespace } // namespace
namespace manifold { namespace manifold {
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ, Boolean3::Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
OpType op) OpType op)
: inP_(inP), inQ_(inQ), expandP_(op == OpType::Add ? 1.0 : -1.0) { : inP_(inP), inQ_(inQ), expandP_(op == OpType::Add ? 1.0 : -1.0) {
// Symbolic perturbation: // Symbolic perturbation:
// Union -> expand inP // Union -> expand inP
// Difference, Intersection -> contract inP // Difference, Intersection -> contract inP
constexpr size_t INT_MAX_SZ =
#ifdef MANIFOLD_DEBUG static_cast<size_t>(std::numeric_limits<int>::max());
Timer broad;
broad.Start();
#endif
if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) { if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) {
PRINT("No overlap, early out"); PRINT("No overlap, early out");
@ -510,88 +461,34 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
return; 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 #ifdef MANIFOLD_DEBUG
broad.Stop();
Timer intersections; Timer intersections;
intersections.Start(); intersections.Start();
#endif #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<int> s11;
Vec<vec4> 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<int> s02;
Vec<double> z02;
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_);
PRINT("s02 size = " << s02.size());
Vec<int> s20;
Vec<double> z20;
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_);
PRINT("s20 size = " << s20.size());
// Level 3 // Level 3
// Build up the intersection of the edges and triangles, keeping only those // Build up the intersection of the edges and triangles, keeping only those
// that intersect, and record the direction the edge is passing through the // that intersect, and record the direction the edge is passing through the
// triangle. // triangle.
std::tie(x12_, v12_) = std::tie(x12_, v12_) = Intersect12(inP, inQ, p1q2_, expandP_, true);
Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true);
PRINT("x12 size = " << x12_.size()); PRINT("x12 size = " << x12_.size());
std::tie(x21_, v21_) = std::tie(x21_, v21_) = Intersect12(inP, inQ, p2q1_, expandP_, false);
Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false);
PRINT("x21 size = " << x21_.size()); PRINT("x21 size = " << x21_.size());
s11.clear(); if (x12_.size() > INT_MAX_SZ || x21_.size() > INT_MAX_SZ) {
xyzz11.clear(); valid = false;
z02.clear(); return;
z20.clear(); }
Vec<int> p0 = p0q2.Copy(false);
p0q2.Resize(0);
Vec<int> q0 = p2q0.Copy(true);
p2q0.Resize(0);
// Sum up the winding numbers of all vertices. // Sum up the winding numbers of all vertices.
w03_ = Winding03(inP, p0, s02, false); w03_ = Winding03(inP, inQ, expandP_, true);
w30_ = Winding03(inP, inQ, expandP_, false);
w30_ = Winding03(inQ, q0, s20, true);
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
intersections.Stop(); intersections.Stop();
if (ManifoldParams().verbose) { if (ManifoldParams().verbose) {
broad.Print("Broad phase");
intersections.Print("Intersections"); intersections.Print("Intersections");
} }
#endif #endif

View File

@ -13,11 +13,11 @@
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include "./impl.h" #include "impl.h"
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
#define PRINT(msg) \ #define PRINT(msg) \
if (ManifoldParams().verbose) std::cout << msg << std::endl; if (ManifoldParams().verbose > 0) std::cout << msg << std::endl;
#else #else
#define PRINT(msg) #define PRINT(msg)
#endif #endif
@ -53,8 +53,9 @@ class Boolean3 {
private: private:
const Manifold::Impl &inP_, &inQ_; const Manifold::Impl &inP_, &inQ_;
const double expandP_; const double expandP_;
SparseIndices p1q2_, p2q1_; Vec<std::array<int, 2>> p1q2_, p2q1_;
Vec<int> x12_, x21_, w03_, w30_; Vec<int> x12_, x21_, w03_, w30_;
Vec<vec3> v12_, v21_; Vec<vec3> v12_, v21_;
bool valid = true;
}; };
} // namespace manifold } // namespace manifold

View File

@ -16,9 +16,9 @@
#include <array> #include <array>
#include <map> #include <map>
#include "./boolean3.h" #include "boolean3.h"
#include "./parallel.h" #include "parallel.h"
#include "./utils.h" #include "utils.h"
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>) #if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 #define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
@ -37,7 +37,7 @@ using namespace manifold;
template <> template <>
struct std::hash<std::pair<int, int>> { struct std::hash<std::pair<int, int>> {
size_t operator()(const std::pair<int, int> &p) const { size_t operator()(const std::pair<int, int>& p) const {
return std::hash<int>()(p.first) ^ std::hash<int>()(p.second); return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);
} }
}; };
@ -83,12 +83,12 @@ struct CountNewVerts {
VecView<int> countP; VecView<int> countP;
VecView<int> countQ; VecView<int> countQ;
VecView<const int> i12; VecView<const int> i12;
const SparseIndices &pq; const Vec<std::array<int, 2>>& pq;
VecView<const Halfedge> halfedges; VecView<const Halfedge> halfedges;
void operator()(const int idx) { void operator()(const int idx) {
int edgeP = pq.Get(idx, inverted); int edgeP = pq[idx][inverted ? 1 : 0];
int faceQ = pq.Get(idx, !inverted); int faceQ = pq[idx][inverted ? 0 : 1];
int inclusion = std::abs(i12[idx]); int inclusion = std::abs(i12[idx]);
if (atomic) { if (atomic) {
@ -106,10 +106,10 @@ struct CountNewVerts {
}; };
std::tuple<Vec<int>, Vec<int>> SizeOutput( std::tuple<Vec<int>, Vec<int>> SizeOutput(
Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ, Manifold::Impl& outR, const Manifold::Impl& inP, const Manifold::Impl& inQ,
const Vec<int> &i03, const Vec<int> &i30, const Vec<int> &i12, const Vec<int>& i03, const Vec<int>& i30, const Vec<int>& i12,
const Vec<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1, const Vec<int>& i21, const Vec<std::array<int, 2>>& p1q2,
bool invertQ) { const Vec<std::array<int, 2>>& p2q1, bool invertQ) {
ZoneScoped; ZoneScoped;
Vec<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0); Vec<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
// note: numFaceR <= facePQ2R.size() = sidesPerFacePQ.size() + 1 // note: numFaceR <= facePQ2R.size() = sidesPerFacePQ.size() + 1
@ -154,7 +154,7 @@ std::tuple<Vec<int>, Vec<int>> SizeOutput(
int numFaceR = facePQ2R.back(); int numFaceR = facePQ2R.back();
facePQ2R.resize(inP.NumTri() + inQ.NumTri()); facePQ2R.resize(inP.NumTri() + inQ.NumTri());
outR.faceNormal_.resize(numFaceR); outR.faceNormal_.resize_nofill(numFaceR);
Vec<size_t> tmpBuffer(outR.faceNormal_.size()); Vec<size_t> tmpBuffer(outR.faceNormal_.size());
auto faceIds = TransformIterator(countAt(0_uz), [&sidesPerFacePQ](size_t i) { auto faceIds = TransformIterator(countAt(0_uz), [&sidesPerFacePQ](size_t i) {
@ -196,8 +196,9 @@ std::tuple<Vec<int>, Vec<int>> SizeOutput(
} }
struct EdgePos { struct EdgePos {
int vert;
double edgePos; double edgePos;
int vert;
int collisionId;
bool isStart; bool isStart;
}; };
@ -212,8 +213,8 @@ void AddNewEdgeVerts(
// we need concurrent_map because we will be adding things concurrently // we need concurrent_map because we will be adding things concurrently
concurrent_map<int, std::vector<EdgePos>> &edgesP, concurrent_map<int, std::vector<EdgePos>> &edgesP,
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew, concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
const SparseIndices &p1q2, const Vec<int> &i12, const Vec<int> &v12R, const Vec<std::array<int, 2>> &p1q2, const Vec<int> &i12, const Vec<int> &v12R,
const Vec<Halfedge> &halfedgeP, bool forward) { const Vec<Halfedge> &halfedgeP, bool forward, size_t offset) {
ZoneScoped; ZoneScoped;
// For each edge of P that intersects a face of Q (p1q2), add this vertex to // 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 // 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. // the output vert index. When forward is false, all is reversed.
auto process = [&](std::function<void(size_t)> lock, auto process = [&](std::function<void(size_t)> lock,
std::function<void(size_t)> unlock, size_t i) { std::function<void(size_t)> unlock, size_t i) {
const int edgeP = p1q2.Get(i, !forward); const int edgeP = p1q2[i][forward ? 0 : 1];
const int faceQ = p1q2.Get(i, forward); const int faceQ = p1q2[i][forward ? 1 : 0];
const int vert = v12R[i]; const int vert = v12R[i];
const int inclusion = i12[i]; const int inclusion = i12[i];
@ -236,21 +237,22 @@ void AddNewEdgeVerts(
bool direction = inclusion < 0; bool direction = inclusion < 0;
std::hash<std::pair<int, int>> pairHasher; std::hash<std::pair<int, int>> pairHasher;
std::array<std::tuple<bool, size_t, std::vector<EdgePos> *>, 3> edges = { std::array<std::tuple<bool, size_t, std::vector<EdgePos>*>, 3> edges = {
std::make_tuple(direction, std::hash<int>{}(edgeP), &edgesP[edgeP]), std::make_tuple(direction, std::hash<int>{}(edgeP), &edgesP[edgeP]),
std::make_tuple(direction ^ !forward, // revert if not forward std::make_tuple(direction ^ !forward, // revert if not forward
pairHasher(keyRight), &edgesNew[keyRight]), pairHasher(keyRight), &edgesNew[keyRight]),
std::make_tuple(direction ^ forward, // revert if forward std::make_tuple(direction ^ forward, // revert if forward
pairHasher(keyLeft), &edgesNew[keyLeft])}; pairHasher(keyLeft), &edgesNew[keyLeft])};
for (const auto &tuple : edges) { for (const auto& tuple : edges) {
lock(std::get<1>(tuple)); lock(std::get<1>(tuple));
for (int j = 0; j < std::abs(inclusion); ++j) 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<int>(i + offset), std::get<0>(tuple)});
unlock(std::get<1>(tuple)); unlock(std::get<1>(tuple));
direction = !direction; direction = !direction;
} }
}; };
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>) #if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)
// parallelize operations, requires concurrent_map so we can only enable this // parallelize operations, requires concurrent_map so we can only enable this
// with tbb // with tbb
if (p1q2.size() > kParallelThreshold) { if (p1q2.size() > kParallelThreshold) {
@ -264,19 +266,19 @@ void AddNewEdgeVerts(
std::placeholders::_1); std::placeholders::_1);
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0_uz, p1q2.size(), 32), tbb::blocked_range<size_t>(0_uz, p1q2.size(), 32),
[&](const tbb::blocked_range<size_t> &range) { [&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i != range.end(); i++) processFun(i); for (size_t i = range.begin(); i != range.end(); i++) processFun(i);
}, },
ap); ap);
return; return;
} }
#endif #endif
auto processFun = std::bind( auto processFun =
process, [](size_t _) {}, [](size_t _) {}, std::placeholders::_1); std::bind(process, [](size_t) {}, [](size_t) {}, std::placeholders::_1);
for (size_t i = 0; i < p1q2.size(); ++i) processFun(i); for (size_t i = 0; i < p1q2.size(); ++i) processFun(i);
} }
std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) { std::vector<Halfedge> PairUp(std::vector<EdgePos>& edgePos) {
// Pair start vertices with end vertices to form edges. The choice of pairing // 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 // 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 // geometrically valid. If the order does not go start-end-start-end... then
@ -289,7 +291,11 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
[](EdgePos x) { return x.isStart; }); [](EdgePos x) { return x.isStart; });
DEBUG_ASSERT(static_cast<size_t>(middle - edgePos.begin()) == nEdges, DEBUG_ASSERT(static_cast<size_t>(middle - edgePos.begin()) == nEdges,
topologyErr, "Non-manifold edge!"); 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(edgePos.begin(), middle, cmp);
std::stable_sort(middle, edgePos.end(), cmp); std::stable_sort(middle, edgePos.end(), cmp);
std::vector<Halfedge> edges; std::vector<Halfedge> edges;
@ -298,11 +304,11 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
return edges; return edges;
} }
void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP, void AppendPartialEdges(Manifold::Impl& outR, Vec<char>& wholeHalfedgeP,
Vec<int> &facePtrR, Vec<int>& facePtrR,
concurrent_map<int, std::vector<EdgePos>> &edgesP, concurrent_map<int, std::vector<EdgePos>>& edgesP,
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP, Vec<TriRef>& halfedgeRef, const Manifold::Impl& inP,
const Vec<int> &i03, const Vec<int> &vP2R, const Vec<int>& i03, const Vec<int>& vP2R,
const Vec<int>::IterC faceP2R, bool forward) { const Vec<int>::IterC faceP2R, bool forward) {
ZoneScoped; ZoneScoped;
// Each edge in the map is partially retained; for each of these, look up // Each edge in the map is partially retained; for each of these, look up
@ -310,15 +316,15 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
// while remapping them to the output using vP2R. Use the verts position // while remapping them to the output using vP2R. Use the verts position
// projected along the edge vector to pair them up, then distribute these // projected along the edge vector to pair them up, then distribute these
// edges to their faces. // edges to their faces.
Vec<Halfedge> &halfedgeR = outR.halfedge_; Vec<Halfedge>& halfedgeR = outR.halfedge_;
const Vec<vec3> &vertPosP = inP.vertPos_; const Vec<vec3>& vertPosP = inP.vertPos_;
const Vec<Halfedge> &halfedgeP = inP.halfedge_; const Vec<Halfedge>& halfedgeP = inP.halfedge_;
for (auto &value : edgesP) { for (auto& value : edgesP) {
const int edgeP = value.first; const int edgeP = value.first;
std::vector<EdgePos> &edgePosP = value.second; std::vector<EdgePos>& edgePosP = value.second;
const Halfedge &halfedge = halfedgeP[edgeP]; const Halfedge& halfedge = halfedgeP[edgeP];
wholeHalfedgeP[edgeP] = false; wholeHalfedgeP[edgeP] = false;
wholeHalfedgeP[halfedge.pairedHalfedge] = false; wholeHalfedgeP[halfedge.pairedHalfedge] = false;
@ -326,13 +332,13 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
const int vEnd = halfedge.endVert; const int vEnd = halfedge.endVert;
const vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart]; const vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart];
// Fill in the edge positions of the old points. // 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); edge.edgePos = la::dot(outR.vertPos_[edge.vert], edgeVec);
} }
int inclusion = i03[vStart]; int inclusion = i03[vStart];
EdgePos edgePos = {vP2R[vStart], EdgePos edgePos = {la::dot(outR.vertPos_[vP2R[vStart]], edgeVec),
la::dot(outR.vertPos_[vP2R[vStart]], edgeVec), vP2R[vStart], std::numeric_limits<int>::max(),
inclusion > 0}; inclusion > 0};
for (int j = 0; j < std::abs(inclusion); ++j) { for (int j = 0; j < std::abs(inclusion); ++j) {
edgePosP.push_back(edgePos); edgePosP.push_back(edgePos);
@ -340,8 +346,8 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
} }
inclusion = i03[vEnd]; inclusion = i03[vEnd];
edgePos = {vP2R[vEnd], la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec), edgePos = {la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec), vP2R[vEnd],
inclusion < 0}; std::numeric_limits<int>::max(), inclusion < 0};
for (int j = 0; j < std::abs(inclusion); ++j) { for (int j = 0; j < std::abs(inclusion); ++j) {
edgePosP.push_back(edgePos); edgePosP.push_back(edgePos);
++edgePos.vert; ++edgePos.vert;
@ -359,8 +365,8 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
// reference is now to the endVert instead of the startVert, which is one // 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 // position advanced CCW. This is only valid if this is a retained vert; it
// will be ignored later if the vert is new. // will be ignored later if the vert is new.
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP}; const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1};
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP}; const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1};
for (Halfedge e : edges) { for (Halfedge e : edges) {
const int forwardEdge = facePtrR[faceLeft]++; const int forwardEdge = facePtrR[faceLeft]++;
@ -379,18 +385,18 @@ void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
} }
void AppendNewEdges( void AppendNewEdges(
Manifold::Impl &outR, Vec<int> &facePtrR, Manifold::Impl& outR, Vec<int>& facePtrR,
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew, concurrent_map<std::pair<int, int>, std::vector<EdgePos>>& edgesNew,
Vec<TriRef> &halfedgeRef, const Vec<int> &facePQ2R, const int numFaceP) { Vec<TriRef>& halfedgeRef, const Vec<int>& facePQ2R, const int numFaceP) {
ZoneScoped; ZoneScoped;
// Pair up each edge's verts and distribute to faces based on indices in key. // Pair up each edge's verts and distribute to faces based on indices in key.
Vec<Halfedge> &halfedgeR = outR.halfedge_; Vec<Halfedge>& halfedgeR = outR.halfedge_;
Vec<vec3> &vertPosR = outR.vertPos_; Vec<vec3>& vertPosR = outR.vertPos_;
for (auto &value : edgesNew) { for (auto& value : edgesNew) {
const int faceP = value.first.first; const int faceP = value.first.first;
const int faceQ = value.first.second; const int faceQ = value.first.second;
std::vector<EdgePos> &edgePos = value.second; std::vector<EdgePos>& edgePos = value.second;
Box bbox; Box bbox;
for (auto edge : edgePos) { for (auto edge : edgePos) {
@ -401,7 +407,7 @@ void AppendNewEdges(
const int i = (size.x > size.y && size.x > size.z) ? 0 const int i = (size.x > size.y && size.x > size.z) ? 0
: size.y > size.z ? 1 : size.y > size.z ? 1
: 2; : 2;
for (auto &edge : edgePos) { for (auto& edge : edgePos) {
edge.edgePos = vertPosR[edge.vert][i]; edge.edgePos = vertPosR[edge.vert][i];
} }
@ -411,8 +417,8 @@ void AppendNewEdges(
// add halfedges to result // add halfedges to result
const int faceLeft = facePQ2R[faceP]; const int faceLeft = facePQ2R[faceP];
const int faceRight = facePQ2R[numFaceP + faceQ]; const int faceRight = facePQ2R[numFaceP + faceQ];
const TriRef forwardRef = {0, -1, faceP}; const TriRef forwardRef = {0, -1, faceP, -1};
const TriRef backwardRef = {1, -1, faceQ}; const TriRef backwardRef = {1, -1, faceQ, -1};
for (Halfedge e : edges) { for (Halfedge e : edges) {
const int forwardEdge = facePtrR[faceLeft]++; const int forwardEdge = facePtrR[faceLeft]++;
const int backwardEdge = facePtrR[faceRight]++; const int backwardEdge = facePtrR[faceRight]++;
@ -459,8 +465,8 @@ struct DuplicateHalfedges {
// Negative inclusion means the halfedges are reversed, which means our // Negative inclusion means the halfedges are reversed, which means our
// reference is now to the endVert instead of the startVert, which is one // reference is now to the endVert instead of the startVert, which is one
// position advanced CCW. // position advanced CCW.
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP}; const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP, -1};
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP}; const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP, -1};
for (int i = 0; i < std::abs(inclusion); ++i) { for (int i = 0; i < std::abs(inclusion); ++i) {
int forwardEdge = AtomicAdd(facePtr[newFace], 1); int forwardEdge = AtomicAdd(facePtr[newFace], 1);
@ -479,10 +485,10 @@ struct DuplicateHalfedges {
} }
}; };
void AppendWholeEdges(Manifold::Impl &outR, Vec<int> &facePtrR, void AppendWholeEdges(Manifold::Impl& outR, Vec<int>& facePtrR,
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP, Vec<TriRef>& halfedgeRef, const Manifold::Impl& inP,
const Vec<char> wholeHalfedgeP, const Vec<int> &i03, const Vec<char> wholeHalfedgeP, const Vec<int>& i03,
const Vec<int> &vP2R, VecView<const int> faceP2R, const Vec<int>& vP2R, VecView<const int> faceP2R,
bool forward) { bool forward) {
ZoneScoped; ZoneScoped;
for_each_n( for_each_n(
@ -496,26 +502,26 @@ struct MapTriRef {
VecView<const TriRef> triRefQ; VecView<const TriRef> triRefQ;
const int offsetQ; const int offsetQ;
void operator()(TriRef &triRef) { void operator()(TriRef& triRef) {
const int tri = triRef.tri; const int tri = triRef.faceID;
const bool PQ = triRef.meshID == 0; const bool PQ = triRef.meshID == 0;
triRef = PQ ? triRefP[tri] : triRefQ[tri]; triRef = PQ ? triRefP[tri] : triRefQ[tri];
if (!PQ) triRef.meshID += offsetQ; if (!PQ) triRef.meshID += offsetQ;
} }
}; };
void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP, void UpdateReference(Manifold::Impl& outR, const Manifold::Impl& inP,
const Manifold::Impl &inQ, bool invertQ) { const Manifold::Impl& inQ, bool invertQ) {
const int offsetQ = Manifold::Impl::meshIDCounter_; const int offsetQ = Manifold::Impl::meshIDCounter_;
for_each_n( for_each_n(
autoPolicy(outR.NumTri(), 1e5), outR.meshRelation_.triRef.begin(), autoPolicy(outR.NumTri(), 1e5), outR.meshRelation_.triRef.begin(),
outR.NumTri(), outR.NumTri(),
MapTriRef({inP.meshRelation_.triRef, inQ.meshRelation_.triRef, offsetQ})); 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; 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] = pair.second;
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^= outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
invertQ; invertQ;
@ -537,10 +543,10 @@ struct Barycentric {
const TriRef refPQ = ref[tri]; const TriRef refPQ = ref[tri];
if (halfedgeR[3 * tri].startVert < 0) return; if (halfedgeR[3 * tri].startVert < 0) return;
const int triPQ = refPQ.tri; const int triPQ = refPQ.faceID;
const bool PQ = refPQ.meshID == 0; const bool PQ = refPQ.meshID == 0;
const auto &vertPos = PQ ? vertPosP : vertPosQ; const auto& vertPos = PQ ? vertPosP : vertPosQ;
const auto &halfedge = PQ ? halfedgeP : halfedgeQ; const auto& halfedge = PQ ? halfedgeP : halfedgeQ;
mat3 triPos; mat3 triPos;
for (const int j : {0, 1, 2}) for (const int j : {0, 1, 2})
@ -553,18 +559,16 @@ struct Barycentric {
} }
}; };
void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP, void CreateProperties(Manifold::Impl& outR, const Manifold::Impl& inP,
const Manifold::Impl &inQ) { const Manifold::Impl& inQ) {
ZoneScoped; ZoneScoped;
const int numPropP = inP.NumProp(); const int numPropP = inP.NumProp();
const int numPropQ = inQ.NumProp(); const int numPropQ = inQ.NumProp();
const int numProp = std::max(numPropP, numPropQ); const int numProp = std::max(numPropP, numPropQ);
outR.meshRelation_.numProp = numProp; outR.numProp_ = numProp;
if (numProp == 0) return; if (numProp == 0) return;
const int numTri = outR.NumTri(); const int numTri = outR.NumTri();
outR.meshRelation_.triProperties.resize(numTri);
Vec<vec3> bary(outR.halfedge_.size()); Vec<vec3> bary(outR.halfedge_.size());
for_each_n(autoPolicy(numTri, 1e4), countAt(0), numTri, for_each_n(autoPolicy(numTri, 1e4), countAt(0), numTri,
Barycentric({bary, outR.meshRelation_.triRef, inP.vertPos_, 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[0].resize(inQ.NumPropVert(), -1);
propMissIdx[1].resize(inP.NumPropVert(), -1); propMissIdx[1].resize(inP.NumPropVert(), -1);
outR.meshRelation_.properties.reserve(outR.NumVert() * numProp); outR.properties_.reserve(outR.NumVert() * numProp);
int idx = 0; int idx = 0;
for (int tri = 0; tri < numTri; ++tri) { 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 TriRef ref = outR.meshRelation_.triRef[tri];
const bool PQ = ref.meshID == 0; const bool PQ = ref.meshID == 0;
const int oldNumProp = PQ ? numPropP : numPropQ; const int oldNumProp = PQ ? numPropP : numPropQ;
const auto &properties = const auto& properties = PQ ? inP.properties_ : inQ.properties_;
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties; const auto& halfedge = PQ ? inP.halfedge_ : inQ.halfedge_;
const ivec3 &triProp = oldNumProp == 0 ? ivec3(-1)
: PQ ? inP.meshRelation_.triProperties[ref.tri]
: inQ.meshRelation_.triProperties[ref.tri];
for (const int i : {0, 1, 2}) { for (const int i : {0, 1, 2}) {
const int vert = outR.halfedge_[3 * tri + i].startVert; 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); ivec4 key(PQ, idMissProp, -1, -1);
if (oldNumProp > 0) { if (oldNumProp > 0) {
@ -604,7 +605,7 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
for (const int j : {0, 1, 2}) { for (const int j : {0, 1, 2}) {
if (uvw[j] == 1) { if (uvw[j] == 1) {
// On a retained vert, the propVert must also match // On a retained vert, the propVert must also match
key[2] = triProp[j]; key[2] = halfedge[3 * ref.faceID + j].propVert;
edge = -1; edge = -1;
break; break;
} }
@ -612,8 +613,8 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
} }
if (edge >= 0) { if (edge >= 0) {
// On an edge, both propVerts must match // On an edge, both propVerts must match
const int p0 = triProp[Next3(edge)]; const int p0 = halfedge[3 * ref.faceID + Next3(edge)].propVert;
const int p1 = triProp[Prev3(edge)]; const int p1 = halfedge[3 * ref.faceID + Prev3(edge)].propVert;
key[1] = vert; key[1] = vert;
key[2] = std::min(p0, p1); key[2] = std::min(p0, p1);
key[3] = std::max(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) { if (key.y == idMissProp && key.z >= 0) {
// only key.x/key.z matters // only key.x/key.z matters
auto &entry = propMissIdx[key.x][key.z]; auto& entry = propMissIdx[key.x][key.z];
if (entry >= 0) { if (entry >= 0) {
outR.meshRelation_.triProperties[tri][i] = entry; outR.halfedge_[3 * tri + i].propVert = entry;
continue; continue;
} }
entry = idx; entry = idx;
} else { } else {
auto &bin = propIdx[key.y]; auto& bin = propIdx[key.y];
bool bFound = false; bool bFound = false;
for (const auto &b : bin) { for (const auto& b : bin) {
if (b.first == ivec3(key.x, key.z, key.w)) { if (b.first == ivec3(key.x, key.z, key.w)) {
bFound = true; bFound = true;
outR.meshRelation_.triProperties[tri][i] = b.second; outR.halfedge_[3 * tri + i].propVert = b.second;
break; 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)); 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) { for (int p = 0; p < numProp; ++p) {
if (p < oldNumProp) { if (p < oldNumProp) {
vec3 oldProps; vec3 oldProps;
for (const int j : {0, 1, 2}) for (const int j : {0, 1, 2})
oldProps[j] = properties[oldNumProp * triProp[j] + p]; oldProps[j] =
outR.meshRelation_.properties.push_back(la::dot(uvw, oldProps)); properties[oldNumProp * halfedge[3 * ref.faceID + j].propVert +
p];
outR.properties_.push_back(la::dot(uvw, oldProps));
} else { } else {
outR.meshRelation_.properties.push_back(0); outR.properties_.push_back(0);
} }
} }
} }
} }
} }
void ReorderHalfedges(VecView<Halfedge>& 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<Halfedge, 3> 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
namespace manifold { namespace manifold {
@ -697,6 +733,12 @@ Manifold::Impl Boolean3::Result(OpType op) const {
return inP_; return inP_;
} }
if (!valid) {
auto impl = Manifold::Impl();
impl.status_ = Manifold::Error::ResultTooLarge;
return impl;
}
const bool invertQ = op == OpType::Subtract; const bool invertQ = op == OpType::Subtract;
// Convert winding numbers to inclusion values based on operation type. // 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.epsilon_ = std::max(inP_.epsilon_, inQ_.epsilon_);
outR.tolerance_ = std::max(inP_.tolerance_, inQ_.tolerance_); 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]. // Add vertices, duplicating for inclusion numbers not in [-1, 1].
// Retained vertices from P and Q: // Retained vertices from P and Q:
for_each_n(autoPolicy(inP_.NumVert(), 1e4), countAt(0), inP_.NumVert(), 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 <P, Q> // This key is the face index of <P, Q>
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> edgesNew; concurrent_map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true); AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true, 0);
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false); AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false,
p1q2_.size());
v12R.clear(); v12R.clear();
v21R.clear(); v21R.clear();
@ -842,6 +885,8 @@ Manifold::Impl Boolean3::Result(OpType op) const {
halfedgeRef.clear(); halfedgeRef.clear();
faceEdge.clear(); faceEdge.clear();
ReorderHalfedges(outR.halfedge_);
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
triangulate.Stop(); triangulate.Stop();
Timer simplify; Timer simplify;
@ -856,7 +901,7 @@ Manifold::Impl Boolean3::Result(OpType op) const {
UpdateReference(outR, inP_, inQ_, invertQ); UpdateReference(outR, inP_, inQ_, invertQ);
outR.SimplifyTopology(); outR.SimplifyTopology(nPv + nQv);
outR.RemoveUnreferencedVerts(); outR.RemoveUnreferencedVerts();
if (ManifoldParams().intermediateChecks) if (ManifoldParams().intermediateChecks)

View File

@ -13,11 +13,10 @@
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include "./parallel.h"
#include "./sparse.h"
#include "./utils.h"
#include "./vec.h"
#include "manifold/common.h" #include "manifold/common.h"
#include "parallel.h"
#include "utils.h"
#include "vec.h"
#ifdef _MSC_VER #ifdef _MSC_VER
#include <intrin.h> #include <intrin.h>
@ -40,7 +39,7 @@ constexpr int kRoot = 1;
#ifdef _MSC_VER #ifdef _MSC_VER
#ifndef _WINDEF_ #ifndef _WINDEF_
typedef unsigned long DWORD; using DWORD = unsigned long;
#endif #endif
uint32_t inline ctz(uint32_t value) { uint32_t inline ctz(uint32_t value) {
@ -163,14 +162,16 @@ struct FindCollision {
VecView<const T> queries; VecView<const T> queries;
VecView<const Box> nodeBBox_; VecView<const Box> nodeBBox_;
VecView<const std::pair<int, int>> internalChildren_; VecView<const std::pair<int, int>> 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]); bool overlaps = nodeBBox_[node].DoesOverlap(queries[queryIdx]);
if (overlaps && IsLeaf(node)) { if (overlaps && IsLeaf(node)) {
int leafIdx = Node2Leaf(node); int leafIdx = Node2Leaf(node);
if (!selfCollision || leafIdx != queryIdx) { if (!selfCollision || leafIdx != queryIdx) {
recorder.record(queryIdx, leafIdx, ind); recorder.record(queryIdx, leafIdx, local);
} }
} }
return overlaps && IsInternal(node); // Should traverse into node return overlaps && IsInternal(node); // Should traverse into node
@ -183,14 +184,14 @@ struct FindCollision {
int top = -1; int top = -1;
// Depth-first search // Depth-first search
int node = kRoot; int node = kRoot;
SparseIndices& ind = recorder.local(); Local& local = recorder.local();
while (1) { while (1) {
int internal = Node2Internal(node); int internal = Node2Internal(node);
int child1 = internalChildren_[internal].first; int child1 = internalChildren_[internal].first;
int child2 = internalChildren_[internal].second; int child2 = internalChildren_[internal].second;
int traverse1 = RecordCollision(child1, queryIdx, ind); int traverse1 = RecordCollision(child1, queryIdx, local);
int traverse2 = RecordCollision(child2, queryIdx, ind); int traverse2 = RecordCollision(child2, queryIdx, local);
if (!traverse1 && !traverse2) { if (!traverse1 && !traverse2) {
if (top < 0) break; // done if (top < 0) break; // done
@ -205,35 +206,6 @@ struct FindCollision {
} }
}; };
template <const bool inverted>
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 <const bool inverted>
struct ParCollisionRecorder {
tbb::combinable<SparseIndices>& 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 { struct BuildInternalBoxes {
VecView<Box> nodeBBox_; VecView<Box> nodeBBox_;
VecView<int> counter_; VecView<int> counter_;
@ -266,6 +238,22 @@ constexpr inline uint32_t SpreadBits3(uint32_t v) {
} }
} // namespace collider_internal } // namespace collider_internal
template <typename F>
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 <typename F>
inline SimpleRecorder<F> MakeSimpleRecorder(F& f) {
return SimpleRecorder<F>{f};
}
/** @ingroup Private */ /** @ingroup Private */
class Collider { class Collider {
public: public:
@ -278,7 +266,7 @@ class Collider {
"vectors must be the same length"); "vectors must be the same length");
int num_nodes = 2 * leafBB.size() - 1; int num_nodes = 2 * leafBB.size() - 1;
// assign and allocate members // assign and allocate members
nodeBBox_.resize(num_nodes); nodeBBox_.resize_nofill(num_nodes);
nodeParent_.resize(num_nodes, -1); nodeParent_.resize(num_nodes, -1);
internalChildren_.resize(leafBB.size() - 1, std::make_pair(-1, -1)); internalChildren_.resize(leafBB.size() - 1, std::make_pair(-1, -1));
// organize tree // organize tree
@ -321,40 +309,27 @@ class Collider {
{nodeBBox_, counter, nodeParent_, internalChildren_})); {nodeBBox_, counter, nodeParent_, internalChildren_}));
} }
template <const bool selfCollision = false, const bool inverted = false, // This function iterates over queriesIn and calls recorder.record(queryIdx,
typename T> // leafIdx, local) for each collision it found.
void Collisions(const VecView<const T>& queriesIn, // If selfCollisionl is true, it will skip the case where queryIdx == leafIdx.
SparseIndices& queryTri) const { // 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 <const bool selfCollision = false, typename T, typename Recorder>
void Collisions(const VecView<const T>& queriesIn, Recorder& recorder,
bool parallel = true) const {
ZoneScoped; ZoneScoped;
using collider_internal::FindCollision; using collider_internal::FindCollision;
#if (MANIFOLD_PAR == 1) if (internalChildren_.empty()) return;
if (queriesIn.size() > collider_internal::kSequentialThreshold) { for_each_n(parallel ? autoPolicy(queriesIn.size(),
tbb::combinable<SparseIndices> store; collider_internal::kSequentialThreshold)
for_each_n( : ExecutionPolicy::Seq,
ExecutionPolicy::Par, countAt(0), queriesIn.size(), countAt(0), queriesIn.size(),
FindCollision<T, selfCollision, FindCollision<T, selfCollision, Recorder>{
collider_internal::ParCollisionRecorder<inverted>>{ queriesIn, nodeBBox_, internalChildren_, recorder});
queriesIn, nodeBBox_, internalChildren_, {store}});
std::vector<SparseIndices> 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<T, selfCollision,
collider_internal::SeqCollisionRecorder<inverted>>{
queriesIn, nodeBBox_, internalChildren_, {queryTri}});
}
template <const bool selfCollision = false, const bool inverted = false,
typename T>
SparseIndices Collisions(const VecView<const T>& queriesIn) const {
SparseIndices result;
Collisions<selfCollision, inverted, T>(queriesIn, result);
return result;
} }
static uint32_t MortonCode(vec3 position, Box bBox) { static uint32_t MortonCode(vec3 position, Box bBox) {

View File

@ -12,10 +12,43 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./csg_tree.h" #include "csg_tree.h"
#include "./impl.h" #include "impl.h"
#include "./parallel.h" #include "manifold/manifold.h"
#include "manifold/polygon.h" #include "manifold/polygon.h"
#include "parallel.h"
namespace {
using namespace manifold;
template <typename P, typename I>
std::shared_ptr<Manifold::Impl> SmoothImpl(
const MeshGLP<P, I>& meshGL,
const std::vector<Smoothness>& sharpenedEdges) {
DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
"when supplying tangents, the normal constructor should be used "
"rather than Smooth().");
MeshGLP<P, I> meshTmp = meshGL;
meshTmp.faceID.resize(meshGL.NumTri());
std::iota(meshTmp.faceID.begin(), meshTmp.faceID.end(), 0);
std::shared_ptr<Manifold::Impl> impl =
std::make_shared<Manifold::Impl>(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 { namespace manifold {
/** /**
@ -48,13 +81,7 @@ namespace manifold {
*/ */
Manifold Manifold::Smooth(const MeshGL& meshGL, Manifold Manifold::Smooth(const MeshGL& meshGL,
const std::vector<Smoothness>& sharpenedEdges) { const std::vector<Smoothness>& sharpenedEdges) {
DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error, return Manifold(SmoothImpl(meshGL, sharpenedEdges));
"when supplying tangents, the normal constructor should be used "
"rather than Smooth().");
std::shared_ptr<Impl> impl = std::make_shared<Impl>(meshGL);
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
return Manifold(impl);
} }
/** /**
@ -87,13 +114,7 @@ Manifold Manifold::Smooth(const MeshGL& meshGL,
*/ */
Manifold Manifold::Smooth(const MeshGL64& meshGL64, Manifold Manifold::Smooth(const MeshGL64& meshGL64,
const std::vector<Smoothness>& sharpenedEdges) { const std::vector<Smoothness>& sharpenedEdges) {
DEBUG_ASSERT(meshGL64.halfedgeTangent.empty(), std::runtime_error, return Manifold(SmoothImpl(meshGL64, sharpenedEdges));
"when supplying tangents, the normal constructor should be used "
"rather than Smooth().");
std::shared_ptr<Impl> impl = std::make_shared<Impl>(meshGL64);
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
return Manifold(impl);
} }
/** /**
@ -173,8 +194,7 @@ Manifold Manifold::Sphere(double radius, int circularSegments) {
int n = circularSegments > 0 ? (circularSegments + 3) / 4 int n = circularSegments > 0 ? (circularSegments + 3) / 4
: Quality::GetCircularSegments(radius) / 4; : Quality::GetCircularSegments(radius) / 4;
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron); auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
pImpl_->Subdivide( pImpl_->Subdivide([n](vec3, vec4, vec4) { return n - 1; });
[n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; });
for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(), for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(),
pImpl_->NumVert(), [radius](vec3& v) { pImpl_->NumVert(), [radius](vec3& v) {
v = la::cos(kHalfPi * (1.0 - v)); v = la::cos(kHalfPi * (1.0 - v));
@ -277,7 +297,7 @@ Manifold Manifold::Extrude(const Polygons& crossSection, double height,
pImpl_->CreateHalfedges(triVertsDH); pImpl_->CreateHalfedges(triVertsDH);
pImpl_->Finish(); pImpl_->Finish();
pImpl_->InitializeOriginal(); pImpl_->InitializeOriginal();
pImpl_->CreateFaces(); pImpl_->MarkCoplanar();
return Manifold(pImpl_); 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; const size_t next = i + 1 == poly.size() ? 0 : i + 1;
if ((poly[next].x < 0) != (poly[i].x < 0)) { 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].y - poly[next].y) /
(poly[i].x - poly[next].x); (poly[i].x - poly[next].x);
polygons.back().push_back({0, y}); 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; const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1;
for (const auto& poly : polygons) { for (const auto& poly : polygons) {
std::size_t nPosVerts = 0; size_t nPosVerts = 0;
std::size_t nRevolveAxisVerts = 0; size_t nRevolveAxisVerts = 0;
for (auto& pt : poly) { for (auto& pt : poly) {
if (pt.x > 0) { if (pt.x > 0) {
nPosVerts++; nPosVerts++;
@ -420,7 +440,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments,
pImpl_->CreateHalfedges(triVertsDH); pImpl_->CreateHalfedges(triVertsDH);
pImpl_->Finish(); pImpl_->Finish();
pImpl_->InitializeOriginal(); pImpl_->InitializeOriginal();
pImpl_->CreateFaces(); pImpl_->MarkCoplanar();
return Manifold(pImpl_); return Manifold(pImpl_);
} }

View File

@ -376,6 +376,16 @@ CrossSection CrossSection::BatchBoolean(
return crossSections[0]; return crossSections[0];
auto subjs = crossSections[0].GetPaths(); 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; int n_clips = 0;
for (size_t i = 1; i < crossSections.size(); ++i) { for (size_t i = 1; i < crossSections.size(); ++i) {
n_clips += crossSections[i].GetPaths()->paths_.size(); 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 * Construct a CrossSection from a vector of other CrossSections (batch
* boolean union). * boolean union).
*/ */
CrossSection CrossSection::Compose(std::vector<CrossSection>& crossSections) { CrossSection CrossSection::Compose(
const std::vector<CrossSection>& crossSections) {
return BatchBoolean(crossSections, OpType::Add); 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 * Mirror this CrossSection over the arbitrary axis whose normal is described by
* of the given vector. If the length of the vector is zero, an empty * the unit form of the given vector. If the length of the vector is zero, an
* CrossSection is returned. This operation can be chained. Transforms are * empty CrossSection is returned. This operation can be chained. Transforms are
* combined and applied lazily. * combined and applied lazily.
* *
* @param ax the axis to be mirrored over * @param ax the axis to be mirrored over
@ -529,7 +540,7 @@ CrossSection CrossSection::Mirror(const vec2 ax) const {
if (la::length(ax) == 0.) { if (la::length(ax) == 0.) {
return CrossSection(); 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)); auto m = mat2x3(mat2(la::identity) - 2.0 * la::outerprod(n, n), vec2(0.0));
return Transform(m); return Transform(m);
} }
@ -641,7 +652,7 @@ CrossSection CrossSection::Simplify(double epsilon) const {
* to expand, and retraction of inner (hole) contours. Negative deltas will * to expand, and retraction of inner (hole) contours. Negative deltas will
* have the opposite effect. * have the opposite effect.
* @param jointype The join type specifying the treatment of contour joins * @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 * @param miter_limit The maximum distance in multiples of delta that vertices
* can be offset from their original positions with before squaring is * can be offset from their original positions with before squaring is
* applied, <B>when the join type is Miter</B> (default is 2, which is the * applied, <B>when the join type is Miter</B> (default is 2, which is the

View File

@ -12,27 +12,23 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_priority_queue.h>) #if MANIFOLD_PAR == 1
#include <tbb/tbb.h> #include <tbb/tbb.h>
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
#include <tbb/concurrent_priority_queue.h>
#endif #endif
#include <algorithm> #include <algorithm>
#include "./boolean3.h" #include "boolean3.h"
#include "./csg_tree.h" #include "csg_tree.h"
#include "./impl.h" #include "impl.h"
#include "./mesh_fixes.h" #include "mesh_fixes.h"
#include "./parallel.h" #include "parallel.h"
constexpr int kParallelThreshold = 4096;
namespace { namespace {
using namespace manifold; using namespace manifold;
struct MeshCompare { struct MeshCompare {
bool operator()(const std::shared_ptr<CsgLeafNode> &a, bool operator()(const std::shared_ptr<CsgLeafNode>& a,
const std::shared_ptr<CsgLeafNode> &b) { const std::shared_ptr<CsgLeafNode>& b) {
return a->GetImpl()->NumVert() < b->GetImpl()->NumVert(); return a->GetImpl()->NumVert() < b->GetImpl()->NumVert();
} }
}; };
@ -41,7 +37,7 @@ struct MeshCompare {
namespace manifold { namespace manifold {
std::shared_ptr<CsgNode> CsgNode::Boolean( std::shared_ptr<CsgNode> CsgNode::Boolean(
const std::shared_ptr<CsgNode> &second, OpType op) { const std::shared_ptr<CsgNode>& second, OpType op) {
if (second->GetNodeType() != CsgNodeType::Leaf) { if (second->GetNodeType() != CsgNodeType::Leaf) {
// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is // "this" is not a CsgOpNode (which overrides Boolean), but if "second" is
// and the operation is commutative, we let it built the tree. // and the operation is commutative, we let it built the tree.
@ -54,13 +50,13 @@ std::shared_ptr<CsgNode> CsgNode::Boolean(
return std::make_shared<CsgOpNode>(children, op); return std::make_shared<CsgOpNode>(children, op);
} }
std::shared_ptr<CsgNode> CsgNode::Translate(const vec3 &t) const { std::shared_ptr<CsgNode> CsgNode::Translate(const vec3& t) const {
mat3x4 transform = la::identity; mat3x4 transform = la::identity;
transform[3] += t; transform[3] += t;
return Transform(transform); return Transform(transform);
} }
std::shared_ptr<CsgNode> CsgNode::Scale(const vec3 &v) const { std::shared_ptr<CsgNode> CsgNode::Scale(const vec3& v) const {
mat3x4 transform; mat3x4 transform;
for (int i : {0, 1, 2}) transform[i][i] = v[i]; for (int i : {0, 1, 2}) transform[i][i] = v[i];
return Transform(transform); return Transform(transform);
@ -102,18 +98,18 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::ToLeafNode() const {
return std::make_shared<CsgLeafNode>(*this); return std::make_shared<CsgLeafNode>(*this);
} }
std::shared_ptr<CsgNode> CsgLeafNode::Transform(const mat3x4 &m) const { std::shared_ptr<CsgNode> CsgLeafNode::Transform(const mat3x4& m) const {
return std::make_shared<CsgLeafNode>(pImpl_, m * Mat4(transform_)); return std::make_shared<CsgLeafNode>(pImpl_, m * Mat4(transform_));
} }
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; } CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }
std::shared_ptr<CsgLeafNode> ImplToLeaf(Manifold::Impl &&impl) { std::shared_ptr<CsgLeafNode> ImplToLeaf(Manifold::Impl&& impl) {
return std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(impl)); return std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(impl));
} }
std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl &a, std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl& a,
const Manifold::Impl &b, OpType op) { const Manifold::Impl& b, OpType op) {
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
auto dump = [&]() { auto dump = [&]() {
dump_lock.lock(); dump_lock.lock();
@ -121,6 +117,7 @@ std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl &a,
<< std::endl; << std::endl;
std::cout << "RHS self-intersecting: " << b.IsSelfIntersecting() std::cout << "RHS self-intersecting: " << b.IsSelfIntersecting()
<< std::endl; << std::endl;
#ifdef MANIFOLD_EXPORT
if (ManifoldParams().verbose) { if (ManifoldParams().verbose) {
if (op == OpType::Add) if (op == OpType::Add)
std::cout << "Add"; std::cout << "Add";
@ -132,22 +129,23 @@ std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl &a,
std::cout << a; std::cout << a;
std::cout << b; std::cout << b;
} }
#endif
dump_lock.unlock(); dump_lock.unlock();
}; };
try { try {
Boolean3 boolean(a, b, op); Boolean3 boolean(a, b, op);
auto impl = boolean.Result(op); auto impl = boolean.Result(op);
if (ManifoldParams().intermediateChecks && impl.IsSelfIntersecting()) { if (ManifoldParams().selfIntersectionChecks && impl.IsSelfIntersecting()) {
dump_lock.lock(); dump_lock.lock();
std::cout << "self intersections detected" << std::endl; std::cout << "self intersections detected" << std::endl;
dump_lock.unlock(); dump_lock.unlock();
throw logicErr("self intersection detected"); throw logicErr("self intersection detected");
} }
return ImplToLeaf(std::move(impl)); return ImplToLeaf(std::move(impl));
} catch (logicErr &err) { } catch (logicErr& err) {
dump(); dump();
throw err; throw err;
} catch (geometryErr &err) { } catch (geometryErr& err) {
dump(); dump();
throw err; throw err;
} }
@ -160,7 +158,7 @@ std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl &a,
* Efficient union of a set of pairwise disjoint meshes. * Efficient union of a set of pairwise disjoint meshes.
*/ */
std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose( std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
const std::vector<std::shared_ptr<CsgLeafNode>> &nodes) { const std::vector<std::shared_ptr<CsgLeafNode>>& nodes) {
ZoneScoped; ZoneScoped;
double epsilon = -1; double epsilon = -1;
double tolerance = -1; double tolerance = -1;
@ -173,7 +171,7 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
std::vector<int> triIndices; std::vector<int> triIndices;
std::vector<int> propVertIndices; std::vector<int> propVertIndices;
int numPropOut = 0; int numPropOut = 0;
for (auto &node : nodes) { for (auto& node : nodes) {
if (node->pImpl_->status_ != Manifold::Error::NoError) { if (node->pImpl_->status_ != Manifold::Error::NoError) {
Manifold::Impl impl; Manifold::Impl impl;
impl.status_ = node->pImpl_->status_; impl.status_ = node->pImpl_->status_;
@ -199,22 +197,20 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
const int numProp = node->pImpl_->NumProp(); const int numProp = node->pImpl_->NumProp();
numPropOut = std::max(numPropOut, numProp); numPropOut = std::max(numPropOut, numProp);
numPropVert += numPropVert +=
numProp == 0 ? 1 numProp == 0 ? 1 : node->pImpl_->properties_.size() / numProp;
: node->pImpl_->meshRelation_.properties.size() / numProp;
} }
Manifold::Impl combined; Manifold::Impl combined;
combined.epsilon_ = epsilon; combined.epsilon_ = epsilon;
combined.tolerance_ = tolerance; combined.tolerance_ = tolerance;
combined.vertPos_.resize(numVert); combined.vertPos_.resize_nofill(numVert);
combined.halfedge_.resize(2 * numEdge); combined.halfedge_.resize_nofill(2 * numEdge);
combined.faceNormal_.resize(numTri); combined.faceNormal_.resize_nofill(numTri);
combined.halfedgeTangent_.resize(2 * numEdge); combined.halfedgeTangent_.resize(2 * numEdge);
combined.meshRelation_.triRef.resize(numTri); combined.meshRelation_.triRef.resize_nofill(numTri);
if (numPropOut > 0) { if (numPropOut > 0) {
combined.meshRelation_.numProp = numPropOut; combined.numProp_ = numPropOut;
combined.meshRelation_.properties.resize(numPropOut * numPropVert, 0); combined.properties_.resize(numPropOut * numPropVert, 0);
combined.meshRelation_.triProperties.resize(numTri);
} }
auto policy = autoPolicy(numTri); auto policy = autoPolicy(numTri);
@ -228,50 +224,35 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
countAt(0), nodes.size(), countAt(0), nodes.size(),
[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices, [&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,
numPropOut, &combined, policy](int i) { numPropOut, &combined, policy](int i) {
auto &node = nodes[i]; auto& node = nodes[i];
copy(node->pImpl_->halfedgeTangent_.begin(), copy(node->pImpl_->halfedgeTangent_.begin(),
node->pImpl_->halfedgeTangent_.end(), node->pImpl_->halfedgeTangent_.end(),
combined.halfedgeTangent_.begin() + edgeIndices[i]); combined.halfedgeTangent_.begin() + edgeIndices[i]);
const int nextVert = vertIndices[i]; const int nextVert = vertIndices[i];
const int nextEdge = edgeIndices[i]; const int nextEdge = edgeIndices[i];
const int nextFace = triIndices[i]; const int nextProp = propVertIndices[i];
transform(node->pImpl_->halfedge_.begin(), transform(node->pImpl_->halfedge_.begin(),
node->pImpl_->halfedge_.end(), node->pImpl_->halfedge_.end(),
combined.halfedge_.begin() + edgeIndices[i], combined.halfedge_.begin() + edgeIndices[i],
[nextVert, nextEdge, nextFace](Halfedge edge) { [nextVert, nextEdge, nextProp](Halfedge edge) {
edge.startVert += nextVert; edge.startVert += nextVert;
edge.endVert += nextVert; edge.endVert += nextVert;
edge.pairedHalfedge += nextEdge; edge.pairedHalfedge += nextEdge;
edge.propVert += nextProp;
return edge; return edge;
}); });
if (numPropOut > 0) { if (node->pImpl_->NumProp() > 0) {
auto start = const int numProp = node->pImpl_->NumProp();
combined.meshRelation_.triProperties.begin() + triIndices[i]; auto& oldProp = node->pImpl_->properties_;
if (node->pImpl_->NumProp() > 0) { auto& newProp = combined.properties_;
auto &triProp = node->pImpl_->meshRelation_.triProperties; for (int p = 0; p < numProp; ++p) {
const int nextProp = propVertIndices[i]; auto oldRange =
transform(triProp.begin(), triProp.end(), start, StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp);
[nextProp](ivec3 tri) { auto newRange = StridedRange(
tri += nextProp; newProp.begin() + numPropOut * propVertIndices[i] + p,
return tri; newProp.end(), numPropOut);
}); copy(oldRange.begin(), oldRange.end(), newRange.begin());
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]));
} }
} }
@ -323,16 +304,16 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
}); });
for (size_t i = 0; i < nodes.size(); i++) { for (size_t i = 0; i < nodes.size(); i++) {
auto &node = nodes[i]; auto& node = nodes[i];
const int offset = i * Manifold::Impl::meshIDCounter_; 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; combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;
} }
} }
// required to remove parts that are smaller than the tolerance // required to remove parts that are smaller than the tolerance
combined.SimplifyTopology(); combined.RemoveDegenerates();
combined.Finish(); combined.Finish();
combined.IncrementMeshIDs(); combined.IncrementMeshIDs();
return ImplToLeaf(std::move(combined)); return ImplToLeaf(std::move(combined));
@ -343,7 +324,7 @@ std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
* operation. Only supports union and intersection. * operation. Only supports union and intersection.
*/ */
std::shared_ptr<CsgLeafNode> BatchBoolean( std::shared_ptr<CsgLeafNode> BatchBoolean(
OpType operation, std::vector<std::shared_ptr<CsgLeafNode>> &results) { OpType operation, std::vector<std::shared_ptr<CsgLeafNode>>& results) {
ZoneScoped; ZoneScoped;
DEBUG_ASSERT(operation != OpType::Subtract, logicErr, DEBUG_ASSERT(operation != OpType::Subtract, logicErr,
"BatchBoolean doesn't support Difference."); "BatchBoolean doesn't support Difference.");
@ -353,50 +334,44 @@ std::shared_ptr<CsgLeafNode> BatchBoolean(
if (results.size() == 2) if (results.size() == 2)
return SimpleBoolean(*results[0]->GetImpl(), *results[1]->GetImpl(), return SimpleBoolean(*results[0]->GetImpl(), *results[1]->GetImpl(),
operation); operation);
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>)
tbb::task_group group;
tbb::concurrent_priority_queue<std::shared_ptr<CsgLeafNode>, MeshCompare>
queue(results.size());
for (auto result : results) {
queue.emplace(result);
}
results.clear();
std::function<void()> process = [&]() {
while (queue.size() > 1) {
std::shared_ptr<CsgLeafNode> 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<CsgLeafNode> r;
queue.try_pop(r);
return r;
#endif
// apply boolean operations starting from smaller meshes // apply boolean operations starting from smaller meshes
// the assumption is that boolean operations on smaller meshes is faster, // the assumption is that boolean operations on smaller meshes is faster,
// due to less data being copied and processed // due to less data being copied and processed
auto cmpFn = MeshCompare(); auto cmpFn = MeshCompare();
std::make_heap(results.begin(), results.end(), cmpFn); std::make_heap(results.begin(), results.end(), cmpFn);
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
#if MANIFOLD_PAR == 1
tbb::task_group group;
std::mutex mutex;
#endif
while (results.size() > 1) { while (results.size() > 1) {
std::pop_heap(results.begin(), results.end(), cmpFn); for (size_t i = 0; i < 4 && results.size() > 1; i++) {
auto a = std::move(results.back()); std::pop_heap(results.begin(), results.end(), cmpFn);
results.pop_back(); auto a = std::move(results.back());
std::pop_heap(results.begin(), results.end(), cmpFn); results.pop_back();
auto b = std::move(results.back()); std::pop_heap(results.begin(), results.end(), cmpFn);
results.pop_back(); auto b = std::move(results.back());
// boolean operation results.pop_back();
auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation); #if MANIFOLD_PAR == 1
if (results.size() == 0) return result; group.run([&, a, b]() {
results.push_back(result); auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);
std::push_heap(results.begin(), results.end(), cmpFn); 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(); return results.front();
} }
@ -406,7 +381,7 @@ std::shared_ptr<CsgLeafNode> BatchBoolean(
* possible. * possible.
*/ */
std::shared_ptr<CsgLeafNode> BatchUnion( std::shared_ptr<CsgLeafNode> BatchUnion(
std::vector<std::shared_ptr<CsgLeafNode>> &children) { std::vector<std::shared_ptr<CsgLeafNode>>& children) {
ZoneScoped; ZoneScoped;
// INVARIANT: children_ is a vector of leaf nodes // INVARIANT: children_ is a vector of leaf nodes
// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check // this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check
@ -429,7 +404,7 @@ std::shared_ptr<CsgLeafNode> BatchUnion(
// each set contains a set of children that are pairwise disjoint // each set contains a set of children that are pairwise disjoint
std::vector<Vec<size_t>> disjointSets; std::vector<Vec<size_t>> disjointSets;
for (size_t i = 0; i < boxes.size(); i++) { for (size_t i = 0; i < boxes.size(); i++) {
auto lambda = [&boxes, i](const Vec<size_t> &set) { auto lambda = [&boxes, i](const Vec<size_t>& set) {
return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) { return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) {
return boxes[i].DoesOverlap(boxes[j]); return boxes[i].DoesOverlap(boxes[j]);
}) == set.end(); }) == set.end();
@ -443,7 +418,7 @@ std::shared_ptr<CsgLeafNode> BatchUnion(
} }
// compose each set of disjoint children // compose each set of disjoint children
std::vector<std::shared_ptr<CsgLeafNode>> impls; std::vector<std::shared_ptr<CsgLeafNode>> impls;
for (auto &set : disjointSets) { for (auto& set : disjointSets) {
if (set.size() == 1) { if (set.size() == 1) {
impls.push_back(children[start + set[0]]); impls.push_back(children[start + set[0]]);
} else { } else {
@ -466,7 +441,7 @@ std::shared_ptr<CsgLeafNode> BatchUnion(
CsgOpNode::CsgOpNode() {} CsgOpNode::CsgOpNode() {}
CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children, CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children,
OpType op) OpType op)
: impl_(children), op_(op) {} : impl_(children), op_(op) {}
@ -475,7 +450,7 @@ CsgOpNode::~CsgOpNode() {
auto impl = impl_.GetGuard(); auto impl = impl_.GetGuard();
std::vector<std::shared_ptr<CsgOpNode>> toProcess; std::vector<std::shared_ptr<CsgOpNode>> toProcess;
auto handleChildren = auto handleChildren =
[&toProcess](std::vector<std::shared_ptr<CsgNode>> &children) { [&toProcess](std::vector<std::shared_ptr<CsgNode>>& children) {
while (!children.empty()) { while (!children.empty()) {
// move out so shrinking the vector will not trigger recursive drop // move out so shrinking the vector will not trigger recursive drop
auto movedChild = std::move(children.back()); auto movedChild = std::move(children.back());
@ -498,7 +473,7 @@ CsgOpNode::~CsgOpNode() {
} }
std::shared_ptr<CsgNode> CsgOpNode::Boolean( std::shared_ptr<CsgNode> CsgOpNode::Boolean(
const std::shared_ptr<CsgNode> &second, OpType op) { const std::shared_ptr<CsgNode>& second, OpType op) {
std::vector<std::shared_ptr<CsgNode>> children; std::vector<std::shared_ptr<CsgNode>> children;
children.push_back(shared_from_this()); children.push_back(shared_from_this());
children.push_back(second); children.push_back(second);
@ -506,7 +481,7 @@ std::shared_ptr<CsgNode> CsgOpNode::Boolean(
return std::make_shared<CsgOpNode>(children, op); return std::make_shared<CsgOpNode>(children, op);
} }
std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4 &m) const { std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4& m) const {
auto node = std::make_shared<CsgOpNode>(); auto node = std::make_shared<CsgOpNode>();
node->impl_ = impl_; node->impl_ = impl_;
node->transform_ = m * Mat4(transform_); node->transform_ = m * Mat4(transform_);
@ -520,14 +495,14 @@ struct CsgStackFrame {
bool finalize; bool finalize;
OpType parent_op; OpType parent_op;
mat3x4 transform; mat3x4 transform;
Nodes *positive_dest; Nodes* positive_dest;
Nodes *negative_dest; Nodes* negative_dest;
std::shared_ptr<const CsgOpNode> op_node; std::shared_ptr<const CsgOpNode> op_node;
Nodes positive_children; Nodes positive_children;
Nodes negative_children; Nodes negative_children;
CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform, CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform,
Nodes *positive_dest, Nodes *negative_dest, Nodes* positive_dest, Nodes* negative_dest,
std::shared_ptr<const CsgOpNode> op_node) std::shared_ptr<const CsgOpNode> op_node)
: finalize(finalize), : finalize(finalize),
parent_op(parent_op), parent_op(parent_op),
@ -675,8 +650,8 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
stack.pop_back(); stack.pop_back();
} else { } else {
auto add_children = auto add_children =
[&stack](std::shared_ptr<CsgNode> &node, OpType op, mat3x4 transform, [&stack](std::shared_ptr<CsgNode>& node, OpType op, mat3x4 transform,
CsgStackFrame::Nodes *dest1, CsgStackFrame::Nodes *dest2) { CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) {
if (node->GetNodeType() == CsgNodeType::Leaf) if (node->GetNodeType() == CsgNodeType::Leaf)
dest1->push_back(std::static_pointer_cast<CsgLeafNode>( dest1->push_back(std::static_pointer_cast<CsgLeafNode>(
node->Transform(transform))); node->Transform(transform)));
@ -702,14 +677,14 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
const mat3x4 transform = const mat3x4 transform =
canCollapse ? (frame->transform * Mat4(frame->op_node->transform_)) canCollapse ? (frame->transform * Mat4(frame->op_node->transform_))
: la::identity; : la::identity;
CsgStackFrame::Nodes *pos_dest = CsgStackFrame::Nodes* pos_dest =
canCollapse ? frame->positive_dest : &frame->positive_children; canCollapse ? frame->positive_dest : &frame->positive_children;
CsgStackFrame::Nodes *neg_dest = CsgStackFrame::Nodes* neg_dest =
canCollapse ? frame->negative_dest : &frame->negative_children; canCollapse ? frame->negative_dest : &frame->negative_children;
for (size_t i = 0; i < impl->size(); i++) { for (size_t i = 0; i < impl->size(); i++) {
const bool negative = op == OpType::Subtract && i != 0; const bool negative = op == OpType::Subtract && i != 0;
CsgStackFrame::Nodes *dest1 = negative ? neg_dest : pos_dest; CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest;
CsgStackFrame::Nodes *dest2 = CsgStackFrame::Nodes* dest2 =
(op == OpType::Subtract && i == 0) ? neg_dest : nullptr; (op == OpType::Subtract && i == 0) ? neg_dest : nullptr;
add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1, add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1,
dest2); dest2);

View File

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include "./utils.h"
#include "manifold/manifold.h" #include "manifold/manifold.h"
#include "utils.h"
namespace manifold { namespace manifold {
@ -25,14 +25,14 @@ class CsgLeafNode;
class CsgNode : public std::enable_shared_from_this<CsgNode> { class CsgNode : public std::enable_shared_from_this<CsgNode> {
public: public:
virtual std::shared_ptr<CsgLeafNode> ToLeafNode() const = 0; virtual std::shared_ptr<CsgLeafNode> ToLeafNode() const = 0;
virtual std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const = 0; virtual std::shared_ptr<CsgNode> Transform(const mat3x4& m) const = 0;
virtual CsgNodeType GetNodeType() const = 0; virtual CsgNodeType GetNodeType() const = 0;
virtual std::shared_ptr<CsgNode> Boolean( virtual std::shared_ptr<CsgNode> Boolean(
const std::shared_ptr<CsgNode> &second, OpType op); const std::shared_ptr<CsgNode>& second, OpType op);
std::shared_ptr<CsgNode> Translate(const vec3 &t) const; std::shared_ptr<CsgNode> Translate(const vec3& t) const;
std::shared_ptr<CsgNode> Scale(const vec3 &s) const; std::shared_ptr<CsgNode> Scale(const vec3& s) const;
std::shared_ptr<CsgNode> Rotate(double xDegrees = 0, double yDegrees = 0, std::shared_ptr<CsgNode> Rotate(double xDegrees = 0, double yDegrees = 0,
double zDegrees = 0) const; double zDegrees = 0) const;
}; };
@ -47,12 +47,12 @@ class CsgLeafNode final : public CsgNode {
std::shared_ptr<CsgLeafNode> ToLeafNode() const override; std::shared_ptr<CsgLeafNode> ToLeafNode() const override;
std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const override; std::shared_ptr<CsgNode> Transform(const mat3x4& m) const override;
CsgNodeType GetNodeType() const override; CsgNodeType GetNodeType() const override;
static std::shared_ptr<CsgLeafNode> Compose( static std::shared_ptr<CsgLeafNode> Compose(
const std::vector<std::shared_ptr<CsgLeafNode>> &nodes); const std::vector<std::shared_ptr<CsgLeafNode>>& nodes);
private: private:
mutable std::shared_ptr<const Manifold::Impl> pImpl_; mutable std::shared_ptr<const Manifold::Impl> pImpl_;
@ -63,12 +63,12 @@ class CsgOpNode final : public CsgNode {
public: public:
CsgOpNode(); CsgOpNode();
CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children, OpType op); CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children, OpType op);
std::shared_ptr<CsgNode> Boolean(const std::shared_ptr<CsgNode> &second, std::shared_ptr<CsgNode> Boolean(const std::shared_ptr<CsgNode>& second,
OpType op) override; OpType op) override;
std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const override; std::shared_ptr<CsgNode> Transform(const mat3x4& m) const override;
std::shared_ptr<CsgLeafNode> ToLeafNode() const override; std::shared_ptr<CsgLeafNode> ToLeafNode() const override;

View File

@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./impl.h" #include <unordered_map>
#include "./parallel.h"
#include "impl.h"
#include "parallel.h"
#include "shared.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
@ -47,33 +50,46 @@ struct DuplicateEdge {
struct ShortEdge { struct ShortEdge {
VecView<const Halfedge> halfedge; VecView<const Halfedge> halfedge;
VecView<const vec3> vertPos; VecView<const vec3> vertPos;
const double tolerance; const double epsilon;
const int firstNewVert;
bool operator()(int edge) const { 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 // Flag short edges
const vec3 delta = const vec3 delta = vertPos[half.endVert] - vertPos[half.startVert];
vertPos[halfedge[edge].endVert] - vertPos[halfedge[edge].startVert]; return la::dot(delta, delta) < epsilon * epsilon;
return la::dot(delta, delta) < tolerance * tolerance;
} }
}; };
struct FlagEdge { struct FlagEdge {
VecView<const Halfedge> halfedge; VecView<const Halfedge> halfedge;
VecView<const TriRef> triRef; VecView<const TriRef> triRef;
const int firstNewVert;
bool operator()(int edge) const { 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 // Flag redundant edges - those where the startVert is surrounded by only
// two original triangles. // two original triangles.
const TriRef ref0 = triRef[edge / 3]; const TriRef ref0 = triRef[edge / 3];
int current = NextHalfedge(halfedge[edge].pairedHalfedge); int current = NextHalfedge(half.pairedHalfedge);
const TriRef ref1 = triRef[current / 3]; TriRef ref1 = triRef[current / 3];
bool ref1Updated = !ref0.SameFace(ref1);
while (current != edge) { while (current != edge) {
current = NextHalfedge(halfedge[current].pairedHalfedge); current = NextHalfedge(halfedge[current].pairedHalfedge);
int tri = current / 3; int tri = current / 3;
const TriRef ref = triRef[tri]; 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; return true;
} }
@ -84,9 +100,15 @@ struct SwappableEdge {
VecView<const vec3> vertPos; VecView<const vec3> vertPos;
VecView<const vec3> triNormal; VecView<const vec3> triNormal;
const double tolerance; const double tolerance;
const int firstNewVert;
bool operator()(int edge) const { 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; int tri = edge / 3;
ivec3 triEdge = TriOf(edge); ivec3 triEdge = TriOf(edge);
@ -98,7 +120,7 @@ struct SwappableEdge {
return false; return false;
// Switch to neighbor's projection. // Switch to neighbor's projection.
edge = halfedge[edge].pairedHalfedge; edge = half.pairedHalfedge;
tri = edge / 3; tri = edge / 3;
triEdge = TriOf(edge); triEdge = TriOf(edge);
projection = GetAxisAlignedProjection(triNormal[tri]); projection = GetAxisAlignedProjection(triNormal[tri]);
@ -109,14 +131,67 @@ struct SwappableEdge {
} }
}; };
struct SortEntry { struct FlagStore {
int start; #if MANIFOLD_PAR == 1
int end; tbb::combinable<std::vector<size_t>> store;
size_t index; #endif
inline bool operator<(const SortEntry& other) const { std::vector<size_t> s;
return start == other.start ? end < other.end : start < other.start;
template <typename Pred, typename F>
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 <typename Pred, typename F>
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<size_t>(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<std::vector<size_t>> stores;
std::vector<size_t> result;
store.combine_each(
[&](auto& data) { stores.emplace_back(std::move(data)); });
std::vector<size_t> 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 <typename Pred, typename F>
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
namespace manifold { namespace manifold {
@ -131,41 +206,7 @@ void Manifold::Impl::CleanupTopology() {
// In the case of a very bad triangulation, it is possible to create pinched // In the case of a very bad triangulation, it is possible to create pinched
// verts. They must be removed before edge collapse. // verts. They must be removed before edge collapse.
SplitPinchedVerts(); SplitPinchedVerts();
DedupeEdges();
while (1) {
ZoneScopedN("DedupeEdge");
const size_t nbEdges = halfedge_.size();
size_t numFlagged = 0;
Vec<SortEntry> 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
}
} }
/** /**
@ -184,95 +225,112 @@ void Manifold::Impl::CleanupTopology() {
* meshes, thus decreasing the Genus(). It only increases when meshes that have * meshes, thus decreasing the Genus(). It only increases when meshes that have
* collapsed to just a pair of triangles are removed entirely. * 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 * 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}. * 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; if (!halfedge_.size()) return;
CleanupTopology(); CleanupTopology();
CollapseShortEdges(firstNewVert);
CollapseColinearEdges(firstNewVert);
SwapDegenerates(firstNewVert);
}
if (!ManifoldParams().cleanupTriangles) { void Manifold::Impl::RemoveDegenerates(int firstNewVert) {
return; if (!halfedge_.size()) return;
}
const size_t nbEdges = halfedge_.size(); CleanupTopology();
auto policy = autoPolicy(nbEdges, 1e5); CollapseShortEdges(firstNewVert);
SwapDegenerates(firstNewVert);
}
void Manifold::Impl::CollapseShortEdges(int firstNewVert) {
ZoneScopedN("CollapseShortEdge");
FlagStore s;
size_t numFlagged = 0; size_t numFlagged = 0;
Vec<uint8_t> bFlags(nbEdges); const size_t nbEdges = halfedge_.size();
std::vector<int> scratchBuffer; std::vector<int> scratchBuffer;
scratchBuffer.reserve(10); scratchBuffer.reserve(10);
{ // Short edges get to skip several checks and hence remove more classes of
ZoneScopedN("CollapseShortEdge"); // degenerate triangles than flagged edges do, but this could in theory lead
numFlagged = 0; // to error stacking where a vertex moves too far. For this reason this is
ShortEdge se{halfedge_, vertPos_, epsilon_}; // restricted to epsilon, rather than tolerance.
for_each_n(policy, countAt(0_uz), nbEdges, ShortEdge se{halfedge_, vertPos_, epsilon_, firstNewVert};
[&](size_t i) { bFlags[i] = se(i); }); s.run(nbEdges, se, [&](size_t i) {
for (size_t i = 0; i < nbEdges; ++i) { const bool didCollapse = CollapseEdge(i, scratchBuffer);
if (bFlags[i]) { if (didCollapse) numFlagged++;
CollapseEdge(i, scratchBuffer); scratchBuffer.resize(0);
scratchBuffer.resize(0); });
numFlagged++;
}
}
}
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (ManifoldParams().verbose && numFlagged > 0) { if (ManifoldParams().verbose > 0 && numFlagged > 0) {
std::cout << "found " << numFlagged << " short edges to collapse" std::cout << "collapsed " << numFlagged << " short edges" << std::endl;
<< std::endl;
} }
#endif #endif
}
{ void Manifold::Impl::CollapseColinearEdges(int firstNewVert) {
FlagStore s;
size_t numFlagged = 0;
const size_t nbEdges = halfedge_.size();
std::vector<int> scratchBuffer;
scratchBuffer.reserve(10);
while (1) {
ZoneScopedN("CollapseFlaggedEdge"); ZoneScopedN("CollapseFlaggedEdge");
numFlagged = 0; numFlagged = 0;
FlagEdge se{halfedge_, meshRelation_.triRef}; // Collapse colinear edges, but only remove new verts, i.e. verts with
for_each_n(policy, countAt(0_uz), nbEdges, // index
[&](size_t i) { bFlags[i] = se(i); }); // >= firstNewVert. This is used to keep the Boolean from changing the
for (size_t i = 0; i < nbEdges; ++i) { // non-intersecting parts of the input meshes. Colinear is defined not by a
if (bFlags[i]) { // local check, but by the global MarkCoplanar function, which keeps this
CollapseEdge(i, scratchBuffer); // from being vulnerable to error stacking.
scratchBuffer.resize(0); FlagEdge se{halfedge_, meshRelation_.triRef, firstNewVert};
numFlagged++; 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 #ifdef MANIFOLD_DEBUG
if (ManifoldParams().verbose && numFlagged > 0) { if (ManifoldParams().verbose > 0 && numFlagged > 0) {
std::cout << "found " << numFlagged << " colinear edges to collapse" std::cout << "collapsed " << numFlagged << " colinear edges" << std::endl;
<< std::endl; }
}
#endif #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<int> edgeSwapStack;
std::vector<int> 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<int> scratchBuffer;
scratchBuffer.reserve(10);
SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_, firstNewVert};
std::vector<int> edgeSwapStack;
std::vector<int> 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 #ifdef MANIFOLD_DEBUG
if (ManifoldParams().verbose && numFlagged > 0) { if (ManifoldParams().verbose > 0 && numFlagged > 0) {
std::cout << "found " << numFlagged << " edges to swap" << std::endl; std::cout << "swapped " << numFlagged << " edges" << std::endl;
} }
#endif #endif
} }
@ -283,6 +341,7 @@ void Manifold::Impl::DedupeEdge(const int edge) {
// Orbit endVert // Orbit endVert
const int startVert = halfedge_[edge].startVert; const int startVert = halfedge_[edge].startVert;
const int endVert = halfedge_[edge].endVert; const int endVert = halfedge_[edge].endVert;
const int endProp = halfedge_[NextHalfedge(edge)].propVert;
int current = halfedge_[NextHalfedge(edge)].pairedHalfedge; int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;
while (current != edge) { while (current != edge) {
const int vert = halfedge_[current].startVert; const int vert = halfedge_[current].startVert;
@ -297,36 +356,30 @@ void Manifold::Impl::DedupeEdge(const int edge) {
UpdateVert(newVert, current, opposite); UpdateVert(newVert, current, opposite);
int newHalfedge = halfedge_.size(); int newHalfedge = halfedge_.size();
int newFace = newHalfedge / 3;
int oldFace = current / 3; int oldFace = current / 3;
int outsideVert = halfedge_[current].startVert; int outsideVert = halfedge_[current].startVert;
halfedge_.push_back({endVert, newVert, -1}); halfedge_.push_back({endVert, newVert, -1, endProp});
halfedge_.push_back({newVert, outsideVert, -1}); halfedge_.push_back({newVert, outsideVert, -1, endProp});
halfedge_.push_back({outsideVert, endVert, -1}); halfedge_.push_back(
{outsideVert, endVert, -1, halfedge_[current].propVert});
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge); PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
PairUp(newHalfedge + 1, current); PairUp(newHalfedge + 1, current);
if (meshRelation_.triRef.size() > 0) if (meshRelation_.triRef.size() > 0)
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]); 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]); if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
newHalfedge += 3; newHalfedge += 3;
++newFace;
oldFace = opposite / 3; oldFace = opposite / 3;
outsideVert = halfedge_[opposite].startVert; outsideVert = halfedge_[opposite].startVert;
halfedge_.push_back({newVert, endVert, -1}); halfedge_.push_back({newVert, endVert, -1, endProp}); // fix prop
halfedge_.push_back({endVert, outsideVert, -1}); halfedge_.push_back({endVert, outsideVert, -1, endProp});
halfedge_.push_back({outsideVert, newVert, -1}); halfedge_.push_back(
{outsideVert, newVert, -1, halfedge_[opposite].propVert});
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge); PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
PairUp(newHalfedge + 1, opposite); PairUp(newHalfedge + 1, opposite);
PairUp(newHalfedge, newHalfedge - 3); PairUp(newHalfedge, newHalfedge - 3);
if (meshRelation_.triRef.size() > 0) if (meshRelation_.triRef.size() > 0)
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]); 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]); if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
break; break;
@ -420,7 +473,7 @@ void Manifold::Impl::CollapseTri(const ivec3& triEdge) {
halfedge_[pair1].pairedHalfedge = pair2; halfedge_[pair1].pairedHalfedge = pair2;
halfedge_[pair2].pairedHalfedge = pair1; halfedge_[pair2].pairedHalfedge = pair1;
for (int i : {0, 1, 2}) { 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 // Collapses the given edge by removing startVert - returns false if the edge
// topologically if the collapse would have resulted in a 4-manifold edge. Do // cannot be collapsed. May split the mesh topologically if the collapse would
// not collapse an edge if startVert is pinched - the vert will be marked NaN, // have resulted in a 4-manifold edge. Do not collapse an edge if startVert is
// but other edges may still be pointing to it. // pinched - the vert would be marked NaN, but other edges could still be
void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) { // pointing to it.
bool Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
Vec<TriRef>& triRef = meshRelation_.triRef; Vec<TriRef>& triRef = meshRelation_.triRef;
Vec<ivec3>& triProp = meshRelation_.triProperties;
const Halfedge toRemove = halfedge_[edge]; const Halfedge toRemove = halfedge_[edge];
if (toRemove.pairedHalfedge < 0) return; if (toRemove.pairedHalfedge < 0) return false;
const int endVert = toRemove.endVert; const int endVert = toRemove.endVert;
const ivec3 tri0edge = TriOf(edge); const ivec3 tri0edge = TriOf(edge);
@ -470,23 +523,16 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
const vec3 pNew = vertPos_[endVert]; const vec3 pNew = vertPos_[endVert];
const vec3 pOld = vertPos_[toRemove.startVert]; const vec3 pOld = vertPos_[toRemove.startVert];
const vec3 delta = pNew - pOld; const vec3 delta = pNew - pOld;
const bool shortEdge = la::dot(delta, delta) < tolerance_ * tolerance_; const bool shortEdge = la::dot(delta, delta) < epsilon_ * epsilon_;
// Orbit endVert
int current = halfedge_[tri0edge[1]].pairedHalfedge;
while (current != tri1edge[2]) {
current = NextHalfedge(current);
edges.push_back(current);
current = halfedge_[current].pairedHalfedge;
}
// Orbit startVert // Orbit startVert
int start = halfedge_[tri1edge[1]].pairedHalfedge; int start = halfedge_[tri1edge[1]].pairedHalfedge;
int current = tri1edge[2];
if (!shortEdge) { if (!shortEdge) {
current = start; current = start;
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3]; TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert]; vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
while (current != tri0edge[2]) { while (current != tri1edge[0]) {
current = NextHalfedge(current); current = NextHalfedge(current);
vec3 pNext = vertPos_[halfedge_[current].endVert]; vec3 pNext = vertPos_[halfedge_[current].endVert];
const int tri = current / 3; const int tri = current / 3;
@ -495,28 +541,43 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
// Don't collapse if the edge is not redundant (this may have changed due // Don't collapse if the edge is not redundant (this may have changed due
// to the collapse of neighbors). // to the collapse of neighbors).
if (!ref.SameFace(refCheck)) { if (!ref.SameFace(refCheck)) {
const TriRef oldRef = refCheck;
refCheck = triRef[edge / 3]; refCheck = triRef[edge / 3];
if (!ref.SameFace(refCheck)) { if (!ref.SameFace(refCheck)) {
return; return false;
} else { }
// Don't collapse if the edges separating the faces are not colinear if (ref.meshID != oldRef.meshID || ref.faceID != oldRef.faceID ||
// (can happen when the two faces are coplanar). la::dot(faceNormal_[toRemove.pairedHalfedge / 3],
if (CCW(projection * pOld, projection * pLast, projection * pNew, 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) epsilon_) != 0)
return; return false;
} }
} }
// Don't collapse edge if it would cause a triangle to invert. // Don't collapse edge if it would cause a triangle to invert.
if (CCW(projection * pNext, projection * pLast, projection * pNew, if (CCW(projection * pNext, projection * pLast, projection * pNew,
epsilon_) < 0) epsilon_) < 0)
return; return false;
pLast = pNext; pLast = pNext;
current = halfedge_[current].pairedHalfedge; 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. // Remove toRemove.startVert and replace with endVert.
vertPos_[toRemove.startVert] = vec3(NAN); vertPos_[toRemove.startVert] = vec3(NAN);
CollapseTri(tri1edge); CollapseTri(tri1edge);
@ -524,20 +585,18 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
// Orbit startVert // Orbit startVert
const int tri0 = edge / 3; const int tri0 = edge / 3;
const int tri1 = toRemove.pairedHalfedge / 3; const int tri1 = toRemove.pairedHalfedge / 3;
const int triVert0 = (edge + 1) % 3;
const int triVert1 = toRemove.pairedHalfedge % 3;
current = start; current = start;
while (current != tri0edge[2]) { while (current != tri0edge[2]) {
current = NextHalfedge(current); current = NextHalfedge(current);
if (triProp.size() > 0) { if (NumProp() > 0) {
// Update the shifted triangles to the vertBary of endVert // Update the shifted triangles to the vertBary of endVert
const int tri = current / 3; const int tri = current / 3;
const int vIdx = current - 3 * tri;
if (triRef[tri].SameFace(triRef[tri0])) { 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])) { } 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<int>& edges) {
UpdateVert(endVert, start, tri0edge[2]); UpdateVert(endVert, start, tri0edge[2]);
CollapseTri(tri0edge); CollapseTri(tri0edge);
RemoveIfFolded(start); RemoveIfFolded(start);
return true;
} }
void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag, 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 tri0edge = TriOf(edge);
const ivec3 tri1edge = TriOf(pair); const ivec3 tri1edge = TriOf(pair);
const ivec3 perm0 = TriOf(edge % 3);
const ivec3 perm1 = TriOf(pair % 3);
mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]); mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]);
vec2 v[4]; 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 l02 = la::length(v[2] - v[0]);
const double a = std::max(0.0, std::min(1.0, l02 / l01)); const double a = std::max(0.0, std::min(1.0, l02 / l01));
// Update properties if applicable // Update properties if applicable
if (meshRelation_.properties.size() > 0) { if (properties_.size() > 0) {
Vec<ivec3>& triProp = meshRelation_.triProperties; Vec<double>& prop = properties_;
Vec<double>& prop = meshRelation_.properties; halfedge_[tri0edge[1]].propVert = halfedge_[tri1edge[0]].propVert;
triProp[tri0] = triProp[tri1]; halfedge_[tri0edge[0]].propVert = halfedge_[tri1edge[2]].propVert;
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]]; halfedge_[tri0edge[2]].propVert = halfedge_[tri1edge[2]].propVert;
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
const int numProp = NumProp(); const int numProp = NumProp();
const int newProp = prop.size() / numProp; const int newProp = prop.size() / numProp;
const int propIdx0 = triProp[tri1][perm1[0]]; const int propIdx0 = halfedge_[tri1edge[0]].propVert;
const int propIdx1 = triProp[tri1][perm1[1]]; const int propIdx1 = halfedge_[tri1edge[1]].propVert;
for (int p = 0; p < numProp; ++p) { for (int p = 0; p < numProp; ++p) {
prop.push_back(a * prop[numProp * propIdx0 + p] + prop.push_back(a * prop[numProp * propIdx0 + p] +
(1 - a) * prop[numProp * propIdx1 + p]); (1 - a) * prop[numProp * propIdx1 + p]);
} }
triProp[tri1][perm1[0]] = newProp; halfedge_[tri1edge[0]].propVert = newProp;
triProp[tri0][perm0[2]] = newProp; halfedge_[tri0edge[2]].propVert = newProp;
} }
// if the new edge already exists, duplicate the verts and split the mesh. // 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() { void Manifold::Impl::SplitPinchedVerts() {
ZoneScoped; ZoneScoped;
std::vector<bool> vertProcessed(NumVert(), false);
std::vector<bool> halfedgeProcessed(halfedge_.size(), false); auto nbEdges = halfedge_.size();
for (size_t i = 0; i < halfedge_.size(); ++i) { #if MANIFOLD_PAR == 1
if (halfedgeProcessed[i]) continue; if (nbEdges > 1e4) {
int vert = halfedge_[i].startVert; std::mutex mutex;
if (vertProcessed[vert]) { std::vector<size_t> pinched;
vertPos_.push_back(vertPos_[vert]); // This parallelized version is non-trivial so we can't reuse the code
vert = NumVert() - 1; //
} else { // The idea here is to identify cycles of halfedges that can be iterated
vertProcessed[vert] = true; // 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<std::atomic<size_t>> largestEdge(NumVert());
for_each(ExecutionPolicy::Par, countAt(0), countAt(NumVert()),
[&largestEdge](size_t i) {
largestEdge[i].store(std::numeric_limits<size_t>::max());
});
tbb::combinable<std::vector<bool>> store(
[nbEdges]() { return std::vector<bool>(nbEdges, false); });
tbb::parallel_for(
tbb::blocked_range<size_t>(0, nbEdges),
[&store, &mutex, &pinched, &largestEdge, this](const auto& r) {
auto& local = store.local();
std::vector<size_t> 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<size_t>(current));
});
size_t expected = std::numeric_limits<size_t>::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<std::mutex> lock(mutex);
pinched.insert(pinched.end(), pinchedLocal.begin(),
pinchedLocal.end());
}
});
manifold::stable_sort(pinched.begin(), pinched.end());
std::vector<bool> 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) { } else
halfedgeProcessed[current] = true; #endif
halfedge_[current].startVert = vert; {
halfedge_[halfedge_[current].pairedHalfedge].endVert = vert; std::vector<bool> vertProcessed(NumVert(), false);
}); std::vector<bool> 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<size_t> duplicates;
auto localLoop = [&](size_t start, size_t end, std::vector<bool>& local,
std::vector<size_t>& 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<std::pair<int, int>> endVerts;
std::unordered_map<int, int> 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<int, int>& 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<int, int>& 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<std::vector<bool>> store(
[nbEdges]() { return std::vector<bool>(nbEdges, false); });
tbb::parallel_for(
tbb::blocked_range<size_t>(0, nbEdges),
[&store, &mutex, &duplicates, &localLoop](const auto& r) {
auto& local = store.local();
std::vector<size_t> duplicatesLocal;
localLoop(r.begin(), r.end(), local, duplicatesLocal);
if (!duplicatesLocal.empty()) {
std::lock_guard<std::mutex> 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<bool> 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 } // namespace manifold

View File

@ -12,16 +12,75 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <unordered_set>
#include "impl.h"
#include "manifold/common.h"
#include "manifold/polygon.h"
#include "parallel.h"
#include "shared.h"
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>) #if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)
#include <tbb/tbb.h> #include <tbb/tbb.h>
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 #define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
#include <tbb/concurrent_map.h> #include <tbb/concurrent_map.h>
#endif #endif
#include <unordered_set>
#include "./impl.h" namespace {
#include "./parallel.h" using namespace manifold;
#include "manifold/polygon.h"
/**
* 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<std::vector<int>> AssembleHalfedges(VecView<Halfedge>::IterC start,
VecView<Halfedge>::IterC end,
const int startHalfedgeIdx) {
std::multimap<int, int> vert_edge;
for (auto edge = start; edge != end; ++edge) {
vert_edge.emplace(
std::make_pair(edge->startVert, static_cast<int>(edge - start)));
}
std::vector<std::vector<int>> 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<std::vector<int>>& polys,
const Vec<Halfedge>& halfedge,
const Vec<vec3>& 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 { namespace manifold {
@ -39,83 +98,71 @@ using AddTriangle = std::function<void(int, ivec3, vec3, TriRef)>;
* faceNormal_ values are retained, repeated as necessary. * faceNormal_ values are retained, repeated as necessary.
*/ */
void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge, void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
const Vec<TriRef>& halfedgeRef) { const Vec<TriRef>& halfedgeRef,
bool allowConvex) {
ZoneScoped; ZoneScoped;
Vec<ivec3> triVerts; Vec<ivec3> triVerts;
Vec<vec3> triNormal; Vec<vec3> triNormal;
Vec<ivec3> triProp;
Vec<TriRef>& triRef = meshRelation_.triRef; Vec<TriRef>& triRef = meshRelation_.triRef;
triRef.resize(0); triRef.clear();
auto processFace = [&](GeneralTriangulation general, AddTriangle addTri, auto processFace = [&](GeneralTriangulation general, AddTriangle addTri,
int face) { int face) {
const int firstEdge = faceEdge[face]; const int firstEdge = faceEdge[face];
const int lastEdge = faceEdge[face + 1]; const int lastEdge = faceEdge[face + 1];
const int numEdge = lastEdge - firstEdge; const int numEdge = lastEdge - firstEdge;
if (numEdge == 0) return;
DEBUG_ASSERT(numEdge >= 3, topologyErr, "face has less than three edges."); DEBUG_ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
const vec3 normal = faceNormal_[face]; const vec3 normal = faceNormal_[face];
if (numEdge == 3) { // Single triangle if (numEdge == 3) { // Single triangle
int mapping[3] = {halfedge_[firstEdge].startVert, ivec3 triEdge(firstEdge, firstEdge + 1, firstEdge + 2);
halfedge_[firstEdge + 1].startVert,
halfedge_[firstEdge + 2].startVert};
ivec3 tri(halfedge_[firstEdge].startVert, ivec3 tri(halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert, halfedge_[firstEdge + 1].startVert,
halfedge_[firstEdge + 2].startVert); halfedge_[firstEdge + 2].startVert);
ivec3 ends(halfedge_[firstEdge].endVert, halfedge_[firstEdge + 1].endVert, ivec3 ends(halfedge_[firstEdge].endVert, halfedge_[firstEdge + 1].endVert,
halfedge_[firstEdge + 2].endVert); halfedge_[firstEdge + 2].endVert);
if (ends[0] == tri[2]) { if (ends[0] == tri[2]) {
std::swap(triEdge[1], triEdge[2]);
std::swap(tri[1], tri[2]); std::swap(tri[1], tri[2]);
std::swap(ends[1], ends[2]); std::swap(ends[1], ends[2]);
} }
DEBUG_ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0], DEBUG_ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0],
topologyErr, "These 3 edges do not form a triangle!"); 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 } 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); const mat2x3 projection = GetAxisAlignedProjection(normal);
auto triCCW = [&projection, this](const ivec3 tri) { auto triCCW = [&projection, this](const ivec3 tri) {
return CCW(projection * this->vertPos_[tri[0]], return CCW(projection * this->vertPos_[halfedge_[tri[0]].startVert],
projection * this->vertPos_[tri[1]], projection * this->vertPos_[halfedge_[tri[1]].startVert],
projection * this->vertPos_[tri[2]], epsilon_) >= 0; projection * this->vertPos_[halfedge_[tri[2]].startVert],
epsilon_) >= 0;
}; };
ivec3 tri0(halfedge_[firstEdge].startVert, halfedge_[firstEdge].endVert, std::vector<int> quad = AssembleHalfedges(
-1); halfedge_.cbegin() + faceEdge[face],
ivec3 tri1(-1, -1, tri0[0]); halfedge_.cbegin() + faceEdge[face + 1], faceEdge[face])[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);
if (!secondValid) { const la::mat<int, 3, 2> tris[2] = {
tri0[2] = tri1[0]; {{quad[0], quad[1], quad[2]}, {quad[0], quad[2], quad[3]}},
tri1[2] = tri0[0]; {{quad[1], quad[2], quad[3]}, {quad[0], quad[1], quad[3]}}};
} else if (firstValid) {
vec3 firstCross = vertPos_[tri0[0]] - vertPos_[tri1[0]]; int choice = 0;
vec3 secondCross = vertPos_[tri0[1]] - vertPos_[tri1[1]];
if (la::dot(firstCross, firstCross) < if (!(triCCW(tris[0][0]) && triCCW(tris[0][1]))) {
la::dot(secondCross, secondCross)) { choice = 1;
tri0[2] = tri1[0]; } else if (triCCW(tris[1][0]) && triCCW(tris[1][1])) {
tri1[2] = tri0[0]; 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]); addTri(face, tri, normal, halfedgeRef[firstEdge]);
} }
} else { // General triangulation } else { // General triangulation
@ -127,10 +174,12 @@ void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
auto generalTriangulation = [&](int face) { auto generalTriangulation = [&](int face) {
const vec3 normal = faceNormal_[face]; const vec3 normal = faceNormal_[face];
const mat2x3 projection = GetAxisAlignedProjection(normal); const mat2x3 projection = GetAxisAlignedProjection(normal);
const PolygonsIdx polys = const PolygonsIdx polys = ProjectPolygons(
Face2Polygons(halfedge_.cbegin() + faceEdge[face], AssembleHalfedges(halfedge_.cbegin() + faceEdge[face],
halfedge_.cbegin() + faceEdge[face + 1], projection); halfedge_.cbegin() + faceEdge[face + 1],
return TriangulateIdx(polys, epsilon_); faceEdge[face]),
halfedge_, vertPos_, projection);
return TriangulateIdx(polys, epsilon_, allowConvex);
}; };
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>) #if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>)
tbb::task_group group; tbb::task_group group;
@ -156,13 +205,17 @@ void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
// prefix sum computation (assign unique index to each face) and preallocation // prefix sum computation (assign unique index to each face) and preallocation
exclusive_scan(triCount.begin(), triCount.end(), triCount.begin(), 0_uz); exclusive_scan(triCount.begin(), triCount.end(), triCount.begin(), 0_uz);
triVerts.resize(triCount.back()); triVerts.resize(triCount.back());
triProp.resize(triCount.back());
triNormal.resize(triCount.back()); triNormal.resize(triCount.back());
triRef.resize(triCount.back()); triRef.resize(triCount.back());
auto processFace2 = std::bind( auto processFace2 = std::bind(
processFace, [&](size_t face) { return std::move(results[face]); }, processFace, [&](size_t face) { return std::move(results[face]); },
[&](size_t face, ivec3 tri, vec3 normal, TriRef r) { [&](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; triNormal[triCount[face]] = normal;
triRef[triCount[face]] = r; triRef[triCount[face]] = r;
triCount[face]++; triCount[face]++;
@ -177,8 +230,15 @@ void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
triRef.reserve(faceEdge.size()); triRef.reserve(faceEdge.size());
auto processFace2 = std::bind( auto processFace2 = std::bind(
processFace, generalTriangulation, processFace, generalTriangulation,
[&](size_t _face, ivec3 tri, vec3 normal, TriRef r) { [&](size_t, ivec3 tri, vec3 normal, TriRef r) {
triVerts.push_back(tri); 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); triNormal.push_back(normal);
triRef.push_back(r); triRef.push_back(r);
}, },
@ -189,41 +249,7 @@ void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
#endif #endif
faceNormal_ = std::move(triNormal); faceNormal_ = std::move(triNormal);
CreateHalfedges(triVerts); CreateHalfedges(triProp, 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<Halfedge>::IterC start,
VecView<Halfedge>::IterC end,
mat2x3 projection) const {
std::multimap<int, int> vert_edge;
for (auto edge = start; edge != end; ++edge) {
vert_edge.emplace(
std::make_pair(edge->startVert, static_cast<int>(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;
} }
Polygons Manifold::Impl::Slice(double height) const { 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; plane.min.z = plane.max.z = height;
Vec<Box> query; Vec<Box> query;
query.push_back(plane); query.push_back(plane);
const SparseIndices collisions =
collider_.Collisions<false, false>(query.cview());
std::unordered_set<int> tris; std::unordered_set<int> tris;
for (size_t i = 0; i < collisions.size(); ++i) { auto recordCollision = [&](int, int tri) {
const int tri = collisions.Get(i, 1);
double min = std::numeric_limits<double>::infinity(); double min = std::numeric_limits<double>::infinity();
double max = -std::numeric_limits<double>::infinity(); double max = -std::numeric_limits<double>::infinity();
for (const int j : {0, 1, 2}) { for (const int j : {0, 1, 2}) {
@ -248,7 +271,10 @@ Polygons Manifold::Impl::Slice(double height) const {
if (min <= height && max > height) { if (min <= height && max > height) {
tris.insert(tri); tris.insert(tri);
} }
} };
auto recorder = MakeSimpleRecorder(recordCollision);
collider_.Collisions<false>(query.cview(), recorder, false);
Polygons polys; Polygons polys;
while (!tris.empty()) { while (!tris.empty()) {
@ -303,7 +329,8 @@ Polygons Manifold::Impl::Project() const {
cusps.begin()); cusps.begin());
PolygonsIdx polysIndexed = PolygonsIdx polysIndexed =
Face2Polygons(cusps.cbegin(), cusps.cend(), projection); ProjectPolygons(AssembleHalfedges(cusps.cbegin(), cusps.cend(), 0), cusps,
vertPos_, projection);
Polygons polys; Polygons polys;
for (const auto& poly : polysIndexed) { for (const auto& poly : polysIndexed) {

View File

@ -16,13 +16,12 @@
#include <atomic> #include <atomic>
#include "./utils.h" #include "utils.h"
#include "./vec.h" #include "vec.h"
namespace { namespace {
typedef unsigned long long int Uint64; using hash_fun_t = uint64_t(uint64_t);
typedef Uint64 (*hash_fun_t)(Uint64); inline constexpr uint64_t kOpen = std::numeric_limits<uint64_t>::max();
inline constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
template <typename T> template <typename T>
T AtomicCAS(T& target, T compare, T val) { T AtomicCAS(T& target, T compare, T val) {
@ -45,13 +44,6 @@ T AtomicLoad(const T& target) {
return tar.load(std::memory_order_acquire); 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
namespace manifold { namespace manifold {
@ -59,7 +51,7 @@ namespace manifold {
template <typename V, hash_fun_t H = hash64bit> template <typename V, hash_fun_t H = hash64bit>
class HashTableD { class HashTableD {
public: public:
HashTableD(Vec<Uint64>& keys, Vec<V>& values, std::atomic<size_t>& used, HashTableD(Vec<uint64_t>& keys, Vec<V>& values, std::atomic<size_t>& used,
uint32_t step = 1) uint32_t step = 1)
: step_{step}, keys_{keys}, values_{values}, used_{used} {} : step_{step}, keys_{keys}, values_{values}, used_{used} {}
@ -70,12 +62,12 @@ class HashTableD {
static_cast<size_t>(Size()); static_cast<size_t>(Size());
} }
void Insert(Uint64 key, const V& val) { void Insert(uint64_t key, const V& val) {
uint32_t idx = H(key) & (Size() - 1); uint32_t idx = H(key) & (Size() - 1);
while (1) { while (1) {
if (Full()) return; if (Full()) return;
Uint64& k = keys_[idx]; uint64_t& k = keys_[idx];
const Uint64 found = AtomicCAS(k, kOpen, key); const uint64_t found = AtomicCAS(k, kOpen, key);
if (found == kOpen) { if (found == kOpen) {
used_.fetch_add(1, std::memory_order_relaxed); used_.fetch_add(1, std::memory_order_relaxed);
values_[idx] = val; 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); uint32_t idx = H(key) & (Size() - 1);
while (1) { while (1) {
const Uint64 k = AtomicLoad(keys_[idx]); const uint64_t k = AtomicLoad(keys_[idx]);
if (k == key || k == kOpen) { if (k == key || k == kOpen) {
return values_[idx]; 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); uint32_t idx = H(key) & (Size() - 1);
while (1) { while (1) {
const Uint64 k = AtomicLoad(keys_[idx]); const uint64_t k = AtomicLoad(keys_[idx]);
if (k == key || k == kOpen) { if (k == key || k == kOpen) {
return values_[idx]; 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]; } V& At(int idx) { return values_[idx]; }
const V& At(int idx) const { return values_[idx]; } const V& At(int idx) const { return values_[idx]; }
private: private:
uint32_t step_; uint32_t step_;
VecView<Uint64> keys_; VecView<uint64_t> keys_;
VecView<V> values_; VecView<V> values_;
std::atomic<size_t>& used_; std::atomic<size_t>& used_;
}; };
@ -157,10 +149,10 @@ class HashTable {
Vec<V>& GetValueStore() { return values_; } Vec<V>& GetValueStore() { return values_; }
static Uint64 Open() { return kOpen; } static uint64_t Open() { return kOpen; }
private: private:
Vec<Uint64> keys_; Vec<uint64_t> keys_;
Vec<V> values_; Vec<V> values_;
std::atomic<size_t> used_ = 0; std::atomic<size_t> used_ = 0;
uint32_t step_; uint32_t step_;

View File

@ -12,38 +12,113 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./impl.h" #include "impl.h"
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <cstring>
#include <map> #include <map>
#include <optional> #include <optional>
#include "./hashtable.h" #include "csg_tree.h"
#include "./mesh_fixes.h" #include "hashtable.h"
#include "./parallel.h" #include "manifold/optional_assert.h"
#include "./svd.h" #include "mesh_fixes.h"
#include "parallel.h"
#ifdef MANIFOLD_EXPORT #include "shared.h"
#include <string.h> #include "svd.h"
#include <iostream>
#endif
namespace { namespace {
using namespace manifold; using namespace manifold;
constexpr uint64_t kRemove = std::numeric_limits<uint64_t>::max(); /**
* Returns arc cosine of 𝑥.
void AtomicAddVec3(vec3& target, const vec3& add) { *
for (int i : {0, 1, 2}) { * @return value in range [0,M_PI]
std::atomic<double>& tar = * @return NAN if 𝑥 ∈ {NAN,+INFINITY,-INFINITY}
reinterpret_cast<std::atomic<double>&>(target[i]); * @return NAN if 𝑥 ∉ [-1,1]
double old_val = tar.load(std::memory_order_relaxed); */
while (!tar.compare_exchange_weak(old_val, old_val + add[i], double sun_acos(double x) {
std::memory_order_relaxed)) { /*
* 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 { struct Transform4x3 {
@ -52,143 +127,12 @@ struct Transform4x3 {
vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); } vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); }
}; };
template <bool calculateTriNormal>
struct AssignNormals {
VecView<vec3> faceNormal;
VecView<vec3> vertNormal;
VecView<const vec3> vertPos;
VecView<const Halfedge> 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 { struct UpdateMeshID {
const HashTableD<uint32_t> meshIDold2new; const HashTableD<uint32_t> meshIDold2new;
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; } void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
}; };
struct CoplanarEdge {
VecView<std::pair<int, int>> face2face;
VecView<double> triArea;
VecView<const Halfedge> halfedge;
VecView<const vec3> vertPos;
VecView<const TriRef> triRef;
VecView<const ivec3> 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<int> comp2tri;
VecView<const Halfedge> halfedge;
VecView<const vec3> vertPos;
std::vector<int>* components;
const double tolerance;
void operator()(int tri) {
const int component = (*components)[tri];
const int referenceTri =
reinterpret_cast<std::atomic<int>*>(&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<std::atomic<int>*>(&comp2tri[component])
->store(-1, std::memory_order_relaxed);
break;
}
}
}
};
int GetLabels(std::vector<int>& components, int GetLabels(std::vector<int>& components,
const Vec<std::pair<int, int>>& edges, int numNodes) { const Vec<std::pair<int, int>>& edges, int numNodes) {
UnionFind<> uf(numNodes); UnionFind<> uf(numNodes);
@ -199,19 +143,6 @@ int GetLabels(std::vector<int>& components,
return uf.connectedComponents(components); return uf.connectedComponents(components);
} }
void DedupePropVerts(manifold::Vec<ivec3>& triProp,
const Vec<std::pair<int, int>>& vert2vert,
size_t numPropVert) {
ZoneScoped;
std::vector<int> vertLabels;
const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert);
std::vector<int> 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
namespace manifold { namespace manifold {
@ -271,7 +202,7 @@ Manifold::Impl::Impl(Shape shape, const mat3x4 m) {
CreateHalfedges(triVerts); CreateHalfedges(triVerts);
Finish(); Finish();
InitializeOriginal(); InitializeOriginal();
CreateFaces(); MarkCoplanar();
} }
void Manifold::Impl::RemoveUnreferencedVerts() { void Manifold::Impl::RemoveUnreferencedVerts() {
@ -297,124 +228,165 @@ void Manifold::Impl::InitializeOriginal(bool keepFaceID) {
const int meshID = ReserveIDs(1); const int meshID = ReserveIDs(1);
meshRelation_.originalID = meshID; meshRelation_.originalID = meshID;
auto& triRef = meshRelation_.triRef; auto& triRef = meshRelation_.triRef;
triRef.resize(NumTri()); triRef.resize_nofill(NumTri());
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(), for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
[meshID, keepFaceID, &triRef](const int tri) { [meshID, keepFaceID, &triRef](const int tri) {
triRef[tri] = {meshID, meshID, tri, triRef[tri] = {meshID, meshID, -1,
keepFaceID ? triRef[tri].faceID : tri}; keepFaceID ? triRef[tri].coplanarID : tri};
}); });
meshRelation_.meshIDtransform.clear(); meshRelation_.meshIDtransform.clear();
meshRelation_.meshIDtransform[meshID] = {meshID}; meshRelation_.meshIDtransform[meshID] = {meshID};
} }
void Manifold::Impl::CreateFaces() { void Manifold::Impl::MarkCoplanar() {
ZoneScoped; ZoneScoped;
Vec<std::pair<int, int>> face2face(halfedge_.size(), {-1, -1}); const int numTri = NumTri();
Vec<std::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1}); struct TriPriority {
Vec<double> triArea(NumTri()); double area2;
int tri;
};
Vec<TriPriority> 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(); stable_sort(triPriority.begin(), triPriority.end(),
if (numProp > 0) { [](auto a, auto b) { return a.area2 > b.area2; });
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;
if (meshRelation_.triRef[edgeFace].meshID != Vec<int> interiorHalfedges;
meshRelation_.triRef[pairFace].meshID) for (const auto tp : triPriority) {
return; if (meshRelation_.triRef[tp.tri].coplanarID >= 0) continue;
const int baseNum = edgeIdx - 3 * edgeFace; meshRelation_.triRef[tp.tri].coplanarID = tp.tri;
const int jointNum = edge.pairedHalfedge - 3 * pairFace; 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 vec3 v = vertPos_[halfedge_[h].endVert];
const int prop1 = if (std::abs(dot(v - base, normal)) < tolerance_) {
meshRelation_ meshRelation_.triRef[h / 3].coplanarID = tp.tri;
.triProperties[pairFace][jointNum == 2 ? 0 : jointNum + 1];
if (prop0 == prop1) return;
bool propEqual = true; if (interiorHalfedges.empty() ||
for (size_t p = 0; p < numProp; ++p) { h != halfedge_[interiorHalfedges.back()].pairedHalfedge) {
if (meshRelation_.properties[numProp * prop0 + p] != interiorHalfedges.push_back(h);
meshRelation_.properties[numProp * prop1 + p]) { } else {
propEqual = false; interiorHalfedges.pop_back();
break; }
} const int hNext = NextHalfedge(h);
} interiorHalfedges.push_back(hNext);
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<int> components;
const int numComponent = GetLabels(components, face2face, NumTri());
Vec<int> 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>& triRef = meshRelation_.triRef;
for (size_t tri = 0; tri < NumTri(); ++tri) {
const int referenceTri = comp2tri[components[tri]];
if (referenceTri >= 0) {
triRef[tri].faceID = referenceTri;
} }
} }
} }
/** /**
* 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<ivec3>& triVerts) { void Manifold::Impl::DedupePropVerts() {
ZoneScoped; ZoneScoped;
const size_t numTri = triVerts.size(); const size_t numProp = NumProp();
if (numProp == 0) return;
Vec<std::pair<int, int>> 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<int> vertLabels;
const size_t numPropVert = NumPropVert();
const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert);
std::vector<int> 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<ivec3>& triProp,
const Vec<ivec3>& triVert) {
ZoneScoped;
const size_t numTri = triProp.size();
const int numHalfedge = 3 * numTri; const int numHalfedge = 3 * numTri;
// drop the old value first to avoid copy // drop the old value first to avoid copy
halfedge_.resize(0); halfedge_.clear(true);
halfedge_.resize(numHalfedge); halfedge_.resize_nofill(numHalfedge);
Vec<uint64_t> edge(numHalfedge); Vec<uint64_t> edge(numHalfedge);
Vec<int> ids(numHalfedge); Vec<int> ids(numHalfedge);
auto policy = autoPolicy(numTri, 1e5); auto policy = autoPolicy(numTri, 1e5);
sequence(ids.begin(), ids.end()); sequence(ids.begin(), ids.end());
for_each_n(policy, countAt(0), numTri, for_each_n(policy, countAt(0), numTri,
[this, &edge, &triVerts](const int tri) { [this, &edge, &triProp, &triVert](const int tri) {
const ivec3& verts = triVerts[tri]; const ivec3& props = triProp[tri];
for (const int i : {0, 1, 2}) { for (const int i : {0, 1, 2}) {
const int j = (i + 1) % 3; const int j = (i + 1) % 3;
const int e = 3 * tri + i; 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 // Sort the forward halfedges in front of the backward ones
// by setting the highest-order bit. // by setting the highest-order bit.
edge[e] = uint64_t(verts[i] < verts[j] ? 1 : 0) << 63 | edge[e] = uint64_t(v0 < v1 ? 1 : 0) << 63 |
((uint64_t)std::min(verts[i], verts[j])) << 32 | ((uint64_t)std::min(v0, v1)) << 32 |
std::max(verts[i], verts[j]); std::max(v0, v1);
} }
}); });
// Stable sort is required here so that halfedges from the same face are // Stable sort is required here so that halfedges from the same face are
// paired together (the triangles were created in face order). In some // paired together (the triangles were created in face order). In some
// degenerate situations the triangulator can add the same internal edge in // degenerate situations the triangulator can add the same internal edge in
// two different faces, causing this edge to not be 2-manifold. These are // 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) { stable_sort(ids.begin(), ids.end(), [&edge](const int& a, const int& b) {
return edge[a] < edge[b]; return edge[a] < edge[b];
}); });
@ -422,26 +394,62 @@ void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
// Mark opposed triangles for removal - this may strand unreferenced verts // Mark opposed triangles for removal - this may strand unreferenced verts
// which are removed later by RemoveUnreferencedVerts() and Finish(). // which are removed later by RemoveUnreferencedVerts() and Finish().
const int numEdge = numHalfedge / 2; 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]; const int pair0 = ids[i];
Halfedge h0 = halfedge_[pair0]; Halfedge& h0 = halfedge_[pair0];
int k = i + numEdge; int k = consecutiveStart + numEdge;
while (1) { while (1) {
const int pair1 = ids[k]; const int pair1 = ids[k];
Halfedge h1 = halfedge_[pair1]; Halfedge& h1 = halfedge_[pair1];
if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break; if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break;
if (halfedge_[NextHalfedge(pair0)].endVert == if (halfedge_[NextHalfedge(pair0)].endVert ==
halfedge_[NextHalfedge(pair1)].endVert) { halfedge_[NextHalfedge(pair1)].endVert) {
h0 = {-1, -1, -1}; h0.pairedHalfedge = h1.pairedHalfedge = kRemovedHalfedge;
h1 = {-1, -1, -1};
// Reorder so that remaining edges pair up // Reorder so that remaining edges pair up
if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]); if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]);
break; break;
} }
++k; ++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<std::pair<int, int>> 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<int, int>& 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 // 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 // correspond to their backward pair at the same offset in the second half
@ -449,9 +457,11 @@ void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) { for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) {
const int pair0 = ids[i]; const int pair0 = ids[i];
const int pair1 = ids[i + numEdge]; const int pair1 = ids[i + numEdge];
if (halfedge_[pair0].startVert >= 0) { if (halfedge_[pair0].pairedHalfedge != kRemovedHalfedge) {
halfedge_[pair0].pairedHalfedge = pair1; halfedge_[pair0].pairedHalfedge = pair1;
halfedge_[pair1].pairedHalfedge = pair0; halfedge_[pair1].pairedHalfedge = pair0;
} else {
halfedge_[pair0] = halfedge_[pair1] = {-1, -1, -1};
} }
}); });
} }
@ -468,13 +478,13 @@ void Manifold::Impl::Update() {
collider_.UpdateBoxes(faceBox); collider_.UpdateBoxes(faceBox);
} }
void Manifold::Impl::MarkFailure(Error status) { void Manifold::Impl::MakeEmpty(Error status) {
bBox_ = Box(); bBox_ = Box();
vertPos_.resize(0); vertPos_.clear();
halfedge_.resize(0); halfedge_.clear();
vertNormal_.resize(0); vertNormal_.clear();
faceNormal_.resize(0); faceNormal_.clear();
halfedgeTangent_.resize(0); halfedgeTangent_.clear();
meshRelation_ = MeshRelationD(); meshRelation_ = MeshRelationD();
status_ = status; status_ = status;
} }
@ -489,15 +499,14 @@ void Manifold::Impl::WarpBatch(std::function<void(VecView<vec3>)> warpFunc) {
warpFunc(vertPos_.view()); warpFunc(vertPos_.view());
CalculateBBox(); CalculateBBox();
if (!IsFinite()) { if (!IsFinite()) {
MarkFailure(Error::NonFiniteVertex); MakeEmpty(Error::NonFiniteVertex);
return; return;
} }
Update(); Update();
faceNormal_.resize(0); // force recalculation of triNormal faceNormal_.clear(); // force recalculation of triNormal
CalculateNormals();
SetEpsilon(); SetEpsilon();
Finish(); Finish();
CreateFaces(); MarkCoplanar();
meshRelation_.originalID = -1; meshRelation_.originalID = -1;
} }
@ -511,13 +520,15 @@ Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
return result; return result;
} }
if (!all(la::isfinite(transform_))) { if (!all(la::isfinite(transform_))) {
result.MarkFailure(Error::NonFiniteVertex); result.MakeEmpty(Error::NonFiniteVertex);
return result; return result;
} }
result.collider_ = collider_; result.collider_ = collider_;
result.meshRelation_ = meshRelation_; result.meshRelation_ = meshRelation_;
result.epsilon_ = epsilon_; result.epsilon_ = epsilon_;
result.tolerance_ = tolerance_; result.tolerance_ = tolerance_;
result.numProp_ = numProp_;
result.properties_ = properties_;
result.bBox_ = bBox_; result.bBox_ = bBox_;
result.halfedge_ = halfedge_; result.halfedge_ = halfedge_;
result.halfedgeTangent_.resize(halfedgeTangent_.size()); result.halfedgeTangent_.resize(halfedgeTangent_.size());
@ -592,23 +603,73 @@ void Manifold::Impl::SetEpsilon(double minEpsilon, bool useSingle) {
void Manifold::Impl::CalculateNormals() { void Manifold::Impl::CalculateNormals() {
ZoneScoped; ZoneScoped;
vertNormal_.resize(NumVert()); vertNormal_.resize(NumVert());
auto policy = autoPolicy(NumTri(), 1e4); auto policy = autoPolicy(NumTri());
fill(vertNormal_.begin(), vertNormal_.end(), vec3(0.0));
bool calculateTriNormal = false; std::vector<std::atomic<int>> vertHalfedgeMap(NumVert());
for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) {
vertHalfedgeMap[vert] = std::numeric_limits<int>::max();
});
auto atomicMin = [&vertHalfedgeMap](int value, int vert) {
if (vert < 0) return;
int old = std::numeric_limits<int>::max();
while (!vertHalfedgeMap[vert].compare_exchange_strong(old, value))
if (old < value) break;
};
if (faceNormal_.size() != NumTri()) { if (faceNormal_.size() != NumTri()) {
faceNormal_.resize(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( for_each_n(policy, countAt(0), NumVert(), [&](const size_t vert) {
policy, countAt(0), NumTri(), int firstEdge = vertHalfedgeMap[vert].load();
AssignNormals<true>({faceNormal_, vertNormal_, vertPos_, halfedge_})); // not referenced
else if (firstEdge == std::numeric_limits<int>::max()) {
for_each_n( vertNormal_[vert] = vec3(0.0);
policy, countAt(0), NumTri(), return;
AssignNormals<false>({faceNormal_, vertNormal_, vertPos_, halfedge_})); }
for_each(policy, vertNormal_.begin(), vertNormal_.end(), vec3 normal = vec3(0.0);
[](vec3& v) { v = SafeNormalize(v); }); 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()})); 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<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
const size_t numEdge = edges.size();
Vec<Box> 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<false, true>(QedgeBB.cview());
else
q1p2 = collider_.Collisions<false, false>(QedgeBB.cview());
if (inverted)
for_each(policy, countAt(0_uz), countAt(q1p2.size()),
ReindexEdge<true>({edges, q1p2}));
else
for_each(policy, countAt(0_uz), countAt(q1p2.size()),
ReindexEdge<false>({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<const vec3> vertsIn,
bool inverted) const {
ZoneScoped;
if (inverted)
return collider_.Collisions<false, true>(vertsIn);
else
return collider_.Collisions<false, false>(vertsIn);
}
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
/**
* Debugging output using high precision OBJ files with specialized comments
*/
std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl) { 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 << "# ======= begin mesh ======" << std::endl;
stream << "# tolerance = " << impl.tolerance_ << std::endl; stream << "# tolerance = " << impl.tolerance_ << std::endl;
stream << "# epsilon = " << impl.epsilon_ << 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; stream << "# ======== end mesh =======" << std::endl;
return stream; 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; MeshGL64 mesh;
std::optional<double> epsilon; std::optional<double> epsilon;
stream.precision(17); stream >> std::setprecision(19);
while (true) { while (true) {
char c = stream.get(); char c = stream.get();
if (stream.eof()) break; if (stream.eof()) break;
@ -718,7 +743,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
stream.get(tmp.data(), SIZE, '\n'); stream.get(tmp.data(), SIZE, '\n');
if (strncmp(tmp.data(), "tolerance", SIZE) == 0) { if (strncmp(tmp.data(), "tolerance", SIZE) == 0) {
// skip 3 letters // skip 3 letters
for (int i : {0, 1, 2}) stream.get(); for (int _ : {0, 1, 2}) stream.get();
stream >> mesh.tolerance; stream >> mesh.tolerance;
} else if (strncmp(tmp.data(), "epsilon =", SIZE) == 0) { } else if (strncmp(tmp.data(), "epsilon =", SIZE) == 0) {
double tmp; double tmp;
@ -727,7 +752,7 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
} else { } else {
// add it back because it is not what we want // add it back because it is not what we want
int end = 0; int end = 0;
while (tmp[end] != 0 && end < SIZE) end++; while (end < SIZE && tmp[end] != 0) end++;
while (--end > -1) stream.putback(tmp[end]); while (--end > -1) stream.putback(tmp[end]);
} }
c = stream.get(); c = stream.get();
@ -739,29 +764,42 @@ Manifold Manifold::ImportMeshGL64(std::istream& stream) {
break; break;
} }
case 'v': case 'v':
for (int i : {0, 1, 2}) { for (int _ : {0, 1, 2}) {
double x; double x;
stream >> x; stream >> x;
mesh.vertProperties.push_back(x); mesh.vertProperties.push_back(x);
} }
break; break;
case 'f': case 'f':
for (int i : {0, 1, 2}) { for (int _ : {0, 1, 2}) {
uint64_t x; uint64_t x;
stream >> x; stream >> x;
mesh.triVerts.push_back(x - 1); mesh.triVerts.push_back(x - 1);
} }
break; break;
case '\r':
case '\n': case '\n':
break; break;
default: default:
DEBUG_ASSERT(false, userErr, "unexpected character in MeshGL64 import"); DEBUG_ASSERT(false, userErr, "unexpected character in Manifold import");
} }
} }
auto m = std::make_shared<Manifold::Impl>(mesh); auto m = std::make_shared<Manifold::Impl>(mesh);
if (epsilon) m->SetEpsilon(*epsilon); if (epsilon) m->SetEpsilon(*epsilon);
return Manifold(m); 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 #endif
} // namespace manifold } // namespace manifold

View File

@ -15,12 +15,11 @@
#pragma once #pragma once
#include <map> #include <map>
#include "./collider.h" #include "collider.h"
#include "./shared.h" #include "manifold/common.h"
#include "./sparse.h"
#include "./vec.h"
#include "manifold/manifold.h" #include "manifold/manifold.h"
#include "manifold/polygon.h" #include "shared.h"
#include "vec.h"
namespace manifold { namespace manifold {
@ -34,11 +33,8 @@ struct Manifold::Impl {
struct MeshRelationD { struct MeshRelationD {
/// The originalID of this Manifold if it is an original; -1 otherwise. /// The originalID of this Manifold if it is an original; -1 otherwise.
int originalID = -1; int originalID = -1;
int numProp = 0;
Vec<double> properties;
std::map<int, Relation> meshIDtransform; std::map<int, Relation> meshIDtransform;
Vec<TriRef> triRef; Vec<TriRef> triRef;
Vec<ivec3> triProperties;
}; };
struct BaryIndices { struct BaryIndices {
int tri, start4, end4; int tri, start4, end4;
@ -47,9 +43,13 @@ struct Manifold::Impl {
Box bBox_; Box bBox_;
double epsilon_ = -1; double epsilon_ = -1;
double tolerance_ = -1; double tolerance_ = -1;
int numProp_ = 0;
Error status_ = Error::NoError; Error status_ = Error::NoError;
Vec<vec3> vertPos_; Vec<vec3> vertPos_;
Vec<Halfedge> halfedge_; Vec<Halfedge> halfedge_;
Vec<double> properties_;
// Note that vertNormal_ is not precise due to the use of an approximated acos
// function
Vec<vec3> vertNormal_; Vec<vec3> vertNormal_;
Vec<vec3> faceNormal_; Vec<vec3> faceNormal_;
Vec<vec4> halfedgeTangent_; Vec<vec4> halfedgeTangent_;
@ -69,153 +69,185 @@ struct Manifold::Impl {
const uint32_t numTri = meshGL.NumTri(); const uint32_t numTri = meshGL.NumTri();
if (meshGL.numProp < 3) { if (meshGL.numProp < 3) {
MarkFailure(Error::MissingPositionProperties); MakeEmpty(Error::MissingPositionProperties);
return; return;
} }
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) { if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
MarkFailure(Error::MergeVectorsDifferentLengths); MakeEmpty(Error::MergeVectorsDifferentLengths);
return; return;
} }
if (!meshGL.runTransform.empty() && if (!meshGL.runTransform.empty() &&
12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) { 12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) {
MarkFailure(Error::TransformWrongLength); MakeEmpty(Error::TransformWrongLength);
return; return;
} }
if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() && if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() &&
meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size() && meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size() &&
meshGL.runOriginalID.size() != meshGL.runIndex.size()) { meshGL.runOriginalID.size() != meshGL.runIndex.size()) {
MarkFailure(Error::RunIndexWrongLength); MakeEmpty(Error::RunIndexWrongLength);
return; return;
} }
if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) { if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) {
MarkFailure(Error::FaceIDWrongLength); MakeEmpty(Error::FaceIDWrongLength);
return; return;
} }
std::vector<int> prop2vert(numVert); if (!manifold::all_of(meshGL.vertProperties.begin(),
std::iota(prop2vert.begin(), prop2vert.end(), 0); meshGL.vertProperties.end(),
for (size_t i = 0; i < meshGL.mergeFromVert.size(); ++i) { [](Precision x) { return std::isfinite(x); })) {
const uint32_t from = meshGL.mergeFromVert[i]; MakeEmpty(Error::NonFiniteVertex);
const uint32_t to = meshGL.mergeToVert[i]; return;
if (from >= numVert || to >= numVert) { }
MarkFailure(Error::MergeIndexOutOfBounds);
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<int> 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; const auto numProp = meshGL.numProp - 3;
meshRelation_.numProp = numProp; numProp_ = numProp;
meshRelation_.properties.resize(meshGL.NumVert() * numProp); properties_.resize_nofill(meshGL.NumVert() * numProp);
tolerance_ = meshGL.tolerance; tolerance_ = meshGL.tolerance;
// This will have unreferenced duplicate positions that will be removed by // This will have unreferenced duplicate positions that will be removed by
// Impl::RemoveUnreferencedVerts(). // Impl::RemoveUnreferencedVerts().
vertPos_.resize(meshGL.NumVert()); vertPos_.resize_nofill(meshGL.NumVert());
for (size_t i = 0; i < meshGL.NumVert(); ++i) { for (size_t i = 0; i < meshGL.NumVert(); ++i) {
for (const int j : {0, 1, 2}) for (const int j : {0, 1, 2})
vertPos_[i][j] = meshGL.vertProperties[meshGL.numProp * i + j]; vertPos_[i][j] = meshGL.vertProperties[meshGL.numProp * i + j];
for (size_t j = 0; j < numProp; ++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]; 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 (size_t i = 0; i < halfedgeTangent_.size(); ++i) {
for (const int j : {0, 1, 2, 3}) for (const int j : {0, 1, 2, 3})
halfedgeTangent_[i][j] = meshGL.halfedgeTangent[4 * i + j]; halfedgeTangent_[i][j] = meshGL.halfedgeTangent[4 * i + j];
} }
Vec<TriRef> triRef; Vec<TriRef> triRef;
if (!meshGL.runOriginalID.empty()) { triRef.resize_nofill(meshGL.NumTri());
auto runIndex = meshGL.runIndex;
const auto runEnd = meshGL.triVerts.size();
if (runIndex.empty()) {
runIndex = {0, static_cast<I>(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;
}
if (meshGL.runTransform.empty()) { auto runIndex = meshGL.runIndex;
meshRelation_.meshIDtransform[meshID] = {originalID}; const auto runEnd = meshGL.triVerts.size();
} else { if (runIndex.empty()) {
const Precision* m = meshGL.runTransform.data() + 12 * i; runIndex = {0, static_cast<I>(runEnd)};
meshRelation_.meshIDtransform[meshID] = {originalID, } else if (runIndex.size() == meshGL.runOriginalID.size()) {
{{m[0], m[1], m[2]}, runIndex.push_back(runEnd);
{m[3], m[4], m[5]}, } else if (runIndex.size() == 1) {
{m[6], m[7], m[8]}, runIndex.push_back(runEnd);
{m[9], m[10], m[11]}}}; }
}
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<ivec3> triVerts; Vec<ivec3> triProp;
triVerts.reserve(numTri); triProp.reserve(numTri);
Vec<ivec3> 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) { for (size_t i = 0; i < numTri; ++i) {
ivec3 tri; ivec3 triP, triV;
for (const size_t j : {0, 1, 2}) { for (const size_t j : {0, 1, 2}) {
uint32_t vert = (uint32_t)meshGL.triVerts[3 * i + j]; uint32_t vert = (uint32_t)meshGL.triVerts[3 * i + j];
if (vert >= numVert) { if (vert >= numVert) {
MarkFailure(Error::VertexOutOfBounds); MakeEmpty(Error::VertexOutOfBounds);
return; 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]) { if (triV[0] != triV[1] && triV[1] != triV[2] && triV[2] != triV[0]) {
triVerts.push_back(tri); if (needsPropMap) {
triProp.push_back(triP);
triVert.push_back(triV);
} else {
triProp.push_back(triV);
}
if (triRef.size() > 0) { if (triRef.size() > 0) {
meshRelation_.triRef.push_back(triRef[i]); meshRelation_.triRef.push_back(triRef[i]);
} }
if (numProp > 0) {
meshRelation_.triProperties.push_back(
ivec3(static_cast<uint32_t>(meshGL.triVerts[3 * i]),
static_cast<uint32_t>(meshGL.triVerts[3 * i + 1]),
static_cast<uint32_t>(meshGL.triVerts[3 * i + 2])));
}
} }
} }
CreateHalfedges(triVerts); CreateHalfedges(triProp, triVert);
if (!IsManifold()) { if (!IsManifold()) {
MarkFailure(Error::NotManifold); MakeEmpty(Error::NotManifold);
return; return;
} }
CalculateBBox(); CalculateBBox();
SetEpsilon(-1, std::is_same<Precision, float>::value); SetEpsilon(-1, std::is_same<Precision, float>::value);
SplitPinchedVerts(); // we need to split pinched verts before calculating vertex normals, because
// the algorithm doesn't work with pinched verts
CleanupTopology();
CalculateNormals(); CalculateNormals();
if (meshGL.runOriginalID.empty()) { DedupePropVerts();
InitializeOriginal(); MarkCoplanar();
}
CreateFaces(); RemoveDegenerates();
SimplifyTopology();
RemoveUnreferencedVerts(); RemoveUnreferencedVerts();
Finish(); Finish();
if (!IsFinite()) { if (!IsFinite()) {
MarkFailure(Error::NonFiniteVertex); MakeEmpty(Error::NonFiniteVertex);
return; return;
} }
@ -224,7 +256,8 @@ struct Manifold::Impl {
meshRelation_.originalID = -1; meshRelation_.originalID = -1;
} }
inline void ForVert(int halfedge, std::function<void(int halfedge)> func) { template <typename F>
inline void ForVert(int halfedge, F func) {
int current = halfedge; int current = halfedge;
do { do {
current = NextHalfedge(halfedge_[current].pairedHalfedge); current = NextHalfedge(halfedge_[current].pairedHalfedge);
@ -247,30 +280,28 @@ struct Manifold::Impl {
} while (current != halfedge); } while (current != halfedge);
} }
void CreateFaces(); void MarkCoplanar();
void DedupePropVerts();
void RemoveUnreferencedVerts(); void RemoveUnreferencedVerts();
void InitializeOriginal(bool keepFaceID = false); void InitializeOriginal(bool keepFaceID = false);
void CreateHalfedges(const Vec<ivec3>& triVerts); void CreateHalfedges(const Vec<ivec3>& triProp,
const Vec<ivec3>& triVert = {});
void CalculateNormals(); void CalculateNormals();
void IncrementMeshIDs(); void IncrementMeshIDs();
void Update(); void Update();
void MarkFailure(Error status); void MakeEmpty(Error status);
void Warp(std::function<void(vec3&)> warpFunc); void Warp(std::function<void(vec3&)> warpFunc);
void WarpBatch(std::function<void(VecView<vec3>)> warpFunc); void WarpBatch(std::function<void(VecView<vec3>)> warpFunc);
Impl Transform(const mat3x4& transform) const; Impl Transform(const mat3x4& transform) const;
SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const;
SparseIndices VertexCollisionsZ(VecView<const vec3> vertsIn,
bool inverted = false) const;
bool IsEmpty() const { return NumTri() == 0; } bool IsEmpty() const { return NumTri() == 0; }
size_t NumVert() const { return vertPos_.size(); } size_t NumVert() const { return vertPos_.size(); }
size_t NumEdge() const { return halfedge_.size() / 2; } size_t NumEdge() const { return halfedge_.size() / 2; }
size_t NumTri() const { return halfedge_.size() / 3; } 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 { size_t NumPropVert() const {
return NumProp() == 0 ? NumVert() return NumProp() == 0 ? NumVert() : properties_.size() / NumProp();
: meshRelation_.properties.size() / NumProp();
} }
// properties.cpp // properties.cpp
@ -299,18 +330,20 @@ struct Manifold::Impl {
void GatherFaces(const Impl& old, const Vec<int>& faceNew2Old); void GatherFaces(const Impl& old, const Vec<int>& faceNew2Old);
// face_op.cpp // face_op.cpp
void Face2Tri(const Vec<int>& faceEdge, const Vec<TriRef>& halfedgeRef); void Face2Tri(const Vec<int>& faceEdge, const Vec<TriRef>& halfedgeRef,
PolygonsIdx Face2Polygons(VecView<Halfedge>::IterC start, bool allowConvex = false);
VecView<Halfedge>::IterC end,
mat2x3 projection) const;
Polygons Slice(double height) const; Polygons Slice(double height) const;
Polygons Project() const; Polygons Project() const;
// edge_op.cpp // edge_op.cpp
void CleanupTopology(); 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 DedupeEdge(int edge);
void CollapseEdge(int edge, std::vector<int>& edges); bool CollapseEdge(int edge, std::vector<int>& edges);
void RecursiveEdgeSwap(int edge, int& tag, std::vector<int>& visited, void RecursiveEdgeSwap(int edge, int& tag, std::vector<int>& visited,
std::vector<int>& edgeSwapStack, std::vector<int>& edgeSwapStack,
std::vector<int>& edges); std::vector<int>& edges);
@ -320,6 +353,7 @@ struct Manifold::Impl {
void FormLoop(int current, int end); void FormLoop(int current, int end);
void CollapseTri(const ivec3& triEdge); void CollapseTri(const ivec3& triEdge);
void SplitPinchedVerts(); void SplitPinchedVerts();
void DedupeEdges();
// subdivision.cpp // subdivision.cpp
int GetNeighbor(int tri) const; int GetNeighbor(int tri) const;
@ -353,8 +387,6 @@ struct Manifold::Impl {
void Hull(VecView<vec3> vertPos); void Hull(VecView<vec3> vertPos);
}; };
#ifdef MANIFOLD_DEBUG
extern std::mutex dump_lock; extern std::mutex dump_lock;
std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl); std::ostream& operator<<(std::ostream& stream, const Manifold::Impl& impl);
#endif
} // namespace manifold } // namespace manifold

View File

@ -14,6 +14,7 @@
#pragma once #pragma once
#include <iterator> #include <iterator>
#include <optional>
#include <type_traits> #include <type_traits>
namespace manifold { namespace manifold {
@ -22,7 +23,7 @@ template <typename F, typename Iter>
struct TransformIterator { struct TransformIterator {
private: private:
Iter iter; Iter iter;
F f; std::optional<F> f;
public: public:
using pointer = void; using pointer = void;
@ -36,16 +37,28 @@ struct TransformIterator {
constexpr TransformIterator(Iter iter, F f) : iter(iter), f(f) {} 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) { TransformIterator& operator=(const TransformIterator& other) {
if (this == &other) return *this; if (this == &other) return *this;
// don't copy function, should be the same
iter = other.iter; iter = other.iter;
f.emplace(*other.f);
return *this; 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 // prefix increment
TransformIterator& operator++() { TransformIterator& operator++() {
@ -74,7 +87,7 @@ struct TransformIterator {
} }
constexpr TransformIterator operator+(size_t n) const { constexpr TransformIterator operator+(size_t n) const {
return TransformIterator(iter + n, f); return TransformIterator(iter + n, *f);
} }
TransformIterator& operator+=(size_t n) { TransformIterator& operator+=(size_t n) {
@ -83,7 +96,7 @@ struct TransformIterator {
} }
constexpr TransformIterator operator-(size_t n) const { constexpr TransformIterator operator-(size_t n) const {
return TransformIterator(iter - n, f); return TransformIterator(iter - n, *f);
} }
TransformIterator& operator-=(size_t n) { TransformIterator& operator-=(size_t n) {
@ -108,7 +121,7 @@ struct TransformIterator {
} }
constexpr operator TransformIterator<F, const Iter>() const { constexpr operator TransformIterator<F, const Iter>() const {
return TransformIterator(f, iter); return TransformIterator(*f, iter);
} }
}; };

View File

@ -16,36 +16,17 @@
#include <map> #include <map>
#include <numeric> #include <numeric>
#include "./boolean3.h" #include "boolean3.h"
#include "./csg_tree.h" #include "csg_tree.h"
#include "./impl.h" #include "impl.h"
#include "./parallel.h" #include "parallel.h"
#include "shared.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
ExecutionParams manifoldParams; 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<void(double*, vec3, const double*)> 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) { Manifold Halfspace(Box bBox, vec3 normal, double originOffset) {
normal = la::normalize(normal); normal = la::normalize(normal);
Manifold cutter = Manifold::Cube(vec3(2.0), true).Translate({1.0, 0.0, 0.0}); Manifold cutter = Manifold::Cube(vec3(2.0), true).Translate({1.0, 0.0, 0.0});
@ -94,11 +75,12 @@ MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
VecView<const TriRef> triRef = impl.meshRelation_.triRef; VecView<const TriRef> triRef = impl.meshRelation_.triRef;
// Don't sort originals - keep them in order // Don't sort originals - keep them in order
if (!isOriginal) { if (!isOriginal) {
std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) { std::stable_sort(triNew2Old.begin(), triNew2Old.end(),
return triRef[a].originalID == triRef[b].originalID [triRef](int a, int b) {
? triRef[a].meshID < triRef[b].meshID return triRef[a].originalID == triRef[b].originalID
: triRef[a].originalID < triRef[b].originalID; ? triRef[a].meshID < triRef[b].meshID
}); : triRef[a].originalID < triRef[b].originalID;
});
} }
std::vector<mat3> runNormalTransform; std::vector<mat3> runNormalTransform;
@ -128,7 +110,7 @@ MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
const auto ref = triRef[oldTri]; const auto ref = triRef[oldTri];
const int meshID = ref.meshID; 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}) for (const int i : {0, 1, 2})
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert; out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
@ -166,9 +148,8 @@ MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
for (size_t run = 0; run < out.runOriginalID.size(); ++run) { for (size_t run = 0; run < out.runOriginalID.size(); ++run) {
for (size_t tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3; for (size_t tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3;
++tri) { ++tri) {
const ivec3 triProp = impl.meshRelation_.triProperties[triNew2Old[tri]];
for (const int i : {0, 1, 2}) { 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]; const int vert = out.triVerts[3 * tri + i];
auto& bin = vertPropPair[vert]; auto& bin = vertPropPair[vert];
@ -189,8 +170,7 @@ MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
out.vertProperties.push_back(impl.vertPos_[vert][p]); out.vertProperties.push_back(impl.vertPos_[vert][p]);
} }
for (int p = 0; p < numProp; ++p) { for (int p = 0; p < numProp; ++p) {
out.vertProperties.push_back( out.vertProperties.push_back(impl.properties_[prop * numProp + p]);
impl.meshRelation_.properties[prop * numProp + p]);
} }
if (updateNormals) { if (updateNormals) {
@ -332,11 +312,9 @@ MeshGL64 Manifold::GetMeshGL64(int normalIdx) const {
bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); } bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); }
/** /**
* Returns the reason for an input Mesh producing an empty Manifold. This Status * 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 * will carry on through operations like NaN propogation, ensuring an errored
* combined into a new Manifold via operations, the status reverts to NoError, * mesh doesn't get mysteriously lost. Empty meshes may still show
* simply processing the problem mesh as empty. Likewise, empty meshes may still * NoError, for instance the intersection of non-overlapping meshes.
* show NoError, for instance if they are small enough relative to their
* tolerance to be collapsed to nothing.
*/ */
Manifold::Error Manifold::Status() const { Manifold::Error Manifold::Status() const {
return GetCsgLeafNode().GetImpl()->status_; return GetCsgLeafNode().GetImpl()->status_;
@ -404,7 +382,7 @@ Manifold Manifold::SetTolerance(double tolerance) const {
auto impl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl()); auto impl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (tolerance > impl->tolerance_) { if (tolerance > impl->tolerance_) {
impl->tolerance_ = tolerance; impl->tolerance_ = tolerance;
impl->CreateFaces(); impl->MarkCoplanar();
impl->SimplifyTopology(); impl->SimplifyTopology();
impl->Finish(); impl->Finish();
} else { } else {
@ -415,6 +393,27 @@ Manifold Manifold::SetTolerance(double tolerance) const {
return Manifold(impl); 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<Impl>(*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 * 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 * 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 * This removes all relations (originalID, faceID, transform) to ancestor meshes
* and this new Manifold is marked an original. It also collapses colinear edges * and this new Manifold is marked an original. It also recreates faces
* - these don't get collapsed at boundaries where originalID changes, so the * - these don't get joined at boundaries where originalID changes, so the
* reset may allow flat faces to be further simplified. * reset may allow triangles of flat faces to be further collapsed with
* Simplify().
*/ */
Manifold Manifold::AsOriginal() const { Manifold Manifold::AsOriginal() const {
auto oldImpl = GetCsgLeafNode().GetImpl(); auto oldImpl = GetCsgLeafNode().GetImpl();
@ -463,9 +463,7 @@ Manifold Manifold::AsOriginal() const {
} }
auto newImpl = std::make_shared<Impl>(*oldImpl); auto newImpl = std::make_shared<Impl>(*oldImpl);
newImpl->InitializeOriginal(); newImpl->InitializeOriginal();
newImpl->CreateFaces(); newImpl->MarkCoplanar();
newImpl->SimplifyTopology();
newImpl->Finish();
newImpl->InitializeOriginal(true); newImpl->InitializeOriginal(true);
return Manifold(std::make_shared<CsgLeafNode>(newImpl)); return Manifold(std::make_shared<CsgLeafNode>(newImpl));
} }
@ -497,22 +495,6 @@ size_t Manifold::NumDegenerateTris() const {
return GetCsgLeafNode().GetImpl()->NumDegenerateTris(); 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 * Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily. * combined and applied lazily.
@ -635,38 +617,33 @@ Manifold Manifold::SetProperties(
propFunc) const { propFunc) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl()); auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
const int oldNumProp = NumProp(); const int oldNumProp = NumProp();
const Vec<double> oldProperties = pImpl->meshRelation_.properties; const Vec<double> oldProperties = pImpl->properties_;
auto& triProperties = pImpl->meshRelation_.triProperties;
if (numProp == 0) { if (numProp == 0) {
triProperties.resize(0); pImpl->properties_.clear();
pImpl->meshRelation_.properties.resize(0);
} else { } else {
if (triProperties.size() == 0) { pImpl->properties_ = Vec<double>(numProp * NumPropVert(), 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<double>(numProp * NumVert(), 0);
} else {
pImpl->meshRelation_.properties = Vec<double>(numProp * NumPropVert(), 0);
}
for_each_n( for_each_n(
propFunc == nullptr ? ExecutionPolicy::Par : ExecutionPolicy::Seq, propFunc == nullptr ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
countAt(0), NumTri(), countAt(0), NumTri(), [&](int tri) {
UpdateProperties( for (int i : {0, 1, 2}) {
{pImpl->meshRelation_.properties.data(), numProp, const Halfedge& edge = pImpl->halfedge_[3 * tri + i];
oldProperties.data(), oldNumProp, pImpl->vertPos_.data(), const int vert = edge.startVert;
triProperties.data(), pImpl->halfedge_.data(), const int propVert = edge.propVert;
propFunc == nullptr ? [](double* newProp, vec3 position, if (propFunc == nullptr) {
const double* oldProp) { *newProp = 0; } for (int p = 0; p < numProp; ++p) {
: propFunc})); 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<CsgLeafNode>(pImpl)); return Manifold(std::make_shared<CsgLeafNode>(pImpl));
} }
@ -754,14 +731,15 @@ Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl()); auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (!IsEmpty()) { if (!IsEmpty()) {
if (minSmoothness == 0) { if (minSmoothness == 0) {
const int numProp = pImpl->meshRelation_.numProp; const int numProp = pImpl->numProp_;
Vec<double> properties = pImpl->meshRelation_.properties; Vec<double> properties = pImpl->properties_;
Vec<ivec3> triProperties = pImpl->meshRelation_.triProperties; Vec<Halfedge> halfedge = pImpl->halfedge_;
pImpl->SetNormals(0, minSharpAngle); pImpl->SetNormals(0, minSharpAngle);
pImpl->CreateTangents(0); pImpl->CreateTangents(0);
pImpl->meshRelation_.numProp = numProp; // Reset the properties to the original values, removing temporary normals
pImpl->meshRelation_.properties.swap(properties); pImpl->numProp_ = numProp;
pImpl->meshRelation_.triProperties.swap(triProperties); pImpl->properties_.swap(properties);
pImpl->halfedge_.swap(halfedge);
} else { } else {
pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness)); pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness));
} }
@ -783,8 +761,7 @@ Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const {
Manifold Manifold::Refine(int n) const { Manifold Manifold::Refine(int n) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl()); auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (n > 1) { if (n > 1) {
pImpl->Refine( pImpl->Refine([n](vec3, vec4, vec4) { return n - 1; });
[n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; });
} }
return Manifold(std::make_shared<CsgLeafNode>(pImpl)); return Manifold(std::make_shared<CsgLeafNode>(pImpl));
} }
@ -802,7 +779,7 @@ Manifold Manifold::Refine(int n) const {
Manifold Manifold::RefineToLength(double length) const { Manifold Manifold::RefineToLength(double length) const {
length = std::abs(length); length = std::abs(length);
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl()); auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
pImpl->Refine([length](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { pImpl->Refine([length](vec3 edge, vec4, vec4) {
return static_cast<int>(la::length(edge) / length); return static_cast<int>(la::length(edge) / length);
}); });
return Manifold(std::make_shared<CsgLeafNode>(pImpl)); return Manifold(std::make_shared<CsgLeafNode>(pImpl));

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#pragma once #pragma once
#include "./shared.h" #include "shared.h"
namespace { namespace {
using namespace manifold; using namespace manifold;

View File

@ -17,7 +17,7 @@
#pragma once #pragma once
#include "./iters.h" #include "iters.h"
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
#include <tbb/combinable.h> #include <tbb/combinable.h>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
@ -114,20 +114,20 @@ template <typename T, typename InputIter, typename OutputIter, typename BinOp>
struct ScanBody { struct ScanBody {
T sum; T sum;
T identity; T identity;
BinOp &f; BinOp& f;
InputIter input; InputIter input;
OutputIter output; 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) {} : sum(sum), identity(identity), f(f), input(input), output(output) {}
ScanBody(ScanBody &b, tbb::split) ScanBody(ScanBody& b, tbb::split)
: sum(b.identity), : sum(b.identity),
identity(b.identity), identity(b.identity),
f(b.f), f(b.f),
input(b.input), input(b.input),
output(b.output) {} output(b.output) {}
template <typename Tag> template <typename Tag>
void operator()(const tbb::blocked_range<size_t> &r, Tag) { void operator()(const tbb::blocked_range<size_t>& r, Tag) {
T temp = sum; T temp = sum;
for (size_t i = r.begin(); i < r.end(); ++i) { for (size_t i = r.begin(); i < r.end(); ++i) {
T inputTmp = input[i]; T inputTmp = input[i];
@ -137,23 +137,23 @@ struct ScanBody {
sum = temp; sum = temp;
} }
T get_sum() const { return sum; } T get_sum() const { return sum; }
void reverse_join(ScanBody &a) { sum = f(a.sum, sum); } void reverse_join(ScanBody& a) { sum = f(a.sum, sum); }
void assign(ScanBody &b) { sum = b.sum; } void assign(ScanBody& b) { sum = b.sum; }
}; };
template <typename InputIter, typename OutputIter, typename P> template <typename InputIter, typename OutputIter, typename P>
struct CopyIfScanBody { struct CopyIfScanBody {
size_t sum; size_t sum;
P &pred; P& pred;
InputIter input; InputIter input;
OutputIter output; OutputIter output;
CopyIfScanBody(P &pred, InputIter input, OutputIter output) CopyIfScanBody(P& pred, InputIter input, OutputIter output)
: sum(0), pred(pred), input(input), output(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) {} : sum(0), pred(b.pred), input(b.input), output(b.output) {}
template <typename Tag> template <typename Tag>
void operator()(const tbb::blocked_range<size_t> &r, Tag) { void operator()(const tbb::blocked_range<size_t>& r, Tag) {
size_t temp = sum; size_t temp = sum;
for (size_t i = r.begin(); i < r.end(); ++i) { for (size_t i = r.begin(); i < r.end(); ++i) {
if (pred(i)) { if (pred(i)) {
@ -164,8 +164,8 @@ struct CopyIfScanBody {
sum = temp; sum = temp;
} }
size_t get_sum() const { return sum; } size_t get_sum() const { return sum; }
void reverse_join(CopyIfScanBody &a) { sum = a.sum + sum; } void reverse_join(CopyIfScanBody& a) { sum = a.sum + sum; }
void assign(CopyIfScanBody &b) { sum = b.sum; } void assign(CopyIfScanBody& b) { sum = b.sum; }
}; };
template <typename N, const int K> template <typename N, const int K>
@ -173,11 +173,11 @@ struct Hist {
using SizeType = N; using SizeType = N;
static constexpr int k = K; static constexpr int k = K;
N hist[k][256] = {{0}}; N hist[k][256] = {{0}};
void merge(const Hist<N, K> &other) { void merge(const Hist<N, K>& other) {
for (int i = 0; i < k; ++i) for (int i = 0; i < k; ++i)
for (int j = 0; j < 256; ++j) hist[i][j] += other.hist[i][j]; 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) { for (int i = 0; i < k; ++i) {
size_t count = 0; size_t count = 0;
for (int j = 0; j < 256; ++j) { for (int j = 0; j < 256; ++j) {
@ -191,8 +191,8 @@ struct Hist {
}; };
template <typename T, typename H> template <typename T, typename H>
void histogram(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) { auto worker = [](T* ptr, typename H::SizeType n, H& hist) {
for (typename H::SizeType i = 0; i < n; ++i) for (typename H::SizeType i = 0; i < n; ++i)
for (int k = 0; k < hist.k; ++k) for (int k = 0; k < hist.k; ++k)
++hist.hist[k][(ptr[i] >> (8 * k)) & 0xFF]; ++hist.hist[k][(ptr[i] >> (8 * k)) & 0xFF];
@ -203,21 +203,21 @@ void histogram(T *ptr, typename H::SizeType n, H &hist) {
tbb::combinable<H> store; tbb::combinable<H> store;
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<typename H::SizeType>(0, n, kSeqThreshold), tbb::blocked_range<typename H::SizeType>(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()); 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 <typename T, typename H> template <typename T, typename H>
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) for (typename H::SizeType i = 0; i < n; ++i)
target[hist.hist[k][(src[i] >> (8 * k)) & 0xFF]++] = src[i]; target[hist.hist[k][(src[i] >> (8 * k)) & 0xFF]++] = src[i];
} }
template <typename T, typename SizeType> template <typename T, typename SizeType>
bool LSB_radix_sort(T *input, T *tmp, SizeType n) { bool LSB_radix_sort(T* input, T* tmp, SizeType n) {
Hist<SizeType, sizeof(T) / sizeof(char)> hist; Hist<SizeType, sizeof(T) / sizeof(char)> hist;
if (std::is_sorted(input, input + n)) return false; if (std::is_sorted(input, input + n)) return false;
histogram(input, n, hist); histogram(input, n, hist);
@ -240,9 +240,9 @@ struct SortedRange {
SizeType offset = 0, length = 0; SizeType offset = 0, length = 0;
bool inTmp = false; 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) {} : input(input), tmp(tmp), offset(offset), length(length) {}
SortedRange(SortedRange<T, SizeType> &r, tbb::split) SortedRange(SortedRange<T, SizeType>& r, tbb::split)
: input(r.input), tmp(r.tmp) {} : input(r.input), tmp(r.tmp) {}
// FIXME: no idea why thread sanitizer reports data race here // FIXME: no idea why thread sanitizer reports data race here
#if defined(__has_feature) #if defined(__has_feature)
@ -251,7 +251,7 @@ struct SortedRange {
#endif #endif
#endif #endif
void void
operator()(const tbb::blocked_range<SizeType> &range) { operator()(const tbb::blocked_range<SizeType>& range) {
SortedRange<T, SizeType> rhs(input, tmp, range.begin(), SortedRange<T, SizeType> rhs(input, tmp, range.begin(),
range.end() - range.begin()); range.end() - range.begin());
rhs.inTmp = rhs.inTmp =
@ -267,7 +267,7 @@ struct SortedRange {
copy(src + offset, src + offset + length, target + offset); copy(src + offset, src + offset + length, target + offset);
return !inTmp; return !inTmp;
} }
void join(const SortedRange<T, SizeType> &rhs) { void join(const SortedRange<T, SizeType>& rhs) {
if (inTmp != rhs.inTmp) { if (inTmp != rhs.inTmp) {
if (length < rhs.length) if (length < rhs.length)
inTmp = swapBuffer(); inTmp = swapBuffer();
@ -286,8 +286,8 @@ struct SortedRange {
}; };
template <typename T, typename SizeTy> template <typename T, typename SizeTy>
void radix_sort(T *input, SizeTy n) { void radix_sort(T* input, SizeTy n) {
T *aux = new T[n]; T* aux = new T[n];
SizeTy blockSize = std::max(n / tbb::this_task_arena::max_concurrency() / 4, SizeTy blockSize = std::max(n / tbb::this_task_arena::max_concurrency() / 4,
static_cast<SizeTy>(kSeqThreshold / sizeof(T))); static_cast<SizeTy>(kSeqThreshold / sizeof(T)));
SortedRange<T, SizeTy> result(input, aux); SortedRange<T, SizeTy> result(input, aux);
@ -306,7 +306,7 @@ void mergeSort(ExecutionPolicy policy, Iterator first, Iterator last,
// apparently this prioritizes threads inside here? // apparently this prioritizes threads inside here?
tbb::this_task_arena::isolate([&] { tbb::this_task_arena::isolate([&] {
size_t length = std::distance(first, last); size_t length = std::distance(first, last);
T *tmp = new T[length]; T* tmp = new T[length];
copy(policy, first, last, tmp); copy(policy, first, last, tmp);
details::mergeSortRec(tmp, first, 0, length, comp); details::mergeSortRec(tmp, first, 0, length, comp);
delete[] tmp; delete[] tmp;
@ -376,13 +376,16 @@ void for_each(ExecutionPolicy policy, Iter first, Iter last, F f) {
typename std::iterator_traits<Iter>::iterator_category, typename std::iterator_traits<Iter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
tbb::parallel_for(tbb::blocked_range<Iter>(first, last), tbb::this_task_arena::isolate([&]() {
[&f](const tbb::blocked_range<Iter> &range) { tbb::parallel_for(tbb::blocked_range<Iter>(first, last),
for (Iter i = range.begin(); i != range.end(); i++) [&f](const tbb::blocked_range<Iter>& range) {
f(*i); for (Iter i = range.begin(); i != range.end(); i++)
}); f(*i);
});
});
return; return;
} }
#endif #endif
@ -412,16 +415,19 @@ T reduce(ExecutionPolicy policy, InputIter first, InputIter last, T init,
typename std::iterator_traits<InputIter>::iterator_category, typename std::iterator_traits<InputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
// should we use deterministic reduce here? // should we use deterministic reduce here?
return tbb::parallel_reduce( return tbb::this_task_arena::isolate([&]() {
tbb::blocked_range<InputIter>(first, last, details::kSeqThreshold), return tbb::parallel_reduce(
init, tbb::blocked_range<InputIter>(first, last, details::kSeqThreshold),
[&f](const tbb::blocked_range<InputIter> &range, T value) { init,
return std::reduce(range.begin(), range.end(), value, f); [&f](const tbb::blocked_range<InputIter>& range, T value) {
}, return std::reduce(range.begin(), range.end(), value, f);
f); },
f);
});
} }
#endif #endif
return std::reduce(first, last, init, f); return std::reduce(first, last, init, f);
@ -488,21 +494,24 @@ void inclusive_scan(ExecutionPolicy policy, InputIter first, InputIter last,
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
tbb::parallel_scan( tbb::this_task_arena::isolate([&]() {
tbb::blocked_range<size_t>(0, std::distance(first, last)), tbb::parallel_scan(
static_cast<T>(0), tbb::blocked_range<size_t>(0, std::distance(first, last)),
[&](const tbb::blocked_range<size_t> &range, T sum, static_cast<T>(0),
bool is_final_scan) { [&](const tbb::blocked_range<size_t>& range, T sum,
T temp = sum; bool is_final_scan) {
for (size_t i = range.begin(); i < range.end(); ++i) { T temp = sum;
temp = temp + first[i]; for (size_t i = range.begin(); i < range.end(); ++i) {
if (is_final_scan) d_first[i] = temp; temp = temp + first[i];
} if (is_final_scan) d_first[i] = temp;
return temp; }
}, return temp;
std::plus<T>()); },
std::plus<T>());
});
return; return;
} }
#endif #endif
@ -550,12 +559,16 @@ void exclusive_scan(ExecutionPolicy policy, InputIter first, InputIter last,
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
(void)identity;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
details::ScanBody<T, InputIter, OutputIter, BinOp> body(init, identity, f, details::ScanBody<T, InputIter, OutputIter, BinOp> body(init, identity, f,
first, d_first); first, d_first);
tbb::parallel_scan( tbb::this_task_arena::isolate([&]() {
tbb::blocked_range<size_t>(0, std::distance(first, last)), body); tbb::parallel_scan(
tbb::blocked_range<size_t>(0, std::distance(first, last)), body);
});
return; return;
} }
#endif #endif
@ -603,15 +616,18 @@ void transform(ExecutionPolicy policy, InputIter first, InputIter last,
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
tbb::parallel_for(tbb::blocked_range<size_t>( tbb::this_task_arena::isolate([&]() {
0, static_cast<size_t>(std::distance(first, last))), tbb::parallel_for(tbb::blocked_range<size_t>(
[&](const tbb::blocked_range<size_t> &range) { 0, static_cast<size_t>(std::distance(first, last))),
std::transform(first + range.begin(), [&](const tbb::blocked_range<size_t>& range) {
first + range.end(), std::transform(first + range.begin(),
d_first + range.begin(), f); first + range.end(),
}); d_first + range.begin(), f);
});
});
return; return;
} }
#endif #endif
@ -647,15 +663,18 @@ void copy(ExecutionPolicy policy, InputIter first, InputIter last,
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
tbb::parallel_for(tbb::blocked_range<size_t>( tbb::this_task_arena::isolate([&]() {
0, static_cast<size_t>(std::distance(first, last)), tbb::parallel_for(tbb::blocked_range<size_t>(
details::kSeqThreshold), 0, static_cast<size_t>(std::distance(first, last)),
[&](const tbb::blocked_range<size_t> &range) { details::kSeqThreshold),
std::copy(first + range.begin(), first + range.end(), [&](const tbb::blocked_range<size_t>& range) {
d_first + range.begin()); std::copy(first + range.begin(), first + range.end(),
}); d_first + range.begin());
});
});
return; return;
} }
#endif #endif
@ -704,12 +723,15 @@ void fill(ExecutionPolicy policy, OutputIter first, OutputIter last, T value) {
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
tbb::parallel_for(tbb::blocked_range<OutputIter>(first, last), tbb::this_task_arena::isolate([&]() {
[&](const tbb::blocked_range<OutputIter> &range) { tbb::parallel_for(tbb::blocked_range<OutputIter>(first, last),
std::fill(range.begin(), range.end(), value); [&](const tbb::blocked_range<OutputIter>& range) {
}); std::fill(range.begin(), range.end(), value);
});
});
return; return;
} }
#endif #endif
@ -727,6 +749,7 @@ void fill(OutputIter first, OutputIter last, T value) {
template <typename InputIter, typename P> template <typename InputIter, typename P>
size_t count_if(ExecutionPolicy policy, InputIter first, InputIter last, size_t count_if(ExecutionPolicy policy, InputIter first, InputIter last,
P pred) { P pred) {
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
return reduce(policy, TransformIterator(first, pred), 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<InputIter>::iterator_category, typename std::iterator_traits<InputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
// should we use deterministic reduce here? // should we use deterministic reduce here?
return tbb::parallel_reduce( return tbb::this_task_arena::isolate([&]() {
tbb::blocked_range<InputIter>(first, last), true, return tbb::parallel_reduce(
[&](const tbb::blocked_range<InputIter> &range, bool value) { tbb::blocked_range<InputIter>(first, last), true,
if (!value) return false; [&](const tbb::blocked_range<InputIter>& range, bool value) {
for (InputIter i = range.begin(); i != range.end(); i++) if (!value) return false;
if (!pred(*i)) return false; for (InputIter i = range.begin(); i != range.end(); i++)
return true; if (!pred(*i)) return false;
}, return true;
[](bool a, bool b) { return a && b; }); },
[](bool a, bool b) { return a && b; });
});
} }
#endif #endif
return std::all_of(first, last, pred); return std::all_of(first, last, pred);
@ -798,13 +824,16 @@ OutputIter copy_if(ExecutionPolicy policy, InputIter first, InputIter last,
typename std::iterator_traits<OutputIter>::iterator_category, typename std::iterator_traits<OutputIter>::iterator_category,
std::random_access_iterator_tag>, std::random_access_iterator_tag>,
"You can only parallelize RandomAccessIterator."); "You can only parallelize RandomAccessIterator.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
auto pred2 = [&](size_t i) { return pred(first[i]); }; auto pred2 = [&](size_t i) { return pred(first[i]); };
details::CopyIfScanBody body(pred2, first, d_first); details::CopyIfScanBody body(pred2, first, d_first);
tbb::parallel_scan( tbb::this_task_arena::isolate([&]() {
tbb::blocked_range<size_t>(0, std::distance(first, last)), body); tbb::parallel_scan(
return d_first + body.get_sum(); tbb::blocked_range<size_t>(0, std::distance(first, last)), body);
return d_first + body.get_sum();
});
} }
#endif #endif
return std::copy_if(first, last, d_first, pred); 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<T>, static_assert(std::is_trivially_destructible_v<T>,
"Our simple implementation does not support types that are " "Our simple implementation does not support types that are "
"not trivially destructable."); "not trivially destructable.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
T *tmp = new T[std::distance(first, last)]; T* tmp = new T[std::distance(first, last)];
auto back = auto back =
copy_if(policy, first, last, tmp, [&](T v) { return !pred(v); }); copy_if(policy, first, last, tmp, [&](T v) { return !pred(v); });
copy(policy, tmp, back, first); 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<T>, static_assert(std::is_trivially_destructible_v<T>,
"Our simple implementation does not support types that are " "Our simple implementation does not support types that are "
"not trivially destructable."); "not trivially destructable.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par) { if (policy == ExecutionPolicy::Par) {
T *tmp = new T[std::distance(first, last)]; T* tmp = new T[std::distance(first, last)];
auto back = auto back =
copy_if(policy, first, last, tmp, [&](T v) { return v != value; }); copy_if(policy, first, last, tmp, [&](T v) { return v != value; });
copy(policy, tmp, back, first); copy(policy, tmp, back, first);
@ -939,13 +970,14 @@ Iter unique(ExecutionPolicy policy, Iter first, Iter last) {
static_assert(std::is_trivially_destructible_v<T>, static_assert(std::is_trivially_destructible_v<T>,
"Our simple implementation does not support types that are " "Our simple implementation does not support types that are "
"not trivially destructable."); "not trivially destructable.");
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
if (policy == ExecutionPolicy::Par && first != last) { if (policy == ExecutionPolicy::Par && first != last) {
Iter newSrcStart = first; Iter newSrcStart = first;
// cap the maximum buffer size, proved to be beneficial for unique with huge // cap the maximum buffer size, proved to be beneficial for unique with huge
// array size // array size
constexpr size_t MAX_BUFFER_SIZE = 1 << 16; 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<size_t>(std::distance(first, last)))]; static_cast<size_t>(std::distance(first, last)))];
auto pred = [&](size_t i) { return tmp[i] != tmp[i + 1]; }; auto pred = [&](size_t i) { return tmp[i] != tmp[i + 1]; };
do { 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 // 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. // element with its predecessor we need to compare i and i + 1.
details::CopyIfScanBody body(pred, tmp + 1, first + 1); details::CopyIfScanBody body(pred, tmp + 1, first + 1);
tbb::parallel_scan(tbb::blocked_range<size_t>(0, length - 1), body); tbb::this_task_arena::isolate([&]() {
tbb::parallel_scan(tbb::blocked_range<size_t>(0, length - 1), body);
});
first += body.get_sum() + 1; first += body.get_sum() + 1;
newSrcStart += length; newSrcStart += length;
} while (newSrcStart != last); } while (newSrcStart != last);
@ -992,6 +1026,7 @@ Iter unique(Iter first, Iter last) {
template <typename Iterator, template <typename Iterator,
typename T = typename std::iterator_traits<Iterator>::value_type> typename T = typename std::iterator_traits<Iterator>::value_type>
void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last) { void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last) {
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
details::SortFunctor<Iterator, T>()(policy, first, last); details::SortFunctor<Iterator, T>()(policy, first, last);
#else #else
@ -1023,6 +1058,7 @@ template <typename Iterator,
typename Comp = decltype(std::less<T>())> typename Comp = decltype(std::less<T>())>
void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last, void stable_sort(ExecutionPolicy policy, Iterator first, Iterator last,
Comp comp) { Comp comp) {
(void)policy;
#if (MANIFOLD_PAR == 1) #if (MANIFOLD_PAR == 1)
details::mergeSort(policy, first, last, comp); details::mergeSort(policy, first, last, comp);
#else #else

View File

@ -18,15 +18,20 @@
#include <map> #include <map>
#include <set> #include <set>
#include "./collider.h" #include "manifold/manifold.h"
#include "./parallel.h"
#include "./utils.h"
#include "manifold/optional_assert.h" #include "manifold/optional_assert.h"
#include "parallel.h"
#include "tree2d.h"
#include "utils.h"
#ifdef MANIFOLD_DEBUG
#include <iomanip>
#endif
namespace { namespace {
using namespace manifold; using namespace manifold;
static ExecutionParams params; constexpr int TRIANGULATOR_VERBOSE_LEVEL = 2;
constexpr double kBest = -std::numeric_limits<double>::infinity(); constexpr double kBest = -std::numeric_limits<double>::infinity();
@ -40,9 +45,9 @@ struct PolyEdge {
int startVert, endVert; int startVert, endVert;
}; };
std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &polys) { std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx& polys) {
std::vector<PolyEdge> halfedges; std::vector<PolyEdge> halfedges;
for (const auto &poly : polys) { for (const auto& poly : polys) {
for (size_t i = 1; i < poly.size(); ++i) { for (size_t i = 1; i < poly.size(); ++i) {
halfedges.push_back({poly[i - 1].idx, poly[i].idx}); halfedges.push_back({poly[i - 1].idx, poly[i].idx});
} }
@ -51,10 +56,10 @@ std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &polys) {
return halfedges; return halfedges;
} }
std::vector<PolyEdge> Triangles2Edges(const std::vector<ivec3> &triangles) { std::vector<PolyEdge> Triangles2Edges(const std::vector<ivec3>& triangles) {
std::vector<PolyEdge> halfedges; std::vector<PolyEdge> halfedges;
halfedges.reserve(triangles.size() * 3); 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[0], tri[1]});
halfedges.push_back({tri[1], tri[2]}); halfedges.push_back({tri[1], tri[2]});
halfedges.push_back({tri[2], tri[0]}); halfedges.push_back({tri[2], tri[0]});
@ -62,7 +67,7 @@ std::vector<PolyEdge> Triangles2Edges(const std::vector<ivec3> &triangles) {
return halfedges; return halfedges;
} }
void CheckTopology(const std::vector<PolyEdge> &halfedges) { void CheckTopology(const std::vector<PolyEdge>& halfedges) {
DEBUG_ASSERT(halfedges.size() % 2 == 0, topologyErr, DEBUG_ASSERT(halfedges.size() % 2 == 0, topologyErr,
"Odd number of halfedges."); "Odd number of halfedges.");
size_t n_edges = halfedges.size() / 2; size_t n_edges = halfedges.size() / 2;
@ -83,8 +88,8 @@ void CheckTopology(const std::vector<PolyEdge> &halfedges) {
backward.resize(n_edges); backward.resize(n_edges);
std::for_each(backward.begin(), backward.end(), std::for_each(backward.begin(), backward.end(),
[](PolyEdge &e) { std::swap(e.startVert, e.endVert); }); [](PolyEdge& e) { std::swap(e.startVert, e.endVert); });
auto cmp = [](const PolyEdge &a, const PolyEdge &b) { auto cmp = [](const PolyEdge& a, const PolyEdge& b) {
return a.startVert < b.startVert || return a.startVert < b.startVert ||
(a.startVert == b.startVert && a.endVert < b.endVert); (a.startVert == b.startVert && a.endVert < b.endVert);
}; };
@ -97,8 +102,8 @@ void CheckTopology(const std::vector<PolyEdge> &halfedges) {
} }
} }
void CheckTopology(const std::vector<ivec3> &triangles, void CheckTopology(const std::vector<ivec3>& triangles,
const PolygonsIdx &polys) { const PolygonsIdx& polys) {
std::vector<PolyEdge> halfedges = Triangles2Edges(triangles); std::vector<PolyEdge> halfedges = Triangles2Edges(triangles);
std::vector<PolyEdge> openEdges = Polygons2Edges(polys); std::vector<PolyEdge> openEdges = Polygons2Edges(polys);
for (PolyEdge e : openEdges) { for (PolyEdge e : openEdges) {
@ -107,23 +112,24 @@ void CheckTopology(const std::vector<ivec3> &triangles,
CheckTopology(halfedges); CheckTopology(halfedges);
} }
void CheckGeometry(const std::vector<ivec3> &triangles, void CheckGeometry(const std::vector<ivec3>& triangles,
const PolygonsIdx &polys, double epsilon) { const PolygonsIdx& polys, double epsilon) {
std::unordered_map<int, vec2> vertPos; std::unordered_map<int, vec2> vertPos;
for (const auto &poly : polys) { for (const auto& poly : polys) {
for (size_t i = 0; i < poly.size(); ++i) { for (size_t i = 0; i < poly.size(); ++i) {
vertPos[poly[i].idx] = poly[i].pos; vertPos[poly[i].idx] = poly[i].pos;
} }
} }
DEBUG_ASSERT(std::all_of(triangles.begin(), triangles.end(), 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]], return CCW(vertPos[tri[0]], vertPos[tri[1]],
vertPos[tri[2]], epsilon) >= 0; vertPos[tri[2]], epsilon) >= 0;
}), }),
geometryErr, "triangulation is not entirely CCW!"); 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; std::cout << "Polygon 0 " << epsilon << " " << polys.size() << std::endl;
for (auto poly : polys) { for (auto poly : polys) {
std::cout << poly.size() << std::endl; 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::atomic<int> numFailures(0);
std::vector<ivec3> &triangles, double epsilon) {
void PrintFailure(const std::exception& e, const PolygonsIdx& polys,
std::vector<ivec3>& 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 << "-----------------------------------" << std::endl;
std::cout << "Triangulation failed! Precision = " << epsilon << std::endl; std::cout << "Triangulation failed! Precision = " << epsilon << std::endl;
std::cout << e.what() << 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() std::cout << "Output truncated due to producing " << triangles.size()
<< " triangles." << std::endl; << " triangles." << std::endl;
return; return;
@ -159,8 +171,9 @@ void PrintFailure(const std::exception &e, const PolygonsIdx &polys,
} }
} }
#define PRINT(msg) \ #define PRINT(msg) \
if (params.verbose) std::cout << msg << std::endl; if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) \
std::cout << msg << std::endl;
#else #else
#define PRINT(msg) #define PRINT(msg)
#endif #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 * Exactly colinear edges and zero-length edges are treated conservatively as
* reflex. Does not check for overlaps. * reflex. Does not check for overlaps.
*/ */
bool IsConvex(const PolygonsIdx &polys, double epsilon) { bool IsConvex(const PolygonsIdx& polys, double epsilon) {
for (const SimplePolygonIdx &poly : polys) { for (const SimplePolygonIdx& poly : polys) {
const vec2 firstEdge = poly[0].pos - poly[poly.size() - 1].pos; 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 // 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 // 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 * Triangulates a set of convex polygons by alternating instead of a fan, to
* avoid creating high-degree vertices. * avoid creating high-degree vertices.
*/ */
std::vector<ivec3> TriangulateConvex(const PolygonsIdx &polys) { std::vector<ivec3> TriangulateConvex(const PolygonsIdx& polys) {
const size_t numTri = manifold::transform_reduce( const size_t numTri = manifold::transform_reduce(
polys.begin(), polys.end(), 0_uz, polys.begin(), polys.end(), 0_uz,
[](size_t a, size_t b) { return a + b; }, [](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<ivec3> triangles; std::vector<ivec3> triangles;
triangles.reserve(numTri); triangles.reserve(numTri);
for (const SimplePolygonIdx &poly : polys) { for (const SimplePolygonIdx& poly : polys) {
size_t i = 0; size_t i = 0;
size_t k = poly.size() - 1; size_t k = poly.size() - 1;
bool right = true; bool right = true;
@ -222,9 +235,7 @@ std::vector<ivec3> TriangulateConvex(const PolygonsIdx &polys) {
* Ear-clipping triangulator based on David Eberly's approach from Geometric * Ear-clipping triangulator based on David Eberly's approach from Geometric
* Tools, but adjusted to handle epsilon-valid polygons, and including a * Tools, but adjusted to handle epsilon-valid polygons, and including a
* fallback that ensures a manifold triangulation even for overlapping polygons. * 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 * This is reduced from an O(n^2) algorithm by means of our BVH Collider.
* 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.
* *
* The main adjustments for robustness involve clipping the sharpest ears first * The main adjustments for robustness involve clipping the sharpest ears first
* (a known technique to get higher triangle quality), and doing an exhaustive * (a known technique to get higher triangle quality), and doing an exhaustive
@ -234,11 +245,11 @@ std::vector<ivec3> TriangulateConvex(const PolygonsIdx &polys) {
class EarClip { class EarClip {
public: public:
EarClip(const PolygonsIdx &polys, double epsilon) : epsilon_(epsilon) { EarClip(const PolygonsIdx& polys, double epsilon) : epsilon_(epsilon) {
ZoneScoped; ZoneScoped;
size_t numVert = 0; size_t numVert = 0;
for (const SimplePolygonIdx &poly : polys) { for (const SimplePolygonIdx& poly : polys) {
numVert += poly.size(); numVert += poly.size();
} }
polygon_.reserve(numVert + 2 * polys.size()); polygon_.reserve(numVert + 2 * polys.size());
@ -272,19 +283,19 @@ class EarClip {
private: private:
struct Vert; struct Vert;
typedef std::vector<Vert>::iterator VertItr; using VertItr = std::vector<Vert>::iterator;
typedef std::vector<Vert>::const_iterator VertItrC; using VertItrC = std::vector<Vert>::const_iterator;
struct MaxX { 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; return a->pos.x > b->pos.x;
} }
}; };
struct MinCost { 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; return a->cost < b->cost;
} }
}; };
typedef std::set<VertItr, MinCost>::iterator qItr; using qItr = std::set<VertItr, MinCost>::iterator;
// The flat list where all the Verts are stored. Not used much for traversal. // The flat list where all the Verts are stored. Not used much for traversal.
std::vector<Vert> polygon_; std::vector<Vert> polygon_;
@ -306,9 +317,8 @@ class EarClip {
double epsilon_; double epsilon_;
struct IdxCollider { struct IdxCollider {
Collider collider; Vec<PolyVert> points;
std::vector<VertItr> itr; std::vector<VertItr> itr;
SparseIndices ind;
}; };
// A circularly-linked list representing the polygon(s) that still need to be // A circularly-linked list representing the polygon(s) that still need to be
@ -399,20 +409,9 @@ class EarClip {
return true; return true;
} }
// A major key to robustness is to only clip convex ears, but this is // Returns true for convex or colinear ears.
// 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.
bool IsConvex(double epsilon) const { bool IsConvex(double epsilon) const {
const int convexity = CCW(left->pos, pos, right->pos, epsilon); return CCW(left->pos, pos, right->pos, epsilon) >= 0;
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);
} }
// Subtly different from !IsConvex because IsConvex will return true for // 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 // values < -epsilon so they will never affect validity. The first
// totalCost is designed to give priority to sharper angles. Any cost < (-1 // totalCost is designed to give priority to sharper angles. Any cost < (-1
// - epsilon) has satisfied the Delaunay condition. // - 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; vec2 openSide = left->pos - right->pos;
const vec2 center = 0.5 * (left->pos + right->pos); const vec2 center = 0.5 * (left->pos + right->pos);
const double scale = 4 / la::dot(openSide, openSide); const double scale = 4 / la::dot(openSide, openSide);
@ -502,38 +501,32 @@ class EarClip {
return totalCost; return totalCost;
} }
Box earBox = Box{vec3(center.x - radius, center.y - radius, 0), Rect earBox = Rect(vec2(center.x - radius, center.y - radius),
vec3(center.x + radius, center.y + radius, 0)}; vec2(center.x + radius, center.y + radius));
earBox.Union(vec3(pos, 0)); earBox.Union(pos);
collider.collider.Collisions(VecView<const Box>(&earBox, 1), earBox.min -= epsilon;
collider.ind); earBox.max += epsilon;
const int lid = left->mesh_idx; const int lid = left->mesh_idx;
const int rid = right->mesh_idx; const int rid = right->mesh_idx;
QueryTwoDTree(collider.points, earBox, [&](PolyVert point) {
totalCost = transform_reduce( const VertItr test = collider.itr[point.idx];
countAt(0), countAt(collider.ind.size()), totalCost, if (!Clipped(test) && test->mesh_idx != mesh_idx &&
[](double a, double b) { return std::max(a, b); }, test->mesh_idx != lid &&
[&](size_t i) { test->mesh_idx != rid) { // Skip duplicated verts
const VertItr test = collider.itr[collider.ind.Get(i, true)]; double cost = Cost(test, openSide, epsilon);
if (!Clipped(test) && test->mesh_idx != mesh_idx && if (cost < -epsilon) {
test->mesh_idx != lid && cost = DelaunayCost(test->pos - center, scale, epsilon);
test->mesh_idx != rid) { // Skip duplicated verts }
double cost = Cost(test, openSide, epsilon); if (cost > totalCost) totalCost = cost;
if (cost < -epsilon) { }
cost = DelaunayCost(test->pos - center, scale, epsilon); });
}
return cost;
}
return std::numeric_limits<double>::lowest();
});
collider.ind.Clear();
return totalCost; return totalCost;
} }
void PrintVert() const { void PrintVert() const {
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (!params.verbose) return; if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return;
std::cout << "vert: " << mesh_idx << ", left: " << left->mesh_idx std::cout << "vert: " << mesh_idx << ", left: " << left->mesh_idx
<< ", right: " << right->mesh_idx << ", cost: " << cost << ", right: " << right->mesh_idx << ", cost: " << cost
<< std::endl; << std::endl;
@ -603,8 +596,7 @@ class EarClip {
// If an ear will make a degenerate triangle, clip it early to avoid // 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 // difficulty in key-holing. This function is recursive, as the process of
// clipping may cause the neighbors to degenerate. Reflex degenerates *must // clipping may cause the neighbors to degenerate.
// not* be clipped, unless they have a short edge.
void ClipIfDegenerate(VertItr ear) { void ClipIfDegenerate(VertItr ear) {
if (Clipped(ear)) { if (Clipped(ear)) {
return; return;
@ -614,8 +606,7 @@ class EarClip {
} }
if (ear->IsShort(epsilon_) || if (ear->IsShort(epsilon_) ||
(CCW(ear->left->pos, ear->pos, ear->right->pos, epsilon_) == 0 && (CCW(ear->left->pos, ear->pos, ear->right->pos, epsilon_) == 0 &&
la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0 && la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0)) {
ear->IsConvex(epsilon_))) {
ClipEar(ear); ClipEar(ear);
ClipIfDegenerate(ear->left); ClipIfDegenerate(ear->left);
ClipIfDegenerate(ear->right); ClipIfDegenerate(ear->right);
@ -623,11 +614,18 @@ class EarClip {
} }
// Build the circular list polygon structures. // Build the circular list polygon structures.
std::vector<VertItr> Initialize(const PolygonsIdx &polys) { std::vector<VertItr> Initialize(const PolygonsIdx& polys) {
std::vector<VertItr> starts; std::vector<VertItr> starts;
for (const SimplePolygonIdx &poly : polys) { const auto invalidItr = polygon_.begin();
for (const SimplePolygonIdx& poly : polys) {
auto vert = poly.begin(); 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()); const VertItr first = std::prev(polygon_.end());
bBox_.Union(first->pos); bBox_.Union(first->pos);
@ -639,7 +637,13 @@ class EarClip {
for (++vert; vert != poly.end(); ++vert) { for (++vert; vert != poly.end(); ++vert) {
bBox_.Union(vert->pos); 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()); VertItr next = std::prev(polygon_.end());
Link(last, next); Link(last, next);
@ -740,7 +744,7 @@ class EarClip {
JoinPolygons(start, connector); JoinPolygons(start, connector);
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (params.verbose) { if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) {
std::cout << "connected " << start->mesh_idx << " to " std::cout << "connected " << start->mesh_idx << " to "
<< connector->mesh_idx << std::endl; << connector->mesh_idx << std::endl;
} }
@ -767,7 +771,8 @@ class EarClip {
above * CCW(start->pos, vert->pos, connector->pos, epsilon_); above * CCW(start->pos, vert->pos, connector->pos, epsilon_);
if (vert->pos.x > start->pos.x - epsilon_ && if (vert->pos.x > start->pos.x - epsilon_ &&
vert->pos.y * above > start->pos.y * above - 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_)) { vert->InsideEdge(edge, epsilon_, true) && vert->IsReflex(epsilon_)) {
connector = vert; connector = vert;
} }
@ -803,7 +808,7 @@ class EarClip {
// Recalculate the cost of the Vert v ear, updating it in the queue by // Recalculate the cost of the Vert v ear, updating it in the queue by
// removing and reinserting it. // removing and reinserting it.
void ProcessEar(VertItr v, IdxCollider &collider) { void ProcessEar(VertItr v, IdxCollider& collider) {
if (v->ear != earsQueue_.end()) { if (v->ear != earsQueue_.end()) {
earsQueue_.erase(v->ear); earsQueue_.erase(v->ear);
v->ear = earsQueue_.end(); v->ear = earsQueue_.end();
@ -823,35 +828,16 @@ class EarClip {
// epsilon_. Each ear uses this BVH to quickly find a subset of vertices to // epsilon_. Each ear uses this BVH to quickly find a subset of vertices to
// check for cost. // check for cost.
IdxCollider VertCollider(VertItr start) const { IdxCollider VertCollider(VertItr start) const {
Vec<Box> vertBox; ZoneScoped;
Vec<uint32_t> vertMorton;
std::vector<VertItr> itr; std::vector<VertItr> itr;
const Box box(vec3(bBox_.min, 0), vec3(bBox_.max, 0)); Vec<PolyVert> points;
Loop(start, [&itr, &points](VertItr v) {
Loop(start, [&vertBox, &vertMorton, &itr, &box, this](VertItr v) { points.push_back({v->pos, static_cast<int>(itr.size())});
itr.push_back(v); 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()) { BuildTwoDTree(points);
return {Collider(), itr}; return {std::move(points), std::move(itr)};
}
const int numVert = itr.size();
Vec<int> 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};
} }
// The main ear-clipping loop. This is called once for each simple polygon - // The main ear-clipping loop. This is called once for each simple polygon -
@ -907,7 +893,7 @@ class EarClip {
void Dump(VertItrC start) const { void Dump(VertItrC start) const {
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (!params.verbose) return; if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return;
VertItrC v = start; VertItrC v = start;
std::cout << "show(array([" << std::setprecision(15) << std::endl; std::cout << "show(array([" << std::setprecision(15) << std::endl;
do { do {
@ -944,16 +930,21 @@ namespace manifold {
* references back to the original vertices. * references back to the original vertices.
* @param epsilon The value of &epsilon;, bounding the uncertainty of the * @param epsilon The value of &epsilon;, bounding the uncertainty of the
* input. * 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<ivec3> The triangles, referencing the original * @return std::vector<ivec3> The triangles, referencing the original
* vertex indicies. * vertex indicies.
*/ */
std::vector<ivec3> TriangulateIdx(const PolygonsIdx &polys, double epsilon) { std::vector<ivec3> TriangulateIdx(const PolygonsIdx& polys, double epsilon,
bool allowConvex) {
std::vector<ivec3> triangles; std::vector<ivec3> triangles;
double updatedEpsilon = epsilon; double updatedEpsilon = epsilon;
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
try { try {
#endif #endif
if (IsConvex(polys, epsilon)) { // fast path if (allowConvex && IsConvex(polys, epsilon)) { // fast path
triangles = TriangulateConvex(polys); triangles = TriangulateConvex(polys);
} else { } else {
EarClip triangulator(polys, epsilon); EarClip triangulator(polys, epsilon);
@ -961,18 +952,18 @@ std::vector<ivec3> TriangulateIdx(const PolygonsIdx &polys, double epsilon) {
updatedEpsilon = triangulator.GetPrecision(); updatedEpsilon = triangulator.GetPrecision();
} }
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (params.intermediateChecks) { if (ManifoldParams().intermediateChecks) {
CheckTopology(triangles, polys); CheckTopology(triangles, polys);
if (!params.processOverlaps) { if (!ManifoldParams().processOverlaps) {
CheckGeometry(triangles, polys, 2 * updatedEpsilon); CheckGeometry(triangles, polys, 2 * updatedEpsilon);
} }
} }
} catch (const geometryErr &e) { } catch (const geometryErr& e) {
if (!params.suppressErrors) { if (!ManifoldParams().suppressErrors) {
PrintFailure(e, polys, triangles, updatedEpsilon); PrintFailure(e, polys, triangles, updatedEpsilon);
} }
throw; throw;
} catch (const std::exception &e) { } catch (const std::exception& e) {
PrintFailure(e, polys, triangles, updatedEpsilon); PrintFailure(e, polys, triangles, updatedEpsilon);
throw; throw;
} }
@ -989,22 +980,25 @@ std::vector<ivec3> TriangulateIdx(const PolygonsIdx &polys, double epsilon) {
* polygons and/or holes. * polygons and/or holes.
* @param epsilon The value of &epsilon;, bounding the uncertainty of the * @param epsilon The value of &epsilon;, bounding the uncertainty of the
* input. * 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<ivec3> The triangles, referencing the original * @return std::vector<ivec3> The triangles, referencing the original
* polygon points in order. * polygon points in order.
*/ */
std::vector<ivec3> Triangulate(const Polygons &polygons, double epsilon) { std::vector<ivec3> Triangulate(const Polygons& polygons, double epsilon,
bool allowConvex) {
int idx = 0; int idx = 0;
PolygonsIdx polygonsIndexed; PolygonsIdx polygonsIndexed;
for (const auto &poly : polygons) { for (const auto& poly : polygons) {
SimplePolygonIdx simpleIndexed; SimplePolygonIdx simpleIndexed;
for (const vec2 &polyVert : poly) { for (const vec2& polyVert : poly) {
simpleIndexed.push_back({polyVert, idx++}); simpleIndexed.push_back({polyVert, idx++});
} }
polygonsIndexed.push_back(simpleIndexed); polygonsIndexed.push_back(simpleIndexed);
} }
return TriangulateIdx(polygonsIndexed, epsilon); return TriangulateIdx(polygonsIndexed, epsilon, allowConvex);
} }
ExecutionParams &PolygonParams() { return params; }
} // namespace manifold } // namespace manifold

View File

@ -14,9 +14,13 @@
#include <limits> #include <limits>
#include "./impl.h" #if MANIFOLD_PAR == 1
#include "./parallel.h" #include <tbb/combinable.h>
#include "./tri_dist.h" #endif
#include "impl.h"
#include "parallel.h"
#include "tri_dist.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
@ -64,48 +68,6 @@ struct CurvatureAngles {
} }
}; };
struct UpdateProperties {
VecView<ivec3> triProp;
VecView<double> properties;
VecView<uint8_t> counters;
VecView<const double> oldProperties;
VecView<const Halfedge> halfedge;
VecView<const double> meanCurvature;
VecView<const double> 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<std::atomic<uint8_t>*>(&counters[propVert]),
static_cast<uint8_t>(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 { struct CheckHalfedges {
VecView<const Halfedge> halfedges; VecView<const Halfedge> halfedges;
@ -138,33 +100,8 @@ struct CheckCCW {
for (int i : {0, 1, 2}) for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedges[3 * face + i].startVert]; v[i] = projection * vertPos[halfedges[3 * face + i].startVert];
int ccw = CCW(v[0], v[1], v[2], std::abs(tol)); const int ccw = CCW(v[0], v[1], v[2], std::abs(tol));
bool check = tol > 0 ? ccw >= 0 : ccw == 0; return 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<long>(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;
} }
}; };
} // namespace } // namespace
@ -211,55 +148,58 @@ std::mutex dump_lock;
* Note that this is not checking for epsilon-validity. * Note that this is not checking for epsilon-validity.
*/ */
bool Manifold::Impl::IsSelfIntersecting() const { bool Manifold::Impl::IsSelfIntersecting() const {
const double epsilonSq = epsilon_ * epsilon_; const double ep = 2 * epsilon_;
const double epsilonSq = ep * ep;
Vec<Box> faceBox; Vec<Box> faceBox;
Vec<uint32_t> faceMorton; Vec<uint32_t> faceMorton;
GetFaceBoxMorton(faceBox, faceMorton); GetFaceBoxMorton(faceBox, faceMorton);
SparseIndices collisions = collider_.Collisions<true>(faceBox.cview());
const bool verbose = ManifoldParams().verbose; std::atomic<bool> intersecting(false);
return !all_of(countAt(0), countAt(collisions.size()), [&](size_t i) {
size_t x = collisions.Get(i, false); auto f = [&](int tri0, int tri1) {
size_t y = collisions.Get(i, true); std::array<vec3, 3> triVerts0, triVerts1;
std::array<vec3, 3> tri_x, tri_y;
for (int i : {0, 1, 2}) { for (int i : {0, 1, 2}) {
tri_x[i] = vertPos_[halfedge_[3 * x + i].startVert]; triVerts0[i] = vertPos_[halfedge_[3 * tri0 + i].startVert];
tri_y[i] = vertPos_[halfedge_[3 * y + 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 // check. we relax the sharing criteria a bit to allow for at most
// distance epsilon squared // distance epsilon squared
for (int i : {0, 1, 2}) for (int i : {0, 1, 2})
for (int j : {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 // try to move the triangles around the normal of the other face
std::array<vec3, 3> tmp_x, tmp_y; std::array<vec3, 3> tmp0, tmp1;
for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] + epsilon_ * faceNormal_[y]; for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] + ep * faceNormal_[tri1];
if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true; if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return;
for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] - epsilon_ * faceNormal_[y]; for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] - ep * faceNormal_[tri1];
if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true; if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return;
for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] + epsilon_ * faceNormal_[x]; for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] + ep * faceNormal_[tri0];
if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true; if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return;
for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] - epsilon_ * faceNormal_[x]; for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] - ep * faceNormal_[tri0];
if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true; if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return;
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
if (verbose) { if (ManifoldParams().verbose > 0) {
dump_lock.lock(); dump_lock.lock();
std::cout << "intersecting:" << std::endl; 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; 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; std::cout << std::endl;
dump_lock.unlock(); dump_lock.unlock();
} }
#endif #endif
return false; intersecting.store(true);
} }
return true; };
});
auto recorder = MakeSimpleRecorder(f);
collider_.Collisions<true>(faceBox.cview(), recorder);
return intersecting.load();
} }
/** /**
@ -335,20 +275,36 @@ void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
const int oldNumProp = NumProp(); const int oldNumProp = NumProp();
const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1); const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1);
const Vec<double> oldProperties = meshRelation_.properties; const Vec<double> oldProperties = properties_;
meshRelation_.properties = Vec<double>(numProp * NumPropVert(), 0); properties_ = Vec<double>(numProp * NumPropVert(), 0);
meshRelation_.numProp = numProp; numProp_ = numProp;
if (meshRelation_.triProperties.size() == 0) {
meshRelation_.triProperties.resize(NumTri());
}
const Vec<uint8_t> counters(NumPropVert(), 0); Vec<uint8_t> counters(NumPropVert(), 0);
for_each_n( for_each_n(policy, countAt(0_uz), NumTri(), [&](const size_t tri) {
policy, countAt(0_uz), NumTri(), for (const int i : {0, 1, 2}) {
UpdateProperties({meshRelation_.triProperties, meshRelation_.properties, const Halfedge& edge = halfedge_[3 * tri + i];
counters, oldProperties, halfedge_, vertMeanCurvature, const int vert = edge.startVert;
vertGaussianCurvature, oldNumProp, numProp, gaussianIdx, const int propVert = edge.propVert;
meanIdx}));
auto old = std::atomic_exchange(
reinterpret_cast<std::atomic<uint8_t>*>(&counters[propVert]),
static_cast<uint8_t>(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<const ivec3> triVerts) const {
return minmax[0] >= 0 && minmax[1] < static_cast<int>(NumVert()); return minmax[0] >= 0 && minmax[1] < static_cast<int>(NumVert());
} }
struct MinDistanceRecorder {
using Local = double;
const Manifold::Impl &self, &other;
#if MANIFOLD_PAR == 1
tbb::combinable<double> store = tbb::combinable<double>(
[]() { return std::numeric_limits<double>::infinity(); });
Local& local() { return store.local(); }
double get() {
double result = std::numeric_limits<double>::infinity();
store.combine_each([&](double& val) { result = std::min(result, val); });
return result;
}
#else
double result = std::numeric_limits<double>::infinity();
Local& local() { return result; }
double get() { return result; }
#endif
void record(int triOther, int tri, double& minDistance) {
std::array<vec3, 3> p;
std::array<vec3, 3> 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 * Returns the minimum gap between two manifolds. Returns a double between
* 0 and searchLength. * 0 and searchLength.
@ -422,26 +408,11 @@ double Manifold::Impl::MinGap(const Manifold::Impl& other,
box.max + vec3(searchLength)); box.max + vec3(searchLength));
}); });
SparseIndices collisions = collider_.Collisions(faceBoxOther.cview()); MinDistanceRecorder recorder{*this, other};
collider_.Collisions<false, Box, MinDistanceRecorder>(faceBoxOther.cview(),
double minDistanceSquared = transform_reduce( recorder, false);
countAt(0_uz), countAt(collisions.size()), searchLength * searchLength, double minDistanceSquared =
[](double a, double b) { return std::min(a, b); }, std::min(recorder.get(), searchLength * searchLength);
[&collisions, this, &other](int i) {
const int tri = collisions.Get(i, 1);
const int triOther = collisions.Get(i, 0);
std::array<vec3, 3> p;
std::array<vec3, 3> 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);
});
return sqrt(minDistanceSquared); return sqrt(minDistanceSquared);
}; };

View File

@ -20,7 +20,7 @@
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>
#include "./impl.h" #include "impl.h"
namespace manifold { namespace manifold {
@ -289,7 +289,7 @@ std::pair<Vec<Halfedge>, Vec<vec3>> QuickHull::buildMesh(double epsilon) {
for_each( for_each(
autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(), autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),
[&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; }); [&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; });
counts.resize(originalVertexData.size() + 1); counts.resize_nofill(originalVertexData.size() + 1);
fill(counts.begin(), counts.end(), 0); fill(counts.begin(), counts.end(), 0);
// remove unused vertices // remove unused vertices
@ -804,7 +804,7 @@ void QuickHull::setupInitialTetrahedron() {
std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() { std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() {
auto r = indexVectorPool.get(); auto r = indexVectorPool.get();
r->resize(0); r->clear();
return r; return r;
} }
@ -851,10 +851,9 @@ void Manifold::Impl::Hull(VecView<vec3> vertPos) {
std::tie(halfedge_, vertPos_) = qh.buildMesh(); std::tie(halfedge_, vertPos_) = qh.buildMesh();
CalculateBBox(); CalculateBBox();
SetEpsilon(); SetEpsilon();
CalculateNormals();
InitializeOriginal(); InitializeOriginal();
Finish(); Finish();
CreateFaces(); MarkCoplanar();
} }
} // namespace manifold } // namespace manifold

View File

@ -58,8 +58,8 @@
#include <deque> #include <deque>
#include <vector> #include <vector>
#include "./shared.h" #include "shared.h"
#include "./vec.h" #include "vec.h"
namespace manifold { namespace manifold {

View File

@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./hashtable.h" #include "hashtable.h"
#include "./impl.h" #include "impl.h"
#include "./parallel.h"
#include "./utils.h"
#include "./vec.h"
#include "manifold/manifold.h" #include "manifold/manifold.h"
#include "parallel.h"
#include "utils.h"
#include "vec.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
@ -95,13 +95,14 @@ ivec4 Neighbor(ivec4 base, int i) {
return neighborIndex; return neighborIndex;
} }
Uint64 EncodeIndex(ivec4 gridPos, ivec3 gridPow) { uint64_t EncodeIndex(ivec4 gridPos, ivec3 gridPow) {
return static_cast<Uint64>(gridPos.w) | static_cast<Uint64>(gridPos.z) << 1 | return static_cast<uint64_t>(gridPos.w) |
static_cast<Uint64>(gridPos.y) << (1 + gridPow.z) | static_cast<uint64_t>(gridPos.z) << 1 |
static_cast<Uint64>(gridPos.x) << (1 + gridPow.z + gridPow.y); static_cast<uint64_t>(gridPos.y) << (1 + gridPow.z) |
static_cast<uint64_t>(gridPos.x) << (1 + gridPow.z + gridPow.y);
} }
ivec4 DecodeIndex(Uint64 idx, ivec3 gridPow) { ivec4 DecodeIndex(uint64_t idx, ivec3 gridPow) {
ivec4 gridPos; ivec4 gridPos;
gridPos.w = idx & 1; gridPos.w = idx & 1;
idx = idx >> 1; idx = idx >> 1;
@ -211,7 +212,7 @@ struct NearSurface {
const double level; const double level;
const double tol; const double tol;
inline void operator()(Uint64 index) { inline void operator()(uint64_t index) {
ZoneScoped; ZoneScoped;
if (gridVerts.Full()) return; if (gridVerts.Full()) return;
@ -296,7 +297,7 @@ struct ComputeVerts {
void operator()(int idx) { void operator()(int idx) {
ZoneScoped; ZoneScoped;
Uint64 baseKey = gridVerts.KeyAt(idx); uint64_t baseKey = gridVerts.KeyAt(idx);
if (baseKey == kOpen) return; if (baseKey == kOpen) return;
GridVert& gridVert = gridVerts.At(idx); GridVert& gridVert = gridVerts.At(idx);
@ -358,7 +359,7 @@ struct BuildTris {
void operator()(int idx) { void operator()(int idx) {
ZoneScoped; ZoneScoped;
Uint64 baseKey = gridVerts.KeyAt(idx); uint64_t baseKey = gridVerts.KeyAt(idx);
if (baseKey == kOpen) return; if (baseKey == kOpen) return;
const GridVert& base = gridVerts.At(idx); const GridVert& base = gridVerts.At(idx);
@ -467,7 +468,7 @@ Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
const vec3 spacing = dim / (vec3(gridSize - 1)); const vec3 spacing = dim / (vec3(gridSize - 1));
const ivec3 gridPow(la::log2(gridSize + 2) + 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 // Parallel policies violate will crash language runtimes with runtime locks
// that expect to not be called back by unregistered threads. This allows // that expect to not be called back by unregistered threads. This allows
@ -479,15 +480,15 @@ Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
Vec<double> voxels(maxIndex); Vec<double> voxels(maxIndex);
for_each_n( for_each_n(
pol, countAt(0_uz), maxIndex, 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, voxels[idx] = BoundedSDF(DecodeIndex(idx, gridPow) - kVoxelOffset,
origin, spacing, gridSize, level, sdf); origin, spacing, gridSize, level, sdf);
}); });
size_t tableSize = std::min( size_t tableSize = std::min(
2 * maxIndex, static_cast<Uint64>(10 * la::pow(maxIndex, 0.667))); 2 * maxIndex, static_cast<uint64_t>(10 * la::pow(maxIndex, 0.667)));
HashTable<GridVert> gridVerts(tableSize); HashTable<GridVert> gridVerts(tableSize);
vertPos.resize(gridVerts.Size() * 7); vertPos.resize_nofill(gridVerts.Size() * 7);
while (1) { while (1) {
Vec<int> index(1, 0); Vec<int> index(1, 0);
@ -497,7 +498,7 @@ Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
if (gridVerts.Full()) { // Resize HashTable if (gridVerts.Full()) { // Resize HashTable
const vec3 lastVert = vertPos[index[0] - 1]; const vec3 lastVert = vertPos[index[0] - 1];
const Uint64 lastIndex = const uint64_t lastIndex =
EncodeIndex(ivec4(ivec3((lastVert - origin) / spacing), 1), gridPow); EncodeIndex(ivec4(ivec3((lastVert - origin) / spacing), 1), gridPow);
const double ratio = static_cast<double>(maxIndex) / lastIndex; const double ratio = static_cast<double>(maxIndex) / lastIndex;
@ -529,6 +530,7 @@ Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
pImpl_->RemoveUnreferencedVerts(); pImpl_->RemoveUnreferencedVerts();
pImpl_->Finish(); pImpl_->Finish();
pImpl_->InitializeOriginal(); pImpl_->InitializeOriginal();
pImpl_->MarkCoplanar();
return Manifold(pImpl_); return Manifold(pImpl_);
} }
} // namespace manifold } // namespace manifold

View File

@ -14,10 +14,9 @@
#pragma once #pragma once
#include "./parallel.h" #include "parallel.h"
#include "./sparse.h" #include "utils.h"
#include "./utils.h" #include "vec.h"
#include "./vec.h"
namespace manifold { namespace manifold {
@ -120,6 +119,7 @@ inline vec3 GetBarycentric(const vec3& v, const mat3& triPos,
struct Halfedge { struct Halfedge {
int startVert, endVert; int startVert, endVert;
int pairedHalfedge; int pairedHalfedge;
int propVert;
bool IsForward() const { return startVert < endVert; } bool IsForward() const { return startVert < endVert; }
bool operator<(const Halfedge& other) const { bool operator<(const Halfedge& other) const {
return startVert == other.startVert ? endVert < other.endVert return startVert == other.startVert ? endVert < other.endVert
@ -142,12 +142,13 @@ struct TriRef {
int originalID; int originalID;
/// Probably the triangle index of the original triangle this was part of: /// 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. /// 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; int faceID;
/// Triangles with the same coplanar ID are coplanar.
int coplanarID;
bool SameFace(const TriRef& other) const { 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<TmpEdge> inline CreateTmpEdges(const Vec<Halfedge>& halfedge) {
return edges; return edges;
} }
template <const bool inverted>
struct ReindexEdge {
VecView<const TmpEdge> edges;
SparseIndices& indices;
void operator()(size_t i) {
int& edge = indices.Get(i, inverted);
edge = edges[edge].halfedgeIdx;
}
};
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) { inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) {
return stream << "startVert = " << edge.startVert return stream << "startVert = " << edge.startVert
<< ", endVert = " << edge.endVert << ", endVert = " << edge.endVert
<< ", pairedHalfedge = " << edge.pairedHalfedge; << ", pairedHalfedge = " << edge.pairedHalfedge
<< ", propVert = " << edge.propVert;
} }
inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) { 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) { inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) {
return stream << "meshID: " << ref.meshID return stream << "meshID: " << ref.meshID
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri << ", originalID: " << ref.originalID
<< ", faceID: " << ref.faceID; << ", faceID: " << ref.faceID
<< ", coplanarID: " << ref.coplanarID;
} }
#endif #endif
} // namespace manifold } // namespace manifold

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./impl.h" #include "impl.h"
#include "./parallel.h" #include "parallel.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
@ -254,13 +254,10 @@ namespace manifold {
* normalIdx shows the beginning of where normals are stored in the properties. * normalIdx shows the beginning of where normals are stored in the properties.
*/ */
vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const { vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const {
const int tri = halfedge / 3; const int prop = halfedge_[halfedge].propVert;
const int j = halfedge % 3;
const int prop = meshRelation_.triProperties[tri][j];
vec3 normal; vec3 normal;
for (const int i : {0, 1, 2}) { for (const int i : {0, 1, 2}) {
normal[i] = normal[i] = properties_[prop * numProp_ + normalIdx + i];
meshRelation_.properties[prop * meshRelation_.numProp + normalIdx + i];
} }
return normal; return normal;
} }
@ -320,12 +317,12 @@ bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const {
// sharpenedEdges are referenced to the input Mesh, but the triangles have // sharpenedEdges are referenced to the input Mesh, but the triangles have
// been sorted in creating the Manifold, so the indices are converted using // been sorted in creating the Manifold, so the indices are converted using
// meshRelation_. // meshRelation_.faceID, which temporarily holds the mapping.
std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges( std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges(
const std::vector<Smoothness>& sharpenedEdges) const { const std::vector<Smoothness>& sharpenedEdges) const {
std::unordered_map<int, int> oldHalfedge2New; std::unordered_map<int, int> oldHalfedge2New;
for (size_t tri = 0; tri < NumTri(); ++tri) { 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; for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
} }
std::vector<Smoothness> newSharp = sharpenedEdges; std::vector<Smoothness> newSharp = sharpenedEdges;
@ -371,7 +368,7 @@ Vec<bool> Manifold::Impl::FlatFaces() const {
// gets -1, and if there are more than one it gets -2. // gets -1, and if there are more than one it gets -2.
Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const { Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const {
Vec<int> vertFlatFace(NumVert(), -1); Vec<int> vertFlatFace(NumVert(), -1);
Vec<TriRef> vertRef(NumVert(), {-1, -1, -1}); Vec<TriRef> vertRef(NumVert(), {-1, -1, -1, -1});
for (size_t tri = 0; tri < NumTri(); ++tri) { for (size_t tri = 0; tri < NumTri(); ++tri) {
if (flatFaces[tri]) { if (flatFaces[tri]) {
for (const int j : {0, 1, 2}) { for (const int j : {0, 1, 2}) {
@ -439,7 +436,6 @@ void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {
if (normalIdx < 0) return; if (normalIdx < 0) return;
const int oldNumProp = NumProp(); const int oldNumProp = NumProp();
const int numTri = NumTri();
Vec<bool> triIsFlatFace = FlatFaces(); Vec<bool> triIsFlatFace = FlatFaces();
Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace); Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);
@ -470,164 +466,153 @@ void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {
const int numProp = std::max(oldNumProp, normalIdx + 3); const int numProp = std::max(oldNumProp, normalIdx + 3);
Vec<double> oldProperties(numProp * NumPropVert(), 0); Vec<double> oldProperties(numProp * NumPropVert(), 0);
meshRelation_.properties.swap(oldProperties); properties_.swap(oldProperties);
meshRelation_.numProp = numProp; 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<ivec3> oldTriProp(numTri, {-1, -1, -1});
meshRelation_.triProperties.swap(oldTriProp);
for (int tri = 0; tri < numTri; ++tri) { Vec<int> oldHalfedgeProp(halfedge_.size());
for (const int i : {0, 1, 2}) { for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),
if (meshRelation_.triProperties[tri][i] >= 0) continue; [this, &oldHalfedgeProp](int i) {
int startEdge = 3 * tri + i; oldHalfedgeProp[i] = halfedge_[i].propVert;
const int vert = halfedge_[startEdge].startVert; halfedge_[i].propVert = -1;
});
if (vertNumSharp[vert] < 2) { // vertex has single normal const int numEdge = halfedge_.size();
const vec3 normal = vertFlatFace[vert] >= 0 for (int startEdge = 0; startEdge < numEdge; ++startEdge) {
? faceNormal_[vertFlatFace[vert]] if (halfedge_[startEdge].propVert >= 0) continue;
: vertNormal_[vert]; const int vert = halfedge_[startEdge].startVert;
int lastProp = -1;
ForVert(startEdge, [&](int current) { if (vertNumSharp[vert] < 2) { // vertex has single normal
const int thisTri = current / 3; const vec3 normal = vertFlatFace[vert] >= 0
const int j = current - 3 * thisTri; ? faceNormal_[vertFlatFace[vert]]
const int prop = oldTriProp[thisTri][j]; : vertNormal_[vert];
meshRelation_.triProperties[thisTri][j] = prop; int lastProp = -1;
if (prop == lastProp) return; ForVert(startEdge, [&](int current) {
lastProp = prop; const int prop = oldHalfedgeProp[current];
// update property vertex halfedge_[current].propVert = prop;
auto start = oldProperties.begin() + prop * oldNumProp; 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<int> group;
// Length number of normals
std::vector<vec3> 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<FaceEdge>(
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, std::copy(start, start + oldNumProp,
meshRelation_.properties.begin() + prop * numProp); properties_.begin() + newProp * numProp);
for (const int i : {0, 1, 2}) for (const int i : {0, 1, 2}) {
meshRelation_.properties[prop * numProp + normalIdx + i] = properties_[newProp * numProp + normalIdx + i] =
normal[i]; normals[group[idx]][i];
});
} else { // vertex has multiple normals
const vec3 centerPos = vertPos_[vert];
// Length degree
std::vector<int> group;
// Length number of normals
std::vector<vec3> 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; } else if (prop != lastProp) {
prevFace = face; // update property vertex
} while (current != startEdge); lastProp = prop;
newProp = prop;
const int endEdge = current; std::copy(start, start + oldNumProp,
properties_.begin() + prop * numProp);
struct FaceEdge { for (const int i : {0, 1, 2})
int face; properties_[prop * numProp + normalIdx + i] =
vec3 edgeVec; normals[group[idx]][i];
};
// calculate pseudo-normals between each sharp edge
ForVert<FaceEdge>(
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);
} }
int lastGroup = 0; // point to updated property vertex
int lastProp = -1; halfedge_[current1].propVert = newProp;
int newProp = -1; ++idx;
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;
});
}
} }
} }
} }
@ -770,7 +755,7 @@ void Manifold::Impl::CreateTangents(int normalIdx) {
ZoneScoped; ZoneScoped;
const int numVert = NumVert(); const int numVert = NumVert();
const int numHalfedge = halfedge_.size(); const int numHalfedge = halfedge_.size();
halfedgeTangent_.resize(0); halfedgeTangent_.clear();
Vec<vec4> tangent(numHalfedge); Vec<vec4> tangent(numHalfedge);
Vec<bool> fixedHalfedge(numHalfedge, false); Vec<bool> fixedHalfedge(numHalfedge, false);
@ -854,7 +839,7 @@ void Manifold::Impl::CreateTangents(int normalIdx) {
void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) { void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {
ZoneScoped; ZoneScoped;
const int numHalfedge = halfedge_.size(); const int numHalfedge = halfedge_.size();
halfedgeTangent_.resize(0); halfedgeTangent_.clear();
Vec<vec4> tangent(numHalfedge); Vec<vec4> tangent(numHalfedge);
Vec<bool> fixedHalfedge(numHalfedge, false); Vec<bool> fixedHalfedge(numHalfedge, false);
@ -994,9 +979,11 @@ void Manifold::Impl::Refine(std::function<int(vec3, vec4, vec4)> edgeDivisions,
InterpTri({vertPos_, vertBary, &old})); InterpTri({vertPos_, vertBary, &old}));
} }
halfedgeTangent_.resize(0); halfedgeTangent_.clear();
Finish(); Finish();
CreateFaces(); if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
MarkCoplanar();
}
meshRelation_.originalID = -1; meshRelation_.originalID = -1;
} }

View File

@ -15,8 +15,9 @@
#include <atomic> #include <atomic>
#include <set> #include <set>
#include "./impl.h" #include "impl.h"
#include "./parallel.h" #include "parallel.h"
#include "shared.h"
namespace { namespace {
using namespace manifold; using namespace manifold;
@ -31,37 +32,6 @@ uint32_t MortonCode(vec3 position, Box bBox) {
return Collider::MortonCode(position, bBox); return Collider::MortonCode(position, bBox);
} }
struct Reindex {
VecView<const int> indexInv;
void operator()(Halfedge& edge) {
if (edge.startVert < 0) return;
edge.startVert = indexInv[edge.startVert];
edge.endVert = indexInv[edge.endVert];
}
};
struct MarkProp {
VecView<int> keep;
void operator()(ivec3 triProp) {
for (const int i : {0, 1, 2}) {
reinterpret_cast<std::atomic<int>*>(&keep[triProp[i]])
->store(1, std::memory_order_relaxed);
}
}
};
struct ReindexProps {
VecView<const int> old2new;
void operator()(ivec3& triProp) {
for (const int i : {0, 1, 2}) {
triProp[i] = old2new[triProp[i]];
}
}
};
struct ReindexFace { struct ReindexFace {
VecView<Halfedge> halfedge; VecView<Halfedge> halfedge;
VecView<vec4> halfedgeTangent; VecView<vec4> halfedgeTangent;
@ -180,18 +150,20 @@ bool MergeMeshGLP(MeshGLP<Precision, I>& mesh) {
Permute(vertMorton, vertNew2Old); Permute(vertMorton, vertNew2Old);
Permute(vertBox, vertNew2Old); Permute(vertBox, vertNew2Old);
Permute(openVerts, vertNew2Old); Permute(openVerts, vertNew2Old);
Collider collider(vertBox, vertMorton);
SparseIndices toMerge = collider.Collisions<true>(vertBox.cview());
Collider collider(vertBox, vertMorton);
UnionFind<> uf(numVert); UnionFind<> uf(numVert);
auto f = [&uf, &openVerts](int a, int b) {
return uf.unionXY(openVerts[a], openVerts[b]);
};
auto recorder = MakeSimpleRecorder(f);
collider.Collisions<true>(vertBox.cview(), recorder, false);
for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) { for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) {
uf.unionXY(static_cast<int>(mesh.mergeFromVert[i]), uf.unionXY(static_cast<int>(mesh.mergeFromVert[i]),
static_cast<int>(mesh.mergeToVert[i])); static_cast<int>(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.mergeToVert.clear();
mesh.mergeFromVert.clear(); mesh.mergeFromVert.clear();
@ -221,7 +193,7 @@ void Manifold::Impl::Finish() {
SetEpsilon(epsilon_); SetEpsilon(epsilon_);
if (!bBox_.IsFinite()) { if (!bBox_.IsFinite()) {
// Decimated out of existence - early out. // Decimated out of existence - early out.
MarkFailure(Error::NoError); MakeEmpty(Error::NoError);
return; return;
} }
@ -237,31 +209,33 @@ void Manifold::Impl::Finish() {
"Not an even number of faces after sorting faces!"); "Not an even number of faces after sorting faces!");
#ifdef MANIFOLD_DEBUG #ifdef MANIFOLD_DEBUG
auto MaxOrMinus = [](int a, int b) { if (ManifoldParams().intermediateChecks) {
return std::min(a, b) < 0 ? -1 : std::max(a, b); 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}; int face = 0;
for (size_t i = 0; i < halfedge_.size(); i++) { Halfedge extrema = {0, 0, 0};
Halfedge e = halfedge_[i]; for (size_t i = 0; i < halfedge_.size(); i++) {
if (!e.IsForward()) std::swap(e.startVert, e.endVert); Halfedge e = halfedge_[i];
extrema.startVert = std::min(extrema.startVert, e.startVert); if (!e.IsForward()) std::swap(e.startVert, e.endVert);
extrema.endVert = std::min(extrema.endVert, e.endVert); extrema.startVert = std::min(extrema.startVert, e.startVert);
extrema.pairedHalfedge = extrema.endVert = std::min(extrema.endVert, e.endVert);
MaxOrMinus(extrema.pairedHalfedge, e.pairedHalfedge); extrema.pairedHalfedge =
face = MaxOrMinus(face, i / 3); 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<int>(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<int>(NumEdge()),
topologyErr, "Halfedge index exceeds number of halfedges!");
DEBUG_ASSERT(face >= 0, topologyErr, "Face index is negative!");
DEBUG_ASSERT(face < static_cast<int>(NumTri()), topologyErr,
"Face index exceeds number of faces!");
} }
DEBUG_ASSERT(extrema.startVert >= 0, topologyErr,
"Vertex index is negative!");
DEBUG_ASSERT(extrema.endVert < static_cast<int>(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<int>(NumEdge()),
topologyErr, "Halfedge index exceeds number of halfedges!");
DEBUG_ASSERT(face >= 0, topologyErr, "Face index is negative!");
DEBUG_ASSERT(face < static_cast<int>(NumTri()), topologyErr,
"Face index exceeds number of faces!");
#endif #endif
DEBUG_ASSERT(meshRelation_.triRef.size() == NumTri() || 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 // Verts were flagged for removal with NaNs and assigned kNoCode to sort
// them to the end, which allows them to be removed. // them to the end, which allows them to be removed.
const auto newNumVert = std::find_if(vertNew2Old.begin(), vertNew2Old.end(), const auto newNumVert =
[&vertMorton](const int vert) { std::lower_bound(vertNew2Old.begin(), vertNew2Old.end(), kNoCode,
return vertMorton[vert] == kNoCode; [&vertMorton](const int vert, const uint32_t val) {
}) - return vertMorton[vert] < val;
vertNew2Old.begin(); }) -
vertNew2Old.begin();
vertNew2Old.resize(newNumVert); vertNew2Old.resize(newNumVert);
Permute(vertPos_, vertNew2Old); Permute(vertPos_, vertNew2Old);
@ -326,31 +301,41 @@ void Manifold::Impl::ReindexVerts(const Vec<int>& vertNew2Old,
Vec<int> vertOld2New(oldNumVert); Vec<int> vertOld2New(oldNumVert);
scatter(countAt(0), countAt(static_cast<int>(NumVert())), vertNew2Old.begin(), scatter(countAt(0), countAt(static_cast<int>(NumVert())), vertNew2Old.begin(),
vertOld2New.begin()); vertOld2New.begin());
const bool hasProp = NumProp() > 0;
for_each(autoPolicy(oldNumVert, 1e5), halfedge_.begin(), halfedge_.end(), 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() { void Manifold::Impl::CompactProps() {
ZoneScoped; 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<int> keep(numVerts, 0); Vec<int> keep(numVerts, 0);
auto policy = autoPolicy(numVerts, 1e5); auto policy = autoPolicy(numVerts, 1e5);
for_each(policy, meshRelation_.triProperties.cbegin(), for_each(policy, halfedge_.cbegin(), halfedge_.cend(), [&keep](Halfedge h) {
meshRelation_.triProperties.cend(), MarkProp({keep})); reinterpret_cast<std::atomic<int>*>(&keep[h.propVert])
->store(1, std::memory_order_relaxed);
});
Vec<int> propOld2New(numVerts + 1, 0); Vec<int> propOld2New(numVerts + 1, 0);
inclusive_scan(keep.begin(), keep.end(), propOld2New.begin() + 1); inclusive_scan(keep.begin(), keep.end(), propOld2New.begin() + 1);
Vec<double> oldProp = meshRelation_.properties; Vec<double> oldProp = properties_;
const int numVertsNew = propOld2New[numVerts]; const int numVertsNew = propOld2New[numVerts];
const int numProp = meshRelation_.numProp; auto& properties = properties_;
auto& properties = meshRelation_.properties; properties.resize_nofill(numProp * numVertsNew);
properties.resize(numProp * numVertsNew);
for_each_n( for_each_n(
policy, countAt(0), numVerts, policy, countAt(0), numVerts,
[&properties, &oldProp, &propOld2New, &keep, &numProp](const int oldIdx) { [&properties, &oldProp, &propOld2New, &keep, &numProp](const int oldIdx) {
@ -360,8 +345,10 @@ void Manifold::Impl::CompactProps() {
oldProp[oldIdx * numProp + p]; oldProp[oldIdx * numProp + p];
} }
}); });
for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(), for_each(policy, halfedge_.begin(), halfedge_.end(),
ReindexProps({propOld2New})); [&propOld2New](Halfedge& edge) {
edge.propVert = propOld2New[edge.propVert];
});
} }
/** /**
@ -372,8 +359,9 @@ void Manifold::Impl::CompactProps() {
void Manifold::Impl::GetFaceBoxMorton(Vec<Box>& faceBox, void Manifold::Impl::GetFaceBoxMorton(Vec<Box>& faceBox,
Vec<uint32_t>& faceMorton) const { Vec<uint32_t>& faceMorton) const {
ZoneScoped; ZoneScoped;
faceBox.resize(NumTri()); // faceBox should be initialized
faceMorton.resize(NumTri()); faceBox.resize(NumTri(), Box());
faceMorton.resize_nofill(NumTri());
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(), for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
[this, &faceBox, &faceMorton](const int face) { [this, &faceBox, &faceMorton](const int face) {
// Removed tris are marked by all halfedges having pairedHalfedge // Removed tris are marked by all halfedges having pairedHalfedge
@ -413,11 +401,12 @@ void Manifold::Impl::SortFaces(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton) {
// Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode // Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode
// to sort them to the end, which allows them to be removed. // to sort them to the end, which allows them to be removed.
const int newNumTri = std::find_if(faceNew2Old.begin(), faceNew2Old.end(), const int newNumTri =
[&faceMorton](const int face) { std::lower_bound(faceNew2Old.begin(), faceNew2Old.end(), kNoCode,
return faceMorton[face] == kNoCode; [&faceMorton](const int face, const uint32_t val) {
}) - return faceMorton[face] < val;
faceNew2Old.begin(); }) -
faceNew2Old.begin();
faceNew2Old.resize(newNumTri); faceNew2Old.resize(newNumTri);
Permute(faceMorton, faceNew2Old); Permute(faceMorton, faceNew2Old);
@ -435,8 +424,6 @@ void Manifold::Impl::GatherFaces(const Vec<int>& faceNew2Old) {
const auto numTri = faceNew2Old.size(); const auto numTri = faceNew2Old.size();
if (meshRelation_.triRef.size() == NumTri()) if (meshRelation_.triRef.size() == NumTri())
Permute(meshRelation_.triRef, faceNew2Old); Permute(meshRelation_.triRef, faceNew2Old);
if (meshRelation_.triProperties.size() == NumTri())
Permute(meshRelation_.triProperties, faceNew2Old);
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old); if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
Vec<Halfedge> oldHalfedge(std::move(halfedge_)); Vec<Halfedge> oldHalfedge(std::move(halfedge_));
@ -446,8 +433,9 @@ void Manifold::Impl::GatherFaces(const Vec<int>& faceNew2Old) {
scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(), scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(),
faceOld2New.begin()); faceOld2New.begin());
halfedge_.resize(3 * numTri); halfedge_.resize_nofill(3 * numTri);
if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri); if (oldHalfedgeTangent.size() != 0)
halfedgeTangent_.resize_nofill(3 * numTri);
for_each_n(policy, countAt(0), numTri, for_each_n(policy, countAt(0), numTri,
ReindexFace({halfedge_, halfedgeTangent_, oldHalfedge, ReindexFace({halfedge_, halfedgeTangent_, oldHalfedge,
oldHalfedgeTangent, faceNew2Old, faceOld2New})); oldHalfedgeTangent, faceNew2Old, faceOld2New}));
@ -457,7 +445,7 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec<int>& faceNew2Old) {
ZoneScoped; ZoneScoped;
const auto numTri = faceNew2Old.size(); const auto numTri = faceNew2Old.size();
meshRelation_.triRef.resize(numTri); meshRelation_.triRef.resize_nofill(numTri);
gather(faceNew2Old.begin(), faceNew2Old.end(), gather(faceNew2Old.begin(), faceNew2Old.end(),
old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin()); old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin());
@ -465,17 +453,13 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec<int>& faceNew2Old) {
meshRelation_.meshIDtransform[pair.first] = pair.second; meshRelation_.meshIDtransform[pair.first] = pair.second;
} }
if (old.meshRelation_.triProperties.size() > 0) { if (old.NumProp() > 0) {
meshRelation_.triProperties.resize(numTri); numProp_ = old.numProp_;
gather(faceNew2Old.begin(), faceNew2Old.end(), properties_ = old.properties_;
old.meshRelation_.triProperties.begin(),
meshRelation_.triProperties.begin());
meshRelation_.numProp = old.meshRelation_.numProp;
meshRelation_.properties = old.meshRelation_.properties;
} }
if (old.faceNormal_.size() == old.NumTri()) { if (old.faceNormal_.size() == old.NumTri()) {
faceNormal_.resize(numTri); faceNormal_.resize_nofill(numTri);
gather(faceNew2Old.begin(), faceNew2Old.end(), old.faceNormal_.begin(), gather(faceNew2Old.begin(), faceNew2Old.end(), old.faceNormal_.begin(),
faceNormal_.begin()); faceNormal_.begin());
} }
@ -484,8 +468,9 @@ void Manifold::Impl::GatherFaces(const Impl& old, const Vec<int>& faceNew2Old) {
scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(), scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(),
faceOld2New.begin()); faceOld2New.begin());
halfedge_.resize(3 * numTri); halfedge_.resize_nofill(3 * numTri);
if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri); if (old.halfedgeTangent_.size() != 0)
halfedgeTangent_.resize_nofill(3 * numTri);
for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri, for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,
ReindexFace({halfedge_, halfedgeTangent_, old.halfedge_, ReindexFace({halfedge_, halfedgeTangent_, old.halfedge_,
old.halfedgeTangent_, faceNew2Old, faceOld2New})); old.halfedgeTangent_, faceNew2Old, faceOld2New}));

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "./impl.h" #include "impl.h"
#include "./parallel.h" #include "parallel.h"
template <> template <>
struct std::hash<manifold::ivec4> { struct std::hash<manifold::ivec4> {
@ -113,7 +113,7 @@ class Partition {
} }
const int offset = interiorOffset - newVerts.size(); const int offset = interiorOffset - newVerts.size();
size_t old = 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); std::iota(newVerts.begin() + old, newVerts.end(), old + offset);
const int numTri = triVert.size(); const int numTri = triVert.size();
@ -535,7 +535,7 @@ Vec<Barycentric> Manifold::Impl::Subdivide(
auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) { auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) {
int longest = 0; int longest = 0;
int total = 0; int total = 0;
for (int j : {0, 1, 2}) { for (int _ : {0, 1, 2}) {
const int added = edgeAdded[half2Edge[hIdx]]; const int added = edgeAdded[half2Edge[hIdx]];
longest = la::max(longest, added); longest = la::max(longest, added);
total += added; total += added;
@ -586,7 +586,7 @@ Vec<Barycentric> Manifold::Impl::Subdivide(
std::vector<Partition> subTris(numTri); std::vector<Partition> subTris(numTri);
for_each_n(policy, countAt(0), 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]; const ivec4 halfedges = faceHalfedges[tri];
ivec4 divisions(0); ivec4 divisions(0);
for (const int i : {0, 1, 2, 3}) { for (const int i : {0, 1, 2, 3}) {
@ -684,18 +684,19 @@ Vec<Barycentric> Manifold::Impl::Subdivide(
}); });
vertPos_ = newVertPos; vertPos_ = newVertPos;
faceNormal_.resize(0); faceNormal_.clear();
if (meshRelation_.numProp > 0) { if (numProp_ > 0) {
const int numPropVert = NumPropVert(); const int numPropVert = NumPropVert();
const int addedVerts = NumVert() - numVert; const int addedVerts = NumVert() - numVert;
const int propOffset = numPropVert - numVert; const int propOffset = numPropVert - numVert;
Vec<double> prop(meshRelation_.numProp * // duplicate the prop verts along all new edges even though this is
(numPropVert + addedVerts + totalEdgeAdded)); // unnecessary for edges that share the same prop verts. The duplicates will
// be removed by CompactProps.
Vec<double> prop(numProp_ * (numPropVert + addedVerts + totalEdgeAdded));
// copy retained prop verts // copy retained prop verts
copy(meshRelation_.properties.begin(), meshRelation_.properties.end(), copy(properties_.begin(), properties_.end(), prop.begin());
prop.begin());
// copy interior prop verts and forward edge prop verts // copy interior prop verts and forward edge prop verts
for_each_n( for_each_n(
@ -705,104 +706,99 @@ Vec<Barycentric> Manifold::Impl::Subdivide(
const int vert = numPropVert + i; const int vert = numPropVert + i;
const Barycentric bary = vertBary[numVert + i]; const Barycentric bary = vertBary[numVert + i];
const ivec4 halfedges = faceHalfedges[bary.tri]; 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) { if (halfedges[3] < 0) {
vec3 triProp; vec3 triProp;
for (const int i : {0, 1, 2}) { for (const int i : {0, 1, 2}) {
triProp[i] = rel.properties[rel.triProperties[bary.tri][i] * triProp[i] =
rel.numProp + properties_[halfedge_[3 * bary.tri + i].propVert * numProp +
p]; p];
} }
prop[vert * rel.numProp + p] = la::dot(triProp, vec3(bary.uvw)); prop[vert * numProp + p] = la::dot(triProp, vec3(bary.uvw));
} else { } else {
vec4 quadProp; vec4 quadProp;
for (const int i : {0, 1, 2, 3}) { for (const int i : {0, 1, 2, 3}) {
const int tri = halfedges[i] / 3;
const int j = halfedges[i] % 3;
quadProp[i] = 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, for_each_n(policy, countAt(0), numEdge,
[this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset, [this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset,
addedVerts](const int i) { addedVerts](const int i) {
const int n = edgeAdded[i]; const int n = edgeAdded[i];
const int offset = edgeOffset[i] + propOffset + addedVerts; const int offset = edgeOffset[i] + propOffset + addedVerts;
auto& rel = meshRelation_; const int numProp = NumProp();
const double frac = 1.0 / (n + 1); const double frac = 1.0 / (n + 1);
const int halfedgeIdx = const int halfedgeIdx =
halfedge_[edges[i].halfedgeIdx].pairedHalfedge; halfedge_[edges[i].halfedgeIdx].pairedHalfedge;
const int v0 = halfedgeIdx % 3; const int prop0 = halfedge_[halfedgeIdx].propVert;
const int tri = halfedgeIdx / 3; const int prop1 =
const int prop0 = rel.triProperties[tri][v0]; halfedge_[NextHalfedge(halfedgeIdx)].propVert;
const int prop1 = rel.triProperties[tri][Next3(v0)];
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
for (int p = 0; p < rel.numProp; ++p) { for (int p = 0; p < numProp; ++p) {
prop[(offset + i) * rel.numProp + p] = prop[(offset + i) * numProp + p] = la::lerp(
la::lerp(rel.properties[prop0 * rel.numProp + p], properties_[prop0 * numProp + p],
rel.properties[prop1 * rel.numProp + p], properties_[prop1 * numProp + p], (i + 1) * frac);
(i + 1) * frac);
} }
} }
}); });
Vec<ivec3> triProp(triVerts.size()); Vec<ivec3> triProp(triVerts.size());
for_each_n(policy, countAt(0), numTri, for_each_n(
[this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset, policy, countAt(0), numTri,
&interiorOffset, &faceHalfedges, propOffset, [this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset,
addedVerts](const int tri) { &interiorOffset, &faceHalfedges, propOffset,
const ivec4 halfedges = faceHalfedges[tri]; addedVerts](const int tri) {
if (halfedges[0] < 0) return; const ivec4 halfedges = faceHalfedges[tri];
if (halfedges[0] < 0) return;
auto& rel = meshRelation_; ivec4 tri3;
ivec4 tri3; ivec4 edgeOffsets;
ivec4 edgeOffsets; bvec4 edgeFwd(true);
bvec4 edgeFwd(true); for (const int i : {0, 1, 2, 3}) {
for (const int i : {0, 1, 2, 3}) { if (halfedges[i] < 0) {
if (halfedges[i] < 0) { tri3[i] = -1;
tri3[i] = -1; continue;
continue; }
} const Halfedge& halfedge = halfedge_[halfedges[i]];
const int thisTri = halfedges[i] / 3; tri3[i] = halfedge.propVert;
const int j = halfedges[i] % 3; edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];
const Halfedge& halfedge = halfedge_[halfedges[i]]; if (!halfedge.IsForward()) {
tri3[i] = rel.triProperties[thisTri][j]; if (halfedge_[halfedge.pairedHalfedge].propVert !=
edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]]; halfedge_[NextHalfedge(halfedges[i])].propVert ||
if (!halfedge.IsForward()) { halfedge_[NextHalfedge(halfedge.pairedHalfedge)].propVert !=
const int pairTri = halfedge.pairedHalfedge / 3; halfedge.propVert) {
const int k = halfedge.pairedHalfedge % 3; // if the edge doesn't match, point to the backward edge
if (rel.triProperties[pairTri][k] != // propverts.
rel.triProperties[thisTri][Next3(j)] || edgeOffsets[i] += addedVerts;
rel.triProperties[pairTri][Next3(k)] != } else {
rel.triProperties[thisTri][j]) { edgeFwd[i] = false;
edgeOffsets[i] += addedVerts; }
} else { }
edgeFwd[i] = false; }
}
}
}
Vec<ivec3> newTris = subTris[tri].Reindex( Vec<ivec3> newTris =
tri3, edgeOffsets + propOffset, edgeFwd, subTris[tri].Reindex(tri3, edgeOffsets + propOffset, edgeFwd,
interiorOffset[tri] + propOffset); interiorOffset[tri] + propOffset);
copy(newTris.begin(), newTris.end(), copy(newTris.begin(), newTris.end(),
triProp.begin() + triOffset[tri]); triProp.begin() + triOffset[tri]);
}); });
meshRelation_.properties = prop; properties_ = prop;
meshRelation_.triProperties = triProp; CreateHalfedges(triProp, triVerts);
} else {
CreateHalfedges(triVerts);
} }
CreateHalfedges(triVerts);
return vertBary; return vertBary;
} }

View File

@ -82,7 +82,7 @@ struct QR {
mat3 Q, R; mat3 Q, R;
}; };
// Calculates the squared norm of the vector. // 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 // For an explanation of the math see
// http://pages.cs.wisc.edu/~sifakis/papers/SVD_TR1690.pdf Computing the // http://pages.cs.wisc.edu/~sifakis/papers/SVD_TR1690.pdf Computing the
// Singular Value Decomposition of 3 x 3 matrices with minimal branching and // 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, inline void JacobiConjugation(const int32_t x, const int32_t y, const int32_t z,
Symmetric3x3& S, vec4& q) { Symmetric3x3& S, vec4& q) {
auto g = ApproximateGivensQuaternion(S); auto g = ApproximateGivensQuaternion(S);
double scale = 1.0 / fma(g.ch, g.ch, g.sh * g.sh); double scale = 1.0 / (g.ch * g.ch + g.sh * g.sh);
double a = fma(g.ch, g.ch, -g.sh * g.sh) * scale; double a = (g.ch * g.ch - g.sh * g.sh) * scale;
double b = 2.0 * g.sh * g.ch * scale; double b = 2.0 * g.sh * g.ch * scale;
Symmetric3x3 _S = S; Symmetric3x3 _S = S;
// perform conjugation S = Q'*S*Q // perform conjugation S = Q'*S*Q
S.m_00 = S.m_00 = a * (a * _S.m_00 + b * _S.m_10) + b * (a * _S.m_10 + b * _S.m_11);
fma(a, fma(a, _S.m_00, b * _S.m_10), b * (fma(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_10 = fma(a, fma(-b, _S.m_00, a * _S.m_10), S.m_11 = -b * (-b * _S.m_00 + a * _S.m_10) + a * (-b * _S.m_10 + a * _S.m_11);
b * (fma(-b, _S.m_10, a * _S.m_11))); S.m_20 = a * _S.m_20 + b * _S.m_21;
S.m_11 = fma(-b, fma(-b, _S.m_00, a * _S.m_10), S.m_21 = -b * _S.m_20 + a * _S.m_21;
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_22 = _S.m_22; S.m_22 = _S.m_22;
// update cumulative rotation qV // update cumulative rotation qV
vec3 tmp = g.sh * vec3(q); vec3 tmp = g.sh * vec3(q);
g.sh *= q[3]; g.sh *= q[3];
// (x,y,z) corresponds to ((0,1,2),(1,2,0),(2,0,1)) for (p,q) = // (x,y,z) corresponds to ((0,1,2),(1,2,0),(2,0,1)) for (p,q) =
// ((0,1),(1,2),(0,2)) // ((0,1),(1,2),(0,2))
q[z] = fma(q[z], g.ch, g.sh); q[z] = q[z] * g.ch + g.sh;
q[3] = fma(q[3], g.ch, -tmp[z]); // w q[3] = q[3] * g.ch + -tmp[z]; // w
q[x] = fma(q[x], g.ch, tmp[y]); q[x] = q[x] * g.ch + tmp[y];
q[y] = fma(q[y], g.ch, -tmp[x]); q[y] = q[y] * g.ch + -tmp[x];
// re-arrange matrix for next iteration // re-arrange matrix for next iteration
_S.m_00 = S.m_11; _S.m_00 = S.m_11;
_S.m_10 = S.m_21; _S.m_10 = S.m_21;
@ -148,15 +145,15 @@ inline mat3 JacobiEigenAnalysis(Symmetric3x3 S) {
JacobiConjugation(1, 2, 0, S, q); JacobiConjugation(1, 2, 0, S, q);
JacobiConjugation(2, 0, 1, S, q); JacobiConjugation(2, 0, 1, S, q);
} }
return mat3({1.0 - 2.0 * (fma(q.y, q.y, q.z * q.z)), // return mat3({1.0 - 2.0 * (q.y * q.y + q.z * q.z), //
2.0 * fma(q.x, q.y, +q.w * q.z), // 2.0 * (q.x * q.y + +q.w * q.z), //
2.0 * fma(q.x, q.z, -q.w * q.y)}, // 2.0 * (q.x * q.z + -q.w * q.y)}, //
{2 * fma(q.x, q.y, -q.w * q.z), // {2 * (q.x * q.y + -q.w * q.z), //
1 - 2 * fma(q.x, q.x, q.z * q.z), // 1 - 2 * (q.x * q.x + q.z * q.z), //
2 * fma(q.y, q.z, q.w * q.x)}, // 2 * (q.y * q.z + q.w * q.x)}, //
{2 * fma(q.x, q.z, q.w * q.y), // {2 * (q.x * q.z + q.w * q.y), //
2 * fma(q.y, q.z, -q.w * q.x), // 2 * (q.y * q.z + -q.w * q.x), //
1 - 2 * fma(q.x, q.x, q.y * q.y)}); 1 - 2 * (q.x * q.x + q.y * q.y)});
} }
// Implementation of Algorithm 3 // Implementation of Algorithm 3
inline void SortSingularValues(mat3& B, mat3& V) { inline void SortSingularValues(mat3& B, mat3& V) {
@ -207,65 +204,64 @@ inline QR QRDecomposition(mat3& B) {
mat3 Q, R; mat3 Q, R;
// first Givens rotation (ch,0,0,sh) // first Givens rotation (ch,0,0,sh)
auto g1 = QRGivensQuaternion(B[0][0], B[0][1]); 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; auto b = 2.0 * g1.ch * g1.sh;
// apply B = Q' * B // apply B = Q' * B
R[0][0] = fma(a, B[0][0], b * B[0][1]); R[0][0] = a * B[0][0] + b * B[0][1];
R[1][0] = fma(a, B[1][0], b * B[1][1]); R[1][0] = a * B[1][0] + b * B[1][1];
R[2][0] = fma(a, B[2][0], b * B[2][1]); R[2][0] = a * B[2][0] + b * B[2][1];
R[0][1] = fma(-b, B[0][0], a * B[0][1]); R[0][1] = -b * B[0][0] + a * B[0][1];
R[1][1] = fma(-b, B[1][0], a * B[1][1]); R[1][1] = -b * B[1][0] + a * B[1][1];
R[2][1] = fma(-b, B[2][0], a * B[2][1]); R[2][1] = -b * B[2][0] + a * B[2][1];
R[0][2] = B[0][2]; R[0][2] = B[0][2];
R[1][2] = B[1][2]; R[1][2] = B[1][2];
R[2][2] = B[2][2]; R[2][2] = B[2][2];
// second Givens rotation (ch,0,-sh,0) // second Givens rotation (ch,0,-sh,0)
auto g2 = QRGivensQuaternion(R[0][0], R[0][2]); 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; b = 2.0 * g2.ch * g2.sh;
// apply B = Q' * B; // apply B = Q' * B;
B[0][0] = fma(a, R[0][0], b * R[0][2]); B[0][0] = a * R[0][0] + b * R[0][2];
B[1][0] = fma(a, R[1][0], b * R[1][2]); B[1][0] = a * R[1][0] + b * R[1][2];
B[2][0] = fma(a, R[2][0], b * R[2][2]); B[2][0] = a * R[2][0] + b * R[2][2];
B[0][1] = R[0][1]; B[0][1] = R[0][1];
B[1][1] = R[1][1]; B[1][1] = R[1][1];
B[2][1] = R[2][1]; B[2][1] = R[2][1];
B[0][2] = fma(-b, R[0][0], a * R[0][2]); B[0][2] = -b * R[0][0] + a * R[0][2];
B[1][2] = fma(-b, R[1][0], a * R[1][2]); B[1][2] = -b * R[1][0] + a * R[1][2];
B[2][2] = fma(-b, R[2][0], a * R[2][2]); B[2][2] = -b * R[2][0] + a * R[2][2];
// third Givens rotation (ch,sh,0,0) // third Givens rotation (ch,sh,0,0)
auto g3 = QRGivensQuaternion(B[1][1], B[1][2]); 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; b = 2.0 * g3.ch * g3.sh;
// R is now set to desired value // R is now set to desired value
R[0][0] = B[0][0]; R[0][0] = B[0][0];
R[1][0] = B[1][0]; R[1][0] = B[1][0];
R[2][0] = B[2][0]; R[2][0] = B[2][0];
R[0][1] = fma(a, B[0][1], b * B[0][2]); R[0][1] = a * B[0][1] + b * B[0][2];
R[1][1] = fma(a, B[1][1], b * B[1][2]); R[1][1] = a * B[1][1] + b * B[1][2];
R[2][1] = fma(a, B[2][1], b * B[2][2]); R[2][1] = a * B[2][1] + b * B[2][2];
R[0][2] = fma(-b, B[0][1], a * B[0][2]); R[0][2] = -b * B[0][1] + a * B[0][2];
R[1][2] = fma(-b, B[1][1], a * B[1][2]); R[1][2] = -b * B[1][1] + a * B[1][2];
R[2][2] = fma(-b, B[2][1], a * B[2][2]); R[2][2] = -b * B[2][1] + a * B[2][2];
// construct the cumulative rotation Q=Q1 * Q2 * Q3 // construct the cumulative rotation Q=Q1 * Q2 * Q3
// the number of floating point operations for three quaternion // the number of floating point operations for three quaternion
// multiplications is more or less comparable to the explicit form of the // multiplications is more or less comparable to the explicit form of the
// joined matrix. certainly more memory-efficient! // joined matrix. certainly more memory-efficient!
auto sh12 = 2.0 * fma(g1.sh, g1.sh, -0.5); auto sh12 = 2.0 * (g1.sh * g1.sh + -0.5);
auto sh22 = 2.0 * fma(g2.sh, g2.sh, -0.5); auto sh22 = 2.0 * (g2.sh * g2.sh + -0.5);
auto sh32 = 2.0 * fma(g3.sh, g3.sh, -0.5); auto sh32 = 2.0 * (g3.sh * g3.sh + -0.5);
Q[0][0] = sh12 * sh22; Q[0][0] = sh12 * sh22;
Q[1][0] = fma(4.0 * g2.ch * g3.ch, sh12 * g2.sh * g3.sh, Q[1][0] =
2.0 * g1.ch * g1.sh * sh32); 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, Q[2][0] =
-2.0 * g2.ch * sh12 * g2.sh * sh32); 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[0][1] = -2.0 * g1.ch * g1.sh * sh22;
Q[1][1] = Q[1][1] = -8.0 * g1.ch * g2.ch * g3.ch * g1.sh * g2.sh * g3.sh + sh12 * sh32;
fma(-8.0 * g1.ch * g2.ch * g3.ch, g1.sh * g2.sh * g3.sh, sh12 * sh32); Q[2][1] =
Q[2][1] = fma( -2.0 * g3.ch * g3.sh +
-2.0 * g3.ch, g3.sh, 4.0 * g1.sh * (g3.ch * g1.sh * g3.sh + g1.ch * g2.ch * g2.sh * sh32);
4.0 * g1.sh * fma(g3.ch * g1.sh, g3.sh, g1.ch * g2.ch * g2.sh * sh32));
Q[0][2] = 2.0 * g2.ch * g2.sh; Q[0][2] = 2.0 * g2.ch * g2.sh;
Q[1][2] = -2.0 * g3.ch * sh22 * g3.sh; Q[1][2] = -2.0 * g3.ch * sh22 * g3.sh;

59
thirdparty/manifold/src/tree2d.cpp vendored Normal file
View File

@ -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(<tracy/Tracy.hpp>)
#include <tracy/Tracy.hpp>
#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<PolyVert> points, bool sortX) {
using CmpFn = std::function<bool(const PolyVert&, const PolyVert&)>;
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<PolyVert> points) {
ZoneScoped;
// don't even bother...
if (points.size() <= 8) return;
BuildTwoDTreeImpl(points, true);
}
} // namespace manifold

85
thirdparty/manifold/src/tree2d.h vendored Normal file
View File

@ -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<PolyVert> points, bool sortX);
void BuildTwoDTree(VecView<PolyVert> points);
template <typename F>
void QueryTwoDTree(VecView<PolyVert> 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<double>::infinity());
current.max = vec2(std::numeric_limits<double>::infinity());
int level = 0;
VecView<PolyVert> currentView = points;
std::array<Rect, 64> rectStack;
std::array<VecView<PolyVert>, 64> viewStack;
std::array<int, 64> 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

View File

@ -15,6 +15,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <cstdint>
#include "manifold/common.h" #include "manifold/common.h"

View File

@ -19,8 +19,8 @@
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "./vec.h"
#include "manifold/common.h" #include "manifold/common.h"
#include "vec.h"
#ifndef MANIFOLD_PAR #ifndef MANIFOLD_PAR
#error "MANIFOLD_PAR must be defined to either 1 (parallel) or -1 (series)" #error "MANIFOLD_PAR must be defined to either 1 (parallel) or -1 (series)"
@ -33,7 +33,7 @@
#endif #endif
#endif #endif
#include "./parallel.h" #include "parallel.h"
#if __has_include(<tracy/Tracy.hpp>) #if __has_include(<tracy/Tracy.hpp>)
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
@ -72,7 +72,7 @@ inline int Prev3(int i) {
template <typename T, typename T1> template <typename T, typename T1>
void Permute(Vec<T>& inOut, const Vec<T1>& new2Old) { void Permute(Vec<T>& inOut, const Vec<T1>& new2Old) {
Vec<T> tmp(std::move(inOut)); Vec<T> tmp(std::move(inOut));
inOut.resize(new2Old.size()); inOut.resize_nofill(new2Old.size());
gather(new2Old.begin(), new2Old.end(), tmp.begin(), inOut.begin()); gather(new2Old.begin(), new2Old.end(), tmp.begin(), inOut.begin());
} }
@ -106,6 +106,12 @@ class ConcurrentSharedPtr {
ConcurrentSharedPtr(T value) : impl(std::make_shared<T>(value)) {} ConcurrentSharedPtr(T value) : impl(std::make_shared<T>(value)) {}
ConcurrentSharedPtr(const ConcurrentSharedPtr<T>& other) ConcurrentSharedPtr(const ConcurrentSharedPtr<T>& other)
: impl(other.impl), mutex(other.mutex) {} : impl(other.impl), mutex(other.mutex) {}
ConcurrentSharedPtr& operator=(const ConcurrentSharedPtr<T>& other) {
if (this == &other) return *this;
impl = other.impl;
mutex = other.mutex;
return *this;
}
class SharedPtrGuard { class SharedPtrGuard {
public: public:
SharedPtrGuard(std::recursive_mutex* mutex, T* content) SharedPtrGuard(std::recursive_mutex* mutex, T* content)
@ -211,7 +217,7 @@ struct Negate {
inline int CCW(vec2 p0, vec2 p1, vec2 p2, double tol) { inline int CCW(vec2 p0, vec2 p1, vec2 p2, double tol) {
vec2 v1 = p1 - p0; vec2 v1 = p1 - p0;
vec2 v2 = p2 - 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)); double base2 = la::max(la::dot(v1, v1), la::dot(v2, v2));
if (area * area * 4 <= base2 * tol * tol) if (area * area * 4 <= base2 * tol * tol)
return 0; 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}); } 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 } // namespace manifold

View File

@ -21,8 +21,8 @@
#endif #endif
#include <vector> #include <vector>
#include "./parallel.h"
#include "manifold/vec_view.h" #include "manifold/vec_view.h"
#include "parallel.h"
namespace manifold { namespace manifold {
@ -45,40 +45,40 @@ class Vec : public VecView<T> {
// Note that the vector constructed with this constructor will contain // Note that the vector constructed with this constructor will contain
// uninitialized memory. Please specify `val` if you need to make sure that // uninitialized memory. Please specify `val` if you need to make sure that
// the data is initialized. // the data is initialized.
Vec(size_t size) { Vec(size_t size) : VecView<T>() {
reserve(size); reserve(size);
this->size_ = size; this->size_ = size;
} }
Vec(size_t size, T val) { resize(size, val); } Vec(size_t size, T val) : VecView<T>() { resize(size, val); }
Vec(const Vec<T> &vec) { *this = Vec(vec.view()); } Vec(const Vec<T>& vec) : VecView<T>() { *this = Vec(vec.view()); }
Vec(const VecView<const T> &vec) { Vec(const VecView<const T>& vec) : VecView<T>() {
this->size_ = vec.size(); this->size_ = vec.size();
this->capacity_ = this->size_; this->capacity_ = this->size_;
auto policy = autoPolicy(this->size_); auto policy = autoPolicy(this->size_);
if (this->size_ != 0) { if (this->size_ != 0) {
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T))); this->ptr_ = reinterpret_cast<T*>(malloc(this->size_ * sizeof(T)));
ASSERT(this->ptr_ != nullptr, std::bad_alloc()); ASSERT(this->ptr_ != nullptr, std::bad_alloc());
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
copy(policy, vec.begin(), vec.end(), this->ptr_); copy(policy, vec.begin(), vec.end(), this->ptr_);
} }
} }
Vec(const std::vector<T> &vec) { Vec(const std::vector<T>& vec) : VecView<T>() {
this->size_ = vec.size(); this->size_ = vec.size();
this->capacity_ = this->size_; this->capacity_ = this->size_;
auto policy = autoPolicy(this->size_); auto policy = autoPolicy(this->size_);
if (this->size_ != 0) { if (this->size_ != 0) {
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T))); this->ptr_ = reinterpret_cast<T*>(malloc(this->size_ * sizeof(T)));
ASSERT(this->ptr_ != nullptr, std::bad_alloc()); ASSERT(this->ptr_ != nullptr, std::bad_alloc());
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
copy(policy, vec.begin(), vec.end(), this->ptr_); copy(policy, vec.begin(), vec.end(), this->ptr_);
} }
} }
Vec(Vec<T> &&vec) { Vec(Vec<T>&& vec) : VecView<T>() {
this->ptr_ = vec.ptr_; this->ptr_ = vec.ptr_;
this->size_ = vec.size_; this->size_ = vec.size_;
capacity_ = vec.capacity_; capacity_ = vec.capacity_;
@ -100,7 +100,7 @@ class Vec : public VecView<T> {
capacity_ = 0; capacity_ = 0;
} }
Vec<T> &operator=(const Vec<T> &other) { Vec<T>& operator=(const Vec<T>& other) {
if (&other == this) return *this; if (&other == this) return *this;
if (this->ptr_ != nullptr) { if (this->ptr_ != nullptr) {
TracyFreeS(this->ptr_, 3); TracyFreeS(this->ptr_, 3);
@ -109,7 +109,7 @@ class Vec : public VecView<T> {
this->size_ = other.size_; this->size_ = other.size_;
capacity_ = other.size_; capacity_ = other.size_;
if (this->size_ != 0) { if (this->size_ != 0) {
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T))); this->ptr_ = reinterpret_cast<T*>(malloc(this->size_ * sizeof(T)));
ASSERT(this->ptr_ != nullptr, std::bad_alloc()); ASSERT(this->ptr_ != nullptr, std::bad_alloc());
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3); TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
manifold::copy(other.begin(), other.end(), this->ptr_); manifold::copy(other.begin(), other.end(), this->ptr_);
@ -117,7 +117,7 @@ class Vec : public VecView<T> {
return *this; return *this;
} }
Vec<T> &operator=(Vec<T> &&other) { Vec<T>& operator=(Vec<T>&& other) {
if (&other == this) return *this; if (&other == this) return *this;
if (this->ptr_ != nullptr) { if (this->ptr_ != nullptr) {
TracyFreeS(this->ptr_, 3); TracyFreeS(this->ptr_, 3);
@ -134,38 +134,37 @@ class Vec : public VecView<T> {
operator VecView<T>() const { return {this->ptr_, this->size_}; } operator VecView<T>() const { return {this->ptr_, this->size_}; }
void swap(Vec<T> &other) { void swap(Vec<T>& other) {
std::swap(this->ptr_, other.ptr_); std::swap(this->ptr_, other.ptr_);
std::swap(this->size_, other.size_); std::swap(this->size_, other.size_);
std::swap(capacity_, other.capacity_); 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_) { if (this->size_ >= capacity_) {
// avoid dangling pointer in case val is a reference of our array // avoid dangling pointer in case val is a reference of our array
T val_copy = val; T val_copy = val;
reserve(capacity_ == 0 ? 128 : capacity_ * 2, seq); reserve(capacity_ == 0 ? 128 : capacity_ * 2);
this->ptr_[this->size_++] = val_copy; this->ptr_[this->size_++] = val_copy;
return; return;
} }
this->ptr_[this->size_++] = val; 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_) if (this->size_ + n >= capacity_)
reserve(capacity_ == 0 ? 128 : std::max(capacity_ * 2, this->size_ + n), reserve(capacity_ == 0 ? 128 : std::max(capacity_ * 2, this->size_ + n));
seq);
this->size_ += n; this->size_ += n;
} }
void reserve(size_t n, bool seq = false) { void reserve(size_t n) {
if (n > capacity_) { if (n > capacity_) {
T *newBuffer = reinterpret_cast<T *>(malloc(n * sizeof(T))); T* newBuffer = reinterpret_cast<T*>(malloc(n * sizeof(T)));
ASSERT(newBuffer != nullptr, std::bad_alloc()); ASSERT(newBuffer != nullptr, std::bad_alloc());
TracyAllocS(newBuffer, n * sizeof(T), 3); TracyAllocS(newBuffer, n * sizeof(T), 3);
if (this->size_ > 0) if (this->size_ > 0)
manifold::copy(seq ? ExecutionPolicy::Seq : autoPolicy(this->size_), manifold::copy(autoPolicy(this->size_), this->ptr_,
this->ptr_, this->ptr_ + this->size_, newBuffer); this->ptr_ + this->size_, newBuffer);
if (this->ptr_ != nullptr) { if (this->ptr_ != nullptr) {
TracyFreeS(this->ptr_, 3); TracyFreeS(this->ptr_, 3);
free(this->ptr_); free(this->ptr_);
@ -176,7 +175,7 @@ class Vec : public VecView<T> {
} }
void resize(size_t newSize, T val = T()) { 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); reserve(newSize);
if (this->size_ < newSize) { if (this->size_ < newSize) {
fill(autoPolicy(newSize - this->size_), this->ptr_ + this->size_, fill(autoPolicy(newSize - this->size_), this->ptr_ + this->size_,
@ -186,7 +185,14 @@ class Vec : public VecView<T> {
if (shrink) shrink_to_fit(); 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) { void clear(bool shrink = true) {
this->size_ = 0; this->size_ = 0;
@ -194,9 +200,9 @@ class Vec : public VecView<T> {
} }
void shrink_to_fit() { void shrink_to_fit() {
T *newBuffer = nullptr; T* newBuffer = nullptr;
if (this->size_ > 0) { if (this->size_ > 0) {
newBuffer = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T))); newBuffer = reinterpret_cast<T*>(malloc(this->size_ * sizeof(T)));
ASSERT(newBuffer != nullptr, std::bad_alloc()); ASSERT(newBuffer != nullptr, std::bad_alloc());
TracyAllocS(newBuffer, this->size_ * sizeof(T), 3); TracyAllocS(newBuffer, this->size_ * sizeof(T), 3);
manifold::copy(this->ptr_, this->ptr_ + this->size_, newBuffer); manifold::copy(this->ptr_, this->ptr_ + this->size_, newBuffer);