Exhibiting my work in the IM Showcase, as well as interacting with everybody’s projects was such an enjoyable experience for me. I did face some issues with the quality of the projector and the lighting after I noticed that the webcam wasn’t picking up a lot of movements because of the dim lighting. But I managed to solve at least on of those issues through setting up my phone’s flashlight next to the webcam, which increased the response of the graphics to the user’s movements. It was very rewarding to see people enjoy interacting with the project, and provide feedback about the experience. I also loved hearing Nick say that he feels like an “airbender” when interacting with the graphics, and my friends running away from the screen screaming “IT’S CHASING ME”.
At first, I had intended to lower the brightness on the laptop display, so that the user can try to figure out how to interact with the project. But unfortunately the colors displayed by the projector were a bit faded, so the graphics weren’t showing in the opacity that I originally intended to display. You can also notice the flashlight placed on the pedestal as well, which drastically improved the speed of the interaction, since it allowed the Webcam to detect figures more easily. I enjoyed observing how people tried out a range of movements with the bodies and limbs, in order to try to see the extent of the effect their movements have on the motion graphics. I’ve had people run, jump up and down and flail their arms around to see if the projection will follow their movements, so that was fun to watch.
Graphics Code:
import java.util.Calendar; import ch.bildspur.postfx.builder.*; import ch.bildspur.postfx.pass.*; import ch.bildspur.postfx.*; import oscP5.*; import netP5.*; PostFX fx; //PVector center; Particles particles; float centerX, centerY; float oscX, oscY; OscP5 oscP5; NetAddress myRemoteLocation; void setup(){ //size(displayWidth, displayHeight, P3D); fullScreen(P3D); smooth(8); background(0); // init form centerX = width/2; centerY = height/2; //center = new PVector(width/2, height/2); fx = new PostFX(this); particles = new Particles(centerX, centerY); background(0); oscP5 = new OscP5(this,12000); myRemoteLocation = new NetAddress("127.0.0.1",1234); } void draw(){ // floating towards mouse position particles.run(oscX*50, oscY*50); float ratio = 0.5; float intensity = 10; //diffuse fx.render() .blur( round(map(ratio, 0, 1, 0, intensity)), map(ratio, 1, 0, 0, intensity)) .compose(); //decay noStroke(); fill(0, 15); rect(0, 0, width, height); } // timestamp String timestamp() { Calendar now = Calendar.getInstance(); return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now); } /* incoming osc message are forwarded to the oscEvent method. */ void oscEvent(OscMessage theOscMessage) { /* print the address pattern and the typetag of the received OscMessage */ print("### received an osc message."); print(" addrpattern: "+theOscMessage.addrPattern()); print(" message 1: "+theOscMessage.get(0).floatValue()); print(" message 2: "+theOscMessage.get(1).floatValue()); println(" typetag: "+theOscMessage.typetag()); oscX = theOscMessage.get(0).floatValue(); oscY = theOscMessage.get(1).floatValue(); }
Particle class:
class Particle { PVector pos, startPos; float heading = random(TWO_PI); float startHeading = heading; float step = random(1.0, 5.5); float stepNext = step; float lifespan; color col; Particle() { this.pos = new PVector(random(width), random(height)); this.build(); lifespan = 255.0; } Particle(PVector pos, float heading) { this.pos = pos.copy(); this.startPos = pos.copy(); this.heading = heading; this.startHeading = heading; if (pos.x > width/2) { //change color per segment if (pos.y > height/2) { col = color(178,153,255); } else { col = color(156, 0, 128); } } else { if (pos.y > height/2) { col = color(183, 0, 183); } else { col = color(255, 132, 178); } } } Particle(PVector pos, float heading, color col) { this.pos = pos.copy(); this.startPos = pos.copy(); this.heading = heading; this.startHeading = heading; this.col = col; } void build() { } void update(float newCenterX, float newCenterY) { pos.add(newCenterX, newCenterY); pos.add(new PVector(cos(heading), sin(heading)).mult(stepNext)); if (this.outOfBound()) { pos = startPos.copy(); heading = startHeading; } lifespan -= 1.0; } void render() { //deposit stroke(col); vertex(pos.x, pos.y); } boolean outOfBound() { return ( pos.x < 0 || pos.x > width || pos.y < 0 || pos.y > height ); } boolean isDead(){ if (lifespan < 0.0){ return true; } else { return false; } } }
Particles (mother) class:
class Particles { ArrayList<Particle> particles; Particles(float _xCenter, float _yCenter) { this.build(_xCenter, _yCenter); } void build(float centerX, float centerY) { particles = new ArrayList<Particle>(); int steps = 30000; float step = TWO_PI/steps; float rad = 300; float amp = PI/8; for (float i = 0; i <+ steps; i++) { //add new particle at random position float a = step * i; particles.add(new Particle( new PVector(cos(a), sin(a)).mult(rad).add(centerX, centerY).add(PVector.random2D().mult(20)), a + PI/2 + PI/8 + random(amp, amp))); //random pos amp } } void update(float changeX, float changeY) { for (Particle p : particles) { p.update(changeX, changeY); //int c = get(2, 3); //int r = c >> 16 & 0xFF; //int g = c >> 8 & 0xFF; //int b = c & 0xFF; } } void render() { //render each particle beginShape(POINTS); stroke(255); strokeWeight(1.0); for (Particle p : particles) { p.render(); } endShape(); } void run(float newX, float newY) { this.update(newX, newY); this.render(); } }
Webcam code Aaron helped me with:
import gab.opencv.*; import processing.video.*; import oscP5.*; import netP5.*; OscP5 oscP5; NetAddress dest; OpenCV opencv; Capture video; PVector smoothedFlow; void setup() { size(320, 240); video = new Capture(this, 320, 240); opencv = new OpenCV(this, 320, 240); video.start(); smoothedFlow = new PVector(0, 0); /* start oscP5, listening for incoming messages at port 12000 */ oscP5 = new OscP5(this, 1234); dest = new NetAddress("127.0.0.1", 12000); } void draw() { background(0); if (video.available() == true) { video.read(); } opencv.loadImage(video); opencv.flip(opencv.HORIZONTAL); opencv.calculateOpticalFlow(); pushMatrix(); scale(-1.0, 1.0); image(video, -video.width, 0); popMatrix(); stroke(255, 0, 0); PVector aveFlow = opencv.getAverageFlow(); PVector diff = PVector.sub(aveFlow, smoothedFlow); smoothedFlow.add( diff.mult(.1)); stroke(255); strokeWeight(2); translate(width/2, height/2); line(0, 0, 50*smoothedFlow.x, 50*smoothedFlow.y); sendOsc(); } void sendOsc() { OscMessage msg = new OscMessage("/opticalFlow"); msg.add((float)smoothedFlow.x); msg.add((float)smoothedFlow.y); oscP5.send(msg, dest); }
It was a great semester!