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.

Quick-add: + add another resource

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.

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.

            
              <head>
<canvas id="canvas"></canvas>
</head>
<body>
<div class="label">BOIDS</div>
<div class="instructions">DRAG TO MOVE CAMERA</div>
	</body>

            
          
!
            
              body {
    background-color: #fff;    
    margin: 0;
		overflow: hidden;
}
.label {
	position: absolute;
	top: 0;
	left: 0;
	padding: 5px 15px;
	color: #fff;
	font-size: 13px;
	background-color: rgba(0, 0, 0, .15);
}
.instructions {
	position: absolute;
	bottom: 0%;
	left: 0;
	padding: 5px 15px;
	color: #fff;
	font-size: 13px;
	background-color: rgba(0, 0, 0, .15);
}
canvas { display:block; }
            
          
!
            
              //Based on:
//http://www.red3d.com/cwr/boids/
//http://www.kfish.org/boids/pseudocode.html

var canvas = document.getElementById("canvas");
var TWO_PI = Math.PI*2;

  const mobile = ( navigator.userAgent.match(/Android/i)
      || navigator.userAgent.match(/webOS/i)
      || navigator.userAgent.match(/iPhone/i)
      //|| navigator.userAgent.match(/iPad/i)
      || navigator.userAgent.match(/iPod/i)
      || navigator.userAgent.match(/BlackBerry/i)
      || navigator.userAgent.match(/Windows Phone/i)
      );

//Spatial variables
var width = 2000;
var height = 2000;
var depth = 2000;
var centre = [width/2,height/2, depth/2];

//Agents
var boids = [];
var food = [];
var predators = [];

var boidCount;
if(mobile){
  boidCount = 200;
}else{
  boidCount = 600;
}

var foodCount = 10;
var predatorCount = 3;

var periodic = false;
var sphere = true;
var toggle_predators = false;

//Boid movement variables
var neighbourhood = width/6;
var proximity = width/40;
var minNeighbour = width/10;
var maxNeighbour = width/2;
var minProx = width/100;
var maxProx = 4*proximity;
var speed = 3;

var scene = new THREE.Scene();

var renderer = new THREE.WebGLRenderer({antialias: true, canvas: canvas});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x66deff, 1);
document.body.appendChild( renderer.domElement );

distance = 400;

var FOV = 2 * Math.atan( window.innerHeight / ( 2 * distance ) ) * 90 / Math.PI;

var camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 1, 20000);

camera.position.set(centre[0], centre[1], -1000);

window.addEventListener( 'resize', onWindowResize, false );

function onWindowResize(){

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
	renderer.setSize( window.innerWidth, window.innerHeight );
}

// --------------- //

//Lights
var light_1 = new THREE.AmbientLight( 0xffffff, 1.7); 
scene.add(light_1);

var light_2;
light_2 = new THREE.DirectionalLight(0xa0a0a0, 0.5);
light_2.position.set(0, -1, 0);
scene.add(light_2);

var light_3;
light_3 = new THREE.DirectionalLight(0xa0a0a0, 1);
light_3.position.set(0, 1, -1);
scene.add(light_3);

//Paper plane geometry
var geom = new THREE.Geometry();
var p_geom = new THREE.SphereGeometry( 5, 32, 32 );

//Brackets for purely aesthetic considerations
{
  geom.vertices.push(
      new THREE.Vector3(   0,  0.5,  0 ),
      new THREE.Vector3( -0.4, -0.6, 0 ),
      new THREE.Vector3( -0.1, -0.6, -0.1 ),
      new THREE.Vector3(   0, -0.6, 0.15 ),
      new THREE.Vector3(  0.1, -0.6, -0.1 ),
      new THREE.Vector3(  0.4, -0.6, 0 )
      );
}

geom.faces.push( new THREE.Face3( 0, 1, 2 ) );
geom.faces.push( new THREE.Face3( 0, 2, 3 ) );
geom.faces.push( new THREE.Face3( 0, 3, 4 ) );
geom.faces.push( new THREE.Face3( 0, 4, 5 ) );

geom.scale(30,30,30);
//Rotate planes to point forward with their noses
geom.rotateX( Math.PI / 2 );

var colour = 0xffffff;

var material = new THREE.MeshStandardMaterial( {color: colour, side: THREE.DoubleSide, shading: THREE.FlatShading} );
var p_material = new THREE.MeshStandardMaterial( {color: 0xff0000, side: THREE.DoubleSide, shading: THREE.FlatShading} );

//Generate random boids
for(i = 0; i < boidCount; i++){

  var g_ = new THREE.Mesh(geom, material);
  var x = 0.5 - Math.random();
  var y = 0.5 - Math.random();
  var z = 0.5 - Math.random();

  var boid = {
    vel_x: x,
    vel_y: y,
    vel_z: z,
    geo: g_
  };

  boid.geo.position.x = width/2 - Math.random() * width;
  boid.geo.position.y = height/2 - Math.random() * height;
  boid.geo.position.z = depth/2 - Math.random() * depth;
  boids.push(boid);
}

for(i = 0; i < boids.length; i++){
  scene.add(boids[i].geo);
}

//Generate food and predators
for(i = 0; i < predatorCount; i++){

  var g_ = new THREE.Mesh(geom, p_material);
  var x = 0.5 - Math.random();
  var y = 0.5 - Math.random();
  var z = 0.5 - Math.random();

  var predator = {
    vel_x: 0,
    vel_y: 0,
    vel_z: 0,
    geo: g_
  };

  predator.geo.position.x = width/2 - Math.random() * width;
  predator.geo.position.y = height/2 - Math.random() * height;
  predator.geo.position.z = depth/2 - Math.random() * depth;
  if(!toggle_predators){
    predator.geo.visible = false;
  }
  predators.push(predator);
};


for(i = 0; i < predators.length; i++){
  scene.add(predators[i].geo);
}

//-----------GUI-----------//
//dat.gui library controls
var reset_button = { reset:function(){ 
  toggle_predators = false;
  setTransparency();
  colour = 0xffffff;
  setColour(colour);
  periodic = false;
  sphere = true;
  speed = 3;
  neighbourhood = width/6;
  proximity = width/40;
  
  for(i = 0; i < boidCount; i++){

  var x = 0.5 - Math.random();
  var y = 0.5 - Math.random();
  var z = 0.5 - Math.random();

    boids[i].vel_x = x;
    boids[i].vel_y = y;
    boids[i].vel_z = z;

  boids[i].geo.position.x = width/2 - Math.random() * width;
  boids[i].geo.position.y = height/2 - Math.random() * height;
  boids[i].geo.position.z = depth/2 - Math.random() * depth;
}

}};

var gui = new dat.GUI();

gui.add(this, 'proximity').min(minProx).max(maxProx).step(10).listen();
gui.add(this, 'neighbourhood').min(minNeighbour).max(maxNeighbour).step(10).listen();
gui.add(this, 'speed').min(0).max(10).step(1).listen();
gui.add(this, 'sphere').listen().onChange(function(value) { periodic = false; sphere = true;} );
gui.add(this, 'periodic').listen().onChange(function(value) { periodic = true; sphere = false;} );
gui.addColor(this, 'colour').listen().onChange(function(value) { setColour();} );
gui.add(this, 'toggle_predators').listen().onChange(function(value){ setTransparency();});
gui.add(reset_button, 'reset');

gui.close();

function setColour(){
  material.color.setHex(colour);
}

function setTransparency(){
  if(toggle_predators){
    for(p = 0; p < predatorCount; p++){
      predators[p].geo.visible = true
      predators[p].geo.position.x = width/2 - Math.random() * width;
      predators[p].geo.position.y = height/2 - Math.random() * height;
      predators[p].geo.position.z = depth/2 - Math.random() * depth;;
    }
  }else{
    for(p = 0; p < predatorCount; p++){
      predators[p].geo.visible = false;
    }
  }
}


//----------RULES----------//
function rules(i){
  var direction = {
    x: 0,
    y: 0,
    z: 0
  } 
  var position = {
    x: 0,
    y: 0,
    z: 0
  };
  var spread = {
    x: 0,
    y: 0,
    z: 0
  };

  var neighbours = 0;
  var dist;
  var dot;
  for(j = 0; j < boidCount; j++){
    if(i != j){
      dist = Math.sqrt((boids[i].geo.position.x - boids[j].geo.position.x) * (boids[i].geo.position.x - boids[j].geo.position.x) + (boids[i].geo.position.y - boids[j].geo.position.y) * (boids[i].geo.position.y - boids[j].geo.position.y) +(boids[i].geo.position.z - boids[j].geo.position.z) * (boids[i].geo.position.z - boids[j].geo.position.z));
      dot = boids[i].vel_x * (boids[j].geo.position.x - boids[i].geo.position.x) + boids[i].vel_y * (boids[j].geo.position.y - boids[i].geo.position.y)  + boids[i].vel_z * (boids[j].geo.position.z - boids[i].geo.position.z);  
      if((dist < neighbourhood) && (dot > -0.75)){
        neighbours++;
        direction.x += boids[j].vel_x;
        direction.y += boids[j].vel_y;
        direction.z += boids[j].vel_z;

        position.x += boids[j].geo.position.x;
        position.y += boids[j].geo.position.y;
        position.z += boids[j].geo.position.z;
      }

      if(dist < proximity){
        spread.x = (boids[i].geo.position.x - boids[j].geo.position.x);
        spread.y = (boids[i].geo.position.y - boids[j].geo.position.y);
        spread.z = (boids[i].geo.position.z - boids[j].geo.position.z);

        boids[i].vel_x += (spread.x)/500;
        boids[i].vel_y += (spread.y)/500;
        boids[i].vel_z += (spread.z)/500;  
      }    
    }
  }

  if(toggle_predators){
    for(p = 0; p < predatorCount; p++){
      dist = Math.sqrt((boids[i].geo.position.x - predators[p].geo.position.x) * (boids[i].geo.position.x - predators[p].geo.position.x) + (boids[i].geo.position.y - predators[p].geo.position.y) * (boids[i].geo.position.y - predators[p].geo.position.y) +(boids[i].geo.position.z - predators[p].geo.position.z) * (boids[i].geo.position.z - predators[p].geo.position.z));
      if(dist < 0.5*neighbourhood){

        spread.x = (boids[i].geo.position.x - predators[p].geo.position.x);
        spread.y = (boids[i].geo.position.y - predators[p].geo.position.y);
        spread.z = (boids[i].geo.position.z - predators[p].geo.position.z);

        boids[i].vel_x += (spread.x)/150;
        boids[i].vel_y += (spread.y)/150;
        boids[i].vel_z += (spread.z)/150;  
      }
    }
  }
  direction.x /= neighbours;
  direction.y /= neighbours;
  direction.z /= neighbours;

  position.x /= neighbours;
  position.y /= neighbours;
  position.z /= neighbours;

  if(neighbours > 0){
    boids[i].vel_x += (direction.x - boids[i].vel_x)/200;
    boids[i].vel_y += (direction.y - boids[i].vel_y)/200;
    boids[i].vel_z += (direction.z - boids[i].vel_z)/200;

    boids[i].vel_x += (position.x - boids[i].geo.position.x)/500;
    boids[i].vel_y += (position.y - boids[i].geo.position.y)/500;
    boids[i].vel_z += (position.z - boids[i].geo.position.z)/500; 
  }
}

function selectPrey(p){
  var prey = 0;
  var minDist = width * 100;

  var position = {
    x: 0,
    y: 0,
    z: 0
  };

  for(i = 0; i < boidCount; i++){
    dist = Math.sqrt((boids[i].geo.position.x - predators[p].geo.position.x) * (boids[i].geo.position.x - predators[p].geo.position.x) + (boids[i].geo.position.y - predators[p].geo.position.y) * (boids[i].geo.position.y - predators[p].geo.position.y) +(boids[i].geo.position.z - predators[p].geo.position.z) * (boids[i].geo.position.z - predators[p].geo.position.z));
    dot = predators[p].vel_x * (boids[i].geo.position.x - predators[p].geo.position.x) + predators[p].vel_y * (boids[i].geo.position.y - predators[p].geo.position.y)  + predators[p].vel_z * (boids[i].geo.position.z - predators[p].geo.position.z);  
    if((Math.abs(dist) < Math.abs(minDist)) && (dot > 0.75)){
      minDist = dist;
      prey = i;  
    }
  }

  position.x = (predators[p].geo.position.x - boids[prey].geo.position.x);
  position.y = (predators[p].geo.position.y - boids[prey].geo.position.y);
  position.z = (predators[p].geo.position.z - boids[prey].geo.position.z);
 
  predators[p].vel_x -= position.x/150;
  predators[p].vel_y -= position.y/150;
  predators[p].vel_z -= position.z/150;
}

//----------MOVE----------//
function move(){
  for(i = 0; i < boidCount; i++){

    if(periodic){
      if(boids[i].geo.position.x > width/2){
        boids[i].geo.position.x = -width/2+10;
      }
      if(boids[i].geo.position.y > height/2){
        boids[i].geo.position.y = -height/2+10;
      }
      if(boids[i].geo.position.z > depth/2){
        boids[i].geo.position.z = -depth/2+10;
      }

      if(boids[i].geo.position.x < -width/2){
        boids[i].geo.position.x = width/2-10;
      }
      if(boids[i].geo.position.y < -height/2){
        boids[i].geo.position.y = height/2-10;
      }
      if(boids[i].geo.position.z < -depth/2){
        boids[i].geo.position.z = depth/2-10;
      }
    }

    if(sphere){
      var dist = Math.sqrt(boids[i].geo.position.x * boids[i].geo.position.x + boids[i].geo.position.y * boids[i].geo.position.y + boids[i].geo.position.z * boids[i].geo.position.z); 
      if(dist > width/2){
        boids[i].vel_x -= boids[i].geo.position.x/5000;
        boids[i].vel_y -= boids[i].geo.position.y/5000;
        boids[i].vel_z -= boids[i].geo.position.z/5000;
      }
    }

    var magnitude = Math.sqrt((boids[i].vel_x * boids[i].vel_x) + (boids[i].vel_y * boids[i].vel_y) + (boids[i].vel_z * boids[i].vel_z));

    if(magnitude > 0){
      boids[i].vel_x /= magnitude;
      boids[i].vel_y /= magnitude;
      boids[i].vel_z /= magnitude;
    }

    if(speed > 0){
      boids[i].vel_x *= speed;
      boids[i].vel_y *= speed;
      boids[i].vel_z *= speed;

      boids[i].geo.position.x += boids[i].vel_x;
      boids[i].geo.position.y += boids[i].vel_y;
      boids[i].geo.position.z += boids[i].vel_z;

      boids[i].geo.lookAt(new THREE.Vector3(boids[i].geo.position.x + boids[i].vel_x, boids[i].geo.position.y + boids[i].vel_y, boids[i].geo.position.z + boids[i].vel_z));
    }
  }

  if(toggle_predators){
    for(p = 0; p < predatorCount; p++){
      var dist = Math.sqrt(predators[p].geo.position.x * predators[p].geo.position.x + predators[p].geo.position.y * predators[p].geo.position.y + predators[p].geo.position.z * predators[p].geo.position.z); 
      if(dist > width/2){
        predators[p].vel_x -= predators[p].geo.position.x/50;
        predators[p].vel_y -= predators[p].geo.position.y/50;
        predators[p].vel_z -= predators[p].geo.position.z/50;
      }

      var magnitude = Math.sqrt((predators[p].vel_x * predators[p].vel_x) + (predators[p].vel_y * predators[p].vel_y) + (predators[p].vel_z * predators[p].vel_z));

      if(magnitude > 0){
        predators[p].vel_x /= magnitude;
        predators[p].vel_y /= magnitude;
        predators[p].vel_z /= magnitude;
      }

      if(speed > 0){
        predators[p].vel_x *= speed;
        predators[p].vel_y *= speed;
        predators[p].vel_z *= speed;

        predators[p].geo.position.x += predators[p].vel_x;
        predators[p].geo.position.y += predators[p].vel_y;
        predators[p].geo.position.z += predators[p].vel_z;

        predators[p].geo.lookAt(new THREE.Vector3(predators[p].geo.position.x + predators[p].vel_x, predators[p].geo.position.y + predators[p].vel_y, predators[p].geo.position.z + predators[p].vel_z));
      }
    }
  } 
}


//OrbitControls.js for camera manipulation
controls = new THREE.OrbitControls( camera, renderer.domElement );

//----------DRAW----------//
function draw(){

  if(toggle_predators){
  for(p = 0; p < predatorCount; p++){
    selectPrey(p);
  }
  }

  for(i = 0; i < boidCount; i++){
    rules(i);
  }

  move();
  renderer.render(scene, camera);
  requestAnimationFrame(draw);
}

requestAnimationFrame(draw);
            
          
!
999px
Loading ..................

Console