Skip to content

Commit 772cd37

Browse files
authored
Simplify the CsgOpNodes as we build them, rather than in GetChildren (elalish#368)
* Flatten the CsgOpNodes as we build them, rather than in GetChildren * Don't inline reused nodes in CsgNode::Boolean * Reinstate difference -> union rewrite * No need for opportunistic flattening anymore * Fix typo * Remove CsgOpNode::Impl::simplified_ * Don't overskip referenced nodes in simplification * Avoid needless copy of shared pointers * Disambiguate CsgOpNode::Impl::flatten_ -> forcedToLeafNodes_ and align GetChildren arg naming * Simplified CsgOpNode::GetChildren * Simpler (a-(b+c)) -> (a-b-c) transform in CsgOpNode::Boolean to work better w/ CsgOpNode::ToLeafNode * Simpler build hints for large_scene_test.cpp * [flatten] Refine rules about tree flattening + added test case * [manifold] Propagate transforms when flattening op trees * [flatten] move tests from manifold_test to boolean_test
1 parent 88644f0 commit 772cd37

File tree

6 files changed

+188
-45
lines changed

6 files changed

+188
-45
lines changed

extras/CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@ project(extras)
1616

1717
add_executable(perfTest perf_test.cpp)
1818
target_link_libraries(perfTest manifold)
19-
2019
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
2120
target_compile_features(perfTest PUBLIC cxx_std_17)
2221

22+
add_executable(largeSceneTest large_scene_test.cpp)
23+
target_link_libraries(largeSceneTest manifold)
24+
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
25+
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
26+
2327
if(BUILD_TEST_CGAL)
2428
add_executable(perfTestCGAL perf_test_cgal.cpp)
2529
find_package(CGAL REQUIRED COMPONENTS Core)
2630
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
27-
2831
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
2932
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
30-
3133
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
3234
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
3335
endif()

extras/large_scene_test.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2020 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+
#include <chrono>
16+
#include <iostream>
17+
18+
#include "manifold.h"
19+
20+
using namespace manifold;
21+
22+
/*
23+
Build & execute with the following command:
24+
25+
( mkdir -p build && cd build && \
26+
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR=TBB .. && \
27+
make -j && \
28+
time ./extras/largeSceneTest 50 )
29+
*/
30+
int main(int argc, char **argv) {
31+
int n = 20;
32+
if (argc == 2) n = atoi(argv[1]);
33+
34+
std::cout << "n = " << n << std::endl;
35+
36+
auto start = std::chrono::high_resolution_clock::now();
37+
Manifold scene;
38+
39+
for (int i = 0; i < n; ++i) {
40+
for (int j = 0; j < n; ++j) {
41+
for (int k = 0; k < n; ++k) {
42+
if (i == 0 && j == 0 && k == 0) continue;
43+
44+
Manifold sphere = Manifold::Sphere(1).Translate(glm::vec3(i, j, k));
45+
scene = scene.Boolean(sphere, OpType::Add);
46+
}
47+
}
48+
}
49+
scene.NumTri();
50+
auto end = std::chrono::high_resolution_clock::now();
51+
std::chrono::duration<double> elapsed = end - start;
52+
std::cout << "nTri = " << scene.NumTri() << ", time = " << elapsed.count()
53+
<< " sec" << std::endl;
54+
}

src/manifold/src/csg_tree.cpp

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ struct CheckOverlap {
7373
} // namespace
7474
namespace manifold {
7575

76+
std::shared_ptr<CsgNode> CsgNode::Boolean(
77+
const std::shared_ptr<CsgNode> &second, OpType op) {
78+
if (auto opNode = std::dynamic_pointer_cast<CsgOpNode>(second)) {
79+
// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is
80+
// and the operation is commutative, we let it built the tree.
81+
if ((op == OpType::Add || op == OpType::Intersect)) {
82+
return opNode->Boolean(shared_from_this(), op);
83+
}
84+
}
85+
std::vector<std::shared_ptr<CsgNode>> children({shared_from_this(), second});
86+
return std::make_shared<CsgOpNode>(children, op);
87+
}
88+
7689
std::shared_ptr<CsgNode> CsgNode::Translate(const glm::vec3 &t) const {
7790
glm::mat4x3 transform(1.0f);
7891
transform[3] += t;
@@ -227,8 +240,6 @@ CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children,
227240
auto impl = impl_.GetGuard();
228241
impl->children_ = children;
229242
SetOp(op);
230-
// opportunistically flatten the tree without costly evaluation
231-
GetChildren(false);
232243
}
233244

234245
CsgOpNode::CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
@@ -237,8 +248,50 @@ CsgOpNode::CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
237248
auto impl = impl_.GetGuard();
238249
impl->children_ = children;
239250
SetOp(op);
240-
// opportunistically flatten the tree without costly evaluation
241-
GetChildren(false);
251+
}
252+
253+
std::shared_ptr<CsgNode> CsgOpNode::Boolean(
254+
const std::shared_ptr<CsgNode> &second, OpType op) {
255+
std::vector<std::shared_ptr<CsgNode>> children;
256+
257+
auto isReused = [](const auto &node) { return node->impl_.UseCount() > 1; };
258+
259+
auto copyChildren = [&](const auto &list, const glm::mat4x3 &transform) {
260+
for (const auto &child : list) {
261+
children.push_back(child->Transform(transform));
262+
}
263+
};
264+
265+
auto self = std::dynamic_pointer_cast<CsgOpNode>(shared_from_this());
266+
assert(self);
267+
if (IsOp(op) && !isReused(self)) {
268+
auto impl = impl_.GetGuard();
269+
copyChildren(impl->children_, transform_);
270+
} else {
271+
children.push_back(self);
272+
}
273+
274+
auto secondOp = std::dynamic_pointer_cast<CsgOpNode>(second);
275+
auto canInlineSecondOp = [&]() {
276+
switch (op) {
277+
case OpType::Add:
278+
case OpType::Intersect:
279+
return secondOp->IsOp(op);
280+
case OpType::Subtract:
281+
return secondOp->IsOp(OpType::Add);
282+
default:
283+
return false;
284+
}
285+
};
286+
287+
if (secondOp && canInlineSecondOp() && !isReused(secondOp)) {
288+
auto secondImpl = secondOp->impl_.GetGuard();
289+
copyChildren(secondImpl->children_, secondOp->transform_);
290+
} else {
291+
children.push_back(second);
292+
}
293+
294+
return std::make_shared<CsgOpNode>(children, op);
242295
}
243296

244297
std::shared_ptr<CsgNode> CsgOpNode::Transform(const glm::mat4x3 &m) const {
@@ -410,44 +463,24 @@ void CsgOpNode::BatchUnion() const {
410463

411464
/**
412465
* Flatten the children to a list of leaf nodes and return them.
413-
* If finalize is true, the list will be guaranteed to be a list of leaf nodes
414-
* (i.e. no ops). Otherwise, the list may contain ops.
415-
* Note that this function will not apply the transform to children, as they may
416-
* be shared with other nodes.
466+
* If forceToLeafNodes is true, the list will be guaranteed to be a list of leaf
467+
* nodes (i.e. no ops). Otherwise, the list may contain ops. Note that this
468+
* function will not apply the transform to children, as they may be shared with
469+
* other nodes.
417470
*/
418471
std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
419-
bool finalize) const {
472+
bool forceToLeafNodes) const {
420473
auto impl = impl_.GetGuard();
421-
auto &children_ = impl->children_;
422-
if (children_.empty() || (impl->simplified_ && !finalize) || impl->flattened_)
423-
return children_;
424-
impl->simplified_ = true;
425-
impl->flattened_ = finalize;
426-
std::vector<std::shared_ptr<CsgNode>> newChildren;
427-
428-
CsgNodeType op = op_;
429-
for (auto &child : children_) {
430-
if (child->GetNodeType() == op && child.use_count() == 1 &&
431-
std::dynamic_pointer_cast<CsgOpNode>(child)->impl_.UseCount() == 1) {
432-
auto grandchildren =
433-
std::dynamic_pointer_cast<CsgOpNode>(child)->GetChildren(finalize);
434-
int start = children_.size();
435-
for (auto &grandchild : grandchildren) {
436-
newChildren.push_back(grandchild->Transform(child->GetTransform()));
437-
}
438-
} else {
439-
if (!finalize || child->GetNodeType() == CsgNodeType::Leaf) {
440-
newChildren.push_back(child);
441-
} else {
442-
newChildren.push_back(child->ToLeafNode());
474+
475+
if (forceToLeafNodes && !impl->forcedToLeafNodes_) {
476+
impl->forcedToLeafNodes_ = true;
477+
for (auto &child : impl->children_) {
478+
if (child->GetNodeType() != CsgNodeType::Leaf) {
479+
child = child->ToLeafNode();
443480
}
444481
}
445-
// special handling for difference: we treat it as first - (second + third +
446-
// ...) so op = Union after the first node
447-
if (op == CsgNodeType::Difference) op = CsgNodeType::Union;
448482
}
449-
children_ = newChildren;
450-
return children_;
483+
return impl->children_;
451484
}
452485

453486
void CsgOpNode::SetOp(OpType op) {
@@ -464,6 +497,19 @@ void CsgOpNode::SetOp(OpType op) {
464497
}
465498
}
466499

500+
bool CsgOpNode::IsOp(OpType op) {
501+
switch (op) {
502+
case OpType::Add:
503+
return op_ == CsgNodeType::Union;
504+
case OpType::Subtract:
505+
return op_ == CsgNodeType::Difference;
506+
case OpType::Intersect:
507+
return op_ == CsgNodeType::Intersection;
508+
default:
509+
return false;
510+
}
511+
}
512+
467513
glm::mat4x3 CsgOpNode::GetTransform() const { return transform_; }
468514

469515
} // namespace manifold

src/manifold/src/csg_tree.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ enum class CsgNodeType { Union, Intersection, Difference, Leaf };
2222

2323
class CsgLeafNode;
2424

25-
class CsgNode {
25+
class CsgNode : public std::enable_shared_from_this<CsgNode> {
2626
public:
2727
virtual std::shared_ptr<CsgLeafNode> ToLeafNode() const = 0;
2828
virtual std::shared_ptr<CsgNode> Transform(const glm::mat4x3 &m) const = 0;
2929
virtual CsgNodeType GetNodeType() const = 0;
3030
virtual glm::mat4x3 GetTransform() const = 0;
3131

32+
virtual std::shared_ptr<CsgNode> Boolean(
33+
const std::shared_ptr<CsgNode> &second, OpType op);
34+
3235
std::shared_ptr<CsgNode> Translate(const glm::vec3 &t) const;
3336
std::shared_ptr<CsgNode> Scale(const glm::vec3 &s) const;
3437
std::shared_ptr<CsgNode> Rotate(float xDegrees = 0, float yDegrees = 0,
@@ -68,6 +71,9 @@ class CsgOpNode final : public CsgNode {
6871

6972
CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children, OpType op);
7073

74+
std::shared_ptr<CsgNode> Boolean(const std::shared_ptr<CsgNode> &second,
75+
OpType op) override;
76+
7177
std::shared_ptr<CsgNode> Transform(const glm::mat4x3 &m) const override;
7278

7379
std::shared_ptr<CsgLeafNode> ToLeafNode() const override;
@@ -79,8 +85,7 @@ class CsgOpNode final : public CsgNode {
7985
private:
8086
struct Impl {
8187
std::vector<std::shared_ptr<CsgNode>> children_;
82-
bool simplified_ = false;
83-
bool flattened_ = false;
88+
bool forcedToLeafNodes_ = false;
8489
};
8590
mutable ConcurrentSharedPtr<Impl> impl_ = ConcurrentSharedPtr<Impl>(Impl{});
8691
CsgNodeType op_;
@@ -89,6 +94,7 @@ class CsgOpNode final : public CsgNode {
8994
mutable std::shared_ptr<CsgLeafNode> cache_ = nullptr;
9095

9196
void SetOp(OpType);
97+
bool IsOp(OpType op);
9298

9399
static std::shared_ptr<Manifold::Impl> BatchBoolean(
94100
OpType operation,
@@ -97,7 +103,7 @@ class CsgOpNode final : public CsgNode {
97103
void BatchUnion() const;
98104

99105
std::vector<std::shared_ptr<CsgNode>> &GetChildren(
100-
bool finalize = true) const;
106+
bool forceToLeafNodes = true) const;
101107
};
102108

103109
} // namespace manifold

src/manifold/src/manifold.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,7 @@ Manifold Manifold::Refine(int n) const {
585585
* @param op The type of operation to perform.
586586
*/
587587
Manifold Manifold::Boolean(const Manifold& second, OpType op) const {
588-
std::vector<std::shared_ptr<CsgNode>> children({pNode_, second.pNode_});
589-
return Manifold(std::make_shared<CsgOpNode>(children, op));
588+
return Manifold(pNode_->Boolean(second.pNode_, op));
590589
}
591590

592591
Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,

test/boolean_test.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,39 @@ TEST(Boolean, UnionDifference) {
589589
float blocksize = block.GetProperties().volume;
590590
EXPECT_NEAR(resultsize, blocksize * 2, 0.0001);
591591
}
592+
593+
TEST(Boolean, BooleanVolumes) {
594+
glm::mat4 m = glm::translate(glm::mat4(1.0f), glm::vec3(1.0f));
595+
596+
// Define solids which volumes are easy to compute w/ bit arithmetics:
597+
// m1, m2, m4 are unique, non intersecting "bits" (of volume 1, 2, 4)
598+
// m3 = m1 + m2
599+
// m7 = m1 + m2 + m3
600+
auto m1 = Manifold::Cube({1, 1, 1});
601+
auto m2 = Manifold::Cube({2, 1, 1}).Transform(
602+
glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 0, 0)));
603+
auto m4 = Manifold::Cube({4, 1, 1}).Transform(
604+
glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 0, 0)));
605+
auto m3 = Manifold::Cube({3, 1, 1});
606+
auto m7 = Manifold::Cube({7, 1, 1});
607+
608+
EXPECT_FLOAT_EQ((m1 ^ m2).GetProperties().volume, 0);
609+
EXPECT_FLOAT_EQ((m1 + m2 + m4).GetProperties().volume, 7);
610+
EXPECT_FLOAT_EQ((m1 + m2 - m4).GetProperties().volume, 3);
611+
EXPECT_FLOAT_EQ((m1 + (m2 ^ m4)).GetProperties().volume, 1);
612+
EXPECT_FLOAT_EQ((m7 ^ m4).GetProperties().volume, 4);
613+
EXPECT_FLOAT_EQ((m7 ^ m3 ^ m1).GetProperties().volume, 1);
614+
EXPECT_FLOAT_EQ((m7 ^ (m1 + m2)).GetProperties().volume, 3);
615+
EXPECT_FLOAT_EQ((m7 - m4).GetProperties().volume, 3);
616+
EXPECT_FLOAT_EQ((m7 - m4 - m2).GetProperties().volume, 1);
617+
EXPECT_FLOAT_EQ((m7 - (m7 - m1)).GetProperties().volume, 1);
618+
EXPECT_FLOAT_EQ((m7 - (m1 + m2)).GetProperties().volume, 4);
619+
}
620+
621+
TEST(Boolean, TreeTransforms) {
622+
auto a = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}))
623+
.Translate({1, 0, 0});
624+
auto b = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}));
625+
626+
EXPECT_FLOAT_EQ((a + b).GetProperties().volume, 2);
627+
}

0 commit comments

Comments
 (0)