cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              // 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
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

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

Console