mirror of
https://github.com/wassname/phaser.git
synced 2026-07-21 12:40:56 +08:00
377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
/*
|
|
* Copyright (c) 2012 Ju Hyung Lee
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
* and associated documentation files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or
|
|
* substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Wheel Joint
|
|
//
|
|
// Point-to-Line constraint:
|
|
// d = p2 - p1
|
|
// n = normalize(perp(d))
|
|
// C = dot(n, d)
|
|
// Cdot = dot(d, dn/dt) + dot(n dd/dt)
|
|
// = dot(d, cross(w1, n)) + dot(n, v2 + cross(w2, r2) - v1 - cross(w1, r1))
|
|
// = dot(d, cross(w1, n)) + dot(n, v2) + dot(n, cross(w2, r2)) - dot(n, v1) - dot(n, cross(w1, r1))
|
|
// = -dot(n, v1) - dot(cross(d + r1, n), w1) + dot(n, v2) + dot(cross(r2, n), w2)
|
|
// J = [ -n, -sn1, n, sn2 ]
|
|
// sn1 = cross(r1 + d, n)
|
|
// sn2 = cross(r2, n)
|
|
//
|
|
// impulse = JT * lambda = [ -n * lambda, -(sn1 * lambda), n * lambda, sn2 * lambda ]
|
|
//
|
|
// Spring constraint:
|
|
// u = normalize(d)
|
|
// C = dot(u, d)
|
|
// Cdot = -dot(u, v1) - dot(cross(d + r1, u), w1) + dot(u, v2) + dot(cross(r2, u), w2)
|
|
// J = [ -u, -su1, u, su2 ]
|
|
// su1 = cross(r1 + d, u)
|
|
// su2 = cross(r2, u)
|
|
//
|
|
// impulse = JT * lambda = [ -u * lambda, -(su1 * lambda), u * lambda, su2 * lambda ]
|
|
//
|
|
// Motor rotational constraint:
|
|
// Cdot = w2 - w1
|
|
// J = [ 0, -1, 0, 1 ]
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
WheelJoint = function(body1, body2, anchor1, anchor2) {
|
|
Joint.call(this, Joint.TYPE_WHEEL, body1, body2, true);
|
|
|
|
// Local anchor points
|
|
this.anchor1 = this.body1.getLocalPoint(anchor1);
|
|
this.anchor2 = this.body2.getLocalPoint(anchor2);
|
|
|
|
var d = vec2.sub(anchor2, anchor1);
|
|
|
|
// Rest length
|
|
this.restLength = d.length();
|
|
|
|
// Body1's local axis
|
|
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
|
|
this.n_local = vec2.perp(this.u_local);
|
|
|
|
// Accumulated impulse
|
|
this.lambda_acc = 0;
|
|
this.motorLambda_acc = 0;
|
|
this.springLambda_acc = 0;
|
|
|
|
// Motor
|
|
this.motorEnabled = false;
|
|
this.motorSpeed = 0;
|
|
this.maxMotorTorque = 0;
|
|
|
|
// Soft constraint coefficients
|
|
this.gamma = 0;
|
|
this.beta_c = 0;
|
|
|
|
// Spring coefficients
|
|
this.frequencyHz = 0;
|
|
this.dampingRatio = 0;
|
|
}
|
|
|
|
WheelJoint.prototype = new Joint;
|
|
WheelJoint.prototype.constructor = WheelJoint;
|
|
|
|
WheelJoint.prototype.setWorldAnchor1 = function(anchor1) {
|
|
this.anchor1 = this.body1.getLocalPoint(anchor1);
|
|
|
|
var d = vec2.sub(this.getWorldAnchor2(), anchor1);
|
|
|
|
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
|
|
this.n_local = vec2.perp(this.u_local);
|
|
}
|
|
|
|
WheelJoint.prototype.setWorldAnchor2 = function(anchor2) {
|
|
this.anchor2 = this.body2.getLocalPoint(anchor2);
|
|
|
|
var d = vec2.sub(anchor2, this.getWorldAnchor1());
|
|
|
|
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
|
|
this.n_local = vec2.perp(this.u_local);
|
|
}
|
|
|
|
WheelJoint.prototype.serialize = function() {
|
|
return {
|
|
"type": "WheelJoint",
|
|
"body1": this.body1.id,
|
|
"body2": this.body2.id,
|
|
"anchor1": this.body1.getWorldPoint(this.anchor1),
|
|
"anchor2": this.body2.getWorldPoint(this.anchor2),
|
|
"collideConnected": this.collideConnected,
|
|
"maxForce": this.maxForce,
|
|
"breakable": this.breakable,
|
|
"motorEnabled": this.motorEnabled,
|
|
"motorSpeed": this.motorSpeed,
|
|
"maxMotorTorque": this.maxMotorTorque,
|
|
"frequencyHz": this.frequencyHz,
|
|
"dampingRatio": this.dampingRatio
|
|
};
|
|
}
|
|
|
|
WheelJoint.prototype.setSpringFrequencyHz = function(frequencyHz) {
|
|
// NOTE: frequencyHz should be limited to under 4 times time steps
|
|
this.frequencyHz = frequencyHz;
|
|
}
|
|
|
|
WheelJoint.prototype.setSpringDampingRatio = function(dampingRatio) {
|
|
this.dampingRatio = dampingRatio;
|
|
}
|
|
|
|
WheelJoint.prototype.enableMotor = function(flag) {
|
|
this.motorEnabled = flag;
|
|
}
|
|
|
|
WheelJoint.prototype.setMotorSpeed = function(speed) {
|
|
this.motorSpeed = speed;
|
|
}
|
|
|
|
WheelJoint.prototype.setMaxMotorTorque = function(torque) {
|
|
this.maxMotorTorque = torque;
|
|
}
|
|
|
|
WheelJoint.prototype.initSolver = function(dt, warmStarting) {
|
|
var body1 = this.body1;
|
|
var body2 = this.body2;
|
|
|
|
// Max impulse
|
|
this.maxImpulse = this.maxForce * dt;
|
|
|
|
// Transformed r1, r2
|
|
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
|
|
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
|
|
|
|
// World anchor points
|
|
var p1 = vec2.add(body1.p, this.r1);
|
|
var p2 = vec2.add(body2.p, this.r2);
|
|
|
|
// Delta vector between world anchor points
|
|
var d = vec2.sub(p2, p1);
|
|
|
|
// r1 + d
|
|
this.r1_d = vec2.add(this.r1, d);
|
|
|
|
// World line normal
|
|
this.n = vec2.rotate(this.n_local, body1.a);
|
|
|
|
// sn1, sn2
|
|
this.sn1 = vec2.cross(this.r1_d, this.n);
|
|
this.sn2 = vec2.cross(this.r2, this.n);
|
|
|
|
// invEM = J * invM * JT
|
|
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.sn1 * this.sn1 + body2.i_inv * this.sn2 * this.sn2;
|
|
this.em = em_inv > 0 ? 1 / em_inv : em_inv;
|
|
|
|
// Compute soft constraint parameters
|
|
if (this.frequencyHz > 0) {
|
|
// World delta axis
|
|
this.u = vec2.rotate(this.u_local, body1.a);
|
|
|
|
// su1, su2
|
|
this.su1 = vec2.cross(this.r1_d, this.u);
|
|
this.su2 = vec2.cross(this.r2, this.u);
|
|
|
|
// invEM = J * invM * JT
|
|
var springEm_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.su1 * this.su1 + body2.i_inv * this.su2 * this.su2;
|
|
springEm = springEm_inv == 0 ? 0 : 1 / springEm_inv;
|
|
|
|
// Frequency
|
|
var omega = 2 * Math.PI * this.frequencyHz;
|
|
|
|
// Spring stiffness
|
|
var k = springEm * omega * omega;
|
|
|
|
// Damping coefficient
|
|
var c = springEm * 2 * this.dampingRatio * omega;
|
|
|
|
// Soft constraint formulas
|
|
// gamma and beta are divided by dt to reduce computation
|
|
this.gamma = (c + k * dt) * dt;
|
|
this.gamma = this.gamma == 0 ? 0 : 1 / this.gamma;
|
|
var beta = dt * k * this.gamma;
|
|
|
|
// Position constraint
|
|
var pc = vec2.dot(d, this.u) - this.restLength;
|
|
this.beta_c = beta * pc;
|
|
|
|
// invEM = invEM + gamma * I (to reduce calculation)
|
|
springEm_inv = springEm_inv + this.gamma;
|
|
this.springEm = springEm_inv == 0 ? 0 : 1 / springEm_inv;
|
|
}
|
|
else {
|
|
this.gamma = 0;
|
|
this.beta_c = 0;
|
|
this.springLambda_acc = 0;
|
|
}
|
|
|
|
if (this.motorEnabled) {
|
|
this.maxMotorImpulse = this.maxMotorTorque * dt;
|
|
|
|
// invEM2 = J2 * invM * J2T
|
|
var motorEm_inv = body1.i_inv + body2.i_inv;
|
|
this.motorEm = motorEm_inv > 0 ? 1 / motorEm_inv : motorEm_inv;
|
|
}
|
|
else {
|
|
this.motorEm = 0;
|
|
this.motorLambda_acc = 0;
|
|
}
|
|
|
|
if (warmStarting) {
|
|
// impulse = JT * lambda
|
|
var linearImpulse = vec2.scale(this.n, this.lambda_acc);
|
|
var angularImpulse1 = this.sn1 * this.lambda_acc + this.motorLambda_acc;
|
|
var angularImpulse2 = this.sn2 * this.lambda_acc + this.motorLambda_acc;
|
|
|
|
if (this.frequencyHz > 0) {
|
|
linearImpulse.addself(vec2.scale(this.u, this.springLambda_acc));
|
|
angularImpulse1 += this.su1 * this.springLambda_acc;
|
|
angularImpulse2 += this.su2 * this.springLambda_acc;
|
|
}
|
|
|
|
// Apply cached constraint impulses
|
|
// V += JT * lambda * invM
|
|
body1.v.mad(linearImpulse, -body1.m_inv);
|
|
body1.w -= angularImpulse1 * body1.i_inv;
|
|
|
|
body2.v.mad(linearImpulse, body2.m_inv);
|
|
body2.w += angularImpulse2 * body2.i_inv;
|
|
}
|
|
else {
|
|
this.lambda_acc = 0;
|
|
this.springLambda_acc = 0;
|
|
this.motorLambda_acc = 0;
|
|
}
|
|
}
|
|
|
|
WheelJoint.prototype.solveVelocityConstraints = function() {
|
|
var body1 = this.body1;
|
|
var body2 = this.body2;
|
|
|
|
// Solve spring constraint
|
|
if (this.frequencyHz > 0) {
|
|
// Compute lambda for velocity constraint
|
|
// Solve J * invM * JT * lambda = -(J * V + beta * C + gamma * (lambda_acc + lambda))
|
|
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.su2 * body2.w - this.su1 * body1.w;
|
|
var soft = this.beta_c + this.gamma * this.springLambda_acc;
|
|
var lambda = -this.springEm * (cdot + soft);
|
|
|
|
// Accumulate lambda
|
|
this.springLambda_acc += lambda;
|
|
|
|
// linearImpulse = JT * lambda
|
|
var impulse = vec2.scale(this.u, lambda);
|
|
|
|
// Apply constraint impulses
|
|
// V += JT * lambda * invM
|
|
body1.v.mad(impulse, -body1.m_inv);
|
|
body1.w -= this.su1 * lambda * body1.i_inv;
|
|
|
|
body2.v.mad(impulse, body2.m_inv);
|
|
body2.w += this.su2 * lambda * body2.i_inv;
|
|
}
|
|
|
|
// Solve motor constraint
|
|
if (this.motorEnabled) {
|
|
// Compute motor impulse
|
|
var cdot = body2.w - body1.w - this.motorSpeed;
|
|
var lambda = -this.motorEm * cdot;
|
|
|
|
var motorLambdaOld = this.motorLambda_acc;
|
|
this.motorLambda_acc = Math.clamp(this.motorLambda_acc + lambda, -this.maxMotorImpulse, this.maxMotorImpulse);
|
|
lambda = this.motorLambda_acc - motorLambdaOld;
|
|
|
|
// Apply motor impulses
|
|
body1.w -= lambda * body1.i_inv;
|
|
body2.w += lambda * body2.i_inv;
|
|
}
|
|
|
|
// Compute lambda for velocity constraint
|
|
// Solve J * invM * JT * lambda = -J * V
|
|
var cdot = this.n.dot(vec2.sub(body2.v, body1.v)) + this.sn2 * body2.w - this.sn1 * body1.w;
|
|
var lambda = -this.em * cdot;
|
|
|
|
// Accumulate lambda
|
|
this.lambda_acc += lambda;
|
|
|
|
// linearImpulse = JT * lambda
|
|
var impulse = vec2.scale(this.n, lambda);
|
|
|
|
// Apply constraint impulses
|
|
// V += JT * lambda * invM
|
|
body1.v.mad(impulse, -body1.m_inv);
|
|
body1.w -= this.sn1 * lambda * body1.i_inv;
|
|
|
|
body2.v.mad(impulse, body2.m_inv);
|
|
body2.w += this.sn2 * lambda * body2.i_inv;
|
|
}
|
|
|
|
WheelJoint.prototype.solvePositionConstraints = function() {
|
|
var body1 = this.body1;
|
|
var body2 = this.body2;
|
|
|
|
// Transformed r1, r2
|
|
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
|
|
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
|
|
|
|
// World anchor points
|
|
var p1 = vec2.add(body1.p, r1);
|
|
var p2 = vec2.add(body2.p, r2);
|
|
|
|
// Delta vector between world anchor points
|
|
var d = vec2.sub(p2, p1);
|
|
|
|
// r1 + d
|
|
var r1_d = vec2.add(r1, d);
|
|
|
|
// World line normal
|
|
var n = vec2.rotate(this.n_local, body1.a);
|
|
|
|
// Position constraint
|
|
var c = vec2.dot(n, d);
|
|
var correction = Math.clamp(c, -Joint.MAX_LINEAR_CORRECTION, Joint.MAX_LINEAR_CORRECTION);
|
|
|
|
// Compute lambda for position constraint
|
|
// Solve J * invM * JT * lambda = -C / dt
|
|
var s1 = vec2.cross(r1_d, n);
|
|
var s2 = vec2.cross(r2, n);
|
|
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * s1 * s1 + body2.i_inv * s2 * s2;
|
|
var k_inv = em_inv == 0 ? 0 : 1 / em_inv;
|
|
var lambda_dt = k_inv * (-correction);
|
|
|
|
// Apply constraint impulses
|
|
// impulse = JT * lambda
|
|
// X += impulse * invM * dt
|
|
var impulse_dt = vec2.scale(n, lambda_dt);
|
|
|
|
body1.p.mad(impulse_dt, -body1.m_inv);
|
|
body1.a -= s1 * lambda_dt * body1.i_inv;
|
|
|
|
body2.p.mad(impulse_dt, body2.m_inv);
|
|
body2.a += s2 * lambda_dt * body2.i_inv;
|
|
|
|
return Math.abs(c) < Joint.LINEAR_SLOP;
|
|
}
|
|
|
|
WheelJoint.prototype.getReactionForce = function(dt_inv) {
|
|
return vec2.scale(this.n, this.lambda_acc * dt_inv);
|
|
}
|
|
|
|
WheelJoint.prototype.getReactionTorque = function(dt_inv) {
|
|
return 0;
|
|
}
|
|
|