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 Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

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
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console