Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/csg_tree.cpp
9902 views
1
// Copyright 2022 The Manifold Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#if MANIFOLD_PAR == 1
16
#include <tbb/tbb.h>
17
#endif
18
19
#include <algorithm>
20
21
#include "boolean3.h"
22
#include "csg_tree.h"
23
#include "impl.h"
24
#include "mesh_fixes.h"
25
#include "parallel.h"
26
27
namespace {
28
using namespace manifold;
29
struct MeshCompare {
30
bool operator()(const std::shared_ptr<CsgLeafNode>& a,
31
const std::shared_ptr<CsgLeafNode>& b) {
32
return a->GetImpl()->NumVert() < b->GetImpl()->NumVert();
33
}
34
};
35
36
} // namespace
37
namespace manifold {
38
39
std::shared_ptr<CsgNode> CsgNode::Boolean(
40
const std::shared_ptr<CsgNode>& second, OpType op) {
41
if (second->GetNodeType() != CsgNodeType::Leaf) {
42
// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is
43
// and the operation is commutative, we let it built the tree.
44
if ((op == OpType::Add || op == OpType::Intersect)) {
45
return std::static_pointer_cast<CsgOpNode>(second)->Boolean(
46
shared_from_this(), op);
47
}
48
}
49
std::vector<std::shared_ptr<CsgNode>> children({shared_from_this(), second});
50
return std::make_shared<CsgOpNode>(children, op);
51
}
52
53
std::shared_ptr<CsgNode> CsgNode::Translate(const vec3& t) const {
54
mat3x4 transform = la::identity;
55
transform[3] += t;
56
return Transform(transform);
57
}
58
59
std::shared_ptr<CsgNode> CsgNode::Scale(const vec3& v) const {
60
mat3x4 transform;
61
for (int i : {0, 1, 2}) transform[i][i] = v[i];
62
return Transform(transform);
63
}
64
65
std::shared_ptr<CsgNode> CsgNode::Rotate(double xDegrees, double yDegrees,
66
double zDegrees) const {
67
mat3 rX({1.0, 0.0, 0.0}, //
68
{0.0, cosd(xDegrees), sind(xDegrees)}, //
69
{0.0, -sind(xDegrees), cosd(xDegrees)});
70
mat3 rY({cosd(yDegrees), 0.0, -sind(yDegrees)}, //
71
{0.0, 1.0, 0.0}, //
72
{sind(yDegrees), 0.0, cosd(yDegrees)});
73
mat3 rZ({cosd(zDegrees), sind(zDegrees), 0.0}, //
74
{-sind(zDegrees), cosd(zDegrees), 0.0}, //
75
{0.0, 0.0, 1.0});
76
mat3x4 transform(rZ * rY * rX, vec3());
77
return Transform(transform);
78
}
79
80
CsgLeafNode::CsgLeafNode() : pImpl_(std::make_shared<Manifold::Impl>()) {}
81
82
CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_)
83
: pImpl_(pImpl_) {}
84
85
CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_,
86
mat3x4 transform_)
87
: pImpl_(pImpl_), transform_(transform_) {}
88
89
std::shared_ptr<const Manifold::Impl> CsgLeafNode::GetImpl() const {
90
if (transform_ == mat3x4(la::identity)) return pImpl_;
91
pImpl_ =
92
std::make_shared<const Manifold::Impl>(pImpl_->Transform(transform_));
93
transform_ = la::identity;
94
return pImpl_;
95
}
96
97
std::shared_ptr<CsgLeafNode> CsgLeafNode::ToLeafNode() const {
98
return std::make_shared<CsgLeafNode>(*this);
99
}
100
101
std::shared_ptr<CsgNode> CsgLeafNode::Transform(const mat3x4& m) const {
102
return std::make_shared<CsgLeafNode>(pImpl_, m * Mat4(transform_));
103
}
104
105
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }
106
107
std::shared_ptr<CsgLeafNode> ImplToLeaf(Manifold::Impl&& impl) {
108
return std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(impl));
109
}
110
111
std::shared_ptr<CsgLeafNode> SimpleBoolean(const Manifold::Impl& a,
112
const Manifold::Impl& b, OpType op) {
113
#ifdef MANIFOLD_DEBUG
114
auto dump = [&]() {
115
dump_lock.lock();
116
std::cout << "LHS self-intersecting: " << a.IsSelfIntersecting()
117
<< std::endl;
118
std::cout << "RHS self-intersecting: " << b.IsSelfIntersecting()
119
<< std::endl;
120
#ifdef MANIFOLD_EXPORT
121
if (ManifoldParams().verbose) {
122
if (op == OpType::Add)
123
std::cout << "Add";
124
else if (op == OpType::Intersect)
125
std::cout << "Intersect";
126
else
127
std::cout << "Subtract";
128
std::cout << std::endl;
129
std::cout << a;
130
std::cout << b;
131
}
132
#endif
133
dump_lock.unlock();
134
};
135
try {
136
Boolean3 boolean(a, b, op);
137
auto impl = boolean.Result(op);
138
if (ManifoldParams().selfIntersectionChecks && impl.IsSelfIntersecting()) {
139
dump_lock.lock();
140
std::cout << "self intersections detected" << std::endl;
141
dump_lock.unlock();
142
throw logicErr("self intersection detected");
143
}
144
return ImplToLeaf(std::move(impl));
145
} catch (logicErr& err) {
146
dump();
147
throw err;
148
} catch (geometryErr& err) {
149
dump();
150
throw err;
151
}
152
#else
153
return ImplToLeaf(Boolean3(a, b, op).Result(op));
154
#endif
155
}
156
157
/**
158
* Efficient union of a set of pairwise disjoint meshes.
159
*/
160
std::shared_ptr<CsgLeafNode> CsgLeafNode::Compose(
161
const std::vector<std::shared_ptr<CsgLeafNode>>& nodes) {
162
ZoneScoped;
163
double epsilon = -1;
164
double tolerance = -1;
165
int numVert = 0;
166
int numEdge = 0;
167
int numTri = 0;
168
int numPropVert = 0;
169
std::vector<int> vertIndices;
170
std::vector<int> edgeIndices;
171
std::vector<int> triIndices;
172
std::vector<int> propVertIndices;
173
int numPropOut = 0;
174
for (auto& node : nodes) {
175
if (node->pImpl_->status_ != Manifold::Error::NoError) {
176
Manifold::Impl impl;
177
impl.status_ = node->pImpl_->status_;
178
return ImplToLeaf(std::move(impl));
179
}
180
double nodeOldScale = node->pImpl_->bBox_.Scale();
181
double nodeNewScale =
182
node->pImpl_->bBox_.Transform(node->transform_).Scale();
183
double nodeEpsilon = node->pImpl_->epsilon_;
184
nodeEpsilon *= std::max(1.0, nodeNewScale / nodeOldScale);
185
nodeEpsilon = std::max(nodeEpsilon, kPrecision * nodeNewScale);
186
if (!std::isfinite(nodeEpsilon)) nodeEpsilon = -1;
187
epsilon = std::max(epsilon, nodeEpsilon);
188
tolerance = std::max(tolerance, node->pImpl_->tolerance_);
189
190
vertIndices.push_back(numVert);
191
edgeIndices.push_back(numEdge * 2);
192
triIndices.push_back(numTri);
193
propVertIndices.push_back(numPropVert);
194
numVert += node->pImpl_->NumVert();
195
numEdge += node->pImpl_->NumEdge();
196
numTri += node->pImpl_->NumTri();
197
const int numProp = node->pImpl_->NumProp();
198
numPropOut = std::max(numPropOut, numProp);
199
numPropVert +=
200
numProp == 0 ? 1 : node->pImpl_->properties_.size() / numProp;
201
}
202
203
Manifold::Impl combined;
204
combined.epsilon_ = epsilon;
205
combined.tolerance_ = tolerance;
206
combined.vertPos_.resize_nofill(numVert);
207
combined.halfedge_.resize_nofill(2 * numEdge);
208
combined.faceNormal_.resize_nofill(numTri);
209
combined.halfedgeTangent_.resize(2 * numEdge);
210
combined.meshRelation_.triRef.resize_nofill(numTri);
211
if (numPropOut > 0) {
212
combined.numProp_ = numPropOut;
213
combined.properties_.resize(numPropOut * numPropVert, 0);
214
}
215
auto policy = autoPolicy(numTri);
216
217
// if we are already parallelizing for each node, do not perform multithreaded
218
// copying as it will slightly hurt performance
219
if (nodes.size() > 1 && policy == ExecutionPolicy::Par)
220
policy = ExecutionPolicy::Seq;
221
222
for_each_n(
223
nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
224
countAt(0), nodes.size(),
225
[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,
226
numPropOut, &combined, policy](int i) {
227
auto& node = nodes[i];
228
copy(node->pImpl_->halfedgeTangent_.begin(),
229
node->pImpl_->halfedgeTangent_.end(),
230
combined.halfedgeTangent_.begin() + edgeIndices[i]);
231
const int nextVert = vertIndices[i];
232
const int nextEdge = edgeIndices[i];
233
const int nextProp = propVertIndices[i];
234
transform(node->pImpl_->halfedge_.begin(),
235
node->pImpl_->halfedge_.end(),
236
combined.halfedge_.begin() + edgeIndices[i],
237
[nextVert, nextEdge, nextProp](Halfedge edge) {
238
edge.startVert += nextVert;
239
edge.endVert += nextVert;
240
edge.pairedHalfedge += nextEdge;
241
edge.propVert += nextProp;
242
return edge;
243
});
244
245
if (node->pImpl_->NumProp() > 0) {
246
const int numProp = node->pImpl_->NumProp();
247
auto& oldProp = node->pImpl_->properties_;
248
auto& newProp = combined.properties_;
249
for (int p = 0; p < numProp; ++p) {
250
auto oldRange =
251
StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp);
252
auto newRange = StridedRange(
253
newProp.begin() + numPropOut * propVertIndices[i] + p,
254
newProp.end(), numPropOut);
255
copy(oldRange.begin(), oldRange.end(), newRange.begin());
256
}
257
}
258
259
if (node->transform_ == mat3x4(la::identity)) {
260
copy(node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
261
combined.vertPos_.begin() + vertIndices[i]);
262
copy(node->pImpl_->faceNormal_.begin(),
263
node->pImpl_->faceNormal_.end(),
264
combined.faceNormal_.begin() + triIndices[i]);
265
} else {
266
// no need to apply the transform to the node, just copy the vertices
267
// and face normals and apply transform on the fly
268
const mat3x4 transform = node->transform_;
269
auto vertPosBegin = TransformIterator(
270
node->pImpl_->vertPos_.begin(), [&transform](vec3 position) {
271
return transform * vec4(position, 1.0);
272
});
273
mat3 normalTransform =
274
la::inverse(la::transpose(mat3(node->transform_)));
275
auto faceNormalBegin =
276
TransformIterator(node->pImpl_->faceNormal_.begin(),
277
TransformNormals({normalTransform}));
278
copy_n(vertPosBegin, node->pImpl_->vertPos_.size(),
279
combined.vertPos_.begin() + vertIndices[i]);
280
copy_n(faceNormalBegin, node->pImpl_->faceNormal_.size(),
281
combined.faceNormal_.begin() + triIndices[i]);
282
283
const bool invert = la::determinant(mat3(node->transform_)) < 0;
284
for_each_n(policy, countAt(0), node->pImpl_->halfedgeTangent_.size(),
285
TransformTangents{combined.halfedgeTangent_,
286
edgeIndices[i], mat3(node->transform_),
287
invert, node->pImpl_->halfedgeTangent_,
288
node->pImpl_->halfedge_});
289
if (invert)
290
for_each_n(policy, countAt(triIndices[i]), node->pImpl_->NumTri(),
291
FlipTris({combined.halfedge_}));
292
}
293
// Since the nodes may be copies containing the same meshIDs, it is
294
// important to add an offset so that each node instance gets
295
// unique meshIDs.
296
const int offset = i * Manifold::Impl::meshIDCounter_;
297
transform(node->pImpl_->meshRelation_.triRef.begin(),
298
node->pImpl_->meshRelation_.triRef.end(),
299
combined.meshRelation_.triRef.begin() + triIndices[i],
300
[offset](TriRef ref) {
301
ref.meshID += offset;
302
return ref;
303
});
304
});
305
306
for (size_t i = 0; i < nodes.size(); i++) {
307
auto& node = nodes[i];
308
const int offset = i * Manifold::Impl::meshIDCounter_;
309
310
for (const auto& pair : node->pImpl_->meshRelation_.meshIDtransform) {
311
combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;
312
}
313
}
314
315
// required to remove parts that are smaller than the tolerance
316
combined.RemoveDegenerates();
317
combined.Finish();
318
combined.IncrementMeshIDs();
319
return ImplToLeaf(std::move(combined));
320
}
321
322
/**
323
* Efficient boolean operation on a set of nodes utilizing commutativity of the
324
* operation. Only supports union and intersection.
325
*/
326
std::shared_ptr<CsgLeafNode> BatchBoolean(
327
OpType operation, std::vector<std::shared_ptr<CsgLeafNode>>& results) {
328
ZoneScoped;
329
DEBUG_ASSERT(operation != OpType::Subtract, logicErr,
330
"BatchBoolean doesn't support Difference.");
331
// common cases
332
if (results.size() == 0) return std::make_shared<CsgLeafNode>();
333
if (results.size() == 1) return results.front();
334
if (results.size() == 2)
335
return SimpleBoolean(*results[0]->GetImpl(), *results[1]->GetImpl(),
336
operation);
337
// apply boolean operations starting from smaller meshes
338
// the assumption is that boolean operations on smaller meshes is faster,
339
// due to less data being copied and processed
340
auto cmpFn = MeshCompare();
341
std::make_heap(results.begin(), results.end(), cmpFn);
342
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
343
#if MANIFOLD_PAR == 1
344
tbb::task_group group;
345
std::mutex mutex;
346
#endif
347
while (results.size() > 1) {
348
for (size_t i = 0; i < 4 && results.size() > 1; i++) {
349
std::pop_heap(results.begin(), results.end(), cmpFn);
350
auto a = std::move(results.back());
351
results.pop_back();
352
std::pop_heap(results.begin(), results.end(), cmpFn);
353
auto b = std::move(results.back());
354
results.pop_back();
355
#if MANIFOLD_PAR == 1
356
group.run([&, a, b]() {
357
auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);
358
mutex.lock();
359
tmp.push_back(result);
360
mutex.unlock();
361
});
362
#else
363
auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);
364
tmp.push_back(result);
365
#endif
366
}
367
#if MANIFOLD_PAR == 1
368
group.wait();
369
#endif
370
for (auto result : tmp) {
371
results.push_back(result);
372
std::push_heap(results.begin(), results.end(), cmpFn);
373
}
374
tmp.clear();
375
}
376
return results.front();
377
}
378
379
/**
380
* Efficient union operation on a set of nodes by doing Compose as much as
381
* possible.
382
*/
383
std::shared_ptr<CsgLeafNode> BatchUnion(
384
std::vector<std::shared_ptr<CsgLeafNode>>& children) {
385
ZoneScoped;
386
// INVARIANT: children_ is a vector of leaf nodes
387
// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check
388
// with O(n^2) complexity to take too long.
389
// If the number of children exceeded this limit, we will operate on chunks
390
// with size kMaxUnionSize.
391
constexpr size_t kMaxUnionSize = 1000;
392
DEBUG_ASSERT(!children.empty(), logicErr,
393
"BatchUnion should not have empty children");
394
while (children.size() > 1) {
395
const size_t start = (children.size() > kMaxUnionSize)
396
? (children.size() - kMaxUnionSize)
397
: 0;
398
Vec<Box> boxes;
399
boxes.reserve(children.size() - start);
400
for (size_t i = start; i < children.size(); i++) {
401
boxes.push_back(children[i]->GetImpl()->bBox_);
402
}
403
// partition the children into a set of disjoint sets
404
// each set contains a set of children that are pairwise disjoint
405
std::vector<Vec<size_t>> disjointSets;
406
for (size_t i = 0; i < boxes.size(); i++) {
407
auto lambda = [&boxes, i](const Vec<size_t>& set) {
408
return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) {
409
return boxes[i].DoesOverlap(boxes[j]);
410
}) == set.end();
411
};
412
auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);
413
if (it == disjointSets.end()) {
414
disjointSets.push_back(std::vector<size_t>{i});
415
} else {
416
it->push_back(i);
417
}
418
}
419
// compose each set of disjoint children
420
std::vector<std::shared_ptr<CsgLeafNode>> impls;
421
for (auto& set : disjointSets) {
422
if (set.size() == 1) {
423
impls.push_back(children[start + set[0]]);
424
} else {
425
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
426
for (size_t j : set) {
427
tmp.push_back(children[start + j]);
428
}
429
impls.push_back(CsgLeafNode::Compose(tmp));
430
}
431
}
432
433
children.erase(children.begin() + start, children.end());
434
children.push_back(BatchBoolean(OpType::Add, impls));
435
// move it to the front as we process from the back, and the newly added
436
// child should be quite complicated
437
std::swap(children.front(), children.back());
438
}
439
return children.front();
440
}
441
442
CsgOpNode::CsgOpNode() {}
443
444
CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children,
445
OpType op)
446
: impl_(children), op_(op) {}
447
448
CsgOpNode::~CsgOpNode() {
449
if (impl_.UseCount() == 1) {
450
auto impl = impl_.GetGuard();
451
std::vector<std::shared_ptr<CsgOpNode>> toProcess;
452
auto handleChildren =
453
[&toProcess](std::vector<std::shared_ptr<CsgNode>>& children) {
454
while (!children.empty()) {
455
// move out so shrinking the vector will not trigger recursive drop
456
auto movedChild = std::move(children.back());
457
children.pop_back();
458
if (movedChild->GetNodeType() != CsgNodeType::Leaf)
459
toProcess.push_back(
460
std::static_pointer_cast<CsgOpNode>(std::move(movedChild)));
461
}
462
};
463
handleChildren(*impl);
464
while (!toProcess.empty()) {
465
auto child = std::move(toProcess.back());
466
toProcess.pop_back();
467
if (impl_.UseCount() == 1) {
468
auto childImpl = child->impl_.GetGuard();
469
handleChildren(*childImpl);
470
}
471
}
472
}
473
}
474
475
std::shared_ptr<CsgNode> CsgOpNode::Boolean(
476
const std::shared_ptr<CsgNode>& second, OpType op) {
477
std::vector<std::shared_ptr<CsgNode>> children;
478
children.push_back(shared_from_this());
479
children.push_back(second);
480
481
return std::make_shared<CsgOpNode>(children, op);
482
}
483
484
std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4& m) const {
485
auto node = std::make_shared<CsgOpNode>();
486
node->impl_ = impl_;
487
node->transform_ = m * Mat4(transform_);
488
node->op_ = op_;
489
return node;
490
}
491
492
struct CsgStackFrame {
493
using Nodes = std::vector<std::shared_ptr<CsgLeafNode>>;
494
495
bool finalize;
496
OpType parent_op;
497
mat3x4 transform;
498
Nodes* positive_dest;
499
Nodes* negative_dest;
500
std::shared_ptr<const CsgOpNode> op_node;
501
Nodes positive_children;
502
Nodes negative_children;
503
504
CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform,
505
Nodes* positive_dest, Nodes* negative_dest,
506
std::shared_ptr<const CsgOpNode> op_node)
507
: finalize(finalize),
508
parent_op(parent_op),
509
transform(transform),
510
positive_dest(positive_dest),
511
negative_dest(negative_dest),
512
op_node(op_node) {}
513
};
514
515
std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
516
if (cache_ != nullptr) return cache_;
517
518
// Note: We do need a pointer here to avoid vector pointers from being
519
// invalidated after pushing elements into the explicit stack.
520
// It is a `shared_ptr` because we may want to drop the stack frame while
521
// still referring to some of the elements inside the old frame.
522
// It is possible to use `unique_ptr`, extending the lifetime of the frame
523
// when we remove it from the stack, but it is a bit more complicated and
524
// there is no measurable overhead from using `shared_ptr` here...
525
std::vector<std::shared_ptr<CsgStackFrame>> stack;
526
// initial node, positive_dest is a nullptr because we don't need to put the
527
// result anywhere else (except in the cache_).
528
stack.push_back(std::make_shared<CsgStackFrame>(
529
false, op_, la::identity, nullptr, nullptr,
530
std::static_pointer_cast<const CsgOpNode>(shared_from_this())));
531
532
// Instead of actually using recursion in the algorithm, we use an explicit
533
// stack, do DFS and store the intermediate states into `CsgStackFrame` to
534
// avoid stack overflow.
535
//
536
// Before performing boolean operations, we should make sure that all children
537
// 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 its
541
// 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 its
544
// 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 parent
550
// operation, except when the operation is `Subtract` (see below).
551
// Note that to avoid repeating work, we will not collapse nodes that are
552
// reused. And in the special case where the children set only contains one
553
// 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 collapsing
555
// check until a fixed point, we remember the `positive_dest` where we should
556
// put the `CsgLeafNode` into. Normally, the `positive_dest` pointer point to
557
// the parent `children` set. However, when a child is being collapsed, we
558
// 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 the
560
// 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 the
562
// children.
563
//
564
// `Subtract` is handled differently from `Add` and `Intersect`.
565
// For the first operand, it is treated as normal subtract. Negative children
566
// in this operand is propagated to the parent, which is equivalent to
567
// 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 or
572
// the NOT transformed result, while `cache_` should contain the transformed
573
// result. This is because `impl` can be shared between `CsgOpNode` that
574
// 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 is
582
// // 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
// else
600
// 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 // subtract
608
// *impl = { BatchUnion(positive_children) -
609
// BatchUnion(negative_children)};
610
// // node local transform
611
// node->cache_ = (*impl)[0].Transform(node.transform);
612
// // collapsed node transforms
613
// if (destination)
614
// destination->push_back(node->cache_->Transform(transform));
615
// }
616
while (!stack.empty()) {
617
std::shared_ptr<CsgStackFrame> frame = stack.back();
618
auto impl = frame->op_node->impl_.GetGuard();
619
if (frame->finalize) {
620
switch (frame->op_node->op_) {
621
case OpType::Add:
622
*impl = {BatchUnion(frame->positive_children)};
623
break;
624
case OpType::Intersect: {
625
*impl = {BatchBoolean(OpType::Intersect, frame->positive_children)};
626
break;
627
};
628
case OpType::Subtract:
629
if (frame->positive_children.empty()) {
630
// nothing to subtract from, so the result is empty.
631
*impl = {std::make_shared<CsgLeafNode>()};
632
} else {
633
auto positive = BatchUnion(frame->positive_children);
634
if (frame->negative_children.empty()) {
635
// nothing to subtract, result equal to the LHS.
636
*impl = {frame->positive_children[0]};
637
} else {
638
auto negative = BatchUnion(frame->negative_children);
639
*impl = {SimpleBoolean(*positive->GetImpl(), *negative->GetImpl(),
640
OpType::Subtract)};
641
}
642
}
643
break;
644
}
645
frame->op_node->cache_ = std::static_pointer_cast<CsgLeafNode>(
646
(*impl)[0]->Transform(frame->op_node->transform_));
647
if (frame->positive_dest != nullptr)
648
frame->positive_dest->push_back(std::static_pointer_cast<CsgLeafNode>(
649
frame->op_node->cache_->Transform(frame->transform)));
650
stack.pop_back();
651
} else {
652
auto add_children =
653
[&stack](std::shared_ptr<CsgNode>& node, OpType op, mat3x4 transform,
654
CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) {
655
if (node->GetNodeType() == CsgNodeType::Leaf)
656
dest1->push_back(std::static_pointer_cast<CsgLeafNode>(
657
node->Transform(transform)));
658
else
659
stack.push_back(std::make_shared<CsgStackFrame>(
660
false, op, transform, dest1, dest2,
661
std::static_pointer_cast<const CsgOpNode>(node)));
662
};
663
// op_node use_count == 2 because it is both inside one CsgOpNode
664
// and in our stack.
665
// if there is only one child, we can also collapse.
666
const OpType op = frame->op_node->op_;
667
const bool canCollapse =
668
frame->positive_dest != nullptr &&
669
((op == frame->parent_op && frame->op_node.use_count() <= 2 &&
670
frame->op_node->impl_.UseCount() == 1) ||
671
impl->size() == 1);
672
if (canCollapse)
673
stack.pop_back();
674
else
675
frame->finalize = true;
676
677
const mat3x4 transform =
678
canCollapse ? (frame->transform * Mat4(frame->op_node->transform_))
679
: la::identity;
680
CsgStackFrame::Nodes* pos_dest =
681
canCollapse ? frame->positive_dest : &frame->positive_children;
682
CsgStackFrame::Nodes* neg_dest =
683
canCollapse ? frame->negative_dest : &frame->negative_children;
684
for (size_t i = 0; i < impl->size(); i++) {
685
const bool negative = op == OpType::Subtract && i != 0;
686
CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest;
687
CsgStackFrame::Nodes* dest2 =
688
(op == OpType::Subtract && i == 0) ? neg_dest : nullptr;
689
add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1,
690
dest2);
691
}
692
}
693
}
694
return cache_;
695
}
696
697
CsgNodeType CsgOpNode::GetNodeType() const {
698
switch (op_) {
699
case OpType::Add:
700
return CsgNodeType::Union;
701
case OpType::Subtract:
702
return CsgNodeType::Difference;
703
case OpType::Intersect:
704
return CsgNodeType::Intersection;
705
}
706
// unreachable...
707
return CsgNodeType::Leaf;
708
}
709
710
} // namespace manifold
711
712