UNPKG

@rpgjs/physic

Version:

A deterministic 2D top-down physics library for RPG, sandbox and MMO games

125 lines (124 loc) 4.25 kB
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