Source: Spatial.js

module.exports = Spatial;

var Vec2 = require('./Vec2.js');

/**
 * Basic component giving an entity the concept of being located somewhere.
 * Creates basic spatial heirachy, positions, rotation, etc.
 * @constructor
 */
function Spatial()
{
  // Alloc our types
  this.position     = new Vec2();
  this.scale        = new Vec2();
  this.hwidth       = new Vec2();

  this._absScale    = new Vec2();
  this._absPosition = new Vec2();
  this._absHwidth   = new Vec2();

  // Ref to quad tree node this component is in
  this._node = null;

  // Setup
  this.__init();
}

/**
 * Setup called by pool.
 * @private
 */
Spatial.prototype.__init = function()
{
  this.rotation = 0;

  // For spatial indexing
  this._quadEntry = null;

  // Parent component
  this.parent = null;

  this.position.clear();
  this.scale.set(1, 1);
  this.hwidth.clear();
  this._absPosition.clear();
  this._absScale.set(1, 1);
  this._absHwidth.clear();
};

/**
 * Attach this component to another entity's Spatial.
 * @param {{spatial: Spatial}} entity An entity with a Spatial component
 */
Spatial.prototype.attachTo = function(entity)
{
  if (this.parent)
    throw new Error ('Entity already attached');

  if (!entity || !entity.spatial)
    throw new Error('Invalid / missing entity');

  this.parent = entity.spatial;
};

/**
 * The absolute world position of this component.
 * @return {Vec2}
 */
Spatial.prototype.absPosition = function()
{
  var v = this._absPosition.assign(this.position);

  // Trivial case, no parent
  if (!this.parent) {
    return v;
  }

  var parent = this.parent;

  // Rotate our angle by parent's angle
  var r = this.absRotation();
  v.rotate(parent.absRotation());

  // Scale by parents scale
  var pScale = parent.absScale();
  v.set(v.x * pScale.x, v.y * pScale.y);

  // Add parent's position
  return v.add(parent.absPosition());
};

/**
 * The absolute world rotation of this component.
 * @return {Number}
 */
Spatial.prototype.absRotation = function()
{
  // Trivial case
  if (!this.parent)
    return this.rotation;

  // This + parent
  var r = this.rotation + this.parent.absRotation();

  return r % (Math.PI*2);
};

/**
 * The absolute world hwidth for this component.
 * @return {Vec2}
 */
Spatial.prototype.absHwidth = function()
{
  var h = this._absHwidth.assign(this.hwidth);

  if (!this.parent)
    return h;

  var scale = this.absScale();

  return h.set(h.x * scale.x, h.y * scale.y);
};

/**
 * The absolute world scale of this component.
 * @return {Vec2}
 */
Spatial.prototype.absScale = function()
{
  var v = this._absScale.assign(this.scale);

  // Trivial case
  if (!this.parent) {
    return v;
  }

  var pScale = this.parent.absScale();

  return v.set(v.x * pScale.x, v.y * pScale.y);
};