Illuminated.js Docs (v0.1)

A 2D lights and shadows rendering engine for your HTML5 web applications and games.
Show:

File: src/illuminated.js

/**
Illuminated.js - A 2D lights and shadows rendering engine for HTML5
applications and games.

@module illuminated
**/

// Declare a root "class" for the static methods on the main namespace.
/**
@class illuminated
**/

/*
Copyright (C) 2012 Gaëtan Renaudeau <renaudeau.gaetan@gmail.com>
http://greweb.fr/illuminated.js/

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

(function(cp){
  /**
  Vec2 represents a 2d position or a 2d vector.
  It is used everywhere in Illuminated.js.

  Vec2 is based on Box2d’s Vec2 except that in Illuminated.js a Vec2
  vector is immutable. It means every method creates a new Vec2 instance and
  you can safely use a same Vec2 instance everywhere because the immutability
  guarantees that properties will not be modified.

  @class Vec2
  @namespace illuminated
  @constructor
  @param {Number} [x=0] X coordinate for the vector.
  @param {Number} [y=0] Y coordinate for the vector.
  **/
  cp.Vec2 = function (x, y) {
    /**
    X coordinate for the vector.
    @property x
    @type Number
    @default 0
    **/
    this.x = x||0;

    /**
    Y coordinate for the vector.
    @property y
    @type Number
    @default 0
    **/
    this.y = y||0;
  }

  /**
  Returns a copy of this vector.
  @method copy
  @return {illuminated.Vec2} A new vector that is a copy of this vector.
  **/
  cp.Vec2.prototype.copy = function () {
    return new cp.Vec2(this.x, this.y);
  }

  /**
  Calculates the dot product of this vector and the given vector.
  @method dot
  @param {illuminated.Vec2} v A vector with which to calculate the dot product.
  @return {Number} The result of the dot product.
  **/
  cp.Vec2.prototype.dot = function (v) {
    return v.x*this.x + v.y*this.y;
  }

  /**
  Subtracts the given vector from this vector.
  @method sub
  @param {illuminated.Vec2} v A vector to subtract from this vector.
  @return {illuminated.Vec2} A new vector that is the result of the subtraction.
  **/
  cp.Vec2.prototype.sub = function (v) {
    return new cp.Vec2(this.x-v.x, this.y-v.y);
  }

  /**
  Adds the given vector to this vector.
  @method add
  @param {illuminated.Vec2} v A vector to add to this vector.
  @return {illuminated.Vec2} A new vector that is the result of the addition.
  **/
  cp.Vec2.prototype.add = function (v) {
    return new cp.Vec2(this.x+v.x, this.y+v.y);
  }

  /**
  Multiplies the given vector with this vector.
  @method mul
  @param {illuminated.Vec2} v A vector to multiply with this vector.
  @return {illuminated.Vec2} A new vector that is the result of the multiplication.
  **/
  cp.Vec2.prototype.mul = function (n) {
    return new cp.Vec2(this.x*n, this.y*n);
  }

  /**
  Returns the inverse of this vector.
  @method inv
  @return {illuminated.Vec2} A new vector that is the inverse of this vector.
  **/
  cp.Vec2.prototype.inv = function () {
    return this.mul(-1);
  }

  /**
  Calculates the squared distance between this vector and the given vector.
  @method dist2
  @param {illuminated.Vec2} v A vector with which the squared distance is calculated.
  @return {Number} The squared distance.
  **/
  cp.Vec2.prototype.dist2 = function (v) {
    var dx = this.x - v.x;
    var dy = this.y - v.y;
    return dx*dx + dy*dy;
  }

  /**
  Calculates the normalized form of this vector.
  @method normalize
  @return {illuminated.Vec2} A new vector in normalized form.
  **/
  cp.Vec2.prototype.normalize = function () {
    var length = Math.sqrt(this.length2());
    return new cp.Vec2(this.x/length, this.y/length);
  }

  /**
  Calculates the squared length of this vector.
  @method length2
  @return {Number} The squared length.
  **/
  cp.Vec2.prototype.length2 = function (v) {
    return this.x*this.x + this.y*this.y;
  }

  /**
  Returns a string representing this vector.
  @method toString
  @return {String} A string representing this vector.
  **/
  cp.Vec2.prototype.toString = function () {
    return this.x+","+this.y;
  }


  /**
  Determines if this vector is within the bounds defined by the given vectors.
  @method inBound
  @param {illuminated.Vec2} topleft A vector that is the top-left of the bounds.
  @param {illuminated.Vec2} bottomright A vector that is the bottom-right of the bounds.
  @return {Boolean} True if this vector is within the given bounds.
  **/
  cp.Vec2.prototype.inBound = function (topleft, bottomright) {
    return (topleft.x < this.x && this.x < bottomright.x
         && topleft.y < this.y && this.y < bottomright.y);
  }


  /**
  Abstract class for light objects.
  @class Light
  @constructor
  @param {Object} [options] Options to be applied to this light.
  @param {illuminated.Vec2} [options.position] Position of this light. (0,0) by default.
  @param {Number} [options.distance=100] Intensity of this light.
  @param {Number} [options.diffuse=0.8] How diffuse this light is.
  **/
  cp.Light = function (options) { extend(this, cp.Light.defaults, options); }

  cp.Light.defaults = {
    /**
    Position of this light. (0,0) by default.
    @property position
    @type Vec2
    @default new Vec2(0, 0)
    **/
    position: new cp.Vec2(),

    /**
    Intensity of this light.
    @property distance
    @type Number
    @default 100
    **/
    distance: 100,

    /**
    How diffuse this light is.
    @property diffuse
    @type Number
    @default 0.8
    **/
    diffuse: 0.8
  };
  
  /**
  Render the light onto the given context.
  @method render
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  light will be rendered.
  **/
  cp.Light.prototype.render = function (ctx) { }

  /**
  Render a mask representing the visibility. (Used by DarkMask.)
  @method mask
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the mask
  will be rendered.
  **/
  cp.Light.prototype.mask = function (ctx) {
    var c = this._getVisibleMaskCache();
    ctx.drawImage(
      c.canvas,
      Math.round(this.position.x-c.w/2),
      Math.round(this.position.y-c.h/2)
    );
  }

  /**
  Calculate the boundaries of this light using the light's distance.
  @method bounds
  @return {Object} An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.Light.prototype.bounds = function () {
    return {
      topleft: new cp.Vec2(this.position.x-this.distance, this.position.y-this.distance),
      bottomright: new cp.Vec2(this.position.x+this.distance, this.position.y+this.distance)
    }
  }
  
  /**
  Return the center of this light.
  i.e. The position where the light intensity is the highest
  @method center
  @return {illuminated.Vec2} A new vector that represents the center of this light.
  **/
  cp.Light.prototype.center = function () {
    return new cp.Vec2( this.distance, this.distance );
  }

  /**
  Invoke a function for every sample generated by this light.
  @method forEachSample
  @param {Function} f Function to be called for every sample. The function will
  be passed a vector representing the position of the sample.
  **/
  // Implement it by spreading samples and calling f at each time
  cp.Light.prototype.forEachSample = function (f) { f(this.position); }

  /**
  Creates a canvas context with the visible mask rendered onto it.
  @private
  @method _getVisibleMaskCache
  @return {CanvasRenderingContext2D} A canvas context with the visible mask
  rendered onto it.
  **/
  cp.Light.prototype._getVisibleMaskCache = function () {
    // By default use a radial gradient based on the distance
    var d = Math.floor(this.distance*1.4);
    var hash = ""+d;
    if (this.vismaskhash != hash) {
      this.vismaskhash = hash;
      var c = this._vismaskcache = createCanvasAnd2dContext(2*d, 2*d);
      var g = c.ctx.createRadialGradient(d, d, 0, d, d, d);
      g.addColorStop( 0, 'rgba(0,0,0,1)' );
      g.addColorStop( 1, 'rgba(0,0,0,0)' );
      c.ctx.fillStyle = g;
      c.ctx.fillRect(0, 0, c.w, c.h);
    }
    return this._vismaskcache;
  }


  /**
  Abstract class for opaque objects.
  @class OpaqueObject
  @constructor
  @param {Object} [options] Options to be applied to this opaque object.
  @param {Number} [options.diffuse] How diffuse this opaque object should be.
  **/
  cp.OpaqueObject = function (options) { extend(this, cp.OpaqueObject.defaults, options); }

  cp.OpaqueObject.defaults = {
    /**
    How diffuse this opaque object should be.
    @property diffuse
    @type Number
    @default 0.8
    **/
    diffuse: 0.8
  };

  /**
  Fill ctx with the shadows projected by this opaque object at the origin
  point, constrained by the given bounds.
  @method cast
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  shadows will be cast.
  @param {illuminated.Vec2} origin A vector that represents the origin for the casted shadows.
  @param {Object} bounds An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.OpaqueObject.prototype.cast = function (ctx, origin, bounds) { }

  /**
  Draw the path of the opaque object shape onto the ctx.
  @method path
  @param {CanvasRenderingContext2D} ctx The context onto which the path will be
  drawn.
  **/
  cp.OpaqueObject.prototype.path = function (ctx) { }

  /**
  Calculate the boundaries of this opaque object.
  @method bounds
  @return {Object} An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.OpaqueObject.prototype.bounds = function () { return { topleft: new cp.Vec2(), bottomright: new cp.Vec2() } }

  /**
  Determine if the given point is inside the object.
  @method contains
  @param {illuminated.Vec2} point The point to be checked.
  @return {Boolean} True if the opaque object contains the given point.
  **/
  cp.OpaqueObject.prototype.contains = function (point) { return false }


  // LIGHTS

  /**
  A circular light rendered as a radial gradient. Lamps can also be "oriented"
  in a specific direction.
  @class Lamp
  @extends illuminated.Light
  @constructor
  @example
      new Lamp({
        position: new Vec2(12, 34),
        distance: 100,
        diffuse: 0.8,
        color: 'rgba(250,220,150,0.8)',
        radius: 0,
        samples: 1,
        angle: 0,
        roughness: 0
      })
  @param {Object} [options] Options to be applied to this lamp.
  @param {illuminated.Vec2} [options.position] Position of this lamp. (0,0) by default.
  @param {Number} [options.distance=100] Intensity of this lamp.
  @param {Number} [options.diffuse=0.8] How diffuse this lamp is.
  @param {String} [options.color='rgba(250,220,150,0.8)'] The color emitted by
  the lamp. The color can be specified in any CSS format.
  @param {Number} [options.radius=0] The size of the lamp. Bigger lamps cast
  smoother shadows.
  @param {Number} [options.samples=1] The number of points which will be used
  for shadow projection. It defines the quality of the rendering.
  @param {Number} [options.angle=0] The angle of the orientation of the lamp.
  @param {Number} [options.roughness=0] The roughness of the oriented effect.
  **/
  cp.Lamp = function (options) { extend(this, cp.Light.defaults, cp.Lamp.defaults, options); }
  inherit(cp.Lamp, cp.Light);

  cp.Lamp.defaults = {
    /**
    The color emitted by the lamp. The color can be specified in any CSS format.
    @property color
    @type String
    @default 'rgba(250,220,150,0.8)'
    **/
    color: 'rgba(250,220,150,0.8)',

    /**
    The size of the lamp. Bigger lamps cast smoother shadows.
    @property radius
    @type Number
    @default 0
    **/
    radius: 0,

    /**
    The number of points which will be used for shadow projection. It defines
    the quality of the rendering.
    @property samples
    @type Number
    @default 1
    **/
    samples: 1,

    /**
    The angle of the orientation of the lamp.
    @property angle
    @type Number
    @default 0
    **/
    angle: 0,

    /**
    The roughness of the oriented effect.
    @property roughness
    @type Number
    @default 0
    **/
    roughness: 0
  };

  /**
  Return a string hash key representing this lamp.
  @private
  @method _getHashCache
  @return {String} The hash key.
  **/
  cp.Lamp.prototype._getHashCache = function () {
    return [this.color, this.distance, this.diffuse, this.angle, this.roughness].toString();
  }

  /**
  Return the center of this lamp.
  i.e. The position where the lamp intensity is the highest
  @method center
  @return {illuminated.Vec2} A new vector that represents the center of this lamp.
  **/
  cp.Lamp.prototype.center = function () {
    return new cp.Vec2( (1-Math.cos(this.angle)*this.roughness)*this.distance, (1+Math.sin(this.angle)*this.roughness)*this.distance );
  }

  /**
  Calculate the boundaries of this lamp based on its properties.
  @method bounds
  @return {Object} An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.Light.prototype.bounds = function () {
    var orientationCenter = new cp.Vec2(Math.cos(this.angle), -Math.sin(this.angle)).mul(this.roughness*this.distance);
    return {
      topleft: new cp.Vec2(this.position.x+orientationCenter.x-this.distance, this.position.y+orientationCenter.y-this.distance),
      bottomright: new cp.Vec2(this.position.x+orientationCenter.x+this.distance, this.position.y+orientationCenter.y+this.distance)
    }
  }

  /**
  Render a mask representing the visibility. (Used by DarkMask.)
  @method mask
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the mask
  will be rendered.
  **/
  cp.Lamp.prototype.mask = function (ctx) {
    var c = this._getVisibleMaskCache();
    var orientationCenter = new cp.Vec2(Math.cos(this.angle), -Math.sin(this.angle)).mul(this.roughness*this.distance);
    ctx.drawImage(c.canvas, Math.round(this.position.x+orientationCenter.x-c.w/2), Math.round(this.position.y+orientationCenter.y-c.h/2));
  }

  /**
  Renders this lamp's gradient onto a cached canvas at the given position.
  @private
  @method _getGradientCache
  @param {illuminated.Vec2} center The position of the center of the gradient to render.
  **/
  cp.Lamp.prototype._getGradientCache = function (center) {
    var hashcode = this._getHashCache();
    if (this._cacheHashcode == hashcode) {
      return this._gcache;
    }
    this._cacheHashcode = hashcode;
    var d = Math.round(this.distance);
    var D = d*2;
    var cache = createCanvasAnd2dContext(D, D);
    var g = cache.ctx.createRadialGradient(center.x, center.y, 0, d, d, d);
    g.addColorStop( Math.min(1,this.radius/this.distance), this.color );
    g.addColorStop( 1, cp.getRGBA(this.color, 0) );
    cache.ctx.fillStyle = g;
    cache.ctx.fillRect(0, 0, cache.w, cache.h);
    return this._gcache = cache;
  }

  /**
  Render the lamp onto the given context (without any shadows).
  @method render
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  light will be rendered.
  **/
  cp.Lamp.prototype.render = function (ctx) {
    var center = this.center();
    var c = this._getGradientCache(center);
    ctx.drawImage(c.canvas, Math.round(this.position.x-center.x), Math.round(this.position.y-center.y))
  }

  /**
  Invoke a function for every sample generated by this lamp. The samples for
  lamps are generated using a "spiral" algorithm.
  @method forEachSample
  @param {Function} f Function to be called for every sample. The function will
  be passed a vector representing the position of the sample.
  **/
  cp.Lamp.prototype.forEachSample = function (f) {
    // "spiral" algorithm for spreading emit samples
    for (var s=0; s<this.samples; ++s) {
      var a = s * GOLDEN_ANGLE;
      var r = Math.sqrt(s/this.samples)*this.radius;
      var delta = new cp.Vec2( Math.cos(a)*r, Math.sin(a)*r );
      f( this.position.add(delta) );
    }
  }

  /*
   * Spot
   * TODO
   */
  /*
  cp.Spot = function (position, distance) {
    this.position = position;
    this.distance = position;
  }
  inherit(cp.Spot, cp.Light);
  */

  /*
   * Neon
   * TODO
   */
  /*
  cp.Neon = function (position, distance, color, size, samples, angle) {
    this.position = position;
    this.distance = distance;
    this.color = color;
    this.size = size || 10;
    this.samples = samples || 2;
    this.angle = angle || 0;
  }
  inherit(cp.Neon, cp.Light);

  // TODO .center() and .bound()

  cp.Neon.prototype.render = function (ctx) {
    var center = this.center();
    var c = this._getGradientCache(center);
    ctx.drawImage(c.canvas, Math.round(this.position.x-center.x), Math.round(this.position.y-center.y))
  }

  cp.Neon.prototype._getHashCache = function () {
    return [this.color, this.distance, this.angle].toString();
  }

  cp.Neon.prototype._getGradientCache = function (center) {
    var hashcode = this._getHashCache();
    if (this.cacheHashcode == hashcode) {
      return this.gradientCache;
    }
    this.cacheHashcode = hashcode;
    var d = Math.round(this.distance);
    var D = d*2;
    var cache = createCanvasAnd2dContext(D, D);
    var g = cache.ctx.createRadialGradient(center.x, center.y, 0, d, d, d);
    g.addColorStop( 0, this.color );
    g.addColorStop( 1, 'rgba(0,0,0,0)' );
    cache.ctx.fillStyle = g;
    cache.ctx.fillRect(0, 0, cache.w, cache.h);
    return this.gradientCache = cache;
  }
  */

  /*
   * OrientedNeon: Neon with one side
   * TODO
   */
  /*
  cp.OrientedNeon = function (position, distance) {
    this.position = position;
    this.distance = position;
  }
  inherit(cp.OrientedNeon, cp.Light);
  */

  /* Get tangents from (0,0) to circle of radius with given center, for cp.DiscObject.prototype.cast. */
  function getTan2(radius, center) {
    var epsilon = getTan2.epsilon || 1e-6, // constant
        x0, y0, len2, soln, 
        solutions=[], a=radius;
    if (typeof a === "object" && typeof center === "number") { 
      var tmp=a; center = a; center = tmp; // swap
    }
    if (typeof center === "number") {
        // getTan2(radius:number, x0:number, y0:number)
        x0 = center;
        y0 = arguments[2];
        len2 = x0*x0 + y0*y0;
    } else {
        // getTans2(radius:number, center:object={x:x0,y:y0})
        x0 = center.x;
        y0 = center.y;
        len2 = center.length2();
    }
    // t = +/- Math.acos( (-a*x0 +/- y0 * Math.sqrt(x0*x0 + y0*y0 - a*a))/(x0*x0 + y0*y) );
    var len2a = y0 * Math.sqrt(len2 - a*a), 
        tt = Math.acos( (-a*x0 + len2a) / len2 ),
        nt = Math.acos( (-a*x0 - len2a) / len2 ),
        tt_cos = a*Math.cos(tt),
        tt_sin = a*Math.sin(tt),
        nt_cos = a*Math.cos(nt),
        nt_sin = a*Math.sin(nt);
    
    // Note: cos(-t) == cos(t) and sin(-t) == -sin(t) for all t, so find
    // x0 + a*cos(t), y0 +/- a*sin(t)
    // Solutions have equal lengths
    soln = new cp.Vec2(x0 + nt_cos, y0 + nt_sin);
    solutions.push(soln);
    var dist0 = soln.length2();
    
    soln = new cp.Vec2(x0 + tt_cos, y0 - tt_sin);
    solutions.push(soln);
    var dist1 = soln.length2();
    if ( Math.abs(dist0 - dist1) < epsilon ) return solutions;
    
    soln = new cp.Vec2(x0 + nt_cos, y0 - nt_sin);
    solutions.push(soln);
    var dist2 = soln.length2();
    // Changed order so no strange X of light inside the circle. Could also sort results.
    if ( Math.abs(dist1 - dist2) < epsilon ) return [soln, solutions[1]]; 
    if ( Math.abs(dist0 - dist2) < epsilon ) return [solutions[0], soln];
    
    soln = new cp.Vec2(x0 + tt_cos, y0 + tt_sin);
    solutions.push(soln);
    var dist3 = soln.length2();
    if ( Math.abs(dist2 - dist3) < epsilon ) return [solutions[2], soln];
    if ( Math.abs(dist1 - dist3) < epsilon ) return [solutions[1], soln];
    if ( Math.abs(dist0 - dist3) < epsilon ) return [solutions[0], soln];
    
    // return all 4 solutions if no matching vector lengths found.
    return solutions;
  }
  
  // OBJECTS

  /**
  A circular, opaque object.
  @class DiscObject
  @extends illuminated.OpaqueObject
  @constructor
  @param {Object} [options] Options to be applied to this disc object.
  @param {illuminated.Vec2} [options.center] Position of the disc object.
  @param {Number} [options.radius] Size of the disc object.
  @param {Number} [options.diffuse] How diffuse this disc object should be.
  **/
  cp.DiscObject = function (options) { extend(this, cp.OpaqueObject.defaults, cp.DiscObject.defaults, options); }
  inherit(cp.DiscObject, cp.OpaqueObject);

  cp.DiscObject.defaults = {
    /**
    Position of the disc object.
    @property center
    @type Vec2
    @default new illuminated.Vec2()
    **/
    center: new cp.Vec2(),

    /**
    Size of the disc object.
    @property radius
    @type Number
    @default 20
    **/
    radius: 20
  };

  /**
  Fill ctx with the shadows projected by this disc object from the origin
  point, constrained by the given bounds.
  @method cast
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  shadows will be cast.
  @param {illuminated.Vec2} origin A vector that represents the origin for the casted shadows.
  @param {Object} bounds An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.DiscObject.prototype.cast = function (ctx, origin, bounds) {
    var m = this.center;
    var originToM = m.sub(origin);

    // FIXED: this method was wrong... TODO must see http://en.wikipedia.org/wiki/Tangent_lines_to_circles
    // var d = new cp.Vec2(originToM.y, -originToM.x).normalize().mul(this.radius);
    
    // var a = this.center.add(d);
    // var b = this.center.add(d.inv());

    // var originToA = a.sub(origin);
    // var originToB = b.sub(origin);
    
    var tangentLines = getTan2(this.radius, originToM);
    var originToA = tangentLines[0];
    var originToB = tangentLines[1];
    var a = originToA.add(origin);
    var b = originToB.add(origin);

    // normalize to distance
    var distance = ((bounds.bottomright.x-bounds.topleft.x)+(bounds.bottomright.y-bounds.topleft.y))/2;
    originToM = originToM.normalize().mul(distance);
    originToA = originToA.normalize().mul(distance);
    originToB = originToB.normalize().mul(distance);
    
    // project points
    var oam = a.add(originToM);
    var obm = b.add(originToM);
    var ap = a.add(originToA);
    var bp = b.add(originToB);

    var start = Math.atan2(originToM.x, -originToM.y);
    ctx.beginPath();
    path(ctx, [b, bp, obm, oam, ap, a], true);
    ctx.arc(m.x, m.y, this.radius, start, start+Math.PI);
    ctx.fill();
  }

  /**
  Draw the path of the disc onto the ctx.
  @method path
  @param {CanvasRenderingContext2D} ctx The context onto which the path will be
  drawn.
  **/
  cp.DiscObject.prototype.path = function (ctx) {
    ctx.arc(this.center.x, this.center.y, this.radius, 0, _2PI);
  }
  
  /**
  Calculate the boundaries of this disc object.
  @method bounds
  @return {Object} An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.DiscObject.prototype.bounds = function () { 
    return { 
      topleft: new cp.Vec2(this.center.x-this.radius, this.center.y-this.radius),
      bottomright: new cp.Vec2(this.center.x+this.radius, this.center.y+this.radius)
    } 
  }
  
  /**
  Determine if the given point is inside the disc.
  @method contains
  @param {illuminated.Vec2} point The point to be checked.
  @return {Boolean} True if the disc object contains the given point.
  **/
  cp.DiscObject.prototype.contains = function (point) { 
    return point.dist2(this.center) < this.radius*this.radius;
  }

  /**
  An opaque polygon object
  @class PolygonObject
  @extends illuminated.OpaqueObject
  @constructor
  @param {Object} [options] Options to be applied to this disc object.
  @param {Array} options.points An array of
  {{#crossLink "illuminated.Vec2"}}{{/crossLink}} points that define the polygon.
  @param {Number} [options.diffuse] How diffuse this polygon object should be.
  **/
  cp.PolygonObject = function (options) { extend(this, cp.OpaqueObject.defaults, cp.PolygonObject.defaults, options); }
  inherit(cp.PolygonObject, cp.OpaqueObject);

  cp.PolygonObject.defaults = {
    /**
    An array of {{#crossLink "illuminated.Vec2"}}{{/crossLink}} points that
    define the polygon.
    @property points
    @type Array
    @default []
    **/
    points: []
  };

  /**
  Calculate the boundaries of this polygon object.
  @method bounds
  @return {Object} An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.PolygonObject.prototype.bounds = function () {
    var topleft = this.points[0].copy();
    var bottomright = topleft.copy();
    for (var p=1; p<this.points.length; ++p) {
      var point = this.points[p];
      if (point.x > bottomright.x)
        bottomright.x = point.x;
      if (point.y > bottomright.y)
        bottomright.y = point.y;
      if (point.x < topleft.x)
        topleft.x = point.x;
      if (point.y < topleft.y)
        topleft.y = point.y;
    }
    return { topleft: topleft, bottomright: bottomright };
  }

  /**
  Determine if the given point is inside the polygon.
  @method contains
  @param {illuminated.Vec2} point The point to be checked.
  @return {Boolean} True if the polygon object contains the given point.
  **/
  cp.PolygonObject.prototype.contains = function (p) {
    var points = this.points;
    var i, j=points.length-1;
    var x = p.x, y = p.y;
    var oddNodes = false;

    for (i=0; i<points.length; i++) {
      if ((points[i].y< y && points[j].y>=y
      ||   points[j].y< y && points[i].y>=y)
      &&  (points[i].x<=x || points[j].x<=x)) {
        if (points[i].x+(y-points[i].y)/(points[j].y-points[i].y)*(points[j].x-points[i].x)<x) {
          oddNodes=!oddNodes; 
        }
      }
      j=i; 
    }
    return oddNodes;
  }

  /**
  Draw the path of the polygon onto the ctx.
  @method path
  @param {CanvasRenderingContext2D} ctx The context onto which the path will be
  drawn.
  **/
  cp.PolygonObject.prototype.path = function (ctx) {
    path(ctx, this.points);
  }

  /**
  Fill ctx with the shadows projected by this polygon object from the origin
  point, constrained by the given bounds.
  @method cast
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  shadows will be cast.
  @param {illuminated.Vec2} origin A vector that represents the origin for the casted shadows.
  @param {Object} bounds An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary.
  **/
  cp.PolygonObject.prototype.cast = function (ctx, origin, bounds) {
    // The current implementation of projection is a bit hacky... do you have a proper solution?
    var distance = ((bounds.bottomright.x-bounds.topleft.x)+(bounds.bottomright.y-bounds.topleft.y))/2;
    this._forEachVisibleEdges(origin, bounds, function (a, b, originToA, originToB, aToB) {
      var m; // m is the projected point of origin to [a, b]
      var t = originToA.inv().dot(aToB)/aToB.length2();
      if (t<0)
        m = a;
      else if(t>1)
        m = b;
      else
        m = a.add( aToB.mul(t) );
      var originToM = m.sub(origin);
      // normalize to distance
      originToM = originToM.normalize().mul(distance);
      originToA = originToA.normalize().mul(distance);
      originToB = originToB.normalize().mul(distance);
      // project points
      var oam = a.add(originToM);
      var obm = b.add(originToM);
      var ap = a.add(originToA);
      var bp = b.add(originToB);
      ctx.beginPath();
      path(ctx, [a, b, bp, obm, oam, ap]);
      ctx.fill();
    });
  }


  /**
  Invoke a function for each of the visible edges in this polygon.
  @private
  @method _forEachVisibleEdges
  @param {illuminated.Vec2} origin A vector that represents the origin for the casted shadows.
  @param {Object} bounds An anonymous object with the properties topleft and
  bottomright. The property values are {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects representing the corners
  of the boundary of this polygon.
  @param {Function} f The function to be invoked.
  **/
  cp.PolygonObject.prototype._forEachVisibleEdges = function (origin, bounds, f) {
    var a = this.points[this.points.length-1], b;
    for (var p=0; p<this.points.length; ++p, a=b) {
      b = this.points[p];
      if (a.inBound(bounds.topleft, bounds.bottomright)) {
         var originToA = a.sub(origin);
         var originToB = b.sub(origin);
         var aToB = b.sub(a);
         var normal = new cp.Vec2(aToB.y, -aToB.x);
         if (normal.dot(originToA) < 0) {
           f(a, b, originToA, originToB, aToB);
         }
       }
    }
  }

  /**
  A rectangular, opqaue object.
  @class RectangleObject
  @extends illuminated.PolygonObject
  @constructor
  @param {Object} [options] Options to be applied to this rectangle object.
  @param {illuminated.Vec2} [options.topleft] A vector that is the top-left of the rectangle.
  @param {illuminated.Vec2} [options.bottomright] A vector that is the bottom-right of the rectangle.
  **/
  cp.RectangleObject = function (options) {
    extend(this, cp.OpaqueObject.defaults, cp.PolygonObject.defaults, cp.RectangleObject.defaults, options);
    this.syncFromTopleftBottomright();
  }
  inherit(cp.RectangleObject, cp.PolygonObject);

  cp.RectangleObject.defaults = {
    /**
    A vector that is the top-left of the rectangle.
    @property topleft
    @type Vec2
    @default new illuminated.Vec2()
    **/
    topleft: new cp.Vec2(),
    
    /**
    A vector that is the bottom-right of the rectangle.
    @property bottomright
    @type Vec2
    @default new illuminated.Vec2()
    **/
    bottomright: new cp.Vec2()
  };

  /**
  Initializes the points defining this rectangle based on its specified bounds.
  @private
  @method syncFromTopleftBottomright
  **/
  cp.RectangleObject.prototype.syncFromTopleftBottomright = function () {
    var a = this.topleft;
    var b = new cp.Vec2(this.bottomright.x, this.topleft.y);
    var c = this.bottomright;
    var d = new cp.Vec2(this.topleft.x, this.bottomright.y);
    this.points = [a, b, c, d];
  }

  /**
  Draws this rectangle onto the given context
  @method fill
  @param {CanvasRenderingContext2D} ctx The canvas context onto which the
  rectangle should be drawn.
  **/
  cp.RectangleObject.prototype.fill = function (ctx) {
    var x = this.points[0].x, y = this.points[0].y;
    ctx.rect(x, y, this.points[2].x-x, this.points[2].y-y);
  }

  /**
  An opaque line object
  @class LineObject
  @extends illuminated.PolygonObject
  @constructor
  @param {Object} [options] Options to be applied to this line object.
  @param {illuminated.Vec2} [options.a] A vector that is the first point of the line.
  @param {illuminated.Vec2} [options.b] A vector that is the last point of the line.
  **/
  cp.LineObject = function (options) {
    extend(this, cp.OpaqueObject.defaults, cp.PolygonObject.defaults, cp.LineObject.defaults, options);
    this.syncFromAB();
  }
  inherit(cp.LineObject, cp.PolygonObject);

  cp.LineObject.defaults = {
    /**
    A vector that is the first point of the line.
    @property a
    @type Vec2
    @default new illuminated.Vec2()
    **/
    a: new cp.Vec2(),

    /**
    A vector that is the last point of the line.
    @property b
    @type Vec2
    @default new illuminated.Vec2()
    **/
    b: new cp.Vec2()
  };

  /**
  Initializes the points defining this line based on its options.
  @private
  @method syncFromAB
  **/
  cp.LineObject.prototype.syncFromAB = function () {
    this.points = [this.a, this.b];
  }

  /**
  Defines the lighting of one light through a set of opaque objects.
  @class Lighting 
  @constructor
  @param {Object} [options] Options to be applied to this light.
  @param {illuminated.Light} [options.light] The source of the lighting.
  @param {Array} [options.objects] An array of 
  {{#crossLink "illuminated.OpaqueObject"}}{{/crossLink}} objects which stop the
  light and create shadows.
  **/
  cp.Lighting = function (opts) {
    extend(this, cp.Lighting.defaults, opts);
  }

  cp.Lighting.defaults = {
    /**
    The source of the lighting.
    @property light
    @type Light
    @default new illuminated.Light()
    **/
    light: new cp.Light(),

    /**
    An array of {{#crossLink "illuminated.OpaqueObject"}}{{/crossLink}} objects
    which stop the light and create shadows.
    @property objects
    @type Array
    @default []
    **/
    objects: []
  }

  /**
  Create caches for canvas contexts.
  @private
  @method createCache
  @param {Number} w Width of the contexts.
  @param {Number} h Height of the contexts.
  **/
  cp.Lighting.prototype.createCache = function (w, h) {
    this._cache = createCanvasAnd2dContext(w,h);
    this._castcache = createCanvasAnd2dContext(w,h);
  }

  /**
  Draw the shadows that are cast by the objects. You usually don't have to use
  it if you use render().
  @method cast
  @param {CanvasRenderingContext2D} ctxoutput The canvas context onto which the
  shadows will be drawn.
  **/
  cp.Lighting.prototype.cast = function (ctxoutput) {
    var light = this.light;
    var n = light.samples;
    var c = this._castcache;
    var ctx = c.ctx;
    ctx.clearRect(0, 0, c.w, c.h);
    // Draw shadows for each light sample and objects
    ctx.fillStyle = "rgba(0,0,0,"+Math.round(100/n)/100+")"; // Is there any better way?
    var bounds = light.bounds();
    var objects = this.objects;
    light.forEachSample(function (position) {
      var sampleInObject = false;
      for (var o=0; o<objects.length; ++o) {
        if (objects[o].contains(position)) {
          ctx.fillRect(bounds.topleft.x, bounds.topleft.y, bounds.bottomright.x-bounds.topleft.x, bounds.bottomright.y-bounds.topleft.y);
          return;
        }
      }
      objects.forEach(function(object) {
        object.cast(ctx, position, bounds);
      });
    });
    // Draw objects diffuse - the intensity of the light penetration in objects
    objects.forEach(function(object) {
      var diffuse = object.diffuse===undefined ? 0.8 : object.diffuse;
      diffuse *= light.diffuse;
      ctx.fillStyle = "rgba(0,0,0,"+(1-diffuse)+")";
      ctx.beginPath();
      object.path(ctx);
      ctx.fill();
    });
    ctxoutput.drawImage(c.canvas, 0, 0);
  }

  /**
  Compute the shadows to cast.
  @method compute
  @param {Number} w Width of the canvas context.
  @param {Number} h Height of the canvas context.
  **/
  cp.Lighting.prototype.compute = function (w,h) {
    if (!this._cache || this._cache.w != w || this._cache.h != h)
      this.createCache(w, h);
    var ctx = this._cache.ctx;
    var light = this.light;
    ctx.save();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    
    light.render(ctx);
    ctx.globalCompositeOperation = "destination-out";
    this.cast(ctx);
    ctx.restore();
  }

  /**
  Draws the light and shadows onto the given context.
  @method render
  @param {CanvasRenderingContext2D} ctx The canvas context on which to draw.
  **/
  cp.Lighting.prototype.render = function (ctx) {
    ctx.drawImage(this._cache.canvas, 0, 0);
  }

  /**
  Defines the dark layer which hides the dark area not illuminated by a set of
  lights.
  @class DarkMask
  @constructor
  @param {Object} [options] Options to be applied to this light.
  @param {Array} [options.lights] An array of
  {{#crossLink "illuminated.Light"}}{{/crossLink}} objects that illuminate the
  rest of the scene.
  @param {String} [options.color] The color of the dark area in RGBA format.
  **/
  cp.DarkMask = function (options) {
    extend(this, cp.DarkMask.defaults, options);
  }

  cp.DarkMask.defaults = {
    /**
    An array of {{#crossLink "illuminated.Light"}}{{/crossLink}} objects that
    illuminate the rest of the scene.
    @property lights
    @type Array
    @default []
    **/
    lights: [],

    /**
    The color of the dark area in RGBA format.
    @property color
    @type String
    @default 'rgba(0,0,0,0.9)'
    **/
    color: 'rgba(0,0,0,0.9)'
  }

  /**
  Compute the dark mask.
  @method compute
  @param {Number} w Width of the canvas context.
  @param {Number} h Height of the canvas context.
  **/
  cp.DarkMask.prototype.compute = function (w,h) {
    if (!this._cache || this._cache.w != w || this._cache.h != h)
      this._cache = createCanvasAnd2dContext(w,h);
    var ctx = this._cache.ctx;
    ctx.save();
    ctx.clearRect(0, 0, w, h);
    ctx.fillStyle = this.color;
    ctx.fillRect(0, 0, w, h);
    ctx.globalCompositeOperation = "destination-out";
    this.lights.forEach(function(light){
      light.mask(ctx);
    });
    ctx.restore();
  }

  /**
  Draws the dark mask onto the given context.
  @method render
  @param {CanvasRenderingContext2D} ctx The canvas context on which to draw.
  **/
  cp.DarkMask.prototype.render = function (ctx) {
    ctx.drawImage(this._cache.canvas, 0, 0);
  }

   // UTILS & CONSTANTS

  var GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
  var _2PI = 2*Math.PI;
  
  /**
  @class illuminated
  **/

  /**
  Creates a canvas and context with the given width and height.
  @static
  @method createCanvasAnd2dContext
  @for illuminated
  @param {Number} w Width of the canvas context.
  @param {Number} h Height of the canvas context.
  @return {Object} An anonymous object with "canvas", "ctx", "w" and "h"
  properties.
  **/
  function createCanvasAnd2dContext (w, h) {
    var canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    return { canvas: canvas, ctx: canvas.getContext("2d"), w: w, h: h };
  }
  cp.createCanvasAnd2dContext = createCanvasAnd2dContext;

  /**
  Draw a path defined by the given points onto the given ctx.
  @static
  @method path
  @param {CanvasRenderingContext2D} ctx The context onto which the properties
  should be drawn.
  @param {Array} points An array of
  {{#crossLink "illuminated.Vec2"}}{{/crossLink}} objects that define the path.
  @param {Boolean} dontJoinLast True if the last point should joined with the
  first point in the path.
  **/
  function path (ctx, points, dontJoinLast) {
    var p = points[0];
    ctx.moveTo(p.x, p.y);
    for (var i=1; i<points.length; ++i) {
      p = points[i];
      ctx.lineTo(p.x, p.y);
    }
    if (!dontJoinLast && points.length>2) {
      p = points[0];
      ctx.lineTo(p.x, p.y);
    }
  }
  cp.path = path;

  /**
  Converts a CSS color string into RGBA format.
  @static 
  @method getRGBA
  @param {String} color Color in any CSS format.
  @param {Number} alpha Alpha value for produced color.
  @return {String} Color in RGBA format.
  **/
  var getRGBA = cp.getRGBA = (function(){
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 1;
    var ctx = canvas.getContext("2d");
    return function (color, alpha) {
      ctx.clearRect(0,0,1,1);
      ctx.fillStyle = color;
      ctx.fillRect(0,0,1,1);
      var d = ctx.getImageData(0,0,1,1).data;
      return 'rgba('+[ d[0], d[1], d[2], alpha ]+')';
    }
  }());

  /**
  Converts a CSS color string into an anonymous object with color and alpha
  properties.
  @static 
  @method extractColorAndAlpha
  @param {String} color Color in any CSS format.
  @return {Object} An anonymous object with the properties color and alpha.
  The color property is a string in hex format and the alpha property is a
  number from 0.0 to 1.0, rounded to 3 decimal places.
  **/
  var extractColorAndAlpha = cp.extractColorAndAlpha = (function(){
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 1;
    var ctx = canvas.getContext("2d");

    function toHex (value) { 
      var s = value.toString(16); 
      if(s.length==1) s = "0"+s;
      return s;
    }

    return function (color) {
      ctx.clearRect(0,0,1,1);
      ctx.fillStyle = color;
      ctx.fillRect(0,0,1,1);
      var d = ctx.getImageData(0,0,1,1).data;
      return {
        color: "#"+toHex(d[0])+toHex(d[1])+toHex(d[2]),
        alpha: Math.round(1000*d[3]/255)/1000
      };
    }
  }());

  /**
  Merges the properties from the given parameters into the first parameter.
  @static 
  @method extend
  @param {Object} mergeInto An object to merge into.
  @param {Object} mergeFrom* Objects to merge from.
  **/
  function extend (extending /* , arg1, arg2, ... */) {
    for(var a=1; a<arguments.length; ++a) {
      var source = arguments[a];
      if (source) {
        for (var prop in source)
          if (source[prop] !== void 0)
            extending[prop] = source[prop];
      }
    }
  }
  cp.extend = extend;

  function emptyFn() {};
  function inherit (cls, base) { // from Box2d
    var tmpCtr = cls;
    emptyFn.prototype = base.prototype;
    cls.prototype = new emptyFn;
    cls.prototype.constructor = tmpCtr;
    cls.prototype.__super = base.prototype;
  }
  cp.inherit = inherit;

}(window.illuminated={}));