planck-js
Version:
2D physics engine for JavaScript/HTML5 game development
287 lines (232 loc) • 8.37 kB
JavaScript
/*
* 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;
}