After another highly successful skating CHAT session (YES! you can/should/better join us) Laura and I had a meeting with particle physicist turned coder Dave W about the development of a poetry visualization tool. Laura presented her exciting vision for the tool, and Dave and I discussed how we could help Laura build it.
Dave will handle the back-end database component, and I'll try to tackle the front-end graphical stuff. The visualization will be a dynamically generated 3D plot of user selected data fields. For example, a user may select a list of poems based on a certain time period, meter structure, theme, etc. The tool will plot the results as a series of relational nodes in 3D space, with the different axes and node types representing the relevant metrics. In addition, users will be able to specify style characteristics for nodes as well as save images of the visualizations. We'll soon be shaking the grant trees for funding (ideas/cash welcome) and developing a prototype.
Inspired by Laura's skating and generative prowess, I created a little code piece in the spirit of her vis tool (ok, it will also be an example in my book.) As usual, paste the code below in Processing and run the dang thing. If the animation runs too slowly, try lowering the number of cubies (int cubies = 150;).
// Paste the code below into Processing
Cube stage; // external large cube
int cubies = 150;
Cube[]c = new Cube[cubies]; // internal little cubes
color[][]quadBG = new color[cubies][6];
// controls cubie's movement
float[]x = new float[cubies];
float[]y = new float[cubies];
float[]z = new float[cubies];
float[]xSpeed = new float[cubies];
float[]ySpeed = new float[cubies];
float[]zSpeed = new float[cubies];
// controls cubie's rotation
float[]xRot = new float[cubies];
float[]yRot = new float[cubies];
float[]zRot = new float[cubies];
// size of external cube
float bounds = 300;
void setup(){
size(400, 400, P3D);
framerate(30);
for (int i=0; i<cubies; i++){
// each cube face has a random color component
float colorShift = random(-75, 75);
quadBG[i][0] = color(175+colorShift, 30, 30);
quadBG[i][1] = color(30, 175+colorShift, 30);
quadBG[i][2] = color(30, 30, 175+colorShift);
quadBG[i][3] = color(175+colorShift, 175+colorShift, 30);
quadBG[i][4] = color(175+colorShift, 30, 175+colorShift);
quadBG[i][5] = color(175+colorShift, 87+colorShift, 30);
// cubies are randomly sized
float cubieSize = random(5, 10);
c[i] = new Cube(cubieSize, cubieSize, cubieSize);
//initialize cubie's position, speed and rotation
x[i] = 0;
y[i] = 0;
z[i] = 0;
xSpeed[i] = random(-2, 2);
ySpeed[i] = random(-2, 2);
zSpeed[i] = random(-2, 2);
xRot[i] = random(40, 100);
yRot[i] = random(40, 100);
zRot[i] = random(40, 100);
}
// instantiate external large cube
stage = new Cube(300, 300, 300);
}
void draw(){
background(50);
// center in display window
translate(width/2, height/2, -130);
// outer transparent cube
noFill();
// rotate everything, including external large cube
rotateX(frameCount*PI/225);
rotateY(frameCount*PI/250);
rotateZ(frameCount*PI/275);
stroke(255);
// draw external large cube
stage.create();
//move/rotate cubies
for (int i=0; i<cubies; i++){
pushMatrix();
translate(x[i], y[i], z[i]);
rotateX(frameCount*PI/xRot[i]);
rotateY(frameCount*PI/yRot[i]);
rotateX(frameCount*PI/zRot[i]);
noStroke();
c[i].create(quadBG[i]);
x[i]+=xSpeed[i];
y[i]+=ySpeed[i];
z[i]+=zSpeed[i];
popMatrix();
// draw lines connecting cubies
stroke(35);
if (i< cubies-1){
line(x[i], y[i], z[i], x[i+1], y[i+1], z[i+1]);
}
// check wall collisions
if (x[i]>bounds/2 || x[i]<-bounds/2){
xSpeed[i]*=-1;
}
if (y[i]>bounds/2 || y[i]<-bounds/2){
ySpeed[i]*=-1;
}
if (z[i]>bounds/2 || z[i]<-bounds/2){
zSpeed[i]*=-1;
}
}
}
/*
Extremely simple class to
hold each 3D vertex
*/
class Point3D{
float x, y, z;
// constructors
Point3D(){
}
Point3D(float x, float y, float z){
this.x = x;
this.y = y;
this.z = z;
}
}
/* custom Cube class
slightly cooler than Processing's
box() function */
class Cube{
Point3D[] vertices = new Point3D[24];
float w, h, d;
//constructor
Cube(float w, float h, float d){
this.w = w;
this.h = h;
this.d = d;
// cube composed of 6 quads
//front
vertices[0] = new Point3D(-w/2,-h/2,d/2);
vertices[1] = new Point3D(w/2,-h/2,d/2);
vertices[2] = new Point3D(w/2,h/2,d/2);
vertices[3] = new Point3D(-w/2,h/2,d/2);
//left
vertices[4] = new Point3D(-w/2,-h/2,d/2);
vertices[5] = new Point3D(-w/2,-h/2,-d/2);
vertices[6] = new Point3D(-w/2,h/2,-d/2);
vertices[7] = new Point3D(-w/2,h/2,d/2);
//right
vertices[8] = new Point3D(w/2,-h/2,d/2);
vertices[9] = new Point3D(w/2,-h/2,-d/2);
vertices[10] = new Point3D(w/2,h/2,-d/2);
vertices[11] = new Point3D(w/2,h/2,d/2);
//back
vertices[12] = new Point3D(-w/2,-h/2,-d/2);
vertices[13] = new Point3D(w/2,-h/2,-d/2);
vertices[14] = new Point3D(w/2,h/2,-d/2);
vertices[15] = new Point3D(-w/2,h/2,-d/2);
//top
vertices[16] = new Point3D(-w/2,-h/2,d/2);
vertices[17] = new Point3D(-w/2,-h/2,-d/2);
vertices[18] = new Point3D(w/2,-h/2,-d/2);
vertices[19] = new Point3D(w/2,-h/2,d/2);
//bottom
vertices[20] = new Point3D(-w/2,h/2,d/2);
vertices[21] = new Point3D(-w/2,h/2,-d/2);
vertices[22] = new Point3D(w/2,h/2,-d/2);
vertices[23] = new Point3D(w/2,h/2,d/2);
}
void create(){
// draw cube
for (int i=0; i<6; i++){
beginShape(QUADS);
for (int j=0; j<4; j++){
vertex(vertices[j+4*i].x, vertices[j+4*i].y, vertices[j+4*i].z);
}
endShape();
}
}
void create(color[]quadBG){
// draw cube
for (int i=0; i<6; i++){
fill(quadBG[i]);
beginShape(QUADS);
for (int j=0; j<4; j++){
vertex(vertices[j+4*i].x, vertices[j+4*i].y, vertices[j+4*i].z);
}
endShape();
}
}
}