Manta Ray Automaton: By Matt Kellogg & Miranda Jacoby

We used a combination of Arduino and a projected Processing sketch to make a seascape featuring a manta ray that moves on one axis. The manta ray is able to consume projected creatures by moving toward them, and avoid projected predators by moving away from them.

sketch1sketch2We were successfully able to create and control one-dimensional movement with our slider, in addition to sculpting a manta ray out of clay. We also created a platform out of foam-core board to serve as housing for our circuit and as our projection surface.
MantaRayatRest2We also gave our manta ray a sweet coat of glow-in-the-dark paint (although we later learned that the manta would not glow under projected light).newmantaarduinoWe managed to create various assets to give the manta ray an interesting environment. (The hammerhead shark was removed from the final project, as Processing was having trouble rendering it correctly.)

tigershark hammerheadshark fish shrimp planktonUnfortunately, due to the lack of documentation for our slider/potentiometer part, we managed to burn out our potentiometer. Because we weren’t able to computationally tell where the manta ray was due to the busted potentiometer, we had no way of detecting collisions, and could not implement behavior allowing the manta ray to move away from enemies (sharks) and towards food objects (everything that isn’t a shark).sliderThe good news is that we were eventually able to replace the part, allowing us to make the manta ray a true automaton: able to function without human interaction. Images, code, and video documentation have been updated accordingly.

Here’s our circuit:

ArduinoBoardMRAutomatonSmallmanta_bb

Here’s our Processing code:

//Miranda Jacoby and Matthew Kellogg
//EMS Interactivity Section A
//majacoby@andrew.cmu.edu, mkellogg@andrew.cmu.edu
//Copyright Miranda Jacoby and Matthew Kellogg 2014

//Matt and Miranda's MantaRay Project
import processing.serial.*;

static class Images {
  static PImage tigerShark;
  static PImage hammerHeadShark;
  static PImage shrimp;
  static PImage fish;
  static PImage plankton;
}

abstract class SeaThing {
  int x, y;
  int offX=0, offY=0;
  int l, r, w, h;
  float scale;
  PImage shape;

  void update() {
    x -= 7;
  }
  
  void draw() {
    pushMatrix();
    translate(x, y);
    scale(scale);
    translate(offX, offY);
    image(shape, 0, 0);
    popMatrix();
  }
};

class Shark extends SeaThing {
  Shark(int x, int y) {
    shape = Images.tigerShark;
    scale = 1.6;
    this.x = x;
    this.y = y;
    this.offX = -145;
    this.offY = -166;
  }
}

abstract class Edible extends SeaThing {
}

class Shrimp extends Edible {
  Shrimp(int x, int y) {
    shape = Images.shrimp;
    scale = 0.25;
    this.x = x;
    this.y = y;
    this.offX = -140;
    this.offY = -120;
  }
}

class Fish extends Edible {
  Fish(int x, int y) {
    shape = Images.fish;
    scale = 0.75;
    this.x = x;
    this.y = y;
    this.offX = -60;
    this.offY = -67;
  }
}

class Plankton extends Edible {
  Plankton(int x, int y) {
    shape = Images.plankton;
    scale = 0.15;
    this.x = x;
    this.y = y;
    this.offX = -230;
    this.offY = -230;
  }
}

class Manta {
  int x, y;
  SeaThing target;
  int value;
  Serial myPort;
  boolean bJustBuilt = false;
  int whichValueToAccum = 0;
  int nChars = 0;
  ArrayList serialChars;
  int RIGHT = 0;
  int LEFT = 1;
  int STOP = 2; 
  int dir;
  int minY, maxY;

  Manta(MantaProcessing thiz, int x, int y) {
    this.x = x;
    this.y = y;
    minY = 220;
    maxY = 550;
    dir = STOP;
    if (Serial.list().length > 0) {
      String portName = Serial.list()[0]; 
      myPort = new Serial(thiz, portName, 9600);
    }
    serialChars = new ArrayList();
  }

  void draw() {
    //if (target!=null) {
    //ellipse(x, y, 100, 100);
    //}
  }

  void moveRight() {
    if (myPort!=null) {
      myPort.write("R\n");
    } else if (y-5>minY){
      y-=5;
    }
    dir = RIGHT;
  }

  void moveLeft() {
    if (myPort!=null) {
      myPort.write("L\n");
    } else if (y+5<maxY){
      y+=5;
    }
    dir = LEFT;
  }

  void moveStop() {
    if (myPort!=null) {
      myPort.write("S\n");
    } else {
      //nothing
    }
    dir = STOP;
  }

  void update() {
    readSerial();
    if (shark!=null && shark.x<x+600){ // run from shark
      if (shark.y<540) {         moveLeft();       } else {         moveRight();       }     }else if (target == null) {       for (SeaThing thing : things) {         if (thing.x > x + 50 && thing.x < x + 300 && thing.y > minY && thing.y  y+20) {
      moveLeft();
    } else if (target.y < y-20) {       moveRight();     } else {       moveStop();     }     if (y>maxY && dir==LEFT) {
      moveStop();
    } else if (y y-20 && thing.y < y+20 && thing.x > x-20 && thing.x <x+20) {         things.remove(i);         i--;       }     }          if (!things.contains(target)){       target = null;     }   }   void readSerial() {     if (myPort==null) return;     while (myPort.available () > 0) {
      char aChar = (char) myPort.read();

      // You'll need to add a block like one of these 
      // if you want to add a 3rd sensor:
      if (aChar == 'A') {
        bJustBuilt = false;
        whichValueToAccum = 0;
      } else if (aChar == 'B') {
        bJustBuilt = false;
        whichValueToAccum = 1;
      } else if (((aChar == 13) || (aChar == 10)) && (!bJustBuilt)) {
        // If we just received a return or newline character, build the number: 
        int accum = 0; 
        int nChars = serialChars.size(); 
        for (int i=0; i < nChars; i++) {            int n = (nChars - i) - 1;            int aDigit = ((Character)(serialChars.get(i))).charValue();            accum += aDigit * (int)(pow(10, n));         }         // Set the global variable to the number we captured.         // You'll need to add another block like one of these          // if you want to add a 3rd sensor:         if (whichValueToAccum == 0) {           value = accum;         }         // Now clear the accumulator         serialChars.clear();         bJustBuilt = true;       } else if ((aChar >= 48) && (aChar <= 57)) {
        // If the char is between '0' and '9', save it.
        char aDigit = (char)(aChar - '0'); 
        serialChars.add(aDigit);
      }
    }
    y = (int)(((maxY-minY)*((1023-value)/1023.0))+minY);
  }
}

ArrayList things = new ArrayList();

PImage sharkImage;
PImage causticsImage;

Manta manta;
Shark shark;
PGraphics causticsGraphics;

void buildCausticsGraphics() {
  PGraphics g = causticsGraphics;
  g.beginDraw();
  g.blendMode(ADD);
  g.tint(100, 100, 255, 100);
  g.image(causticsImage, 0, 0);
  g.endDraw();
  g.loadPixels();
  for (int y=0; y<g.height; y++) {
    for (int x=0; x<g.width; x++) {
      color c = g.pixels[x+y*g.width];
      c = color(red(c), green(c), blue(c), blue(c));
      g.pixels[x+y*g.width] = c;
    }
  }
}

void setup() {
  causticsImage = loadImage("caustics.png");
  causticsGraphics = createGraphics(causticsImage.width, causticsImage.height);
  buildCausticsGraphics();
  size(1920, 1080);
  frameRate(60);
  Images.tigerShark = loadImage("tigershark.png");
  Images.hammerHeadShark = loadImage("hammerheadshark.png");
  Images.shrimp = loadImage("shrimp.png");
  Images.fish = loadImage("fish.png");
  Images.plankton = loadImage("plankton.png");
  shark = new Shark(200, 200);
  things.add(shark);
  manta = new Manta(this, width/4, height/2);
}

void draw() {
  if ((frameCount % 20)==0) {
    float r = random(0, 80);
    if (r<2) {
      if (shark==null) {
        shark = new Shark(width*2, 200);
        things.add(shark);
      }
    } else if (r<4) {
      if (shark==null) {
        shark = new Shark(width*2, height - 200);
        things.add(shark);
      }
    } else if (r<24) {
      things.add(new Fish(width*2, (int)random(250, height-250)));
    } else if (r<44) {
      things.add(new Plankton(width*2, (int)random(250, height-250)));
    } else if (r<64) {
      things.add(new Shrimp(width*2, (int)random(250, height-250)));
    }
  }

  for (SeaThing thing : things) {
    thing.update();
  }

  //remove things that are far off screen
  for (int i = 0; i < things.size (); i++) {
    if (things.get(i).x<-width) {
      if (things.get(i) instanceof Shark){
        shark = null;
      }
      things.remove(i);
      i--;
    }
  }

  manta.update();

  background(10, 30, 75);
  image(causticsGraphics, -(frameCount%causticsGraphics.width), 0);
  image(causticsGraphics, -(frameCount%causticsGraphics.width)+causticsGraphics.width, 0);
  image(causticsGraphics, -(frameCount%causticsGraphics.width)+2*causticsGraphics.width, 0);
  for (SeaThing thing : things) {
    thing.draw();
  }

  manta.draw();
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width), -causticsGraphics.height/2);
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width)+causticsGraphics.width, -causticsGraphics.height/2);
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width)+2*causticsGraphics.width, -causticsGraphics.height/2);
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width), causticsGraphics.height/2);
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width)+causticsGraphics.width, causticsGraphics.height/2);
  image(causticsGraphics, -((frameCount*2)%causticsGraphics.width)+2*causticsGraphics.width, causticsGraphics.height/2);
}

Here’s our Arduino code:

// Arduino control for the Manta Ray
// Author: Matthew Kellogg
// Copyright 2014 Matthew Kellogg

static const int dirPin = 2;
static const int goPin = 4;

boolean go = false;
boolean dir = false;

void setup(){
  pinMode(dirPin,OUTPUT);
  pinMode(goPin,OUTPUT);
  Serial.begin(9600);
}

void loop(){
  //Print the value from the potentiometer
  // to serial
  Serial.println("A"+(String)(analogRead(0)));
  
  //Process available controls
  while (Serial.available()){
    char a = Serial.read();
    switch (a){
    case 'L':
      dir = false;
      go = true;
      break;
    case 'R':
      dir = true;
      go = true;
      break;
    case 'S':
      go = false;
      break;
    default:
      break;
    };
  }
  // Set digital outputs based on controls
  digitalWrite(goPin,go);
  digitalWrite(dirPin,dir);
  // Delay to allow some control to control to happen
  delay(50);
}

 

Comments are closed.