-
Notifications
You must be signed in to change notification settings - Fork 101
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
avoid recursion in csg tree #1086
base: master
Are you sure you want to change the base?
Conversation
src/csg_tree.cpp
Outdated
->GetImpl()); | ||
} else { | ||
std::vector<std::shared_ptr<CsgLeafNode>> tmp; | ||
for (size_t j : set) { | ||
tmp.push_back( | ||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j])); | ||
std::dynamic_pointer_cast<CsgLeafNode>(children[start + j])); |
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.
why is this cast dynamic while the one above is static?
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.
ah, I should change these to static.
src/csg_tree.cpp
Outdated
bool finalize; | ||
OpType parent_op; | ||
mat3x4 transform; | ||
std::vector<std::shared_ptr<CsgLeafNode>> *parent_children; |
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.
what's the relationship between parent_children
and left_children
and right_children
? And why is just the parent a pointer?
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.
parent_children
is the list of children of the parent (or ancestor), while left_children
and right_children
are the sets of children of the current node.
Will explain it better with some comment.
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.
I think I might call parent_children
siblings
, if I'm understanding you correctly?
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.
Probably, the issue is that it is not exactly sibling as they may be in different levels... I need to think about a better naming.
op_node(op_node) {} | ||
}; | ||
|
||
std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const { |
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.
I never fully understood what the previous version of this algorithm did either - perhaps some comments on the general steps this is recursing through?
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! Looks like a good direction, though a bit out of my wheelhouse. I'll be curious to understand where the bug is...
CondensedMatter16 hits #970 after this PR. I guess this is due to the code now performs optimization more aggressively, and there are more things that got feed into BatchUnion... |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1086 +/- ##
==========================================
- Coverage 91.72% 91.65% -0.07%
==========================================
Files 30 30
Lines 5910 5897 -13
==========================================
- Hits 5421 5405 -16
- Misses 489 492 +3 ☔ View full report in Codecov by Sentry. |
This slows down the overall test slightly, from 26180ms to 26877ms, ~2.5%. This is due to CondensedMatter64 being slower, which is due to If I tune the BatchUnion threshold from 1000 to 100, it is now taking 25641ms, ~2% faster. Not sure if this is general enough though. |
Will add documentation tmr, a bit tired today. |
Anyway, decided to write the documentation now :) About the original bug: The idea is that the flattening takes O(n) time in the old implementation, which makes it problematic because in the worst case we can have O(n) flattening operations as well, which gives us O(n^2) time. As an example, consider the following:
The implementation here doesn't attempt to do flattening when we construct the node. In fact, it is impossible to avoid this quadratic behavior if we perform (all) flattening when we construct the nodes, because there will be O(n^2) nodes if we do that. The algorithm only does one pass over the tree, and do flattening on-the-fly. Each node is moved only once. This makes it O(n). |
// `Subtract` is handled differently from `Add` and `Intersect`. It is treated | ||
// as two `Add` nodes, `left_children` and `right_children`, that should be | ||
// subtracted later. This allows flattening children `Add` nodes. For normal | ||
// `Add` and `Intersect`, we only use `left_children`. |
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.
Ah! Could these be named positive_children
and negative_children
? Or children
and inverted_children
?
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.
} | ||
auto impl = frame->op_node->impl_.GetGuard(); | ||
if (frame->finalize) { | ||
if (frame->op_node->cache_ == nullptr) switch (frame->op_node->op_) { |
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.
can cache_
be anything but nullptr
here?
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, I forgot to remove this check after adding the check above.
std::vector<std::shared_ptr<const Manifold::Impl>> impls; | ||
for (auto &child : frame->left_children) | ||
impls.push_back(child->GetImpl()); | ||
auto result = BatchBoolean(OpType::Intersect, impls); |
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.
Why is batch intersection so different than batch union?
node->Transform(transform))); | ||
else | ||
stack.push_back(std::make_shared<CsgStackFrame>( | ||
false, op, transform, children, |
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.
I don't quite follow this logic: either we add the node to the set of children, or we add a frame to the stack that contains this node and all the children? Do the children belong to this node or not (when the function is called)?
frame->op_node->cache_->Transform(frame->transform))); | ||
stack.pop_back(); | ||
} else { | ||
auto add_children = [&](std::shared_ptr<CsgNode> &node, OpType op, |
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.
I prefer to be explicit about what's captured in a lambda - it helps me to understand the scope of the function.
frame->op_node.use_count() <= 2 && | ||
frame->op_node->impl_.UseCount() == 1; | ||
if (skipFinalize) stack.pop_back(); | ||
auto transform = |
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.
const? And for short types, I prefer to avoid auto
.
frame->op_node->op_ == frame->parent_op && | ||
frame->op_node.use_count() <= 2 && | ||
frame->op_node->impl_.UseCount() == 1; | ||
if (skipFinalize) stack.pop_back(); |
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.
We just set frame->finalize = true;
above - is that still true? Or does this skipping refer to its child or 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.
The frame is removed here, so we will skip the current frame. Anyway, I can make it explicit.
Fixes #989.
This refactors the code into a single loop that does dfs on the csg tree, and perform flattening on-the-fly.
There are still some failures, I guess this is due to shared nodes being evaluated in a weird way... Will try to fix it soon. But for now I think the logic is mostly there and should be ready for review.
For comparison: