#

API Docs for: 0.6.0
Show: Inherited Protected Private Deprecated

# File: src/objects/Body.js

    module.exports = Body;

    var EventTarget = require('../utils/EventTarget');
    var Shape = require('../shapes/Shape');
    var Vec3 = require('../math/Vec3');
    var Mat3 = require('../math/Mat3');
    var Quaternion = require('../math/Quaternion');
    var Material = require('../material/Material');
    var AABB = require('../collision/AABB');
    var Box = require('../shapes/Box');

    /**
     * Base class for all body types.
     * @class Body
     * @constructor
     * @extends EventTarget
     * @param {object} [options]
     * @param {Vec3} [options.position]
     * @param {number} [options.mass]
     * @param {number} [options.type]
     * @param {number} [options.linearDamping]
     * @example
     *     var body = new Body({
     *         mass: 1
     *     });
     *     var shape = new Sphere(1);
     *     body.addShape(shape);
     *     world.add(body);
     */
    function Body(options){
        options = options || {};

        EventTarget.apply(this);

        this.id = Body.idCounter++;

        /**
         * Reference to the world the body is living in
         * @property world
         * @type {World}
         */
        this.world = null;

        /**
         * Callback function that is used BEFORE stepping the system. Use it to apply forces, for example. Inside the function, "this" will refer to this Body object.
         * @property preStep
         * @type {Function}
         * @deprecated Use World events instead
         */
        this.preStep = null;

        /**
         * Callback function that is used AFTER stepping the system. Inside the function, "this" will refer to this Body object.
         * @property postStep
         * @type {Function}
         * @deprecated Use World events instead
         */
        this.postStep = null;

        this.vlambda = new Vec3();

        /**
         * @property {Number} collisionFilterGroup
         */
        this.collisionFilterGroup = 1;

        /**
         * @property {Number} collisionFilterMask
         */
        this.collisionFilterMask = 1;

        /**
         * Whether to produce contact forces
         * @property {Number} collisionResponse
         */
    	this.collisionResponse = true;

        /**
         * @property position
         * @type {Vec3}
         */
        this.position = new Vec3();

        if(options.position){
            this.position.copy(options.position);
        }

        /**
         * @property {Vec3} previousPosition
         */
        this.previousPosition = new Vec3();

        /**
         * Initial position of the body
         * @property initPosition
         * @type {Vec3}
         */
        this.initPosition = new Vec3();

        /**
         * @property velocity
         * @type {Vec3}
         */
        this.velocity = new Vec3();

        /**
         * @property initVelocity
         * @type {Vec3}
         */
        this.initVelocity = new Vec3();

        /**
         * Linear force on the body
         * @property force
         * @type {Vec3}
         */
        this.force = new Vec3();

        var mass = typeof(options.mass) === 'number' ? options.mass : 0;

        /**
         * @property mass
         * @type {Number}
         */
        this.mass = mass;

        /**
         * @property invMass
         * @type {Number}
         */
        this.invMass = mass > 0 ? 1.0 / mass : 0;

        /**
         * @property material
         * @type {Material}
         */
        this.material = options.material || null;

        /**
         * @property float linearDamping
         * @type {Number}
         */
        this.linearDamping = typeof(options.linearDamping) === 'number' ? options.linearDamping : 0.01;

        /**
         * One of the states Body.DYNAMIC, Body.STATIC and Body.KINEMATIC
         * @property type
         * @type {Number}
         */
        this.type = (mass <= 0.0 ? Body.STATIC : Body.DYNAMIC);
        if(typeof(options.type) === typeof(Body.STATIC)){
            this.type = options.type;
        }

        /**
         * If true, the body will automatically fall to sleep.
         * @property allowSleep
         * @type {Boolean}
         */
        this.allowSleep = true;

        /**
         * Current sleep state.
         * @property sleepState
         * @type {Number}
         */
        this.sleepState = 0;

        /**
         * If the speed (the norm of the velocity) is smaller than this value, the body is considered sleepy.
         * @property sleepSpeedLimit
         * @type {Number}
         */
        this.sleepSpeedLimit = 0.1;

        /**
         * If the body has been sleepy for this sleepTimeLimit seconds, it is considered sleeping.
         * @property sleepTimeLimit
         * @type {Number}
         */
        this.sleepTimeLimit = 1;

        this.timeLastSleepy = 0;

        this._wakeUpAfterNarrowphase = false;

        /**
         * Rotational force on the body, around center of mass
         * @property Vec3 tau
         * @todo should be renamed to .angularForce
         */
        this.tau = new Vec3();

        /**
         * Orientation of the body
         * @property quaternion
         * @type {Quaternion}
         */
        this.quaternion = new Quaternion();

        /**
         * @property initQuaternion
         * @type {Quaternion}
         */
        this.initQuaternion = new Quaternion();

        /**
         * @property angularVelocity
         * @type {Vec3}
         */
        this.angularVelocity = new Vec3();

        /**
         * @property initAngularVelocity
         * @type {Vec3}
         */
        this.initAngularVelocity = new Vec3();

        this.interpolatedPosition = new Vec3();
        this.interpolatedQuaternion = new Quaternion();

        /**
         * @property shapes
         * @type {array}
         */
        this.shapes = [];

        /**
         * @property shapeOffsets
         * @type {array}
         */
        this.shapeOffsets = [];

        /**
         * @property shapeOrientations
         * @type {array}
         */
        this.shapeOrientations = [];

        /**
         * @property inertia
         * @type {Vec3}
         */
        this.inertia = new Vec3();

        /**
         * @property {Vec3} invInertia
         */
        this.invInertia = new Vec3();

        /**
         * @property {Mat3} invInertiaWorld
         */
        this.invInertiaWorld = new Mat3();

        this.invMassSolve = 0;

        /**
         * @property {Vec3} invInertiaSolve
         */
        this.invInertiaSolve = new Vec3();

        /**
         * @property {Mat3} invInertiaWorldSolve
         */
        this.invInertiaWorldSolve = new Mat3();

        /**
         * Set to true if you don't want the body to rotate. Make sure to run .updateMassProperties() after changing this.
         * @property {Boolean} fixedRotation
         */
        this.fixedRotation = false;

        /**
         * @property {Number} angularDamping
         */
        this.angularDamping = 0.01; // Perhaps default should be zero here?

        /**
         * @property aabb
         * @type {Vec3}
         */
        this.aabb = new AABB();

        /**
         * Indicates if the AABB needs to be updated before use.
         * @property aabbNeedsUpdate
         * @type {Boolean}
         */
        this.aabbNeedsUpdate = true;

        this.wlambda = new Vec3();

        this.updateMassProperties();
    }
    Body.prototype = new EventTarget();
    Body.prototype.constructor = Body;

    /**
     * A dynamic body is fully simulated. Can be moved manually by the user, but normally they move according to forces. A dynamic body can collide with all body types. A dynamic body always has finite, non-zero mass.
     * @static
     * @property DYNAMIC
     * @type {Number}
     */
    Body.DYNAMIC = 1;

    /**
     * A static body does not move during simulation and behaves as if it has infinite mass. Static bodies can be moved manually by setting the position of the body. The velocity of a static body is always zero. Static bodies do not collide with other static or kinematic bodies.
     * @static
     * @property DYNAMIC
     * @type {Number}
     */
    Body.STATIC = 2;

    /**
     * A kinematic body moves under simulation according to its velocity. They do not respond to forces. They can be moved manually, but normally a kinematic body is moved by setting its velocity. A kinematic body behaves as if it has infinite mass. Kinematic bodies do not collide with other static or kinematic bodies.
     * @static
     * @property DYNAMIC
     * @type {Number}
     */
    Body.KINEMATIC = 4;

    /**
     * @static
     * @property AWAKE
     * @type {number}
     */
    Body.AWAKE = 0;

    /**
     * @static
     * @property SLEEPY
     * @type {number}
     */
    Body.SLEEPY = 1;

    /**
     * @static
     * @property SLEEPING
     * @type {number}
     */
    Body.SLEEPING = 2;

    Body.idCounter = 0;

    /**
     * Wake the body up.
     * @method wakeUp
     */
    Body.prototype.wakeUp = function(){
        var s = this.sleepState;
        this.sleepState = 0;
        if(s === Body.SLEEPING){
            this.dispatchEvent({type:"wakeup"});
        }
    };

    /**
     * Force body sleep
     * @method sleep
     */
    Body.prototype.sleep = function(){
        this.sleepState = Body.SLEEPING;
        this.velocity.set(0,0,0);
        this.angularVelocity.set(0,0,0);
    };

    Body.sleepyEvent = {
        type: "sleepy"
    };

    Body.sleepEvent = {
        type: "sleep"
    };

    /**
     * Called every timestep to update internal sleep timer and change sleep state if needed.
     * @method sleepTick
     * @param {Number} time The world time in seconds
     */
    Body.prototype.sleepTick = function(time){
        if(this.allowSleep){
            var sleepState = this.sleepState;
            var speedSquared = this.velocity.norm2() + this.angularVelocity.norm2();
            var speedLimitSquared = Math.pow(this.sleepSpeedLimit,2);
            if(sleepState===Body.AWAKE && speedSquared < speedLimitSquared){
                this.sleepState = Body.SLEEPY; // Sleepy
                this.timeLastSleepy = time;
                this.dispatchEvent(Body.sleepyEvent);
            } else if(sleepState===Body.SLEEPY && speedSquared > speedLimitSquared){
                this.wakeUp(); // Wake up
            } else if(sleepState===Body.SLEEPY && (time - this.timeLastSleepy ) > this.sleepTimeLimit){
                this.sleep(); // Sleeping
                this.dispatchEvent(Body.sleepEvent);
            }
        }
    };

    /**
     * If the body is sleeping, it should be immovable / have infinite mass during solve. We solve it by having a separate "solve mass".
     * @method updateSolveMassProperties
     */
    Body.prototype.updateSolveMassProperties = function(){
        if(this.sleepState === Body.SLEEPING || this.type === Body.KINEMATIC){
            this.invMassSolve = 0;
            this.invInertiaSolve.setZero();
            this.invInertiaWorldSolve.setZero();
        } else {
            this.invMassSolve = this.invMass;
            this.invInertiaSolve.copy(this.invInertia);
            this.invInertiaWorldSolve.copy(this.invInertiaWorld);
        }
    };

    /**
     * Convert a world point to local body frame.
     * @method pointToLocalFrame
     * @param  {Vec3} worldPoint
     * @param  {Vec3} result
     * @return {Vec3}
     */
    Body.prototype.pointToLocalFrame = function(worldPoint,result){
        var result = result || new Vec3();
        worldPoint.vsub(this.position,result);
        this.quaternion.conjugate().vmult(result,result);
        return result;
    };

    /**
     * Convert a local body point to world frame.
     * @method pointToWorldFrame
     * @param  {Vec3} worldPoint
     * @param  {Vec3} result
     * @return {Vec3}
     */
    Body.prototype.pointToWorldFrame = function(localPoint,result){
        var result = result || new Vec3();
        this.quaternion.vmult(localPoint,result);
        result.vadd(this.position,result);
        return result;
    };

    /**
     * Convert a local body point to world frame.
     * @method pointToWorldFrame
     * @param  {Vec3} worldPoint
     * @param  {Vec3} result
     * @return {Vec3}
     */
    Body.prototype.vectorToWorldFrame = function(localVector, result){
        var result = result || new Vec3();
        this.quaternion.vmult(localVector, result);
        return result;
    };

    var tmpVec = new Vec3();
    var tmpQuat = new Quaternion();

    Body.prototype.addShape = function(shape, _offset, _orientation){
        var offset = new Vec3();
        var orientation = new Quaternion();

        if(_offset){
            offset.copy(_offset);
        }
        if(_orientation){
            orientation.copy(_orientation);
        }

        this.shapes.push(shape);
        this.shapeOffsets.push(offset);
        this.shapeOrientations.push(orientation);
        this.updateMassProperties();
        this.updateBoundingRadius();

        this.aabbNeedsUpdate = true;
    };

    /**
     * Update the bounding radius of the body. Should be done if any of the shapes are changed.
     * @method updateBoundingRadius
     */
    Body.prototype.updateBoundingRadius = function(){
        var shapes = this.shapes,
            shapeOffsets = this.shapeOffsets,
            N = shapes.length,
            radius = 0;

        for(var i=0; i!==N; i++){
            var shape = shapes[i];
            shape.updateBoundingSphereRadius();
            var offset = shapeOffsets[i].norm(),
                r = shape.boundingSphereRadius;
            if(offset + r > radius){
                radius = offset + r;
            }
        }

        this.boundingRadius = radius;
    };

    /**
     * Updates the .aabb
     * @method computeAABB
     * @todo rename to updateAABB()
     */
    Body.prototype.computeAABB = function(){
        var shapes = this.shapes,
            shapeOffsets = this.shapeOffsets,
            shapeOrientations = this.shapeOrientations,
            N = shapes.length,
            offset = tmpVec,
            orientation = tmpQuat,
            bodyQuat = this.quaternion,
            aabb = this.aabb,
            shapeAABB = new AABB();

        for(var i=0; i!==N; i++){
            var shape = shapes[i];

            // Get shape world quaternion
            shapeOrientations[i].mult(bodyQuat, orientation);

            // Get shape world position
            orientation.vmult(shapeOffsets[i], offset);
            offset.vadd(this.position, offset);

            // vec2.rotate(offset, shapeOffsets[i], bodyAngle);
            // vec2.add(offset, offset, this.position);

            // Get shape AABB
            shape.calculateWorldAABB(offset, orientation, shapeAABB.lowerBound, shapeAABB.upperBound);

            if(i === 0){
                aabb.copy(shapeAABB);
            } else {
                aabb.extend(shapeAABB);
            }
        }

        this.aabbNeedsUpdate = false;
    };

    var uiw_m1 = new Mat3(),
        uiw_m2 = new Mat3(),
        uiw_m3 = new Mat3();

    /**
     * Update .inertiaWorld and .invInertiaWorld
     * @method updateInertiaWorld
     */
    Body.prototype.updateInertiaWorld = function(force){
        var I = this.invInertia;
        if (I.x === I.y && I.y === I.z && !force) {
            // If inertia M = s*I, where I is identity and s a scalar, then
            //    R*M*R' = R*(s*I)*R' = s*R*I*R' = s*R*R' = s*I = M
            // where R is the rotation matrix.
            // In other words, we don't have to transform the inertia if all
            // inertia diagonal entries are equal.
        } else {
            var m1 = uiw_m1,
                m2 = uiw_m2,
                m3 = uiw_m3;
            m1.setRotationFromQuaternion(this.quaternion);
            m1.transpose(m2);
            m1.scale(I,m1);
            m1.mmult(m2,this.invInertiaWorld);
            //m3.getTrace(this.invInertiaWorld);
        }

        /*
        this.quaternion.vmult(this.inertia,this.inertiaWorld);
        this.quaternion.vmult(this.invInertia,this.invInertiaWorld);
        */
    };

    /**
     * Apply force to a world point. This could for example be a point on the Body surface. Applying force this way will add to Body.force and Body.tau.
     * @method applyForce
     * @param  {Vec3} force The amount of force to add.
     * @param  {Vec3} worldPoint A world point to apply the force on.
     */
    var Body_applyForce_r = new Vec3();
    var Body_applyForce_rotForce = new Vec3();
    Body.prototype.applyForce = function(force,worldPoint){
        if(this.type !== Body.DYNAMIC){
            return;
        }

        // Compute point position relative to the body center
        var r = Body_applyForce_r;
        worldPoint.vsub(this.position,r);

        // Compute produced rotational force
        var rotForce = Body_applyForce_rotForce;
        r.cross(force,rotForce);

        // Add linear force
        this.force.vadd(force,this.force);

        // Add rotational force
        this.tau.vadd(rotForce,this.tau);
    };

    /**
     * Apply impulse to a world point. This could for example be a point on the Body surface. An impulse is a force added to a body during a short period of time (impulse = force * time). Impulses will be added to Body.velocity and Body.angularVelocity.
     * @method applyImpulse
     * @param  {Vec3} impulse The amount of impulse to add.
     * @param  {Vec3} worldPoint A world point to apply the force on.
     */
    var Body_applyImpulse_r = new Vec3();
    var Body_applyImpulse_velo = new Vec3();
    var Body_applyImpulse_rotVelo = new Vec3();
    Body.prototype.applyImpulse = function(impulse, worldPoint){
        if(this.type !== Body.DYNAMIC){
            return;
        }

        // Compute point position relative to the body center
        var r = Body_applyImpulse_r;
        worldPoint.vsub(this.position,r);

        // Compute produced central impulse velocity
        var velo = Body_applyImpulse_velo;
        velo.copy(impulse);
        velo.mult(this.invMass,velo);

        // Add linear impulse
        this.velocity.vadd(velo, this.velocity);

        // Compute produced rotational impulse velocity
        var rotVelo = Body_applyImpulse_rotVelo;
        r.cross(impulse,rotVelo);

        /*
        rotVelo.x *= this.invInertia.x;
        rotVelo.y *= this.invInertia.y;
        rotVelo.z *= this.invInertia.z;
        */
        this.invInertiaWorld.vmult(rotVelo,rotVelo);

        // Add rotational Impulse
        this.angularVelocity.vadd(rotVelo, this.angularVelocity);
    };

    /**
     * Should be called whenever you change the body mass.
     * @method updateMassProperties
     */
    /*Body.prototype.updateMassProperties = function(){
        this.invMass = this.mass>0 ? 1.0/this.mass : 0;
        this.shape.calculateLocalInertia(this.mass, this.inertia);
        var I = this.inertia;
        var fixed = this.fixedRotation;
        this.invInertia.set(
            I.x > 0 && !fixed ? 1.0 / I.x : 0,
            I.y > 0 && !fixed ? 1.0 / I.y : 0,
            I.z > 0 && !fixed ? 1.0 / I.z : 0
        );
        this.updateInertiaWorld(true);
    };
    */

    Body.prototype.updateMassProperties = function(){
        var target = new Vec3();

        // TODO: check if only 1 shape at origin, use shape inertia in that case

        this.invMass = this.mass > 0 ? 1.0 / this.mass : 0;
        var I = this.inertia;
        var fixed = this.fixedRotation;

        // Approximate with AABB
        this.computeAABB();
        Box.calculateInertia(new Vec3(
            (this.aabb.upperBound.x-this.aabb.lowerBound.x) / 2,
            (this.aabb.upperBound.y-this.aabb.lowerBound.y) / 2,
            (this.aabb.upperBound.z-this.aabb.lowerBound.z) / 2
        ), this.mass, I);

        this.invInertia.set(
            I.x > 0 && !fixed ? 1.0 / I.x : 0,
            I.y > 0 && !fixed ? 1.0 / I.y : 0,
            I.z > 0 && !fixed ? 1.0 / I.z : 0
        );
        this.updateInertiaWorld(true);
    };

    /**
     * Get world velocity of a point in the body.
     * @method getVelocityAtWorldPoint
     * @param  {Vec3} worldPoint
     * @param  {Vec3} result
     * @return {Vec3} The result vector.
     */
    Body.prototype.getVelocityAtWorldPoint = function(worldPoint, result){
        var r = new Vec3();
        worldPoint.vsub(this.position, r);
        this.angularVelocity.cross(r, result);
        this.velocity.vadd(result, result);
        return result;
    };