UNPKG

planck-js

Version:

2D physics engine for JavaScript/HTML5 game development

1,146 lines (915 loc) 32.5 kB
/* * Copyright (c) 2016 Ali Shakiba http://shakiba.me/planck.js * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ var DEBUG_SOLVER = false; var Settings = require('./Settings'); var Manifold = require('./Manifold'); var Math = require('./common/Math'); var Vec2 = require('./common/Vec2'); var Transform = require('./common/Transform'); var Mat22 = require('./common/Mat22'); var Rot = require('./common/Rot'); module.exports = Contact; /** * A contact edge is used to connect bodies and contacts together in a contact * graph where each body is a node and each contact is an edge. A contact edge * belongs to a doubly linked list maintained in each attached body. Each * contact has two contact nodes, one for each attached body. * * @prop {Contact} contact The contact * @prop {ContactEdge} prev The previous contact edge in the body's contact list * @prop {ContactEdge} next The next contact edge in the body's contact list * @prop {Body} other Provides quick access to the other body attached. */ function ContactEdge(contact) { this.contact = contact; this.prev; this.next; this.other; }; /** * @function Contact~Evaluate * * @param manifold * @param xfA * @param fixtureA * @param indexA * @param xfB * @param fixtureB * @param indexB */ /** * The class manages contact between two shapes. A contact exists for each * overlapping AABB in the broad-phase (except if filtered). Therefore a contact * object may exist that has no contact points. * * @param {Fixture} fA * @param {int} indexA * @param {Fixture} fB * @param {int} indexB * @param {Contact~Evaluate} evaluateFcn */ function Contact(fA, indexA, fB, indexB, evaluateFcn) { // Nodes for connecting bodies. this.m_nodeA = new ContactEdge(this); this.m_nodeB = new ContactEdge(this); this.m_fixtureA = fA; this.m_fixtureB = fB; this.m_indexA = indexA; this.m_indexB = indexB; this.m_evaluateFcn = evaluateFcn; this.m_manifold = new Manifold(); this.m_prev = null; this.m_next = null; this.m_toi = 1.0; this.m_toiCount = 0; this.m_friction = MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction); this.m_restitution = MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution); this.m_tangentSpeed = 0.0; // This contact can be disabled (by user) this.m_enabledFlag = true; // Used when crawling contact graph when forming islands. this.m_islandFlag = false; // Set when the shapes are touching. this.m_touchingFlag = false; // This contact needs filtering because a fixture filter was changed. this.m_filterFlag = false; // This bullet contact had a TOI event this.m_bulletHitFlag = false; // This contact has a valid TOI in m_toi this.m_toiFlag = false; // VelocityConstraint this.v_points = []; // VelocityConstraintPoint[maxManifoldPoints] this.v_K = new Mat22(); this.v_normalMass = new Mat22(); this.v_normal = Vec2(); }; /** * Get the contact manifold. Do not modify the manifold unless you understand * the internals of the library. */ Contact.prototype.GetManifold = function() { return this.m_manifold; } /** * Get the world manifold. * * @param {WorldManifold} [worldManifold] */ Contact.prototype.GetWorldManifold = function(worldManifold) { var bodyA = this.m_fixtureA.GetBody(); var bodyB = this.m_fixtureB.GetBody(); var shapeA = this.m_fixtureA.GetShape(); var shapeB = this.m_fixtureB.GetShape(); return this.m_manifold.GetWorldManifold(worldManifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius); } /** * Enable/disable this contact. This can be used inside the pre-solve contact * listener. The contact is only disabled for the current time step (or sub-step * in continuous collisions). */ Contact.prototype.SetEnabled = function(flag) { this.m_enabledFlag = !!flag; } /** * Has this contact been disabled? */ Contact.prototype.IsEnabled = function() { return this.m_enabledFlag; } /** * Is this contact touching? */ Contact.prototype.IsTouching = function() { return this.m_touchingFlag; } /** * Get the next contact in the world's contact list. */ Contact.prototype.GetNext = function() { return this.m_next; } /** * Get fixture A in this contact. */ Contact.prototype.GetFixtureA = function() { return this.m_fixtureA; } /** * Get fixture B in this contact. */ Contact.prototype.GetFixtureB = function() { return this.m_fixtureB; } /** * Get the child primitive index for fixture A. */ Contact.prototype.GetChildIndexA = function() { return this.m_indexA; } /** * Get the child primitive index for fixture B. */ Contact.prototype.GetChildIndexB = function() { return this.m_indexB; } /** * Flag this contact for filtering. Filtering will occur the next time step. */ Contact.prototype.FlagForFiltering = function() { this.m_filterFlag = true; } /** * Override the default friction mixture. You can call this in * ContactListener.PreSolve. This value persists until set or reset. */ Contact.prototype.SetFriction = function(friction) { this.m_friction = friction; } /** * Get the friction. */ Contact.prototype.GetFriction = function() { return this.m_friction; } /** * Reset the friction mixture to the default value. */ Contact.prototype.ResetFriction = function() { this.m_friction = MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction); } /** * Override the default restitution mixture. You can call this in * ContactListener.PreSolve. The value persists until you set or reset. */ Contact.prototype.SetRestitution = function(restitution) { this.m_restitution = restitution; } /** * Get the restitution. */ Contact.prototype.GetRestitution = function() { return this.m_restitution; } /** * Reset the restitution to the default value. */ Contact.prototype.ResetRestitution = function() { this.m_restitution = MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution); } /** * Set the desired tangent speed for a conveyor belt behavior. In meters per * second. */ Contact.prototype.SetTangentSpeed = function(speed) { this.m_tangentSpeed = speed; } /** * Get the desired tangent speed. In meters per second. */ Contact.prototype.GetTangentSpeed = function() { return this.m_tangentSpeed; } /** * Called by Update method, and implemented by subclasses. */ Contact.prototype.Evaluate = function(manifold, xfA, xfB) { this.m_evaluateFcn(manifold, xfA, this.m_fixtureA, this.m_indexA, xfB, this.m_fixtureB, this.m_indexB); }; /** * Updates the contact manifold and touching status. * * Note: do not assume the fixture AABBs are overlapping or are valid. * * @param {function} listener.BeginContact * @param {function} listener.EndContact * @param {function} listener.PreSolve */ Contact.prototype.Update = function(listener) { var oldManifold = this.m_manifold; // Re-enable this contact. this.m_enabledFlag = true; var touching = false; var wasTouching = this.m_touchingFlag; var sensorA = this.m_fixtureA.IsSensor(); var sensorB = this.m_fixtureB.IsSensor(); var sensor = sensorA || sensorB; var bodyA = this.m_fixtureA.GetBody(); var bodyB = this.m_fixtureB.GetBody(); var xfA = bodyA.GetTransform(); var xfB = bodyB.GetTransform(); // Is this contact a sensor? if (sensor) { var shapeA = this.m_fixtureA.GetShape(); var shapeB = this.m_fixtureB.GetShape(); touching = Distance.TestOverlap(shapeA, this.m_indexA, shapeB, this.m_indexB, xfA, xfB); // Sensors don't generate manifolds. this.m_manifold.pointCount = 0; } else { this.Evaluate(this.m_manifold, xfA, xfB); touching = this.m_manifold.pointCount > 0; // Match old contact ids to new contact ids and copy the // stored impulses to warm start the solver. for (var i = 0; i < this.m_manifold.pointCount; ++i) { var mp2 = this.m_manifold.points[i]; mp2.normalImpulse = 0.0; mp2.tangentImpulse = 0.0; var id2 = mp2.id; // ContactID for (var j = 0; j < oldManifold.pointCount; ++j) { var mp1 = oldManifold.points[j]; if (mp1.id.key == id2.key) { mp2.normalImpulse = mp1.normalImpulse; mp2.tangentImpulse = mp1.tangentImpulse; break; } } } if (touching != wasTouching) { bodyA.SetAwake(true); bodyB.SetAwake(true); } } this.m_touchingFlag = touching; if (wasTouching == false && touching == true && listener) { listener.BeginContact(this); } if (wasTouching == true && touching == false && listener) { listener.EndContact(this); } if (sensor == false && touching && listener) { listener.PreSolve(this, oldManifold); } } Contact.prototype.SolvePositionConstraint = function(step) { return this._SolvePositionConstraint(step, false); } Contact.prototype.SolvePositionConstraintTOI = function(step, toiA, toiB) { return this._SolvePositionConstraint(step, true, toiA, toiB); } Contact.prototype._SolvePositionConstraint = function(step, toi, toiA, toiB) { // Assert(manifold.pointCount > 0); var fixtureA = this.m_fixtureA; var fixtureB = this.m_fixtureB; var shapeA = fixtureA.GetShape(); var shapeB = fixtureB.GetShape(); var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); var invMassA = bodyA.m_invMass; var invMassB = bodyB.m_invMass; var invIA = bodyA.m_invI; var invIB = bodyB.m_invI; var localCenterA = bodyA.m_sweep.localCenter; var localCenterB = bodyB.m_sweep.localCenter; var radiusA = shapeA.m_radius; var radiusB = shapeB.m_radius; var manifold = this.m_manifold; var type = manifold.type; var localNormal = manifold.localNormal; var localPointA = manifold.localPoint; var mA = 0.0; var iA = 0.0; if (!toi || bodyA == toiA || bodyA == toiB) { mA = invMassA; iA = invIA; } var mB = 0.0; var iB = 0.0; if (!toi || bodyB == toiA || bodyB == toiB) { mB = invMassB; iB = invIB; } var cA = bodyA.c_position.c; var aA = bodyA.c_position.a; var cB = bodyB.c_position.c; var aB = bodyB.c_position.a; var minSeparation = 0.0; // Solve normal constraints for (var j = 0; j < manifold.pointCount; ++j) { var localPointB = manifold.points[j].localPoint; var xfA = new Transform(); var xfB = new Transform(); bodyA.c_position.GetTransform(xfA, localCenterA); bodyB.c_position.GetTransform(xfB, localCenterB); var normal, point, separation; // TODO: improve switch (type) { case Manifold.e_circles: var pointA = Transform.Mul(xfA, localPointA); var pointB = Transform.Mul(xfB, localPointB); normal = Vec2.Sub(pointB, pointA); var length = normal.Normalize(); point = Vec2.Mid(pointA, pointB); separation = length - radiusA - radiusB; break; case Manifold.e_faceA: normal = Rot.Mul(xfA.q, localNormal); var planePoint = Transform.Mul(xfA, localPointA); var clipPoint = Transform.Mul(xfB, localPointB); separation = Vec2.Dot(clipPoint, normal) - Vec2.Dot(planePoint, normal) - radiusA - radiusB; point = clipPoint; break; case Manifold.e_faceB: normal = Rot.Mul(xfB.q, localNormal); var planePoint = Transform.Mul(xfB, localPointA); var clipPoint = Transform.Mul(xfA, localPointB); separation = Vec2.Dot(clipPoint, normal) - Vec2.Dot(planePoint, normal) - radiusA - radiusB; point = clipPoint; // Ensure normal points from A to B normal.Mul(-1); break; } var rA = Vec2.Sub(point, cA); var rB = Vec2.Sub(point, cB); // Track max constraint error. minSeparation = Math.min(minSeparation, separation); var baumgarte = toi ? Settings.toiBaugarte : Settings.baumgarte; var linearSlop = Settings.linearSlop; // Prevent large corrections and allow slop. var C = Math.clamp(baumgarte * (separation + linearSlop), -Settings.maxLinearCorrection, 0.0); // Compute the effective mass. var rnA = Vec2.Cross(rA, normal); var rnB = Vec2.Cross(rB, normal); var K = mA + mB + iA * rnA * rnA + iB * rnB * rnB; // Compute normal impulse var impulse = K > 0.0 ? -C / K : 0.0; var P = Vec2.Mul(impulse, normal); cA.WSub(mA, P); aA -= iA * Vec2.Cross(rA, P); cB.WAdd(mB, P); aB += iB * Vec2.Cross(rB, P); } bodyA.c_position.c = cA; bodyA.c_position.a = aA; bodyB.c_position.c = cB; bodyB.c_position.a = aB; return minSeparation; } // TODO merge with ManifoldPoint function VelocityConstraintPoint() { this.rA = Vec2(); this.rB = Vec2(); this.normalImpulse = 0; this.tangentImpulse = 0; this.normalMass = 0; this.tangentMass = 0; this.velocityBias = 0; } Contact.prototype.InitVelocityConstraint = function(step) { // Assert(manifold.pointCount > 0); var fixtureA = this.m_fixtureA; var fixtureB = this.m_fixtureB; var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); var restitution = this.m_restitution; var mA = bodyA.m_invMass; var mB = bodyB.m_invMass; var iA = bodyA.m_invI; var iB = bodyB.m_invI; var shapeA = fixtureA.GetShape(); var shapeB = fixtureB.GetShape(); var radiusA = shapeA.m_radius; var radiusB = shapeB.m_radius; var localCenterA = bodyA.m_sweep.localCenter; var localCenterB = bodyB.m_sweep.localCenter; var cA = bodyA.c_position.c; var aA = bodyA.c_position.a; var vA = bodyA.c_velocity.v; var wA = bodyA.c_velocity.w; var cB = bodyB.c_position.c; var aB = bodyB.c_position.a; var vB = bodyB.c_velocity.v; var wB = bodyB.c_velocity.w; var xfA = new Transform(); var xfB = new Transform(); bodyA.c_position.GetTransform(xfA, localCenterA); bodyB.c_position.GetTransform(xfB, localCenterB); var manifold = this.m_manifold; this.v_pointCount = manifold.pointCount; var worldManifold = manifold.GetWorldManifold(null, xfA, radiusA, xfB, radiusB); this.v_normal.Set(worldManifold.normal); for (var j = 0; j < this.v_pointCount; ++j) { var cp = manifold.points[j]; // ManifoldPoint var vcp = this.v_points[j] = new VelocityConstraintPoint(); vcp.rA.Set(Vec2.Sub(worldManifold.points[j], cA)); vcp.rB.Set(Vec2.Sub(worldManifold.points[j], cB)); var rnA = Vec2.Cross(vcp.rA, this.v_normal); var rnB = Vec2.Cross(vcp.rB, this.v_normal); var kNormal = mA + mB + iA * rnA * rnA + iB * rnB * rnB; vcp.normalMass = kNormal > 0.0 ? 1.0 / kNormal : 0.0; var tangent = Vec2.Cross(this.v_normal, 1.0); var rtA = Vec2.Cross(vcp.rA, tangent); var rtB = Vec2.Cross(vcp.rB, tangent); var kTangent = mA + mB + iA * rtA * rtA + iB * rtB * rtB; vcp.tangentMass = kTangent > 0.0 ? 1.0 / kTangent : 0.0; // Setup a velocity bias for restitution. vcp.velocityBias = 0.0; var vbaseB = Vec2.Add(vB, Vec2.Cross(wB, vcp.rB)); var vbaseA = Vec2.Add(vA, Vec2.Cross(wA, vcp.rA)); var vRel = Vec2.Dot(this.v_normal, Vec2.Sub(vbaseB, vbaseA)); if (vRel < -Settings.velocityThreshold) { vcp.velocityBias = -restitution * vRel; } } // If we have two points, then prepare the block solver. if (this.v_pointCount == 2 && step.blockSolve) { var vcp1 = this.v_points[0]; var vcp2 = this.v_points[1]; var rn1A = Vec2.Cross(vcp1.rA, this.v_normal); var rn1B = Vec2.Cross(vcp1.rB, this.v_normal); var rn2A = Vec2.Cross(vcp2.rA, this.v_normal); var rn2B = Vec2.Cross(vcp2.rB, this.v_normal); var k11 = mA + mB + iA * rn1A * rn1A + iB * rn1B * rn1B; var k22 = mA + mB + iA * rn2A * rn2A + iB * rn2B * rn2B; var k12 = mA + mB + iA * rn1A * rn2A + iB * rn1B * rn2B; // Ensure a reasonable condition number. var k_maxConditionNumber = 1000.0; if (k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) { // K is safe to invert. this.v_K.ex.Set(k11, k12); this.v_K.ey.Set(k12, k22); this.v_normalMass.Set(this.v_K.GetInverse()); } else { // The constraints are redundant, just use one. // TODO_ERIN use deepest? this.v_pointCount = 1; } } }; Contact.prototype.WarmStartConstraint = function(step) { return; // TODO var fixtureA = this.m_fixtureA; var fixtureB = this.m_fixtureB; var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); var mA = bodyA.m_invMass; var mB = bodyB.m_invMass; var iA = bodyA.m_invI; var iB = bodyB.m_invI; var vA = bodyA.c_velocity.v; var wA = bodyA.c_velocity.w; var vB = bodyB.c_velocity.v; var wB = bodyB.c_velocity.w; var manifold = this.m_manifold; var pointCount = this.v_pointCount; var normal = this.v_normal; var tangent = Vec2.Cross(normal, 1.0); for (var j = 0; j < pointCount; ++j) { var cp = manifold.points[j]; // ManifoldPoint var vcp = this.v_points[j]; vcp.normalImpulse = step.dtRatio * cp.normalImpulse; vcp.tangentImpulse = step.dtRatio * cp.tangentImpulse; var P = vcp.normalImpulse * normal + vcp.tangentImpulse * tangent; // Vec2 wA -= iA * Vec2.Cross(vcp.rA, P); vA -= mA * P; wB += iB * Vec2.Cross(vcp.rB, P); vB += mB * P; } bodyA.c_velocity.v = vA; bodyA.c_velocity.w = wA; bodyB.c_velocity.v = vB; bodyB.c_velocity.w = wB; }; Contact.prototype.StoreConstraintImpulses = function(step) { return; // TODO var manifold = this.m_manifold; for (var j = 0; j < this.v_pointCount; ++j) { manifold.points[j].normalImpulse = vc.points[j].normalImpulse; manifold.points[j].tangentImpulse = vc.points[j].tangentImpulse; } }; Contact.prototype.SolveVelocityConstraint = function(step) { var fixtureA = this.m_fixtureA; var fixtureB = this.m_fixtureB; var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); var friction = this.m_friction; var tangentSpeed = this.m_tangentSpeed; var mA = bodyA.m_invMass; var mB = bodyB.m_invMass; var iA = bodyA.m_invI; var iB = bodyB.m_invI; var vA = bodyA.c_velocity.v; var wA = bodyA.c_velocity.w; var vB = bodyB.c_velocity.v; var wB = bodyB.c_velocity.w; var normal = this.v_normal; var tangent = Vec2.Cross(normal, 1.0); var pointCount = this.v_pointCount; Assert(pointCount == 1 || pointCount == 2); // Solve tangent constraints first because non-penetration is more important // than friction. for (var j = 0; j < pointCount; ++j) { var vcp = this.v_points[j]; // Relative velocity at contact var dvB = Vec2.Add(vB, Vec2.Cross(wB, vcp.rB)) var dvA = Vec2.Add(vA, Vec2.Cross(wA, vcp.rA)); var dv = Vec2.Sub(dvB, dvA); // Compute tangent force var vt = Vec2.Dot(dv, tangent) - tangentSpeed; var lambda = vcp.tangentMass * (-vt); // Clamp the accumulated force var maxFriction = friction * vcp.normalImpulse; var newImpulse = Math.clamp(vcp.tangentImpulse + lambda, -maxFriction, maxFriction); lambda = newImpulse - vcp.tangentImpulse; vcp.tangentImpulse = newImpulse; // Apply contact impulse var P = Vec2.Mul(lambda, tangent); vA.WSub(mA, P); wA -= iA * Vec2.Cross(vcp.rA, P); vB.WAdd(mB, P); wB += iB * Vec2.Cross(vcp.rB, P); } // Solve normal constraints if (pointCount == 1 || step.blockSolve == false) { for (var i = 0; i < pointCount; ++i) { var vcp = this.v_points[i]; // Relative velocity at contact var dvB = Vec2.Add(vB, Vec2.Cross(wB, vcp.rB)); var dvA = Vec2.Add(vA, Vec2.Cross(wA, vcp.rA)); var dv = Vec2.Sub(dvB, dvA); // Compute normal impulse var vn = Vec2.Dot(dv, normal); var lambda = -vcp.normalMass * (vn - vcp.velocityBias); // Clamp the accumulated impulse var newImpulse = Math.max(vcp.normalImpulse + lambda, 0.0); lambda = newImpulse - vcp.normalImpulse; vcp.normalImpulse = newImpulse; // Apply contact impulse var P = Vec2.Mul(lambda, normal); vA.WSub(mA, P); wA -= iA * Vec2.Cross(vcp.rA, P); vB.WAdd(mB, P); wB += iB * Vec2.Cross(vcp.rB, P); } } else { // Block solver developed in collaboration with Dirk Gregorius (back in // 01/07 on Box2D_Lite). // Build the mini LCP for this contact patch // // vn = A * x + b, vn >= 0, , vn >= 0, x >= 0 and vn_i * x_i = 0 with i = // 1..2 // // A = J * W * JT and J = ( -n, -r1 x n, n, r2 x n ) // b = vn0 - velocityBias // // The system is solved using the "Total enumeration method" (s. Murty). // The complementary constraint vn_i * x_i // implies that we must have in any solution either vn_i = 0 or x_i = 0. // So for the 2D contact problem the cases // vn1 = 0 and vn2 = 0, x1 = 0 and x2 = 0, x1 = 0 and vn2 = 0, x2 = 0 and // vn1 = 0 need to be tested. The first valid // solution that satisfies the problem is chosen. // // In order to account of the accumulated impulse 'a' (because of the // iterative nature of the solver which only requires // that the accumulated impulse is clamped and not the incremental // impulse) we change the impulse variable (x_i). // // Substitute: // // x = a + d // // a := old total impulse // x := new total impulse // d := incremental impulse // // For the current iteration we extend the formula for the incremental // impulse // to compute the new total impulse: // // vn = A * d + b // = A * (x - a) + b // = A * x + b - A * a // = A * x + b' // b' = b - A * a; var cp1 = this.v_points[0]; var cp2 = this.v_points[1]; var a = Vec2(cp1.normalImpulse, cp2.normalImpulse); Assert(a.x >= 0.0 && a.y >= 0.0); // Relative velocity at contact var dv1B = Vec2.Add(vB, Vec2.Cross(wB, cp1.rB)); var dv1A = Vec2.Add(vA, Vec2.Cross(wA, cp1.rA)); var dv1 = Vec2.Sub(dv1B, dv1A); var dv2B = Vec2.Add(vB, Vec2.Cross(wB, cp2.rB)); var dv2A = Vec2.Add(vA, Vec2.Cross(wA, cp2.rA)); var dv2 = Vec2.Sub(dv2B, dv2A); // Compute normal velocity var vn1 = Vec2.Dot(dv1, normal); var vn2 = Vec2.Dot(dv2, normal); var b = Vec2(vn1 - cp1.velocityBias, vn2 - cp2.velocityBias); // Compute b' b.Sub(Mat22.Mul(this.v_K, a)); var k_errorTol = 1e-3; // NOT_USED(k_errorTol); for (;;) { // // Case 1: vn = 0 // // 0 = A * x + b' // // Solve for x: // // x = - inv(A) * b' // var x = Vec2.Neg(Mat22.Mul(this.v_normalMass, b)); if (x.x >= 0.0 && x.y >= 0.0) { // Get the incremental impulse var d = Vec2.Sub(x, a); // Apply incremental impulse var P1 = Vec2.Mul(d.x, normal); var P2 = Vec2.Mul(d.y, normal); vA.WSub(mA, P1, mA, P2); wA -= iA * (Vec2.Cross(cp1.rA, P1) + Vec2.Cross(cp2.rA, P2)); vB.WAdd(mB, P1, mB, P2); wB += iB * (Vec2.Cross(cp1.rB, P1) + Vec2.Cross(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; if (DEBUG_SOLVER) { // Postconditions dv1 = vB + Vec2.Cross(wB, cp1.rB) - vA - Vec2.Cross(wA, cp1.rA); dv2 = vB + Vec2.Cross(wB, cp2.rB) - vA - Vec2.Cross(wA, cp2.rA); // Compute normal velocity vn1 = Dot(dv1, normal); vn2 = Dot(dv2, normal); Assert(Abs(vn1 - cp1.velocityBias) < k_errorTol); Assert(Abs(vn2 - cp2.velocityBias) < k_errorTol); } break; } // // Case 2: vn1 = 0 and x2 = 0 // // 0 = a11 * x1 + a12 * 0 + b1' // vn2 = a21 * x1 + a22 * 0 + b2' // x.x = -cp1.normalMass * b.x; x.y = 0.0; vn1 = 0.0; vn2 = this.v_K.ex.y * x.x + b.y; if (x.x >= 0.0 && vn2 >= 0.0) { // Get the incremental impulse var d = Vec2.Sub(x, a); // Apply incremental impulse var P1 = Vec2.Mul(d.x, normal); var P2 = Vec2.Mul(d.y, normal); vA.WSub(mA, P1, mA, P2); wA -= iA * (Vec2.Cross(cp1.rA, P1) + Vec2.Cross(cp2.rA, P2)); vB.WAdd(mB, P1, mB, P2); wB += iB * (Vec2.Cross(cp1.rB, P1) + Vec2.Cross(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; if (DEBUG_SOLVER) { // Postconditions var dv1B = Vec2.Add(vB, Vec2.Cross(wB, cp1.rB)); var dv1A = Vec2.Add(vA, Vec2.Cross(wA, cp1.rA)); var dv1 = Vec2.Sub(dv1B, dv1A); // Compute normal velocity vn1 = Vec2.Dot(dv1, normal); Assert(Math.abs(vn1 - cp1.velocityBias) < k_errorTol); } break; } // // Case 3: vn2 = 0 and x1 = 0 // // vn1 = a11 * 0 + a12 * x2 + b1' // 0 = a21 * 0 + a22 * x2 + b2' // x.x = 0.0; x.y = -cp2.normalMass * b.y; vn1 = this.v_K.ey.x * x.y + b.x; vn2 = 0.0; if (x.y >= 0.0 && vn1 >= 0.0) { // Resubstitute for the incremental impulse var d = Vec2.Sub(x, a); // Apply incremental impulse var P1 = Vec2.Mul(d.x, normal); var P2 = Vec2.Mul(d.y, normal); vA.WSub(mA, P1, mA, P2); wA -= iA * (Vec2.Cross(cp1.rA, P1) + Vec2.Cross(cp2.rA, P2)); vB.WAdd(mB, P1, mB, P2); wB += iB * (Vec2.Cross(cp1.rB, P1) + Vec2.Cross(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; if (DEBUG_SOLVER) { // Postconditions var dv2B = Vec2.Add(vB, Vec2.Cross(wB, cp2.rB)); var dv2A = Vec2.Add(vA, Vec2.Cross(wA, cp2.rA)); var dv1 = Vec2.Sub(dv2B, dv2A); // Compute normal velocity vn2 = Vec2.Dot(dv2, normal); Assert(Math.abs(vn2 - cp2.velocityBias) < k_errorTol); } break; } // // Case 4: x1 = 0 and x2 = 0 // // vn1 = b1 // vn2 = b2; // x.x = 0.0; x.y = 0.0; vn1 = b.x; vn2 = b.y; if (vn1 >= 0.0 && vn2 >= 0.0) { // Resubstitute for the incremental impulse var d = Vec2.Sub(x, a); // Apply incremental impulse var P1 = Vec2.Mul(d.x, normal); var P2 = Vec2.Mul(d.y, normal); vA.WSub(mA, P1, mA, P2); wA -= iA * (Vec2.Cross(cp1.rA, P1) + Vec2.Cross(cp2.rA, P2)); vB.WAdd(mB, P1, mB, P2); wB += iB * (Vec2.Cross(cp1.rB, P1) + Vec2.Cross(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; break; } // No solution, give up. This is hit sometimes, but it doesn't seem to // matter. break; } } bodyA.c_velocity.v.Set(vA); bodyA.c_velocity.w = wA; bodyB.c_velocity.v.Set(vB); bodyB.c_velocity.w = wB; }; /** * Friction mixing law. The idea is to allow either fixture to drive the * restitution to zero. For example, anything slides on ice. */ function MixFriction(friction1, friction2) { return Math.sqrt(friction1 * friction2); } /** * Restitution mixing law. The idea is allow for anything to bounce off an * inelastic surface. For example, a superball bounces on anything. */ function MixRestitution(restitution1, restitution2) { return restitution1 > restitution2 ? restitution1 : restitution2; } var s_registers = []; /** * @param fn function(fixtureA, indexA, fixtureB, indexB) Contact */ Contact.AddType = function(type1, type2, callback) { s_registers[type1] = s_registers[type1] || {}; s_registers[type1][type2] = callback; } Contact.Create = function(fixtureA, indexA, fixtureB, indexB) { var typeA = fixtureA.GetType(); // Shape.Type var typeB = fixtureB.GetType(); // Shape.Type // TODO: pool contacts var contact, evaluateFcn; if (evaluateFcn = s_registers[typeA] && s_registers[typeA][typeB]) { contact = new Contact(fixtureA, indexA, fixtureB, indexB, evaluateFcn); } else if (evaluateFcn = s_registers[typeB] && s_registers[typeB][typeA]) { contact = new Contact(fixtureB, indexB, fixtureA, indexA, evaluateFcn); } else { return null; } // Contact creation may swap fixtures. fixtureA = contact.GetFixtureA(); fixtureB = contact.GetFixtureB(); indexA = contact.GetChildIndexA(); indexB = contact.GetChildIndexB(); var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); // Connect to body A contact.m_nodeA.contact = contact; contact.m_nodeA.other = bodyB; contact.m_nodeA.prev = null; contact.m_nodeA.next = bodyA.m_contactList; if (bodyA.m_contactList != null) { bodyA.m_contactList.prev = contact.m_nodeA; } bodyA.m_contactList = contact.m_nodeA; // Connect to body B contact.m_nodeB.contact = contact; contact.m_nodeB.other = bodyA; contact.m_nodeB.prev = null; contact.m_nodeB.next = bodyB.m_contactList; if (bodyB.m_contactList != null) { bodyB.m_contactList.prev = contact.m_nodeB; } bodyB.m_contactList = contact.m_nodeB; // Wake up the bodies if (fixtureA.IsSensor() == false && fixtureB.IsSensor() == false) { bodyA.SetAwake(true); bodyB.SetAwake(true); } return contact; } Contact.Destroy = function(contact, callback) { var fixtureA = contact.m_fixtureA; var fixtureB = contact.m_fixtureB; var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); if (contact.IsTouching()) { callback.EndContact(contact); } // Remove from body 1 if (contact.m_nodeA.prev) { contact.m_nodeA.prev.next = contact.m_nodeA.next; } if (contact.m_nodeA.next) { contact.m_nodeA.next.prev = contact.m_nodeA.prev; } if (contact.m_nodeA == bodyA.m_contactList) { bodyA.m_contactList = contact.m_nodeA.next; } // Remove from body 2 if (contact.m_nodeB.prev) { contact.m_nodeB.prev.next = contact.m_nodeB.next; } if (contact.m_nodeB.next) { contact.m_nodeB.next.prev = contact.m_nodeB.prev; } if (contact.m_nodeB == bodyB.m_contactList) { bodyB.m_contactList = contact.m_nodeB.next; } if (contact.m_manifold.pointCount > 0 && fixtureA.IsSensor() == false && fixtureB.IsSensor() == false) { bodyA.SetAwake(true); bodyB.SetAwake(true); } var typeA = fixtureA.GetType(); // Shape.Type var typeB = fixtureB.GetType(); // Shape.Type var destroyFcn = s_registers[typeA][typeB].destroyFcn; if (typeof destroyFcn === 'function') { destroyFcn(contact); } }