Show:

File: src/constraint/Constraint.js

                                /**
                                * 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,
                                            lineWidth: 2,
                                            strokeStyle: '#ffffff',
                                            type: 'line',
                                            anchors: true
                                        };
                                
                                        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)
                                        };
                                    };
                                
                                    /*
                                    *
                                    *  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 {}
                                     */
                                
                                })();