Skip to content

perf: Only test polygon edges inside the hitbox overlap and drop allocations from segment intersection - #4045

Merged
spydon merged 2 commits into
mainfrom
perf/narrow-phase-overlap
Sep 14, 2026
Merged

spydon merged 2 commits into
mainfrom
perf/narrow-phase-overlap

Conversation

@spydon

@spydon spydon commented Sep 13, 2026 •

Copy link
Copy Markdown
Member

Description

Two changes to the collision narrow phase that reduce the cost of polygon hitboxes, especially ones with many vertices.

Only test the edges inside the overlapping area. The polygon intersection systems already accept an overlappingRect and filter edges with it through possibleIntersectionVertices, but nothing ever passed one in, so every edge of one polygon was tested against every edge of the other. ShapeHitbox.intersections now computes the overlap of the two AABBs, inflated by the existing 0.01 tolerance so shapes that only touch still report their shared edge, and hands it to the intersection system. Any intersection point lies inside both AABBs, so edges that do not reach into the overlap cannot contribute one.

Allocation-free segment intersection with a bounding box early out. LineSegment.intersections built two Line objects, a result list, and several temporary vectors for every edge pair. It now rejects pairs whose bounding boxes do not overlap before doing any math and computes the same line intersection inline, allocating only when there is a hit. The parallel and collinear handling is unchanged.

Measured with a collision benchmark of 50 moving, rotating hitboxes of one kind against 50 of another, milliseconds per 20 ticks in JIT:

Scene Before After
10-vertex polygon vs circle 6.7 5.4
10-vertex polygon vs rectangle 6.2 5.1
10-vertex polygon vs 10-vertex polygon 10.3 5.9
37-vertex polygon vs circle 17.5 10.2
37-vertex polygon vs rectangle 12.4 6.8
37-vertex polygon vs 10-vertex polygon 16.0 5.9
37-vertex polygon vs 37-vertex polygon 92.0 11.4

The 37-vertex polygons come from sampling a Path contour, which is what the upcoming path hitbox work produces, but the change is independent of that. #4046 is stacked on this PR and continues from these numbers.

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.

…cations from segment intersection

ShapeHitbox.intersections now passes the overlap of the two AABBs to the intersection systems, which already knew how to filter edges by a rect but never received one. LineSegment.intersections rejects segment pairs whose bounding boxes do not overlap before doing any math and no longer allocates Line objects or result lists for misses.

@ufrshubham ufrshubham left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@spydon
spydon enabled auto-merge (squash) September 14, 2026 19:05
@spydon
spydon merged commit a9a3dd9 into main Sep 14, 2026
8 checks passed
@spydon
spydon deleted the perf/narrow-phase-overlap branch September 14, 2026 19:13
spydon added a commit that referenced this pull request Sep 28, 2026
… circle center out of the edge loop (#4046)

# Description

Follow-up to #4045, and stacked on it since the circle change touches
the code that PR added. Two more narrow phase costs that showed up once
the edge pair work was gone.

**Compose one transform for the global vertices.**
`PolygonComponent.globalVertices` called `absolutePositionOf` for every
vertex, which walks the ancestor chain and allocates a vector per level
per vertex, and then decided whether its cache was valid by comparing a
list of cloned vectors from three more ancestor walks. It now walks the
ancestors once, composes a single 2D affine transform, writes every
vertex into the preallocated list, and uses the six transform values
plus the size as the cache key. Whether the polygon is mirrored is read
from the determinant instead of the scale signs, which is the same
condition for scale and rotation transforms. One call for a 37-vertex
polygon under a rotating parent went from 1.24 to 0.19 microseconds.

**Hoist the circle center and radius out of the edge loop.**
`CirclePolygonIntersections` called `absoluteCenter` and `scaledRadius`
for every polygon edge, each walking the ancestors with allocations.
They are now computed once per pair and passed into
`lineSegmentIntersections` through new optional parameters, and an edge
whose bounding box lies farther than the radius from the center is
skipped before the quadratic solve.

Measured with the same benchmark as #4045, milliseconds per 20 ticks in
JIT with 50 hitboxes of each kind, starting from that PR:

| Scene | #4045 | Global vertices | Plus circle hoist |
|---|---|---|---|
| 10-vertex polygon vs circle | 5.4 | 4.7 | 3.4 |
| 10-vertex polygon vs rectangle | 5.1 | 3.0 | 2.9 |
| 10-vertex polygon vs 10-vertex polygon | 5.9 | 3.4 | 3.3 |
| 37-vertex polygon vs circle | 10.2 | 8.7 | 4.7 |
| 37-vertex polygon vs rectangle | 6.8 | 4.2 | 4.0 |
| 37-vertex polygon vs 10-vertex polygon | 5.9 | 3.5 | 3.4 |
| 37-vertex polygon vs 37-vertex polygon | 11.4 | 7.8 | 7.7 |

## Checklist

- [x] I have followed the [Contributor Guide] when preparing my PR.
- [x] 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.
- [x] No, this PR is not a breaking change.

<!-- Links -->
[Contributor Guide]:
https://github.com/flame-engine/flame/blob/main/CONTRIBUTING.md
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