Path: blob/master/thirdparty/manifold/src/csg_tree.cpp
9902 views
// Copyright 2022 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#if MANIFOLD_PAR == 115#include <tbb/tbb.h>16#endif1718#include <algorithm>1920#include "boolean3.h"21#include "csg_tree.h"22#include "impl.h"23#include "mesh_fixes.h"24#include "parallel.h"2526namespace {27using namespace manifold;28struct MeshCompare {29bool operator()(const std::shared_ptr<CsgLeafNode>& a,30const std::shared_ptr<CsgLeafNode>& b) {31return a->GetImpl()->NumVert() < b->GetImpl()->NumVert();32}33};3435} // namespace36namespace manifold {3738std::shared_ptr<CsgNode> CsgNode::Boolean(39const std::shared_ptr<CsgNode>& second, OpType op) {40if (second->GetNodeType() != CsgNodeType::Leaf) {41// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is42// and the operation is commutative, we let it built the tree.43if ((op == OpType::Add || op == OpType::Intersect)) {44return std::static_pointer_cast<CsgOpNode>(second)->Boolean(45shared_from_this(), op);46}47}48std::vector<std::shared_ptr<CsgNode>> children({shared_from_this(), second});49return std::make_shared<CsgOpNode>(children, op);50}5152std::shared_ptr<CsgNode> CsgNode::Translate(const vec3& t) const {53mat3x4 transform = la::identity;54transform[3] += t;55return Transform(transform);56}5758std::shared_ptr<CsgNode> CsgNode::Scale(const vec3& v) const {59mat3x4 transform;60for (int i : {0, 1, 2}) transform[i][i] = v[i];61return Transform(transform);62}6364std::shared_ptr<CsgNode> CsgNode::Rotate(double xDegrees, double yDegrees,65double zDegrees) const {66mat3 rX({1.0, 0.0, 0.0}, //67{0.0, cosd(xDegrees), sind(xDegrees)}, //68{0.0, -sind(xDegrees), cosd(xDegrees)});69mat3 rY({cosd(yDegrees), 0.0, -sind(yDegrees)}, //70{0.0, 1.0, 0.0}, //71{sind(yDegrees), 0.0, cosd(yDegrees)});72mat3 rZ({cosd(zDegrees), sind(zDegrees), 0.0}, //73{-sind(zDegrees), cosd(zDegrees), 0.0}, //74{0.0, 0.0, 1.0});75mat3x4 transform(rZ * rY * rX, vec3());76return Transform(transform);77}7879CsgLeafNode::CsgLeafNode() : pImpl_(std::make_shared<Manifold::Impl>()) {}8081CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_)82: pImpl_(pImpl_) {}8384CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_,85mat3x4 transform_)86: pImpl_(pImpl_), transform_(transform_) {}8788std::shared_ptr<const Manifold::Impl> CsgLeafNode::GetImpl() const {89if (transform_ == mat3x4(la::identity)) return pImpl_;90pImpl_ =91std::make_shared<const Manifold::Impl>(pImpl_->Transform(transform_));92transform_ = la::identity;93return pImpl_;94}9596std::shared_ptr<CsgLeafNode> CsgLeafNode::ToLeafNode() const {97return std::make_shared<CsgLeafNode>(*this);98}99100std::shared_ptr<CsgNode> CsgLeafNode::Transform(const mat3x4& m) const {101return std::make_shared<CsgLeafNode>(pImpl_, m * Mat4(transform_));102}103104CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }105106std::shared_ptr<CsgLeafNode> ImplToLeaf(Manifold::Impl&& impl) {107return std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(impl));108}109110std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl& a,111const Manifold::Impl& b, OpType op) {112#ifdef MANIFOLD_DEBUG113auto dump = [&]() {114dump_lock.lock();115std::cout << "LHS self-intersecting: " << a.IsSelfIntersecting()116<< std::endl;117std::cout << "RHS self-intersecting: " << b.IsSelfIntersecting()118<< std::endl;119#ifdef MANIFOLD_EXPORT120if (ManifoldParams().verbose) {121if (op == OpType::Add)122std::cout << "Add";123else if (op == OpType::Intersect)124std::cout << "Intersect";125else126std::cout << "Subtract";127std::cout << std::endl;128std::cout << a;129std::cout << b;130}131#endif132dump_lock.unlock();133};134try {135Boolean3 boolean(a, b, op);136auto impl = boolean.Result(op);137if (ManifoldParams().selfIntersectionChecks && impl.IsSelfIntersecting()) {138dump_lock.lock();139std::cout << "self intersections detected" << std::endl;140dump_lock.unlock();141throw logicErr("self intersection detected");142}143return ImplToLeaf(std::move(impl));144} catch (logicErr& err) {145dump();146throw err;147} catch (geometryErr& err) {148dump();149throw err;150}151#else152return ImplToLeaf(Boolean3(a, b, op).Result(op));153#endif154}155156/**157* Efficient union of a set of pairwise disjoint meshes.158*/159std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(160const std::vector<std::shared_ptr<CsgLeafNode>>& nodes) {161ZoneScoped;162double epsilon = -1;163double tolerance = -1;164int numVert = 0;165int numEdge = 0;166int numTri = 0;167int numPropVert = 0;168std::vector<int> vertIndices;169std::vector<int> edgeIndices;170std::vector<int> triIndices;171std::vector<int> propVertIndices;172int numPropOut = 0;173for (auto& node : nodes) {174if (node->pImpl_->status_ != Manifold::Error::NoError) {175Manifold::Impl impl;176impl.status_ = node->pImpl_->status_;177return ImplToLeaf(std::move(impl));178}179double nodeOldScale = node->pImpl_->bBox_.Scale();180double nodeNewScale =181node->pImpl_->bBox_.Transform(node->transform_).Scale();182double nodeEpsilon = node->pImpl_->epsilon_;183nodeEpsilon *= std::max(1.0, nodeNewScale / nodeOldScale);184nodeEpsilon = std::max(nodeEpsilon, kPrecision * nodeNewScale);185if (!std::isfinite(nodeEpsilon)) nodeEpsilon = -1;186epsilon = std::max(epsilon, nodeEpsilon);187tolerance = std::max(tolerance, node->pImpl_->tolerance_);188189vertIndices.push_back(numVert);190edgeIndices.push_back(numEdge * 2);191triIndices.push_back(numTri);192propVertIndices.push_back(numPropVert);193numVert += node->pImpl_->NumVert();194numEdge += node->pImpl_->NumEdge();195numTri += node->pImpl_->NumTri();196const int numProp = node->pImpl_->NumProp();197numPropOut = std::max(numPropOut, numProp);198numPropVert +=199numProp == 0 ? 1 : node->pImpl_->properties_.size() / numProp;200}201202Manifold::Impl combined;203combined.epsilon_ = epsilon;204combined.tolerance_ = tolerance;205combined.vertPos_.resize_nofill(numVert);206combined.halfedge_.resize_nofill(2 * numEdge);207combined.faceNormal_.resize_nofill(numTri);208combined.halfedgeTangent_.resize(2 * numEdge);209combined.meshRelation_.triRef.resize_nofill(numTri);210if (numPropOut > 0) {211combined.numProp_ = numPropOut;212combined.properties_.resize(numPropOut * numPropVert, 0);213}214auto policy = autoPolicy(numTri);215216// if we are already parallelizing for each node, do not perform multithreaded217// copying as it will slightly hurt performance218if (nodes.size() > 1 && policy == ExecutionPolicy::Par)219policy = ExecutionPolicy::Seq;220221for_each_n(222nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,223countAt(0), nodes.size(),224[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,225numPropOut, &combined, policy](int i) {226auto& node = nodes[i];227copy(node->pImpl_->halfedgeTangent_.begin(),228node->pImpl_->halfedgeTangent_.end(),229combined.halfedgeTangent_.begin() + edgeIndices[i]);230const int nextVert = vertIndices[i];231const int nextEdge = edgeIndices[i];232const int nextProp = propVertIndices[i];233transform(node->pImpl_->halfedge_.begin(),234node->pImpl_->halfedge_.end(),235combined.halfedge_.begin() + edgeIndices[i],236[nextVert, nextEdge, nextProp](Halfedge edge) {237edge.startVert += nextVert;238edge.endVert += nextVert;239edge.pairedHalfedge += nextEdge;240edge.propVert += nextProp;241return edge;242});243244if (node->pImpl_->NumProp() > 0) {245const int numProp = node->pImpl_->NumProp();246auto& oldProp = node->pImpl_->properties_;247auto& newProp = combined.properties_;248for (int p = 0; p < numProp; ++p) {249auto oldRange =250StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp);251auto newRange = StridedRange(252newProp.begin() + numPropOut * propVertIndices[i] + p,253newProp.end(), numPropOut);254copy(oldRange.begin(), oldRange.end(), newRange.begin());255}256}257258if (node->transform_ == mat3x4(la::identity)) {259copy(node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),260combined.vertPos_.begin() + vertIndices[i]);261copy(node->pImpl_->faceNormal_.begin(),262node->pImpl_->faceNormal_.end(),263combined.faceNormal_.begin() + triIndices[i]);264} else {265// no need to apply the transform to the node, just copy the vertices266// and face normals and apply transform on the fly267const mat3x4 transform = node->transform_;268auto vertPosBegin = TransformIterator(269node->pImpl_->vertPos_.begin(), [&transform](vec3 position) {270return transform * vec4(position, 1.0);271});272mat3 normalTransform =273la::inverse(la::transpose(mat3(node->transform_)));274auto faceNormalBegin =275TransformIterator(node->pImpl_->faceNormal_.begin(),276TransformNormals({normalTransform}));277copy_n(vertPosBegin, node->pImpl_->vertPos_.size(),278combined.vertPos_.begin() + vertIndices[i]);279copy_n(faceNormalBegin, node->pImpl_->faceNormal_.size(),280combined.faceNormal_.begin() + triIndices[i]);281282const bool invert = la::determinant(mat3(node->transform_)) < 0;283for_each_n(policy, countAt(0), node->pImpl_->halfedgeTangent_.size(),284TransformTangents{combined.halfedgeTangent_,285edgeIndices[i], mat3(node->transform_),286invert, node->pImpl_->halfedgeTangent_,287node->pImpl_->halfedge_});288if (invert)289for_each_n(policy, countAt(triIndices[i]), node->pImpl_->NumTri(),290FlipTris({combined.halfedge_}));291}292// Since the nodes may be copies containing the same meshIDs, it is293// important to add an offset so that each node instance gets294// unique meshIDs.295const int offset = i * Manifold::Impl::meshIDCounter_;296transform(node->pImpl_->meshRelation_.triRef.begin(),297node->pImpl_->meshRelation_.triRef.end(),298combined.meshRelation_.triRef.begin() + triIndices[i],299[offset](TriRef ref) {300ref.meshID += offset;301return ref;302});303});304305for (size_t i = 0; i < nodes.size(); i++) {306auto& node = nodes[i];307const int offset = i * Manifold::Impl::meshIDCounter_;308309for (const auto& pair : node->pImpl_->meshRelation_.meshIDtransform) {310combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;311}312}313314// required to remove parts that are smaller than the tolerance315combined.RemoveDegenerates();316combined.Finish();317combined.IncrementMeshIDs();318return ImplToLeaf(std::move(combined));319}320321/**322* Efficient boolean operation on a set of nodes utilizing commutativity of the323* operation. Only supports union and intersection.324*/325std::shared_ptr<CsgLeafNode> BatchBoolean(326OpType operation, std::vector<std::shared_ptr<CsgLeafNode>>& results) {327ZoneScoped;328DEBUG_ASSERT(operation != OpType::Subtract, logicErr,329"BatchBoolean doesn't support Difference.");330// common cases331if (results.size() == 0) return std::make_shared<CsgLeafNode>();332if (results.size() == 1) return results.front();333if (results.size() == 2)334return SimpleBoolean(*results[0]->GetImpl(), *results[1]->GetImpl(),335operation);336// apply boolean operations starting from smaller meshes337// the assumption is that boolean operations on smaller meshes is faster,338// due to less data being copied and processed339auto cmpFn = MeshCompare();340std::make_heap(results.begin(), results.end(), cmpFn);341std::vector<std::shared_ptr<CsgLeafNode>> tmp;342#if MANIFOLD_PAR == 1343tbb::task_group group;344std::mutex mutex;345#endif346while (results.size() > 1) {347for (size_t i = 0; i < 4 && results.size() > 1; i++) {348std::pop_heap(results.begin(), results.end(), cmpFn);349auto a = std::move(results.back());350results.pop_back();351std::pop_heap(results.begin(), results.end(), cmpFn);352auto b = std::move(results.back());353results.pop_back();354#if MANIFOLD_PAR == 1355group.run([&, a, b]() {356auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);357mutex.lock();358tmp.push_back(result);359mutex.unlock();360});361#else362auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);363tmp.push_back(result);364#endif365}366#if MANIFOLD_PAR == 1367group.wait();368#endif369for (auto result : tmp) {370results.push_back(result);371std::push_heap(results.begin(), results.end(), cmpFn);372}373tmp.clear();374}375return results.front();376}377378/**379* Efficient union operation on a set of nodes by doing Compose as much as380* possible.381*/382std::shared_ptr<CsgLeafNode> BatchUnion(383std::vector<std::shared_ptr<CsgLeafNode>>& children) {384ZoneScoped;385// INVARIANT: children_ is a vector of leaf nodes386// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check387// with O(n^2) complexity to take too long.388// If the number of children exceeded this limit, we will operate on chunks389// with size kMaxUnionSize.390constexpr size_t kMaxUnionSize = 1000;391DEBUG_ASSERT(!children.empty(), logicErr,392"BatchUnion should not have empty children");393while (children.size() > 1) {394const size_t start = (children.size() > kMaxUnionSize)395? (children.size() - kMaxUnionSize)396: 0;397Vec<Box> boxes;398boxes.reserve(children.size() - start);399for (size_t i = start; i < children.size(); i++) {400boxes.push_back(children[i]->GetImpl()->bBox_);401}402// partition the children into a set of disjoint sets403// each set contains a set of children that are pairwise disjoint404std::vector<Vec<size_t>> disjointSets;405for (size_t i = 0; i < boxes.size(); i++) {406auto lambda = [&boxes, i](const Vec<size_t>& set) {407return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) {408return boxes[i].DoesOverlap(boxes[j]);409}) == set.end();410};411auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);412if (it == disjointSets.end()) {413disjointSets.push_back(std::vector<size_t>{i});414} else {415it->push_back(i);416}417}418// compose each set of disjoint children419std::vector<std::shared_ptr<CsgLeafNode>> impls;420for (auto& set : disjointSets) {421if (set.size() == 1) {422impls.push_back(children[start + set[0]]);423} else {424std::vector<std::shared_ptr<CsgLeafNode>> tmp;425for (size_t j : set) {426tmp.push_back(children[start + j]);427}428impls.push_back(CsgLeafNode::Compose(tmp));429}430}431432children.erase(children.begin() + start, children.end());433children.push_back(BatchBoolean(OpType::Add, impls));434// move it to the front as we process from the back, and the newly added435// child should be quite complicated436std::swap(children.front(), children.back());437}438return children.front();439}440441CsgOpNode::CsgOpNode() {}442443CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children,444OpType op)445: impl_(children), op_(op) {}446447CsgOpNode::~CsgOpNode() {448if (impl_.UseCount() == 1) {449auto impl = impl_.GetGuard();450std::vector<std::shared_ptr<CsgOpNode>> toProcess;451auto handleChildren =452[&toProcess](std::vector<std::shared_ptr<CsgNode>>& children) {453while (!children.empty()) {454// move out so shrinking the vector will not trigger recursive drop455auto movedChild = std::move(children.back());456children.pop_back();457if (movedChild->GetNodeType() != CsgNodeType::Leaf)458toProcess.push_back(459std::static_pointer_cast<CsgOpNode>(std::move(movedChild)));460}461};462handleChildren(*impl);463while (!toProcess.empty()) {464auto child = std::move(toProcess.back());465toProcess.pop_back();466if (impl_.UseCount() == 1) {467auto childImpl = child->impl_.GetGuard();468handleChildren(*childImpl);469}470}471}472}473474std::shared_ptr<CsgNode> CsgOpNode::Boolean(475const std::shared_ptr<CsgNode>& second, OpType op) {476std::vector<std::shared_ptr<CsgNode>> children;477children.push_back(shared_from_this());478children.push_back(second);479480return std::make_shared<CsgOpNode>(children, op);481}482483std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4& m) const {484auto node = std::make_shared<CsgOpNode>();485node->impl_ = impl_;486node->transform_ = m * Mat4(transform_);487node->op_ = op_;488return node;489}490491struct CsgStackFrame {492using Nodes = std::vector<std::shared_ptr<CsgLeafNode>>;493494bool finalize;495OpType parent_op;496mat3x4 transform;497Nodes* positive_dest;498Nodes* negative_dest;499std::shared_ptr<const CsgOpNode> op_node;500Nodes positive_children;501Nodes negative_children;502503CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform,504Nodes* positive_dest, Nodes* negative_dest,505std::shared_ptr<const CsgOpNode> op_node)506: finalize(finalize),507parent_op(parent_op),508transform(transform),509positive_dest(positive_dest),510negative_dest(negative_dest),511op_node(op_node) {}512};513514std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {515if (cache_ != nullptr) return cache_;516517// Note: We do need a pointer here to avoid vector pointers from being518// invalidated after pushing elements into the explicit stack.519// It is a `shared_ptr` because we may want to drop the stack frame while520// still referring to some of the elements inside the old frame.521// It is possible to use `unique_ptr`, extending the lifetime of the frame522// when we remove it from the stack, but it is a bit more complicated and523// there is no measurable overhead from using `shared_ptr` here...524std::vector<std::shared_ptr<CsgStackFrame>> stack;525// initial node, positive_dest is a nullptr because we don't need to put the526// result anywhere else (except in the cache_).527stack.push_back(std::make_shared<CsgStackFrame>(528false, op_, la::identity, nullptr, nullptr,529std::static_pointer_cast<const CsgOpNode>(shared_from_this())));530531// Instead of actually using recursion in the algorithm, we use an explicit532// stack, do DFS and store the intermediate states into `CsgStackFrame` to533// avoid stack overflow.534//535// Before performing boolean operations, we should make sure that all children536// are `CsgLeafNodes`, i.e. are actual meshes that can be operated on. Hence,537// we do it in two steps:538// 1. Populate `children` (`positive_children` and `negative_children`)539// If the child is a `CsgOpNode`, we either collapse it or compute its540// boolean operation result.541// 2. Performs boolean after populating the `children` set.542// After a boolean operation is completed, we put the result back to its543// parent's `children` set.544//545// When we populate `children`, we perform collapsing on-the-fly.546// For example, we want to turn `(Union a (Union b c))` into `(Union a b c)`.547// This allows more efficient `BatchBoolean`/`BatchUnion` calls.548// We can do this when the child operation is the same as the parent549// operation, except when the operation is `Subtract` (see below).550// Note that to avoid repeating work, we will not collapse nodes that are551// reused. And in the special case where the children set only contains one552// element, we don't need any operation, so we can collapse that as well.553// Instead of moving `b` and `c` into the parent, and running this collapsing554// check until a fixed point, we remember the `positive_dest` where we should555// put the `CsgLeafNode` into. Normally, the `positive_dest` pointer point to556// the parent `children` set. However, when a child is being collapsed, we557// keep using the old `positive_dest` pointer for the grandchildren. Hence,558// removing a node by collapsing takes O(1) time. We also need to store the559// parent operation type for checking if the node is eligible for collapsing,560// and transform matrix because we need to re-apply the transformation to the561// children.562//563// `Subtract` is handled differently from `Add` and `Intersect`.564// For the first operand, it is treated as normal subtract. Negative children565// in this operand is propagated to the parent, which is equivalent to566// collapsing `(a - b) - c` into `a - (b + c)`.567// For the remaining operands, they are treated as a nested `Add` node,568// collapsing `a - (b + (c + d))` into `a - (b + c + d)`.569//570// `impl` should always contain either the raw set of children or571// the NOT transformed result, while `cache_` should contain the transformed572// result. This is because `impl` can be shared between `CsgOpNode` that573// differ in `transform_`, so we want it to be able to share the result.574// ===========================================================================575// Recursive version (pseudocode only):576//577// void f(CsgOpNode node, OpType parent_op, mat3x4 transform,578// Nodes *positive_dest, Nodes *negative_dest) {579// auto impl = node->impl_.GetGuard();580// // can collapse when we have the same operation as the parent and is581// // unique, or when we have only one children.582// const OpType op = node->op_;583// const bool canCollapse = (op == parent_op && IsUnique(node)) ||584// impl->size() == 1;585// const mat3x4 transform2 = canCollapse ? transform * node->transform_586// : la::identity;587// Nodes positive_children, negative_children;588// Nodes* pos_dest = canCollapse ? positive_dest : &positive_children;589// Nodes* neg_dest = canCollapse ? negative_dest : &negative_children;590// for (size_t i = 0; i < impl->size(); i++) {591// auto child = (*impl)[i];592// const bool negative = op == OpType::Subtract && i != 0;593// Nodes *dest1 = negative ? neg_dest : pos_dest;594// Nodes *dest2 = (op == OpType::Subtract && i == 0) ?595// neg_dest : nullptr;596// if (child->GetNodeType() == CsgNodeType::Leaf)597// dest1.push_back(child);598// else599// f(child, op, transform2, dest1, dest2);600// }601// if (canCollapse) return;602// if (node->op_ == OpType::Add)603// *impl = {BatchUnion(positive_children)};604// else if (node->op_ == OpType::Intersect)605// *impl = {BatchBoolean(Intersect, positive_children)};606// else // subtract607// *impl = { BatchUnion(positive_children) -608// BatchUnion(negative_children)};609// // node local transform610// node->cache_ = (*impl)[0].Transform(node.transform);611// // collapsed node transforms612// if (destination)613// destination->push_back(node->cache_->Transform(transform));614// }615while (!stack.empty()) {616std::shared_ptr<CsgStackFrame> frame = stack.back();617auto impl = frame->op_node->impl_.GetGuard();618if (frame->finalize) {619switch (frame->op_node->op_) {620case OpType::Add:621*impl = {BatchUnion(frame->positive_children)};622break;623case OpType::Intersect: {624*impl = {BatchBoolean(OpType::Intersect, frame->positive_children)};625break;626};627case OpType::Subtract:628if (frame->positive_children.empty()) {629// nothing to subtract from, so the result is empty.630*impl = {std::make_shared<CsgLeafNode>()};631} else {632auto positive = BatchUnion(frame->positive_children);633if (frame->negative_children.empty()) {634// nothing to subtract, result equal to the LHS.635*impl = {frame->positive_children[0]};636} else {637auto negative = BatchUnion(frame->negative_children);638*impl = {SimpleBoolean(*positive->GetImpl(), *negative->GetImpl(),639OpType::Subtract)};640}641}642break;643}644frame->op_node->cache_ = std::static_pointer_cast<CsgLeafNode>(645(*impl)[0]->Transform(frame->op_node->transform_));646if (frame->positive_dest != nullptr)647frame->positive_dest->push_back(std::static_pointer_cast<CsgLeafNode>(648frame->op_node->cache_->Transform(frame->transform)));649stack.pop_back();650} else {651auto add_children =652[&stack](std::shared_ptr<CsgNode>& node, OpType op, mat3x4 transform,653CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) {654if (node->GetNodeType() == CsgNodeType::Leaf)655dest1->push_back(std::static_pointer_cast<CsgLeafNode>(656node->Transform(transform)));657else658stack.push_back(std::make_shared<CsgStackFrame>(659false, op, transform, dest1, dest2,660std::static_pointer_cast<const CsgOpNode>(node)));661};662// op_node use_count == 2 because it is both inside one CsgOpNode663// and in our stack.664// if there is only one child, we can also collapse.665const OpType op = frame->op_node->op_;666const bool canCollapse =667frame->positive_dest != nullptr &&668((op == frame->parent_op && frame->op_node.use_count() <= 2 &&669frame->op_node->impl_.UseCount() == 1) ||670impl->size() == 1);671if (canCollapse)672stack.pop_back();673else674frame->finalize = true;675676const mat3x4 transform =677canCollapse ? (frame->transform * Mat4(frame->op_node->transform_))678: la::identity;679CsgStackFrame::Nodes* pos_dest =680canCollapse ? frame->positive_dest : &frame->positive_children;681CsgStackFrame::Nodes* neg_dest =682canCollapse ? frame->negative_dest : &frame->negative_children;683for (size_t i = 0; i < impl->size(); i++) {684const bool negative = op == OpType::Subtract && i != 0;685CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest;686CsgStackFrame::Nodes* dest2 =687(op == OpType::Subtract && i == 0) ? neg_dest : nullptr;688add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1,689dest2);690}691}692}693return cache_;694}695696CsgNodeType CsgOpNode::GetNodeType() const {697switch (op_) {698case OpType::Add:699return CsgNodeType::Union;700case OpType::Subtract:701return CsgNodeType::Difference;702case OpType::Intersect:703return CsgNodeType::Intersection;704}705// unreachable...706return CsgNodeType::Leaf;707}708709} // namespace manifold710711712