UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

518 lines (437 loc) 18.6 kB
/** * The `Matter.Constraint` module contains methods for creating and manipulating constraints. * Constraints are used for specifying that a fixed distance must be maintained between two bodies (or a body and a fixed world-space position). * The stiffness of constraints can be modified to create springs or elastic. * * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). * * @class Constraint */ var Constraint = {}; module.exports = Constraint; var Vertices = require('../geometry/Vertices'); var Vector = require('../geometry/Vector'); var Sleeping = require('../core/Sleeping'); var Bounds = require('../geometry/Bounds'); var Axes = require('../geometry/Axes'); var Common = require('../core/Common'); (function() { Constraint._warming = 0.4; Constraint._torqueDampen = 1; Constraint._minLength = 0.000001; /** * Creates a new constraint. * All properties have default values, and many are pre-calculated automatically based on other properties. * To simulate a revolute constraint (or pin joint) set `length: 0` and a high `stiffness` value (e.g. `0.7` or above). * If the constraint is unstable, try lowering the `stiffness` value and / or increasing `engine.constraintIterations`. * For compound bodies, constraints must be applied to the parent body (not one of its parts). * See the properties section below for detailed information on what you can pass via the `options` object. * @method create * @param {} options * @return {constraint} constraint */ Constraint.create = function(options) { var constraint = options; // if bodies defined but no points, use body centre if (constraint.bodyA && !constraint.pointA) constraint.pointA = { x: 0, y: 0 }; if (constraint.bodyB && !constraint.pointB) constraint.pointB = { x: 0, y: 0 }; // calculate static length using initial world space points var initialPointA = constraint.bodyA ? Vector.add(constraint.bodyA.position, constraint.pointA) : constraint.pointA, initialPointB = constraint.bodyB ? Vector.add(constraint.bodyB.position, constraint.pointB) : constraint.pointB, length = Vector.magnitude(Vector.sub(initialPointA, initialPointB)); constraint.length = typeof constraint.length !== 'undefined' ? constraint.length : length; // option defaults constraint.id = constraint.id || Common.nextId(); constraint.label = constraint.label || 'Constraint'; constraint.type = 'constraint'; constraint.stiffness = constraint.stiffness || (constraint.length > 0 ? 1 : 0.7); constraint.damping = constraint.damping || 0; constraint.angularStiffness = constraint.angularStiffness || 0; constraint.angleA = constraint.bodyA ? constraint.bodyA.angle : constraint.angleA; constraint.angleB = constraint.bodyB ? constraint.bodyB.angle : constraint.angleB; constraint.plugin = {}; // render var render = { visible: true, type: 'line', anchors: true, lineColor: null, // custom Phaser property lineOpacity: null, // custom Phaser property lineThickness: null, // custom Phaser property pinSize: null, // custom Phaser property anchorColor: null, // custom Phaser property anchorSize: null // custom Phaser property }; if (constraint.length === 0 && constraint.stiffness > 0.1) { render.type = 'pin'; render.anchors = false; } else if (constraint.stiffness < 0.9) { render.type = 'spring'; } constraint.render = Common.extend(render, constraint.render); return constraint; }; /** * Prepares for solving by constraint warming. * @private * @method preSolveAll * @param {body[]} bodies */ Constraint.preSolveAll = function(bodies) { for (var i = 0; i < bodies.length; i += 1) { var body = bodies[i], impulse = body.constraintImpulse; if (body.isStatic || (impulse.x === 0 && impulse.y === 0 && impulse.angle === 0)) { continue; } body.position.x += impulse.x; body.position.y += impulse.y; body.angle += impulse.angle; } }; /** * Solves all constraints in a list of collisions. * @private * @method solveAll * @param {constraint[]} constraints * @param {number} delta */ Constraint.solveAll = function(constraints, delta) { var timeScale = Common.clamp(delta / Common._baseDelta, 0, 1); // Solve fixed constraints first. for (var i = 0; i < constraints.length; i += 1) { var constraint = constraints[i], fixedA = !constraint.bodyA || (constraint.bodyA && constraint.bodyA.isStatic), fixedB = !constraint.bodyB || (constraint.bodyB && constraint.bodyB.isStatic); if (fixedA || fixedB) { Constraint.solve(constraints[i], timeScale); } } // Solve free constraints last. for (i = 0; i < constraints.length; i += 1) { constraint = constraints[i]; fixedA = !constraint.bodyA || (constraint.bodyA && constraint.bodyA.isStatic); fixedB = !constraint.bodyB || (constraint.bodyB && constraint.bodyB.isStatic); if (!fixedA && !fixedB) { Constraint.solve(constraints[i], timeScale); } } }; /** * Solves a distance constraint with Gauss-Siedel method. * @private * @method solve * @param {constraint} constraint * @param {number} timeScale */ Constraint.solve = function(constraint, timeScale) { var bodyA = constraint.bodyA, bodyB = constraint.bodyB, pointA = constraint.pointA, pointB = constraint.pointB; if (!bodyA && !bodyB) return; // update reference angle if (bodyA && !bodyA.isStatic) { Vector.rotate(pointA, bodyA.angle - constraint.angleA, pointA); constraint.angleA = bodyA.angle; } // update reference angle if (bodyB && !bodyB.isStatic) { Vector.rotate(pointB, bodyB.angle - constraint.angleB, pointB); constraint.angleB = bodyB.angle; } var pointAWorld = pointA, pointBWorld = pointB; if (bodyA) pointAWorld = Vector.add(bodyA.position, pointA); if (bodyB) pointBWorld = Vector.add(bodyB.position, pointB); if (!pointAWorld || !pointBWorld) return; var delta = Vector.sub(pointAWorld, pointBWorld), currentLength = Vector.magnitude(delta); // prevent singularity if (currentLength < Constraint._minLength) { currentLength = Constraint._minLength; } // solve distance constraint with Gauss-Siedel method var difference = (currentLength - constraint.length) / currentLength, isRigid = constraint.stiffness >= 1 || constraint.length === 0, stiffness = isRigid ? constraint.stiffness * timeScale : constraint.stiffness * timeScale * timeScale, damping = constraint.damping * timeScale, force = Vector.mult(delta, difference * stiffness), massTotal = (bodyA ? bodyA.inverseMass : 0) + (bodyB ? bodyB.inverseMass : 0), inertiaTotal = (bodyA ? bodyA.inverseInertia : 0) + (bodyB ? bodyB.inverseInertia : 0), resistanceTotal = massTotal + inertiaTotal, torque, share, normal, normalVelocity, relativeVelocity; if (damping > 0) { var zero = Vector.create(); normal = Vector.div(delta, currentLength); relativeVelocity = Vector.sub( bodyB && Vector.sub(bodyB.position, bodyB.positionPrev) || zero, bodyA && Vector.sub(bodyA.position, bodyA.positionPrev) || zero ); normalVelocity = Vector.dot(normal, relativeVelocity); } if (bodyA && !bodyA.isStatic) { share = bodyA.inverseMass / massTotal; // keep track of applied impulses for post solving bodyA.constraintImpulse.x -= force.x * share; bodyA.constraintImpulse.y -= force.y * share; // apply forces bodyA.position.x -= force.x * share; bodyA.position.y -= force.y * share; // apply damping if (damping > 0) { bodyA.positionPrev.x -= damping * normal.x * normalVelocity * share; bodyA.positionPrev.y -= damping * normal.y * normalVelocity * share; } // apply torque torque = (Vector.cross(pointA, force) / resistanceTotal) * Constraint._torqueDampen * bodyA.inverseInertia * (1 - constraint.angularStiffness); bodyA.constraintImpulse.angle -= torque; bodyA.angle -= torque; } if (bodyB && !bodyB.isStatic) { share = bodyB.inverseMass / massTotal; // keep track of applied impulses for post solving bodyB.constraintImpulse.x += force.x * share; bodyB.constraintImpulse.y += force.y * share; // apply forces bodyB.position.x += force.x * share; bodyB.position.y += force.y * share; // apply damping if (damping > 0) { bodyB.positionPrev.x += damping * normal.x * normalVelocity * share; bodyB.positionPrev.y += damping * normal.y * normalVelocity * share; } // apply torque torque = (Vector.cross(pointB, force) / resistanceTotal) * Constraint._torqueDampen * bodyB.inverseInertia * (1 - constraint.angularStiffness); bodyB.constraintImpulse.angle += torque; bodyB.angle += torque; } }; /** * Performs body updates required after solving constraints. * @private * @method postSolveAll * @param {body[]} bodies */ Constraint.postSolveAll = function(bodies) { for (var i = 0; i < bodies.length; i++) { var body = bodies[i], impulse = body.constraintImpulse; if (body.isStatic || (impulse.x === 0 && impulse.y === 0 && impulse.angle === 0)) { continue; } Sleeping.set(body, false); // update geometry and reset for (var j = 0; j < body.parts.length; j++) { var part = body.parts[j]; Vertices.translate(part.vertices, impulse); if (j > 0) { part.position.x += impulse.x; part.position.y += impulse.y; } if (impulse.angle !== 0) { Vertices.rotate(part.vertices, impulse.angle, body.position); Axes.rotate(part.axes, impulse.angle); if (j > 0) { Vector.rotateAbout(part.position, impulse.angle, body.position, part.position); } } Bounds.update(part.bounds, part.vertices, body.velocity); } // dampen the cached impulse for warming next step impulse.angle *= Constraint._warming; impulse.x *= Constraint._warming; impulse.y *= Constraint._warming; } }; /** * Returns the world-space position of `constraint.pointA`, accounting for `constraint.bodyA`. * @method pointAWorld * @param {constraint} constraint * @returns {vector} the world-space position */ Constraint.pointAWorld = function(constraint) { return { x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + (constraint.pointA ? constraint.pointA.x : 0), y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + (constraint.pointA ? constraint.pointA.y : 0) }; }; /** * Returns the world-space position of `constraint.pointB`, accounting for `constraint.bodyB`. * @method pointBWorld * @param {constraint} constraint * @returns {vector} the world-space position */ Constraint.pointBWorld = function(constraint) { return { x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + (constraint.pointB ? constraint.pointB.x : 0), y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + (constraint.pointB ? constraint.pointB.y : 0) }; }; /** * Returns the current length of the constraint. * This is the distance between both of the constraint's end points. * See `constraint.length` for the target rest length. * @method currentLength * @param {constraint} constraint * @returns {number} the current length */ Constraint.currentLength = function(constraint) { var pointAX = (constraint.bodyA ? constraint.bodyA.position.x : 0) + (constraint.pointA ? constraint.pointA.x : 0); var pointAY = (constraint.bodyA ? constraint.bodyA.position.y : 0) + (constraint.pointA ? constraint.pointA.y : 0); var pointBX = (constraint.bodyB ? constraint.bodyB.position.x : 0) + (constraint.pointB ? constraint.pointB.x : 0); var pointBY = (constraint.bodyB ? constraint.bodyB.position.y : 0) + (constraint.pointB ? constraint.pointB.y : 0); var deltaX = pointAX - pointBX; var deltaY = pointAY - pointBY; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); }; /* * * Properties Documentation * */ /** * An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`. * * @property id * @type number */ /** * A `String` denoting the type of object. * * @property type * @type string * @default "constraint" * @readOnly */ /** * An arbitrary `String` name to help the user identify and manage bodies. * * @property label * @type string * @default "Constraint" */ /** * An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`. * * @property render * @type object */ /** * A flag that indicates if the constraint should be rendered. * * @property render.visible * @type boolean * @default true */ /** * A `Number` that defines the line width to use when rendering the constraint outline. * A value of `0` means no outline will be rendered. * * @property render.lineWidth * @type number * @default 2 */ /** * A `String` that defines the stroke style to use when rendering the constraint outline. * It is the same as when using a canvas, so it accepts CSS style property values. * * @property render.strokeStyle * @type string * @default a random colour */ /** * A `String` that defines the constraint rendering type. * The possible values are 'line', 'pin', 'spring'. * An appropriate render type will be automatically chosen unless one is given in options. * * @property render.type * @type string * @default 'line' */ /** * A `Boolean` that defines if the constraint's anchor points should be rendered. * * @property render.anchors * @type boolean * @default true */ /** * The first possible `Body` that this constraint is attached to. * * @property bodyA * @type body * @default null */ /** * The second possible `Body` that this constraint is attached to. * * @property bodyB * @type body * @default null */ /** * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position. * * @property pointA * @type vector * @default { x: 0, y: 0 } */ /** * A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyB` if defined, otherwise a world-space position. * * @property pointB * @type vector * @default { x: 0, y: 0 } */ /** * A `Number` that specifies the stiffness of the constraint, i.e. the rate at which it returns to its resting `constraint.length`. * A value of `1` means the constraint should be very stiff. * A value of `0.2` means the constraint acts like a soft spring. * * @property stiffness * @type number * @default 1 */ /** * A `Number` that specifies the damping of the constraint, * i.e. the amount of resistance applied to each body based on their velocities to limit the amount of oscillation. * Damping will only be apparent when the constraint also has a very low `stiffness`. * A value of `0.1` means the constraint will apply heavy damping, resulting in little to no oscillation. * A value of `0` means the constraint will apply no damping. * * @property damping * @type number * @default 0 */ /** * A `Number` that specifies the target resting length of the constraint. * It is calculated automatically in `Constraint.create` from initial positions of the `constraint.bodyA` and `constraint.bodyB`. * * @property length * @type number */ /** * An object reserved for storing plugin-specific properties. * * @property plugin * @type {} */ })();