Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/csg_tree.cpp
20875 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
// make sure the order of result is deterministic
346
std::vector<std::shared_ptr<CsgLeafNode>> parallelTmp;
347
for (int i = 0; i < 4; i++) parallelTmp.push_back(nullptr);
348
#endif
349
while (results.size() > 1) {
350
for (size_t i = 0; i < 4 && results.size() > 1; i++) {
351
std::pop_heap(results.begin(), results.end(), cmpFn);
352
auto a = std::move(results.back());
353
results.pop_back();
354
std::pop_heap(results.begin(), results.end(), cmpFn);
355
auto b = std::move(results.back());
356
results.pop_back();
357
#if MANIFOLD_PAR == 1
358
group.run([&, i, a, b]() {
359
parallelTmp[i] = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);
360
});
361
#else
362
auto result = SimpleBoolean(*a->GetImpl(), *b->GetImpl(), operation);
363
tmp.push_back(result);
364
#endif
365
}
366
#if MANIFOLD_PAR == 1
367
group.wait();
368
for (int i = 0; i < 4 && parallelTmp[i]; i++)
369
tmp.emplace_back(std::move(parallelTmp[i]));
370
#endif
371
for (auto result : tmp) {
372
results.push_back(result);
373
std::push_heap(results.begin(), results.end(), cmpFn);
374
}
375
tmp.clear();
376
}
377
return results.front();
378
}
379
380
/**
381
* Efficient union operation on a set of nodes by doing Compose as much as
382
* possible.
383
*/
384
std::shared_ptr<CsgLeafNode> BatchUnion(
385
std::vector<std::shared_ptr<CsgLeafNode>>& children) {
386
ZoneScoped;
387
// INVARIANT: children_ is a vector of leaf nodes
388
// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check
389
// with O(n^2) complexity to take too long.
390
// If the number of children exceeded this limit, we will operate on chunks
391
// with size kMaxUnionSize.
392
constexpr size_t kMaxUnionSize = 1000;
393
DEBUG_ASSERT(!children.empty(), logicErr,
394
"BatchUnion should not have empty children");
395
while (children.size() > 1) {
396
const size_t start = (children.size() > kMaxUnionSize)
397
? (children.size() - kMaxUnionSize)
398
: 0;
399
Vec<Box> boxes;
400
boxes.reserve(children.size() - start);
401
for (size_t i = start; i < children.size(); i++) {
402
boxes.push_back(children[i]->GetImpl()->bBox_);
403
}
404
// partition the children into a set of disjoint sets
405
// each set contains a set of children that are pairwise disjoint
406
std::vector<Vec<size_t>> disjointSets;
407
for (size_t i = 0; i < boxes.size(); i++) {
408
auto lambda = [&boxes, i](const Vec<size_t>& set) {
409
return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) {
410
return boxes[i].DoesOverlap(boxes[j]);
411
}) == set.end();
412
};
413
auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);
414
if (it == disjointSets.end()) {
415
disjointSets.push_back(std::vector<size_t>{i});
416
} else {
417
it->push_back(i);
418
}
419
}
420
// compose each set of disjoint children
421
std::vector<std::shared_ptr<CsgLeafNode>> impls;
422
for (auto& set : disjointSets) {
423
if (set.size() == 1) {
424
impls.push_back(children[start + set[0]]);
425
} else {
426
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
427
for (size_t j : set) {
428
tmp.push_back(children[start + j]);
429
}
430
impls.push_back(CsgLeafNode::Compose(tmp));
431
}
432
}
433
434
children.erase(children.begin() + start, children.end());
435
children.push_back(BatchBoolean(OpType::Add, impls));
436
// move it to the front as we process from the back, and the newly added
437
// child should be quite complicated
438
std::swap(children.front(), children.back());
439
}
440
return children.front();
441
}
442
443
CsgOpNode::CsgOpNode() {}
444
445
CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>>& children,
446
OpType op)
447
: impl_(children), op_(op) {}
448
449
CsgOpNode::~CsgOpNode() {
450
if (impl_.UseCount() == 1) {
451
auto impl = impl_.GetGuard();
452
std::vector<std::shared_ptr<CsgOpNode>> toProcess;
453
auto handleChildren =
454
[&toProcess](std::vector<std::shared_ptr<CsgNode>>& children) {
455
while (!children.empty()) {
456
// move out so shrinking the vector will not trigger recursive drop
457
auto movedChild = std::move(children.back());
458
children.pop_back();
459
if (movedChild->GetNodeType() != CsgNodeType::Leaf)
460
toProcess.push_back(
461
std::static_pointer_cast<CsgOpNode>(std::move(movedChild)));
462
}
463
};
464
handleChildren(*impl);
465
while (!toProcess.empty()) {
466
auto child = std::move(toProcess.back());
467
toProcess.pop_back();
468
if (impl_.UseCount() == 1) {
469
auto childImpl = child->impl_.GetGuard();
470
handleChildren(*childImpl);
471
}
472
}
473
}
474
}
475
476
std::shared_ptr<CsgNode> CsgOpNode::Boolean(
477
const std::shared_ptr<CsgNode>& second, OpType op) {
478
std::vector<std::shared_ptr<CsgNode>> children;
479
children.push_back(shared_from_this());
480
children.push_back(second);
481
482
return std::make_shared<CsgOpNode>(children, op);
483
}
484
485
std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4& m) const {
486
auto node = std::make_shared<CsgOpNode>();
487
node->impl_ = impl_;
488
node->transform_ = m * Mat4(transform_);
489
node->op_ = op_;
490
return node;
491
}
492
493
struct CsgStackFrame {
494
using Nodes = std::vector<std::shared_ptr<CsgLeafNode>>;
495
496
bool finalize;
497
OpType parent_op;
498
mat3x4 transform;
499
Nodes* positive_dest;
500
Nodes* negative_dest;
501
std::shared_ptr<const CsgOpNode> op_node;
502
Nodes positive_children;
503
Nodes negative_children;
504
505
CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform,
506
Nodes* positive_dest, Nodes* negative_dest,
507
std::shared_ptr<const CsgOpNode> op_node)
508
: finalize(finalize),
509
parent_op(parent_op),
510
transform(transform),
511
positive_dest(positive_dest),
512
negative_dest(negative_dest),
513
op_node(op_node) {}
514
};
515
516
std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
517
if (cache_ != nullptr) return cache_;
518
519
// Note: We do need a pointer here to avoid vector pointers from being
520
// invalidated after pushing elements into the explicit stack.
521
// It is a `shared_ptr` because we may want to drop the stack frame while
522
// still referring to some of the elements inside the old frame.
523
// It is possible to use `unique_ptr`, extending the lifetime of the frame
524
// when we remove it from the stack, but it is a bit more complicated and
525
// there is no measurable overhead from using `shared_ptr` here...
526
std::vector<std::shared_ptr<CsgStackFrame>> stack;
527
// initial node, positive_dest is a nullptr because we don't need to put the
528
// result anywhere else (except in the cache_).
529
stack.push_back(std::make_shared<CsgStackFrame>(
530
false, op_, la::identity, nullptr, nullptr,
531
std::static_pointer_cast<const CsgOpNode>(shared_from_this())));
532
533
// Instead of actually using recursion in the algorithm, we use an explicit
534
// stack, do DFS and store the intermediate states into `CsgStackFrame` to
535
// avoid stack overflow.
536
//
537
// Before performing boolean operations, we should make sure that all children
538
// are `CsgLeafNodes`, i.e. are actual meshes that can be operated on. Hence,
539
// we do it in two steps:
540
// 1. Populate `children` (`positive_children` and `negative_children`)
541
// If the child is a `CsgOpNode`, we either collapse it or compute its
542
// boolean operation result.
543
// 2. Performs boolean after populating the `children` set.
544
// After a boolean operation is completed, we put the result back to its
545
// parent's `children` set.
546
//
547
// When we populate `children`, we perform collapsing on-the-fly.
548
// For example, we want to turn `(Union a (Union b c))` into `(Union a b c)`.
549
// This allows more efficient `BatchBoolean`/`BatchUnion` calls.
550
// We can do this when the child operation is the same as the parent
551
// operation, except when the operation is `Subtract` (see below).
552
// Note that to avoid repeating work, we will not collapse nodes that are
553
// reused. And in the special case where the children set only contains one
554
// element, we don't need any operation, so we can collapse that as well.
555
// Instead of moving `b` and `c` into the parent, and running this collapsing
556
// check until a fixed point, we remember the `positive_dest` where we should
557
// put the `CsgLeafNode` into. Normally, the `positive_dest` pointer point to
558
// the parent `children` set. However, when a child is being collapsed, we
559
// keep using the old `positive_dest` pointer for the grandchildren. Hence,
560
// removing a node by collapsing takes O(1) time. We also need to store the
561
// parent operation type for checking if the node is eligible for collapsing,
562
// and transform matrix because we need to re-apply the transformation to the
563
// children.
564
//
565
// `Subtract` is handled differently from `Add` and `Intersect`.
566
// For the first operand, it is treated as normal subtract. Negative children
567
// in this operand is propagated to the parent, which is equivalent to
568
// collapsing `(a - b) - c` into `a - (b + c)`.
569
// For the remaining operands, they are treated as a nested `Add` node,
570
// collapsing `a - (b + (c + d))` into `a - (b + c + d)`.
571
//
572
// `impl` should always contain either the raw set of children or
573
// the NOT transformed result, while `cache_` should contain the transformed
574
// result. This is because `impl` can be shared between `CsgOpNode` that
575
// differ in `transform_`, so we want it to be able to share the result.
576
// ===========================================================================
577
// Recursive version (pseudocode only):
578
//
579
// void f(CsgOpNode node, OpType parent_op, mat3x4 transform,
580
// Nodes *positive_dest, Nodes *negative_dest) {
581
// auto impl = node->impl_.GetGuard();
582
// // can collapse when we have the same operation as the parent and is
583
// // unique, or when we have only one children.
584
// const OpType op = node->op_;
585
// const bool canCollapse = (op == parent_op && IsUnique(node)) ||
586
// impl->size() == 1;
587
// const mat3x4 transform2 = canCollapse ? transform * node->transform_
588
// : la::identity;
589
// Nodes positive_children, negative_children;
590
// Nodes* pos_dest = canCollapse ? positive_dest : &positive_children;
591
// Nodes* neg_dest = canCollapse ? negative_dest : &negative_children;
592
// for (size_t i = 0; i < impl->size(); i++) {
593
// auto child = (*impl)[i];
594
// const bool negative = op == OpType::Subtract && i != 0;
595
// Nodes *dest1 = negative ? neg_dest : pos_dest;
596
// Nodes *dest2 = (op == OpType::Subtract && i == 0) ?
597
// neg_dest : nullptr;
598
// if (child->GetNodeType() == CsgNodeType::Leaf)
599
// dest1.push_back(child);
600
// else
601
// f(child, op, transform2, dest1, dest2);
602
// }
603
// if (canCollapse) return;
604
// if (node->op_ == OpType::Add)
605
// *impl = {BatchUnion(positive_children)};
606
// else if (node->op_ == OpType::Intersect)
607
// *impl = {BatchBoolean(Intersect, positive_children)};
608
// else // subtract
609
// *impl = { BatchUnion(positive_children) -
610
// BatchUnion(negative_children)};
611
// // node local transform
612
// node->cache_ = (*impl)[0].Transform(node.transform);
613
// // collapsed node transforms
614
// if (destination)
615
// destination->push_back(node->cache_->Transform(transform));
616
// }
617
while (!stack.empty()) {
618
std::shared_ptr<CsgStackFrame> frame = stack.back();
619
auto impl = frame->op_node->impl_.GetGuard();
620
if (frame->finalize) {
621
if (!frame->op_node->cache_) {
622
switch (frame->op_node->op_) {
623
case OpType::Add:
624
*impl = {BatchUnion(frame->positive_children)};
625
break;
626
case OpType::Intersect: {
627
*impl = {BatchBoolean(OpType::Intersect, frame->positive_children)};
628
break;
629
};
630
case OpType::Subtract:
631
if (frame->positive_children.empty()) {
632
// nothing to subtract from, so the result is empty.
633
*impl = {std::make_shared<CsgLeafNode>()};
634
} else {
635
auto positive = BatchUnion(frame->positive_children);
636
if (frame->negative_children.empty()) {
637
// nothing to subtract, result equal to the LHS.
638
*impl = {frame->positive_children[0]};
639
} else {
640
auto negative = BatchUnion(frame->negative_children);
641
*impl = {SimpleBoolean(*positive->GetImpl(),
642
*negative->GetImpl(), OpType::Subtract)};
643
}
644
}
645
break;
646
}
647
frame->op_node->cache_ = std::static_pointer_cast<CsgLeafNode>(
648
(*impl)[0]->Transform(frame->op_node->transform_));
649
}
650
if (frame->positive_dest != nullptr)
651
frame->positive_dest->push_back(std::static_pointer_cast<CsgLeafNode>(
652
frame->op_node->cache_->Transform(frame->transform)));
653
stack.pop_back();
654
} else {
655
auto add_children =
656
[&stack](std::shared_ptr<CsgNode>& node, OpType op, mat3x4 transform,
657
CsgStackFrame::Nodes* dest1, CsgStackFrame::Nodes* dest2) {
658
if (node->GetNodeType() == CsgNodeType::Leaf)
659
dest1->push_back(std::static_pointer_cast<CsgLeafNode>(
660
node->Transform(transform)));
661
else
662
stack.push_back(std::make_shared<CsgStackFrame>(
663
false, op, transform, dest1, dest2,
664
std::static_pointer_cast<const CsgOpNode>(node)));
665
};
666
// op_node use_count == 2 because it is both inside one CsgOpNode
667
// and in our stack.
668
// if there is only one child, we can also collapse.
669
const OpType op = frame->op_node->op_;
670
const bool canCollapse =
671
frame->positive_dest != nullptr &&
672
((op == frame->parent_op && frame->op_node.use_count() <= 2 &&
673
frame->op_node->impl_.UseCount() == 1) ||
674
impl->size() == 1);
675
if (canCollapse)
676
stack.pop_back();
677
else
678
frame->finalize = true;
679
680
const mat3x4 transform =
681
canCollapse ? (frame->transform * Mat4(frame->op_node->transform_))
682
: la::identity;
683
CsgStackFrame::Nodes* pos_dest =
684
canCollapse ? frame->positive_dest : &frame->positive_children;
685
CsgStackFrame::Nodes* neg_dest =
686
canCollapse ? frame->negative_dest : &frame->negative_children;
687
for (size_t i = 0; i < impl->size(); i++) {
688
const bool negative = op == OpType::Subtract && i != 0;
689
CsgStackFrame::Nodes* dest1 = negative ? neg_dest : pos_dest;
690
CsgStackFrame::Nodes* dest2 =
691
(op == OpType::Subtract && i == 0) ? neg_dest : nullptr;
692
add_children((*impl)[i], negative ? OpType::Add : op, transform, dest1,
693
dest2);
694
}
695
}
696
}
697
return cache_;
698
}
699
700
CsgNodeType CsgOpNode::GetNodeType() const {
701
switch (op_) {
702
case OpType::Add:
703
return CsgNodeType::Union;
704
case OpType::Subtract:
705
return CsgNodeType::Difference;
706
case OpType::Intersect:
707
return CsgNodeType::Intersection;
708
}
709
// unreachable...
710
return CsgNodeType::Leaf;
711
}
712
713
} // namespace manifold
714
715