` ````
// changing these parameters can strongly affect emergent behaviour
var centering_factor = 0.002;
var avoidance_factor = 2;
var agent_optimal_distance = 0.01;
// our agents:
var agents = [];
// some obstacles:
var obstacles = [];
function make_predator() {
var a = {
pos: new vec2(random(), random()),
vel: vec2.random(random() * 0.004),
acceleration: new vec2(),
size: 1/30,
flipper_rate: 5*(random() + 1),
flipper_amount: 0.05,
max_speed: 0.008,
max_force: 0.0005,
field_of_view: 2,
range_of_view: 20,
type: "predator",
};
agents.push(a);
return a;
}
function make_agent() {
var a = {
pos: new vec2(random(), random()),
vel: vec2.random(random() * 0.004),
acceleration: new vec2(),
size: (random() + 1)/160,
flipper_rate: 30*(random() + 1),
flipper_amount: 0.5,
max_speed: 0.004,
max_force: 0.0002,
field_of_view: 2.5,
range_of_view: 5,
type: "prey",
};
agents.push(a);
return a;
}
// make a few agents:
make_predator();
for (var i=0; i<50; i++) make_agent();
// make obstacles:
function make_obstacle() {
var p = {
pos: new vec2(random(), random()),
size: (random() + 1)*0.03
};
obstacles.push(p);
return p;
}
for (var i=0; i<10; i++)make_obstacle();
function move_agent(a) {
// forward Euler integration + constraints
a.vel.add(a.acceleration).limit(a.max_speed);
a.pos.add(a.vel).wrap(1);
}
function update_agent(a) {
// we will compute a desired velocity
// intially, a simple random walker
// (take current velocity and rotate it slightly)
var flipper = Math.sin(a.flipper_rate * now);
var desired_velocity = a.vel.clone()
.rotate(a.flipper_amount*flipper)
.setmag(a.max_speed * random());
// useful to know our direction:
var dir = a.vel.angle();
// information to gather about visible neighbors:
var neighbours = 0;
// these are for the three steering forces:
var neighbour_locations = new vec2();
var neighbour_velocities = new vec2();
var neighbour_avoidances = new vec2();
// check for visible neighbours:
// (might not be the most optimal, but it is simple):
for (var n of agents) {
if (n == a) continue; // don't count yourself!
// get the (relative) vector to the neighbor from the agent:
// (clone() so that we don't modify n.pos)
var rel = n.pos.clone().sub(a.pos);
// because we are in toroidal space, spanning borders,
// there can be more than one relative vector
// this call makes sure we get the shortest one:
rel.relativewrap(1); // 1 is the size of our world
// to get the view distance, subtract sizes,
// (want distance between bodies, not between centers)
var distance = Math.max(rel.len() - a.size - n.size, 0);
// is the neighbour close enough to be seen?
var in_visible_range = distance < a.range_of_view * n.size;
// now rotate this into the view of the agent (global-to-local):
// (i.e. directly in front of the agent is an angle of zero)
var viewrel = rel.clone().rotate(-dir);
// is the neighbor within the agent's field of view?
// use absolute value to capture left & right sides:
var in_visible_angle = Math.abs(viewrel.angle()) < a.field_of_view;
// neighbour seen if within range & field of view:
if (in_visible_range && in_visible_angle) {
if (a.type == "prey" && n.type == "predator") {
// flee!
var fleepath = rel.clone().negate().normalize();
neighbour_avoidances.add(fleepath);
} else {
// yes -- add to count of neighbours
neighbours++;
// accumulate relative locations for centering force
neighbour_locations.add(rel);
// rotate neighbour velocity into agent's perspective,
// accumulate for aligning force
var relative_velocity = n.vel.clone().rotate(-dir);
neighbour_velocities.add(n.vel);
// are we likely to collide?
// compute from where we are *going* to be
var npos1 = n.pos.clone().add(n.vel);
var apos1 = a.pos.clone().add(a.vel);
var rel1 = npos1.sub(apos1);
rel1.relativewrap(1);
var distance1 = Math.max(rel1.len() - a.size - n.size, 0);
// feel uncomfortable if the neighbour is too close:
// e.g. closer than optimal distance
var optimal_distance = agent_optimal_distance;
var negative_feeling = Math.max(0, optimal_distance - distance);
if (negative_feeling > 0) {
var normalized = negative_feeling / optimal_distance;
if (a.type == "predator") {
neighbour_avoidances.add(rel.clone().setmag(+Math.sqrt(normalized)));
} else {
neighbour_avoidances.add(rel.clone().setmag(-Math.sqrt(normalized)));
}
}
}
}
}
for (var p of obstacles) {
// get the (relative) vector to the neighbor from the agent:
// (clone() so that we don't modify n.pos)
var rel = p.pos.clone().sub(a.pos);
// because we are in toroidal space, spanning borders,
// there can be more than one relative vector
// this call makes sure we get the shortest one:
rel.relativewrap(1); // 1 is the size of our world
// to get the view distance, subtract sizes,
// (want distance between bodies, not between centers)
var distance = Math.max(rel.len() - a.size - p.size, 0);
// is the neighbour close enough to be seen?
var in_visible_range = distance < a.range_of_view * p.size;
// now rotate this into the view of the agent (global-to-local):
// (i.e. directly in front of the agent is an angle of zero)
var viewrel = rel.clone().rotate(-dir);
// is the neighbor within the agent's field of view?
// use absolute value to capture left & right sides:
var in_visible_angle = Math.abs(viewrel.angle()) < a.field_of_view;
if (in_visible_range && in_visible_angle) {
// feel uncomfortable if the neighbour is too close:
// e.g. closer than optimal distance
var optimal_distance = agent_optimal_distance;
var negative_feeling = Math.max(0, optimal_distance - distance);
if (negative_feeling > 0) {
var normalized = negative_feeling / optimal_distance;
neighbour_avoidances.add(rel.clone().setmag(-Math.sqrt(normalized)));
}
}
}
// did we see anyone?
a.sees_neighbours = neighbours > 0;
if (a.sees_neighbours) {
// apply scaling to forces:
neighbour_locations.mul(centering_factor / neighbours);
desired_velocity.add(neighbour_locations);
if (a.type == "prey") {
neighbour_velocities.div(neighbours);
desired_velocity.add(neighbour_velocities);
}
}
// avoidances can happen even without neighbours (obstacles)
neighbour_avoidances.mul(avoidance_factor);
desired_velocity.add(neighbour_avoidances);
// to convert desired_velocity into a steering force,
// need to subtract current velocity
a.acceleration = desired_velocity.sub(a.vel);
// apply constraints:
a.acceleration.limit(a.max_force);
}
function update() {
// to separate passes to prevent artefacts
// (similar to double-buffering)
for (var a of agents) {
update_agent(a);
}
for (var a of agents) {
move_agent(a);
}
}
function draw() {
draw2D.color("grey");
for (var p of obstacles) {
draw2D.circle(p.pos, p.size);
}
for (var a of agents) {
// push into agent's local coordinate system
draw2D.push().translate(a.pos).rotate(a.vel).scale(a.size);
// draw agent body:
var hue = a.type == "predator" ? 0.7 : 0.5;
var active = a.sees_neighbours ? 0.7 : 0.4;
draw2D.hsl(hue, active, active);
draw2D.rect();
// draw agent eyes:
draw2D.color("white");
draw2D.circle([0.5, 0.5], 0.5);
draw2D.circle([0.5,-0.5], 0.5);
// done drawing agent:
draw2D.pop();
}
}
// click to add more agents:
function mouse(e, pt) {
if (e == "down") {
var a = agents[0];
a.pos.set(pt);
}
}
```

999px

Loading
..................

Alt F
Opt F
Find & Replace

Also see: Tab Triggers