Source: components/physic/AabbComponent.js

/*global FM*/
/**
 * Represent an axis aligned bounding box to add to a game object for physics
 * behavior and collisions.
 * @class FM.AabbComponent
 * @extends FM.PhysicComponent
 * @param {int} pWidth Width of the aabb.
 * @param {int} pHeight Height of the aabb.
 * @param {FM.GameObject} pOwner The game object to which the component belongs.
 * @constructor
 * @author Simon Chauvin.
 */
FM.AabbComponent = function (pWidth, pHeight, pOwner) {
    "use strict";
    //Calling the constructor of the FM.PhysicComponent
    FM.PhysicComponent.call(this, pWidth, pHeight, pOwner);
    /**
     * Spatial component reference.
     * @type FM.SpatialComponent
     * @private
     */
    this.spatial = pOwner.components[FM.ComponentTypes.SPATIAL];
    /**
     * Check if the needed components are present.
     */
    if (FM.Parameters.debug) {
        if (!this.spatial) {
            console.log("ERROR: No spatial component was added and you need one for physics.");
        }
    }
};
/**
 * FM.AabbComponent inherits from FM.PhysicComponent.
 */
FM.AabbComponent.prototype = Object.create(FM.PhysicComponent.prototype);
FM.AabbComponent.prototype.constructor = FM.AabbComponent;
/**
 * Check if the current circle is overlapping with the specified type.
 * @method FM.AabbComponent#overlapsWithType
 * @memberOf FM.AabbComponent
 * @param {FM.ObjectType} pType The type to test for overlap with this 
 * aabb component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.AabbComponent.prototype.overlapsWithType = function (pType) {
    "use strict";
    //TODO
    return null;
};
/**
 * Check if the current aabb is overlapping with the given physic 
 * object.
 * @method FM.AabbComponent#overlapsWithObject
 * @memberOf FM.AabbComponent
 * @param {FM.PhysicComponent} pPhysic The physic component to test for
 * overlap with the current one.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.AabbComponent.prototype.overlapsWithObject = function (pPhysic) {
    "use strict";
    var collision = pPhysic.overlapsWithAabb(this);
    if (collision) {
        return collision;
    }
    return null;
};
/**
 * Check if the current aabb is overlapping with the specified aabb.
 * @method FM.AabbComponent#overlapsWithAabb
 * @memberOf FM.AabbComponent
 * @param {FM.AabbComponent} aabb The aabb component that needs to be tested
 * for overlap with the current aabb component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.AabbComponent.prototype.overlapsWithAabb = function (aabb) {
    "use strict";
    var otherSpatial = aabb.owner.components[FM.ComponentTypes.SPATIAL],
        min = new FM.Vector(this.spatial.position.x + this.offset.x, this.spatial.position.y + this.offset.y),
        otherMin = new FM.Vector(otherSpatial.position.x + aabb.offset.x, otherSpatial.position.y + aabb.offset.y),
        max = new FM.Vector(min.x + this.width, min.y + this.height),
        otherMax = new FM.Vector(otherMin.x + aabb.width, otherMin.y + aabb.height),
        center = new FM.Vector(min.x + this.width / 2, min.y + this.height / 2),
        otherCenter = new FM.Vector(otherMin.x + aabb.width / 2, otherMin.y + aabb.height / 2),
        normal = FM.Math.substractVectors(otherCenter, center),
        extent = (max.x - min.x) / 2,
        otherExtent = (otherMax.x - otherMin.x) / 2,
        xOverlap = extent + otherExtent - Math.abs(normal.x),
        yOverlap,
        collision = null;
    // Exit with no intersection if found separated along an axis
    if (max.x < otherMin.x || min.x > otherMax.x) { return null; }
    if (max.y < otherMin.y || min.y > otherMax.y) { return null; }

    if (xOverlap > 0) {
        extent = (max.y - min.y) / 2;
        otherExtent = (otherMax.y - otherMin.y) / 2;
        yOverlap = extent + otherExtent - Math.abs(normal.y);
        if (yOverlap > 0) {
            collision = new FM.Collision(this, aabb);
            // Find out which axis is the one of least penetration
            if (xOverlap < yOverlap) {
                if (normal.x < 0) {
                    collision.normal = normal.reset(-1, 0);
                } else {
                    collision.normal = normal.reset(1, 0);
                }
                collision.penetration = xOverlap;
            } else {
                if (normal.y < 0) {
                    collision.normal = normal.reset(0, -1);
                } else {
                    collision.normal = normal.reset(0, 1);
                }
                collision.penetration = yOverlap;
            }
            return collision;
        }
    }
    return null;
};
/**
 * Check if the current aabb is overlapping with the specified circle.
 * @method FM.AabbComponent#overlapsWithCircle
 * @memberOf FM.AabbComponent
 * @param {FM.CircleComponent} circle The circle component that needs to 
 * be tested for overlap with the current aabb component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.AabbComponent.prototype.overlapsWithCircle = function (circle) {
    "use strict";
    var otherSpatial = circle.owner.components[FM.ComponentTypes.SPATIAL],
        min = new FM.Vector(this.spatial.position.x + this.offset.x, this.spatial.position.y + this.offset.y),
        otherMin = new FM.Vector(otherSpatial.position.x + circle.offset.x, otherSpatial.position.y + circle.offset.y),
        max = new FM.Vector(min.x + this.width, min.y + this.height),
        center = new FM.Vector(min.x + this.width / 2, min.y + this.height / 2),
        otherCenter = new FM.Vector(otherMin.x + circle.radius, otherMin.y + circle.radius),
        normal = FM.Math.substractVectors(otherCenter, center),
        distance,
        radius,
        closest = normal.clone(),
        xExtent = (max.x - min.x) / 2,
        yExtent = (max.y - min.y) / 2,
        inside = false,
        collision = null;
    closest.x = FM.Math.clamp(closest.x, -xExtent, xExtent);
    closest.y = FM.Math.clamp(closest.y, -yExtent, yExtent);
    if (normal.isEquals(closest)) {
        inside = true;
        if (Math.abs(normal.x) > Math.abs(normal.y)) {
            if (closest.x > 0) {
                closest.x = xExtent;
            } else {
                closest.x = -xExtent;
            }
        } else {
            if (closest.y > 0) {
                closest.y = yExtent;
            } else {
                closest.y = -yExtent;
            }
        }
    }
    collision = new FM.Collision();
    collision.a = this;
    collision.b = circle;
    collision.normal = FM.Math.substractVectors(normal, closest);
    distance = collision.normal.getLengthSquared();
    radius = circle.radius;
    if (distance > radius * radius && !inside) {
        return null;
    }
    distance = Math.sqrt(distance);
    collision.penetration = radius - distance;
    if (inside) {
        collision.normal.reset(-collision.normal.x, -collision.normal.y);
    }
    collision.normal.normalize();
    return collision;
};
/**
 * Draw debug information.
 * @method FM.AabbComponent#drawDebug
 * @memberOf FM.AabbComponent
 * @param {CanvasRenderingContext2D} bufferContext Context on wich drawing 
 * is done.
 * @param {FM.Vector} newPosition Position of the sprite to render.
 */
FM.AabbComponent.prototype.drawDebug = function (bufferContext, newPosition) {
    "use strict";
    var newCenter = new FM.Vector(newPosition.x + this.width / 2, newPosition.y + this.height / 2),
        dir = new FM.Vector(Math.cos(this.spatial.angle), Math.sin(this.spatial.angle));
    bufferContext.strokeStyle = '#f4f';
    bufferContext.strokeRect(newPosition.x + this.offset.x - bufferContext.xOffset, newPosition.y + this.offset.y - bufferContext.yOffset, this.width,
                            this.height);
    bufferContext.beginPath();
    bufferContext.strokeStyle = "Blue";
    bufferContext.beginPath();
    bufferContext.moveTo(newCenter.x + this.offset.x - bufferContext.xOffset, newCenter.y + this.offset.y - bufferContext.yOffset);
    bufferContext.lineTo((newCenter.x + this.offset.x + dir.x * 50) - bufferContext.xOffset, (newCenter.y + this.offset.y  + dir.y * 50) - bufferContext.yOffset);
    bufferContext.stroke();
};
/**
 * Destroy the component and its objects.
 * @method FM.AabbComponent#destroy
 * @memberOf FM.AabbComponent
 */
FM.AabbComponent.prototype.destroy = function () {
    "use strict";
    this.spatial = null;
    FM.PhysicComponent.prototype.destroy.call(this);
};