> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.7U2vPnjVHOS/rev.2587
 * 
 * authors: 
 *   ManaT
 *   

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/***************************************************
 ***************************************************
 *                                                 *
 * Inspired by chapters in Valentino Braitenberg's *
 * "Vehicles: Experiments in Synthetic Psychology" *
 *                                                 *
 * Created by: Christopher Field                   *
 *                                                 *
 ***************************************************
 ***************************************************/


int numVehicles = 100;

ArrayList vehicles;
int numAlive;
int frameCount;

void setup() {
  size(500, 500);
  frameRate(30);
  strokeWeight(3);
  rectMode(CENTER);

  vehicles = new ArrayList();
  for (int i = 0; i < numVehicles; i++)
    vehicles.add(new Vehicle());
  numAlive = numVehicles;

  frameCount = 0;
}

void draw() {
  background(0);

  createGradient(250, 250, 200, color(250), color(0));
  for (int i = 0; i < vehicles.size(); i++) {
    Vehicle vehicle = (Vehicle)vehicles.get(i);
    vehicle.update();
    vehicle.draw();
  }

  //Draw death circle
  noFill();
  ellipse(250, 250, 100, 100);

  if (numAlive == 0) return; 

  frameCount++;
  if (frameCount >= numAlive && random(numVehicles) > numAlive) {
    frameCount %= numAlive;
    Vehicle parent = null;
    while (true) {
      int i = int(random(numVehicles));
      Vehicle vehicle = (Vehicle)vehicles.get(i);
      if (vehicle.alive) {
        parent = vehicle;
        break;
      }
    }

    for (int i = 0; i < vehicles.size(); i++) {
      Vehicle vehicle = (Vehicle)vehicles.get(i);
      if (!vehicle.alive) {
        vehicle.respawn(parent);
        break;
      }
    }
  }
}

void createGradient (float x, float y, float radius, color c1, color c2) {
  if (radius < 1) return;

  // calculate differences between color components
  float deltaR = red(c2)-red(c1);
  float deltaG = green(c2)-green(c1);
  float deltaB = blue(c2)-blue(c1);

  noFill();
  for (float i = 1; i < radius; i++) {
    float tempRed = red(c1) + deltaR * (i / radius);
    float tempGreen = green(c1) + deltaG * (i / radius);
    float tempBlue = blue(c1) + deltaB * (i / radius);
    stroke(tempRed, tempGreen, tempBlue);
    ellipse(x, y, i*2, i*2);
  }
}

class Vehicle {
  float x, y;
  float rotation;
  color skin;
  boolean alive;
  ArrayList eyes;
  ArrayList wheels;

  Vehicle() {
    x = random(500);
    y = random(500);
    skin = color(random(256), random(256), random(256));
    rotation = random(2)*PI;
    
    alive = true;
    
    wheels = new ArrayList();
    wheels.add(new Wheel(-10,-5));
    wheels.add(new Wheel(-10,5));
    
    eyes = new ArrayList();
    if (random(2) < 1) {
      eyes.add(new Eye(10,-5, (Wheel)wheels.get(0)));
      eyes.add(new Eye(10,5, (Wheel)wheels.get(1)));
    } else {
      eyes.add(new Eye(10,-5, (Wheel)wheels.get(1)));
      eyes.add(new Eye(10,5, (Wheel)wheels.get(0)));
    }
  }

  void update() {
    if (!alive) return;

    //Brownian motion
    rotation += random(-0.1, 0.1)*PI;
    x += random(-2, 2);
    y += random(-2, 2);

    //Check for death
    float lumines = dist(x, y, 250, 250);
    if (x >= 500 || x < 0 ||
      y >= 500 || y < 0 ||
      lumines < 50) {
      die();
      return;
    }

    //See
    Eye eye1 = (Eye)eyes.get(0);
    Eye eye2 = (Eye)eyes.get(1);
    eye1.see(250-dist(x+cos(rotation)*10,y+sin(rotation)*-5, 250, 250));
    eye2.see(250-dist(x+cos(rotation)*10,y+sin(rotation)*5, 250, 250));

    //Move
    PVector force = new PVector(0,0);
    Wheel wheel1 = (Wheel)wheels.get(0);
    Wheel wheel2 = (Wheel)wheels.get(1);
    force.add(new PVector(wheel1.strength*cos(rotation),wheel1.strength*sin(rotation)));
    force.add(new PVector(wheel2.strength*cos(rotation),wheel2.strength*sin(rotation)));
    
    rotation += (wheel1.strength-wheel2.strength) * 0.01*PI;
    x += force.x;
    y += force.y;
  }

  void draw() {
    if (!alive) return;
    
    pushMatrix();
    translate(x, y);
    rotate(rotation);
    fill(skin);
    rect(0, 0, 20, 10);
    popMatrix();
    
    for (int i = 0; i < eyes.size(); i++) {
      Eye eye = (Eye)eyes.get(i);
      eye.draw(x,y,rotation);
    }
    
    for (int i = 0; i < wheels.size(); i++) {
      Wheel wheel = (Wheel)wheels.get(i);
      wheel.draw(x,y,rotation);
    }
  }

  void die() {
    alive = false;
    numAlive--;
  }

  void respawn(Vehicle parent) {
    x = parent.x;
    y = parent.y;
    skin = parent.skin;
    rotation = parent.rotation-0.1*PI;
    parent.rotation += 0.1*PI;
    eyes = parent.eyes;
    alive = true;
    numAlive++;
    
    if (random(10) < 1) mutate();
  }
  
  void mutate() {
    switch(int(random(3))) {
      case 0:
        skin = color((red(skin)+random(-50,50))%256, green(skin), blue(skin));
        break;
      case 1:
        skin = color(red(skin), (green(skin)+random(-50,50))%256, blue(skin));
        break;
      case 2:
        skin = color(red(skin), green(skin), (blue(skin)+random(-50,50))%256);
        break;
      default:
        break;
    }
    skin = color(random(256), random(256), random(256));
    
    Eye eye = (Eye)eyes.get(int(random(2)));
    switch(int(random(5))) {
      case 0:
        eye.brightThreshold = random(250);
        eye.darkThreshold = random(eye.brightThreshold);
        break;
      case 1:
        eye.brightStrength = random(2);
        break;
      case 2:
        eye.darkStrength = random(2);
        break;
      case 3:
        eye.normalStrength = random(2);
        break;
      case 4:
        Eye eye0 = (Eye)eyes.get(0);
        Eye eye1 = (Eye)eyes.get(1);
        Wheel tempWheel = eye0.wheel;
        eye0.wheel = eye1.wheel;
        eye1.wheel = tempWheel;
        break;
      default:
        break;
    }
    
    if (random(10) < 1) xmutate();
  }
  
  void xmutate() {
    skin = color(random(256), random(256), random(256));
    
    eyes = new ArrayList();
    if (random(2) < 1) {
      eyes.add(new Eye(10,-5, (Wheel)wheels.get(0)));
      eyes.add(new Eye(10,5, (Wheel)wheels.get(1)));
    } else {
      eyes.add(new Eye(10,-5, (Wheel)wheels.get(1)));
      eyes.add(new Eye(10,5, (Wheel)wheels.get(0)));
    }
  }
}

class Eye {
  float x, y;
  Wheel wheel;
  float brightThreshold;
  float darkThreshold;
  float brightStrength;
  float darkStrength;
  float normalStrength;
  
  Eye(float x, float y, Wheel wheel) {
    this.x = x;
    this.y = y;
    this.wheel = wheel;
    brightThreshold = random(250);
    darkThreshold = random(brightThreshold);
    brightStrength = random(2);
    darkStrength = random(2);
    normalStrength = random(2);
  }
  
  void draw(float x, float y, float rotation) {
    fill(255);
    pushMatrix();
    translate(x,y);
    rotate(rotation);
    ellipse(this.x,this.y,5,5);
    popMatrix();
  }
  
  void see(float lumines) {
    if (lumines > brightThreshold) {
      wheel.setStrength(brightStrength);
      return;
    }
    if (lumines < darkThreshold) {
      wheel.setStrength(darkStrength);
      return;
    }
    wheel.setStrength(normalStrength);
  }
}

class Wheel {
  float x, y;
  float strength;
  
  Wheel(float x, float y) {
    this.x = x;
    this.y = y;
    strength = 1;
  }
  
  void draw(float x, float y, float rotation) {
    fill(255);
    pushMatrix();
    translate(x,y);
    rotate(rotation);
    rect(this.x,this.y,8,4);
    popMatrix();
  }
  
  void setStrength(float strength) {
    this.strength = strength;
  }
  
  PVector getForce(float rotation) {
    return new PVector(strength*cos(rotation),strength*sin(rotation));
  }
}