Skip to content
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

fix: Updated PolygonComponent.containsPoint to account for concave polygons #2979

Merged
merged 4 commits into from Jan 21, 2024

Conversation

davidjan3
Copy link
Contributor

@davidjan3 davidjan3 commented Jan 13, 2024

Description

Previously, the PolygonComponent.containsPoint() and .containsLocalPoint() functions consisted of duplicate code that checked whether a given point lies within a convex polygon. They didn't function properly with concave polygons.

I created a new _containsPoint() function that is called from both functions to reduce redundancies. This new function uses a different approach to figure out whether a point lies within a polygon, which should also work for concave polygons, or even polygons with holes. The algorithm is vaguely explained within code comments, and is visualized in this post: https://stackoverflow.com/a/218081/5008997

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

@davidjan3 davidjan3 changed the title fix: updated PolygonComponent.containsPoint to account for concave polygons fix: Updated PolygonComponent.containsPoint to account for concave polygons Jan 13, 2024
Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

Looks good after a quick look, how's the performance impact?

@davidjan3
Copy link
Contributor Author

Just stress-tested the performance within my current project.
Calling containsPoint(vertex) for 4 vertices on 10,000 randomly generated polygons with 10-24 vertices each:
Old method avg: ~370ms
New method avg: ~530ms

So it takes about 43% longer on average.

However, by replacing

final edge = getEdge(i, vertices: vertices);

with

final from = vertices[i];
final to = vertices[(i + 1) % vertices.length];

It goes down to ~350ms and still survives every test.
Though this also speeds up the old version to ~320ms.

It's important to note that in my test, the old function returns false almost all the time. And looking at the code, returning false can happen much faster than checking all edges before returning true.

@spydon
Copy link
Member

spydon commented Jan 15, 2024

@davidjan3 can you try with a bit more vertices, like 10?

@davidjan3
Copy link
Contributor Author

All polygons had 10-24 vertices. My test just ran the .containsPoint(vertex) method 4 times per polygon, to see if all 4 corners of a rectangle lie within it. I can share my code sample after work.

@davidjan3
Copy link
Contributor Author

davidjan3 commented Jan 15, 2024

@spydon Code sample (useless of course, just to serve performance testing):

final stopwatch = Stopwatch()..start();
for (Vector2 vertex in rect.globalVertices()) {
  for (PolygonComponent polygon in polygons) {
    // polygon is a random convex or concave polygon with between 10 and 24 vertices
    collided = polygon.containsPoint(vertex);
  }
}
log("Collision check took ${stopwatch.elapsedMilliseconds} ms");

Ran this in a loop and it kept averaging around the numbers stated in earlier comments.

If you like, I can push the version where getEdge() is substituted for performance gains as described above.
Otherwise I'd just add a couple tests and hope for approval.

@spydon
Copy link
Member

spydon commented Jan 15, 2024

Otherwise I'd just add a couple tests and hope for approval.

Sounds good, seems like a reasonable performance trade-off.

@davidjan3
Copy link
Contributor Author

davidjan3 commented Jan 15, 2024

@spydon So would you prefer keeping the getEdge() function for consistency, or should I push this?:

However, by replacing

final edge = getEdge(i, vertices: vertices);

with

final from = vertices[i];
final to = vertices[(i + 1) % vertices.length];

It goes down to ~350ms and still survives every test.

That way its worst case would outperform the old implementation's average, so no performance trade-off :)

@spydon
Copy link
Member

spydon commented Jan 16, 2024

@davidjan3 sounds like it's definitely worth changing it then! :)

@davidjan3
Copy link
Contributor Author

@spydon Added said optimization, and a little test :)

Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

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

Lgtm! Sorry for the delay, I've been away skiing in the alps the last week 😅

@spydon spydon enabled auto-merge (squash) January 21, 2024 10:50
@spydon spydon merged commit a6fe62a into flame-engine:main Jan 21, 2024
8 checks passed
@davidjan3 davidjan3 deleted the polygon-containspoint branch March 25, 2024 19:01
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.

None yet

2 participants