From 7bff7e9f6817b4e4b59b543a6ab38c2167238021 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:06:58 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/simu/aiosXnet.js | 878 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 878 insertions(+) create mode 100644 js/simu/aiosXnet.js diff --git a/js/simu/aiosXnet.js b/js/simu/aiosXnet.js new file mode 100644 index 0000000..44dd3e0 --- /dev/null +++ b/js/simu/aiosXnet.js @@ -0,0 +1,878 @@ +// 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 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