I made a projection of virtual butterflies which will come land on you (well, your projected silhouette) if you hold still, and will fly away if you move.
Inspiration
This semester, a friend of mine successfully lobbied for the creation of a “Mindfulness Room” to be created in one of the dorms on campus. The room is meant to be a place where students go to relax, meditate, and, as the name implies, be more mindful.
For my final project, I wanted to create something that was for a particular place, and so I chose the Mindfulness Room. Having tried to meditate in the past, I know it can be very challenging to clear your mind and sit entirely still for very long. So, the core of this project was to make something that would make you want to be still (and that would also fit in with the overall look and feel of the room.)
Technical Aspects
Some of the technical hurdles in this project:
Capturing a silhouette from a Kinect cam image. I tried to DIY this initially, which didn’t go well. Instead, I ended up finding this tutorial about integrating a Kinect and PBox2D. I fixed the tutorial code so that it would run in the most recent version of Processing and with the most recent version of the SimpleOpenNI library.
Integrating assorted libraries: SimpleOpenNI, blobDetection, PBox2D, ToxicLibs, standard Java libraries. I almost certainly didn’t actually need to use all of them, but figured that out too late.
Dealing with janky parts of those libraries (e.g., jitteriness in the blobDetection library, fussiness of SimpleOpenNI). Using the libraries made my project possible, but I also couldn’t fix some things about them. I did, however, manage to improve blob detection from the Kinect cam image by filtering out all non-blue pixels (the Kinect highlights a User in blue).
Trying to simulate butterflies flying—with physics. Trying to simulate a whimsical flight path using forces in PBox2D had only ok results. I think it would be easier to create their paths in vanilla Processing or with another library, (though that might make collision detection far more challenging.)
Finding a computationally cheap way to do motion tracking. When I tried simple motion tracking, my program ate all my computer’s memory and still didn’t run. I ended up taking the Kinect/SimpleOpenNI provided “Center of Mass” and using that to track motion, which worked pretty well for my purposes.
Critical Reflection
As I worked on this project, I was unsure throughout that all the pieces (butterflies, kinect, etc.) would come together and/or work well. I think they came together fairly well in the end. Even though the project right now doesn’t live up to what I imagined in my head at the beginning, it still does what I essentially wanted it to do—making you want to stay still.
When people saw the project, their general response was “that’s really cool”, which was rewarding. Also, the person in charge of the Mindfulness room liked it enough that she wanted me to figure out how to make it work there long term. (Which could be really logistically difficult, in terms of setup and security because the room is always open and unsupervised, and drilling into the walls to mount things isn’t allowed.)
So, though there’s a list of things I think should be better about this project (see below), I think I managed to my concept simplistically, and well given that simplicity.
Things that could be better about this:
Butterflies’ visual appeal. Ideally, the wings would be hinged-together PBox2D objects. And antennae/other details would add a lot.
Butterflies movement. Could be more butterfly-like.
Attraction to person should probably be more gradual/a few butterflies at a time.
Code cleanliness: not good.
Ragged edge of person’s silhouette should be smooth.
Better capture of user. Sometimes the Kinect refuses to recognize a person as a User, or stops tracking it. This could have to do with how I treat the cam image, or placement, or lighting, or just be part of how I was doing Kinect/SimpleOpenNI. After talking with Golan, I think ditching OpenNI altogether and doing thresholding on the depth image would work best.
When this assignment was first released, all I wanted to do was simulate a Monkeysphere. The program adds a “friend” every time you press any key. The most recent friend would take up the creature’s attention, thus the creature always keeps the most recent friend in its sight. Although this program was only supposed to be strictly simulation, things got a lot more personal once I started adding names since the names are of people I knew/know in real life. I started projecting, resulting in the part where you press the mouse and discover a sort of anxiety-ridden creature trying to escape the sphere but it can’t. At any rate, once you get to Dunbar’s number, the creature has hit its limit of “friends” and needs to take a break. Once you give it some time, it’ll return to befriend a new round of friends.
I think I’ll work on this a bit more to figure out smoother animations and of course, the freezing problem: the program randomly freezes during the second round of counting up to Dunbar’s number (it seems it’s 23?).
“Chara” is intended to be short for “character” or “characteristics”. “Chromism” is a chemistry term that stands for change in color.
This was intended to be a simulation of an over-simplified version of my observations of human group behavior.
The color of each Dot represents its core personality. In addition, each also has its own set of characteristics that influence how the Dot approaches with and is influenced by other Dots.
The simulation is not working as I intend it to yet. There are a few characteristics that I haven’t implemented yet. This is a work in progress. I would like to put the Processing program on here, but it does not run when I upload it onto OpenProcessing and I do not know why.
ArrayList allFlocks;
ArrayList allDots;
int nDots;
void setup() {
size(400, 400);
colorMode(HSB, 100);
noStroke();
nDots = 50;
allFlocks = new ArrayList();
allDots = new ArrayList();
for (int i=0; i1.0) {
ithDot.shade+=(shadeDiff*ithDot.agreeableness/100.0);
}
if (iFlock.stdev>(40.0*ithDot.openness)) {
iFlock.removeDot(ithDot);
if (iFlock.flockSize()==0) {
allFlocks.remove(iFlock);
}
Flock fnew = new Flock();
fnew.addDot(ithDot);
ithDot.flock = fnew;
}
else {
for (int j=0; j ithDot.radius && dh group; //prioritized members of group this Dot associates with
//The following are the Dot's characteristics
float agreeableness; //probability of going w/ flow of group
float extraversion; //probability of associating with other Dots
float openness; //probability of being interested in a different color
float spontaneity; //probability of randomly going off somewhere
Flock flock;
Dot (float x, float y, float s, float rad) {
position = new PVector(x, y);
float angle = random(TWO_PI);
velocity = new PVector(cos(angle), sin(angle));
acceleration = new PVector(0, 0);
shade = s;
radius = rad;
maxspeed = 1.0;
maxforce = 0.01;
group = new ArrayList();
flock = new Flock();
flock.addDot(this);
agreeableness = random(0, 1.0);
extraversion = random(0, 1.0);
openness = random(0, 1.0);
spontaneity = random(0, 1.0);
// println(str(shade)+" "+str(openness));
}
float checkCompatibility (float s) {
float lower = min(s, shade);
float higher = max(s, shade);
float shadeDist = min(higher-lower,(100-higher)+lower);
float distRatio = abs(shadeDist)/100;
return distRatio;
}
void updateFlock(Flock f) {
flock = f;
}
void run(ArrayList dots) {
flock(dots);
update();
handleBoundaries();
render();
}
void handleBoundaries() {
if (position.x > width+radius ) position.x -= width;
if (position.x < 0-radius ) position.x += width;
if (position.y > height+radius) position.y -= height;
if (position.y < 0-radius ) position.y += height;
}
void applyForce (PVector force) {
acceleration.add(force);
}
//From Flocking by Daniel Shiffman
//http://processing.org/examples/flocking.html
void flock(ArrayList dots) {
PVector sep = separate(allDots);
PVector ali = align(dots);
PVector coh = cohesion(dots);
sep.mult(1.5);
coh.mult(1.0);
ali.mult(1.0);
applyForce(sep);
applyForce(coh);
applyForce(ali);
}
// Method to update position
void update() {
// Update velocity
velocity.add(acceleration);
// Limit speed
velocity.limit(maxspeed);
position.add(velocity);
// Reset accelertion to 0 each cycle
acceleration.mult(0);
}
// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
PVector seek(PVector target) {
PVector desired = PVector.sub(target, position); // A vector pointing from the position to the target
// Scale to maximum speed
desired.normalize();
desired.mult(maxspeed);
// Steering = Desired minus Velocity
PVector steer = PVector.sub(desired, velocity);
steer.limit(maxforce); // Limit to maximum steering force
return steer;
}
// Separation
// Method checks for nearby dots and steers away
PVector separate (ArrayList dots) {
float desiredseparation = 15.0f;
PVector steer = new PVector(0, 0, 0);
int count = 0;
// For every dot in the system, check if it's too close
for (Dot other : dots) {
float d = PVector.dist(position, other.position);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) && (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
PVector diff = PVector.sub(position, other.position);
diff.normalize();
diff.div(d); // Weight by distance
steer.add(diff);
count++; // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer.div((float)count);
}
// As long as the vector is greater than 0
if (steer.mag() > 0) {
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// steer.setMag(maxspeed);
// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer.mult(maxspeed);
steer.sub(velocity);
steer.limit(maxforce);
}
return steer;
}
// Alignment
// For every nearby dot in the system, calculate the average velocity
PVector align (ArrayList dots) {
float neighbordist = 50;
PVector sum = new PVector(0, 0);
int count = 0;
for (Dot other : dots) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
sum.add(other.velocity);
count++;
}
}
if (count > 0) {
sum.div((float)count);
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// sum.setMag(maxspeed);
// Implement Reynolds: Steering = Desired - Velocity
sum.normalize();
sum.mult(maxspeed);
PVector steer = PVector.sub(sum, velocity);
steer.limit(maxforce);
return steer;
}
else {
return new PVector(0, 0);
}
}
// Cohesion
// For the average position (i.e. center) of all nearby dots, calculate steering vector towards that position
PVector cohesion (ArrayList dots) {
float neighbordist = 50;
PVector sum = new PVector(0, 0); // Start with empty vector to accumulate all positions
int count = 0;
for (Dot other : dots) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
sum.add(other.position); // Add position
count++;
}
}
if (count > 0) {
sum.div(count);
return seek(sum); // Steer towards the position
}
else {
return new PVector(0, 0);
}
}
void render() {
fill(shade,100,100);
ellipse(position.x, position.y, radius, radius);
}
}
class Flock {
ArrayList dots;
float avgShade;
float pcx;
float pcy;
float stdev;
Flock() {
dots = new ArrayList();
avgShade = 0;
pcx = 0;
pcy = 0;
stdev = 0;
allFlocks.add(this);
}
void updateInfo(){
float newx = 0;
float newy = 0;
int n = dots.size();
float tempsum = 0;
float totalShade = avgShade*n;
for (Dot d: dots) {
PVector pos = d.position;
newx+=pos.x;
newy+=pos.y;
tempsum+=pow((totalShade-d.shade),2);
}
pcx = newx/dots.size();
pcy = newy/dots.size();
stdev = sqrt(tempsum/n);
}
void addDot(Dot d) {
//Using previous numbers to calculate new avgs for runtime
int n0 = dots.size();
float totShade0 = avgShade*n0;
int n = n0+1;
float totShade = totShade0+d.shade;
//Update the avg
avgShade = totShade/n;
d.updateFlock(this);
dots.add(d);
}
void removeDot(Dot d) {
//Using previous numbers to calculate new avgs for runtime
int n0 = dots.size();
float totShade0 = avgShade*n0;
int n = n0-1;
float totShade = totShade0-d.shade;
//Update the avg
avgShade = totShade/n;
dots.remove(d);
}
int flockSize() {
return dots.size();
}
void run() {
for (Dot d: dots) {
d.run(dots);
}
}
}
In the rush of things, I seem to have forgotten a few things. 1) The ability for art to elegantly express that which is hard to express, 2) the ability for art in catharsis, and 3) reaching out to people when in need of help.
I originally had numerous separate ideas for the assignments, but after numerous iterations and talk of being ‘poetic’, I realized that this creature that I made–originally intended to be a “live stress ball”–could have so much more meaning when placed into context. Especially if that context is my own studio in the Design school. Lately, I have been feeling very nervous in general about all of my classes, ever so precariously keeping the balance between them, outside activities, and mental states. The stress of it can be overwhelming, and leave one feeling very trapped as I do now.
But in the process of making the video, and watching my own creature almost ‘come to terms’ with his confines, I can’t help but wonder whether or not such stress and pressure that I feel is merely an artificial boundary I place upon myself. It is up to me to figure out, given the limitations upon my skills and my time, what am I capable of doing, rather than focusing just on the limits and failures.
This is definitely the same sort of thinking with which I approached these assignments, keeping it as simple as possible, and working with the code that I’m most comfortable with. On the programming end, there are still many things I would like to fix later on as I get more and more fluent in Processing. One of those things includes figuring out how to get the creature attracted to the mouse, since at the moment a mouse press only activates a different gravity vector (which, I admit, I modified throughout the process of filming). I couldn’t quite figure out how to recall each node that creates the spring skeleton of the creature…
Unfortunately Javascript mode doesn’t seem to like my sketch and refuses to run in the OpenProcessing applet… Will have to figure out how to work that kink.
I began this project with a simple question in mind, how could I simulate a living creature? I thought about making something that would expand and deflate to simulate breathing. The simplest thing was to plug some eyes onto a shape, as Golan pointed out to me.By making it blink once in a while, the object becomes alive. My thoughts wandered and eventually landed on food. Creatures all need to eat right? So I started to formulate a critter that would devour another critter. I wanted to make something approachable, entertaining, and different from my previous projects.
After some quick sketches, I decided to create a baby chameleon-like creature, named Camel.
I wanted to give my creature an environment where he could live, but it would have to be contained enough so he wouldn’t escape. The first thought that came into mind was just to make a simple boxed out area on the canvas so he wouldn’t be able to step out of the border. Simple enough right? However, I felt like that didn’t depict the life of a chameleon. A chameleon is a slow careful creature that spends most of its life up in the tree tops, catching bugs. I thought that this type easy-going lifestyle needed a fluid shape that would represent something soft, without extreme changes.
Furthermore, I wanted to describe a cycle in life. When the fly would buzz around the chameleon, within a certain range of Camel’s vision, he would reach out for the fly with his tongue. But Camel is still a baby, so sometimes the fly would get away when it twitches violently enough. The viewer can also chose to help out Camel catch the fly. The fly naturally wants to run away from anything coming towards it, so if you push scare it in the right direction, Camel will be able to eat it.
Behavior is more important than visual realism in creating the illusion of life. We observe this in Karl Sims’ “Evolved Virtual Creatures”, a simulation in which evolved box creatures interact with their environment in surprising and often humorous ways.
With this principle in mind, I sought to invent a charming creature with lifelike mannerisms. I drew inspiration from two sources: the dutch artist Theo Jansen, and the Kikkerland line of Wind Up Toys.
A wind-powered Strandbeest (Dutch: strand = beach) roams a beach
A charming toy that teaches children about the perils of the adult world
I wrote a custom spring system based on Hooke’s Law to control his limbs, and the sketch runs using the P3D renderer. It is interactive in a limited sense – if one clicks, the Wind Walker turns to face the mouse.
Unfortunately, this doesn’t look nearly as good in tiny youtube format, as it’s actually 1000+ pixels wide. The Macs in the clusters are huge and I wanted to make a thing that takes up most, if not all of the space. The nippers are pretty simple in nature: you feed them by typing on your keyboard, and after they eat a certain amount they will explode and become two tiny nippers. If there’s no food left for them to eat, their will to leave dissipates and they slowly move to a stop. Poor little nippers!
Originally, I wanted to assign more significance to the act of typing–there’s a residual element of that left over as the most frequent letters found in the English language (e, t, and a) give the nippers more energy than the least common (z, q, x). This was easily cribbed from Wikipedia, lots of people have done research into this and little kids use it to break each other’s substitution ciphers. As it stands, it’s more fun to smash keys and watch the nippers run around.
Were I to have more time to work on this, I would’ve loved to add a way for the nippers to slowly die. They each have their own different speeds, and when you run the program you’ll sometimes notice one brutally beating the competition, picking up food left and right and leaving slow, hungry nippers in its dust. I’d like to have them slow to a stop and begin to shrink if they haven’t eaten in a certain amount of time, reminding us that the circle of life involves stone-cold competition.
int zoom = 400;
PImage lLeg;
PImage rLeg;
PImage body;
//---GLOBAL COLORS---
color green = color(163, 169, 72);
color yellow = color(237, 185, 46);
color orange = color(248, 89, 49);
color red = color(206, 24, 54);
color blue = color(0, 153, 137);
// safely add bugs
if (queue > 0) {
for (int i = 0; i < queue; i++) {
float x = bugCoords.get(i);
float y = bugCoords.get(i+1);
bugs.add(new Bug(x+5, y));
}
queue = 0;
bugCoords.clear();
}
}
//----------------------------------------------------
//-- CHECK THE KEYBOARD FOR DELICIOUS FOOD! --
//----------------------------------------------------
void keyReleased() {
//upper case
if (int(key) >= 65 && int(key) < = 90) {
float xpos = random(10, width-400-50);
float ypos = random(height*.75+15, height - 5);
int pri = checkUpper(key);
if (pri != 0) {
foods.add(new Food(xpos, ypos, 8, checkUpper(key)));
}
}
//lower case
else if (int(key) >= 97 && int(key) < = 122) {
float xpos = random(10, width-400-50);
float ypos = random(height*.75+15, height - 5);
int pri = checkLower(key);
if (pri != 0) {
foods.add(new Food(xpos, ypos, 8, checkLower(key)));
}
}
}
//----------------------------------------------------
//-- GRAB COLOR/PRIORITY INFO FROM KEYBOARD --
//----------------------------------------------------
int checkLower(char letter) {
int pri = 0;
if ("eta".indexOf(str(letter)) > -1) {
pri = 5;
}
else if ("oinshr".indexOf(str(letter)) > -1) {
pri = 4;
}
else if ("dlcumwfg".indexOf(str(letter)) > -1) {
pri = 3;
}
else if ("ypbvk".indexOf(str(letter)) > -1) {
pri = 2;
}
else if ("xqz".indexOf(str(letter)) > -1) {
pri = 1;
}
return pri;
}
int checkUpper(char letter) {
int pri = 0;
if ("ETA".indexOf(str(letter)) > -1) {
pri = 5;
}
else if ("OSINSHR".indexOf(str(letter)) > -1) {
pri = 4;
}
else if ("DLCUMWFG".indexOf(str(letter)) > -1) {
pri = 3;
}
else if ("YPBVK".indexOf(str(letter)) > -1) {
pri = 2;
}
else if ("XQZ".indexOf(str(letter)) > -1) {
pri = 1;
}
return pri;
}
My creature ended up not being an abstract or fantastical mystical one, but a relatively normal-looking robin that responds to the user’s voice. Lately, I’ve been doing a lot of work with the AudioInput library in processing because of the level of interactivity it offers between the user and the program. As someone who grew up in a musically oriented environment, I strongly believe that music is one of the best methods of bonding individuals. Thus, I created a robin that responds to singing but runs away when the input audio level exceeds a certain threshold. At the certain point, the robin will start to dance and even sing along once it has gained enough of the user’s trust.
I admit that I am still not completely satisfied with this project – I had spent so much time on trying to make the audio work and handling awkward boolean logic that I was not able to make the simulation as sophisticated as I wanted it to be. Nevertheless, this was a very valuable learning experience for me as it forced me to reason about my code more carefully and reacquainted me with good ol’ null pointer exceptions.
Dropbox link for the code because OpenProcessing hates me: https://www.dropbox.com/s/m1pbwk68zsvgkzz/singing_bird.zip
This piece is a simple evolution simulator. I was inspired by my complete bafflement at the Evolution vs Creationism “debate” in the United States. I could not understand how such a simple logic behind the concept of evolution was so difficult to understand for so many people, so I created a created a very basic emulation of it. The reference in this piece is the pepper moth case, where a population of species of moth near London clearly changed color depending on the pollution level. Similarly, the finches in the simulation has a higher probability of eating a moth that has greater contrast with the background. The speed of the simulation and the shade of the tree is also adjustable by the user, so there’s no bullshit going on here.