Kimpi


(The video was laggy because my computer was having trouble handling Processing, FaceOSC, and video capture at the same time. Welp.)

I bit off a bit more than I can chew with trying to do things in 3D. I thought it would be cool to make a 3D game controlled by head movements that makes use of 3-D information provided by FaceOSC. The idea is to have a critter run on a grid and fire beams from its mouth to destroy obstacles. Properties used: face rotation, mouth height. The design of Kimpi (the critter) is supposed to include more complicated patterns, but I haven’t quite figured out how to draw them on correctly yet (too much math and pixel-manipulating). After making the general movements work, I realized that my visions of this game doesn’t really fit using FaceOSC very well – FaceOSC loses track of face easily, especially when the face is turned too much, so it is not fit for a slightly fast-paced game I wanted.

Task queue if/when time/interest allows/persists. As I was typing this, I realized this is way too ambitious even if I duplicated myself for the sole completion of this task:
– make Kimpi bounce up/down instead of glide (shouldn’t be hard)
– draw designs on Kimpi
– make keyboard controls
– include board tilt – Kimpi slides rapidly to one side
– include obstacles that can be destroyed when beam lands on them
– include enemies that actively attack Kimpi (flocking?) that can be destroyed by beam

Code for Kimpi Demo, Kimpi outward of screen and mirrors head movements. Made for testing Kimpi object class:

import oscP5.*;
OscP5 oscP5;

int     found;
PVector poseOrientation = new PVector();
float mouthHeight;

Kimpi kimpi = new Kimpi(50);

void setup() {
  size(400, 400, OPENGL, P3D);
  background(200);
  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "mouthHeightReceived", "/gesture/mouth/height");

}
  
void draw() {
  background(200);
  translate(width/2, height/2, 0);
  spotLight(255, 255, 255, width/2, height/4, height*2, 0, 0, -1, PI/4, 2);

  if (found>0) {
    rotateY (poseOrientation.y); 
    rotateX (0-poseOrientation.x); 
    rotateZ (0-poseOrientation.z);
    println(mouthHeight);
  }


  kimpi.update(mouthHeight);
  kimpi.drawKimpi();

}


//----------------------------------
public void found (int i) { 
  found = i; 
}
public void poseOrientation(float x, float y, float z) {
  poseOrientation.set(x, y, z);
}
public void mouthHeightReceived(float h) {
  println("mouth height: " + h);
  mouthHeight = h;
}

class Kimpi{
  float radius;
  float headpieceW;
  float headpieceL;
  float mouthR = 0;
  float mouthRmax = 6;
  float eyeH;
  PFont f = createFont("Courier New Bold",16,true);
  
  Kimpi(float r){
    radius = r;
    headpieceW = radius/2;
    headpieceL = 5*headpieceW;
    eyeH = radius/12;
  }
  
  void update(float r){
    if (r<0) {
      mouthR = 0;
    }
    else if (r>mouthRmax) {
      mouthR = mouthRmax;
    }
    else {
      mouthR = r;
    }
  }

  void drawKimpi(){
    noStroke();
    fill(255,255,255);
    sphere(radius);
    
    float hx = 0;
    float hy = -radius;
    float hz = 0;
    
    pushMatrix();
    noFill();
    stroke(0,0,255,200);
    int nLines = 30;
    for (int i=0; i0) {
      rotateX(-PI/6);
      stroke(100);
      strokeWeight(3);
      noFill();
      beginShape();
      float theta = map((mouthR-1)/mouthRmax,0,mouthRmax,0,PI);
      for (int i=0; i< =20; i++){
        float phi = map(i,0,20,0,TWO_PI);
        float x = radius * sin(theta) * cos(phi); 
        float y = radius * sin(theta) * sin(phi); 
        float z = radius * cos(theta/2);
        vertex(x, y, z);
      }
      endShape();
      
      if ((mouthR-2>0)){
        float temp;
        if (mouthR-2<0) {
          temp = 0;
        } else {
          temp = mouthR-2;
        }
        stroke(200,220,255,200);
        float theta1 = map(temp/mouthRmax,0,mouthRmax,0,PI);        
        for (int i=0; i< =20; i++){
          float phi1 = map(i,0,20,0,TWO_PI);
          float x1 = radius * sin(theta1) * cos(phi1); 
          float y1 = radius * sin(theta1) * sin(phi1); 
          float z1 = radius * cos(theta1/2);
        
          line(x1,y1,z1,x1,y1-height/6,z1+height);
        }
      }
    }
    popMatrix();
  }
}

Code for Kimpi Beam, Kimpi faces into screen and travels on a grid:

import oscP5.*;
OscP5 oscP5;

int found;
PVector poseOrientation = new PVector();
float mouthHeight;

Kimpi kimpi = new Kimpi(20);

float unitSize = 30;
int nFrames0 = 50;
int nFrames = nFrames0;
float VX;
float VZ;
float maxV = 2;

Grid floor;

void setup() {
  size(800, 400, P3D);
  background(255,255,255);
  //  colorMode(HSB, 100);  
  floor = new Grid(width/2,height/5);
  
  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "mouthHeightReceived", "/gesture/mouth/height");
  
  VX = sin(0)*maxV;
  VZ = cos(0)*maxV;
}

void draw() {
  background(255,255,255);
  
  float percentComplete = (float)(frameCount%nFrames)/ (float)nFrames;
  float runPercent = percentComplete;

  pushMatrix();
  translate(width/2,height/2,0);
  rotateX(PI/2.2);
  if (found>0) {
    VX = -sin(poseOrientation.y)*maxV;
    VZ = cos(poseOrientation.y)*maxV;
  }
  else {
    VX = sin(0)*maxV;
    VZ = cos(0)*maxV;
  }
  floor.drawGrid(percentComplete);
  popMatrix();
  
  
  pushMatrix();
  noStroke();
  fill(255,255,255);
  translate(width/2, 8.3*height/16, height/2);
  spotLight(255, 255, 255, width/2, height/4, height*2, 0, 0, -1, PI/4, 2);
  scale(1,1,-1);
  rotateX(PI/6);
  if (found>0) rotateY(poseOrientation.y);
  kimpi.update(mouthHeight);
  kimpi.drawKimpi();
  popMatrix();

}


class Grid {
  float left;
  float top;
  float currTX = 0;
  float currTZ = VZ;
  int dimension = int(2*width/unitSize);

  Grid(float cx, float cy) {
    left = cx-3*width/2;
    top = cy-height/1.5;
  }

  void drawGrid(float percent) {
    currTX+=VX;
    currTZ+=VZ;
    if (currTX>unitSize) currTX-=unitSize;
    if (currTZ>unitSize) currTZ-=unitSize;
    pushMatrix();
    translate(currTX,currTZ,0);
    stroke(0,240,255);
    fill(0);
    for (int i=0; imouthRmax) {
      mouthR = mouthRmax;
    }
    else {
      mouthR = r;
    }
  }

  void drawKimpi(){
    noStroke();
    fill(255,255,255);
    sphere(radius);
    
    float hx = 0;
    float hy = -radius;
    float hz = 0;
    
    pushMatrix();
    noFill();
    stroke(0,0,255,200);
    int nLines = 30;
    for (int i=0; i0) {
      rotateX(-PI/6);
      stroke(100);
      strokeWeight(3);
      noFill();
      beginShape();
      float theta = map((mouthR-1)/mouthRmax,0,mouthRmax,0,PI);
      for (int i=0; i< =20; i++){
        float phi = map(i,0,20,0,TWO_PI);
        float x = radius * sin(theta) * cos(phi); 
        float y = radius * sin(theta) * sin(phi); 
        float z = radius * cos(theta/2);
        vertex(x, y, z);
      }
      endShape();
      
      if ((mouthR-2>0)){
        float temp;
        if (mouthR-2<0) {
          temp = 0;
        } else {
          temp = mouthR-2;
        }
        stroke(200,220,255,200);
        float theta1 = map(temp/mouthRmax,0,mouthRmax,0,PI);        
        for (int i=0; i< =20; i++){
          float phi1 = map(i,0,20,0,TWO_PI);
          float x1 = radius * sin(theta1) * cos(phi1); 
          float y1 = radius * sin(theta1) * sin(phi1); 
          float z1 = radius * cos(theta1/2);
        
          line(x1,y1,z1,x1,y1-height/6,z1+height);
        }
      }
    }
    popMatrix();
  }
}

Comments are closed.