Path: blob/master/thirdparty/manifold/src/csg_tree.cpp
20875 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;344// make sure the order of result is deterministic345std::vector<std::shared_ptr<CsgLeafNode>> parallelTmp;346for (int i = 0; i < 4; i++) parallelTmp.push_back(nullptr);347#endif348while (results.size() > 1) {349for (size_t i = 0; i < 4 && results.size() > 1; i++) {350std::pop_heap(results.begin(), results.end(), cmpFn);351auto a = std::move(results.back());352results.pop_back();353std::pop_heap(results.begin(), results.end(), cmpFn);354auto b = std::move(results.back());355results.pop_back();356#if MANIFOLD_PAR == 1357group.run([&, i, a, b]() {358parallelTmp[i] = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);359});360#else361auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);362tmp.push_back(result);363#endif364}365#if MANIFOLD_PAR == 1366group.wait();367for (int i = 0; i < 4 && parallelTmp[i]; i++)368tmp.emplace_back(std::move(parallelTmp[i]));369#endif370for (auto result : tmp) {371results.push_back(result);372std::push_heap(results.begin(), results.end(), cmpFn);373}374tmp.clear();375}376return results.front();377}378379/**380* Efficient union operation on a set of nodes by doing Compose as much as381* possible.382*/383std::shared_ptr<CsgLeafNode> BatchUnion(384std::vector<std::shared_ptr<CsgLeafNode>>& children) {385ZoneScoped;386// INVARIANT: children_ is a vector of leaf nodes387// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check388// with O(n^2) complexity to take too long.389// If the number of children exceeded this limit, we will operate on chunks390// with size kMaxUnionSize.391constexpr size_t kMaxUnionSize = 1000;392DEBUG_ASSERT(!children.empty(), logicErr,393"BatchUnion should not have empty children");394while (children.size() > 1) {395const size_t start = (children.size() > kMaxUnionSize)396? (children.size() - kMaxUnionSize)397: 0;398Vec<Box> boxes;399boxes.reserve(children.size() - start);400for (size_t i = start; i < children.size(); i++) {401boxes.push_back(children[i]->GetImpl()->bBox_);402}403// partition the children into a set of disjoint sets404// each set contains a set of children that are pairwise disjoint405std::vector<Vec<size_t>> disjointSets;406for (size_t i = 0; i < boxes.size(); i++) {407auto lambda = [&boxes, i](const Vec<size_t>& set) {408return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) {409return boxes[i].DoesOverlap(boxes[j]);410}) == set.end();411};412auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);413if (it == disjointSets.end()) {414disjointSets.push_back(std::vector<size_t>{i});415} else {416it->push_back(i);417}418}419// compose each set of disjoint children420std::vector<std::shared_ptr<CsgLeafNode>> impls;421for (auto& set : disjointSets) {422if (set.size() == 1) {423impls.push_back(children[start + set[0]]);424} else {425std::vector<std::shared_ptr<CsgLeafNode>> tmp;426for (size_t j : set) {427tmp.push_back(children[start + j]);428}429impls.push_back(CsgLeafNode::Compose(tmp));430}431}432433children.erase(children.begin() + start, children.end());434children.push_back(BatchBoolean(OpType::Add, impls));435// move it to the front as we process from the back, and the newly added436// child should be quite complicated437std::swap(children.front(), children.back());438}439return children.front();440}441442CsgOpNode::CsgOpNode() {}443444CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children,445OpType op)446: impl_(children), op_(op) {}447448CsgOpNode::~CsgOpNode() {449if (impl_.UseCount() == 1) {450auto impl = impl_.GetGuard();451std::vector<std::shared_ptr<CsgOpNode>> toProcess;452auto handleChildren =453[&toProcess](std::vector<std::shared_ptr<CsgNode>>& children) {454while (!children.empty()) {455// move out so shrinking the vector will not trigger recursive drop456auto movedChild = std::move(children.back());457children.pop_back();458if (movedChild->GetNodeType() != CsgNodeType::Leaf)459toProcess.push_back(460std::static_pointer_cast<CsgOpNode>(std::move(movedChild)));461}462};463handleChildren(*impl);464while (!toProcess.empty()) {465auto child = std::move(toProcess.back());466toProcess.pop_back();467if (impl_.UseCount() == 1) {468auto childImpl = child->impl_.GetGuard();469handleChildren(*childImpl);470}471}472}473}474475std::shared_ptr<CsgNode> CsgOpNode::Boolean(476const std::shared_ptr<CsgNode>& second, OpType op) {477std::vector<std::shared_ptr<CsgNode>> children;478children.push_back(shared_from_this());479children.push_back(second);480481return std::make_shared<CsgOpNode>(children, op);482}483484std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4& m) const {485auto node = std::make_shared<CsgOpNode>();486node->impl_ = impl_;487node->transform_ = m * Mat4(transform_);488node->op_ = op_;489return node;490}491492struct CsgStackFrame {493using Nodes = std::vector<std::shared_ptr<CsgLeafNode>>;494495bool finalize;496OpType parent_op;497mat3x4 transform;498Nodes* positive_dest;499Nodes* negative_dest;500std::shared_ptr<const CsgOpNode> op_node;501Nodes positive_children;502Nodes negative_children;503504CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform,505Nodes* positive_dest, Nodes* negative_dest,506std::shared_ptr<const CsgOpNode> op_node)507: finalize(finalize),508parent_op(parent_op),509transform(transform),510positive_dest(positive_dest),511negative_dest(negative_dest),512op_node(op_node) {}513};514515std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {516if (cache_ != nullptr) return cache_;517518// Note: We do need a pointer here to avoid vector pointers from being519// invalidated after pushing elements into the explicit stack.520// It is a `shared_ptr` because we may want to drop the stack frame while521// still referring to some of the elements inside the old frame.522// It is possible to use `unique_ptr`, extending the lifetime of the frame523// when we remove it from the stack, but it is a bit more complicated and524// there is no measurable overhead from using `shared_ptr` here...525std::vector<std::shared_ptr<CsgStackFrame>> stack;526// initial node, positive_dest is a nullptr because we don't need to put the527// result anywhere else (except in the cache_).528stack.push_back(std::make_shared<CsgStackFrame>(529false, op_, la::identity, nullptr, nullptr,530std::static_pointer_cast<const CsgOpNode>(shared_from_this())));531532// Instead of actually using recursion in the algorithm, we use an explicit533// stack, do DFS and store the intermediate states into `CsgStackFrame` to534// avoid stack overflow.535//536// Before performing boolean operations, we should make sure that all children537// are `CsgLeafNodes`, i.e. are actual meshes that can be operated on. Hence,538// we do it in two steps:539// 1. Populate `children` (`positive_children` and `negative_children`)540// If the child is a `CsgOpNode`, we either collapse it or compute its541// boolean operation result.542// 2. Performs boolean after populating the `children` set.543// After a boolean operation is completed, we put the result back to its544// parent's `children` set.545//546// When we populate `children`, we perform collapsing on-the-fly.547// For example, we want to turn `(Union a (Union b c))` into `(Union a b c)`.548// This allows more efficient `BatchBoolean`/`BatchUnion` calls.549// We can do this when the child operation is the same as the parent550// operation, except when the operation is `Subtract` (see below).551// Note that to avoid repeating work, we will not collapse nodes that are552// reused. And in the special case where the children set only contains one553// element, we don't need any operation, so we can collapse that as well.554// Instead of moving `b` and `c` into the parent, and running this collapsing555// check until a fixed point, we remember the `positive_dest` where we should556// put the `CsgLeafNode` into. Normally, the `positive_dest` pointer point to557// the parent `children` set. However, when a child is being collapsed, we558// keep using the old `positive_dest` pointer for the grandchildren. Hence,559// removing a node by collapsing takes O(1) time. We also need to store the560// parent operation type for checking if the node is eligible for collapsing,561// and transform matrix because we need to re-apply the transformation to the562// children.563//564// `Subtract` is handled differently from `Add` and `Intersect`.565// For the first operand, it is treated as normal subtract. Negative children566// in this operand is propagated to the parent, which is equivalent to567// collapsing `(a - b) - c` into `a - (b + c)`.568// For the remaining operands, they are treated as a nested `Add` node,569// collapsing `a - (b + (c + d))` into `a - (b + c + d)`.570//571// `impl` should always contain either the raw set of children or572// the NOT transformed result, while `cache_` should contain the transformed573// result. This is because `impl` can be shared between `CsgOpNode` that574// differ in `transform_`, so we want it to be able to share the result.575// ===========================================================================576// Recursive version (pseudocode only):577//578// void f(CsgOpNode node, OpType parent_op, mat3x4 transform,579// Nodes *positive_dest, Nodes *negative_dest) {580// auto impl = node->impl_.GetGuard();581// // can collapse when we have the same operation as the parent and is582// // unique, or when we have only one children.583// const OpType op = node->op_;584// const bool canCollapse = (op == parent_op && IsUnique(node)) ||585// impl->size() == 1;586// const mat3x4 transform2 = canCollapse ? transform * node->transform_587// : la::identity;588// Nodes positive_children, negative_children;589// Nodes* pos_dest = canCollapse ? positive_dest : &positive_children;590// Nodes* neg_dest = canCollapse ? negative_dest : &negative_children;591// for (size_t i = 0; i < impl->size(); i++) {592// auto child = (*impl)[i];593// const bool negative = op == OpType::Subtract && i != 0;594// Nodes *dest1 = negative ? neg_dest : pos_dest;595// Nodes *dest2 = (op == OpType::Subtract && i == 0) ?596// neg_dest : nullptr;597// if (child->GetNodeType() == CsgNodeType::Leaf)598// dest1.push_back(child);599// else600// f(child, op, transform2, dest1, dest2);601// }602// if (canCollapse) return;603// if (node->op_ == OpType::Add)604// *impl = {BatchUnion(positive_children)};605// else if (node->op_ == OpType::Intersect)606// *impl = {BatchBoolean(Intersect, positive_children)};607// else // subtract608// *impl = { BatchUnion(positive_children) -609// BatchUnion(negative_children)};610// // node local transform611// node->cache_ = (*impl)[0].Transform(node.transform);612// // collapsed node transforms613// if (destination)614// destination->push_back(node->cache_->Transform(transform));615// }616while (!stack.empty()) {617std::shared_ptr<CsgStackFrame> frame = stack.back();618auto impl = frame->op_node->impl_.GetGuard();619if (frame->finalize) {620if (!frame->op_node->cache_) {621switch (frame->op_node->op_) {622case OpType::Add:623*impl = {BatchUnion(frame->positive_children)};624break;625case OpType::Intersect: {626*impl = {BatchBoolean(OpType::Intersect, frame->positive_children)};627break;628};629case OpType::Subtract:630if (frame->positive_children.empty()) {631// nothing to subtract from, so the result is empty.632*impl = {std::make_shared<CsgLeafNode>()};633} else {634auto positive = BatchUnion(frame->positive_children);635if (frame->negative_children.empty()) {636// nothing to subtract, result equal to the LHS.637*impl = {frame->positive_children[0]};638} else {639auto negative = BatchUnion(frame->negative_children);640*impl = {SimpleBoolean(*positive->GetImpl(),641*negative->GetImpl(), OpType::Subtract)};642}643}644break;645}646frame->op_node->cache_ = std::static_pointer_cast<CsgLeafNode>(647(*impl)[0]->Transform(frame->op_node->transform_));648}649if (frame->positive_dest != nullptr)650frame->positive_dest->push_back(std::static_pointer_cast<CsgLeafNode>(651frame->op_node->cache_->Transform(frame->transform)));652stack.pop_back();653} else {654auto add_children =655[&stack](std::shared_ptr<CsgNode>& node, OpType op, mat3x4 transform,656CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) {657if (node->GetNodeType() == CsgNodeType::Leaf)658dest1->push_back(std::static_pointer_cast<CsgLeafNode>(659node->Transform(transform)));660else661stack.push_back(std::make_shared<CsgStackFrame>(662false, op, transform, dest1, dest2,663std::static_pointer_cast<const CsgOpNode>(node)));664};665// op_node use_count == 2 because it is both inside one CsgOpNode666// and in our stack.667// if there is only one child, we can also collapse.668const OpType op = frame->op_node->op_;669const bool canCollapse =670frame->positive_dest != nullptr &&671((op == frame->parent_op && frame->op_node.use_count() <= 2 &&672frame->op_node->impl_.UseCount() == 1) ||673impl->size() == 1);674if (canCollapse)675stack.pop_back();676else677frame->finalize = true;678679const mat3x4 transform =680canCollapse ? (frame->transform * Mat4(frame->op_node->transform_))681: la::identity;682CsgStackFrame::Nodes* pos_dest =683canCollapse ? frame->positive_dest : &frame->positive_children;684CsgStackFrame::Nodes* neg_dest =685canCollapse ? frame->negative_dest : &frame->negative_children;686for (size_t i = 0; i < impl->size(); i++) {687const bool negative = op == OpType::Subtract && i != 0;688CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest;689CsgStackFrame::Nodes* dest2 =690(op == OpType::Subtract && i == 0) ? neg_dest : nullptr;691add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1,692dest2);693}694}695}696return cache_;697}698699CsgNodeType CsgOpNode::GetNodeType() const {700switch (op_) {701case OpType::Add:702return CsgNodeType::Union;703case OpType::Subtract:704return CsgNodeType::Difference;705case OpType::Intersect:706return CsgNodeType::Intersection;707}708// unreachable...709return CsgNodeType::Leaf;710}711712} // namespace manifold713714715