From 9f289256e8d21971fff247884e1662ecab246755 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Wed, 24 Jul 2024 17:32:14 -0500 Subject: [PATCH] fix: rayCast returns consistent ordering of hit distance --- CHANGELOG.md | 1 + .../SparseHashGridCollisionProcessor.ts | 7 +- .../SparseHashGridCollisionProcessorSpec.ts | 79 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec3239304..9abdd4ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ are doing mtv adjustments during precollision. ### Fixed +- 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 })` - Fixed issue where `ex.Scene.onPreLoad(loader: ex.DefaultLoader)` would lock up the engine if there was an empty loader diff --git a/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts b/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts index ca1e63ed5..4c477e286 100644 --- a/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts +++ b/src/engine/Collision/Detection/SparseHashGridCollisionProcessor.ts @@ -203,14 +203,12 @@ export class SparseHashGridCollisionProcessor implements CollisionProcessor { results.push(hit); if (!searchAllColliders) { done = true; - break; } } } else { results.push(hit); if (!searchAllColliders) { done = true; - break; } } } @@ -227,6 +225,11 @@ export class SparseHashGridCollisionProcessor implements CollisionProcessor { } } + // Sort by distance + results.sort((hit1, hit2) => hit1.distance - hit2.distance); + if (!searchAllColliders && results.length) { + return [results[0]]; + } return results; } diff --git a/src/spec/SparseHashGridCollisionProcessorSpec.ts b/src/spec/SparseHashGridCollisionProcessorSpec.ts index 5e4e09cd1..73ea3000b 100644 --- a/src/spec/SparseHashGridCollisionProcessorSpec.ts +++ b/src/spec/SparseHashGridCollisionProcessorSpec.ts @@ -261,4 +261,83 @@ describe('A Sparse Hash Grid Broadphase', () => { expect(hits[0].distance).toBe(275); expect(hits[0].point).toEqual(ex.vec(275, 0)); }); + + it('can rayCast and have the right first collider returned regardless of track order', () => { + const sut = new ex.SparseHashGridCollisionProcessor({ size: 200 }); + const actor3 = new ex.Actor({ x: 300, y: 0, width: 50, height: 50 }); + sut.track(actor3.collider.get()); + const actor2 = new ex.Actor({ x: 200, y: 0, width: 50, height: 50 }); + sut.track(actor2.collider.get()); + const actor1 = new ex.Actor({ x: 100, y: 0, width: 50, height: 50 }); + sut.track(actor1.collider.get()); + + const ray = new ex.Ray(ex.vec(0, 0), ex.Vector.Right); + const hits = sut.rayCast(ray, { + searchAllColliders: false + }); + + expect(hits.length).toBe(1); + expect(hits[0].body).toEqual(actor1.body); + expect(hits[0].collider).toEqual(actor1.collider.get()); + expect(hits[0].distance).toBe(75); + expect(hits[0].point).toEqual(ex.vec(75, 0)); + }); + + it('can rayCast and have the right ordering returned regardless of track order', () => { + const sut = new ex.SparseHashGridCollisionProcessor({ size: 200 }); + const actor3 = new ex.Actor({ x: 300, y: 0, width: 50, height: 50 }); + sut.track(actor3.collider.get()); + const actor2 = new ex.Actor({ x: 200, y: 0, width: 50, height: 50 }); + sut.track(actor2.collider.get()); + const actor1 = new ex.Actor({ x: 100, y: 0, width: 50, height: 50 }); + sut.track(actor1.collider.get()); + + const ray = new ex.Ray(ex.vec(0, 0), ex.Vector.Right); + const hits = sut.rayCast(ray, { + searchAllColliders: true + }); + + expect(hits.length).toBe(3); + expect(hits[0].body).toEqual(actor1.body); + expect(hits[0].collider).toEqual(actor1.collider.get()); + expect(hits[0].distance).toBe(75); + expect(hits[0].point).toEqual(ex.vec(75, 0)); + + expect(hits[1].body).toEqual(actor2.body); + expect(hits[1].collider).toEqual(actor2.collider.get()); + expect(hits[1].distance).toBe(175); + expect(hits[1].point).toEqual(ex.vec(175, 0)); + + expect(hits[2].body).toEqual(actor3.body); + expect(hits[2].collider).toEqual(actor3.collider.get()); + expect(hits[2].distance).toBe(275); + expect(hits[2].point).toEqual(ex.vec(275, 0)); + }); + + it('can rayCast to max distance and have the right ordering returned regardless of track order', () => { + const sut = new ex.SparseHashGridCollisionProcessor({ size: 200 }); + const actor3 = new ex.Actor({ x: 300, y: 0, width: 50, height: 50 }); + sut.track(actor3.collider.get()); + const actor2 = new ex.Actor({ x: 200, 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 hits = sut.rayCast(ray, { + searchAllColliders: true, + maxDistance: 175 + }); + + expect(hits.length).toBe(2); + expect(hits[0].body).toEqual(actor1.body); + expect(hits[0].collider).toEqual(actor1.collider.get()); + expect(hits[0].distance).toBe(75); + expect(hits[0].point).toEqual(ex.vec(75, 0)); + + expect(hits[1].body).toEqual(actor2.body); + expect(hits[1].collider).toEqual(actor2.collider.get()); + expect(hits[1].distance).toBe(175); + expect(hits[1].point).toEqual(ex.vec(175, 0)); + }); });