'use strict';
var Vec2 = require('../Common/Vec2');
var Behavior = require('./Behavior');
/**
* Helbing-Farkas,Vicsek Simulating dynamical features of escape panic
*
* @class Panic
* @constructor
* @module CrowdSim
* @submodule Panic
* @param {World} world parent
* @param {Object} options for the behavior model algorithm
* @param {Object} [options.A] repulsive force constant
* @param {Object} [options.B] repulsive force constant
* @param {Object} [options.kn] compression large constant
* @param {Object} [options.Kv] friction large constant
* @param {Object} [options.relaxationTime] time to simulate progressive stopping
* @extends Behavior
*/
var Panic = function(world, options) {
Behavior.call(this, world);
this.options = Lazy(options).defaults(Panic.defaults).toObject();
};
/**
* Return the acceleration result for a agent going to its target.
*
* @method getAccel
* @param {Agent} agent
* @param {Object} target a destination with target.pos and target.in = function => path point, point, other agent
* @return {Vec2} acceleration result of the model
*/
Panic.prototype.getAccel = function(agent, target) {
Behavior.prototype.getAccel.call(this, agent, target);
var desiredForce = Vec2.create();
var agentsForce = Vec2.create();
var wallsForce = Vec2.create();
var accel = Vec2.create();
var arrived;
// check agent desired force
Vec2.add(accel, agentsForce, wallsForce);
if (target) { // agent is going somewhere?
arrived = target.in(agent.pos);
if (!arrived) {
Vec2.subtract(desiredForce, target.pos, agent.pos);
if (Vec2.length(desiredForce) > agent.maxAccel) {
Vec2.normalizeAndScale(desiredForce, desiredForce, agent.maxAccel * agent.mass);
}
}
}
// check other agents interaction
var neighbours = this.world.getNearAgents(agent);
if (neighbours.length) {
for (var n in neighbours) {
var neighbour = neighbours[n];
if (neighbour !== agent) {
var neighbourToAgentForce = this.calculateAgentForce(agent, neighbour);
Vec2.add(agentsForce, agentsForce, neighbourToAgentForce);
}
}
}
// check walls interaction
var walls = this.world.getNearWalls(agent);
if (walls.length > 0) {
for (var w in walls) { // check all walls
var wall = walls[w];
for (var s = 0; s < wall.getJoints().length - 1; s++) { // check each segment of wall
var projection = wall.getProjection(agent.pos, s);
var wallsToAgentForce = this.calculateWallForce(agent, projection, wall.getWidth());
Vec2.add(wallsForce, wallsForce, wallsToAgentForce);
}
}
}
// fix to stay in place if no target is selected or already at target
if (!target || arrived) {
Vec2.negate(desiredForce, agent.vel);
Vec2.scale(desiredForce, desiredForce, this.options.relaxationTime);
if (Vec2.length(desiredForce) > agent.maxAccel) {
Vec2.normalizeAndScale(desiredForce, desiredForce, agent.maxAccel);
}
}
Vec2.add3(accel, desiredForce, agentsForce, wallsForce);
// return desiredForce + agentsForce + wallsForce;
if (agent.debug) {
if (isNaN(desiredForce[0]) || isNaN(agentsForce[0]) || isNaN(wallsForce[0]) ||
isNaN(desiredForce[1]) || isNaN(agentsForce[1]) || isNaN(wallsForce[1])) {
throw 'One of the forces is a NaN!';
}
agent.debug.forces = {
desired: desiredForce,
agents: agentsForce,
walls: wallsForce
};
}
//console.log(Vec2.str(desiredForce) + '|' + Vec2.str(agentsForce) + '|' + Vec2.str(wallsForce));
return accel;
};
/**
* Calculate the social force between two agents i,j.
*
* @method calculateAgentForce
* @param {Agent} i
* @param {Agent} j
* @return {Vec2} force
*/
Panic.prototype.calculateAgentForce = function(i, j) {
var interactionForce = Vec2.create();
var rij = i.size + j.size;
var dij = Vec2.distance(i.pos, j.pos);
// ij direction
var nijV2 = Vec2.create();
Vec2.subtract(nijV2, i.pos, j.pos);
Vec2.scale(nijV2, nijV2, 1 / dij);
// ij tangencial direction
var tijV2 = Vec2.fromValues(-nijV2[1], nijV2[0]);
var rdij = rij - dij;
Vec2.scale(interactionForce, nijV2, this.options.A * Math.exp(rdij / this.options.B));
if (rdij > 0) { // agents touch each other
// ij tangencial velocity
Vec2.scaleAndAdd(interactionForce, interactionForce, nijV2, this.options.kn * rdij); // body force
// sliding friction
var vjiV2 = Vec2.create();
Vec2.subtract(vjiV2, j.vel, i.vel);
var deltaVji = Vec2.dot(vjiV2, tijV2);
Vec2.scaleAndAdd(interactionForce, interactionForce, tijV2, this.options.Kv * rdij * deltaVji);
}
return interactionForce;
};
/**
* Calculate the social force between an agent and a wall.
*
* @method calculateWallForce
* @param {Agent} i
* @param {Vec2} projection point the wall
* @param {Number} width of the wall
* @return {Vec2} force
*/
Panic.prototype.calculateWallForce = function(i, projection, width) {
var interactionForce = Vec2.create();
var rij = i.size + width;
// ij direction
var nijV2 = projection;
var dij = Vec2.length(projection);
Vec2.scale(nijV2, nijV2, 1 / dij);
// ij tangencial direction
var tijV2 = Vec2.fromValues(-nijV2[1], nijV2[0]);
var rdij = rij - dij;
Vec2.scale(interactionForce, nijV2, this.options.A * Math.exp(rdij / this.options.B));
if (rdij > 0) { // agents touch each other
// ij tangencial velocity
var vjiV2 = Vec2.create();
var dotViT = Vec2.dot(i.vel, tijV2);
Vec2.scaleAndAdd(interactionForce, interactionForce, nijV2, this.options.kn * rdij); // body force
Vec2.scaleAndAdd(interactionForce, interactionForce, tijV2, -this.options.Kv * rdij * dotViT);
}
return interactionForce;
};
Panic.defaults = {
A: 2e3, // N
B: 0.08, // m
kn: 1.2e5, // kg s-2
Kv: 2.4e5, //kg m-1 s-1
relaxationTime: 0.3
};
module.exports = Panic;