Skip to content

Commit

Permalink
feat: Add additional rayCast options ignoreCollisionGroupAll and `f…
Browse files Browse the repository at this point in the history
…ilter: (hit: RayCastHit) => boolean`
  • Loading branch information
eonarheim committed Feb 13, 2024
1 parent 4e7dc28 commit a35218b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added additional options in rayCast options
* `ignoreCollisionGroupAll: boolean` will ignore testing against anything with the `CollisionGroup.All` which is the default for all
* `filter: (hit: RayCastHit) => boolean` will allow people to do arbitrary filtering on raycast results, this runs very last after all other collision group/collision mask decisions have been made
- Added additional data `side` and `lastContact` to `onCollisionEnd` and `collisionend` events
- Added configuration option to `ex.PhysicsConfig` to configure composite collider onCollisionStart/End behavior
- Added configuration option to `ex.TileMap({ meshingLookBehind: Infinity })` which allows users to configure how far the TileMap looks behind for matching colliders (default is 10).
Expand Down
28 changes: 27 additions & 1 deletion src/engine/Collision/Detection/DynamicTreeCollisionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ export interface RayCastOptions {
* Optionally specify to search for all colliders that intersect the ray cast, not just the first which is the default
*/
searchAllColliders?: boolean;
/**
* Optionally ignore things with CollisionGroup.All and only test against things with an explicit group
*
* Default false
*/
ignoreCollisionGroupAll?: boolean;

/**
* Optionally provide a any filter function to filter on arbitrary qualities of a ray cast hit
*
* Filters run after any collision mask/collision group filtering, it is the last decision
*
* Returning true means you want to include the collider in your results, false means exclude it
*/
filter?: (hit: RayCastHit) => boolean;
}

/**
Expand Down Expand Up @@ -65,6 +80,10 @@ export class DynamicTreeCollisionProcessor implements CollisionProcessor {
const owner = collider.owner;
const maybeBody = owner.get(BodyComponent);

if (options?.ignoreCollisionGroupAll && maybeBody.group === CollisionGroup.All) {
return false;
}

const canCollide = (collisionMask & maybeBody.group.category) !== 0;

// Early exit if not the right group
Expand All @@ -73,8 +92,15 @@ export class DynamicTreeCollisionProcessor implements CollisionProcessor {
}

const hit = collider.rayCast(ray, maxDistance);

if (hit) {
results.push(hit);
if (options?.filter) {
if (options.filter(hit)) {
results.push(hit);
}
} else {
results.push(hit);
}
if (!searchAllColliders) {
// returning true exits the search
return true;
Expand Down
47 changes: 47 additions & 0 deletions src/spec/PhysicsWorldSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,51 @@ describe('A physics world', () => {
expect(hits[0].distance).toBe(75);
expect(hits[0].point).toEqual(ex.vec(75, 0));
});

it('can rayCast with ignoreCollisionGroupAll, returns 1 hit', () => {
const sut = TestUtils.engine();
const actor1 = new ex.Actor({x: 100, y: 0, width: 50, height: 50});
sut.currentScene.add(actor1);
const actor2 = new ex.Actor({x: 200, y: 0, width: 50, height: 50});
sut.currentScene.add(actor2);
const actor3 = new ex.Actor({x: 300, y: 0, width: 50, height: 50, collisionGroup: new ex.CollisionGroup('test', 0b1, ~0b1)});
sut.currentScene.add(actor3);

const ray = new ex.Ray(ex.vec(0, 0), ex.Vector.Right);
const hits = sut.currentScene.physics.rayCast(ray, {
searchAllColliders: true,
collisionMask: 0b1,
ignoreCollisionGroupAll: true
});

expect(hits.length).toBe(1);
expect(hits[0].body).toEqual(actor3.body);
expect(hits[0].collider).toEqual(actor3.collider.get());
expect(hits[0].distance).toBe(275);
expect(hits[0].point).toEqual(ex.vec(275, 0));
});

it('can rayCast with filter, returns 1 hit', () => {
const sut = TestUtils.engine();
const actor1 = new ex.Actor({x: 100, y: 0, width: 50, height: 50});
sut.currentScene.add(actor1);
const actor2 = new ex.Actor({x: 200, y: 0, width: 50, height: 50});
sut.currentScene.add(actor2);
const actor3 = new ex.Actor({x: 300, y: 0, width: 50, height: 50, collisionGroup: new ex.CollisionGroup('test', 0b1, ~0b1)});
sut.currentScene.add(actor3);

const ray = new ex.Ray(ex.vec(0, 0), ex.Vector.Right);
const hits = sut.currentScene.physics.rayCast(ray, {
searchAllColliders: true,
filter: (hit) => {
return hit.body.group.name === 'test';
}
});

expect(hits.length).toBe(1);
expect(hits[0].body).toEqual(actor3.body);
expect(hits[0].collider).toEqual(actor3.collider.get());
expect(hits[0].distance).toBe(275);
expect(hits[0].point).toEqual(ex.vec(275, 0));
});
});

0 comments on commit a35218b

Please sign in to comment.