From 61b3ca4de1224de9c06f645d5a854a76bb739d99 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sat, 28 Dec 2024 16:24:11 -0600 Subject: [PATCH] fix: Add contact solve order/bias to Realistic solver --- CHANGELOG.md | 2 ++ src/engine/Collision/PhysicsConfig.ts | 2 ++ .../Collision/Solver/RealisticSolver.ts | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3109989bc..ff16b78a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - New PostProcessor.onDraw() hook to handle uploading textures +- Adds contact solve bias to RealisticSolver, this allows customization on which direction contacts are solved first. By default there is no bias set to 'none'. ### Fixed +- Fixed issue where Realistic solver would not sort contacts by distance causing some artifacts on seams - Fixed issue with CompositeCollider where large TileMaps would sometimes causes odd collision behavior in the Realistic Solver when the body & collider components are far apart in a TileMap. - Fixed crash on Xiaomi Redmi Phones by lazy loading the GPU particle renderer, GPU particles still do not work on these phones - Add warning if World.add() falls through! This is caused by multiple versions of Excalibur usually diff --git a/src/engine/Collision/PhysicsConfig.ts b/src/engine/Collision/PhysicsConfig.ts index e6a67a97f..52bc2d9fc 100644 --- a/src/engine/Collision/PhysicsConfig.ts +++ b/src/engine/Collision/PhysicsConfig.ts @@ -177,6 +177,7 @@ export interface PhysicsConfig { * Configure the {@apilink RealisticSolver} */ realistic?: { + contactSolveBias?: ContactSolveBias; /** * Number of position iterations (overlap) to run in the solver * @@ -248,6 +249,7 @@ export const getDefaultPhysicsConfig: () => DeepRequired = () => contactSolveBias: ContactSolveBias.None }, realistic: { + contactSolveBias: ContactSolveBias.None, positionIterations: 3, velocityIterations: 8, slop: 1, diff --git a/src/engine/Collision/Solver/RealisticSolver.ts b/src/engine/Collision/Solver/RealisticSolver.ts index f3c7df939..0320e9a8f 100644 --- a/src/engine/Collision/Solver/RealisticSolver.ts +++ b/src/engine/Collision/Solver/RealisticSolver.ts @@ -9,8 +9,12 @@ import { DegreeOfFreedom } from '../BodyComponent'; import { CollisionJumpTable } from '../Colliders/CollisionJumpTable'; import { DeepRequired } from '../../Util/Required'; import { PhysicsConfig } from '../PhysicsConfig'; +import { ContactBias, ContactSolveBias, HorizontalFirst, None, VerticalFirst } from './ContactBias'; export class RealisticSolver implements CollisionSolver { + directionMap = new Map(); + distanceMap = new Map(); + constructor(public config: DeepRequired['realistic']>) {} lastFrameContacts: Map = new Map(); @@ -27,6 +31,32 @@ export class RealisticSolver implements CollisionSolver { // Remove any canceled contacts contacts = contacts.filter((c) => !c.isCanceled()); + // Locate collision bias order + let bias: ContactBias; + switch (this.config.contactSolveBias) { + case ContactSolveBias.HorizontalFirst: { + bias = HorizontalFirst; + break; + } + case ContactSolveBias.VerticalFirst: { + bias = VerticalFirst; + break; + } + default: { + bias = None; + } + } + + // Sort by bias (None, VerticalFirst, HorizontalFirst) to avoid artifacts with seams + // Sort contacts by distance to avoid artifacts with seams + // It's important to solve in a specific order + contacts.sort((a, b) => { + const aDir = this.directionMap.get(a.id); + const bDir = this.directionMap.get(b.id); + const aDist = this.distanceMap.get(a.id); + const bDist = this.distanceMap.get(b.id); + return bias[aDir] - bias[bDir] || aDist - bDist; + }); // Solve velocity first this.solveVelocity(contacts); @@ -51,6 +81,11 @@ export class RealisticSolver implements CollisionSolver { } // Publish collision events on both participants const side = Side.fromDirection(contact.mtv); + const distance = Math.abs(contact?.info?.separation || 0); + + this.distanceMap.set(contact.id, distance); + this.directionMap.set(contact.id, side === Side.Left || side === Side.Right ? 'horizontal' : 'vertical'); + contact.colliderA.events.emit( 'precollision', new PreCollisionEvent(contact.colliderA, contact.colliderB, side, contact.mtv, contact)