Report the overlap akgl_collide_rectangles could not see
It asked whether either rectangle enclosed one of the other's four corners -- eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a different question from "do these overlap", and it has the wrong answer for one arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus sign with no corner of either inside the other, and all eight tests said no. A long thin platform crossing a tall thin character is exactly that shape, so this is a shape a 2D game produces, not a curiosity. util.h carried an @note describing it and docs/18-utilities.md had a diagram of it, both under the heading of a limitation rather than a defect, and there was no test for it at all -- nor for full containment, nor for a shared edge. It is four comparisons now, on both axes. `<=` rather than `<` because akgl_collide_point_rectangle is inclusive on all four edges and these two have always agreed that touching counts; a span test written with `<` would have silently changed a contract both the header and the manual state. The test was written first and failed on the cross before the fix went in. Two answers change for a caller upgrading, and both are in the header note and the chapter: - The cross reports `true`, which is the point. - The comparison is in float rather than through akgl_Point's int members, so a sub-pixel overlap is no longer truncated away. A pickup test that was accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials use this for coins and hazards; both still pass. Faster as a side effect rather than a goal, and worth recording because the numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1 disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint case gained most because it was the one that ran all eight tests before answering. The three moved rows are re-recorded in PERFORMANCE.md and nothing else is. akgl_rectangle_points is untouched by this change and reads 6.1 in the same run against the 4.0 recorded, so 6 ns is this run's floor and the new figure means "too cheap to measure" rather than "exactly 6.1" -- said in the prose so the next reader does not re-baseline the table around it. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ software-rasterized. At 60 fps a frame is 16.67 ms:
|
||||
| Part of the frame | Cost | Share of 16.67 ms |
|
||||
|---|---:|---:|
|
||||
| Logic: 64 actor updates + one physics sweep | 0.006 ms | 0.03% |
|
||||
| All-pairs collision over 64 actors, if you do it | 0.115 ms | 0.7% |
|
||||
| All-pairs collision over 64 actors, if you do it | 0.012 ms | 0.07% |
|
||||
| Clear + present | 0.023 ms | 0.1% |
|
||||
| 64 actor renders (48x48 blits) | 0.191 ms | 1.1% |
|
||||
| 1200 tile blits | 16.26 ms | 97.6% |
|
||||
|
||||
@@ -53,36 +53,44 @@ drawn with a non-zero `angle` still collides as its unrotated box.
|
||||
**Edges count as touching.** A point exactly on a boundary is inside; two rectangles sharing
|
||||
an edge and no area overlap. Both comparisons are `>=` and `<=`.
|
||||
|
||||
**Coordinates are truncated from `float` to `int` on the way in.** A rectangle at
|
||||
`x = 10.9` has its corners at 10. That is right for tile-grid work and wrong for sub-pixel
|
||||
work; if you need the latter, do not round-trip through `akgl_rectangle_points`.
|
||||
**`akgl_rectangle_points` truncates from `float` to `int`.** A rectangle at `x = 10.9` has
|
||||
its corners at 10. That is right for tile-grid work and wrong for sub-pixel work; if you
|
||||
need the latter, do not round-trip through it. **`akgl_collide_rectangles` no longer does**
|
||||
— it compares the spans in `float` directly, so a sub-pixel overlap registers.
|
||||
|
||||
**`z` is carried and never used.** `akgl_Point` has one, `akgl_rectangle_points` never
|
||||
writes it, and neither collision test reads it. These are 2D tests.
|
||||
|
||||
### The arrangement it misses
|
||||
### The arrangement it used to miss
|
||||
|
||||
`akgl_collide_rectangles` works corner by corner: it tests each rectangle's four corners
|
||||
against the other, eight tests, stopping at the first hit. Testing both directions is what
|
||||
catches one rectangle wholly inside the other, which has no corner in its neighbour.
|
||||
|
||||
**It still misses the cross.** A tall thin rectangle crossing a short wide one overlaps
|
||||
without either enclosing a corner of the other, and both are reported as not colliding:
|
||||
`akgl_collide_rectangles` compares the two rectangles' spans on both axes. Until 0.8.0 it
|
||||
asked a different question — whether either rectangle enclosed one of the other's four
|
||||
corners, eight tests, stopping at the first hit — and that question has the wrong answer for
|
||||
one arrangement:
|
||||
|
||||
```text
|
||||
+---+ +---+
|
||||
| | | |
|
||||
+---|---|---+ +---+---+---+ <- overlaps, but
|
||||
| | | | is | | | | no corner of
|
||||
| | | | was | | | | no corner of
|
||||
+---|---|---+ reported +---+---+---+ either is inside
|
||||
| | as | | the other
|
||||
+---+ "no hit" +---+
|
||||
```
|
||||
|
||||
The fix is an edge-span comparison instead of a corner-containment one — `r1.x < r2.x + r2.w
|
||||
&& r2.x < r1.x + r1.w` on both axes — and it is not made here. If your game can produce that
|
||||
shape, and a long thin platform crossing a tall thin character is exactly that shape, test
|
||||
for it yourself. Recorded as an `@note` on the function.
|
||||
A tall thin rectangle crossing a short wide one overlaps in a plus sign with no corner of
|
||||
either inside the other, so all eight tests said no — and a long thin platform crossing a
|
||||
tall thin character is exactly that shape. The header carried a `@note` describing it rather
|
||||
than a fix.
|
||||
|
||||
It is four comparisons now, `r1.x <= r2.x + r2.w && r2.x <= r1.x + r1.w` on both axes.
|
||||
`<=` rather than `<` because [`akgl_collide_point_rectangle`](#collision-helpers) is
|
||||
inclusive on all four edges and these two have always agreed: touching counts.
|
||||
|
||||
**If you are upgrading from 0.7.x, two answers change.** The cross now reports `true`, which
|
||||
is the point. And because the comparison no longer round-trips through `akgl_Point`'s `int`
|
||||
members, overlaps and gaps smaller than a pixel are now seen rather than truncated away — so
|
||||
a pickup test that was accidentally forgiving by up to a pixel is no longer forgiving.
|
||||
|
||||
This is separate from the fact that **nothing in the library calls these during a frame.**
|
||||
`akgl_physics_simulate` never calls `collide`, and `akgl_physics_arcade_collide` raises
|
||||
|
||||
Reference in New Issue
Block a user