From 907972d245012518d7075afceaf499ad6c894011 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 31 Jan 2025 09:15:46 -0600 Subject: [PATCH] fix: composite multi contact (#3351) This PR fixes and issue where composite colliders in the "together" strategy contacts were being resolved multiple times in the ArcadeSolver causing visual jitter/artifacts --- src/engine/Collision/Colliders/Shape.ts | 2 ++ src/engine/Collision/CollisionSystem.ts | 2 +- .../Collision/Detection/CollisionContact.ts | 2 +- src/engine/Collision/Solver/ArcadeSolver.ts | 18 +++++++++++++++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/engine/Collision/Colliders/Shape.ts b/src/engine/Collision/Colliders/Shape.ts index da92ad2ee..f0fda3702 100644 --- a/src/engine/Collision/Colliders/Shape.ts +++ b/src/engine/Collision/Colliders/Shape.ts @@ -91,6 +91,7 @@ export class Shape { Shape.Box(width, height - width, Vector.Half, offset), Shape.Circle(width / 2, vec(0, height / 2 - width / 2).add(offset)) ]); + capsule.compositeStrategy = 'together'; return capsule; } else { // width > height, if equal maybe use a circle @@ -99,6 +100,7 @@ export class Shape { Shape.Box(width - height, height, Vector.Half, offset), Shape.Circle(height / 2, vec(width / 2 - height / 2, 0).add(offset)) ]); + capsule.compositeStrategy = 'together'; return capsule; } } diff --git a/src/engine/Collision/CollisionSystem.ts b/src/engine/Collision/CollisionSystem.ts index 7d27c3d67..1356401ec 100644 --- a/src/engine/Collision/CollisionSystem.ts +++ b/src/engine/Collision/CollisionSystem.ts @@ -113,7 +113,7 @@ export class CollisionSystem extends System { this._currentFrameContacts.clear(); // Given possible pairs find actual contacts - let contacts: CollisionContact[] = []; // = this._processor.narrowphase(pairs, this._engine?.debug?.stats?.currFrame); + let contacts: CollisionContact[] = []; const solver: CollisionSolver = this.getSolver(); diff --git a/src/engine/Collision/Detection/CollisionContact.ts b/src/engine/Collision/Detection/CollisionContact.ts index a1c724c3e..8f50698a5 100644 --- a/src/engine/Collision/Detection/CollisionContact.ts +++ b/src/engine/Collision/Detection/CollisionContact.ts @@ -80,7 +80,7 @@ export class CollisionContact { this.info = info; this.id = Pair.calculatePairHash(colliderA.id, colliderB.id); if (colliderA.composite || colliderB.composite) { - // Add on the parent composite pair for start/end contact if 'together + // Add on the parent composite pair for start/end contact if 'together' const colliderAId = colliderA.composite?.compositeStrategy === 'separate' ? colliderA.id : colliderA.composite?.id ?? colliderA.id; const colliderBId = colliderB.composite?.compositeStrategy === 'separate' ? colliderB.id : colliderB.composite?.id ?? colliderB.id; this.id += '|' + Pair.calculatePairHash(colliderAId, colliderBId); diff --git a/src/engine/Collision/Solver/ArcadeSolver.ts b/src/engine/Collision/Solver/ArcadeSolver.ts index 7c27251f2..523bfe899 100644 --- a/src/engine/Collision/Solver/ArcadeSolver.ts +++ b/src/engine/Collision/Solver/ArcadeSolver.ts @@ -69,15 +69,30 @@ export class ArcadeSolver implements CollisionSolver { return contacts; } + private _compositeContactsIds = new Set(); public preSolve(contacts: CollisionContact[]) { const epsilon = 0.0001; for (let i = 0; i < contacts.length; i++) { const contact = contacts[i]; + + // Cancel dup composite together strategy contacts + const index = contact.id.indexOf('|'); + if (index > 0) { + const compositeId = contact.id.substring(index + 1); + if (this._compositeContactsIds.has(compositeId)) { + contact.cancel(); + continue; + } + this._compositeContactsIds.add(compositeId); + } + + // Cancel near 0 mtv collisions if (Math.abs(contact.mtv.x) < epsilon && Math.abs(contact.mtv.y) < epsilon) { - // Cancel near 0 mtv collisions contact.cancel(); continue; } + + // Record distance/direction for sorting const side = Side.fromDirection(contact.mtv); const mtv = contact.mtv.negate(); @@ -93,6 +108,7 @@ export class ArcadeSolver implements CollisionSolver { new PreCollisionEvent(contact.colliderB, contact.colliderA, Side.getOpposite(side), mtv.negate(), contact) ); } + this._compositeContactsIds.clear(); } public postSolve(contacts: CollisionContact[]) {