/* allow moving through time of an entire key frame (CONTROL SHIFT ARROW) allow animations to be blended and layered. the blending and layering should happen in P space. make examples of this. */ import render.*; import java.util.*; import java.awt.*; public class hand3 extends RenderApplet { HandModel handModel; TextIO textIO = new TextIO(); final static int _b = 0, _c = 1, _f = 2, _h = 3, _k = 4, _m = 5; final static int _p = 6, _r = 7, _s = 8, _t = 9, _x = 10, _y = 11; String pChars = "bcfhkmprstxy"; boolean isEnteringText = false; String pLabel[] = { "bend fingers", // b "curl fingers", // c "forefinger up", // f "hand fold", // h "curl thumb", // k "middle finger up", // m "pinkie up", // p "ring finger up", // r "spread fingers", // s "thumb bend", // t "wrist right", // x "wrist forward", // y }; String animationName[] = new String[10]; String animations[] = new String[10]; int NP = pChars.length(); boolean isNoise = true; double defaultRate[] = new double[NP]; double rate [] = new double[NP]; double trans [] = new double[NP]; double target0 [] = new double[NP]; double target1 [] = new double[NP]; int material = HandModel.BONE; int viewMode = 2; int xf0 = 135; int animation[][] = new int[100][NP]; boolean loopMode = false; int lastFrame = 0; String text = ""; boolean isControlKey(Event e) { return (e.modifiers & Event.CTRL_MASK) != 0; } public boolean keyUp(Event e, int key) { if (key >= 'A' && key <= 'Z' && !e.shiftDown()) key += 'a' - 'A'; if (isEnteringText) { if (key == 27) { isEnteringText = false; } else if (key == '\n') { isEnteringText = false; saveAnimation(text); } else if (key == 8) { if (text.length() > 0) text = text.substring(0, text.length() - 1); } else if (key >= 32 && key < 127) text += "" + (char)key; return true; } boolean controlKey = isControlKey(e); switch (key) { case ',': animation[F][P] = Math.max(1, animation[F][P] - 1); target0[P] = V(P); setParameter(P, animation[F][P]); return true; case '.': animation[F][P] = Math.min(9, animation[F][P] + 1); target0[P] = V(P); setParameter(P, animation[F][P]); return true; case 37: // ARROW LEFT if (controlKey && F > 0) { animation[F-1][P] = animation[F][P]; animation[F--][P] = 0; computeLastFrame(); } else F = Math.max(F - 1, 0); evalFrame(); return true; case 38: // ARROW UP P = (P + NP - 1) % NP; evalFrame(); return true; case 39: // ARROW RIGHT int maxFrame = (getWidth() - xf0) / 10 - 1; if (controlKey && F < maxFrame) { animation[F+1][P] = animation[F][P]; animation[F++][P] = 0; computeLastFrame(); } else F = Math.min(F + 1, maxFrame); evalFrame(); return true; case 40: // ARROW DOWN P = (P + 1) % NP; evalFrame(); return true; } interpretKey(key); return true; } void evalFrame() { for (int p = 0 ; p < NP ; p++) if (animation[F][p] != 0) { target0[p] = V(p); setParameter(p, animation[F][p]); } } int P = 0, F = 0; boolean definingAnimation = false; boolean fetchingAnimation = false; void interpretKey(int key) { switch (key) { case 'a': fetchingAnimation = true; return; case 'd': definingAnimation = true; return; case 'l': loopMode = ! loopMode; return; case 'v': viewMode = (viewMode + 1) % 3; return; case ' ': animationTime = 0; return; case 27: // ESCAPE KEY setToDefaults(); return; case 'q': String s = animationToString(); stringToAnimation(s); return; case 'g': switch (material) { case HandModel.GOLD: material = HandModel.BONE; break; case HandModel.BONE: material = HandModel.GOLD; break; } handModel.setMaterial(material); return; case '0': target0[P] = target1[P]; trans [P] = 0; if (viewMode == 2) setAnimation(F, P, 0); return; } int p = pChars.indexOf(key); if (p >= 0) { P = p; fetchingAnimation = false; definingAnimation = false; return; } if (key >= '1' && key <= '9') { if (fetchingAnimation) { if (animations[key - '0'] != null) { animationId = key - '0'; stringToAnimation(animations[animationId]); } fetchingAnimation = false; return; } if (definingAnimation) { animationId = key - '0'; isEnteringText = true; if (animationName[animationId] != null) text = animationName[animationId]; else text = ""; return; } target0[P] = target1[P]; setParameter(P, key - '0'); if (viewMode == 2) setAnimation(F, P, key - '0'); return; } } int animationId = 0; void saveAnimation(String name) { animations[animationId] = animationToString(); animationName[animationId] = name; definingAnimation = false; String s = ""; for (int i = 1 ; i <= 9 ; i++) if (animations[i] != null) { s += i + " " + animationName[i].replaceAll(" ", "#") + " " + animations[i] + "\n"; } textIO.save("animations.txt", s); } void setAnimation(int f, int p, int value) { animation[f][p] = value; computeLastFrame(); } void computeLastFrame() { lastFrame = 0; for (int f = 0 ; f < animation.length ; f++) for (int p = 0 ; p < NP ; p++) if (animation[f][p] != 0) lastFrame = f; } void setParameter(int i, int value) { target1[i] = (value - 5) / 4.0; trans [i] = 0; } String interpretString = ""; public void interpret(String s) { interpretString = "" + s; } int buttonCount; Color cursorColor = new Color(255, 0, 0, 64); Color stripeColor = new Color(255, 255, 255, 64); Color numberColor = new Color(255, 255, 255, 128); Color scrimColor = new Color(255, 255, 255, 96); Color textBgColor = new Color(0, 0, 0, 128); Color textOutlineColor = new Color(255, 255, 255, 128); Color textBoxBorderColor = new Color(180, 220, 220); public void drawOverlay(Graphics g) { int width = getWidth(); int height = getHeight(); if (viewMode >= 1) { for (int p = 0 ; p < NP ; p++) drawButton(g, p, pLabel[p]); } if (viewMode >= 2) { g.setColor(stripeColor); for (int i = 1 - (NP%2) ; i < NP ; i += 2) { int y = i * 14; g.fillRect(0, y, width, 14); } g.setColor(cursorColor); g.fillRect(xf0 + 10 * F, 14 * P, 10, 14); g.setColor(numberColor); for (int x = xf0 ; x < width ; x += 100) g.drawLine(x, 0, x, NP * 14 - 1); for (int f = 0 ; f < animation.length ; f++) for (int p = 0 ; p < NP ; p++) if (animation[f][p] != 0) { int x = xf0 + 2 + 10 * f; int y = 12 + 14 * p; g.drawString("" + (char)('0' + animation[f][p]), x, y); } int y = 14 * NP; for (int i = 1 ; i < 10 ; i++) if (animations[i] != null) { y += 14; String s = i + " " + animationName[i]; if (i == animationId) { g.setColor(textBgColor); g.fillRect(3, y - 11, stringWidth(g, s) + 4, 14); g.setColor(textOutlineColor); g.drawRect(3, y - 11, stringWidth(g, s) + 4, 14); } g.setColor(Color.white); g.drawString(s, 5, y); } } if (isEnteringText) { g.setColor(scrimColor); g.fillRect(0, 0, width, height); int x0 = width/2 - 100, x1 = width/2 + 100; int y0 = height/2 - 10, y1 = height/2 + 10; g.setColor(textBoxBorderColor); g.fill3DRect(x0 - 10, y0 - 10, x1 - x0 + 20, y1 - y0 + 20, true); g.setColor(Color.white); g.fillRect(x0, y0, x1 - x0, y1 - y0); g.setColor(Color.black); g.drawRect(x0, y0, x1 - x0, y1 - y0); g.drawString(text, x0 + 5, y0 + 15); } } Color negativeColor = new Color(128, 0, 128); Color zeroColor = new Color(128, 128, 128); Color positiveColor = new Color(128, 0, 128); void drawButton(Graphics g, int p, String label) { int y = 11 + 14 * p; g.setColor(Color.white); String s = pChars.substring(p, p+1); g.drawString(s, 12 - stringWidth(g, s)/2, y); g.drawString(label, 25, y); s = "" + (5 + (int)(4.1 * V(p))); g.drawString(s, xf0 - 12 - stringWidth(g, s)/2, y + 1); } public void initialize() { for (int i = 0 ; i < rate.length ; i++) { defaultRate[i] = 1.0; rate [i] = 1.0; trans [i] = 1.0; } /* defaultRate[_f] = 2.0; defaultRate[_m] = 2.0; defaultRate[_r] = 2.0; defaultRate[_p] = 2.0; defaultRate[_t] = 2.0; */ setToDefaults(); setBgColor(.25,0,.125); setFOV(0.2); setFL(50); addLight( 1, 1, 1, 1, 1, 1); addLight(-1,-1,-1, 1, 1, 1); getWorld().add(handModel = new HandModel()); Matrix matrix = handModel.getMatrix(); matrix.translate(0, -1.5, 0); String s = textIO.load("animations.txt"); if (s == null) s = getParameter("animations"); StringTokenizer st = new StringTokenizer(s); while (st.hasMoreTokens()) { String idString = st.nextToken(); String nameString = st.nextToken(); String valueString = st.nextToken(); int i = (new Integer(idString)).intValue(); animationName[i] = nameString.replaceAll("#", " "); animations[i] = valueString; } } void setToDefaults() { for (int p = 0 ; p < NP ; p++) { target0[p] = target1[p]; target1[p] = 0; trans[p] = 0; } target1[_b] = -0.5; target1[_c] = -0.5; target1[_f] = -1.0; target1[_h] = -1; target1[_k] = -0.5; target1[_m] = -1.0; target1[_p] = -1.0; target1[_r] = -1.0; target1[_s] = -0.5; target1[_t] = -1.0; for (int f = 0 ; f < animation.length ; f++) for (int p = 0 ; p < NP ; p++) animation[f][p] = 0; for (int p = 0 ; p < NP ; p++) animation[0][p] = 5 + (int)(4.1 * target1[p]); lastFrame = 0; P = F = 0; } double V(int p) { return lerp(sCurve(trans[p]), target0[p], target1[p]); } double b, c, g, s, t, w; double prevTime = 0; double interpretWait = 0; int getNextInterpretChar() { int c = interpretString.charAt(0); interpretString = interpretString.substring(1, interpretString.length()); return c; } double animationTime = 1000; public void animate(double time) { double elapsed = time - prevTime; prevTime = time; int frame = (int)(animationTime * 10); if (frame <= lastFrame + 10 && frame < animation.length) { for (int p = 0 ; p < NP ; p++) if (animation[frame][p] != 0) { target0[p] = V(p); setParameter(p, animation[frame][p]); rate[p] = defaultRate[p]; for (int ff = frame + 1 ; ff <= lastFrame ; ff++) if (animation[ff][p] != 0) { rate[p] = 10.0 / (ff - frame); break; } } animationTime += elapsed; if (loopMode && animationTime >= lastFrame / 10.0 + 1.0) animationTime = 0; } if (interpretWait == 0 && interpretString.length() > 0) { int c = getNextInterpretChar(); if (c == 'w') { int w = getNextInterpretChar(); interpretWait = (w - '0' + 1) / 10.0; } else interpretKey(c); } interpretWait = Math.max(0.0, interpretWait - elapsed); for (int p = 0 ; p < NP ; p++) trans[p] = Math.min(1.0, trans[p] + elapsed * rate[p]); double b = (V(_b) + .2 * (V(_c)+.5) + .5) * 0.6 * Math.PI / 2; double c = (V(_c) + 0.5) * 0.5 * Math.PI; double f = (V(_f) + 1) / 2; double k = (V(_k) + 0.5) * 0.5 * Math.PI; double m = (V(_m) + 1) / 2; double r = (V(_r) + 1) / 2; double p = (V(_p) + 1) / 2; double s = V(_s) * .3 + .1; double t = (V(_t) + 1) / 2 * Math.PI / 4; double h = (V(_h) + 1) / 2; double x = V(_x); double y = V(_y); if (isNoise) { b += .03 * Noise.noise(100 + time * 2); c += .03 * Noise.noise(200 + time * 2); h += .03 * Noise.noise(400 + time * 2); x += .03 * Noise.noise(500 + time); y += .03 * Noise.noise(300 + time); } double ff = 1-f, mf = 1-m, rf = 1-r, pf = 1-p, tf = 1-t; double wx = -x; double wy = y; double hf = h; double tw = s; double t0 = t; double t1 = k/2; double t2 = k/2; double fw = s/2; double f0 = b * ff; double f1 = c/2 * ff; double f2 = c/2 * ff; double mw = 0; double m0 = b * mf; double m1 = c/2 * mf; double m2 = c/2 * mf; double rw = -s/2; double r0 = b * rf; double r1 = c/2 * rf; double r2 = c/2 * rf; double pw = -s; double p0 = b * pf; double p1 = c/2 * pf; double p2 = c/2 * pf; handModel.animate(wx,wy,hf, tw,t0,t1,t2, fw,f0,f1,f2, mw,m0,m1,m2, rw,r0,r1,r2, pw,p0,p1,p2); } String animationToString() { String s = ""; int prevNonEmptyFrame = 0; for (int f = 0 ; f <= lastFrame ; f++) { boolean isNonEmptyFrame = false; for (int p = 0 ; p < NP ; p++) if (animation[f][p] != 0) isNonEmptyFrame = true; if (isNonEmptyFrame) { if (f > 0) { int diff = f - prevNonEmptyFrame; for ( ; diff > 10 ; diff -= 10) s += "w9"; s += "w" + (char)('0' + diff - 1); } prevNonEmptyFrame = f; for (int p = 0 ; p < NP ; p++) if (animation[f][p] != 0) s += pChars.substring(p,p+1) + (char)('0' + animation[f][p]); } } return s; } void stringToAnimation(String s) { setToDefaults(); int f = 0; while (s.length() > 0) { int c = s.charAt(0); int i = s.charAt(1) - '0'; s = s.substring(2, s.length()); if (c == 'w') f += i + 1; else { int p = pChars.indexOf(c); animation[f][p] = i; } } computeLastFrame(); } int stringWidth(Graphics g, String s) { return g.getFontMetrics(g.getFont()).stringWidth(s); } static double sCurve(double t) { return t * t * (3 - t - t); } static double lerp(double t, double a, double b) { return a + t * (b - a); } }