UNPKG

planck-js

Version:

2D physics engine for JavaScript/HTML5 game development

470 lines (397 loc) 15.8 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. */ module.exports = GearJoint; var options = require('../util/options'); var create = require('../util/create'); var Settings = require('../Settings'); var Math = require('../common/Math'); var Vec2 = require('../common/Vec2'); var Vec3 = require('../common/Vec3'); var Mat22 = require('../common/Mat22'); var Mat33 = require('../common/Mat33'); var Rot = require('../common/Rot'); var Sweep = require('../common/Sweep'); var Transform = require('../common/Transform'); var Velocity = require('../common/Velocity'); var Position = require('../common/Position'); var Joint = require('../Joint'); var RevoluteJoint = require('./RevoluteJoint'); var PrismaticJoint = require('./PrismaticJoint'); GearJoint.TYPE = 'gear-joint'; GearJoint._super = Joint; GearJoint.prototype = create(GearJoint._super.prototype); /** * Gear joint definition. * * @prop {float} ratio The gear ratio. See GearJoint for explanation. */ var GearJointDef = { ratio : 1.0 }; /** * A gear joint is used to connect two joints together. Either joint can be a * revolute or prismatic joint. You specify a gear ratio to bind the motions * together: coordinate1 + ratio * coordinate2 = constant * * The ratio can be negative or positive. If one joint is a revolute joint and * the other joint is a prismatic joint, then the ratio will have units of * length or units of 1/length. Warning: You have to manually destroy the gear * joint if joint1 or joint2 is destroyed. * * This definition requires two existing revolute or prismatic joints (any * combination will work). * * @param {RevoluteJoint|PrismaticJoint} joint1 The first revolute/prismatic * joint attached to the gear joint. * @param {PrismaticJoint|RevoluteJoint} joint2 The second prismatic/revolute * joint attached to the gear joint. */ function GearJoint(def, bodyA, bodyB, joint1, joint2, ratio) { if (!(this instanceof GearJoint)) { return new GearJoint(def, bodyA, bodyB, joint1, joint2, ratio); } def = options(def, GearJointDef); Joint.call(this, def, bodyA, bodyB); this.m_type = GearJoint.TYPE; Assert(joint1.m_type == RevoluteJoint.TYPE || joint1.m_type == PrismaticJoint.TYPE); Assert(joint2.m_type == RevoluteJoint.TYPE || joint2.m_type == PrismaticJoint.TYPE); this.m_joint1 = joint1; this.m_joint2 = joint2; this.m_type1 = this.m_joint1.GetType(); this.m_type2 = this.m_joint2.GetType(); // joint1 connects body A to body C // joint2 connects body B to body D var coordinateA, coordinateB; // float // TODO_ERIN there might be some problem with the joint edges in Joint. this.m_bodyC = this.m_joint1.GetBodyA(); this.m_bodyA = this.m_joint1.GetBodyB(); // Get geometry of joint1 var xfA = this.m_bodyA.m_xf; var aA = this.m_bodyA.m_sweep.a; var xfC = this.m_bodyC.m_xf; var aC = this.m_bodyC.m_sweep.a; if (this.m_type1 == RevoluteJoint.TYPE) { var revolute = joint1;// RevoluteJoint this.m_localAnchorC = revolute.m_localAnchorA; this.m_localAnchorA = revolute.m_localAnchorB; this.m_referenceAngleA = revolute.m_referenceAngle; this.m_localAxisC = Vec2(); coordinateA = aA - aC - this.m_referenceAngleA; } else { var prismatic = joint1; // PrismaticJoint this.m_localAnchorC = prismatic.m_localAnchorA; this.m_localAnchorA = prismatic.m_localAnchorB; this.m_referenceAngleA = prismatic.m_referenceAngle; this.m_localAxisC = prismatic.m_localXAxisA; var pC = this.m_localAnchorC; var pA = Rot.MulT(xfC.q, Vec2.Add(Rot.Mul(xfA.q, this.m_localAnchorA), Vec2 .Sub(xfA.p, xfC.p))); coordinateA = Vec2.Dot(pA, this.m_localAxisC) - Vec2.Dot(pC, this.m_localAxisC); } this.m_bodyD = this.m_joint2.GetBodyA(); this.m_bodyB = this.m_joint2.GetBodyB(); // Get geometry of joint2 var xfB = this.m_bodyB.m_xf; var aB = this.m_bodyB.m_sweep.a; var xfD = this.m_bodyD.m_xf; var aD = this.m_bodyD.m_sweep.a; if (this.m_type2 == RevoluteJoint.TYPE) { var revolute = joint2; // RevoluteJoint this.m_localAnchorD = revolute.m_localAnchorA; this.m_localAnchorB = revolute.m_localAnchorB; this.m_referenceAngleB = revolute.m_referenceAngle; this.m_localAxisD = Vec2(); coordinateB = aB - aD - this.m_referenceAngleB; } else { var prismatic = joint2; // PrismaticJoint this.m_localAnchorD = prismatic.m_localAnchorA; this.m_localAnchorB = prismatic.m_localAnchorB; this.m_referenceAngleB = prismatic.m_referenceAngle; this.m_localAxisD = prismatic.m_localXAxisA; var pD = this.m_localAnchorD; var pB = Rot.MulT(xfD.q, Vec2.Add(Rot.Mul(xfB.q, this.m_localAnchorB), Vec2 .Sub(xfB.p, xfD.p))); coordinateB = Vec2.Dot(pB, this.m_localAxisD) - Vec2.Dot(pD, this.m_localAxisD); } this.m_ratio = ratio || def.ratio; this.m_constant = coordinateA + this.m_ratio * coordinateB; this.m_impulse = 0.0; // Solver temp this.m_lcA, this.m_lcB, this.m_lcC, this.m_lcD; // Vec2 this.m_mA, this.m_mB, this.m_mC, this.m_mD; // float this.m_iA, this.m_iB, this.m_iC, this.m_iD; // float this.m_JvAC, this.m_JvBD; // Vec2 this.m_JwA, this.m_JwB, this.m_JwC, this.m_JwD; // float this.m_mass; // float // Gear Joint: // C0 = (coordinate1 + ratio * coordinate2)_initial // C = (coordinate1 + ratio * coordinate2) - C0 = 0 // J = [J1 ratio * J2] // K = J * invM * JT // = J1 * invM1 * J1T + ratio * ratio * J2 * invM2 * J2T // // Revolute: // coordinate = rotation // Cdot = angularVelocity // J = [0 0 1] // K = J * invM * JT = invI // // Prismatic: // coordinate = dot(p - pg, ug) // Cdot = dot(v + cross(w, r), ug) // J = [ug cross(r, ug)] // K = J * invM * JT = invMass + invI * cross(r, ug)^2 }; /** * Get the first joint. */ GearJoint.prototype.GetJoint1 = function() { return this.m_joint1; } /** * Get the second joint. */ GearJoint.prototype.GetJoint2 = function() { return this.m_joint2; } /** * Set/Get the gear ratio. */ GearJoint.prototype.SetRatio = function(ratio) { Assert(IsValid(ratio)); this.m_ratio = ratio; } GearJoint.prototype.GetRatio = function() { return this.m_ratio; } GearJoint.prototype.GetAnchorA = function() { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA); } GearJoint.prototype.GetAnchorB = function() { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB); } GearJoint.prototype.GetReactionForce = function(inv_dt) { var P = this.m_impulse * this.m_JvAC; // Vec2 return inv_dt * P; } GearJoint.prototype.GetReactionTorque = function(inv_dt) { var L = this.m_impulse * this.m_JwA; // float return inv_dt * L; } GearJoint.prototype.InitVelocityConstraints = function(step) { this.m_lcA = this.m_bodyA.m_sweep.localCenter; this.m_lcB = this.m_bodyB.m_sweep.localCenter; this.m_lcC = this.m_bodyC.m_sweep.localCenter; this.m_lcD = this.m_bodyD.m_sweep.localCenter; this.m_mA = this.m_bodyA.m_invMass; this.m_mB = this.m_bodyB.m_invMass; this.m_mC = this.m_bodyC.m_invMass; this.m_mD = this.m_bodyD.m_invMass; this.m_iA = this.m_bodyA.m_invI; this.m_iB = this.m_bodyB.m_invI; this.m_iC = this.m_bodyC.m_invI; this.m_iD = this.m_bodyD.m_invI; var aA = this.m_bodyA.c_position.a; var vA = this.m_bodyA.c_velocity.v; var wA = this.m_bodyA.c_velocity.w; var aB = this.m_bodyB.c_position.a; var vB = this.m_bodyB.c_velocity.v; var wB = this.m_bodyB.c_velocity.w; var aC = this.m_bodyC.c_position.a; var vC = this.m_bodyC.c_velocity.v; var wC = this.m_bodyC.c_velocity.w; var aD = this.m_bodyD.c_position.a; var vD = this.m_bodyD.c_velocity.v; var wD = this.m_bodyD.c_velocity.w; var qA = Rot(aA); var qB = Rot(aB); var qC = Rot(aC); var qD = Rot(aD); this.m_mass = 0.0; if (this.m_type1 == RevoluteJoint.TYPE) { this.m_JvAC = Vec2(); this.m_JwA = 1.0; this.m_JwC = 1.0; this.m_mass += this.m_iA + this.m_iC; } else { var u = Rot.Mul(qC, this.m_localAxisC); // Vec2 var rC = Rot.MulSub(qC, this.m_localAnchorC, this.m_lcC); // Vec2 var rA = Rot.MulSub(qA, this.m_localAnchorA, this.m_lcA); // Vec2 this.m_JvAC = u; this.m_JwC = Vec2.Cross(rC, u); this.m_JwA = Vec2.Cross(rA, u); this.m_mass += this.m_mC + this.m_mA + this.m_iC * this.m_JwC * this.m_JwC + this.m_iA * this.m_JwA * this.m_JwA; } if (this.m_type2 == RevoluteJoint.TYPE) { this.m_JvBD = Vec2(); this.m_JwB = this.m_ratio; this.m_JwD = this.m_ratio; this.m_mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD); } else { var u = Rot.Mul(qD, this.m_localAxisD); // Vec2 var rD = Rot.MulSub(qD, this.m_localAnchorD, this.m_lcD); // Vec2 var rB = Rot.MulSub(qB, this.m_localAnchorB, this.m_lcB); // Vec2 this.m_JvBD = Vec2.Mul(this.m_ratio, u); this.m_JwD = this.m_ratio * Vec2.Cross(rD, u); this.m_JwB = this.m_ratio * Vec2.Cross(rB, u); this.m_mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * this.m_JwD * this.m_JwD + this.m_iB * this.m_JwB * this.m_JwB; } // Compute effective mass. this.m_mass = this.m_mass > 0.0 ? 1.0 / this.m_mass : 0.0; if (step.warmStarting) { vA.WAdd(this.m_mA * this.m_impulse, this.m_JvAC); wA += this.m_iA * this.m_impulse * this.m_JwA; vB.WAdd(this.m_mB * this.m_impulse, this.m_JvBD); wB += this.m_iB * this.m_impulse * this.m_JwB; vC.WSub(this.m_mC * this.m_impulse, this.m_JvAC); wC -= this.m_iC * this.m_impulse * this.m_JwC; vD.WSub(this.m_mD * this.m_impulse, this.m_JvBD); wD -= this.m_iD * this.m_impulse * this.m_JwD; } else { this.m_impulse = 0.0; } this.m_bodyA.c_velocity.v.Set(vA); this.m_bodyA.c_velocity.w = wA; this.m_bodyB.c_velocity.v.Set(vB); this.m_bodyB.c_velocity.w = wB; this.m_bodyC.c_velocity.v.Set(vC); this.m_bodyC.c_velocity.w = wC; this.m_bodyD.c_velocity.v.Set(vD); this.m_bodyD.c_velocity.w = wD; } GearJoint.prototype.SolveVelocityConstraints = function(step) { var vA = this.m_bodyA.c_velocity.v; var wA = this.m_bodyA.c_velocity.w; var vB = this.m_bodyB.c_velocity.v; var wB = this.m_bodyB.c_velocity.w; var vC = this.m_bodyC.c_velocity.v; var wC = this.m_bodyC.c_velocity.w; var vD = this.m_bodyD.c_velocity.v; var wD = this.m_bodyD.c_velocity.w; var Cdot = Vec2.Dot(this.m_JvAC, vA) - Vec2.Dot(this.m_JvAC, vC) + Vec2.Dot(this.m_JvBD, vB) - Vec2.Dot(this.m_JvBD, vD); // float Cdot += (this.m_JwA * wA - this.m_JwC * wC) + (this.m_JwB * wB - this.m_JwD * wD); var impulse = -this.m_mass * Cdot; // float this.m_impulse += impulse; vA.WAdd(this.m_mA * impulse, this.m_JvAC); wA += this.m_iA * impulse * this.m_JwA; vB.WAdd(this.m_mB * impulse, this.m_JvBD); wB += this.m_iB * impulse * this.m_JwB; vC.WSub(this.m_mC * impulse, this.m_JvAC); wC -= this.m_iC * impulse * this.m_JwC; vD.WSub(this.m_mD * impulse, this.m_JvBD); wD -= this.m_iD * impulse * this.m_JwD; this.m_bodyA.c_velocity.v.Set(vA); this.m_bodyA.c_velocity.w = wA; this.m_bodyB.c_velocity.v.Set(vB); this.m_bodyB.c_velocity.w = wB; this.m_bodyC.c_velocity.v.Set(vC); this.m_bodyC.c_velocity.w = wC; this.m_bodyD.c_velocity.v.Set(vD); this.m_bodyD.c_velocity.w = wD; } GearJoint.prototype.SolvePositionConstraints = function(step) { var cA = this.m_bodyA.c_position.c; var aA = this.m_bodyA.c_position.a; var cB = this.m_bodyB.c_position.c; var aB = this.m_bodyB.c_position.a; var cC = this.m_bodyC.c_position.c; var aC = this.m_bodyC.c_position.a; var cD = this.m_bodyD.c_position.c; var aD = this.m_bodyD.c_position.a; var qA = Rot(aA); var qB = Rot(aB); var qC = Rot(aC); var qD = Rot(aD); var linearError = 0.0; // float var coordinateA, coordinateB; // float var JvAC, JvBD; // Vec2 var JwA, JwB, JwC, JwD; // float var mass = 0.0; // float if (this.m_type1 == RevoluteJoint.TYPE) { JvAC = Vec2(); JwA = 1.0; JwC = 1.0; mass += this.m_iA + this.m_iC; coordinateA = aA - aC - this.m_referenceAngleA; } else { var u = Rot.Mul(qC, this.m_localAxisC); // Vec2 var rC = Rot.MulSub(qC, this.m_localAnchorC, this.m_lcC); // Vec2 var rA = Rot.MulSub(qA, this.m_localAnchorA, this.m_lcA); // Vec2 JvAC = u; JwC = Vec2.Cross(rC, u); JwA = Vec2.Cross(rA, u); mass += this.m_mC + this.m_mA + this.m_iC * JwC * JwC + this.m_iA * JwA * JwA; var pC = this.m_localAnchorC - this.m_lcC; // Vec2 var pA = Rot.MulT(qC, Vec2.Add(rA, Vec2.Sub(cA, cC))); // Vec2 coordinateA = Dot(pA - pC, this.m_localAxisC); } if (this.m_type2 == RevoluteJoint.TYPE) { JvBD = Vec2(); JwB = this.m_ratio; JwD = this.m_ratio; mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD); coordinateB = aB - aD - this.m_referenceAngleB; } else { var u = Rot.Mul(qD, this.m_localAxisD); var rD = Rot.MulSub(qD, this.m_localAnchorD, this.m_lcD); var rB = Rot.MulSub(qB, this.m_localAnchorB, this.m_lcB); JvBD = Vec2.Mul(this.m_ratio, u); JwD = this.m_ratio * Vec2.Cross(rD, u); JwB = this.m_ratio * Vec2.Cross(rB, u); mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * JwD * JwD + this.m_iB * JwB * JwB; var pD = Vec2.Sub(this.m_localAnchorD, this.m_lcD); // Vec2 var pB = Rot.MulT(qD, Vec2.Add(rB, Vec2.Sub(cB, cD))); // Vec2 coordinateB = Vec2.Dot(pB, this.m_localAxisD) - Vec2.Dot(pD, this.m_localAxisD); } var C = (coordinateA + this.m_ratio * coordinateB) - this.m_constant; // float var impulse = 0.0; // float if (mass > 0.0) { impulse = -C / mass; } cA.WAdd(this.m_mA * impulse, JvAC); aA += this.m_iA * impulse * JwA; cB.WAdd(this.m_mB * impulse, JvBD); aB += this.m_iB * impulse * JwB; cC.WAdd(this.m_mC * impulse, JvAC); aC -= this.m_iC * impulse * JwC; cD.WAdd(this.m_mD * impulse, JvBD); aD -= this.m_iD * impulse * JwD; this.m_bodyA.c_position.c.Set(cA); this.m_bodyA.c_position.a = aA; this.m_bodyB.c_position.c.Set(cB); this.m_bodyB.c_position.a = aB; this.m_bodyC.c_position.c.Set(cC); this.m_bodyC.c_position.a = aC; this.m_bodyD.c_position.c.Set(cD); this.m_bodyD.c_position.a = aD; // TODO_ERIN not implemented return linearError < Settings.linearSlop; }