// VIDEO TEXTURE MAPPING ON SPRING MESH // 05/22/04 // joe versoza jjv222@nyu.edu static final int SCREEN_WIDTH = 320; static final int SCREEN_HEIGHT = 240; static final int SCREEN_Z = -500; // z limit (depth) static final int MESH_WIDTH = 32; // # of spring end point columns static final int MESH_HEIGHT = 24; // # of spring end point rows static final float WALL_ABSORB = 0.5; // amt of speed absorbed on wall bounce static final float LENGTH_REST = 10; // default length of spring at rest static final float MASS = 0.9000026; // default mass of end point static final float SPRING_CONSTANT = 0.0949996; // default spring constant static final float DRAG = 0.9; // drag on spring motion static final float MOUSE_PRESS_VELOCITY_FACTOR = 4; // scale velocity resulting from mouse static final int NORMAL_MODE = 0; // square setup static final int RIBBON_MODE = 1; // ribbons (translated) setup static final int ROTATE_MODE = 2; // diamonds (rotated) setup static final int MOUSE_REPEL_MODE = 0; // mouse repels proporional to distance static final int MOUSE_ATTRACT_MODE = 1; // mouse attracts prop to dist static final int MOUSE_ATTRACT_MODE_2 = 2; // mouse attracts prop to dist static final int MOUSE_DISPLACE_MODE = 3; // mouse displaces end points static final float MOUSE_DISPLACEMENT_THRESHHOLD = 20; // distance for mouse to displace static final float K_INCREMENT = 0.001; // increment k static final float M_INCREMENT = 0.1; // increment mass static final int DEFAULT_MODE = NORMAL_MODE; static final int CHANGE_THRESHHOLD = 10000; static final int FPS = 20; static final int TIME_TO_RESET = FPS * 4; static final int RESET_DURATION = FPS * 3; color backgroundColor, quadColor, strokeColor; SpringEndPoint[][] endPoints; int[][] oldColor; Point3D mostChange; Point3D curMostChange; Point3D trackPoint; int timeStill, resetCountDown; BImage a; float gM, gK, gR, drag; int meshHeight, meshWidth, mode, mouseMode; boolean bStroke, bMouseFollow; BFont font; void setup() { size (SCREEN_WIDTH, SCREEN_HEIGHT); framerate(FPS); timeStill = 0; resetCountDown = 0; beginVideo(SCREEN_WIDTH, SCREEN_HEIGHT, 20); oldColor = new int[SCREEN_WIDTH][SCREEN_HEIGHT]; for(int i = 0; i < SCREEN_WIDTH; i ++) { for(int j = 0; j < SCREEN_HEIGHT; j ++) { oldColor[i][j] = -1; } } mostChange = new Point3D(0, 0, 0); curMostChange = new Point3D(0, 0, 0); trackPoint = new Point3D(0, 0, 0); // load font font = loadFont("OCR-B.vlw.gz"); textFont(font, 14); // setup colors backgroundColor = color(255, 255, 255); quadColor = color(255, 255, 255); strokeColor = color(0, 60, 20); initEnvironment(MOUSE_ATTRACT_MODE, NORMAL_MODE, false, false); } void loop() { float fraction, f, distanceFromMouse; curMostChange.z = 0; background(backgroundColor); if(mostChange.z > CHANGE_THRESHHOLD) { trackPoint.x = mostChange.x * LENGTH_REST; trackPoint.y = mostChange.y * LENGTH_REST; timeStill = 0; } else if (timeStill < TIME_TO_RESET + 2){ timeStill ++; } else { timeStill = 0; mostChange.z = 0; resetCountDown = RESET_DURATION; } //push(); if(bStroke == false) { noStroke(); } else { stroke(strokeColor); } if(endPoints != null) { for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { if(i < (meshWidth - 1) && j < (meshHeight - 1)) { switch(mode){ case RIBBON_MODE: translate(WIDTH/2, HEIGHT/2, 0); break; case ROTATE_MODE: rotate(HALF_PI); break; default: break; } fill(quadColor); if(endPoints[i+1][j+1] == null) { break; } createTexture(i, j); drawQuad(endPoints[i][j].pos.x, endPoints[i][j].pos.y, endPoints[i][j].pos.z, endPoints[i+1][j].pos.x, endPoints[i+1][j].pos.y, endPoints[i+1][j].pos.z, endPoints[i+1][j+1].pos.x, endPoints[i+1][j+1].pos.y, endPoints[i+1][j+1].pos.z, endPoints[i][j+1].pos.x, endPoints[i][j+1].pos.y, endPoints[i][j+1].pos.z); } if(endPoints[i][j] != null) { Vector3D mousePressVelocity = new Vector3D(0, 0, 0); if(mousePressed || bMouseFollow) { distanceFromMouse = (float)endPoints[i][j].pos.distance(mouseX, mouseY, 0); switch(mouseMode) { case MOUSE_REPEL_MODE: mousePressVelocity = new Vector3D((endPoints[i][j].pos.x - trackPoint.x), (endPoints[i][j].pos.y - trackPoint.y), 0); break; case MOUSE_ATTRACT_MODE: mousePressVelocity = new Vector3D((mouseX - endPoints[i][j].pos.x), (mouseY - endPoints[i][j].pos.y), 0); break; case MOUSE_ATTRACT_MODE_2: mousePressVelocity = new Vector3D((mouseX - endPoints[i][j].pos.x), (mouseY - endPoints[i][j].pos.y), 0); break; case MOUSE_DISPLACE_MODE: if(distanceFromMouse <= MOUSE_DISPLACEMENT_THRESHHOLD) { mousePressVelocity = new Vector3D((endPoints[i][j].pos.x - mouseX), (endPoints[i][j].pos.y - mouseY), 0); } break; } mousePressVelocity.normalize(); mousePressVelocity.multiply((1 / distanceFromMouse) * MOUSE_PRESS_VELOCITY_FACTOR); endPoints[i][j].getVelocity().add(mousePressVelocity); } else if(mostChange.z > CHANGE_THRESHHOLD) { switch(mouseMode) { case MOUSE_REPEL_MODE: distanceFromMouse = (float)endPoints[i][j].pos.distance(trackPoint.x, trackPoint.y, 0); break; case MOUSE_ATTRACT_MODE: distanceFromMouse = (float)endPoints[i][j].pos.distance(trackPoint.x, trackPoint.y, 0); break; default: distanceFromMouse = 0; } mousePressVelocity = new Vector3D((trackPoint.x - endPoints[i][j].pos.x), (endPoints[i][j].pos.y - trackPoint.y), 0); mousePressVelocity.normalize(); mousePressVelocity.multiply((1 / distanceFromMouse) * MOUSE_PRESS_VELOCITY_FACTOR); endPoints[i][j].getVelocity().add(mousePressVelocity); } if(resetCountDown == 0) { if(timeStill > TIME_TO_RESET) { endPoints[i][j].reset(); } else { endPoints[i][j].resolveNeighborForces(); } //endPoints[i][j].resolveNeighborForces(); endPoints[i][j].move(); endPoints[i][j].boundPosition(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0, SCREEN_Z, WALL_ABSORB); endPoints[i][j].getVelocity().multiply(drag); } else { if(resetCountDown == 1) { fraction = 1.0; } else { f = (float)((float)(RESET_DURATION - resetCountDown)/(float)RESET_DURATION); fraction = (cos((f * PI) + PI) + 1.0)/2.0; } if(fraction > 1) { fraction = 1; } else if(fraction < 0) { fraction = 0; } endPoints[i][j].pos.x = (fraction * (endPoints[i][j].anchor.x - endPoints[i][j].pos.x) ) + endPoints[i][j].pos.x; endPoints[i][j].pos.y = (fraction * (endPoints[i][j].anchor.y - endPoints[i][j].pos.y) ) + endPoints[i][j].pos.y; } } } } if(resetCountDown > 0) { resetCountDown --; } } if(curMostChange.z > CHANGE_THRESHHOLD) { mostChange.x = curMostChange.x; mostChange.y = curMostChange.y; mostChange.z = curMostChange.z; //System.out.println(curMostChange.x + ", " + curMostChange.y + " " + curMostChange.z); } else { mostChange.x = 0; mostChange.y = 0; mostChange.z = 0; } //rect(trackPoint.x, trackPoint.y, 25, 25); // handle text fill(strokeColor); String strMouseMode = ""; switch(mouseMode) { case MOUSE_REPEL_MODE: strMouseMode = "REPEL"; break; case MOUSE_ATTRACT_MODE: strMouseMode = "ATTRACT"; break; case MOUSE_ATTRACT_MODE_2: strMouseMode = "ATTRACT 2"; break; case MOUSE_DISPLACE_MODE: strMouseMode = "DISPLACE"; break; } if(bMouseFollow) { strMouseMode = strMouseMode + " AUTOMATIC"; } else { strMouseMode = strMouseMode + " ON BUTTON PRESS"; } //text("SPRING CONSTANT: " + gK, SCREEN_HEIGHT/12, SCREEN_WIDTH - 80); //text("END POINT MASS: " + gM, SCREEN_HEIGHT/12, SCREEN_WIDTH - 60); //text("MOUSE MODE: " + strMouseMode, SCREEN_HEIGHT/12, SCREEN_WIDTH - 40); //image(a , 0, 0); } void createTexture(int col, int row) { int count = 0; int r = 0; int g = 0; int b = 0; int change = 0; a = new BImage((int)LENGTH_REST, (int)LENGTH_REST); for(int i = 0; i < LENGTH_REST; i++) { for(int j = 0; j < LENGTH_REST; j++) { //a.pixels[count] = video.pixels[(int)(j + i * LENGTH_REST * meshWidth + col * LENGTH_REST + LENGTH_REST * LENGTH_REST * row)]; a.pixels[count] = video.pixels[(int)((j + LENGTH_REST * meshWidth * i) + col * LENGTH_REST + (LENGTH_REST * meshWidth * LENGTH_REST * row))]; r += red(a.pixels[count]); g += green(a.pixels[count]); b += blue(a.pixels[count]); count++; } } if(oldColor[col][row] != -1) { change = abs((r + g + b) - oldColor[col][row]); if(change > curMostChange.z) { curMostChange.z = change; curMostChange.x = col; curMostChange.y = row; } } //System.out.println(col + " " + row + " - " + change); oldColor[col][row] = r + g + b; //a = video; } void videoEvent() { } void keyPressed() { if(key == UP) { gK += K_INCREMENT; for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { endPoints[i][j].setSpringConstant(gK); } } } else if(key == DOWN) { gK -= K_INCREMENT; for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { endPoints[i][j].setSpringConstant(gK); } } } else if(key == RIGHT) { gM += M_INCREMENT; for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { endPoints[i][j].setMass(gM); } } } else if(key == LEFT) { gM -= M_INCREMENT; for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { endPoints[i][j].setMass(gM); } } } } void keyReleased() { if(key == 'r' || key =='R') { initEnvironment(MOUSE_ATTRACT_MODE_2, NORMAL_MODE, false, false); } else if(key == 's' || key =='S') { bStroke = !bStroke; } else if(key == 'f' || key == 'F') { bMouseFollow = !bMouseFollow; } else if(key == 'c' || key == 'C') { constructMesh(); } else if(key == 'm' || key == 'M') { if(mouseMode == MOUSE_DISPLACE_MODE) { mouseMode = MOUSE_REPEL_MODE; } else { mouseMode++; } } else if(key == '1') { initEnvironment(mouseMode, NORMAL_MODE, bMouseFollow, bStroke); } else if(key == '2') { initEnvironment(mouseMode, RIBBON_MODE, bMouseFollow, bStroke); } else if(key == '3') { initEnvironment(mouseMode, ROTATE_MODE, bMouseFollow, bStroke); } } void initEnvironment(int _mouseMode, int _mode, boolean _bMouseFollow, boolean _bStroke){ mouseMode = _mouseMode; mode = _mode; bStroke = _bStroke; bMouseFollow = _bMouseFollow; gK = SPRING_CONSTANT; gR = LENGTH_REST; gM = MASS; drag = DRAG; meshHeight = MESH_HEIGHT; meshWidth = MESH_WIDTH; constructMesh(); } void constructMesh() { endPoints = new SpringEndPoint[meshWidth][meshHeight]; float c = gR / 2; //float c = 0; for(int i = 0; i < meshWidth; i++) { for(int j = 0; j < meshHeight; j++) { endPoints[i][j] = new SpringEndPoint(new Point3D((i * gR) + c, (j * gR) + c, 0), gK, gR, gM); if(i > 0) { endPoints[i][j].addEndPoint(endPoints[i-1][j], true); } if(j > 0) { endPoints[i][j].addEndPoint(endPoints[i][j-1], true); } } } } void drawQuad(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) { beginShape(POLYGON); texture(a); vertex(x1, y1, z1, 0, 0); vertex(x2, y2, z2, LENGTH_REST, 0); vertex(x3, y3, z3, LENGTH_REST, LENGTH_REST); vertex(x4, y4, z4, 0, LENGTH_REST); vertex(x1, y1, z1, 0, 0); endShape(); } public class SpringEndPoint { private float k; // spring constant private float r; // rest length private float m; // mass of end point private Vector3D v; // the sum of all the forces on this endpoint private ArrayList oppositeEndPoints; // list of opposite end points public Point3D pos; // location of end point public Point3D anchor; SpringEndPoint(Point3D p, float _k, float _r, float _m) { this.pos = p; this.anchor = new Point3D(this.pos.x, this.pos.y, this.pos.z); this.k = _k; this.r = _r; this.m = _m; this.v = new Vector3D(0, 0, 0); this.oppositeEndPoints = new ArrayList(); } public void addEndPoint(SpringEndPoint ep) { this.oppositeEndPoints.add(ep); } public void addEndPoint(SpringEndPoint ep, boolean bLinkBack) { this.oppositeEndPoints.add(ep); if(bLinkBack == true && ep.hasEndPoint(this) == false) { ep.addEndPoint(this, false); } } public void addForce(Vector3D _v) { this.v.add(_v); } public boolean hasEndPoint(SpringEndPoint ep) { if(this.oppositeEndPoints.indexOf(ep) > -1) { return true; } else { return false; } } public void setSpringConstant(float _k) { this.k = _k; } public void setRestLength(float _r) { this.r = r; } public void setMass(float _m) { this.m = _m; } public void move() { this.pos.addVector(this.v); //this.forceAccumulator.zero(); } public void resolveNeighborForces() { // get distance, resolve x and y, then unit vector for(int i = 0; i < oppositeEndPoints.size(); i ++) { SpringEndPoint springEndPointTemp = (SpringEndPoint)oppositeEndPoints.get(i); // get the distance between this end point and the opposite end point float distance = (float)this.pos.distance(springEndPointTemp.pos); // get all the components of the vector formed by both points float dX = this.pos.x - springEndPointTemp.pos.x; float dY = this.pos.y - springEndPointTemp.pos.y; float dZ = this.pos.z - springEndPointTemp.pos.z; // create and normalize vector Vector3D springForceVector = new Vector3D(dX, dY, dZ); springForceVector.normalize(); // scale the vector float magnitude = (-this.k * (distance - this.r)) / this.m; // combine vector and magnitude and add vector to accumulator springForceVector.multiply(magnitude); addForce(springForceVector); } } public Vector3D getVelocity() { return this.v; } public void boundPosition(float xMax, float yMax, float zMax, float xMin, float yMin, float zMin, float absorb) { boolean reverseX = false; boolean reverseY = false; boolean reverseZ = false; if(this.pos.x > xMax) { this.pos.x = xMax; reverseX = true; } if(this.pos.x < xMin) { this.pos.x = xMin; reverseX = true; } if(this.pos.y > yMax) { this.pos.y = yMax; reverseY = true; } if(this.pos.y < yMin) { this.pos.y = yMin; reverseY = true; } if(this.pos.z > zMax) { this.pos.z = zMax; reverseZ = true; } if(this.pos.z < zMin) { this.pos.z = zMin; reverseZ = true; } if(absorb > 0) { if(reverseX) { this.v.x = -this.v.x; } if(reverseY) { this.v.y = -this.v.y; } if(reverseZ) { this.v.z = -this.v.z; } if(reverseX || reverseY || reverseZ) { this.v.multiply(absorb); } } } void reset() { this.v.x = 0; this.v.y = 0; this.v.z = 0; } }