Source: Core/Engine_Physics.js

/* 
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */


/*jslint node: true, vars: true, white: true */
/*global vec2, CollisionInfo */
/* find out more about jslint: http://www.jslint.com/help.html */

"use strict";  // Operate in Strict mode such that variables must be declared before used!

/**
 * Static refrence to gEngine
 * @type gEngine
 */
var gEngine = gEngine || { };
    // initialize the variable while ensuring it is not redefined

/**
 * Default Constructor<p>
 * Physics engine supporting projection and impulse collision resolution. <p>
 * @class gEngine.Physics
 * @type gEngine.Physics
 */
gEngine.Physics = (function () {

    var mSystemtAcceleration = [0, -20];        // system-wide default acceleration
    var mPosCorrectionRate = 0.8;               // percentage of separation to project objects
    var mRelaxationCount = 15;                  // number of relaxation iteration
    
    var mCorrectPosition  = true;
    var mHasMotion = true;
    
    /**
     * Return Acceleration
     * @memberOf gEngine.Physics
     * @returns {Float[]} Current Acceleration [X, Y]
     */
    var getSystemtAcceleration = function() { return mSystemtAcceleration; };
    /**
     * Sets mCorrectPosition from true to false or vice versa
     * @memberOf gEngine.Physics
     */
    var togglePositionalCorrection = function() {
        mCorrectPosition = !mCorrectPosition;
    };
    /**
     * Return mCorrectPosition
     * @memberOf gEngine.Physics
     * @returns {boolean} mCorrectPosition
     */
    var getPositionalCorrection = function() {
        return mCorrectPosition;
    };
    /**
     * Sets mHasMotion from true to false or vice versa
     * @memberOf gEngine.Physics
     */
    var toggleHasMotion= function() {
        mHasMotion = !mHasMotion;
    };
    /**
     * Return mHasMotion
     * @memberOf gEngine.Physics
     * @returns {boolean} mHasMotion
     */
    var getHasMotion = function() {
        return mHasMotion;
    };
    /**
     * Increment the Relaxation Count
     * @memberOf gEngine.Physics
     * @param {float} dc The amount you want to increment the count by
     */
    var incRelaxationCount = function(dc) {
        mRelaxationCount += dc;
    };
    /**
     * Return Relaxation Count
     * @memberOf gEngine.Physics
     * @returns {float} mRelaxationCount
     */
    var getRelaxationCount = function() {
        return mRelaxationCount;
    };
    
    /**
     * Corrects the position of both colliding objects passed to it
     * @memberOf gEngine.Physics
     * @param {RigidShape} s1 The first rigid shape
     * @param {RigidShape} s2 The second rigid shape
     * @param {CollisionInfo} collisionInfo Used for position correction
     */
    var positionalCorrection = function (s1, s2, collisionInfo) {
        if (!mCorrectPosition)
            return;
        
        var s1InvMass = s1.getInvMass();
        var s2InvMass = s2.getInvMass();

        var num = collisionInfo.getDepth() / (s1InvMass + s2InvMass) * mPosCorrectionRate;
        var correctionAmount = [0, 0];
        vec2.scale(correctionAmount, collisionInfo.getNormal(), num);
        s1.adjustPositionBy(correctionAmount, -s1InvMass);
        s2.adjustPositionBy(correctionAmount, s2InvMass);
    };
    
    /**
     * Updates all of the physics variables of the 2 passed RigidShape
     * @memberOf gEngine.Physics
     * @param {RigidShape} s1 The first rigid shape
     * @param {RigidShape} s2 The second rigid shape
     * @param {CollisionInfo} collisionInfo Used for adjusting physic values
     */
    var resolveCollision = function (s1, s2, collisionInfo) {
        var n = collisionInfo.getNormal();

        //the direction of collisionInfo is always from s1 to s2
        //but the Mass is inversed, so start scale with s2 and end scale with s1
        var invSum = 1 / (s1.getInvMass() + s2.getInvMass());
        var start = [0, 0], end = [0, 0], p = [0, 0];
        vec2.scale(start, collisionInfo.getStart(), s2.getInvMass() * invSum);
        vec2.scale(end, collisionInfo.getEnd(), s1.getInvMass() * invSum);
        vec2.add(p, start, end);
        
        //r is vector from center of object to collision point
        var r1 = [0, 0], r2 = [0, 0];
        vec2.subtract(r1, p, s1.getCenter());
        vec2.subtract(r2, p, s2.getCenter());
        
        //newV = V + mAngularVelocity cross R
        var v1 = [-1 * s1.getAngularVelocity() * r1[1],
                       s1.getAngularVelocity() * r1[0]];
        vec2.add(v1, v1, s1.getVelocity());
        
        var v2 = [-1 * s2.getAngularVelocity() * r2[1], 
                       s2.getAngularVelocity() * r2[0]];
        vec2.add(v2, v2, s2.getVelocity());

        var relativeVelocity = [0, 0];
        vec2.subtract(relativeVelocity, v2, v1);

        // Relative velocity in normal direction
        var rVelocityInNormal = vec2.dot(relativeVelocity, n);

        //if objects moving apart ignore
        if (rVelocityInNormal > 0) {
            return;
        }

        // compute and apply response impulses for each object    
        var newRestituion = Math.min(s1.getRestitution(), s2.getRestitution());
        var newFriction = Math.min(s1.getFriction(), s2.getFriction());

        //R cross N
        var R1crossN = r1[0] * n[1] - r1[1] * n[0]; // r1 cross n
        var R2crossN = r2[0] * n[1] - r2[1] * n[0]; // r2 cross n

        // Calc impulse scalar
        // the formula of jN can be found in http://www.myphysicslab.com/collision.html
        var jN = -(1 + newRestituion) * rVelocityInNormal;
        jN = jN / (s1.getInvMass() + s2.getInvMass() +
                R1crossN * R1crossN * s1.getInertia() +
                R2crossN * R2crossN * s2.getInertia());

        //impulse is in direction of normal ( from s1 to s2)
        var impulse = [0, 0];
        vec2.scale(impulse, n, jN);
        // impulse = F dt = m * ?v
        // ?v = impulse / m
        vec2.scaleAndAdd(s1.getVelocity(), s1.getVelocity(), impulse, -s1.getInvMass());
        vec2.scaleAndAdd(s2.getVelocity(), s2.getVelocity(), impulse, s2.getInvMass());
        
        s1.setAngularVelocityDelta(-R1crossN * jN * s1.getInertia());
        s2.setAngularVelocityDelta(R2crossN * jN * s2.getInertia());

        var tangent = [0, 0];
        vec2.scale(tangent, n, rVelocityInNormal);
        vec2.subtract(tangent, tangent, relativeVelocity);
        vec2.normalize(tangent, tangent);

        var R1crossT = r1[0] * tangent[1] - r1[1] * tangent[0]; // r1.cross(tangent);
        var R2crossT = r2[0] * tangent[1] - r2[1] * tangent[0]; // r2.cross(tangent);
        var rVelocityInTangent = vec2.dot(relativeVelocity, tangent);

        var jT = -(1 + newRestituion) * rVelocityInTangent * newFriction;
        jT = jT / (s1.getInvMass() + s2.getInvMass() + 
                    R1crossT * R1crossT * s1.getInertia() + 
                    R2crossT * R2crossT * s2.getInertia());

        //friction should less than force in normal direction
        if (jT > jN) {
            jT = jN;
        }

        //impulse is from s1 to s2 (in opposite direction of velocity)
        vec2.scale(impulse, tangent, jT);
        vec2.scaleAndAdd(s1.getVelocity(), s1.getVelocity(), impulse, -s1.getInvMass());
        vec2.scaleAndAdd(s2.getVelocity(), s2.getVelocity(), impulse, s2.getInvMass());

        s1.setAngularVelocityDelta(-R1crossT * jT * s1.getInertia());
        s2.setAngularVelocityDelta(R2crossT * jT * s2.getInertia());
    };
    
    /**
     * Handles collisions between all objects within the set
     * @memberOf gEngine.Physics
     * @param {GameObjectSet} set The GameObjetSet that you want to run collision update on
     * @param {CollisionInfo} infoSet Used to collect all collision info
     */
    var processCollision = function(set, infoSet) {
        var i = 0, j = 0, r = 0;
        var iToj = [0, 0];
        var info = new CollisionInfo();
        for (r= 0; r<mRelaxationCount; r++) {
            for (i = 0; i<set.size(); i++) {
                var objI = set.getObjectAt(i).getRigidBody();
                for (j = i+1; j<set.size(); j++) {
                    var objJ = set.getObjectAt(j).getRigidBody();
                    if ( (objI.getInvMass() !== 0) || (objJ.getInvMass() !== 0) ) {
                        if (objI.boundTest(objJ)) {
                            if (objI.collisionTest(objJ, info)) {
                                // make sure info is always from i towards j
                                vec2.subtract(iToj, objJ.getCenter(), objI.getCenter());
                                if (vec2.dot(iToj, info.getNormal()) < 0)
                                    info.changeDir();
                                // infoSet.push(info);
                                positionalCorrection(objI, objJ, info);
                                resolveCollision(objI, objJ, info);
                                // info = new CollisionInfo();
                            }
                        }
                    }
                }
            }
        }
    };
    
    
    var mPublic = {
        getSystemAcceleration: getSystemtAcceleration,
        processCollision: processCollision,
        togglePositionalCorrection: togglePositionalCorrection,
        getPositionalCorrection: getPositionalCorrection,
        incRelaxationCount: incRelaxationCount,
        getRelaxationCount: getRelaxationCount,
        getHasMotion: getHasMotion,
        toggleHasMotion: toggleHasMotion
    };
    return mPublic;
}());