API Docs for: 0.3
Show:

File: src\sim\World.js

'use strict';

var Context = require('./Entities/Context');
var Group = require('./Entities/Group');
var Path = require('./Entities/Path');
var Wall = require('./Entities/Wall');
var Grid = require('./Common/Grid');

/**
 * The world where al entities live
 *
 * @class World
 * @module CrowdSim
 * @submodule World
 * @constructor
 * @param {Object} parent entity
 * @param {Object} options
 */
var World = function(parent, options) {
  this.options = Lazy(options).defaults(World.defaults).toObject();
  var that = this;
  this.parent = parent;
  this.agents = [];

  this.entities = {
    contexts: [],
    groups: [],
    paths: [],
    walls: []
  };
  this.grid = new Grid(this.options.near);
  this.gridWalls = new Grid(this.options.near);
  this.changes = 1;
  this.isFrozen = true;
};

/**
 * Gets and resets the number of steps executed since last call
 *
 * @method changesNumber
 * @return {Number} changes
 */
World.prototype.changesNumber = function() {
  var changes = this.changes;
  this.changes = 0;
  return changes;
};
/**
 * Get/set if the world is not running (Frozen). This is set from the Engine
 *
 * @method freeze
 * @param {Boolean} freeze
 * @return {Boolean} true if world is static
 */
World.prototype.freeze = function(freeze) {
  this.isFrozen = freeze || this.isFrozen;
  return this.isFrozen;
};

/**
 * Returns the first group created.
 *
 * @method getDefaultGroup
 * @return {Object} group
 */
World.prototype.getDefaultGroup = function() {
  return this.entities.groups[0];
};

/**
 * Get the list of Agents.
 *
 * @method getAgents
 * @return {Array} agents
 */
World.prototype.getAgents = function() {
  return this.agents;
};

/**
 * Gets a iterator for all entities.
 *
 * @method getEntitiesIterator
 * @return {Object} Lazy iterator
 */
World.prototype.getEntitiesIterator = function() {
  return Lazy(this.entities).values().flatten();
};

/**
 * Get the list of contexts.
 *
 * @method getContexts
 * @return {Array} contexts
 */
World.prototype.getContexts = function() {
  return this.entities.contexts;
};

/**
 * Get the list of groups.
 *
 * @method getGroups
 * @return {Array} groups
 */
World.prototype.getGroups = function() {
  return this.entities.groups;
};

/**
 * Get the list of paths.
 *
 * @method getPaths
 * @return {Array} paths
 */
World.prototype.getPaths = function() {
  return this.entities.paths;
};

/**
 * Get the list of walls.
 *
 * @method getWalls
 * @return {Array} walls
 */
World.prototype.getWalls = function() {
  return this.entities.walls;
};

/**
 * Add an array of Agents from the world.
 *
 * @method addAgents
 * @param {Array} agents
 */
World.prototype.addAgents = function(agents) {
  this.agents = this.agents.concat(agents);
  this.grid.insert(agents);
  if (this.options.onCreateAgents) {
    this.options.onCreateAgents(agents);
  }
};

/**
 * Remove an array of agents from the World.
 *
 * @method removeAgents
 * @param {Array} agents
 */
World.prototype.removeAgents = function(agents) {
  for (var i in agents) {
    var j = this.agents.indexOf(agents[i]);
    this.agents.splice(j, 1);
  }
  this.grid.remove(agents);
  if (this.options.onDestroyAgents) {
    this.options.onDestroyAgents(agents);
  }
};

/**
 * Get the list of agents in a given Context
 *
 * @method agentsInContext
 * @param {Context} context
 * @return {Array} all entities
 */
World.prototype.agentsInContext = function(context) {
  return this.grid.neighboursContext(context).filter(function(agent) {
    return context.in(agent.pos);
  }).toArray();
};

/**
 * Callback trigger when an entity is created
 *
 * @method _onCreate
 * @param  {Entity} entity Context, Group, Wall or Path
 */
World.prototype._onCreate = function(entity) {
  if (this.options.onCreateEntity) {
    this.options.onCreateEntity(entity);
  }
};

/**
 * Callback trigger when an entity is destroyed
 *
 * @method _onDestroy
 * @param  {Entity} entity Context, Group, Wall or Path
 */
World.prototype._onDestroy = function(entity) {
  if (this.options.onDestroyEntity) {
    this.options.onDestroyEntity(entity);
  }
};

/**
 * Returns the property that holds the entity list, used internally.
 *
 * @method _getEntityList
 * @param  {Entity} entity Context, Group, Wall or Path
 */
World.prototype._getEntityList = function(entity) {
  if (entity instanceof Context) { // is context
    return this.entities.contexts;
  } else if (entity instanceof Group) { // is group
    return this.entities.groups;
  } else if (entity instanceof Path) { // is path
    return this.entities.paths;
  } else if (entity instanceof Wall) { // is wall
    return this.entities.walls;
  } else {
    throw 'Entity object is not context, group, wall or path';
  }
};

/**
 * Remove an entity from the world. Called by entities on destroy.
 *
 * @method removeEntity
 * @param {Entity} entity Context, Group, Wall or Path
 */
World.prototype.removeEntity = function(entity) {
  var entityList = this._getEntityList(entity);
  var idx = entityList.indexOf(entity);
  if (idx !== -1) {
    entityList.splice(idx, 1);
    this._onDestroy(entity);
    this.changes++;
  }
};

/**
 * Add an entity from the world. Called by entities on constructor.
 *
 * @method addEntity
 * @param {Entity} entity Context, Group, Wall or Path
 */
World.prototype.addEntity = function(entity) {
  var entityList = this._getEntityList(entity);
  entityList.push(entity);
  this._onCreate(entity);
  this.changes++;
};

/**
 * Add a Context to the world.
 *
 * @method addContext
 * @param {Context} context
 */
World.prototype.addContext = function(context) {
  this.entities.contexts.push(context);
  this._onCreate(context);
};

/**
 * Add a Group to the world.
 *
 * @method addGroup
 * @param {Group} group
 */
World.prototype.addGroup = function(group) {
  this.entities.groups.push(group);
  this._onCreate(group);
};

/**
 * Add a Path to the world.
 *
 * @method addPath
 * @param {Path} path
 */
World.prototype.addPath = function(path) {
  this.entities.paths.push(path);
  this._onCreate(path);
};

/**
 * Add a Wall to the world.
 *
 * @method addWall
 * @param {Wall} wall
 */
World.prototype.addWall = function(wall) {
  this.entities.walls.push(wall);
  this._onCreate(wall);
};

/**
 * Search an entity in the world by its id.
 *
 * @method getEntityById
 * @param {String} id
 * @return {Entity}
 */
World.prototype.getEntityById = function(id) {
  return Lazy(this.entities).values().flatten().findWhere({id: id});
};

/**
 * Search an context in the world by its id.
 *
 * @method getContextById
 * @param {String} id
 * @return {Array} contexts
 */
World.prototype.getContextById = function(id) {
  return Lazy(this.entities.contexts).findWhere({id: id});
};

/**
 * Search an path in the world by its id.
 *
 * @method getPathById
 * @param {String} id
 * @return {Array} paths
 */
World.prototype.getPathById = function(id) {
  return Lazy(this.entities.paths).findWhere({id: id});
};

/**
 * Returns the agents near to another.
 * The near property is given in the options constructor parameter
 *
 * @method getNearAgents
 * @param {Agent} agent
 * @return {Array} agents
 */
World.prototype.getNearAgents = function(agent) {
  return this.grid.neighbours(agent).toArray();
};

/**
 * Returns the agents near to another.
 *
 * The near property is given in the options constructor parameter
 *
 * @method getNearWalls
 * @param {Agent} agent
 * @return {Array} walls
 */
World.prototype.getNearWalls = function(agent) {
  return this.gridWalls.neighbours(agent).uniq().toArray();
};

/**
 * Save the world state without agents.
 *
 * @method save
 * @param {Boolean} save true to store internally; false to return the JSON data
 */
World.prototype.save = function(save) {
  var raw = this._saveHelper(this.entities);
  if (save) {
    this.entitiesSave = raw;
  } else {
    console.log(raw);
    return raw;
  }
};

/**
 * Load the world state from a loader.
 *
 * @method load
 * @param {String|Function} loader
 * @param {Boolean} loadDefault true to load the last snapshoot created with save(true)
 */
World.prototype.load = function(loader, loadDefault) {
  if (!loader) {
    // snapshoot load
    if (loadDefault && this.entitiesSave) {
      loader = this.entitiesSave;
    } else {
      return;
    }
  }
  if (typeof(loader) === 'function') {
    // try function loader
    loader(this);
  } else {
    // loader of raw JSON strings
    if (typeof(loader) === 'string') {
      loader = JSON.parse(loader);
    }
    var world = this;
    // check if its json data
    // entites are arred to world passing its reference

    Lazy(loader.walls).each(function(e) {
      var joints = e.children.joints;
      var pos = e.children.joints ? [null, null] : e.pos; // to avoid duplicate init
      var wall = new Wall(pos[0], pos[1], world, e.options, e.id);
      Lazy(joints).each(function(j) {
        wall.addJoint(j.pos[0], j.pos[1], j.options, j.id);
      });
    });
    Lazy(loader.paths).each(function(e) {
      var joints = e.children.joints;
      var pos = e.children.joints ? [null, null] : e.pos; // to avoid duplicates init
      var path = new Path(pos[0], pos[1], world, e.options, e.id);
      Lazy(joints).each(function(j) {
        path.addJoint(j.pos[0], j.pos[1], j.options, j.id);
      });
    });
    Lazy(loader.contexts).each(function(e) {
      new Context(e.pos[0], e.pos[1], world, e.options, e.id);
    });
    Lazy(loader.groups).each(function(e) {
      var g = new Group(e.pos[0], e.pos[1], world, e.options, e.id);
      if (e.entities.startContext) {
        var startContext = world.getContextById(e.entities.startContext);
        g.assignStartContext(startContext);
      }
      if (e.entities.endContext) {
        var endContext = world.getContextById(e.entities.endContext);
        g.assignEndContext(endContext);
      }
      if (e.entities.path) {
        var path = world.getPathById(e.entities.path);
        g.assignPath(path, g.options.pathStart);
      }
    });
  }
  this.changes++;
};

/**
 * Save helper to remove loops and agents from world data.
 *
 * @method _saveHelper
 * @param  {Object} o the world.entities property
 * @return {String} JSON data that represents the world.entities
 */
World.prototype._saveHelper = function(o) {
    var ignore = ['view', 'extra', 'agents', 'parent', 'world'];
    var cache = [];
    var result = JSON.stringify(o, function(key, value) {
      if (ignore.indexOf(key) !== -1) { return; }
      if (key === 'entities') {
        var entities = {};
        // map entities to array of ids
        for (var prop in value) {
          entities[prop] = value[prop] ? value[prop].id : null;
        }
        return entities;
      }
      if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
          // Circular reference found, discard key
          throw 'Circular reference found!';
        }
        // Store value in our collection
        cache.push(value);
      }
      return value;
    }, 2);
    return result;
  };

/**
 * Advances the simulation of the world entities one stepSize.
 * If one contexts configured to nofify when agentsIn === 0
 *
 * @method step
 * @param {Number} stepSize defined by the simulation step size
 * @return {Context|Null} contextEmpty that triggers step
 */
World.prototype.step = function(stepSize) {
  var that = this;
  this.grid.updateAll(this.agents);
  this.gridWalls.updateWallsHelper(this.entities.walls);

  // check contexts interaction reducing speed or life of agents
  Lazy(this.entities.contexts).filter(function(c) {return c.getMobility() !== 1;}).each(function(context) {
    var agents = that.agentsInContext(context, that.agents);
    if (agents.length > 0) {
      Lazy(agents).each(function(agent) {
        agent.setCurrentMobility(context.getMobility());
      });
    }
  });

  Lazy(this.agents).each(function(agent) {
    agent.step(stepSize);
  });

  Lazy(this.entities.groups).each(function(group) {
    group.step(stepSize);
  });

  // check contexts that triggers stop on empty
  var contextEmpty = null;
  Lazy(this.entities.contexts).filter(function(c) {return c.getTrigger();}).each(function(context) {
    var agents = that.agentsInContext(context, that.agents);
    if (agents.length === 0) {
      contextEmpty = context; // to inform engine of stop source
    }
  });
  this.changes++;
  return contextEmpty;
};

World.defaults = {
  near: 8, // grid of 3x3 squares of 3 meters
  width: null,
  height: null,
  onCreateAgents: null,
  onDestroyAgents: null,
  onCreateEntity: null,
  onDestroyEntity: null
};
module.exports = World;