UNPKG

p2s

Version:

A JavaScript 2D physics engine.

357 lines (308 loc) 11.5 kB
var Constraint = require('./Constraint') , ContactEquation = require('../equations/ContactEquation') , Equation = require('../equations/Equation') , vec2 = require('../math/vec2') , RotationalLockEquation = require('../equations/RotationalLockEquation'); module.exports = PrismaticConstraint; /** * Constraint that only allows bodies to move along a line, relative to each other. See <a href="http://www.iforce2d.net/b2dtut/joints-prismatic">this tutorial</a>. Also called "slider constraint". * * @class PrismaticConstraint * @constructor * @extends Constraint * @author schteppe * @param {Body} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {Number} [options.maxForce] Max force to be applied by the constraint * @param {Array} [options.localAnchorA] Body A's anchor point, defined in its own local frame. * @param {Array} [options.localAnchorB] Body B's anchor point, defined in its own local frame. * @param {Array} [options.localAxisA] An axis, defined in body A frame, that body B's anchor point may slide along. * @param {Boolean} [options.disableRotationalLock] If set to true, bodyB will be free to rotate around its anchor point. * @param {Number} [options.upperLimit] * @param {Number} [options.lowerLimit] * @todo Ability to create using only a point and a worldAxis * @example * var constraint = new PrismaticConstraint(bodyA, bodyB, { * localAxisA: [0, 1] * }); * world.addConstraint(constraint); */ function PrismaticConstraint(bodyA, bodyB, options){ options = options || {}; Constraint.call(this,bodyA,bodyB,Constraint.PRISMATIC,options); // Get anchors var localAnchorA = vec2.create(), localAxisA = vec2.fromValues(1,0), localAnchorB = vec2.create(); if(options.localAnchorA){ vec2.copy(localAnchorA, options.localAnchorA); } if(options.localAxisA){ vec2.copy(localAxisA, options.localAxisA); } if(options.localAnchorB){ vec2.copy(localAnchorB, options.localAnchorB); } /** * @property localAnchorA * @type {Array} */ this.localAnchorA = localAnchorA; /** * @property localAnchorB * @type {Array} */ this.localAnchorB = localAnchorB; /** * @property localAxisA * @type {Array} */ this.localAxisA = localAxisA; /* The constraint violation for the common axis point is g = ( xj + rj - xi - ri ) * t := gg*t where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame. gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t ) Note the use of the chain rule. Now we identify the jacobian G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj] The rotational part is just a rotation lock. */ var maxForce = this.maxForce = options.maxForce !== undefined ? options.maxForce : Number.MAX_VALUE; // Translational part var trans = new Equation(bodyA,bodyB,-maxForce,maxForce); var ri = new vec2.create(), rj = new vec2.create(), gg = new vec2.create(), t = new vec2.create(); trans.computeGq = function(){ // g = ( xj + rj - xi - ri ) * t return vec2.dot(gg,t); }; trans.updateJacobian = function(){ var G = this.G, xi = bodyA.position, xj = bodyB.position; vec2.rotate(ri,localAnchorA,bodyA.angle); vec2.rotate(rj,localAnchorB,bodyB.angle); vec2.add(gg,xj,rj); vec2.subtract(gg,gg,xi); vec2.subtract(gg,gg,ri); vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2); G[0] = -t[0]; G[1] = -t[1]; G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg); G[3] = t[0]; G[4] = t[1]; G[5] = vec2.crossLength(rj,t); }; this.equations.push(trans); // Rotational part if(!options.disableRotationalLock){ var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce); this.equations.push(rot); } /** * The position of anchor A relative to anchor B, along the constraint axis. * @property position * @type {Number} */ this.position = 0; // Is this one used at all? this.velocity = 0; /** * Set to true to enable lower limit. * @property lowerLimitEnabled * @type {Boolean} */ this.lowerLimitEnabled = options.lowerLimit !== undefined ? true : false; /** * Set to true to enable upper limit. * @property upperLimitEnabled * @type {Boolean} */ this.upperLimitEnabled = options.upperLimit !== undefined ? true : false; /** * Lower constraint limit. The constraint position is forced to be larger than this value. * @property lowerLimit * @type {Number} */ this.lowerLimit = options.lowerLimit !== undefined ? options.lowerLimit : 0; /** * Upper constraint limit. The constraint position is forced to be smaller than this value. * @property upperLimit * @type {Number} */ this.upperLimit = options.upperLimit !== undefined ? options.upperLimit : 1; // Equations used for limits this.upperLimitEquation = new ContactEquation(bodyA,bodyB); this.lowerLimitEquation = new ContactEquation(bodyA,bodyB); // Set max/min forces this.upperLimitEquation.minForce = this.lowerLimitEquation.minForce = 0; this.upperLimitEquation.maxForce = this.lowerLimitEquation.maxForce = maxForce; /** * Equation used for the motor. * @property motorEquation * @type {Equation} */ this.motorEquation = new Equation(bodyA,bodyB); /** * The current motor state. Enable or disable the motor using .enableMotor * @property motorEnabled * @type {Boolean} */ this.motorEnabled = false; /** * Set the target speed for the motor. * @property motorSpeed * @type {Number} */ this.motorSpeed = 0; var that = this; var motorEquation = this.motorEquation; motorEquation.computeGq = function(){ return 0; }; motorEquation.computeGW = function(){ var G = this.G, bi = this.bodyA, bj = this.bodyB, vi = bi.velocity, vj = bj.velocity, wi = bi.angularVelocity, wj = bj.angularVelocity; return this.gmult(G,vi,wi,vj,wj) + that.motorSpeed; }; } PrismaticConstraint.prototype = new Constraint(); PrismaticConstraint.prototype.constructor = PrismaticConstraint; var worldAxisA = vec2.create(), worldAnchorA = vec2.create(), worldAnchorB = vec2.create(), orientedAnchorA = vec2.create(), orientedAnchorB = vec2.create(), tmp = vec2.create(); /** * Update the constraint equations. Should be done if any of the bodies changed position, before solving. * @method update */ PrismaticConstraint.prototype.update = function(){ var eqs = this.equations, trans = eqs[0], upperLimit = this.upperLimit, lowerLimit = this.lowerLimit, upperLimitEquation = this.upperLimitEquation, lowerLimitEquation = this.lowerLimitEquation, bodyA = this.bodyA, bodyB = this.bodyB, localAxisA = this.localAxisA, localAnchorA = this.localAnchorA, localAnchorB = this.localAnchorB; trans.updateJacobian(); // Transform local things to world vec2.rotate(worldAxisA, localAxisA, bodyA.angle); vec2.rotate(orientedAnchorA, localAnchorA, bodyA.angle); vec2.add(worldAnchorA, orientedAnchorA, bodyA.position); vec2.rotate(orientedAnchorB, localAnchorB, bodyB.angle); vec2.add(worldAnchorB, orientedAnchorB, bodyB.position); var relPosition = this.position = vec2.dot(worldAnchorB,worldAxisA) - vec2.dot(worldAnchorA,worldAxisA); // Motor if(this.motorEnabled){ // G = [ a a x ri -a -a x rj ] var G = this.motorEquation.G; G[0] = worldAxisA[0]; G[1] = worldAxisA[1]; G[2] = vec2.crossLength(worldAxisA,orientedAnchorB); G[3] = -worldAxisA[0]; G[4] = -worldAxisA[1]; G[5] = -vec2.crossLength(worldAxisA,orientedAnchorA); } /* Limits strategy: Add contact equation, with normal along the constraint axis. min/maxForce is set so the constraint is repulsive in the correct direction. Some offset is added to either equation.contactPointA or .contactPointB to get the correct upper/lower limit. ^ | upperLimit x | ------ anchorB x<---| B | | | | ------ | ------ | | | | A |-->x anchorA ------ | x lowerLimit | axis */ if(this.upperLimitEnabled && relPosition > upperLimit){ // Update contact constraint normal, etc vec2.scale(upperLimitEquation.normalA, worldAxisA, -1); vec2.subtract(upperLimitEquation.contactPointA, worldAnchorA, bodyA.position); vec2.subtract(upperLimitEquation.contactPointB, worldAnchorB, bodyB.position); vec2.scale(tmp,worldAxisA,upperLimit); vec2.add(upperLimitEquation.contactPointA,upperLimitEquation.contactPointA,tmp); if(eqs.indexOf(upperLimitEquation) === -1){ eqs.push(upperLimitEquation); } } else { var idx = eqs.indexOf(upperLimitEquation); if(idx !== -1){ eqs.splice(idx,1); } } if(this.lowerLimitEnabled && relPosition < lowerLimit){ // Update contact constraint normal, etc vec2.scale(lowerLimitEquation.normalA, worldAxisA, 1); vec2.subtract(lowerLimitEquation.contactPointA, worldAnchorA, bodyA.position); vec2.subtract(lowerLimitEquation.contactPointB, worldAnchorB, bodyB.position); vec2.scale(tmp,worldAxisA,lowerLimit); vec2.subtract(lowerLimitEquation.contactPointB,lowerLimitEquation.contactPointB,tmp); if(eqs.indexOf(lowerLimitEquation) === -1){ eqs.push(lowerLimitEquation); } } else { var idx = eqs.indexOf(lowerLimitEquation); if(idx !== -1){ eqs.splice(idx,1); } } }; /** * Enable the motor * @method enableMotor */ PrismaticConstraint.prototype.enableMotor = function(){ if(this.motorEnabled){ return; } this.equations.push(this.motorEquation); this.motorEnabled = true; }; /** * Disable the rotational motor * @method disableMotor */ PrismaticConstraint.prototype.disableMotor = function(){ if(!this.motorEnabled){ return; } var i = this.equations.indexOf(this.motorEquation); this.equations.splice(i,1); this.motorEnabled = false; }; /** * Set the constraint limits. * @method setLimits * @param {number} lower Lower limit. * @param {number} upper Upper limit. */ PrismaticConstraint.prototype.setLimits = function (lower, upper) { if(typeof(lower) === 'number'){ this.lowerLimit = lower; this.lowerLimitEnabled = true; } else { this.lowerLimit = lower; this.lowerLimitEnabled = false; } if(typeof(upper) === 'number'){ this.upperLimit = upper; this.upperLimitEnabled = true; } else { this.upperLimit = upper; this.upperLimitEnabled = false; } };