UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.

352 lines (297 loc) 14.4 kB
/** * The `Matter.Resolver` module contains methods for resolving collision pairs. * * @class Resolver */ var Resolver = {}; module.exports = Resolver; var Vertices = require('../geometry/Vertices'); var Vector = require('../geometry/Vector'); var Common = require('../core/Common'); var Bounds = require('../geometry/Bounds'); (function() { Resolver._restingThresh = 4; Resolver._restingThreshTangent = 6; Resolver._positionDampen = 0.9; Resolver._positionWarming = 0.8; Resolver._frictionNormalMultiplier = 5; /** * Prepare pairs for position solving. * @method preSolvePosition * @param {pair[]} pairs */ Resolver.preSolvePosition = function(pairs) { var i, pair, activeCount; // find total contacts on each body for (i = 0; i < pairs.length; i++) { pair = pairs[i]; if (!pair.isActive) continue; activeCount = pair.activeContacts.length; pair.collision.parentA.totalContacts += activeCount; pair.collision.parentB.totalContacts += activeCount; } }; /** * Find a solution for pair positions. * @method solvePosition * @param {pair[]} pairs * @param {number} timeScale */ Resolver.solvePosition = function(pairs, bodies, timeScale) { var i, normalX, normalY, pair, collision, bodyA, bodyB, normal, separation, penetration, positionImpulseA, positionImpulseB, contactShare, bodyBtoAX, bodyBtoAY, positionImpulse, impulseCoefficient = timeScale * Resolver._positionDampen; for (i = 0; i < bodies.length; i++) { var body = bodies[i]; body.previousPositionImpulse.x = body.positionImpulse.x; body.previousPositionImpulse.y = body.positionImpulse.y; } // find impulses required to resolve penetration for (i = 0; i < pairs.length; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; collision = pair.collision; bodyA = collision.parentA; bodyB = collision.parentB; normal = collision.normal; positionImpulseA = bodyA.previousPositionImpulse; positionImpulseB = bodyB.previousPositionImpulse; penetration = collision.penetration; // bodyBtoA = positionImpulseB - positionImpulseA + penetration bodyBtoAX = positionImpulseB.x - positionImpulseA.x + penetration.x; bodyBtoAY = positionImpulseB.y - positionImpulseA.y + penetration.y; normalX = normal.x; normalY = normal.y; // separation = dot(normal, bodyBtoA) separation = normalX * bodyBtoAX + normalY * bodyBtoAY; pair.separation = separation; positionImpulse = (separation - pair.slop) * impulseCoefficient; if (bodyA.isStatic || bodyB.isStatic) positionImpulse *= 2; if (!(bodyA.isStatic || bodyA.isSleeping)) { contactShare = positionImpulse / bodyA.totalContacts; bodyA.positionImpulse.x += normalX * contactShare; bodyA.positionImpulse.y += normalY * contactShare; } if (!(bodyB.isStatic || bodyB.isSleeping)) { contactShare = positionImpulse / bodyB.totalContacts; bodyB.positionImpulse.x -= normalX * contactShare; bodyB.positionImpulse.y -= normalY * contactShare; } } }; /** * Apply position resolution. * @method postSolvePosition * @param {body[]} bodies */ Resolver.postSolvePosition = function(bodies) { for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; // reset contact count body.totalContacts = 0; if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) { // update body geometry for (var j = 0; j < body.parts.length; j++) { var part = body.parts[j]; Vertices.translate(part.vertices, body.positionImpulse); Bounds.update(part.bounds, part.vertices, body.velocity); part.position.x += body.positionImpulse.x; part.position.y += body.positionImpulse.y; } // move the body without changing velocity body.positionPrev.x += body.positionImpulse.x; body.positionPrev.y += body.positionImpulse.y; if (Vector.dot(body.positionImpulse, body.velocity) < 0) { // reset cached impulse if the body has velocity along it body.positionImpulse.x = 0; body.positionImpulse.y = 0; } else { // warm the next iteration body.positionImpulse.x *= Resolver._positionWarming; body.positionImpulse.y *= Resolver._positionWarming; } } } }; /** * Prepare pairs for velocity solving. * @method preSolveVelocity * @param {pair[]} pairs */ Resolver.preSolveVelocity = function(pairs) { var i, j, pair, contacts, collision, bodyA, bodyB, normal, tangent, contact, contactVertex, normalImpulse, tangentImpulse, offset, impulse = Vector._temp[0], tempA = Vector._temp[1]; for (i = 0; i < pairs.length; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; contacts = pair.activeContacts; collision = pair.collision; bodyA = collision.parentA; bodyB = collision.parentB; normal = collision.normal; tangent = collision.tangent; // resolve each contact for (j = 0; j < contacts.length; j++) { contact = contacts[j]; contactVertex = contact.vertex; normalImpulse = contact.normalImpulse; tangentImpulse = contact.tangentImpulse; if (normalImpulse !== 0 || tangentImpulse !== 0) { // total impulse from contact impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { offset = Vector.sub(contactVertex, bodyA.position, tempA); bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia; } if (!(bodyB.isStatic || bodyB.isSleeping)) { offset = Vector.sub(contactVertex, bodyB.position, tempA); bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia; } } } } }; /** * Find a solution for pair velocities. * @method solveVelocity * @param {pair[]} pairs * @param {number} timeScale */ Resolver.solveVelocity = function(pairs, timeScale) { var timeScaleSquared = timeScale * timeScale, impulse = Vector._temp[0], tempA = Vector._temp[1], tempB = Vector._temp[2], tempC = Vector._temp[3], tempD = Vector._temp[4], tempE = Vector._temp[5]; for (var i = 0; i < pairs.length; i++) { var pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; var collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, normal = collision.normal, tangent = collision.tangent, contacts = pair.activeContacts, contactShare = 1 / contacts.length; // update body velocities bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x; bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y; bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x; bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y; bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev; bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev; // resolve each contact for (var j = 0; j < contacts.length; j++) { var contact = contacts[j], contactVertex = contact.vertex, offsetA = Vector.sub(contactVertex, bodyA.position, tempA), offsetB = Vector.sub(contactVertex, bodyB.position, tempB), velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC), velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD), relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE), normalVelocity = Vector.dot(normal, relativeVelocity); var tangentVelocity = Vector.dot(tangent, relativeVelocity), tangentSpeed = Math.abs(tangentVelocity), tangentVelocityDirection = Common.sign(tangentVelocity); // raw impulses var normalImpulse = (1 + pair.restitution) * normalVelocity, normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier; // coulomb friction var tangentImpulse = tangentVelocity, maxFriction = Infinity; if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) { maxFriction = tangentSpeed; tangentImpulse = Common.clamp( pair.friction * tangentVelocityDirection * timeScaleSquared, -maxFriction, maxFriction ); } // modify impulses accounting for mass, inertia and offset var oAcN = Vector.cross(offsetA, normal), oBcN = Vector.cross(offsetB, normal), share = contactShare / (bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); normalImpulse *= share; tangentImpulse *= share; // handle high velocity and resting collisions separately if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) { // high normal velocity so clear cached contact normal impulse contact.normalImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // impulse constraint tends to 0 var contactNormalImpulse = contact.normalImpulse; contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0); normalImpulse = contact.normalImpulse - contactNormalImpulse; } // handle high velocity and resting collisions separately if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) { // high tangent velocity so clear cached contact tangent impulse contact.tangentImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // tangent impulse tends to -tangentSpeed or +tangentSpeed var contactTangentImpulse = contact.tangentImpulse; contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction); tangentImpulse = contact.tangentImpulse - contactTangentImpulse; } // total impulse from contact impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia; } if (!(bodyB.isStatic || bodyB.isSleeping)) { bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia; } } } }; })();