From b3fdd9d1467e168ba1bdab1563f97ad2e04c27bd Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Thu, 25 Jul 2024 08:45:03 -0500 Subject: [PATCH] fix: rayCast filter called in hit order --- CHANGELOG.md | 1 + .../Collision/Detection/RayCastOptions.ts | 4 ++- .../SparseHashGridCollisionProcessor.ts | 34 ++++++++++++------- .../SparseHashGridCollisionProcessorSpec.ts | 27 +++++++++++++++ 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9abdd4ea6..e270a1e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ are doing mtv adjustments during precollision. ### Fixed +- Fixed issue where rayCast `filter` would not be called in hit order - Fixed issue where rayCasts would return inconsistent orderings with the `ex.SparseHashGridCollisionProcessor` strategy - Fixed issue where CircleCollider tangent raycast did not work correctly - Fixed issue where you were required to provide a transition if you provided a loader in the `ex.Engine.start('scene', { loader })` diff --git a/src/engine/Collision/Detection/RayCastOptions.ts b/src/engine/Collision/Detection/RayCastOptions.ts index 3898d1412..668059e7d 100644 --- a/src/engine/Collision/Detection/RayCastOptions.ts +++ b/src/engine/Collision/Detection/RayCastOptions.ts @@ -15,7 +15,9 @@ export interface RayCastOptions { */ collisionMask?: number; /** - * Optionally specify to search for all colliders that intersect the ray cast, not just the first which is the default + * Optionally search for all colliders that intersect the ray cast. + * + * Default false */ searchAllColliders?: boolean; /** diff --git a/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts b/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts index 4c477e286..c4602c624 100644 --- a/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts +++ b/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts @@ -179,6 +179,7 @@ export class SparseHashGridCollisionProcessor implements CollisionProcessor { const key = HashGridCell.calculateHashKey(currentXCoord, currentYCoord); const cell = this.hashGrid.sparseHashGrid.get(key); if (cell) { + const cellHits: RayCastHit[] = []; for (let colliderIndex = 0; colliderIndex < cell.proxies.length; colliderIndex++) { const collider = cell.proxies[colliderIndex]; if (!collidersVisited.has(collider.collider.id.value)) { @@ -197,21 +198,30 @@ export class SparseHashGridCollisionProcessor implements CollisionProcessor { const hit = collider.collider.rayCast(ray, maxDistance); + // Collect up all the colliders that hit inside a cell + // they can be in any order so we need to sort them next if (hit) { - if (options?.filter) { - if (options.filter(hit)) { - results.push(hit); - if (!searchAllColliders) { - done = true; - } - } - } else { - results.push(hit); - if (!searchAllColliders) { - done = true; - } + cellHits.push(hit); + } + } + } + cellHits.sort((hit1, hit2) => hit1.distance - hit2.distance); + for (let i = 0; i < cellHits.length; i++) { + const hit = cellHits[i]; + if (options?.filter) { + if (options.filter(hit)) { + results.push(hit); + if (!searchAllColliders) { + done = true; + break; } } + } else { + results.push(hit); + if (!searchAllColliders) { + done = true; + break; + } } } } diff --git a/src/spec/SparseHashGridCollisionProcessorSpec.ts b/src/spec/SparseHashGridCollisionProcessorSpec.ts index 73ea3000b..90e1e8d24 100644 --- a/src/spec/SparseHashGridCollisionProcessorSpec.ts +++ b/src/spec/SparseHashGridCollisionProcessorSpec.ts @@ -340,4 +340,31 @@ describe('A Sparse Hash Grid Broadphase', () => { expect(hits[1].distance).toBe(175); expect(hits[1].point).toEqual(ex.vec(175, 0)); }); + + it('calls filter in hit order', () => { + const sut = new ex.SparseHashGridCollisionProcessor({ size: 200 }); + const actor3 = new ex.Actor({ x: 104, y: 0, width: 50, height: 50 }); + sut.track(actor3.collider.get()); + const actor2 = new ex.Actor({ x: 102, y: 0, radius: 25 }); + sut.track(actor2.collider.get()); + const actor1 = new ex.Actor({ x: 100, y: 0, radius: 25 }); + sut.track(actor1.collider.get()); + + const ray = new ex.Ray(ex.vec(0, 0), ex.Vector.Right); + const filter = jasmine.createSpy('filter').and.callFake(() => true); + const hits = sut.rayCast(ray, { + searchAllColliders: true, + filter + }); + + expect(filter.calls.argsFor(0)).toEqual([ + { point: ex.vec(75, 0), distance: 75, collider: actor1.collider.get(), body: actor1.body, normal: ex.vec(-1, 0) } + ]); + expect(filter.calls.argsFor(1)).toEqual([ + { point: ex.vec(77, 0), distance: 77, collider: actor2.collider.get(), body: actor2.body, normal: ex.vec(-1, 0) } + ]); + expect(filter.calls.argsFor(2)).toEqual([ + { point: ex.vec(79, 0), distance: 79, collider: actor3.collider.get(), body: actor3.body, normal: ex.vec(-1, -0) } + ]); + }); });