8000 kdtree (2D) for collider by pca006132 · Pull Request #1160 · elalish/manifold · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

kdtree (2D) for collider #1160

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

Merged
merged 8 commits into from
Feb 23, 2025
Merged

kdtree (2D) for collider #1160

merged 8 commits into from
Feb 23, 2025

Conversation

pca006132
Copy link
Collaborator

Simple kdtree for collider. This reduces memory consumption (for performance) as well as speed up triangulation a bit. The implementation is very simple for now. Naming is subject to changes, I don't know what is a good name for this.

On my computer, the 104 polygon tests took 1355ms instead of 1554ms previously.

This is doing a lot of sorting, but from my testing building the index takes a negligible amount of time in our triangulator, so it should be fine. I thought about using quadtree, but it feels like kdtree can be more balanced. I also tried making a separate vector for the split coordinates for less cache access. Not helpful either, probably because our polygons are usually quite small.

Multiway kdtree can improve performance slightly but not by much, and the code is considerably more complex, so I don't think it is a good idea to use that. In case you are interested:

template <typename P, bool sortX = true>
void BuildTwoDTreeImpl(VecView<P> points) {
  if (points.size() <= 8) return;
  std::sort(points.begin(), points.end(), [](const P& a, const P& b) {
    return sortX ? a.pt.x < b.pt.x : a.pt.y < b.pt.y;
  });
  size_t mid = points.size() / 2;
  size_t low = mid / 2;
  size_t high = (points.size() - mid) / 2 + mid;
  const P a = points[low];
  const P b = points[mid];
  const P c = points[high];

  std::move_backward(points.begin() + mid + 1, points.begin() + high,
                     points.begin() + high + 1);
  std::move_backward(points.begin() + low + 1, points.begin() + mid,
                     points.begin() + mid + 2);
  std::move_backward(points.begin(), points.begin() + low,
                     points.begin() + low + 3);
  points[0] = a;
  points[1] = b;
  points[2] = c;

  BuildTwoDTreeImpl<P, !sortX>(points.view(high + 1));
  BuildTwoDTreeImpl<P, !sortX>(points.view(mid + 2, high - mid - 1));
  BuildTwoDTreeImpl<P, !sortX>(points.view(low + 3, mid - low - 1));
  BuildTwoDTreeImpl<P, !sortX>(points.view(3, low));
}

template <typename P>
void BuildTwoDTree(VecView<P> points) {
  ZoneScoped;
  BuildTwoDTreeImpl<P, true>(points);
}

template <typename P, typename F>
void QueryDTreeImpl(VecView<P> points, Rect r, Rect parent, F f, bool sortX) {
  if (points.size() <= 8) {
    for (const auto& p : points)
      if (r.Contains(p.pt)) f(p);
    return;
  }
  // first three points are the split points
  for (const auto& p : points.view(0, 3))
    if (r.Contains(p.pt)) f(p);

  auto coord = [sortX](vec2& v) -> double* { return sortX ? &v.x : &v.y; };
  size_t mid = points.size() / 2;
  size_t low = mid / 2;
  size_t high = (points.size() - mid) / 2 + mid;

  Rect current = parent;
  *coord(current.max) = *coord(points[0].pt);
  if (r.DoesOverlap(current)) {
    QueryDTreeImpl(points.view(3, low), r, current, f, !sortX);
  }
  *coord(current.min) = *coord(points[0].pt);
  *coord(current.max) = *coord(points[1].pt);
  if (r.DoesOverlap(current)) {
    QueryDTreeImpl(points.view(low + 3, mid - low - 1), r, current, f,
                     !sortX);
  }
  *coord(current.min) = *coord(points[1].pt);
  *coord(current.max) = *coord(points[2].pt);
  if (r.DoesOverlap(current)) {
    QueryDTreeImpl(points.view(mid + 2, high - mid - 1), r, current, f,
                     !sortX);
  }
  *coord(current.min) = *coord(points[2].pt);
  *coord(current.max) = *coord(parent.max);
  if (r.DoesOverlap(current)) {
    QueryDTreeImpl(points.view(high + 1), r, current, f, !sortX);
  }
}

template <typename P, typename F>
void QueryDTree(VecView<P> points, Rect r, F f) {
  ZoneScoped;
  QueryDTreeImpl(points, r,
                   Rect(vec2(-std::numeric_limits<double>::infinity()),
                        vec2(std::numeric_limits<double>::infinity())),
                   f, true);
}

@pca006132
Copy link
Collaborator Author

we should probably mark nodes as removed while ear clipping.

src/twodtree.h Outdated

namespace manifold {

template <typename P, bool sortX = true>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a wild guess that the next version of this will be 3D, splitting X, Y, Z, X, Y, Z. Maybe bool sortX should be int sortAxis?

src/twodtree.h Outdated
}

template <typename P, typename F>
void QueryTwoDTree(VecView<P> points, Rect r, F f) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if this is faster than the collider for 2D, do you think a version of it will also be faster for 3D? It would be nice to have something universal. Or failing that, to at least have them use the same kind of API so it's easier to compare them? Is that reasonable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think a bit about this.

@pca006132
Copy link
Collaborator Author

For some reason, I can't reproduce the CI failure locally. The exact polygon did not produce invalid triangulation here. Not sure if this is something caused by the unstable sort, so I am switching over to the stable sort we have and that should make it deterministic.

@pca006132
Copy link
Collaborator Author

OK, it seems that we do need to expand the bounding box by epsilon.

Copy link
codecov bot commented Feb 22, 2025

Codecov Report

Attention: Patch coverage is 96.29630% with 2 lines in your changes missing coverage. Please review.

Project coverage is 91.70%. Comparing base (f178cd1) to head (fc20d1f).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/tree2d.cpp 83.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1160      +/-   ##
==========================================
+ Coverage   91.69%   91.70%   +0.01%     
==========================================
  Files          30       32       +2     
  Lines        5959     5980      +21     
==========================================
+ Hits         5464     5484      +20     
- Misses        495      496       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

vec2(center.x + radius, center.y + radius));
earBox.Union(pos);
earBox.min -= epsilon;
earBox.max += epsilon;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call - surprised this wasn't a problem before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need it previously, just that I forgot to add it back. Previously, we enlarge the bbox of each vertex, but now we enlarge the bbox of the query.

}
return cost;
}
return std::numeric_limits<double>::lowest();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this case handled inside the query function now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for lowest, it just means it will not change the cost (because we take the max).

@pca006132 pca006132 merged commit 06b10e7 into master Feb 23, 2025
27 checks passed
@pca006132 pca006132 deleted the kdtree branch February 23, 2025 06:59
@elalish elalish mentioned this pull request May 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0