@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
125 lines (124 loc) • 4.25 kB
JavaScript
class CollisionResolver {
/**
* Creates a new collision resolver
*
* @param config - Resolver configuration
*/
constructor(config = {}) {
this.config = {
positionCorrectionFactor: config.positionCorrectionFactor ?? 0.8,
minPenetrationDepth: config.minPenetrationDepth ?? 1e-4,
maxPositionCorrection: config.maxPositionCorrection ?? 5
};
}
/**
* Resolves a collision
*
* Separates entities and applies collision response.
*
* @param collision - Collision information to resolve
*/
resolve(collision) {
const { entityA, entityB, normal, depth } = collision;
if (depth < this.config.minPenetrationDepth) {
return;
}
this.separateEntities(entityA, entityB, normal, depth);
this.resolveVelocities(entityA, entityB, normal);
}
/**
* Separates two entities by moving them apart
*
* This method applies position corrections to resolve penetration between
* colliding entities. After applying corrections, it notifies position change
* handlers to ensure proper synchronization with game logic (e.g., updating
* owner.x/y signals for network sync).
*
* @param entityA - First entity
* @param entityB - Second entity
* @param normal - Separation normal (from A to B)
* @param depth - Penetration depth
*/
separateEntities(entityA, entityB, normal, depth) {
const totalInvMass = entityA.invMass + entityB.invMass;
if (totalInvMass === 0) {
return;
}
const correction = Math.min(
depth * this.config.positionCorrectionFactor,
this.config.maxPositionCorrection
);
const correctionA = normal.mul(-correction * (entityA.invMass / totalInvMass));
const correctionB = normal.mul(correction * (entityB.invMass / totalInvMass));
if (!entityA.isStatic()) {
entityA.position.addInPlace(correctionA);
entityA.notifyPositionChange();
}
if (!entityB.isStatic()) {
entityB.position.addInPlace(correctionB);
entityB.notifyPositionChange();
}
}
/**
* Resolves velocities using impulse-based collision response
*
* @param entityA - First entity
* @param entityB - Second entity
* @param normal - Collision normal (from A to B)
*/
resolveVelocities(entityA, entityB, normal) {
const relativeVelocity = entityB.velocity.sub(entityA.velocity);
const velocityAlongNormal = relativeVelocity.dot(normal);
if (velocityAlongNormal > 0) {
return;
}
const restitution = Math.min(entityA.restitution, entityB.restitution);
const totalInvMass = entityA.invMass + entityB.invMass;
if (totalInvMass === 0) {
return;
}
const impulseScalar = -(1 + restitution) * velocityAlongNormal / totalInvMass;
const impulse = normal.mul(impulseScalar);
if (!entityA.isStatic()) {
entityA.velocity.addInPlace(impulse.mul(-entityA.invMass));
entityA.notifyMovementChange();
}
if (!entityB.isStatic()) {
entityB.velocity.addInPlace(impulse.mul(entityB.invMass));
entityB.notifyMovementChange();
}
const tangent = relativeVelocity.sub(normal.mul(velocityAlongNormal));
const tangentLength = tangent.length();
if (tangentLength > 1e-5) {
const friction = Math.sqrt(entityA.friction * entityB.friction);
const tangentNormalized = tangent.normalize();
const frictionImpulse = tangentNormalized.mul(-tangentLength * friction / totalInvMass);
const maxFriction = Math.abs(impulseScalar * friction);
if (frictionImpulse.length() > maxFriction) {
frictionImpulse.normalizeInPlace().mulInPlace(maxFriction);
}
if (!entityA.isStatic()) {
entityA.velocity.addInPlace(frictionImpulse.mul(-entityA.invMass));
entityA.notifyMovementChange();
}
if (!entityB.isStatic()) {
entityB.velocity.addInPlace(frictionImpulse.mul(entityB.invMass));
entityB.notifyMovementChange();
}
}
}
/**
* Resolves multiple collisions
*
* @param collisions - Array of collisions to resolve
*/
resolveAll(collisions) {
for (const collision of collisions) {
this.resolve(collision);
}
}
}
export {
CollisionResolver
};
//# sourceMappingURL=index17.js.map