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:
@@ -126,9 +126,9 @@ reciprocal.
|
|||||||
| `akgl_physics_simulate`, empty pool | frame | 63.9 | 15,650,101 |
|
| `akgl_physics_simulate`, empty pool | frame | 63.9 | 15,650,101 |
|
||||||
| logic frame: 64 updates + simulate | frame | 5,763.1 | 173,517 |
|
| logic frame: 64 updates + simulate | frame | 5,763.1 | 173,517 |
|
||||||
| `akgl_rectangle_points` | call | 4.0 | 248,412,026 |
|
| `akgl_rectangle_points` | call | 4.0 | 248,412,026 |
|
||||||
| `akgl_collide_rectangles`, overlapping | call | 24.9 | 40,181,108 |
|
| `akgl_collide_rectangles`, overlapping | call | 6.1 | 164,345,776 |
|
||||||
| `akgl_collide_rectangles`, disjoint | call | 57.9 | 17,261,906 |
|
| `akgl_collide_rectangles`, disjoint | call | 6.1 | 164,423,708 |
|
||||||
| all-pairs collision sweep, 64 actors (2016 pairs) | sweep | 115,023.6 | 8,694 |
|
| all-pairs collision sweep, 64 actors (2016 pairs) | sweep | 12,241.6 | 81,688 |
|
||||||
| `akgl_string_initialize` | call | 32.1 | 31,197,432 |
|
| `akgl_string_initialize` | call | 32.1 | 31,197,432 |
|
||||||
| `akgl_string_copy`, full length | call | 32.2 | 31,075,004 |
|
| `akgl_string_copy`, full length | call | 32.2 | 31,075,004 |
|
||||||
| `json_load_file`, small document | load | 11,338.8 | 88,193 |
|
| `json_load_file`, small document | load | 11,338.8 | 88,193 |
|
||||||
@@ -183,7 +183,7 @@ full screen of 16-pixel tiles and 64 actors:
|
|||||||
| Part of the frame | Cost | Share of 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% |
|
| 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% |
|
| Clear + present | 0.023 ms | 0.1% |
|
||||||
| 64 actor renders (48x48 blits) | 0.191 ms | 1.1% |
|
| 64 actor renders (48x48 blits) | 0.191 ms | 1.1% |
|
||||||
| 1200 tile blits | 16.26 ms | 97.6% |
|
| 1200 tile blits | 16.26 ms | 97.6% |
|
||||||
@@ -306,15 +306,25 @@ and not a string operation.
|
|||||||
|
|
||||||
### The collision helpers are fine; the missing broad phase is the problem
|
### The collision helpers are fine; the missing broad phase is the problem
|
||||||
|
|
||||||
`akgl_collide_rectangles` is 24.9 ns when the rectangles overlap (it returns at
|
`akgl_collide_rectangles` is 6.1 ns whether the rectangles overlap or not. It
|
||||||
the first corner that hits) and 57.9 ns when they do not (all eight corner tests
|
was 24.9 ns overlapping and 57.9 ns disjoint until 0.8.0, when it stopped being
|
||||||
run). Both are fine.
|
eight `akgl_collide_point_rectangle` calls and became four comparisons -- a
|
||||||
|
change made to fix the cross case it could not see, with the speed as a side
|
||||||
|
effect rather than the goal. The disjoint case improved most because it was the
|
||||||
|
one that ran all eight tests before answering.
|
||||||
|
|
||||||
|
**Read that 6.1 as a floor, not a measurement.** `akgl_rectangle_points` is
|
||||||
|
untouched by that change and reads 6.1 in the same run against the 4.0 recorded
|
||||||
|
above, so this run's resolution is around 6 ns and the collision test is now too
|
||||||
|
cheap to distinguish from a function that does almost nothing. The rows above are
|
||||||
|
not re-baselined for it; only the three rows the change actually moved are.
|
||||||
|
|
||||||
What the library does not provide is a broad phase, so a caller that wants
|
What the library does not provide is a broad phase, so a caller that wants
|
||||||
collision writes the all-pairs loop: 2016 pairs for 64 actors, 115 µs a frame,
|
collision writes the all-pairs loop: 2016 pairs for 64 actors, **12 µs** a frame,
|
||||||
0.7% of a 60 fps budget. That is affordable. It is also O(n²): raise
|
0.07% of a 60 fps budget. That is affordable. It is still O(n²): raise
|
||||||
`AKGL_MAX_HEAP_ACTOR` to 256 and the same loop is 32,640 pairs and 1.9 ms — over
|
`AKGL_MAX_HEAP_ACTOR` to 256 and the same loop is 32,640 pairs, which at this
|
||||||
10% of the frame, for a game that has done nothing yet.
|
per-pair cost is around 200 µs — 1.2% of the frame, for a game that has done
|
||||||
|
nothing yet.
|
||||||
|
|
||||||
### Spawning is cheap, and a third of it is a log line
|
### Spawning is cheap, and a third of it is a log line
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
| Part of the frame | Cost | Share of 16.67 ms |
|
||||||
|---|---:|---:|
|
|---|---:|---:|
|
||||||
| Logic: 64 actor updates + one physics sweep | 0.006 ms | 0.03% |
|
| 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% |
|
| Clear + present | 0.023 ms | 0.1% |
|
||||||
| 64 actor renders (48x48 blits) | 0.191 ms | 1.1% |
|
| 64 actor renders (48x48 blits) | 0.191 ms | 1.1% |
|
||||||
| 1200 tile blits | 16.26 ms | 97.6% |
|
| 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
|
**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 `<=`.
|
an edge and no area overlap. Both comparisons are `>=` and `<=`.
|
||||||
|
|
||||||
**Coordinates are truncated from `float` to `int` on the way in.** A rectangle at
|
**`akgl_rectangle_points` truncates from `float` to `int`.** A rectangle at `x = 10.9` has
|
||||||
`x = 10.9` has its corners at 10. That is right for tile-grid work and wrong for sub-pixel
|
its corners at 10. That is right for tile-grid work and wrong for sub-pixel work; if you
|
||||||
work; if you need the latter, do not round-trip through `akgl_rectangle_points`.
|
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
|
**`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.
|
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
|
`akgl_collide_rectangles` compares the two rectangles' spans on both axes. Until 0.8.0 it
|
||||||
against the other, eight tests, stopping at the first hit. Testing both directions is what
|
asked a different question — whether either rectangle enclosed one of the other's four
|
||||||
catches one rectangle wholly inside the other, which has no corner in its neighbour.
|
corners, eight tests, stopping at the first hit — and that question has the wrong answer for
|
||||||
|
one arrangement:
|
||||||
**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:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+---+ +---+
|
+---+ +---+
|
||||||
| | | |
|
| | | |
|
||||||
+---|---|---+ +---+---+---+ <- overlaps, but
|
+---|---|---+ +---+---+---+ <- overlaps, but
|
||||||
| | | | is | | | | no corner of
|
| | | | was | | | | no corner of
|
||||||
+---|---|---+ reported +---+---+---+ either is inside
|
+---|---|---+ reported +---+---+---+ either is inside
|
||||||
| | as | | the other
|
| | as | | the other
|
||||||
+---+ "no hit" +---+
|
+---+ "no hit" +---+
|
||||||
```
|
```
|
||||||
|
|
||||||
The fix is an edge-span comparison instead of a corner-containment one — `r1.x < r2.x + r2.w
|
A tall thin rectangle crossing a short wide one overlaps in a plus sign with no corner of
|
||||||
&& r2.x < r1.x + r1.w` on both axes — and it is not made here. If your game can produce that
|
either inside the other, so all eight tests said no — and a long thin platform crossing a
|
||||||
shape, and a long thin platform crossing a tall thin character is exactly that shape, test
|
tall thin character is exactly that shape. The header carried a `@note` describing it rather
|
||||||
for it yourself. Recorded as an `@note` on the function.
|
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.**
|
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
|
`akgl_physics_simulate` never calls `collide`, and `akgl_physics_arcade_collide` raises
|
||||||
|
|||||||
@@ -89,10 +89,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(akgl_Point *p, a
|
|||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
* @throws AKERR_NULLPOINTER If @p r1, @p r2, or @p collide is `NULL`.
|
* @throws AKERR_NULLPOINTER If @p r1, @p r2, or @p collide is `NULL`.
|
||||||
*
|
*
|
||||||
* @note A corner-containment test misses the one arrangement where two
|
* @note Fixed in 0.8.0. This was eight corner-containment tests, which answer a
|
||||||
* rectangles overlap in a cross without either enclosing a corner of the
|
* different question and get the cross wrong: a tall thin rectangle
|
||||||
* other -- a tall thin rectangle crossing a short wide one. Both are
|
* crossing a short wide one overlaps without either enclosing a corner of
|
||||||
* reported as not colliding.
|
* the other, and every corner test said no. It is a span comparison on
|
||||||
|
* both axes now. Two consequences for a caller upgrading: that
|
||||||
|
* arrangement starts reporting `true`, and the comparison is in `float`
|
||||||
|
* rather than through akgl_Point's `int` members, so an overlap smaller
|
||||||
|
* than one pixel is no longer truncated away.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide);
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide);
|
||||||
|
|
||||||
|
|||||||
65
src/util.c
65
src/util.c
@@ -163,56 +163,33 @@ akerr_ErrorContext *akgl_collide_point_rectangle(akgl_Point *p, akgl_RectanglePo
|
|||||||
|
|
||||||
akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide)
|
akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide)
|
||||||
{
|
{
|
||||||
akgl_RectanglePoints r1p;
|
|
||||||
akgl_RectanglePoints r2p;
|
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
|
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||||
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
|
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||||
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL collision flag reference");
|
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL collision flag reference");
|
||||||
|
|
||||||
ATTEMPT {
|
/*
|
||||||
CATCH(errctx, akgl_rectangle_points(&r1p, r1));
|
* Two rectangles overlap when their spans overlap on both axes. This used to
|
||||||
CATCH(errctx, akgl_rectangle_points(&r2p, r2));
|
* ask a different question -- whether either rectangle enclosed a corner of
|
||||||
|
* the other, eight akgl_collide_point_rectangle calls -- and that question
|
||||||
|
* has a different answer for a tall thin rectangle crossing a short wide
|
||||||
|
* one. They overlap in a plus sign, neither holds a corner of the other, and
|
||||||
|
* all eight tests said no. util.h carried a @note describing the arrangement
|
||||||
|
* rather than a fix.
|
||||||
|
*
|
||||||
|
* `<=` and not `<`, because akgl_collide_point_rectangle is inclusive on all
|
||||||
|
* four edges and this function has always agreed with it: two rectangles
|
||||||
|
* sharing exactly one edge collide.
|
||||||
|
*
|
||||||
|
* The comparison is in float now. The corner form went through akgl_Point,
|
||||||
|
* whose members are int, so a rectangle at x = 10.9 had its corner at 10 and
|
||||||
|
* a sub-pixel overlap was invisible.
|
||||||
|
*/
|
||||||
|
*collide = ( (r1->x <= (r2->x + r2->w)) &&
|
||||||
|
(r2->x <= (r1->x + r1->w)) &&
|
||||||
|
(r1->y <= (r2->y + r2->h)) &&
|
||||||
|
(r2->y <= (r1->y + r1->h)) );
|
||||||
|
|
||||||
// is the upper left corner of r1 contacting r2?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topleft, &r2p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the upper left corner of r2 contacting r1?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topleft, &r1p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the top right corner of r1 contacting r2?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topright, &r2p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the top right corner of r2 contacting r1?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topright, &r1p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the bottom left corner of r1 contacting r2?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomleft, &r2p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the bottom left corner of r2 contacting r1?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomleft, &r1p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the bottom right corner of r1 contacting r2?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomright, &r2p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
// is the bottom right corner of r2 contacting r1?
|
|
||||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomright, &r1p, collide));
|
|
||||||
if ( *collide == true ) { break; }
|
|
||||||
|
|
||||||
} CLEANUP {
|
|
||||||
} PROCESS(errctx) {
|
|
||||||
} FINISH(errctx, true);
|
|
||||||
|
|
||||||
// No trailing `*collide = false;` here. Each corner test writes the flag
|
|
||||||
// itself, so the eighth one leaves it false when nothing hit; assigning
|
|
||||||
// after FINISH would overwrite the hit that broke out of the block early.
|
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
tests/util.c
84
tests/util.c
@@ -247,6 +247,89 @@ akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The arrangements corner containment cannot see, and the ones it must keep.
|
||||||
|
*
|
||||||
|
* Eight corner-in-rectangle tests answer "do these overlap" correctly only when
|
||||||
|
* one rectangle encloses a corner of the other. A tall thin rectangle crossing a
|
||||||
|
* short wide one overlaps in a plus sign with no corner inside either, and eight
|
||||||
|
* corner tests all report false. That was documented on akgl_collide_rectangles
|
||||||
|
* as a known limitation rather than fixed, and there was no test for it.
|
||||||
|
*
|
||||||
|
* The three cases below the cross are the ones a rewrite can break while fixing
|
||||||
|
* it: touching edges must keep counting as a collision, because
|
||||||
|
* akgl_collide_point_rectangle is inclusive on all four edges and both headers
|
||||||
|
* promise it; full containment must keep working; and a separation of less than
|
||||||
|
* one pixel must be seen, which the old implementation could not do because it
|
||||||
|
* routed through akgl_Point and truncated float to int.
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext *test_akgl_collide_rectangles_arrangements(void)
|
||||||
|
{
|
||||||
|
SDL_FRect tall = { .x = 10.0f, .y = 0.0f, .w = 4.0f, .h = 40.0f };
|
||||||
|
SDL_FRect wide = { .x = 0.0f, .y = 10.0f, .w = 40.0f, .h = 4.0f };
|
||||||
|
SDL_FRect outer = { .x = 0.0f, .y = 0.0f, .w = 64.0f, .h = 64.0f };
|
||||||
|
SDL_FRect inner = { .x = 16.0f, .y = 16.0f, .w = 8.0f, .h = 8.0f };
|
||||||
|
SDL_FRect left = { .x = 0.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
||||||
|
SDL_FRect right = { .x = 10.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
||||||
|
SDL_FRect near1 = { .x = 0.0f, .y = 0.0f, .w = 10.9f, .h = 10.0f };
|
||||||
|
SDL_FRect near2 = { .x = 10.5f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
||||||
|
bool collide = false;
|
||||||
|
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
|
||||||
|
ATTEMPT {
|
||||||
|
// The cross. Neither rectangle encloses a corner of the other, and they
|
||||||
|
// plainly overlap in the middle.
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true),
|
||||||
|
"a tall rectangle crossing a wide one was reported as not colliding");
|
||||||
|
|
||||||
|
// The same pair the other way round. The test is symmetric and has to be.
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&wide, &tall, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true),
|
||||||
|
"the crossing pair was reported as not colliding with the arguments swapped");
|
||||||
|
|
||||||
|
// Full containment, both orders.
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&outer, &inner, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true), "a contained rectangle was missed");
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&inner, &outer, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true), "a containing rectangle was missed");
|
||||||
|
|
||||||
|
// A shared edge and nothing more. akgl_collide_point_rectangle is
|
||||||
|
// inclusive on all four edges, so touching counts; util.h and chapter 18
|
||||||
|
// both say so, and a span test written with < rather than <= silently
|
||||||
|
// changes that.
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&left, &right, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true),
|
||||||
|
"two rectangles sharing exactly one edge were reported as not colliding");
|
||||||
|
|
||||||
|
// Overlapping by four tenths of a pixel. The corner tests truncated
|
||||||
|
// float to int on the way in, so this read as a shared edge at 10.
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == true), "a sub-pixel overlap was missed");
|
||||||
|
|
||||||
|
// Separated by four tenths of a pixel. Truncation read this as touching.
|
||||||
|
near2.x = 11.3f;
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == false), "a sub-pixel gap was reported as a collision");
|
||||||
|
|
||||||
|
// Disjoint on one axis only, which is the case a span test gets wrong
|
||||||
|
// when it forgets to check both.
|
||||||
|
tall.x = 100.0f;
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == false),
|
||||||
|
"rectangles separated on x were reported as colliding");
|
||||||
|
tall.x = 10.0f;
|
||||||
|
tall.y = 100.0f;
|
||||||
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
||||||
|
TEST_ASSERT(errctx, (collide == false),
|
||||||
|
"rectangles separated on y were reported as colliding");
|
||||||
|
} CLEANUP {
|
||||||
|
} PROCESS(errctx) {
|
||||||
|
} FINISH(errctx, true);
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
||||||
{
|
{
|
||||||
SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
|
SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
|
||||||
@@ -465,6 +548,7 @@ int main(void)
|
|||||||
CATCH(errctx, test_akgl_collide_point_rectangle_logic());
|
CATCH(errctx, test_akgl_collide_point_rectangle_logic());
|
||||||
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
||||||
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
||||||
|
CATCH(errctx, test_akgl_collide_rectangles_arrangements());
|
||||||
CATCH(errctx, test_akgl_compare_sdl_surfaces_checks_geometry());
|
CATCH(errctx, test_akgl_compare_sdl_surfaces_checks_geometry());
|
||||||
CATCH(errctx, test_akgl_path_relative_releases_contexts());
|
CATCH(errctx, test_akgl_path_relative_releases_contexts());
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
|
|||||||
Reference in New Issue
Block a user