jam/js/simu/aiosXnet.js

879 lines
30 KiB
JavaScript

// Simulation extension for phyiscal (behaviorual) agents
var RTree = Require('rtree/rtree');
var RBush = Require('rtree/rbush');
var RBushKnn = Require('rtree/rbush-knn');
var Comp = Require('com/compat');
var current=none;
var Aios = none;
// patchgrid agent instance counter
var instances = 0;
// Geometric Utiliy Functions
function sind(x) { return Math.sin(x/360*(2*Math.PI)) }
function cosd(x) { return Math.cos(x/360*(2*Math.PI)) }
function rotate(d,a) {
return [
int(d[0]*cosd(a)-d[1]*sind(a)),
int(d[1]*sind(a)+d[0]*sind(a))
]
}
function distance2Rect (pos,bbox,scale) {
if (!scale) scale={x:1,y:1};
var px = pos.x,
py = pos.y,
x0 = bbox.x+bbox.w/2,
y0 = bbox.y+bbox.h/2,
dx = (Math.max(Math.abs(px - x0) - bbox.w / 2, 0))/scale.x,
dy = (Math.max(Math.abs(py - y0) - bbox.h / 2, 0))/scale.y;
return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
}
function distance (pos1,pos2,scale) {
if (!scale) scale={x:1,y:1};
var
dx = Math.abs(pos1.x - pos2.x) / scale.x,
dy = Math.abs(pos1.y - pos2.y) / scale.y;
return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
}
/* construct bbox {x,y,w,h} from geometric data
{x,y,x0,y0,x1,y1,dx,dy,w,h,dir} relative
to current position {x,y}
optional bounds {x0,y0,x1,y1}
+-----> x
| N x,y--+
| W X E | |
v S +--w,h
y
*/
function makeBbox (pos,geo,bounds) {
bbox={x:pos.x,y:pos.y,w:0,h:0} // {x,y,w,h}
if (typeof geo == 'number') // radius around center pos
return {x:pos.x-geo,y:pos.y-geo,w:2*geo+1,h:2*geo+1};
if (geo.x) bbox.x=geo.x;
if (geo.y) bbox.y=geo.y;
if (geo.x0) bbox.x=geo.x0;
if (geo.y0) bbox.x=geo.y0;
if (geo.dx) bbox.x=pos.x+geo.dx;
if (geo.dy) bbox.y=pos.y+geo.dy;
if (geo.w) bbox.w=geo.w;
if (geo.h) bbox.w=geo.h;
if (geo.x1) bbox.w=geo.x1-bbox.x+1;
if (geo.y1) bbox.h=geo.y1-bbox.y+1;
if (geo.r) return {x:bbox.x-geo.r,y:bbox.y-geo.r,w:2*geo.r+1,h:2*geo.r+1};
if (geo.dir) switch (geo.dir) {
// including current position X
// Ex. WEST:
// ****
// ***X
// ****
case Aios.DIR.NORTH:
if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
bbox.x -= int(bbox.w/2); bbox.y -= (bbox.h-1);
break;
case Aios.DIR.SOUTH:
if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
bbox.x -= int(bbox.w/2);
break;
case Aios.DIR.WEST:
if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
bbox.y -= int(bbox.h/2); bbox.x -= (bbox.w-1);
break;
case Aios.DIR.EAST:
if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
bbox.y -= int(bbox.h/2);
break;
}
return bbox;
}
function bbox2pp(bbox) {
return {x0:bbox.x,y0:bbox.y,x1:bbox.x+bbox.w-1,y1:bbox.y+bbox.h-1,
dir:bbox.dir,distance:bbox.distance}
}
function pp2bbox(pp) {
return {x:pp.x0,y:pp.y0,w:pp.x1-pp.x0+1,h:pp.y1-pp.y0+1}
}
function whatType(what) {
// agent-twin => agent
var tokens = what.match(/([a-z]+)(-.+)/)
return tokens?tokens[1]:what;
}
function whatName(what) {
// agent-twin => twin
var tokens = what.match(/[a-z]+-(.+)/)
return tokens?tokens[1]:null;
}
/*
** Generic simulation object iterator (NetLogo comp.)
** 1. Agents
** ask('agent','*',cb) // all
** ask('agent',null,cb) // here
** ask('agent',dir,cb)
** ask('agent',bbox,cb)
** ask('agents-class','*',cb)
** ask('agent',id:string,cb)
** ask('agent',[id1:string,id2,..],cb)
** ask('agent',5,cb) == in-radius 5
** ask('agent',[x,y],cb) == @(x,y)
** ask('agent',{x,y,w,h},cb?) == within[x,y,x+w,y+h]
** ask('agent',{x0,y0,x1,y1},cb?) == within[x,y,x+w,y+h]
** ask('agent',{dx,dy,w,h},cb?) == within[dx+x0,dy+y0,x0+dx+w,y0+dy+h]
** ask('agent',{x,y,w,h},cb?) == within[x,y-x+w,y+h]
** => returns only physical agents!
**
** 2. Resources
** ask('resource',..)
** ask('resources-class',..)
**
** 3. Patches
** ask('patch',..)
** ask('patches',..)
**
** Groups:
** ask('children')
** ask('parent')
**
** ask('distance',dir|number|bbox)
**
** Note: callback is executed in CURRENT agent context unless remote flag is set True:
** typeof callback = function (agent object,node object)
**
** TODO: check for consitency when using arrow callback functions (no this rebind possible)
*/
function aiosXnet(aiosXsimu,module) {
var self=this;
Aios=module;current=Aios.current;
function ask(what,who,callback,remote) {
var type = self.gui.classObject(what)||what,
node = current.node,nodeId,
pos = node.position, bbox, jump,
desc,id,pro,set=[],set2=[],set3=[],multiple=true,
i,j,r,row,col,p,q,agent,agents,nodes,pro,
name=what.match(/[a-z]+-(.+)/,'');
if (!self.options.patch) return;
if (name) name=name[1];
if (what.indexOf(type+'s')==0) type += 's';
switch (type) {
case 'agent':
multiple=false;
case 'agents':
if (typeof who == 'string') {
// entire world has to be searched for agent
if (who=='*') who=/.+/;
else if (self.cache.agent2node[who]) {
agent=self.cache.agent2node[who].getAgentProcess(0);
set=[{
agent:agent.agent.id,
class:agent.agent.ac,
pos:agent.node.position,
distance:distance(pos,agent.node.position),
obj:agent.agent
}]
set2=[agent.node]
}
if (set.length==0) {
agents=self.world.getAgentProcess(who,name);
if (agents) {
if (Comp.obj.isArray(agents)) {
set=agents.map(function (ap) {
return {
agent:ap.agent.id,
class:ap.agent.ac,
pos:ap.node.position,
distance:distance(pos,ap.node.position),
obj:ap.agent
}
});
set2=agents.map(function (ap) { return ap.node });
} else {
agent=agents;
self.cache.agent2node[agent.agent.id]=agent.node;
node = agent.node;
set.push({
agent:agent.agent.id,
class:agent.agent.ac,
pos:agent.node.position,
distance:distance(pos,agent.node.position),
obj:agent.agent
});
set2.push(node);
if (remote) set3.push(agent);
}
}
}
} else if ((Comp.obj.isArray(who) && who.length==2) || who==null) {
if (!who) who=[pos.x,pos.y];
if (self.agentMap) {
if (!self.checkBounds(who[0],who[1])) return [];
row=self.agentMap[who[1]];
col=row[who[0]];
for(p in col) {
agent=self.getAgentProcess(col[p],p);
if (agent && (!name || agent.agent.ac==name)) {
set.push({
agent:agent.agent.id,
class:agent.agent.ac,
pos:agent.node.position,
distance:distance(pos,agent.node.position),
obj:agent.agent
});
set2.push(agent.node);
if (remote) set3.push(agent);
}
}
}
} else if (typeof who == 'number' || Comp.obj.isObj(who)) {
// Bounding box {}?
bbox=bbox2pp(makeBbox(pos,who));
// find agents on nodes in the neighbourhood
if (self.agentMap) {
for(i=bbox.x0;i<=bbox.x1;i++)
for(j=bbox.y0;j<=bbox.y1;j++) {
if (!self.checkBounds(i,j)) continue;
row=self.agentMap[j];
col=row[i];
for(p in col) {
agent=self.getAgentProcess(col[p],p);
if (agent && (!name || agent.agent.ac==name)) {
set.push({
agent:agent.agent.id,
class:agent.agent.ac,
pos:agent.node.position,
distance:distance(pos,agent.node.position),
obj:agent.agent
});
set2.push(agent.node);
if (remote) set3.push(agent);
}
}
}
} else {
if (!name) return;
self.world.nodes.forEach(function (_node) {
if (wihtin(_node.position,node.position,who)) {
agents=_node.getAgentProcess(/[a-zA-Z]+/);
for(p in agents) {
if (agents[p].agent.ac==name) {
set.push({
agent:agents[p].agent.id,
class:agents[p].agent.ac,
pos:_node.position,
distance:distance(pos,_node.position),
obj:agents[p].agent
});
set2.push(_node);
}
}
}
})
}
}
break;
case 'resource':
multiple=false;
case 'resources':
var px = pos.x * self.options.patch.width,
py = pos.y * self.options.patch.height;
if (typeof who == 'string' && who.indexOf('DIR.')<0 && who != '*') {
var obj = self.gui.objects['resource['+who+']'];
if (obj) set = {resource:who,
class:obj.class,
data:obj.data,
distance:distance2Rect({x:px,y:py},
obj.visual,
{x:self.options.patch.width,
y:self.options.patch.height}),
x:int(obj.visual.x/self.options.patch.width),
y:int(obj.visual.y/self.options.patch.height),
w:int(obj.visual.w/self.options.patch.width),
h:int(obj.visual.h/self.options.patch.height)
}
} else if (typeof who == 'number' || Comp.obj.isObj(who) || who=='*' ||
(Comp.obj.isArray(who) && who.length==2) || who==null) {
if (!who) who=[pos.x,pos.y];
if (Comp.obj.isArray(who)) bbox={x:who[0],y:who[1],w:1,h:1}; // or w/h=0
else if (who=='*') bbox={x:0,y:0,w:self.options.x,h:self.options.y};
else bbox=makeBbox(pos,who);
set=self.rtree.search(bbox).map(function (rid) {
var obj = self.gui.objects[rid];
if (name && obj.class != name) return;
return {resource:rid.replace(/resource\[([^\]]+)\]/,'$1'),
class:obj.class,
data:obj.data,
distance:distance2Rect({x:px,y:py},
obj.visual,
{x:self.options.patch.width,
y:self.options.patch.height}),
x:int(obj.visual.x/self.options.patch.width),
y:int(obj.visual.y/self.options.patch.height),
w:int(obj.visual.w/self.options.patch.width),
h:int(obj.visual.h/self.options.patch.height)
}
}).filter (function (o) {return o});
}
break;
case 'patch':
multiple=false;
case 'patches':
if ((Comp.obj.isArray(who) && who.length==2) || who==null) {
if (!who) who=[pos.x,pos.y];
if (!self.checkBounds(who[0],who[1])) return [];
set=self.patches[who[1]][who[0]];
} else if (who=='*') {
set=self.patches;
} else if (typeof who == 'number' || Comp.obj.isObj(who)) {
// Bounding box?
bbox=bbox2pp(makeBbox(pos,who));
// find patches in the neighbourhood
if (name == 'array') {
for(j=bbox.y0;j<=bbox.y1;j++) {
for(i=bbox.x0;i<=bbox.x1;i++) {
if (!self.checkBounds(i,j)) continue;
set.push(self.patches[j][i])
}
}
} else for(j=bbox.y0;j<=bbox.y1;j++) {
row=[]
for(i=bbox.x0;i<=bbox.x1;i++) {
if (!self.checkBounds(i,j)) continue;
row.push(self.patches[j][i])
}
if (row.length==1) set.push(row[0])
else set.push(row)
}
}
break;
// groups
case 'parent':
if (node.parent) {
agents = node.parent.getAgent(/.+/);
set=agents && agents[0]?agents[0].id:undefined;
}
break;
case 'children':
multiple=true;
if (node.children) {
agents=[]
node.children.forEach(function (child) {
agents = agents.concat(child.getAgent(/.+/));
})
set=agents;
}
break;
case 'distance':
function lookup(x,y) {
var row,col,p,a=[];
if (jump && jump.x == x && jump.y == y) return;
row=self.agentMap[y];
if (!row) return;
col=row[x];
if (!col) return;
for(p in col) {
a.push(self.getNode(col[p]));
}
return a.length?a:null;
}
name=name||'';
if ((typeof who == 'string' && who.indexOf('DIR.')==0) ||
typeof who == 'number' || Comp.obj.isObj(who)) {
bbox={x:pos.x,y:pos.y}
if (typeof who == 'number') bbox.distance=who;
if (typeof who == 'string') bbox.dir=who;
if (typeof who == 'object') bbox.dir=who.dir,bbox.distance=who.distance;
agents=null;
switch (bbox.dir) {
case Aios.DIR.NORTH:
bbox={y1:bbox.y-1,y0:bbox.distance?(bbox.y-bbox.distance):0,
x0:pos.x,x1:pos.x,dir:bbox.dir}; break;
case Aios.DIR.SOUTH:
bbox={y0:bbox.y+1,y1:bbox.distance?(bbox.y+bbox.distance):self.options.y-1,
x0:pos.x,x1:pos.x,dir:bbox.dir}; break;
case Aios.DIR.WEST:
bbox={x1:bbox.x-1,x0:bbox.distance?(bbox.x+bbox.distance):0,
y0:pos.y,y1:pos.y,dir:bbox.dir}; break;
case Aios.DIR.EAST:
bbox={x0:bbox.x+1,x1:bbox.distance?(bbox.x+bbox.distance):self.options.x-1,
y0:pos.y,y1:pos.y,dir:bbox.dir}; break;
}
if (name.indexOf('resource')<0) {
// 1. Agents
// Jump over attached group children
if (node.children) {
jump=node.children[0].position;
}
switch (bbox.dir) {
case Aios.DIR.NORTH: for(i=bbox.y1;i>=bbox.y0 && !nodes;i--) nodes=lookup(pos.x,i); break;
case Aios.DIR.SOUTH: for(i=bbox.y0;i<=bbox.y1 && !nodes;i++) nodes=lookup(pos.x,i); break;
case Aios.DIR.WEST: for(i=bbox.x1;i>=bbox.x0 && !nodes;i--) nodes=lookup(i,pos.y); break;
case Aios.DIR.EAST: for(i=bbox.x0;i<=bbox.x1 && !nodes;i++) nodes=lookup(i,pos.y); break;
default:
// radius or full bbox search?
for(r=1;r<=bbox.distance;r++) {
// TODO
}
break;
}
if (nodes) nodes = {
distance:distance(pos,nodes[0].position),
objects:nodes.map(function (node) {
var a = node.getAgent(0);
return a?{agent:a.id,class:a.ac,pos:node.position}:undefined
})
}
}
if (name.indexOf('agent')<0) {
if (name.indexOf('resource')==0) name=null;
// 2. Resources
bbox=pp2bbox(bbox)
self.rtree.search(bbox).forEach(function (rid) {
var obj = self.gui.objects[rid];
var px = pos.x * self.options.patch.width,
py = pos.y * self.options.patch.height;
if (name && obj.class != name) return;
// only resources with distance < nodes.distance are considered
var d = distance2Rect({x:px,y:py},
obj.visual,
{x:self.options.patch.width,
y:self.options.patch.height})
rid=rid.replace(/resource\[([^\]]+)\]/,'$1');
if (nodes && nodes.distance==d) nodes.objects.push({
resource:rid,
class:obj.class,
data:obj.data,
distance:d,
x:int(obj.visual.x/self.options.patch.width),
y:int(obj.visual.x/self.options.patch.height),
w:int(obj.visual.w/self.options.patch.width),
h:int(obj.visual.h/self.options.patch.height)
}); else if (!nodes || d < nodes.distance) nodes = {
distance:d,
objects:[{
resource:rid,
class:obj.class,
data:obj.data,
distance:d,
x:int(obj.visual.x/self.options.patch.width),
y:int(obj.visual.x/self.options.patch.height),
w:int(obj.visual.w/self.options.patch.width),
h:int(obj.visual.h/self.options.patch.height)
}]
}
})
}
return nodes;
} else if (typeof who == 'string') {
// specific object id
// 1. Agent?
if (self.cache.agent2node[who]) {
return distance(pos,self.cache.agent2node[who].position)
}
}
break;
default:
remote=false;
break;
}
if (set && callback) {
if (Comp.obj.isMatrix(set))
for(p in set) {
for (q in set[p]) {
if (set2.length)
callback.call(current.process.agent,set[p][q],set2[p][q],current.process.agent);
else
callback.call(current.process.agent,set[p][q],q,p,current.process.agent);
}
}
else if (Comp.obj.isArray(set))
for(p in set) {
if (remote && set3.length) {
pro=set3[p];
Aios.CB(pro,callback,[pro.agent])
} else
callback.call(current.process.agent,set[p],set2[p],current.process.agent);
}
else {
if (remote && set3) {
pro=set3;
Aios.CB(pro,callback,[pro.agent])
} else
callback.call(current.process.agent,set,set2,current.process.agent);
}
}
if (!multiple && set) return set.length==0?null:(set.length==1?set[0]:set);
else return set;
}
/*
** Generic create operation (NetLogo comp., **physical** agents)
** Creates node+agent pair.
**
** Note: callback is executed in that agent context!
*/
function create(what,num,callback) {
var type = whatType(what),
node = current.node,nodeId,
desc,id,pro,
set=[],
name=whatName(what);
if (!self.options.patch) return;
for(var i=0;i<num;i++) switch (type) {
case 'agent':
case 'agents':
// 1. Get the agent descriptor
if (!name) self.err('create: agent class is missing')
desc=self.model.agents[name];
if (!desc) self.err('create: agent class '+name+' is unknown')
if (desc.type == 'physical') {
// Physical agent: Create a <node,agent> tuple; create agent on this node
nodeId = aiosXsimu.createNode(
name,
node.position.x, // use current position
node.position.y,
name+'-'+instances);
if (!nodeId) self.err('create: node class '+name+' cannot be created')
id=aiosXsimu.createOn(nodeId,name,{},3);
if (self.options.patch)
self.agentMap[node.position.y][node.position.x][id]=nodeId;
pro=self.getProcess(nodeId,id);
// the callback must be executed before
// agent starts execution! Prevent transition after CB
pro.notransition=true;
self.cache.agent2node[id]=pro.node;
pro.type=pro.node.type='physical';
// cover arrow functions too, no this rebind possible! first argument is self, too!
if (callback) Aios.CB(pro,callback,[pro.agent,i]);
set.push(id);
instances++;
} else {
// Create a new computational agent on this node
nodeId=node.id;
id=aiosXsimu.createOn(nodeId,name,{},3);
pro=self.getProcess(nodeId,id);
pro.notransition=true;
if (callback) Aios.CB(pro,callback,[pro.agent,i])
}
break;
}
return set.length==1? set[0]:set
}
function die (who) {
var node=current.node,
agent=current.process.agent;
if (!who) {
delete self.cache.agent2node[agent.id];
Aios.kill();
if (self.world.getNode(node.id)) aiosXsimu.deleteNode(node.id);
} else {
// TODO
}
}
function forward(delta) {
var i,x,y,
node=current.node,
_node,_agent,
agent=current.process.agent,
desc=self.model.agents[agent.ac],id,
agents=[current.process],
Delta=[0,0];
if (!self.options.patch || desc.type != 'physical') return;
id='node['+node.id+']';
var visual=aiosXsimu.getVisual(id);
if (!visual.heading) visual.heading=0;
if (visual.heading<90) Delta[1] -= delta;
else if (visual.heading<180) Delta[0] += delta;
else if (visual.heading<270) Delta[1] += delta;
else if (visual.heading<360) Delta[0] -= delta;
if (node.children) {
node.children.forEach(function (child) {
agents.push(child.getAgentProcess(0));
})
}
// check spatial bounds of all agents to be moved
for(i in agents) {
_agent=agents[i];
_node=_agent.node;
x=_node.position.x+Delta[0];
y=_node.position.y+Delta[1];
if (!self.checkBounds(x,y)) return;
}
// passed - now move all agents
for(i in agents) {
_agent=agents[i];
_node=_agent.node;
x=_node.position.x+Delta[0];
y=_node.position.y+Delta[1];
aiosXnet.setxy(x,y,'agent',_agent)
}
}
function globals() {
return self.model.parameter
}
function get(p) {
var agent=current.process.agent,
node=current.node,id,
desc=self.model.agents[agent.ac],
visual;
if (!self.options.patch) return;
switch (p) {
case 'color':
id='node['+node.id+']';
visual=aiosXsimu.getVisual(id);
return visual.fill.color;
break;
case 'heading':
id='node['+node.id+']';
visual=aiosXsimu.getVisual(id);
return visual.heading;
break;
case 'shape':
id='node['+node.id+']';
visual=aiosXsimu.getVisual(id);
return visual.shape;
break;
}
}
group = {
add: function (parent,children,align) {
var agent,desc,node,pos
agent=self.world.getAgentProcess(parent);
// if (!self.options.patch) return;
if (agent) {
node = agent.node;
pos = Comp.obj.copy(node.position);
switch (align) {
case Aios.DIR.NORTH: pos.y--; break;
case Aios.DIR.SOUTH: pos.y++; break;
case Aios.DIR.WEST : pos.x--; break;
case Aios.DIR.EAST : pos.x++; break;
}
if (!self.checkBounds(pos.x,pos.y)) return;
desc=self.model.agents[agent.agent.ac];
if (desc.type != 'physical') return;
if (!node.children) node.children=[];
children.forEach(function (child) {
var agent2 = self.world.getAgentProcess(child);
if (agent2) {
var desc2=self.model.agents[agent2.agent.ac];
var node2 = agent2.node;
if (desc2.type != 'physical') return;
var pos2 = Comp.obj.copy(pos);
node.children.push(node2);
node2.parent = node;
// move node container to group position
delete self.agentMap[node2.position.y][node2.position.x][agent2.agent.id];
node2.position = pos2;
self.agentMap[pos2.y][pos2.x][agent2.agent.id]=node2.id;
self.moveObjectTo('node['+node2.id+']',
self.world2draw(pos2).x,self.world2draw(pos2).y);
}
})
}
},
rem: function (parent, children) {
var agent,desc,node,pos
agent=self.world.getAgentProcess(parent);
if (agent) {
node = agent.node;
if (!node.children) return;
children.forEach(function (child) {
var agent2 = self.world.getAgentProcess(child);
if (agent2) {
var desc2=self.model.agents[agent2.agent.ac];
var node2 = agent2.node;
if (desc2.type != 'physical') return;
var pos2 = Comp.obj.copy(pos);
node.children=node.children.filter(function (_node) { return _node.id!=node2.id });
node2.parent = null;
}
})
}
},
}
// TODO send net agents a signal
function signal (destination,signal,argument) {
// destination is agent id;
// we need the node id, too
var agent = self.world.getAgentProcess(destination);
return agent?agent.node:'??'
}
function set(p,v) {
var node=current.node,
agent=current.process.agent,
desc=self.model.agents[agent.ac],
obj;
if (!self.options.patch) return;
switch (p) {
case 'color':
if (desc.type == 'physical') {
// Change color of node and agent
id='node['+node.id+']';
aiosXsimu.changeVisual(id,{fill:{color:v}});
id='agent['+agent.ac+':'+agent.id+':'+node.id+']';
aiosXsimu.changeVisual(id,{fill:{color:v}});
} else {
// Change color of agent
}
break;
case 'shape':
if (desc.type == 'physical') {
// Change shape of node
id='node['+node.id+']';
aiosXsimu.changeVisual(id,{shape:v,align:v=='circle'?'center':undefined});
} else {
// Change color of agent
}
break;
}
}
// TODO resources, ..
function setxy(x,y,what,who) {
var type=what||'agent',
desc,
pos,
node=current.node,
agent=current.process.agent;
// only discrete coordinates allowed (due to pos-map tables)
x=x|0; y=y|0;
if (!self.options.patch) return;
// self.log([x,y,type,agent.ac])
switch (type) {
case 'agent':
case 'agents':
if (typeof who == 'string') {
agent=aiosXnet.ask('agent',who);
if (!agent) return;
} else if (typeof who == 'object') {
agent=who.agent; // agent process!
node=who.node;
}
desc=self.model.agents[agent.ac];
if (!desc) return;
if (desc.type == 'physical') {
// move agent and its node!
if (!self.checkBounds(x,y)) return;
pos={x:x,y:y};
// checkBounds(x,y)
if (node.position) {
// Invalidate old worldmap entry
delete self.agentMap[node.position.y][node.position.x][agent.id];
}
self.agentMap[y][x][agent.id]=node.id;
self.moveObjectTo('node['+node.id+']',
self.world2draw(pos).x,self.world2draw(pos).y);
node.position=pos;
// Move child nodes, too (but not parent vice versa)!
if (node.children) {
node.children.forEach(function (node2) {
node2.processes.table.forEach(function (agent2) {
if (!agent2) return;
if (self.model.agents[agent2.agent.ac] &&
self.model.agents[agent2.agent.ac].type != 'physical') return;
if (node2.position) {
// Invalidate old worldmap entry
delete self.agentMap[node2.position.y][node2.position.x][agent2.agent.id];
}
self.agentMap[y][x][agent2.agent.id]=node2.id;
})
self.moveObjectTo('node['+node2.id+']',
self.world2draw(pos).x,self.world2draw(pos).y);
node2.position=pos;
})
}
} else {
// not supported!
}
break;
}
}
// Turn agent or group of agents ...
function turn(angle) {
var node=current.node,_node,_pos,visual,agents,
agent=current.process.agent, relative,
desc=self.model.agents[agent.ac],id,
pos=node.position,delta=0,Delta=[0,0];
switch (angle) {
case Aios.DIR.NORTH: angle=0; break;
case Aios.DIR.SOUTH: angle=180; break;
case Aios.DIR.WEST: angle=270; break;
case Aios.DIR.EAST: angle=90; break;
default:
if (typeof angle == 'number') relative=true;
}
if (!self.options.patch || desc.type != 'physical') return;
id='node['+node.id+']';
visual = aiosXsimu.getVisual(id)
if (!visual.heading) visual.heading=0;
if (relative) angle=(visual.heading+angle) % 360;
delta=angle-visual.heading;
// translate group children?
if (node.children && node.children.length) {
_pos=node.children[0].position;
// All children are overlayed !?
Delta=[_pos.x-pos.x,_pos.y-pos.y]
self.log({from:Delta, to:rotate(Delta,delta)})
Delta=rotate(Delta,delta);
node.children.forEach(function (child) {
var x=pos.x+Delta[0],y=pos.y+Delta[1];
if (!self.checkBounds(x,y)) return;
var _agent = child.getAgentProcess(0);
aiosXnet.setxy(x,y,'agent',_agent)
});
}
// update visual heading parameter
visual.heading = angle;
}
function within(x,y,bbox) {
return x >= bbox.x &&
y >= bbox.y &&
x < (bbox.x+bbox.w) &&
y < (bbox.y+bbox.h)
}
return {
ask:ask,
create:create,
die:die,
forward:forward,
globals:globals,
get:get,
group:group,
set:set,
setxy:setxy,
signal:signal,
turn:turn,
within:within
}
}
module.exports = aiosXnet