-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify the CsgOpNodes as we build them, rather than in GetChildren #368
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I thought that I have already fixed this issue previously but it seems that it is not.
@@ -22,13 +22,16 @@ enum class CsgNodeType { Union, Intersection, Difference, Leaf }; | |||
|
|||
class CsgLeafNode; | |||
|
|||
class CsgNode { | |||
class CsgNode : public std::enable_shared_from_this<CsgNode> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I never know such a thing exists, I wanted to do something similar and have to do it the dumb way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's a nice get out of jail free card 🤣
src/manifold/src/csg_tree.cpp
Outdated
|
||
auto handleOperand = [&](const std::shared_ptr<CsgNode> &operand) { | ||
if (auto opNode = std::dynamic_pointer_cast<CsgOpNode>(operand)) { | ||
if (opNode->IsOp(op) && opNode.use_count() == 1 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can also have some sort of max recursion level parameter, and force flattening the tree when the recursion level is reached, regardless of whether or not the child is shared? So we can avoid stack overflow in any situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So tbh I'm not quite sure in which circumstances the nodes and/or their impl are shared. I've relaxed the test anyway as I didn't realize the operand.use_count()==1
broke the intended fix (there's copies of shared pointers around, and not just because I mistakenly passed it by value in that method - now fixed). Checking that count seems a bit brittle.
I've left the other check (on impl.UseCount()
) to keep some mysterious tests happy, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re/ your suggestion to force-flatten in extreme sharing cases, maybe we could explore as follow up? Not sure if geometry from OpenSCAD would trigger that case though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I don't think it will happen normally.
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## master #368 +/- ##
==========================================
- Coverage 89.39% 85.48% -3.92%
==========================================
Files 33 36 +3
Lines 3979 4409 +430
==========================================
+ Hits 3557 3769 +212
- Misses 422 640 +218
... and 16 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pca006132, you know this code better than I, so I'll leave approval to you.
extras/large_scene_test.cpp
Outdated
time ./extras/largeSceneTest 50 ) | ||
*/ | ||
int main(int argc, char **argv) { | ||
int n = 20; // Crashes at n=50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it still crash at n=50 with this change, or was that only before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was only before, edited this out, thanks!
@@ -419,34 +457,16 @@ std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren( | |||
bool finalize) const { | |||
auto impl = impl_.GetGuard(); | |||
auto &children_ = impl->children_; | |||
if (children_.empty() || (impl->simplified_ && !finalize) || impl->flattened_) | |||
return children_; | |||
impl->simplified_ = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to see this simplification, but what was the difference between simplified
and flattened
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simplified
did not perform flattening if the nodes are shared, iirc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flattening (also aliased here to finalization) was forcing leaf nodes, while simplification was flattening 1 level of op nodes nesting (adopting the children of same op children, and in the case of difference, of union op children past the first child).
Have now renamed finalize & flattened_ to force[d]ToLeafNodes[_] to disambiguate, and simplified this method further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thank you!
This looks fine for me, but can you do a simple benchmark comparing with the current master? Wondering if there is any performance improvement/degradation for our test cases ( |
…n GetChildren arg naming
…better w/ CsgOpNode::ToLeafNode
src/manifold/src/csg_tree.cpp
Outdated
auto handleOperand = [&](const std::shared_ptr<CsgNode> &operand) { | ||
if (auto opNode = std::dynamic_pointer_cast<CsgOpNode>(operand)) { | ||
if ((opNode->IsOp(op) || | ||
(op == OpType::Subtract && opNode->IsOp(OpType::Add))) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now doing (a-(b+c)
-> (a-b-c)
here to play better w/ CsgOpNode::ToLeafNode (which turns it back to (a-batch(b+c))
@pca006132 performance looks identical for these tests and examples, here's the script I used to benchmark (uses hyperfine) |
I was testing this branch and I found it breaks my existing python code. The output stl is different from the current master. I will try to make a minimal example for it. |
auto a = Manifold::Cylinder(10, 10);
auto b = Manifold::Cube({10, 10, 10});
auto c = Manifold::Cylinder(1, 1);
float volume1 = (a - c).GetProperties().volume;
float volume2 = (a + b - c).GetProperties().volume;
EXPECT_GT(volume2, volume1); The assertion failed:
while this passed with the current master:
|
@pca006132 thanks for the repro! Pushed a fix and some tests based on your snippet |
Interestingly it still break my other code... will try to reproduce it later today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minimal example that you can add to the test case:
auto a = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1})).Translate({1, 0, 0});
auto b = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}));
EXPECT_FLOAT_EQ((a + b).GetProperties().volume, 2);
Currently this evaluates to 1. Note that if you write it as
EXPECT_FLOAT_EQ(((Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1})).Translate({1, 0, 0}) + (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}))).GetProperties().volume, 2);
it will not trigger the bug, as the use count is somehow set to 2, perhaps due to how C++ deal with destructors.
src/manifold/src/csg_tree.cpp
Outdated
|
||
if (IsOp(op) && (impl_.UseCount() == 1)) { | ||
auto impl = impl_.GetGuard(); | ||
std::copy(impl->children_.begin(), impl->children_.end(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This copy doesn't apply transform from the parent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hadn't realized the transform was around, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job working together on this!
test/manifold_test.cpp
Outdated
// Define solids which volumes are easy to compute w/ bit arithmetics: | ||
// m1, m2, m4 are unique, non intersecting "bits" (of volume 1, 2, 4) | ||
// m3 = m1 + m2 | ||
// m7 = m1 + m2 + m3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent tests, thank you! Technically these should go in boolean_test.cpp
now that I've separated them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!! Moved them 👌
Thanks a lot guys, appreciate the help & quick turnaround! (aaaaand the amazing library of course!!) |
…lalish#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
Early simplification (while building boolean op nodes) makes it easier to flatten the tree and avoids... stack overflows in GetChildren.
large_scene_test.cpp in this PR will just segfault without the flattening changes (and could potentially form the basis for some large scene benchmark so I thought it worth to check it in).
Found this crash while testing openscad/openscad#4533 (segfaulted on issue2342.scad)