Source: components/physic/CircleComponent.js

/*global FM*/
/**
 * A circle component is a physic component to add to a game object for
 * for collisions and physics behaviors as a circle.
 * @class FM.CircleComponent
 * @extends FM.PhysicComponent
 * @param {int} pRadius The radius of the circle component.
 * @param {FM.GameObject} pOwner The game object to which the component belongs.
 * @constructor
 * @author Simon Chauvin.
 */
FM.CircleComponent = function (pRadius, pOwner) {
    "use strict";
    //Calling the constructor of the FM.PhysicComponent
    FM.PhysicComponent.call(this, pRadius * 2, pRadius * 2, 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.");
        }
    }
    /**
     * Radius of the circle.
     * @type int
     * @public
     */
    this.radius = pRadius;
};
/**
 * FM.CircleComponent inherits from FM.PhysicComponent.
 */
FM.CircleComponent.prototype = Object.create(FM.PhysicComponent.prototype);
FM.CircleComponent.prototype.constructor = FM.CircleComponent;
/**
 * Check if the current circle is overlapping with the specified type.
 * @method FM.CircleComponent#overlapsWithType
 * @memberOf FM.CircleComponent
 * @param {FM.ObjectType} pType The type to test for overlap with this 
 * circle component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.CircleComponent.prototype.overlapsWithType = function (pType) {
    "use strict";
    //TODO
    return null;
};
/**
 * Check if the current circle is overlapping with the specified physic object.
 * @method FM.CircleComponent#overlapsWithObject
 * @memberOf FM.CircleComponent
 * @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.CircleComponent.prototype.overlapsWithObject = function (pPhysic) {
    "use strict";
    var collision = pPhysic.overlapsWithCircle(this);
    if (collision) {
        return collision;
    }
    return null;
};
/**
 * Check if the current circle is overlapping with the specified aabb.
 * @method FM.CircleComponent#overlapsWithAabb
 * @memberOf FM.CircleComponent
 * @param {FM.AabbComponent} aabb The aabb component that needs to be tested
 * for overlap with the current circle component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.CircleComponent.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),
        otherMax = new FM.Vector(otherMin.x + aabb.width, otherMin.y + aabb.height),
        center = new FM.Vector(min.x + this.radius, min.y + this.radius),
        otherCenter = new FM.Vector(otherMin.x + aabb.width / 2, otherMin.y + aabb.height / 2),
        normal = FM.Math.substractVectors(otherCenter, center),
        distance,
        radius,
        closest = normal.clone(),
        xExtent = (otherMax.x - otherMin.x) / 2,
        yExtent = (otherMax.y - otherMin.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 = aabb;
    collision.normal = FM.Math.substractVectors(normal, closest);
    distance = collision.normal.getLengthSquared();
    radius = this.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;
};

/**
 * Check if the current circle is overlapping with the specified circle.
 * @method FM.CircleComponent#overlapsWithCircle
 * @memberOf FM.CircleComponent
 * @param {FM.CircleComponent} circle The circle component that needs to 
 * be tested for overlap with the current circle component.
 * @return {FM.Collision} The collision object that contains the data of 
 * the collision if there is one, null otherwise.
 */
FM.CircleComponent.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),
        center = new FM.Vector(min.x + this.width / 2, min.y + this.height / 2),
        otherCenter = new FM.Vector(otherMin.x + circle.width / 2, otherMin.y + circle.height / 2),
        radius = this.radius + circle.radius,
        radius = radius * radius,
        normal = FM.Math.substractVectors(otherCenter, center),
        distance = normal.getLength(),
        collision = null;
    if (normal.getLengthSquared() > radius) {
        return null;
    } else {
        collision = new FM.Collision();
        collision.a = this;
        collision.b = circle;
        if (distance !== 0) {
            collision.penetration = radius - distance;
            collision.normal = normal.reset(normal.x / distance, normal.y / distance);
        } else {
            collision.penetration = this.radius;
            collision.normal = normal.reset(1, 0);
        }
        return collision;
    }
    return null;
};
/**
 * Draw debug information.
 * @method FM.CircleComponent#drawDebug
 * @memberOf FM.CircleComponent
 * @param {CanvasRenderingContext2D} bufferContext Context on wich drawing 
 * is done.
 * @param {FM.Vector} newPosition Position of the sprite to render.
 */
FM.CircleComponent.prototype.drawDebug = function (bufferContext, newPosition) {
    "use strict";
    var newCenter = new FM.Vector(newPosition.x + this.radius, newPosition.y + this.radius),
        dir = new FM.Vector(Math.cos(this.spatial.angle), Math.sin(this.spatial.angle));
    bufferContext.beginPath();
    bufferContext.strokeStyle = '#f4f';
    bufferContext.arc((newCenter.x + this.offset.x) - bufferContext.xOffset, (newCenter.y + this.offset.y) - bufferContext.yOffset, this.radius, 0, 2 * Math.PI, false);
    bufferContext.stroke();
    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.CircleComponent#destroy
 * @memberOf FM.CircleComponent
 */
FM.CircleComponent.prototype.destroy = function () {
    "use strict";
    this.spatial = null;
    this.radius = null;
    FM.PhysicComponent.prototype.destroy.call(this);
};