Skip to content

Commit

Permalink
fix: Add contact solve order/bias to Realistic solver
Browse files Browse the repository at this point in the history
  • Loading branch information
eonarheim committed Dec 28, 2024
1 parent ec34e7e commit 61b3ca4
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/engine/Collision/PhysicsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export interface PhysicsConfig {
* Configure the {@apilink RealisticSolver}
*/
realistic?: {
contactSolveBias?: ContactSolveBias;
/**
* Number of position iterations (overlap) to run in the solver
*
Expand Down Expand Up @@ -248,6 +249,7 @@ export const getDefaultPhysicsConfig: () => DeepRequired<PhysicsConfig> = () =>
contactSolveBias: ContactSolveBias.None
},
realistic: {
contactSolveBias: ContactSolveBias.None,
positionIterations: 3,
velocityIterations: 8,
slop: 1,
Expand Down
35 changes: 35 additions & 0 deletions src/engine/Collision/Solver/RealisticSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, 'horizontal' | 'vertical'>();
distanceMap = new Map<string, number>();

constructor(public config: DeepRequired<Pick<PhysicsConfig, 'realistic'>['realistic']>) {}
lastFrameContacts: Map<string, CollisionContact> = new Map();

Expand All @@ -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);
Expand All @@ -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)
Expand Down

0 comments on commit 61b3ca4

Please sign in to comment.