UNPKG

planck-js

Version:

2D physics engine for JavaScript/HTML5 game development

287 lines (232 loc) 8.37 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 = MouseJoint; var options = require('../util/options'); var create = require('../util/create'); 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'); MouseJoint.TYPE = 'mouse-joint'; MouseJoint._super = Joint; MouseJoint.prototype = create(MouseJoint._super.prototype); /** * Mouse joint definition. This requires a world target point, tuning * parameters, and the time step. * * @prop [maxForce = 0.0] The maximum constraint force that can be exerted to * move the candidate body. Usually you will express as some multiple of * the weight (multiplier * mass * gravity). * @prop [frequencyHz = 5.0] The response speed. * @prop [dampingRatio = 0.7] The damping ratio. 0 = no damping, 1 = critical * damping. */ var MouseJointDef = { maxForce : 0.0, frequencyHz : 5.0, dampingRatio : 0.7 }; /** * A mouse joint is used to make a point on a body track a specified world * point. This a soft constraint with a maximum force. This allows the * constraint to stretch and without applying huge forces. * * NOTE: this joint is not documented in the manual because it was developed to * be used in the testbed. If you want to learn how to use the mouse joint, look * at the testbed. * * @prop {Vec2} target The initial world target point. This is assumed to * coincide with the body anchor initially. */ function MouseJoint(def, bodyA, bodyB, target) { if (!(this instanceof MouseJoint)) { return new MouseJoint(def, bodyA, bodyB, target); } def = options(def, MouseJointDef); Joint.call(this, def, bodyA, bodyB); this.m_type = MouseJoint.TYPE; Assert(Math.isFinite(def.maxForce) && def.maxForce >= 0.0); Assert(Math.isFinite(def.frequencyHz) && def.frequencyHz >= 0.0); Assert(Math.isFinite(def.dampingRatio) && def.dampingRatio >= 0.0); this.m_targetA = Vec2(target); this.m_localAnchorB = Transform.MulT(this.m_bodyB.GetTransform(), this.m_targetA); this.m_maxForce = def.maxForce; this.m_impulse = Vec2(); this.m_frequencyHz = def.frequencyHz; this.m_dampingRatio = def.dampingRatio; this.m_beta = 0.0; this.m_gamma = 0.0; // Solver temp this.m_rB = Vec2(); this.m_localCenterB = Vec2(); this.m_invMassB = 0.0; this.m_invIB = 0.0; this.mass = new Mat22() this.m_C = Vec2() // p = attached point, m = mouse point // C = p - m // Cdot = v // = v + cross(w, r) // J = [I r_skew] // Identity used: // w k % (rx i + ry j) = w * (-ry i + rx j) } /** * Use this to update the target point. */ MouseJoint.prototype.SetTarget = function(target) { if (this.m_bodyB.IsAwake() == false) { this.m_bodyB.SetAwake(true); } this.m_targetA = Vec2(target); } MouseJoint.prototype.GetTarget = function() { return this.m_targetA; } /** * Set/get the maximum force in Newtons. */ MouseJoint.prototype.SetMaxForce = function(force) { this.m_maxForce = force; } MouseJoint.GetMaxForce = function() { return this.m_maxForce; } /** * Set/get the frequency in Hertz. */ MouseJoint.prototype.SetFrequency = function(hz) { this.m_frequencyHz = hz; } MouseJoint.prototype.GetFrequency = function() { return this.m_frequencyHz; } /** * Set/get the damping ratio (dimensionless). */ MouseJoint.prototype.SetDampingRatio = function(ratio) { this.m_dampingRatio = ratio; } MouseJoint.prototype.GetDampingRatio = function() { return this.m_dampingRatio; } MouseJoint.prototype.GetAnchorA = function() { return Vec2(this.m_targetA); } MouseJoint.prototype.GetAnchorB = function() { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB); } MouseJoint.prototype.GetReactionForce = function(inv_dt) { return Vec2.Mul(inv_dt, this.m_impulse); } MouseJoint.prototype.GetReactionTorque = function(inv_dt) { return inv_dt * 0.0; } MouseJoint.prototype.ShiftOrigin = function(newOrigin) { this.m_targetA.Sub(newOrigin); } MouseJoint.prototype.InitVelocityConstraints = function(step) { this.m_localCenterB = this.m_bodyB.m_sweep.localCenter; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIB = this.m_bodyB.m_invI; var position = this.m_bodyB.c_position; var velocity = this.m_bodyB.c_velocity; var cB = position.c; var aB = position.a; var vB = velocity.v; var wB = velocity.w; var qB = new Rot(aB); var mass = this.m_bodyB.GetMass(); // Frequency var omega = 2.0 * Math.PI * this.m_frequencyHz; // Damping coefficient var d = 2.0 * mass * this.m_dampingRatio * omega; // Spring stiffness var k = mass * (omega * omega); // magic formulas // gamma has units of inverse mass. // beta has units of inverse time. var h = step.dt; Assert(d + h * k > Math.EPSILON); this.m_gamma = h * (d + h * k); if (this.m_gamma != 0.0) { this.m_gamma = 1.0 / this.m_gamma; } this.m_beta = h * k * this.m_gamma; // Compute the effective mass matrix. this.m_rB = Rot.Mul(qB, Vec2.Sub(this.m_localAnchorB, this.m_localCenterB)); // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * // invI2 * skew(r2)] // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y // -r1.x*r1.y] // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] var K = new Mat22(); K.ex.x = this.m_invMassB + this.m_invIB * this.m_rB.y * this.m_rB.y + this.m_gamma; K.ex.y = -this.m_invIB * this.m_rB.x * this.m_rB.y; K.ey.x = K.ex.y; K.ey.y = this.m_invMassB + this.m_invIB * this.m_rB.x * this.m_rB.x + this.m_gamma; this.m_mass = K.GetInverse(); this.m_C.Set(cB); this.m_C.WAdd(1, this.m_rB, -1, this.m_targetA); this.m_C.Mul(this.m_beta); // Cheat with some damping wB *= 0.98; if (step.warmStarting) { this.m_impulse *= step.dtRatio; vB.WAdd(this.m_invMassB, this.m_impulse); wB += this.m_invIB * Cross(this.m_rB, this.m_impulse); } else { this.m_impulse.SetZero(); } velocity.v.Set(vB); velocity.w = wB; } MouseJoint.prototype.SolveVelocityConstraints = function(step) { var velocity = this.m_bodyB.c_velocity; var vB = Vec2(velocity.v); var wB = velocity.w; // Cdot = v + cross(w, r) var Cdot = Vec2.Cross(wB, this.m_rB); Cdot.Add(vB); Cdot.WAdd(1, this.m_C, this.m_gamma, this.m_impulse); Cdot.Neg(); var impulse = Mat22.Mul(this.m_mass, Cdot); var oldImpulse = Vec2(this.m_impulse); this.m_impulse.Add(impulse); var maxImpulse = step.dt * this.m_maxForce; this.m_impulse.Clamp(maxImpulse); impulse = Vec2.Sub(this.m_impulse, oldImpulse); vB.WAdd(this.m_invMassB, impulse); wB += this.m_invIB * Vec2.Cross(this.m_rB, impulse); velocity.v.Set(vB); velocity.w = wB; } MouseJoint.prototype.SolvePositionConstraints = function(step) { return true; }