From e57fd3f4f4a9d3605aa9b7fbbeb8675b099b5088 Mon Sep 17 00:00:00 2001 From: Matt Jennings Date: Sun, 7 Jul 2024 19:49:26 -0500 Subject: [PATCH] feat: add CollisionContact.bias(collider) (#3122) Follow up from https://github.com/excaliburjs/Excalibur/issues/3121 ## Changes: - CollisionContact can be biased toward a collider by using `contact.bias(collider)`. This adjusts the contact so that the given collider is colliderA, and is helpful if you are doing mtv adjustments during precollision. --------- Co-authored-by: Erik Onarheim --- CHANGELOG.md | 2 ++ .../Collision/Detection/CollisionContact.ts | 24 +++++++++++++++++++ src/spec/CollisionContactSpec.ts | 23 ++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74d4b66e..d7c9de937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - show warning in development when Entity hasn't been added to a scene after a few seconds - New `RentalPool` type for sparse object pooling - New `ex.SparseHashGridCollisionProcessor` which is a simpler (and faster) implementation for broadphase pair generation. This works by bucketing colliders into uniform sized square buckets and using that to generate pairs. +- CollisionContact can be biased toward a collider by using `contact.bias(collider)`. This adjusts the contact so that the given collider is colliderA, and is helpful if you +are doing mtv adjustments during precollision. ### Fixed diff --git a/src/engine/Collision/Detection/CollisionContact.ts b/src/engine/Collision/Detection/CollisionContact.ts index cfa7a8b8f..b3c5b0fcb 100644 --- a/src/engine/Collision/Detection/CollisionContact.ts +++ b/src/engine/Collision/Detection/CollisionContact.ts @@ -118,4 +118,28 @@ export class CollisionContact { public cancel(): void { this._canceled = true; } + + /** + * Biases the contact so that the given collider is colliderA + */ + public bias(collider: Collider) { + if (collider !== this.colliderA && collider !== this.colliderB) { + throw new Error('Collider must be either colliderA or colliderB from this contact'); + } + + if (collider === this.colliderA) { + return this; + } + + const colliderA = this.colliderA; + const colliderB = this.colliderB; + + this.colliderB = colliderA; + this.colliderA = colliderB; + this.mtv = this.mtv.negate(); + this.normal = this.normal.negate(); + this.tangent = this.tangent.negate(); + + return this; + } } diff --git a/src/spec/CollisionContactSpec.ts b/src/spec/CollisionContactSpec.ts index 4ee938efe..7352990bf 100644 --- a/src/spec/CollisionContactSpec.ts +++ b/src/spec/CollisionContactSpec.ts @@ -3,6 +3,7 @@ import { TransformComponent } from '@excalibur'; import { EulerIntegrator } from '../engine/Collision/Integrator'; import { MotionComponent } from '../engine/EntityComponentSystem/Components/MotionComponent'; import { DefaultPhysicsConfig } from '../engine/Collision/PhysicsConfig'; +import { ExcaliburMatchers } from 'excalibur-jasmine'; describe('A CollisionContact', () => { let actorA: ex.Actor; @@ -12,6 +13,7 @@ describe('A CollisionContact', () => { let colliderB: ex.Collider; beforeEach(() => { + jasmine.addMatchers(ExcaliburMatchers); actorA = new ex.Actor({ x: 0, y: 0, width: 20, height: 20 }); actorA.collider.useCircleCollider(10); actorA.body.collisionType = ex.CollisionType.Active; @@ -326,4 +328,25 @@ describe('A CollisionContact', () => { expect(emittedA).toBe(true); expect(emittedB).toBe(true); }); + + it('biases to colliderB', () => { + const cc = new ex.CollisionContact( + colliderA, + colliderB, + ex.Vector.Right, + ex.Vector.Right, + ex.Vector.Right.perpendicular(), + [new ex.Vector(10, 0)], + [new ex.Vector(10, 0)], + null + ); + + cc.bias(colliderB); + + expect(cc.colliderA).toBe(colliderB); + expect(cc.colliderB).toBe(colliderA); + expect(cc.mtv).toBeVector(ex.Vector.Left); + expect(cc.normal).toBeVector(ex.Vector.Left); + expect(cc.tangent).toBeVector(ex.Vector.Left.perpendicular()); + }); });