From 8d764581a1ac680fc30b74840411a023aed1d878 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Tue, 31 May 2022 21:57:05 +0800 Subject: [PATCH 01/16] moved transform_ from Impl to Manifold This allows lazy-boolean to reuse Imp for meshes with different transformations. --- manifold/include/manifold.h | 2 ++ manifold/src/constructors.cpp | 4 +-- manifold/src/impl.cpp | 28 ++++++------------ manifold/src/impl.h | 4 +-- manifold/src/manifold.cpp | 54 +++++++++++++++++++++-------------- manifold/src/properties.cpp | 2 -- 6 files changed, 46 insertions(+), 48 deletions(-) diff --git a/manifold/include/manifold.h b/manifold/include/manifold.h index b101c34a6..3d14c453a 100644 --- a/manifold/include/manifold.h +++ b/manifold/include/manifold.h @@ -158,6 +158,8 @@ class Manifold { private: std::unique_ptr pImpl_; + mutable glm::mat4x3 transform_ = glm::mat4x3(1.0f); + void ApplyTransform() const; static int circularSegments_; static float circularAngle_; static float circularEdgeLength_; diff --git a/manifold/src/constructors.cpp b/manifold/src/constructors.cpp index 8fdef1099..ecc2edda0 100644 --- a/manifold/src/constructors.cpp +++ b/manifold/src/constructors.cpp @@ -402,8 +402,8 @@ Manifold Manifold::Compose(const std::vector& manifolds) { int nextTri = 0; int nextBary = 0; for (const Manifold& manifold : manifolds) { + manifold.ApplyTransform(); const Impl& impl = *(manifold.pImpl_); - impl.ApplyTransform(); copy(policy, impl.vertPos_.begin(), impl.vertPos_.end(), combined.vertPos_.begin() + nextVert); @@ -510,7 +510,7 @@ std::vector Manifold::Decompose() const { // can keep using the old one. meshes[i].pImpl_->UpdateMeshIDs(meshIDs, original); - meshes[i].pImpl_->transform_ = pImpl_->transform_; + meshes[i].transform_ = transform_; } return meshes; } diff --git a/manifold/src/impl.cpp b/manifold/src/impl.cpp index 4eaf9b967..ac7aeac4d 100644 --- a/manifold/src/impl.cpp +++ b/manifold/src/impl.cpp @@ -528,42 +528,30 @@ void Manifold::Impl::Update() { collider_.UpdateBoxes(faceBox); } -void Manifold::Impl::ApplyTransform() const { - // This const_cast is here because these operations cancel out, leaving the - // state conceptually unchanged. This enables lazy transformation evaluation. - const_cast(this)->ApplyTransform(); -} - /** - * Bake the manifold's transform into its vertices. This function allows lazy - * evaluation, which is important because often several transforms are applied - * between operations. + * Bake the manifold's transform into its vertices. */ -void Manifold::Impl::ApplyTransform() { - if (transform_ == glm::mat4x3(1.0f)) return; - auto policy = autoPolicy(vertPos_.size()); +void Manifold::Impl::ApplyTransform(const glm::mat4x3 &transform) { + if (transform == glm::mat4x3(1.0f)) return; + auto policy = autoPolicy(NumVert()); for_each(policy, vertPos_.begin(), vertPos_.end(), - Transform4x3({transform_})); + Transform4x3({transform})); glm::mat3 normalTransform = - glm::inverse(glm::transpose(glm::mat3(transform_))); + glm::inverse(glm::transpose(glm::mat3(transform))); for_each(policy, faceNormal_.begin(), faceNormal_.end(), TransformNormals({normalTransform})); for_each(policy, vertNormal_.begin(), vertNormal_.end(), TransformNormals({normalTransform})); // This optimization does a cheap collider update if the transform is // axis-aligned. - if (!collider_.Transform(transform_)) Update(); + if (!collider_.Transform(transform)) Update(); const float oldScale = bBox_.Scale(); - transform_ = glm::mat4x3(1.0f); CalculateBBox(); const float newScale = bBox_.Scale(); - precision_ *= glm::max(1.0f, newScale / oldScale) * - glm::max(glm::length(transform_[0]), - glm::max(glm::length(transform_[1]), - glm::length(transform_[2]))); + precision_ *= glm::max(1.0f, newScale / oldScale); // Maximum of inherited precision loss and translational precision loss. SetPrecision(precision_); diff --git a/manifold/src/impl.h b/manifold/src/impl.h index 7d34cd428..8c86cecdb 100644 --- a/manifold/src/impl.h +++ b/manifold/src/impl.h @@ -48,7 +48,6 @@ struct Manifold::Impl { VecDH halfedgeTangent_; MeshRelationD meshRelation_; Collider collider_; - glm::mat4x3 transform_ = glm::mat4x3(1.0f); static std::atomic meshIDCounter_; @@ -74,8 +73,7 @@ struct Manifold::Impl { int startTri = 0, int n = -1, int startID = 0); void Update(); - void ApplyTransform() const; - void ApplyTransform(); + void ApplyTransform(const glm::mat4x3 &transform); SparseIndices EdgeCollisions(const Impl& B) const; SparseIndices VertexCollisionsZ(const VecDH& vertsIn) const; diff --git a/manifold/src/manifold.cpp b/manifold/src/manifold.cpp index d8678b425..89acc8bc6 100644 --- a/manifold/src/manifold.cpp +++ b/manifold/src/manifold.cpp @@ -63,11 +63,12 @@ Manifold::~Manifold() = default; Manifold::Manifold(Manifold&&) noexcept = default; Manifold& Manifold::operator=(Manifold&&) noexcept = default; -Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)) {} +Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)), transform_(other.transform_) {} Manifold& Manifold::operator=(const Manifold& other) { if (this != &other) { pImpl_.reset(new Impl(*other.pImpl_)); + transform_ = other.transform_; } return *this; } @@ -110,7 +111,7 @@ Manifold::Manifold(const Mesh& mesh, * saving or other operations outside of the context of this library. */ Mesh Manifold::GetMesh() const { - pImpl_->ApplyTransform(); + ApplyTransform(); Mesh result; result.vertPos.insert(result.vertPos.end(), pImpl_->vertPos_.begin(), @@ -211,7 +212,7 @@ int Manifold::NumTri() const { return pImpl_->NumTri(); } * Returns the axis-aligned bounding box of all the Manifold's vertices. */ Box Manifold::BoundingBox() const { - return pImpl_->bBox_.Transform(pImpl_->transform_); + return pImpl_->bBox_.Transform(transform_); } /** @@ -222,7 +223,7 @@ Box Manifold::BoundingBox() const { * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). */ float Manifold::Precision() const { - pImpl_->ApplyTransform(); + ApplyTransform(); return pImpl_->precision_; } @@ -242,7 +243,10 @@ int Manifold::Genus() const { * means degenerate manifolds can by identified by testing these properties as * == 0. */ -Properties Manifold::GetProperties() const { return pImpl_->GetProperties(); } +Properties Manifold::GetProperties() const { + ApplyTransform(); + return pImpl_->GetProperties(); +} /** * Curvature is the inverse of the radius of curvature, and signed such that @@ -252,7 +256,10 @@ Properties Manifold::GetProperties() const { return pImpl_->GetProperties(); } * curvature is their sum. This approximates them for every vertex (returned as * vectors in the structure) and also returns their minimum and maximum values. */ -Curvature Manifold::GetCurvature() const { return pImpl_->GetCurvature(); } +Curvature Manifold::GetCurvature() const { + ApplyTransform(); + return pImpl_->GetCurvature(); +} /** * Gets the relationship to the previous mesh, for the purpose of assinging @@ -337,8 +344,8 @@ int Manifold::NumDegenerateTris() const { return pImpl_->NumDegenerateTris(); } * @param other A Manifold to overlap with. */ int Manifold::NumOverlaps(const Manifold& other) const { - pImpl_->ApplyTransform(); - other.pImpl_->ApplyTransform(); + ApplyTransform(); + other.ApplyTransform(); SparseIndices overlaps = pImpl_->EdgeCollisions(*other.pImpl_); int num_overlaps = overlaps.size(); @@ -354,7 +361,7 @@ int Manifold::NumOverlaps(const Manifold& other) const { * @param v The vector to add to every vertex. */ Manifold& Manifold::Translate(glm::vec3 v) { - pImpl_->transform_[3] += v; + transform_[3] += v; return *this; } @@ -367,7 +374,7 @@ Manifold& Manifold::Translate(glm::vec3 v) { Manifold& Manifold::Scale(glm::vec3 v) { glm::mat3 s(1.0f); for (int i : {0, 1, 2}) s[i] *= v; - pImpl_->transform_ = s * pImpl_->transform_; + transform_ = s * transform_; return *this; } @@ -393,7 +400,7 @@ Manifold& Manifold::Rotate(float xDegrees, float yDegrees, float zDegrees) { glm::mat3 rZ(cosd(zDegrees), sind(zDegrees), 0.0f, // -sind(zDegrees), cosd(zDegrees), 0.0f, // 0.0f, 0.0f, 1.0f); - pImpl_->transform_ = rZ * rY * rX * pImpl_->transform_; + transform_ = rZ * rY * rX * transform_; return *this; } @@ -405,8 +412,8 @@ Manifold& Manifold::Rotate(float xDegrees, float yDegrees, float zDegrees) { * @param m The affine transform matrix to apply to all the vertices. */ Manifold& Manifold::Transform(const glm::mat4x3& m) { - glm::mat4 old(pImpl_->transform_); - pImpl_->transform_ = m * old; + glm::mat4 old(transform_); + transform_ = m * old; return *this; } @@ -420,9 +427,8 @@ Manifold& Manifold::Transform(const glm::mat4x3& m) { * @param warpFunc A function that modifies a given vertex position. */ Manifold& Manifold::Warp(std::function warpFunc) { - pImpl_->ApplyTransform(); - thrust::for_each_n(thrust::host, pImpl_->vertPos_.begin(), NumVert(), - warpFunc); + ApplyTransform(); + thrust::for_each_n(thrust::host, pImpl_->vertPos_.begin(), NumVert(), warpFunc); pImpl_->Update(); pImpl_->faceNormal_.resize(0); // force recalculation of triNormal pImpl_->CalculateNormals(); @@ -460,8 +466,8 @@ Manifold& Manifold::Refine(int n) { * @param op The type of operation to perform. */ Manifold Manifold::Boolean(const Manifold& second, OpType op) const { - pImpl_->ApplyTransform(); - second.pImpl_->ApplyTransform(); + ApplyTransform(); + second.ApplyTransform(); Boolean3 boolean(*pImpl_, *second.pImpl_, op); Manifold result; result.pImpl_ = std::make_unique(boolean.Result(op)); @@ -521,8 +527,8 @@ Manifold& Manifold::operator^=(const Manifold& Q) { * @param cutter */ std::pair Manifold::Split(const Manifold& cutter) const { - pImpl_->ApplyTransform(); - cutter.pImpl_->ApplyTransform(); + ApplyTransform(); + cutter.ApplyTransform(); Boolean3 boolean(*pImpl_, *cutter.pImpl_, OpType::SUBTRACT); std::pair result; result.first.pImpl_ = @@ -556,7 +562,13 @@ std::pair Manifold::SplitByPlane(glm::vec3 normal, * direction of the normal vector. */ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { - pImpl_->ApplyTransform(); + ApplyTransform(); return *this ^ Halfspace(BoundingBox(), normal, originOffset); } + +void Manifold::ApplyTransform() const { + pImpl_->ApplyTransform(transform_); + transform_ = glm::mat4x3(1.0f); +} + } // namespace manifold diff --git a/manifold/src/properties.cpp b/manifold/src/properties.cpp index e33155fe9..14c546030 100644 --- a/manifold/src/properties.cpp +++ b/manifold/src/properties.cpp @@ -249,7 +249,6 @@ int Manifold::Impl::NumDegenerateTris() const { Properties Manifold::Impl::GetProperties() const { if (IsEmpty()) return {0, 0}; - ApplyTransform(); auto areaVolume = transform_reduce>( autoPolicy(NumTri()), countAt(0), countAt(NumTri()), FaceAreaVolume({halfedge_.cptrD(), vertPos_.cptrD(), precision_}), @@ -260,7 +259,6 @@ Properties Manifold::Impl::GetProperties() const { Curvature Manifold::Impl::GetCurvature() const { Curvature result; if (IsEmpty()) return result; - ApplyTransform(); VecDH vertMeanCurvature(NumVert(), 0); VecDH vertGaussianCurvature(NumVert(), glm::two_pi()); VecDH vertArea(NumVert(), 0); From 9fb598495abd8c89cb52c70c0f857e1a14da28bc Mon Sep 17 00:00:00 2001 From: pca006132 Date: Wed, 1 Jun 2022 00:41:56 +0800 Subject: [PATCH 02/16] lazy boolean implementation --- manifold/CMakeLists.txt | 2 +- manifold/src/csg_tree.cpp | 421 ++++++++++++++++++++++++++++++++++++ manifold/src/csg_tree.h | 87 ++++++++ manifold/src/impl.cpp | 66 ++++-- manifold/src/impl.h | 1 + utilities/include/par.h | 2 + utilities/include/sparse.h | 5 - utilities/include/structs.h | 24 +- 8 files changed, 577 insertions(+), 31 deletions(-) create mode 100644 manifold/src/csg_tree.cpp create mode 100644 manifold/src/csg_tree.h diff --git a/manifold/CMakeLists.txt b/manifold/CMakeLists.txt index 482166f1e..71afdd7d0 100644 --- a/manifold/CMakeLists.txt +++ b/manifold/CMakeLists.txt @@ -2,7 +2,7 @@ project (manifold) add_library(${PROJECT_NAME} src/manifold.cpp src/constructors.cpp src/impl.cpp src/properties.cpp src/sort.cpp src/edge_op.cpp src/face_op.cpp - src/smoothing.cpp src/boolean3.cpp src/boolean_result.cpp + src/smoothing.cpp src/boolean3.cpp src/boolean_result.cpp src/csg_tree.cpp ) if(MANIFOLD_USE_CUDA) diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp new file mode 100644 index 000000000..da60f82b3 --- /dev/null +++ b/manifold/src/csg_tree.cpp @@ -0,0 +1,421 @@ +#include "csg_tree.h" + +#include "boolean3.h" +#include "impl.h" +#include "par.h" + +namespace { +using namespace manifold; +struct Transform4x3 { + const glm::mat4x3 transform; + + __host__ __device__ glm::vec3 operator()(glm::vec3 position) { + return transform * glm::vec4(position, 1.0f); + } +}; + +struct TransformNormals { + const glm::mat3 transform; + + __host__ __device__ glm::vec3 operator()(glm::vec3 normal) { + normal = glm::normalize(transform * normal); + if (isnan(normal.x)) normal = glm::vec3(0.0f); + return normal; + } +}; + +struct UpdateTriBary { + const int nextBary; + + __host__ __device__ BaryRef operator()(BaryRef ref) { + for (int i : {0, 1, 2}) + if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary; + return ref; + } +}; + +struct UpdateHalfedge { + const int nextVert; + const int nextEdge; + const int nextFace; + + __host__ __device__ Halfedge operator()(Halfedge edge) { + edge.startVert += nextVert; + edge.endVert += nextVert; + edge.pairedHalfedge += nextEdge; + edge.face += nextFace; + return edge; + } +}; +} // namespace +namespace manifold { + +std::shared_ptr CsgNode::Translate(const glm::vec3 &t) const { + glm::mat4x3 transform(1.0f); + transform[3] += t; + return Transform(transform); +} + +std::shared_ptr CsgNode::Scale(const glm::vec3 &v) const { + glm::mat4x3 transform(1.0f); + for (int i : {0, 1, 2}) transform[i] *= v; + return Transform(transform); +} + +std::shared_ptr CsgNode::Rotate(float xDegrees, float yDegrees, + float zDegrees) const { + glm::mat3 rX(1.0f, 0.0f, 0.0f, // + 0.0f, cosd(xDegrees), sind(xDegrees), // + 0.0f, -sind(xDegrees), cosd(xDegrees)); + glm::mat3 rY(cosd(yDegrees), 0.0f, -sind(yDegrees), // + 0.0f, 1.0f, 0.0f, // + sind(yDegrees), 0.0f, cosd(yDegrees)); + glm::mat3 rZ(cosd(zDegrees), sind(zDegrees), 0.0f, // + -sind(zDegrees), cosd(zDegrees), 0.0f, // + 0.0f, 0.0f, 1.0f); + glm::mat4x3 transform(rZ * rY * rX); + return Transform(transform); +} + +CsgLeafNode::CsgLeafNode() : pImpl_(std::make_shared()) {} + +CsgLeafNode::CsgLeafNode(std::shared_ptr pImpl_) + : pImpl_(pImpl_) {} + +CsgLeafNode::CsgLeafNode(std::shared_ptr pImpl_, + glm::mat4x3 transform_) + : pImpl_(pImpl_), transform_(transform_) {} + +std::shared_ptr CsgLeafNode::GetImpl() const { + if (transform_ == glm::mat4x3(1.0f)) return pImpl_; + pImpl_ = + std::make_shared(pImpl_->Transform(transform_)); + transform_ = glm::mat4x3(1.0f); + return pImpl_; +} + +glm::mat4x3 CsgLeafNode::GetTransform() const { return transform_; } + +Box CsgLeafNode::GetBoundingBox() const { + return pImpl_->bBox_.Transform(transform_); +} + +std::shared_ptr CsgLeafNode::ToLeafNode() const { + return std::make_shared(*this); +} + +std::shared_ptr CsgLeafNode::Transform(const glm::mat4x3 &m) const { + return std::make_shared(pImpl_, m * glm::mat4(transform_)); +} + +CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::LEAF; } + +/** + * Efficient union of a set of pairwise disjoint meshes. + */ +Manifold::Impl CsgLeafNode::Compose( + const std::vector> &nodes) { + int numVert = 0; + int numEdge = 0; + int numTri = 0; + int numBary = 0; + for (auto &node : nodes) { + numVert += node->pImpl_->NumVert(); + numEdge += node->pImpl_->NumEdge(); + numTri += node->pImpl_->NumTri(); + numBary += node->pImpl_->meshRelation_.barycentric.size(); + } + + Manifold::Impl combined; + combined.vertPos_.resize(numVert); + combined.halfedge_.resize(2 * numEdge); + combined.faceNormal_.resize(numTri); + combined.halfedgeTangent_.resize(2 * numEdge); + combined.meshRelation_.barycentric.resize(numBary); + combined.meshRelation_.triBary.resize(numTri); + auto policy = autoPolicy(numTri); + + int nextVert = 0; + int nextEdge = 0; + int nextTri = 0; + int nextBary = 0; + for (auto &node : nodes) { + if (node->transform_ == glm::mat4x3(1.0f)) { + copy(policy, node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(), + combined.vertPos_.begin() + nextVert); + copy(policy, node->pImpl_->faceNormal_.begin(), + node->pImpl_->faceNormal_.end(), + combined.faceNormal_.begin() + nextTri); + } else { + // no need to apply the transform to the node, just copy the vertices and + // face normals and apply transform on the fly + auto vertPosBegin = thrust::make_transform_iterator( + node->pImpl_->vertPos_.begin(), Transform4x3({node->transform_})); + glm::mat3 normalTransform = + glm::inverse(glm::transpose(glm::mat3(node->transform_))); + auto faceNormalBegin = + thrust::make_transform_iterator(node->pImpl_->faceNormal_.begin(), + TransformNormals({normalTransform})); + copy_n(policy, vertPosBegin, node->pImpl_->vertPos_.size(), + combined.vertPos_.begin() + nextVert); + copy_n(policy, faceNormalBegin, node->pImpl_->faceNormal_.size(), + combined.faceNormal_.begin() + nextTri); + } + copy(policy, node->pImpl_->halfedgeTangent_.begin(), + node->pImpl_->halfedgeTangent_.end(), + combined.halfedgeTangent_.begin() + nextEdge); + copy(policy, node->pImpl_->meshRelation_.barycentric.begin(), + node->pImpl_->meshRelation_.barycentric.end(), + combined.meshRelation_.barycentric.begin() + nextBary); + transform(policy, node->pImpl_->meshRelation_.triBary.begin(), + node->pImpl_->meshRelation_.triBary.end(), + combined.meshRelation_.triBary.begin() + nextTri, + UpdateTriBary({nextBary})); + transform(policy, node->pImpl_->halfedge_.begin(), + node->pImpl_->halfedge_.end(), + combined.halfedge_.begin() + nextEdge, + UpdateHalfedge({nextVert, nextEdge, nextTri})); + + // Assign new IDs to triangles added in this iteration, to differentiate + // triangles coming from different manifolds. + VecDH meshIDs; + VecDH original; + for (auto &entry : node->pImpl_->meshRelation_.originalID) { + meshIDs.push_back(entry.first); + original.push_back(entry.second); + } + int meshIDStart = combined.meshRelation_.originalID.size(); + combined.UpdateMeshIDs(meshIDs, original, nextTri, + node->pImpl_->meshRelation_.triBary.size(), + meshIDStart); + + nextVert += node->pImpl_->NumVert(); + nextEdge += 2 * node->pImpl_->NumEdge(); + nextTri += node->pImpl_->NumTri(); + nextBary += node->pImpl_->meshRelation_.barycentric.size(); + } + combined.Finish(); + return combined; +} + +CsgOpNode::CsgOpNode() {} + +CsgOpNode::CsgOpNode(const std::vector> &children, + Manifold::OpType op) + : children_(children) { + SetOp(op); +} + +CsgOpNode::CsgOpNode(std::vector> &&children, + Manifold::OpType op) + : children_(children) { + SetOp(op); +} + +std::shared_ptr CsgOpNode::Transform(const glm::mat4x3 &m) const { + auto node = std::make_shared(); + node->children_ = children_; + node->op_ = op_; + node->transform_ = m * glm::mat4(transform_); + node->simplified = simplified; + return node; +} + +std::shared_ptr CsgOpNode::ToLeafNode() const { + if (cache_ != nullptr) return cache_; + if (children_.empty()) return nullptr; + // turn the children into leaf nodes + GetChildren(); + switch (op_) { + case CsgNodeType::UNION: + BatchUnion(); + break; + case CsgNodeType::INTERSECTION: { + std::vector> impls; + for (auto &child : children_) { + impls.push_back( + std::dynamic_pointer_cast(child)->GetImpl()); + } + BatchBoolean(Manifold::OpType::INTERSECT, impls); + children_.clear(); + children_.push_back(std::make_shared(impls.front())); + break; + }; + case CsgNodeType::DIFFERENCE: { + // take the lhs out and treat the remaining nodes as the rhs, perform + // union optimization for them + auto lhs = std::dynamic_pointer_cast(children_.front()); + children_.erase(children_.begin()); + BatchUnion(); + auto rhs = std::dynamic_pointer_cast(children_.front()); + children_.clear(); + Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(), + Manifold::OpType::SUBTRACT); + children_.push_back( + std::make_shared(std::make_shared( + boolean.Result(Manifold::OpType::SUBTRACT)))); + }; + case CsgNodeType::LEAF: + // unreachable + break; + } + // children_ must contain only one CsgLeafNode now, and its Transform will + // give CsgLeafNode as well + cache_ = std::dynamic_pointer_cast( + children_.front()->Transform(transform_)); + return cache_; +} + +/** + * Efficient boolean operation on a set of nodes utilizing commutativity of the + * operation. Only supports union and intersection. + */ +void CsgOpNode::BatchBoolean( + Manifold::OpType operation, + std::vector> &results) { + assert(operation != Manifold::OpType::SUBTRACT); + auto cmpFn = [](std::shared_ptr a, + std::shared_ptr b) { + // invert the order because we want a min heap + return a->NumVert() > b->NumVert(); + }; + + // apply boolean operations starting from smaller meshes + // the assumption is that boolean operations on smaller meshes is faster, + // due to less data being copied and processed + std::make_heap(results.begin(), results.end(), cmpFn); + while (results.size() > 1) { + std::pop_heap(results.begin(), results.end(), cmpFn); + auto a = std::move(results.back()); + results.pop_back(); + std::pop_heap(results.begin(), results.end(), cmpFn); + auto b = std::move(results.back()); + results.pop_back(); + // boolean operation + Boolean3 boolean(*a, *b, operation); + results.push_back( + std::make_shared(boolean.Result(operation))); + std::push_heap(results.begin(), results.end(), cmpFn); + } +} + +/** + * Efficient union operation on a set of nodes by doing Compose as much as + * possible. + */ +void CsgOpNode::BatchUnion() const { + // INVARIANT: children_ is a vector of leaf nodes + // this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check + // with O(n^2) complexity to take too long. + // If the number of children exceeded this limit, we will operate on chunks + // with size kMaxUnionSize. + constexpr int kMaxUnionSize = 1000; + while (children_.size() > 1) { + int start; + if (children_.size() > kMaxUnionSize) { + start = children_.size() - kMaxUnionSize; + } else { + start = 0; + } + VecDH boxes; + boxes.reserve(children_.size() - start); + for (int i = start; i < children_.size(); i++) { + boxes.push_back(std::dynamic_pointer_cast(children_[i]) + ->GetBoundingBox()); + } + const Box *boxesD = boxes.cptrD(); + // partition the children into a set of disjoint sets + // each set contains a set of children that are pairwise disjoint + std::vector> disjointSets; + for (size_t i = 0; i < boxes.size(); i++) { + auto lambda = [boxesD, i](const VecDH &set) { + return find_if( + autoPolicy(set.size()), set.begin(), set.end(), + [&boxesD, i](size_t j) { + return boxesD[i].DoesOverlap(boxesD[j]); + }) == set.end(); + }; + auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda); + if (it == disjointSets.end()) { + disjointSets.push_back(std::vector{i}); + } else { + it->push_back(i); + } + } + // compose each set of disjoint children + std::vector> impls; + for (const auto &set : disjointSets) { + if (set.size() == 1) { + impls.push_back( + std::dynamic_pointer_cast(children_[start + set[0]]) + ->GetImpl()); + } else { + std::vector> tmp; + for (size_t j : set) { + tmp.push_back( + std::dynamic_pointer_cast(children_[start + j])); + } + impls.push_back( + std::make_shared(CsgLeafNode::Compose(tmp))); + } + } + BatchBoolean(Manifold::OpType::ADD, impls); + children_.erase(children_.begin() + start, children_.end()); + children_.push_back(std::make_shared(impls.back())); + // move it to the front as we process from the back, and the newly added + // child should be quite complicated + std::swap(children_.front(), children_.back()); + } +} + +/** + * Flatten the children to a list of leaf nodes and return them. + * Note that this function will not apply the transform to children, as they may + * be shared with other nodes. + */ +std::vector> &CsgOpNode::GetChildren() const { + if (children_.empty() || simplified) return children_; + simplified = true; + std::vector> newChildren; + + CsgNodeType op = op_; + for (auto &child : children_) { + if (child->GetNodeType() == op) { + auto grandchildren = + std::dynamic_pointer_cast(child)->GetChildren(); + int start = children_.size(); + for (auto &grandchild : grandchildren) { + newChildren.push_back(grandchild->Transform(child->GetTransform())); + } + } else { + if (child->GetNodeType() == CsgNodeType::LEAF) { + newChildren.push_back(child); + } else { + newChildren.push_back(child->ToLeafNode()); + } + } + // special handling for difference: we treat it as first - (second + third + + // ...) so op = UNION after the first node + if (op == CsgNodeType::DIFFERENCE) op = CsgNodeType::UNION; + } + children_ = newChildren; + return children_; +} + +void CsgOpNode::SetOp(Manifold::OpType op) { + switch (op) { + case Manifold::OpType::ADD: + op_ = CsgNodeType::UNION; + break; + case Manifold::OpType::SUBTRACT: + op_ = CsgNodeType::DIFFERENCE; + break; + case Manifold::OpType::INTERSECT: + op_ = CsgNodeType::INTERSECTION; + break; + } +} + +glm::mat4x3 CsgOpNode::GetTransform() const { return transform_; } + +} // namespace manifold diff --git a/manifold/src/csg_tree.h b/manifold/src/csg_tree.h new file mode 100644 index 000000000..c1ac601bd --- /dev/null +++ b/manifold/src/csg_tree.h @@ -0,0 +1,87 @@ +#pragma once +#include "manifold.h" + +namespace manifold { + +enum class CsgNodeType { UNION, INTERSECTION, DIFFERENCE, LEAF }; + +class CsgLeafNode; + +class CsgNode { + public: + virtual std::shared_ptr ToLeafNode() const = 0; + virtual std::shared_ptr Transform(const glm::mat4x3 &m) const = 0; + virtual CsgNodeType GetNodeType() const = 0; + virtual glm::mat4x3 GetTransform() const = 0; + + std::shared_ptr Translate(const glm::vec3 &t) const; + std::shared_ptr Scale(const glm::vec3 &s) const; + std::shared_ptr Rotate(float xDegrees = 0, float yDegrees = 0, + float zDegrees = 0) const; +}; + +class CsgLeafNode final : public CsgNode { + public: + CsgLeafNode(); + CsgLeafNode(std::shared_ptr pImpl_); + CsgLeafNode(std::shared_ptr pImpl_, + glm::mat4x3 transform_); + + std::shared_ptr GetImpl() const; + + Box GetBoundingBox() const; + + std::shared_ptr ToLeafNode() const override; + + std::shared_ptr Transform(const glm::mat4x3 &m) const override; + + CsgNodeType GetNodeType() const override; + + glm::mat4x3 GetTransform() const override; + + static Manifold::Impl Compose( + const std::vector> &nodes); + + private: + mutable std::shared_ptr pImpl_; + mutable glm::mat4x3 transform_ = glm::mat4x3(1.0f); +}; + +class CsgOpNode final : public CsgNode { + public: + CsgOpNode(); + + CsgOpNode(const std::vector> &children, + Manifold::OpType op); + + CsgOpNode(std::vector> &&children, + Manifold::OpType op); + + std::shared_ptr Transform(const glm::mat4x3 &m) const override; + + std::shared_ptr ToLeafNode() const override; + + CsgNodeType GetNodeType() const override { return op_; } + + glm::mat4x3 GetTransform() const override; + + private: + CsgNodeType op_; + glm::mat4x3 transform_ = glm::mat4x3(1.0f); + // the following fields are for lazy evaluation, so they are mutable + mutable std::vector> children_; + mutable std::shared_ptr cache_ = nullptr; + mutable bool simplified = false; + + void SetOp(Manifold::OpType); + + static void BatchBoolean( + Manifold::OpType operation, + std::vector> &results); + + void BatchUnion() const; + + std::vector> &GetChildren() const; +}; + +} // namespace manifold diff --git a/manifold/src/impl.cpp b/manifold/src/impl.cpp index ac7aeac4d..f1d5fab3b 100644 --- a/manifold/src/impl.cpp +++ b/manifold/src/impl.cpp @@ -47,17 +47,18 @@ struct Normalize { struct Transform4x3 { const glm::mat4x3 transform; - __host__ __device__ void operator()(glm::vec3& position) { - position = transform * glm::vec4(position, 1.0f); + __host__ __device__ glm::vec3 operator()(glm::vec3 position) { + return transform * glm::vec4(position, 1.0f); } }; struct TransformNormals { const glm::mat3 transform; - __host__ __device__ void operator()(glm::vec3& normal) { + __host__ __device__ glm::vec3 operator()(glm::vec3 normal) { normal = glm::normalize(transform * normal); if (isnan(normal.x)) normal = glm::vec3(0.0f); + return normal; } }; @@ -279,6 +280,7 @@ struct EdgeBox { thrust::get<0>(inout) = Box(vertPos[edge.first], vertPos[edge.second]); } }; + } // namespace namespace manifold { @@ -357,6 +359,7 @@ Manifold::Impl::Impl(Shape shape) { InitializeNewReference(); } + void Manifold::Impl::ReinitializeReference(int meshID) { // instead of storing the meshID, we store 0 and set the mapping to // 0 -> meshID, because the meshID after boolean operation also starts from 0. @@ -531,21 +534,21 @@ void Manifold::Impl::Update() { /** * Bake the manifold's transform into its vertices. */ -void Manifold::Impl::ApplyTransform(const glm::mat4x3 &transform) { - if (transform == glm::mat4x3(1.0f)) return; +void Manifold::Impl::ApplyTransform(const glm::mat4x3 &transform_) { + if (transform_ == glm::mat4x3(1.0f)) return; auto policy = autoPolicy(NumVert()); - for_each(policy, vertPos_.begin(), vertPos_.end(), - Transform4x3({transform})); + transform(policy, vertPos_.begin(), vertPos_.end(), vertPos_.begin(), + Transform4x3({transform_})); glm::mat3 normalTransform = - glm::inverse(glm::transpose(glm::mat3(transform))); - for_each(policy, faceNormal_.begin(), faceNormal_.end(), - TransformNormals({normalTransform})); - for_each(policy, vertNormal_.begin(), vertNormal_.end(), - TransformNormals({normalTransform})); + glm::inverse(glm::transpose(glm::mat3(transform_))); + transform(policy, faceNormal_.begin(), faceNormal_.end(), faceNormal_.begin(), + TransformNormals({normalTransform})); + transform(policy, vertNormal_.begin(), vertNormal_.end(), vertNormal_.begin(), + TransformNormals({normalTransform})); // This optimization does a cheap collider update if the transform is // axis-aligned. - if (!collider_.Transform(transform)) Update(); + if (!collider_.Transform(transform_)) Update(); const float oldScale = bBox_.Scale(); CalculateBBox(); @@ -557,6 +560,43 @@ void Manifold::Impl::ApplyTransform(const glm::mat4x3 &transform) { SetPrecision(precision_); } +Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3 &transform_) const { + if (transform_ == glm::mat4x3(1.0f)) return *this; + auto policy = autoPolicy(NumVert()); + Impl result; + result.collider_ = collider_; + result.meshRelation_ = meshRelation_; + result.precision_ = precision_; + result.bBox_ = bBox_; + result.halfedge_ = halfedge_; + result.halfedgeTangent_ = halfedgeTangent_; + + result.vertPos_.resize(NumVert()); + result.faceNormal_.resize(faceNormal_.size()); + result.vertNormal_.resize(vertNormal_.size()); + transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(), Transform4x3({transform_})); + + glm::mat3 normalTransform = + glm::inverse(glm::transpose(glm::mat3(transform_))); + transform(policy, faceNormal_.begin(), faceNormal_.end(), result.faceNormal_.begin(), + TransformNormals({normalTransform})); + transform(policy, vertNormal_.begin(), vertNormal_.end(), result.vertNormal_.begin(), + TransformNormals({normalTransform})); + // This optimization does a cheap collider update if the transform is + // axis-aligned. + if (!result.collider_.Transform(transform_)) result.Update(); + + const float oldScale = result.bBox_.Scale(); + result.CalculateBBox(); + + const float newScale = result.bBox_.Scale(); + result.precision_ *= glm::max(1.0f, newScale / oldScale); + + // Maximum of inherited precision loss and translational precision loss. + result.SetPrecision(precision_); + return result; +} + /** * Sets the precision based on the bounding box, and limits its minimum value by * the optional input. diff --git a/manifold/src/impl.h b/manifold/src/impl.h index 8c86cecdb..2782e24fb 100644 --- a/manifold/src/impl.h +++ b/manifold/src/impl.h @@ -74,6 +74,7 @@ struct Manifold::Impl { void Update(); void ApplyTransform(const glm::mat4x3 &transform); + Impl Transform(const glm::mat4x3 &transform) const; SparseIndices EdgeCollisions(const Impl& B) const; SparseIndices VertexCollisionsZ(const VecDH& vertsIn) const; diff --git a/utilities/include/par.h b/utilities/include/par.h index 0fac6dafc..0a3e55848 100644 --- a/utilities/include/par.h +++ b/utilities/include/par.h @@ -132,6 +132,7 @@ THRUST_DYNAMIC_BACKEND_VOID(inclusive_scan) THRUST_DYNAMIC_BACKEND_VOID(exclusive_scan) THRUST_DYNAMIC_BACKEND_VOID(uninitialized_fill) THRUST_DYNAMIC_BACKEND_VOID(uninitialized_copy) +THRUST_DYNAMIC_BACKEND_VOID(copy_n) THRUST_DYNAMIC_BACKEND(all_of, bool) THRUST_DYNAMIC_BACKEND(is_sorted, bool) @@ -145,6 +146,7 @@ THRUST_DYNAMIC_BACKEND(copy_if, void) THRUST_DYNAMIC_BACKEND(remove_if, void) THRUST_DYNAMIC_BACKEND(unique, void) THRUST_DYNAMIC_BACKEND(find, void) +THRUST_DYNAMIC_BACKEND(find_if, void) THRUST_DYNAMIC_BACKEND(reduce_by_key, void) THRUST_DYNAMIC_BACKEND(transform_reduce, void) THRUST_DYNAMIC_BACKEND(lower_bound, void) diff --git a/utilities/include/sparse.h b/utilities/include/sparse.h index 2562e561d..d34625e1c 100644 --- a/utilities/include/sparse.h +++ b/utilities/include/sparse.h @@ -14,11 +14,6 @@ #pragma once #include -#include -#include -#include -#include -#include #include "par.h" #include "structs.h" diff --git a/utilities/include/structs.h b/utilities/include/structs.h index abafd1e10..d27c44c66 100644 --- a/utilities/include/structs.h +++ b/utilities/include/structs.h @@ -415,18 +415,6 @@ struct Box { } }; -/** - * Print the contents of this vector to standard output. - */ -template -void Dump(const std::vector& vec) { - std::cout << "Vec = " << std::endl; - for (int i = 0; i < vec.size(); ++i) { - std::cout << i << ", " << vec[i] << ", " << std::endl; - } - std::cout << std::endl; -} - inline std::ostream& operator<<(std::ostream& stream, const Box& box) { return stream << "min: " << box.min.x << ", " << box.min.y << ", " << box.min.z << ", " @@ -468,6 +456,18 @@ inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) { return stream << "meshID: " << ref.meshID << ", tri: " << ref.tri << ", uvw idx: " << ref.vertBary; } + +/** + * Print the contents of this vector to standard output. + */ +template +void Dump(const std::vector& vec) { + std::cout << "Vec = " << std::endl; + for (int i = 0; i < vec.size(); ++i) { + std::cout << i << ", " << vec[i] << ", " << std::endl; + } + std::cout << std::endl; +} /** @} */ } // namespace manifold From b54cc93b4c27102339c19329b99614cd67de8ede Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 4 Jun 2022 19:25:52 +0800 Subject: [PATCH 03/16] every manifold operation now returns a new manifold object --- manifold/include/manifold.h | 30 +++--- manifold/src/constructors.cpp | 154 ++++++++-------------------- manifold/src/manifold.cpp | 187 ++++++++++++++++------------------ 3 files changed, 152 insertions(+), 219 deletions(-) diff --git a/manifold/include/manifold.h b/manifold/include/manifold.h index 3d14c453a..2ecc219ae 100644 --- a/manifold/include/manifold.h +++ b/manifold/include/manifold.h @@ -20,6 +20,9 @@ namespace manifold { +class CsgNode; +class CsgLeafNode; + /** @defgroup Core * @brief The central classes of the library * @{ @@ -104,20 +107,19 @@ class Manifold { ///@{ MeshRelation GetMeshRelation() const; std::vector GetMeshIDs() const; - int SetAsOriginal(); + std::pair SetAsOriginal() const; ///@} /** @name Modification - * Change this manifold in-place. */ ///@{ - Manifold& Translate(glm::vec3); - Manifold& Scale(glm::vec3); - Manifold& Rotate(float xDegrees, float yDegrees = 0.0f, - float zDegrees = 0.0f); - Manifold& Transform(const glm::mat4x3&); - Manifold& Warp(std::function); - Manifold& Refine(int); + Manifold Translate(glm::vec3) const; + Manifold Scale(glm::vec3) const; + Manifold Rotate(float xDegrees, float yDegrees = 0.0f, + float zDegrees = 0.0f) const; + Manifold Transform(const glm::mat4x3&) const; + Manifold Warp(std::function) const; + Manifold Refine(int) const; // Manifold RefineToLength(float); // Manifold RefineToPrecision(float); ///@} @@ -157,9 +159,13 @@ class Manifold { struct Impl; private: - std::unique_ptr pImpl_; - mutable glm::mat4x3 transform_ = glm::mat4x3(1.0f); - void ApplyTransform() const; + Manifold(std::shared_ptr pNode_); + Manifold(std::shared_ptr pImpl_); + + mutable std::shared_ptr pNode_; + + CsgLeafNode& GetCsgLeafNode() const; + static int circularSegments_; static float circularAngle_; static float circularEdgeLength_; diff --git a/manifold/src/constructors.cpp b/manifold/src/constructors.cpp index ecc2edda0..28859ac76 100644 --- a/manifold/src/constructors.cpp +++ b/manifold/src/constructors.cpp @@ -14,6 +14,7 @@ #include +#include "csg_tree.h" #include "graph.h" #include "impl.h" #include "par.h" @@ -108,9 +109,9 @@ Manifold Manifold::Smooth(const Mesh& mesh, "when supplying tangents, the normal constructor should be used " "rather than Smooth()."); - Manifold manifold(mesh); - manifold.pImpl_->CreateTangents(sharpenedEdges); - return manifold; + std::shared_ptr impl = std::make_shared(mesh); + impl->CreateTangents(sharpenedEdges); + return Manifold(impl); } /** @@ -118,9 +119,7 @@ Manifold Manifold::Smooth(const Mesh& mesh, * and the rest at similarly symmetric points. */ Manifold Manifold::Tetrahedron() { - Manifold tetrahedron; - tetrahedron.pImpl_ = std::make_unique(Impl::Shape::TETRAHEDRON); - return tetrahedron; + return Manifold(std::make_shared(Impl::Shape::TETRAHEDRON)); } /** @@ -131,10 +130,9 @@ Manifold Manifold::Tetrahedron() { * @param center Set to true to shift the center to the origin. */ Manifold Manifold::Cube(glm::vec3 size, bool center) { - Manifold cube; - cube.pImpl_ = std::make_unique(Impl::Shape::CUBE); - cube.Scale(size); - if (center) cube.Translate(-size / 2.0f); + auto cube = Manifold(std::make_shared(Impl::Shape::CUBE)); + cube = cube.Scale(size); + if (center) cube = cube.Translate(-size / 2.0f); return cube; } @@ -164,7 +162,8 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh, } Manifold cylinder = Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale)); - if (center) cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)); + if (center) + cylinder = cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)); return cylinder; } @@ -181,15 +180,14 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh, Manifold Manifold::Sphere(float radius, int circularSegments) { int n = circularSegments > 0 ? (circularSegments + 3) / 4 : GetCircularSegments(radius) / 4; - Manifold sphere; - sphere.pImpl_ = std::make_unique(Impl::Shape::OCTAHEDRON); - sphere.pImpl_->Subdivide(n); - for_each_n(autoPolicy(sphere.NumVert()), sphere.pImpl_->vertPos_.begin(), - sphere.NumVert(), ToSphere({radius})); - sphere.pImpl_->Finish(); + auto pImpl_ = std::make_shared(Impl::Shape::OCTAHEDRON); + pImpl_->Subdivide(n); + for_each_n(autoPolicy(pImpl_->NumVert()), pImpl_->vertPos_.begin(), + pImpl_->NumVert(), ToSphere({radius})); + pImpl_->Finish(); // Ignore preceding octahedron. - sphere.pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1)); - return sphere; + pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1)); + return Manifold(pImpl_); } /** @@ -211,9 +209,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions, float twistDegrees, glm::vec2 scaleTop) { ALWAYS_ASSERT(scaleTop.x >= 0 && scaleTop.y >= 0, userErr, "scale values cannot be negative"); - Manifold extrusion; + auto pImpl_ = std::make_shared(); ++nDivisions; - auto& vertPos = extrusion.pImpl_->vertPos_; + auto& vertPos = pImpl_->vertPos_; VecDH triVertsDH; auto& triVerts = triVertsDH; int nCrossSection = 0; @@ -263,10 +261,10 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions, if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions); } - extrusion.pImpl_->CreateHalfedges(triVertsDH); - extrusion.pImpl_->Finish(); - extrusion.pImpl_->InitializeNewReference(); - return extrusion; + pImpl_->CreateHalfedges(triVertsDH); + pImpl_->Finish(); + pImpl_->InitializeNewReference(); + return Manifold(pImpl_); } /** @@ -289,8 +287,8 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) { } int nDivisions = circularSegments > 2 ? circularSegments : GetCircularSegments(radius); - Manifold revoloid; - auto& vertPos = revoloid.pImpl_->vertPos_; + auto pImpl_ = std::make_shared(); + auto& vertPos = pImpl_->vertPos_; VecDH triVertsDH; auto& triVerts = triVertsDH; float dPhi = 360.0f / nDivisions; @@ -362,10 +360,10 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) { } } - revoloid.pImpl_->CreateHalfedges(triVertsDH); - revoloid.pImpl_->Finish(); - revoloid.pImpl_->InitializeNewReference(); - return revoloid; + pImpl_->CreateHalfedges(triVertsDH); + pImpl_->Finish(); + pImpl_->InitializeNewReference(); + return Manifold(pImpl_); } /** @@ -376,73 +374,11 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) { * @param manifolds A vector of Manifolds to lazy-union together. */ Manifold Manifold::Compose(const std::vector& manifolds) { - int numVert = 0; - int numEdge = 0; - int numTri = 0; - int numBary = 0; - for (const Manifold& manifold : manifolds) { - numVert += manifold.NumVert(); - numEdge += manifold.NumEdge(); - numTri += manifold.NumTri(); - numBary += manifold.pImpl_->meshRelation_.barycentric.size(); + std::vector> children; + for (const auto& manifold : manifolds) { + children.push_back(manifold.pNode_->ToLeafNode()); } - - Manifold out; - Impl& combined = *(out.pImpl_); - combined.vertPos_.resize(numVert); - combined.halfedge_.resize(2 * numEdge); - combined.faceNormal_.resize(numTri); - combined.halfedgeTangent_.resize(2 * numEdge); - combined.meshRelation_.barycentric.resize(numBary); - combined.meshRelation_.triBary.resize(numTri); - auto policy = autoPolicy(numTri); - - int nextVert = 0; - int nextEdge = 0; - int nextTri = 0; - int nextBary = 0; - for (const Manifold& manifold : manifolds) { - manifold.ApplyTransform(); - const Impl& impl = *(manifold.pImpl_); - - copy(policy, impl.vertPos_.begin(), impl.vertPos_.end(), - combined.vertPos_.begin() + nextVert); - copy(policy, impl.faceNormal_.begin(), impl.faceNormal_.end(), - combined.faceNormal_.begin() + nextTri); - copy(policy, impl.halfedgeTangent_.begin(), impl.halfedgeTangent_.end(), - combined.halfedgeTangent_.begin() + nextEdge); - copy(policy, impl.meshRelation_.barycentric.begin(), - impl.meshRelation_.barycentric.end(), - combined.meshRelation_.barycentric.begin() + nextBary); - transform(policy, impl.meshRelation_.triBary.begin(), - impl.meshRelation_.triBary.end(), - combined.meshRelation_.triBary.begin() + nextTri, - UpdateTriBary({nextBary})); - transform(policy, impl.halfedge_.begin(), impl.halfedge_.end(), - combined.halfedge_.begin() + nextEdge, - UpdateHalfedge({nextVert, nextEdge, nextTri})); - - // Assign new IDs to triangles added in this iteration, to differentiate - // triangles coming from different manifolds. - // See the end of `boolean_result.cpp` for details. - VecDH meshIDs; - VecDH original; - for (auto& entry : impl.meshRelation_.originalID) { - meshIDs.push_back(entry.first); - original.push_back(entry.second); - } - int meshIDStart = combined.meshRelation_.originalID.size(); - combined.UpdateMeshIDs(meshIDs, original, nextTri, - impl.meshRelation_.triBary.size(), meshIDStart); - - nextVert += manifold.NumVert(); - nextEdge += 2 * manifold.NumEdge(); - nextTri += manifold.NumTri(); - nextBary += impl.meshRelation_.barycentric.size(); - } - - combined.Finish(); - return out; + return Manifold(std::make_shared(CsgLeafNode::Compose(children))); } /** @@ -452,6 +388,7 @@ Manifold Manifold::Compose(const std::vector& manifolds) { */ std::vector Manifold::Decompose() const { Graph graph; + auto pImpl_ = GetCsgLeafNode().GetImpl(); for (int i = 0; i < NumVert(); ++i) { graph.add_nodes(i); } @@ -476,20 +413,20 @@ std::vector Manifold::Decompose() const { original.push_back(entry.second); } - std::vector meshes(numLabel); + std::vector meshes; for (int i = 0; i < numLabel; ++i) { - meshes[i].pImpl_->vertPos_.resize(NumVert()); + auto impl = std::make_shared(); + impl->vertPos_.resize(NumVert()); VecDH vertNew2Old(NumVert()); auto policy = autoPolicy(NumVert()); - auto start = zip(meshes[i].pImpl_->vertPos_.begin(), vertNew2Old.begin()); + auto start = zip(impl->vertPos_.begin(), vertNew2Old.begin()); int nVert = copy_if( policy, zip(pImpl_->vertPos_.begin(), countAt(0)), zip(pImpl_->vertPos_.end(), countAt(NumVert())), vertLabel.begin(), - zip(meshes[i].pImpl_->vertPos_.begin(), vertNew2Old.begin()), - Equals({i})) - + zip(impl->vertPos_.begin(), vertNew2Old.begin()), Equals({i})) - start; - meshes[i].pImpl_->vertPos_.resize(nVert); + impl->vertPos_.resize(nVert); VecDH faceNew2Old(NumTri()); sequence(policy, faceNew2Old.begin(), faceNew2Old.end()); @@ -501,16 +438,15 @@ std::vector Manifold::Decompose() const { faceNew2Old.begin(); faceNew2Old.resize(nFace); - meshes[i].pImpl_->GatherFaces(*pImpl_, faceNew2Old); - meshes[i].pImpl_->ReindexVerts(vertNew2Old, pImpl_->NumVert()); + impl->GatherFaces(*pImpl_, faceNew2Old); + impl->ReindexVerts(vertNew2Old, pImpl_->NumVert()); - meshes[i].pImpl_->Finish(); + impl->Finish(); // meshIDs and original will only be sorted after successful updates, so we // can keep using the old one. - meshes[i].pImpl_->UpdateMeshIDs(meshIDs, original); - - meshes[i].transform_ = transform_; + impl->UpdateMeshIDs(meshIDs, original); + meshes.push_back(Manifold(impl)); } return meshes; } diff --git a/manifold/src/manifold.cpp b/manifold/src/manifold.cpp index 89acc8bc6..b2ffdb3c5 100644 --- a/manifold/src/manifold.cpp +++ b/manifold/src/manifold.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "boolean3.h" +#include "csg_tree.h" #include "impl.h" #include "par.h" @@ -45,7 +46,7 @@ Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) { Manifold::Cube(glm::vec3(2.0f), true).Translate({1.0f, 0.0f, 0.0f}); float size = glm::length(bBox.Center() - normal * originOffset) + 0.5f * glm::length(bBox.Size()); - cutter.Scale(glm::vec3(size)).Translate({originOffset, 0.0f, 0.0f}); + cutter = cutter.Scale(glm::vec3(size)).Translate({originOffset, 0.0f, 0.0f}); float yDeg = glm::degrees(-glm::asin(normal.z)); float zDeg = glm::degrees(glm::atan(normal.y, normal.x)); return cutter.Rotate(0.0f, yDeg, zDeg); @@ -58,21 +59,32 @@ namespace manifold { * Construct an empty Manifold. * */ -Manifold::Manifold() : pImpl_{std::make_unique()} {} +Manifold::Manifold() : pNode_{std::make_shared()} {} Manifold::~Manifold() = default; Manifold::Manifold(Manifold&&) noexcept = default; Manifold& Manifold::operator=(Manifold&&) noexcept = default; -Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)), transform_(other.transform_) {} +Manifold::Manifold(const Manifold& other) : pNode_(other.pNode_) {} + +Manifold::Manifold(std::shared_ptr pNode) : pNode_(pNode) {} + +Manifold::Manifold(std::shared_ptr pImpl_) + : pNode_(std::make_shared(pImpl_)) {} Manifold& Manifold::operator=(const Manifold& other) { if (this != &other) { - pImpl_.reset(new Impl(*other.pImpl_)); - transform_ = other.transform_; + pNode_ = other.pNode_; } return *this; } +CsgLeafNode& Manifold::GetCsgLeafNode() const { + if (pNode_->GetNodeType() != CsgNodeType::LEAF) { + pNode_ = pNode_->ToLeafNode(); + } + return *std::static_pointer_cast(pNode_); +} + /** * Convert a Mesh into a Manifold. Will throw a topologyErr exception if the * input is not an oriented 2-manifold. Will collapse degenerate triangles and @@ -103,29 +115,29 @@ Manifold::Manifold(const Mesh& mesh, const std::vector& triProperties, const std::vector& properties, const std::vector& propertyTolerance) - : pImpl_{std::make_unique(mesh, triProperties, properties, - propertyTolerance)} {} + : pNode_(std::make_shared(std::make_shared( + mesh, triProperties, properties, propertyTolerance))) {} /** * This returns a Mesh of simple vectors of vertices and triangles suitable for * saving or other operations outside of the context of this library. */ Mesh Manifold::GetMesh() const { - ApplyTransform(); + const Impl& impl = *GetCsgLeafNode().GetImpl(); Mesh result; - result.vertPos.insert(result.vertPos.end(), pImpl_->vertPos_.begin(), - pImpl_->vertPos_.end()); - result.vertNormal.insert(result.vertNormal.end(), pImpl_->vertNormal_.begin(), - pImpl_->vertNormal_.end()); + result.vertPos.insert(result.vertPos.end(), impl.vertPos_.begin(), + impl.vertPos_.end()); + result.vertNormal.insert(result.vertNormal.end(), impl.vertNormal_.begin(), + impl.vertNormal_.end()); result.halfedgeTangent.insert(result.halfedgeTangent.end(), - pImpl_->halfedgeTangent_.begin(), - pImpl_->halfedgeTangent_.end()); + impl.halfedgeTangent_.begin(), + impl.halfedgeTangent_.end()); result.triVerts.resize(NumTri()); // note that `triVerts` is `std::vector`, so we cannot use thrust::device thrust::for_each_n(thrust::host, zip(result.triVerts.begin(), countAt(0)), - NumTri(), MakeTri({pImpl_->halfedge_.cptrH()})); + NumTri(), MakeTri({impl.halfedge_.cptrH()})); return result; } @@ -194,26 +206,24 @@ int Manifold::GetCircularSegments(float radius) { /** * Does the Manifold have any triangles? */ -bool Manifold::IsEmpty() const { return pImpl_->IsEmpty(); } +bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); } /** * The number of vertices in the Manifold. */ -int Manifold::NumVert() const { return pImpl_->NumVert(); } +int Manifold::NumVert() const { return GetCsgLeafNode().GetImpl()->NumVert(); } /** * The number of edges in the Manifold. */ -int Manifold::NumEdge() const { return pImpl_->NumEdge(); } +int Manifold::NumEdge() const { return GetCsgLeafNode().GetImpl()->NumEdge(); } /** * The number of triangles in the Manifold. */ -int Manifold::NumTri() const { return pImpl_->NumTri(); } +int Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); } /** * Returns the axis-aligned bounding box of all the Manifold's vertices. */ -Box Manifold::BoundingBox() const { - return pImpl_->bBox_.Transform(transform_); -} +Box Manifold::BoundingBox() const { return GetCsgLeafNode().GetBoundingBox(); } /** * Returns the precision of this Manifold's vertices, which tracks the @@ -223,8 +233,7 @@ Box Manifold::BoundingBox() const { * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). */ float Manifold::Precision() const { - ApplyTransform(); - return pImpl_->precision_; + return GetCsgLeafNode().GetImpl()->precision_; } /** @@ -244,8 +253,7 @@ int Manifold::Genus() const { * == 0. */ Properties Manifold::GetProperties() const { - ApplyTransform(); - return pImpl_->GetProperties(); + return GetCsgLeafNode().GetImpl()->GetProperties(); } /** @@ -257,8 +265,7 @@ Properties Manifold::GetProperties() const { * vectors in the structure) and also returns their minimum and maximum values. */ Curvature Manifold::GetCurvature() const { - ApplyTransform(); - return pImpl_->GetCurvature(); + return GetCsgLeafNode().GetImpl()->GetCurvature(); } /** @@ -272,7 +279,7 @@ Curvature Manifold::GetCurvature() const { */ MeshRelation Manifold::GetMeshRelation() const { MeshRelation out; - const auto& relation = pImpl_->meshRelation_; + const auto& relation = GetCsgLeafNode().GetImpl()->meshRelation_; out.triBary.insert(out.triBary.end(), relation.triBary.begin(), relation.triBary.end()); for (auto& bary : out.triBary) { @@ -291,8 +298,8 @@ MeshRelation Manifold::GetMeshRelation() const { */ std::vector Manifold::GetMeshIDs() const { std::vector out; - out.reserve(pImpl_->meshRelation_.originalID.size()); - for (auto& entry : pImpl_->meshRelation_.originalID) { + out.reserve(GetCsgLeafNode().GetImpl()->meshRelation_.originalID.size()); + for (auto& entry : GetCsgLeafNode().GetImpl()->meshRelation_.originalID) { out.push_back(entry.second); } return out; @@ -313,29 +320,37 @@ std::vector Manifold::GetMeshIDs() const { * * @returns New MeshID */ -int Manifold::SetAsOriginal() { - int meshID = pImpl_->InitializeNewReference(); - return meshID; +std::pair Manifold::SetAsOriginal() const { + auto newImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); + int meshID = newImpl->InitializeNewReference(); + return std::make_pair(meshID, + Manifold(std::make_shared(newImpl))); } /** * Should always be true. Also checks saneness of the internal data structures. */ -bool Manifold::IsManifold() const { return pImpl_->IsManifold(); } +bool Manifold::IsManifold() const { + return GetCsgLeafNode().GetImpl()->IsManifold(); +} /** * The triangle normal vectors are saved over the course of operations rather * than recalculated to avoid rounding error. This checks that triangles still * match their normal vectors within Precision(). */ -bool Manifold::MatchesTriNormals() const { return pImpl_->MatchesTriNormals(); } +bool Manifold::MatchesTriNormals() const { + return GetCsgLeafNode().GetImpl()->MatchesTriNormals(); +} /** * The number of triangles that are colinear within Precision(). This library * attempts to remove all of these, but it cannot always remove all of them * without changing the mesh by too much. */ -int Manifold::NumDegenerateTris() const { return pImpl_->NumDegenerateTris(); } +int Manifold::NumDegenerateTris() const { + return GetCsgLeafNode().GetImpl()->NumDegenerateTris(); +} /** * This is a checksum-style verification of the collider, simply returning the @@ -344,13 +359,12 @@ int Manifold::NumDegenerateTris() const { return pImpl_->NumDegenerateTris(); } * @param other A Manifold to overlap with. */ int Manifold::NumOverlaps(const Manifold& other) const { - ApplyTransform(); - other.ApplyTransform(); - - SparseIndices overlaps = pImpl_->EdgeCollisions(*other.pImpl_); + SparseIndices overlaps = GetCsgLeafNode().GetImpl()->EdgeCollisions( + *other.GetCsgLeafNode().GetImpl()); int num_overlaps = overlaps.size(); - overlaps = other.pImpl_->EdgeCollisions(*pImpl_); + overlaps = other.GetCsgLeafNode().GetImpl()->EdgeCollisions( + *GetCsgLeafNode().GetImpl()); return num_overlaps += overlaps.size(); } @@ -360,9 +374,8 @@ int Manifold::NumOverlaps(const Manifold& other) const { * * @param v The vector to add to every vertex. */ -Manifold& Manifold::Translate(glm::vec3 v) { - transform_[3] += v; - return *this; +Manifold Manifold::Translate(glm::vec3 v) const { + return Manifold(pNode_->Translate(v)); } /** @@ -371,11 +384,8 @@ Manifold& Manifold::Translate(glm::vec3 v) { * * @param v The vector to multiply every vertex by per component. */ -Manifold& Manifold::Scale(glm::vec3 v) { - glm::mat3 s(1.0f); - for (int i : {0, 1, 2}) s[i] *= v; - transform_ = s * transform_; - return *this; +Manifold Manifold::Scale(glm::vec3 v) const { + return Manifold(pNode_->Scale(v)); } /** @@ -390,18 +400,9 @@ Manifold& Manifold::Scale(glm::vec3 v) { * @param yDegrees Second rotation, degrees about the Y-axis. * @param zDegrees Third rotation, degrees about the Z-axis. */ -Manifold& Manifold::Rotate(float xDegrees, float yDegrees, float zDegrees) { - glm::mat3 rX(1.0f, 0.0f, 0.0f, // - 0.0f, cosd(xDegrees), sind(xDegrees), // - 0.0f, -sind(xDegrees), cosd(xDegrees)); - glm::mat3 rY(cosd(yDegrees), 0.0f, -sind(yDegrees), // - 0.0f, 1.0f, 0.0f, // - sind(yDegrees), 0.0f, cosd(yDegrees)); - glm::mat3 rZ(cosd(zDegrees), sind(zDegrees), 0.0f, // - -sind(zDegrees), cosd(zDegrees), 0.0f, // - 0.0f, 0.0f, 1.0f); - transform_ = rZ * rY * rX * transform_; - return *this; +Manifold Manifold::Rotate(float xDegrees, float yDegrees, + float zDegrees) const { + return Manifold(pNode_->Rotate(xDegrees, yDegrees, zDegrees)); } /** @@ -411,10 +412,8 @@ Manifold& Manifold::Rotate(float xDegrees, float yDegrees, float zDegrees) { * * @param m The affine transform matrix to apply to all the vertices. */ -Manifold& Manifold::Transform(const glm::mat4x3& m) { - glm::mat4 old(transform_); - transform_ = m * old; - return *this; +Manifold Manifold::Transform(const glm::mat4x3& m) const { + return Manifold(pNode_->Transform(m)); } /** @@ -426,14 +425,15 @@ Manifold& Manifold::Transform(const glm::mat4x3& m) { * * @param warpFunc A function that modifies a given vertex position. */ -Manifold& Manifold::Warp(std::function warpFunc) { - ApplyTransform(); - thrust::for_each_n(thrust::host, pImpl_->vertPos_.begin(), NumVert(), warpFunc); - pImpl_->Update(); - pImpl_->faceNormal_.resize(0); // force recalculation of triNormal - pImpl_->CalculateNormals(); - pImpl_->SetPrecision(); - return *this; +Manifold Manifold::Warp(std::function warpFunc) const { + auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); + thrust::for_each_n(thrust::host, pImpl->vertPos_.begin(), NumVert(), + warpFunc); + pImpl->Update(); + pImpl->faceNormal_.resize(0); // force recalculation of triNormal + pImpl->CalculateNormals(); + pImpl->SetPrecision(); + return Manifold(std::make_shared(pImpl)); } /** @@ -446,9 +446,10 @@ Manifold& Manifold::Warp(std::function warpFunc) { * * @param n The number of pieces to split every edge into. Must be > 1. */ -Manifold& Manifold::Refine(int n) { - pImpl_->Refine(n); - return *this; +Manifold Manifold::Refine(int n) const { + auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); + pImpl->Refine(n); + return Manifold(std::make_shared(pImpl)); } /** @@ -466,12 +467,8 @@ Manifold& Manifold::Refine(int n) { * @param op The type of operation to perform. */ Manifold Manifold::Boolean(const Manifold& second, OpType op) const { - ApplyTransform(); - second.ApplyTransform(); - Boolean3 boolean(*pImpl_, *second.pImpl_, op); - Manifold result; - result.pImpl_ = std::make_unique(boolean.Result(op)); - return result; + std::vector> children({pNode_, second.pNode_}); + return Manifold(std::make_shared(children, op)); } /** @@ -527,15 +524,15 @@ Manifold& Manifold::operator^=(const Manifold& Q) { * @param cutter */ std::pair Manifold::Split(const Manifold& cutter) const { - ApplyTransform(); - cutter.ApplyTransform(); - Boolean3 boolean(*pImpl_, *cutter.pImpl_, OpType::SUBTRACT); - std::pair result; - result.first.pImpl_ = - std::make_unique(boolean.Result(OpType::INTERSECT)); - result.second.pImpl_ = - std::make_unique(boolean.Result(OpType::SUBTRACT)); - return result; + auto impl1 = GetCsgLeafNode().GetImpl(); + auto impl2 = cutter.GetCsgLeafNode().GetImpl(); + + Boolean3 boolean(*impl1, *impl2, OpType::SUBTRACT); + auto result1 = std::make_shared( + std::make_unique(boolean.Result(OpType::INTERSECT))); + auto result2 = std::make_shared( + std::make_unique(boolean.Result(OpType::SUBTRACT))); + return std::make_pair(Manifold(result1), Manifold(result2)); } /** @@ -562,13 +559,7 @@ std::pair Manifold::SplitByPlane(glm::vec3 normal, * direction of the normal vector. */ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { - ApplyTransform(); return *this ^ Halfspace(BoundingBox(), normal, originOffset); } -void Manifold::ApplyTransform() const { - pImpl_->ApplyTransform(transform_); - transform_ = glm::mat4x3(1.0f); -} - } // namespace manifold From 8d13c01a54f1c32e73ddc25c893b82594bb61ae0 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 4 Jun 2022 19:26:29 +0800 Subject: [PATCH 04/16] test updates --- samples/src/bracelet.cpp | 10 ++--- samples/src/knot.cpp | 37 ++++++++-------- samples/src/menger_sponge.cpp | 12 ++--- samples/src/rounded_frame.cpp | 12 +++-- test/mesh_test.cpp | 82 ++++++++++++++++------------------- test/samples_test.cpp | 7 ++- 6 files changed, 76 insertions(+), 84 deletions(-) diff --git a/samples/src/bracelet.cpp b/samples/src/bracelet.cpp index c8b048550..144524c1a 100644 --- a/samples/src/bracelet.cpp +++ b/samples/src/bracelet.cpp @@ -31,12 +31,12 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius, decorRadius * sind(dPhiDeg * i)), 0}); } - Manifold decor = std::move(Manifold::Extrude(circle, width, nDivision, 180) - .Scale({1.0f, 0.5f, 1.0f}) - .Translate({0.0f, radius, 0.0f})); + Manifold decor = Manifold::Extrude(circle, width, nDivision, 180) + .Scale({1.0f, 0.5f, 1.0f}) + .Translate({0.0f, radius, 0.0f}); for (int i = 0; i < nDecor; ++i) { - base += decor.Rotate(0, 0, 360.0f / nDecor); + base += decor.Rotate(0, 0, (360.0f / nDecor) * i); } Polygons stretch(1); @@ -52,7 +52,7 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius, } base = Manifold::Extrude(stretch, width) ^ base; - base.SetAsOriginal(); + base = base.SetAsOriginal().second; return base; } diff --git a/samples/src/knot.cpp b/samples/src/knot.cpp index dcc8bc569..a0a009348 100644 --- a/samples/src/knot.cpp +++ b/samples/src/knot.cpp @@ -58,31 +58,32 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius, Manifold knot = Manifold::Revolve(circle, m); - knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) { - float psi = q * atan2(v.x, v.y); - float theta = psi * p / q; - glm::vec2 xy = glm::vec2(v); - float x1 = sqrt(glm::dot(xy, xy)); - float phi = atan2(x1 - 2, v.z); - v = glm::vec3(cos(phi), 0.0f, sin(phi)); - v *= threadRadius; - float r = majorRadius + minorRadius * cos(theta); - v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r))); - v.x += minorRadius; - v = glm::rotateY(v, theta); - v.x += majorRadius; - v = glm::rotateZ(v, psi); - }); + knot = + knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) { + float psi = q * atan2(v.x, v.y); + float theta = psi * p / q; + glm::vec2 xy = glm::vec2(v); + float x1 = sqrt(glm::dot(xy, xy)); + float phi = atan2(x1 - 2, v.z); + v = glm::vec3(cos(phi), 0.0f, sin(phi)); + v *= threadRadius; + float r = majorRadius + minorRadius * cos(theta); + v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r))); + v.x += minorRadius; + v = glm::rotateY(v, theta); + v.x += majorRadius; + v = glm::rotateZ(v, psi); + }); if (kLoops > 1) { std::vector knots; for (float k = 0; k < kLoops; ++k) { - knots.emplace_back(knot); - knots.back().Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p))); + knots.push_back( + knot.Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p)))); } knot = Manifold::Compose(knots); } return knot; } -} // namespace manifold \ No newline at end of file +} // namespace manifold diff --git a/samples/src/menger_sponge.cpp b/samples/src/menger_sponge.cpp index 2cb4798ed..af478f213 100644 --- a/samples/src/menger_sponge.cpp +++ b/samples/src/menger_sponge.cpp @@ -21,8 +21,8 @@ using namespace manifold; void Fractal(std::vector& holes, Manifold& hole, float w, glm::vec2 position, int depth, int maxDepth) { w /= 3; - holes.emplace_back(hole); - holes.back().Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f)); + holes.push_back( + hole.Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f))); if (depth == maxDepth) return; glm::vec2 offsets[8] = {{-w, -w}, {-w, 0.0f}, {-w, w}, {0.0f, w}, @@ -50,9 +50,11 @@ Manifold MengerSponge(int n) { Manifold hole = Manifold::Compose(holes); result -= hole; - result -= hole.Rotate(90); - result -= hole.Rotate(0, 0, 90); - result.SetAsOriginal(); + hole = hole.Rotate(90); + result -= hole; + hole = hole.Rotate(0, 0, 90); + result -= hole; + result = result.SetAsOriginal().second; return result; } } // namespace manifold diff --git a/samples/src/rounded_frame.cpp b/samples/src/rounded_frame.cpp index 3e8ee1cb5..229c3f0ce 100644 --- a/samples/src/rounded_frame.cpp +++ b/samples/src/rounded_frame.cpp @@ -31,20 +31,18 @@ Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) { Manifold corner = Manifold::Sphere(radius, circularSegments); Manifold edge1 = corner + edge; - edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0}); + edge1 = edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0}); - Manifold edge2 = edge1; - edge2.Rotate(0, 0, 180); + Manifold edge2 = edge1.Rotate(0, 0, 180); edge2 += edge1; edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0}); - Manifold edge4 = edge2; - edge4.Rotate(0, 0, 90); + Manifold edge4 = edge2.Rotate(0, 0, 90); edge4 += edge2; Manifold frame = edge4.Translate({0, 0, -edgeLength / 2}); - frame += edge4.Rotate(180); + frame += frame.Rotate(180); return frame; } -} // namespace manifold \ No newline at end of file +} // namespace manifold diff --git a/test/mesh_test.cpp b/test/mesh_test.cpp index e4efdb9c5..5651df519 100644 --- a/test/mesh_test.cpp +++ b/test/mesh_test.cpp @@ -173,8 +173,7 @@ TEST(Manifold, DISABLED_Determinism) { Manifold manifold(ImportMesh("data/gyroidpuzzle.ply")); EXPECT_TRUE(manifold.IsManifold()); - Manifold manifold1 = manifold; - manifold1.Translate(glm::vec3(5.0f)); + Manifold manifold1 = manifold.Translate(glm::vec3(5.0f)); int num_overlaps = manifold.NumOverlaps(manifold1); ASSERT_EQ(num_overlaps, 229611); @@ -273,7 +272,7 @@ TEST(Manifold, Revolve2) { TEST(Manifold, Smooth) { Manifold tet = Manifold::Tetrahedron(); Manifold smooth = Manifold::Smooth(tet.GetMesh()); - smooth.Refine(100); + smooth = smooth.Refine(100); ExpectMeshes(smooth, {{20002, 40000}}); auto prop = smooth.GetProperties(); EXPECT_NEAR(prop.volume, 17.38, 0.1); @@ -312,7 +311,7 @@ TEST(Manifold, ManualSmooth) { smooth.halfedgeTangent[16] = {0, 0, 0, 1}; smooth.halfedgeTangent[18] = {0, 0, 0, 1}; Manifold interp(smooth); - interp.Refine(100); + interp = interp.Refine(100); ExpectMeshes(interp, {{40002, 80000}}); auto prop = interp.GetProperties(); @@ -343,7 +342,7 @@ TEST(Manifold, ManualSmooth) { TEST(Manifold, Csaszar) { Manifold csaszar = Manifold::Smooth(Csaszar()); - csaszar.Refine(100); + csaszar = csaszar.Refine(100); ExpectMeshes(csaszar, {{70000, 140000}}); auto prop = csaszar.GetProperties(); EXPECT_NEAR(prop.volume, 84699, 10); @@ -381,7 +380,7 @@ TEST(Manifold, GetProperties) { EXPECT_FLOAT_EQ(prop.volume, 1.0f); EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f); - cube.Scale(glm::vec3(-1.0f)); + cube = cube.Scale(glm::vec3(-1.0f)); prop = cube.GetProperties(); EXPECT_FLOAT_EQ(prop.volume, -1.0f); EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f); @@ -390,9 +389,9 @@ TEST(Manifold, GetProperties) { TEST(Manifold, Precision) { Manifold cube = Manifold::Cube(); EXPECT_FLOAT_EQ(cube.Precision(), kTolerance); - cube.Scale({0.1, 1, 10}); + cube = cube.Scale({0.1, 1, 10}); EXPECT_FLOAT_EQ(cube.Precision(), 10 * kTolerance); - cube.Translate({-100, -10, -1}); + cube = cube.Translate({-100, -10, -1}); EXPECT_FLOAT_EQ(cube.Precision(), 100 * kTolerance); } @@ -415,7 +414,7 @@ TEST(Manifold, GetCurvature) { EXPECT_NEAR(curvature.minGaussianCurvature, 1, precision); EXPECT_NEAR(curvature.maxGaussianCurvature, 1, precision); - sphere.Scale(glm::vec3(2.0f)); + sphere = sphere.Scale(glm::vec3(2.0f)); curvature = sphere.GetCurvature(); EXPECT_NEAR(curvature.minMeanCurvature, 1, precision); EXPECT_NEAR(curvature.maxMeanCurvature, 1, precision); @@ -431,7 +430,7 @@ TEST(Manifold, GetCurvature) { TEST(Manifold, Transform) { Manifold cube = Manifold::Cube({1, 2, 3}); Manifold cube2 = cube; - cube.Rotate(30, 40, 50).Scale({6, 5, 4}).Translate({1, 2, 3}); + cube = cube.Rotate(30, 40, 50).Scale({6, 5, 4}).Translate({1, 2, 3}); glm::mat3 rX(1.0f, 0.0f, 0.0f, // 0.0f, cosd(30), sind(30), // @@ -448,7 +447,7 @@ TEST(Manifold, Transform) { s[2][2] = 4; glm::mat4x3 transform = glm::mat4x3(s * rZ * rY * rX); transform[3] = glm::vec3(1, 2, 3); - cube2.Transform(transform); + cube2 = cube2.Transform(transform); Identical(cube.GetMesh(), cube2.GetMesh()); } @@ -491,8 +490,7 @@ TEST(Boolean, Tetra) { EXPECT_TRUE(tetra.IsManifold()); Manifold tetra2 = tetra; - tetra2.Translate(glm::vec3(0.5f)); - tetra2.SetAsOriginal(); + tetra2 = tetra2.Translate(glm::vec3(0.5f)).SetAsOriginal().second; Manifold result = tetra2 - tetra; ExpectMeshes(result, {{8, 12}}); @@ -533,11 +531,11 @@ TEST(Boolean, Perturb) { TEST(Boolean, Coplanar) { Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f); - Manifold cylinder2 = cylinder; - cylinder2.SetAsOriginal(); - Manifold out = cylinder - cylinder2.Scale({0.5f, 0.5f, 1.0f}) - .Rotate(0, 0, 15) - .Translate({0.25f, 0.25f, 0.0f}); + Manifold cylinder2 = cylinder.SetAsOriginal().second; + cylinder2 = cylinder2.Scale({0.5f, 0.5f, 1.0f}) + .Rotate(0, 0, 15) + .Translate({0.25f, 0.25f, 0.0f}); + Manifold out = cylinder - cylinder2; ExpectMeshes(out, {{32, 64}}); EXPECT_EQ(out.NumDegenerateTris(), 0); EXPECT_EQ(out.Genus(), 1); @@ -549,9 +547,8 @@ TEST(Boolean, Coplanar) { TEST(Boolean, MultiCoplanar) { Manifold cube = Manifold::Cube(); - Manifold cube2 = cube; - Manifold first = cube - cube2.Translate({0.3f, 0.3f, 0.0f}); - cube.Translate({-0.3f, -0.3f, 0.0f}); + Manifold first = cube - cube.Translate({0.3f, 0.3f, 0.0f}); + cube = cube.Translate({-0.3f, -0.3f, 0.0f}); Manifold out = first - cube; CheckStrictly(out); EXPECT_EQ(out.Genus(), -1); @@ -562,8 +559,7 @@ TEST(Boolean, MultiCoplanar) { TEST(Boolean, FaceUnion) { Manifold cubes = Manifold::Cube(); - Manifold cube2 = cubes; - cubes += cube2.Translate({1, 0, 0}); + cubes += cubes.Translate({1, 0, 0}); EXPECT_EQ(cubes.Genus(), 0); ExpectMeshes(cubes, {{12, 20}}); auto prop = cubes.GetProperties(); @@ -575,23 +571,21 @@ TEST(Boolean, FaceUnion) { TEST(Boolean, EdgeUnion) { Manifold cubes = Manifold::Cube(); - Manifold cube2 = cubes; - cubes += cube2.Translate({1, 1, 0}); + cubes += cubes.Translate({1, 1, 0}); ExpectMeshes(cubes, {{8, 12}, {8, 12}}); } TEST(Boolean, EdgeUnion2) { Manifold tets = Manifold::Tetrahedron(); Manifold cube2 = tets; - tets.Translate({0, 0, -1}); + tets = tets.Translate({0, 0, -1}); tets += cube2.Translate({0, 0, 1}).Rotate(0, 0, 90); ExpectMeshes(tets, {{4, 4}, {4, 4}}); } TEST(Boolean, CornerUnion) { Manifold cubes = Manifold::Cube(); - Manifold cube2 = cubes; - cubes += cube2.Translate({1, 1, 1}); + cubes += cubes.Translate({1, 1, 1}); ExpectMeshes(cubes, {{8, 12}, {8, 12}}); } @@ -601,8 +595,7 @@ TEST(Boolean, CornerUnion) { */ TEST(Boolean, Split) { Manifold cube = Manifold::Cube(glm::vec3(2.0f), true); - Manifold oct = Manifold::Sphere(1, 4); - oct.Translate(glm::vec3(0.0f, 0.0f, 1.0f)); + Manifold oct = Manifold::Sphere(1, 4).Translate(glm::vec3(0.0f, 0.0f, 1.0f)); std::pair splits = cube.Split(oct); CheckStrictly(splits.first); CheckStrictly(splits.second); @@ -613,8 +606,8 @@ TEST(Boolean, Split) { TEST(Boolean, SplitByPlane) { Manifold cube = Manifold::Cube(glm::vec3(2.0f), true); - cube.Translate({0.0f, 1.0f, 0.0f}); - cube.Rotate(90.0f, 0.0f, 0.0f); + cube = cube.Translate({0.0f, 1.0f, 0.0f}); + cube = cube.Rotate(90.0f, 0.0f, 0.0f); std::pair splits = cube.SplitByPlane({0.0f, 0.0f, 1.0f}, 1.0f); CheckStrictly(splits.first); @@ -631,9 +624,9 @@ TEST(Boolean, SplitByPlane) { TEST(Boolean, SplitByPlane60) { Manifold cube = Manifold::Cube(glm::vec3(2.0f), true); - cube.Translate({0.0f, 1.0f, 0.0f}); - cube.Rotate(0.0f, 0.0f, -60.0f); - cube.Translate({2.0f, 0.0f, 0.0f}); + cube = cube.Translate({0.0f, 1.0f, 0.0f}); + cube = cube.Rotate(0.0f, 0.0f, -60.0f); + cube = cube.Translate({2.0f, 0.0f, 0.0f}); float phi = 30.0f; std::pair splits = cube.SplitByPlane({sind(phi), -cosd(phi), 0.0f}, 1.0f); @@ -685,8 +678,7 @@ TEST(Boolean, Winding) { TEST(Boolean, NonIntersecting) { Manifold cube1 = Manifold::Cube(); float vol1 = cube1.GetProperties().volume; - Manifold cube2 = cube1; - cube2.Scale(glm::vec3(2)).Translate({3, 0, 0}); + Manifold cube2 = cube1.Scale(glm::vec3(2)).Translate({3, 0, 0}); float vol2 = cube2.GetProperties().volume; EXPECT_EQ((cube1 + cube2).GetProperties().volume, vol1 + vol2); @@ -700,12 +692,12 @@ TEST(Boolean, Precision) { Manifold cube3 = cube; float distance = 100; float scale = distance * kTolerance; - cube2.Scale(glm::vec3(scale)).Translate({distance, 0, 0}); + cube2 = cube2.Scale(glm::vec3(scale)).Translate({distance, 0, 0}); cube += cube2; ExpectMeshes(cube, {{8, 12}}); - cube3.Scale(glm::vec3(2 * scale)).Translate({distance, 0, 0}); + cube3 = cube3.Scale(glm::vec3(2 * scale)).Translate({distance, 0, 0}); cube += cube3; ExpectMeshes(cube, {{8, 12}, {8, 12}}); } @@ -716,10 +708,10 @@ TEST(Boolean, Precision2) { Manifold cube2 = cube; float distance = scale * (1 - kTolerance / 2); - cube2.Translate(glm::vec3(-distance)); + cube2 = cube2.Translate(glm::vec3(-distance)); EXPECT_TRUE((cube ^ cube2).IsEmpty()); - cube2.Translate(glm::vec3(scale * kTolerance)); + cube2 = cube2.Translate(glm::vec3(scale * kTolerance)); EXPECT_FALSE((cube ^ cube2).IsEmpty()); } @@ -730,8 +722,8 @@ TEST(Boolean, Precision2) { TEST(Boolean, Sphere) { Manifold sphere = Manifold::Sphere(1.0f, 12); Manifold sphere2 = sphere; - sphere2.Translate(glm::vec3(0.5)); - sphere2.SetAsOriginal(); + sphere2 = sphere2.Translate(glm::vec3(0.5)); + sphere2 = sphere2.SetAsOriginal().second; Manifold result = sphere - sphere2; ExpectMeshes(result, {{74, 144}}); @@ -847,7 +839,7 @@ TEST(Boolean, DISABLED_Cylinders) { mat[i][j] = array[j * 4 + i]; } } - m1 += Manifold(rod).Transform(mat); + m1 += rod.Transform(mat); } Manifold m2; @@ -858,7 +850,7 @@ TEST(Boolean, DISABLED_Cylinders) { mat[i][j] = array[j * 4 + i]; } } - m2 += Manifold(rod).Transform(mat); + m2 += rod.Transform(mat); } m1 += m2; diff --git a/test/samples_test.cpp b/test/samples_test.cpp index 4004a8225..0a7374444 100644 --- a/test/samples_test.cpp +++ b/test/samples_test.cpp @@ -100,7 +100,7 @@ TEST(Samples, Scallop) { ExportMesh("scallopFacets.glb", in, options); } - scallop.Refine(50); + scallop = scallop.Refine(50); CheckManifold(scallop); auto prop = scallop.GetProperties(); EXPECT_NEAR(prop.volume, 41.3, 0.1); @@ -126,10 +126,9 @@ TEST(Samples, TetPuzzle) { Manifold puzzle = TetPuzzle(50, 0.2, 50); CheckManifold(puzzle); EXPECT_LE(puzzle.NumDegenerateTris(), 2); - Manifold puzzle2 = puzzle; - puzzle2.Rotate(0, 0, 180); + Manifold puzzle2 = puzzle.Rotate(0, 0, 180); EXPECT_TRUE((puzzle ^ puzzle2).IsEmpty()); - puzzle.Transform(RotateUp({1, -1, -1})); + puzzle = puzzle.Transform(RotateUp({1, -1, -1})); if (options.exportModels) ExportMesh("tetPuzzle.glb", puzzle.GetMesh(), {}); } From 4308da0dd721eccc215efb303eed27957c739f6a Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 21:49:16 +0800 Subject: [PATCH 05/16] disabled CI for draft PRs --- .github/workflows/manifold.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/manifold.yml b/.github/workflows/manifold.yml index 90fd1bf0a..358a1c93e 100644 --- a/.github/workflows/manifold.yml +++ b/.github/workflows/manifold.yml @@ -17,6 +17,7 @@ jobs: cuda_support: [ON, OFF] parallel_backend: [NONE, OMP, TBB] runs-on: ubuntu-20.04 + if: github.event.pull_request.draft == false container: image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04 steps: @@ -47,6 +48,7 @@ jobs: build_wasm: runs-on: ubuntu-20.04 + if: github.event.pull_request.draft == false steps: - name: Install dependencies run: | @@ -83,6 +85,7 @@ jobs: cuda_support: [OFF] max-parallel: 1 runs-on: windows-2019 + if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 with: @@ -126,6 +129,7 @@ jobs: matrix: variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js] runs-on: ubuntu-latest + if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v2.4.0 - uses: cachix/install-nix-action@v15 From caffb8936842dc4f6ba13d678f6a9aee43814498 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 21:50:25 +0800 Subject: [PATCH 06/16] removed ApplyTransform --- manifold/src/impl.cpp | 43 +++++++------------------------------------ manifold/src/impl.h | 3 +-- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/manifold/src/impl.cpp b/manifold/src/impl.cpp index f1d5fab3b..48a623e00 100644 --- a/manifold/src/impl.cpp +++ b/manifold/src/impl.cpp @@ -359,7 +359,6 @@ Manifold::Impl::Impl(Shape shape) { InitializeNewReference(); } - void Manifold::Impl::ReinitializeReference(int meshID) { // instead of storing the meshID, we store 0 and set the mapping to // 0 -> meshID, because the meshID after boolean operation also starts from 0. @@ -531,36 +530,7 @@ void Manifold::Impl::Update() { collider_.UpdateBoxes(faceBox); } -/** - * Bake the manifold's transform into its vertices. - */ -void Manifold::Impl::ApplyTransform(const glm::mat4x3 &transform_) { - if (transform_ == glm::mat4x3(1.0f)) return; - auto policy = autoPolicy(NumVert()); - transform(policy, vertPos_.begin(), vertPos_.end(), vertPos_.begin(), - Transform4x3({transform_})); - - glm::mat3 normalTransform = - glm::inverse(glm::transpose(glm::mat3(transform_))); - transform(policy, faceNormal_.begin(), faceNormal_.end(), faceNormal_.begin(), - TransformNormals({normalTransform})); - transform(policy, vertNormal_.begin(), vertNormal_.end(), vertNormal_.begin(), - TransformNormals({normalTransform})); - // This optimization does a cheap collider update if the transform is - // axis-aligned. - if (!collider_.Transform(transform_)) Update(); - - const float oldScale = bBox_.Scale(); - CalculateBBox(); - - const float newScale = bBox_.Scale(); - precision_ *= glm::max(1.0f, newScale / oldScale); - - // Maximum of inherited precision loss and translational precision loss. - SetPrecision(precision_); -} - -Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3 &transform_) const { +Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const { if (transform_ == glm::mat4x3(1.0f)) return *this; auto policy = autoPolicy(NumVert()); Impl result; @@ -574,14 +544,15 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3 &transform_) const { result.vertPos_.resize(NumVert()); result.faceNormal_.resize(faceNormal_.size()); result.vertNormal_.resize(vertNormal_.size()); - transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(), Transform4x3({transform_})); + transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(), + Transform4x3({transform_})); glm::mat3 normalTransform = glm::inverse(glm::transpose(glm::mat3(transform_))); - transform(policy, faceNormal_.begin(), faceNormal_.end(), result.faceNormal_.begin(), - TransformNormals({normalTransform})); - transform(policy, vertNormal_.begin(), vertNormal_.end(), result.vertNormal_.begin(), - TransformNormals({normalTransform})); + transform(policy, faceNormal_.begin(), faceNormal_.end(), + result.faceNormal_.begin(), TransformNormals({normalTransform})); + transform(policy, vertNormal_.begin(), vertNormal_.end(), + result.vertNormal_.begin(), TransformNormals({normalTransform})); // This optimization does a cheap collider update if the transform is // axis-aligned. if (!result.collider_.Transform(transform_)) result.Update(); diff --git a/manifold/src/impl.h b/manifold/src/impl.h index 2782e24fb..a9e7ad02e 100644 --- a/manifold/src/impl.h +++ b/manifold/src/impl.h @@ -73,8 +73,7 @@ struct Manifold::Impl { int startTri = 0, int n = -1, int startID = 0); void Update(); - void ApplyTransform(const glm::mat4x3 &transform); - Impl Transform(const glm::mat4x3 &transform) const; + Impl Transform(const glm::mat4x3& transform) const; SparseIndices EdgeCollisions(const Impl& B) const; SparseIndices VertexCollisionsZ(const VecDH& vertsIn) const; From 884be1dc981f5dc781925cc7455a81798f9e4872 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 21:53:28 +0800 Subject: [PATCH 07/16] fixed precision bug for compose/decompose --- manifold/src/constructors.cpp | 2 ++ manifold/src/csg_tree.cpp | 12 ++++++++++++ manifold/src/impl.cpp | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/manifold/src/constructors.cpp b/manifold/src/constructors.cpp index 28859ac76..c0fca7123 100644 --- a/manifold/src/constructors.cpp +++ b/manifold/src/constructors.cpp @@ -416,6 +416,8 @@ std::vector Manifold::Decompose() const { std::vector meshes; for (int i = 0; i < numLabel; ++i) { auto impl = std::make_shared(); + // inherit original object's precision + impl->precision_ = pImpl_->precision_; impl->vertPos_.resize(NumVert()); VecDH vertNew2Old(NumVert()); auto policy = autoPolicy(NumVert()); diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index da60f82b3..c7c027e3c 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -115,11 +115,20 @@ CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::LEAF; } */ Manifold::Impl CsgLeafNode::Compose( const std::vector> &nodes) { + float precision = -1; int numVert = 0; int numEdge = 0; int numTri = 0; int numBary = 0; for (auto &node : nodes) { + float nodeOldScale = node->pImpl_->bBox_.Scale(); + float nodeNewScale = node->GetBoundingBox().Scale(); + float nodePrecision = node->pImpl_->precision_; + nodePrecision *= glm::max(1.0f, nodeNewScale / nodeOldScale); + nodePrecision = glm::max(nodePrecision, kTolerance * nodeNewScale); + if (!glm::isfinite(nodePrecision)) nodePrecision = -1; + precision = glm::max(precision, nodePrecision); + numVert += node->pImpl_->NumVert(); numEdge += node->pImpl_->NumEdge(); numTri += node->pImpl_->NumTri(); @@ -127,6 +136,7 @@ Manifold::Impl CsgLeafNode::Compose( } Manifold::Impl combined; + combined.precision_ = precision; combined.vertPos_.resize(numVert); combined.halfedge_.resize(2 * numEdge); combined.faceNormal_.resize(numTri); @@ -194,6 +204,8 @@ Manifold::Impl CsgLeafNode::Compose( nextTri += node->pImpl_->NumTri(); nextBary += node->pImpl_->meshRelation_.barycentric.size(); } + // required to remove parts that are smaller than the precision + combined.SimplifyTopology(); combined.Finish(); return combined; } diff --git a/manifold/src/impl.cpp b/manifold/src/impl.cpp index 48a623e00..c2af29f93 100644 --- a/manifold/src/impl.cpp +++ b/manifold/src/impl.cpp @@ -564,7 +564,7 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const { result.precision_ *= glm::max(1.0f, newScale / oldScale); // Maximum of inherited precision loss and translational precision loss. - result.SetPrecision(precision_); + result.SetPrecision(result.precision_); return result; } From 54693e925be0c8907a0533c6ae857a811c6d4ec1 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 21:53:52 +0800 Subject: [PATCH 08/16] fixed perf_test error --- tools/perf_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/perf_test.cpp b/tools/perf_test.cpp index 2ad385a6b..c2ac36538 100644 --- a/tools/perf_test.cpp +++ b/tools/perf_test.cpp @@ -22,10 +22,10 @@ using namespace manifold; int main(int argc, char **argv) { for (int i = 0; i < 8; ++i) { Manifold sphere = Manifold::Sphere(1, (8 << i) * 4); - Manifold sphere2 = sphere; - sphere2.Translate(glm::vec3(0.5)); + Manifold sphere2 = sphere.Translate(glm::vec3(0.5)); auto start = std::chrono::high_resolution_clock::now(); Manifold diff = sphere - sphere2; + diff.NumTri(); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; std::cout << "nTri = " << sphere.NumTri() << ", time = " << elapsed.count() From 50495ae91dc51d03136d7a780ac1f0babeaee2f3 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 22:15:45 +0800 Subject: [PATCH 09/16] flatten the tree on the fly if possible --- manifold/src/csg_tree.cpp | 18 ++++++++++++++---- manifold/src/csg_tree.h | 4 +++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index c7c027e3c..8e32912e7 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -216,12 +216,16 @@ CsgOpNode::CsgOpNode(const std::vector> &children, Manifold::OpType op) : children_(children) { SetOp(op); + // opportunisticly flatten the tree without costly evaluation + GetChildren(false); } CsgOpNode::CsgOpNode(std::vector> &&children, Manifold::OpType op) : children_(children) { SetOp(op); + // opportunisticly flatten the tree without costly evaluation + GetChildren(false); } std::shared_ptr CsgOpNode::Transform(const glm::mat4x3 &m) const { @@ -230,6 +234,7 @@ std::shared_ptr CsgOpNode::Transform(const glm::mat4x3 &m) const { node->op_ = op_; node->transform_ = m * glm::mat4(transform_); node->simplified = simplified; + node->flattened = flattened; return node; } @@ -382,25 +387,30 @@ void CsgOpNode::BatchUnion() const { /** * Flatten the children to a list of leaf nodes and return them. + * If finalize is true, the list will be guaranteed to be a list of leaf nodes + * (i.e. no ops). Otherwise, the list may contain ops. * Note that this function will not apply the transform to children, as they may * be shared with other nodes. */ -std::vector> &CsgOpNode::GetChildren() const { - if (children_.empty() || simplified) return children_; +std::vector> &CsgOpNode::GetChildren( + bool finalize) const { + if (children_.empty() || (simplified && !finalize) || flattened) + return children_; simplified = true; + flattened = finalize; std::vector> newChildren; CsgNodeType op = op_; for (auto &child : children_) { if (child->GetNodeType() == op) { auto grandchildren = - std::dynamic_pointer_cast(child)->GetChildren(); + std::dynamic_pointer_cast(child)->GetChildren(finalize); int start = children_.size(); for (auto &grandchild : grandchildren) { newChildren.push_back(grandchild->Transform(child->GetTransform())); } } else { - if (child->GetNodeType() == CsgNodeType::LEAF) { + if (!finalize || child->GetNodeType() == CsgNodeType::LEAF) { newChildren.push_back(child); } else { newChildren.push_back(child->ToLeafNode()); diff --git a/manifold/src/csg_tree.h b/manifold/src/csg_tree.h index c1ac601bd..d4e11b237 100644 --- a/manifold/src/csg_tree.h +++ b/manifold/src/csg_tree.h @@ -72,6 +72,7 @@ class CsgOpNode final : public CsgNode { mutable std::vector> children_; mutable std::shared_ptr cache_ = nullptr; mutable bool simplified = false; + mutable bool flattened = false; void SetOp(Manifold::OpType); @@ -81,7 +82,8 @@ class CsgOpNode final : public CsgNode { void BatchUnion() const; - std::vector> &GetChildren() const; + std::vector> &GetChildren( + bool finalize = true) const; }; } // namespace manifold From 7fa9a0d32bee8bca549d5a4e45128be7fe50ef3a Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 22:25:34 +0800 Subject: [PATCH 10/16] renamed SetAsOriginal to AsOriginal --- manifold/include/manifold.h | 2 +- manifold/src/manifold.cpp | 2 +- samples/src/bracelet.cpp | 2 +- samples/src/menger_sponge.cpp | 2 +- test/mesh_test.cpp | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/manifold/include/manifold.h b/manifold/include/manifold.h index 2ecc219ae..1dec81e90 100644 --- a/manifold/include/manifold.h +++ b/manifold/include/manifold.h @@ -107,7 +107,7 @@ class Manifold { ///@{ MeshRelation GetMeshRelation() const; std::vector GetMeshIDs() const; - std::pair SetAsOriginal() const; + std::pair AsOriginal() const; ///@} /** @name Modification diff --git a/manifold/src/manifold.cpp b/manifold/src/manifold.cpp index b2ffdb3c5..9489cd5d9 100644 --- a/manifold/src/manifold.cpp +++ b/manifold/src/manifold.cpp @@ -320,7 +320,7 @@ std::vector Manifold::GetMeshIDs() const { * * @returns New MeshID */ -std::pair Manifold::SetAsOriginal() const { +std::pair Manifold::AsOriginal() const { auto newImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); int meshID = newImpl->InitializeNewReference(); return std::make_pair(meshID, diff --git a/samples/src/bracelet.cpp b/samples/src/bracelet.cpp index 144524c1a..e05e4ac22 100644 --- a/samples/src/bracelet.cpp +++ b/samples/src/bracelet.cpp @@ -52,7 +52,7 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius, } base = Manifold::Extrude(stretch, width) ^ base; - base = base.SetAsOriginal().second; + base = base.AsOriginal().second; return base; } diff --git a/samples/src/menger_sponge.cpp b/samples/src/menger_sponge.cpp index af478f213..69c6bf1ed 100644 --- a/samples/src/menger_sponge.cpp +++ b/samples/src/menger_sponge.cpp @@ -54,7 +54,7 @@ Manifold MengerSponge(int n) { result -= hole; hole = hole.Rotate(0, 0, 90); result -= hole; - result = result.SetAsOriginal().second; + result = result.AsOriginal().second; return result; } } // namespace manifold diff --git a/test/mesh_test.cpp b/test/mesh_test.cpp index 5651df519..46a674515 100644 --- a/test/mesh_test.cpp +++ b/test/mesh_test.cpp @@ -490,7 +490,7 @@ TEST(Boolean, Tetra) { EXPECT_TRUE(tetra.IsManifold()); Manifold tetra2 = tetra; - tetra2 = tetra2.Translate(glm::vec3(0.5f)).SetAsOriginal().second; + tetra2 = tetra2.Translate(glm::vec3(0.5f)).AsOriginal().second; Manifold result = tetra2 - tetra; ExpectMeshes(result, {{8, 12}}); @@ -531,7 +531,7 @@ TEST(Boolean, Perturb) { TEST(Boolean, Coplanar) { Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f); - Manifold cylinder2 = cylinder.SetAsOriginal().second; + Manifold cylinder2 = cylinder.AsOriginal().second; cylinder2 = cylinder2.Scale({0.5f, 0.5f, 1.0f}) .Rotate(0, 0, 15) .Translate({0.25f, 0.25f, 0.0f}); @@ -723,7 +723,7 @@ TEST(Boolean, Sphere) { Manifold sphere = Manifold::Sphere(1.0f, 12); Manifold sphere2 = sphere; sphere2 = sphere2.Translate(glm::vec3(0.5)); - sphere2 = sphere2.SetAsOriginal().second; + sphere2 = sphere2.AsOriginal().second; Manifold result = sphere - sphere2; ExpectMeshes(result, {{74, 144}}); From bc4c4d76997f6ca578575614da72b8ae5cc12d31 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 5 Jun 2022 22:30:23 +0800 Subject: [PATCH 11/16] faster clang-format check so it becomes not noticeable when running in precommit hook --- clang-format.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clang-format.sh b/clang-format.sh index 9b512a6a4..b2650d256 100755 --- a/clang-format.sh +++ b/clang-format.sh @@ -1,4 +1,9 @@ #!/usr/bin/env -S bash -e for f in {collider,manifold,meshIO,polygon,samples,utilities}/**/*.{h,cpp} {test,tools}/*.cpp; do - clang-format --dry-run --Werror $f + clang-format --dry-run --Werror $f & + pids[${i}]=$! +done + +for pid in ${pids[*]}; do + wait $pid done From 241ba65749f356a30980ee121df93987058c0b37 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Mon, 6 Jun 2022 23:37:46 +0800 Subject: [PATCH 12/16] added license to csg_tree.cpp --- manifold/src/csg_tree.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index 8e32912e7..f767d30b5 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -1,3 +1,17 @@ +// Copyright 2022 Emmett Lalish, Chun Kit LAM +// +// 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 "csg_tree.h" #include "boolean3.h" From 9d01694e6e462d74d39df28b30f938c1b94684b1 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 11 Jun 2022 22:02:05 +0800 Subject: [PATCH 13/16] csg_tree: disabled reordering --- manifold/src/csg_tree.cpp | 59 +++++++++++++-------------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index f767d30b5..e6a7c5c69 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -257,39 +257,29 @@ std::shared_ptr CsgOpNode::ToLeafNode() const { if (children_.empty()) return nullptr; // turn the children into leaf nodes GetChildren(); + Manifold::OpType op; switch (op_) { case CsgNodeType::UNION: - BatchUnion(); + op = Manifold::OpType::ADD; break; - case CsgNodeType::INTERSECTION: { - std::vector> impls; - for (auto &child : children_) { - impls.push_back( - std::dynamic_pointer_cast(child)->GetImpl()); - } - BatchBoolean(Manifold::OpType::INTERSECT, impls); - children_.clear(); - children_.push_back(std::make_shared(impls.front())); + case CsgNodeType::DIFFERENCE: + op = Manifold::OpType::SUBTRACT; break; - }; - case CsgNodeType::DIFFERENCE: { - // take the lhs out and treat the remaining nodes as the rhs, perform - // union optimization for them - auto lhs = std::dynamic_pointer_cast(children_.front()); - children_.erase(children_.begin()); - BatchUnion(); - auto rhs = std::dynamic_pointer_cast(children_.front()); - children_.clear(); - Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(), - Manifold::OpType::SUBTRACT); - children_.push_back( - std::make_shared(std::make_shared( - boolean.Result(Manifold::OpType::SUBTRACT)))); - }; - case CsgNodeType::LEAF: - // unreachable + case CsgNodeType::INTERSECTION: + op = Manifold::OpType::INTERSECT; break; + default: + throw std::runtime_error("unreachable CSG operation"); + break; + } + auto a = std::static_pointer_cast(children_[0])->GetImpl(); + for (int i = 1; i < children_.size(); i++) { + auto b = std::static_pointer_cast(children_[i])->GetImpl(); + Boolean3 boolean(*a, *b, op); + a = std::make_shared(boolean.Result(op)); } + children_.clear(); + children_.push_back(std::make_shared(a)); // children_ must contain only one CsgLeafNode now, and its Transform will // give CsgLeafNode as well cache_ = std::dynamic_pointer_cast( @@ -416,19 +406,10 @@ std::vector> &CsgOpNode::GetChildren( CsgNodeType op = op_; for (auto &child : children_) { - if (child->GetNodeType() == op) { - auto grandchildren = - std::dynamic_pointer_cast(child)->GetChildren(finalize); - int start = children_.size(); - for (auto &grandchild : grandchildren) { - newChildren.push_back(grandchild->Transform(child->GetTransform())); - } + if (!finalize || child->GetNodeType() == CsgNodeType::LEAF) { + newChildren.push_back(child); } else { - if (!finalize || child->GetNodeType() == CsgNodeType::LEAF) { - newChildren.push_back(child); - } else { - newChildren.push_back(child->ToLeafNode()); - } + newChildren.push_back(child->ToLeafNode()); } // special handling for difference: we treat it as first - (second + third + // ...) so op = UNION after the first node From 4e8139c80b4497e88db7a34a1c316038bc057f08 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 11 Jun 2022 22:23:32 +0800 Subject: [PATCH 14/16] fix cmake error --- manifold/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifold/CMakeLists.txt b/manifold/CMakeLists.txt index 71afdd7d0..e41527499 100644 --- a/manifold/CMakeLists.txt +++ b/manifold/CMakeLists.txt @@ -10,7 +10,7 @@ if(MANIFOLD_USE_CUDA) src/manifold.cpp src/constructors.cpp src/impl.cpp src/properties.cpp src/sort.cpp src/edge_op.cpp src/face_op.cpp src/smoothing.cpp src/boolean3.cpp src/boolean_result.cpp - src/manifold_set.cpp + src/manifold_set.cpp src/csg_tree.cpp PROPERTIES LANGUAGE CUDA ) From 8d70105ccabb42e473e9215ffab5b84eb1b9eb9b Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 11 Jun 2022 23:45:10 +0800 Subject: [PATCH 15/16] fixed compilation errors --- manifold/src/constructors.cpp | 24 ------------------------ manifold/src/csg_tree.cpp | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/manifold/src/constructors.cpp b/manifold/src/constructors.cpp index c0fca7123..7f4b07353 100644 --- a/manifold/src/constructors.cpp +++ b/manifold/src/constructors.cpp @@ -33,30 +33,6 @@ struct ToSphere { } }; -struct UpdateTriBary { - const int nextBary; - - __host__ __device__ BaryRef operator()(BaryRef ref) { - for (int i : {0, 1, 2}) - if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary; - return ref; - } -}; - -struct UpdateHalfedge { - const int nextVert; - const int nextEdge; - const int nextFace; - - __host__ __device__ Halfedge operator()(Halfedge edge) { - edge.startVert += nextVert; - edge.endVert += nextVert; - edge.pairedHalfedge += nextEdge; - edge.face += nextFace; - return edge; - } -}; - struct Equals { int val; __host__ __device__ bool operator()(int x) { return x == val; } diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index e6a7c5c69..f6d884361 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -14,6 +14,8 @@ #include "csg_tree.h" +#include + #include "boolean3.h" #include "impl.h" #include "par.h" @@ -61,6 +63,14 @@ struct UpdateHalfedge { return edge; } }; + +struct CheckOverlap { + const Box *boxes; + const size_t i; + __host__ __device__ bool operator()(int j) { + return boxes[i].DoesOverlap(boxes[j]); + } +}; } // namespace namespace manifold { @@ -352,11 +362,10 @@ void CsgOpNode::BatchUnion() const { auto lambda = [boxesD, i](const VecDH &set) { return find_if( autoPolicy(set.size()), set.begin(), set.end(), - [&boxesD, i](size_t j) { - return boxesD[i].DoesOverlap(boxesD[j]); - }) == set.end(); + CheckOverlap({boxesD, i})) == set.end(); }; - auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda); + decltype(disjointSets.end()) it = + std::find_if(disjointSets.begin(), disjointSets.end(), lambda); if (it == disjointSets.end()) { disjointSets.push_back(std::vector{i}); } else { From 7374d6b919929adde3c48cfaf6d3eac90acfe61c Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 12 Jun 2022 21:07:45 +0800 Subject: [PATCH 16/16] cleanup --- manifold/include/manifold.h | 2 +- manifold/src/csg_tree.cpp | 10 +++++----- manifold/src/csg_tree.h | 4 ++-- manifold/src/manifold.cpp | 9 ++++----- samples/src/bracelet.cpp | 2 +- samples/src/menger_sponge.cpp | 2 +- test/mesh_test.cpp | 8 ++++---- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/manifold/include/manifold.h b/manifold/include/manifold.h index 1dec81e90..d73d19503 100644 --- a/manifold/include/manifold.h +++ b/manifold/include/manifold.h @@ -107,7 +107,7 @@ class Manifold { ///@{ MeshRelation GetMeshRelation() const; std::vector GetMeshIDs() const; - std::pair AsOriginal() const; + Manifold AsOriginal() const; ///@} /** @name Modification diff --git a/manifold/src/csg_tree.cpp b/manifold/src/csg_tree.cpp index f6d884361..74a5a4adf 100644 --- a/manifold/src/csg_tree.cpp +++ b/manifold/src/csg_tree.cpp @@ -257,8 +257,8 @@ std::shared_ptr CsgOpNode::Transform(const glm::mat4x3 &m) const { node->children_ = children_; node->op_ = op_; node->transform_ = m * glm::mat4(transform_); - node->simplified = simplified; - node->flattened = flattened; + node->simplified_ = simplified_; + node->flattened_ = flattened_; return node; } @@ -407,10 +407,10 @@ void CsgOpNode::BatchUnion() const { */ std::vector> &CsgOpNode::GetChildren( bool finalize) const { - if (children_.empty() || (simplified && !finalize) || flattened) + if (children_.empty() || (simplified_ && !finalize) || flattened_) return children_; - simplified = true; - flattened = finalize; + simplified_ = true; + flattened_ = finalize; std::vector> newChildren; CsgNodeType op = op_; diff --git a/manifold/src/csg_tree.h b/manifold/src/csg_tree.h index d4e11b237..04dadee6c 100644 --- a/manifold/src/csg_tree.h +++ b/manifold/src/csg_tree.h @@ -71,8 +71,8 @@ class CsgOpNode final : public CsgNode { // the following fields are for lazy evaluation, so they are mutable mutable std::vector> children_; mutable std::shared_ptr cache_ = nullptr; - mutable bool simplified = false; - mutable bool flattened = false; + mutable bool simplified_ = false; + mutable bool flattened_ = false; void SetOp(Manifold::OpType); diff --git a/manifold/src/manifold.cpp b/manifold/src/manifold.cpp index 9489cd5d9..99ee1b3cf 100644 --- a/manifold/src/manifold.cpp +++ b/manifold/src/manifold.cpp @@ -318,13 +318,12 @@ std::vector Manifold::GetMeshIDs() const { * should instead call GetMesh(), calculate your properties and use these to * construct a new manifold. * - * @returns New MeshID + * @returns New Mesh */ -std::pair Manifold::AsOriginal() const { +Manifold Manifold::AsOriginal() const { auto newImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); - int meshID = newImpl->InitializeNewReference(); - return std::make_pair(meshID, - Manifold(std::make_shared(newImpl))); + newImpl->InitializeNewReference(); + return Manifold(std::make_shared(newImpl)); } /** diff --git a/samples/src/bracelet.cpp b/samples/src/bracelet.cpp index e05e4ac22..2ad28a15f 100644 --- a/samples/src/bracelet.cpp +++ b/samples/src/bracelet.cpp @@ -52,7 +52,7 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius, } base = Manifold::Extrude(stretch, width) ^ base; - base = base.AsOriginal().second; + base = base.AsOriginal(); return base; } diff --git a/samples/src/menger_sponge.cpp b/samples/src/menger_sponge.cpp index 69c6bf1ed..a9168cf27 100644 --- a/samples/src/menger_sponge.cpp +++ b/samples/src/menger_sponge.cpp @@ -54,7 +54,7 @@ Manifold MengerSponge(int n) { result -= hole; hole = hole.Rotate(0, 0, 90); result -= hole; - result = result.AsOriginal().second; + result = result.AsOriginal(); return result; } } // namespace manifold diff --git a/test/mesh_test.cpp b/test/mesh_test.cpp index 58143053f..a6097e682 100644 --- a/test/mesh_test.cpp +++ b/test/mesh_test.cpp @@ -490,7 +490,7 @@ TEST(Boolean, Tetra) { EXPECT_TRUE(tetra.IsManifold()); Manifold tetra2 = tetra; - tetra2 = tetra2.Translate(glm::vec3(0.5f)).AsOriginal().second; + tetra2 = tetra2.Translate(glm::vec3(0.5f)).AsOriginal(); Manifold result = tetra2 - tetra; ExpectMeshes(result, {{8, 12}}); @@ -531,7 +531,7 @@ TEST(Boolean, Perturb) { TEST(Boolean, Coplanar) { Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f); - Manifold cylinder2 = cylinder.AsOriginal().second; + Manifold cylinder2 = cylinder.AsOriginal(); cylinder2 = cylinder2.Scale({0.5f, 0.5f, 1.0f}) .Rotate(0, 0, 15) .Translate({0.25f, 0.25f, 0.0f}); @@ -723,7 +723,7 @@ TEST(Boolean, Sphere) { Manifold sphere = Manifold::Sphere(1.0f, 12); Manifold sphere2 = sphere; sphere2 = sphere2.Translate(glm::vec3(0.5)); - sphere2 = sphere2.AsOriginal().second; + sphere2 = sphere2.AsOriginal(); Manifold result = sphere - sphere2; ExpectMeshes(result, {{74, 144}}); @@ -874,4 +874,4 @@ TEST(Boolean, Cubes) { EXPECT_NEAR(prop.surfaceArea, 9.2, 0.01); if (options.exportModels) ExportMesh("cubes.glb", result.GetMesh(), {}); -} \ No newline at end of file +}