//
import activeTable.*; import render.*; import java.awt.*; import java.util.*; public class ActiveTableApplet extends ActiveTableDisplay { int N = 6; Doll doll[] = new Doll[N]; boolean enableDolls = false; Geometry objectAtCamera; Material rgb[] = new Material[3]; double FL = 1.8; boolean isRunningScript = false; Action actions[], action; double V(int i) { evalArgs(action); return action.a[i]; } void setV(int i, double value) { action.a[i] = value; } int I(int i) { return (int)(V(i)); } int actionI = 0; int nActors = 0, actorI = 0, actors[] = new int[16]; int nSync = 1; public void runScript(double time) { boolean isDone = true; for (int i = actionI ; actions[i] != null ; i++) { action = actions[i]; switch (action.type) { case Action.ACTOR: if (V(0) == Script.EVERYONE) { nActors = N; for (actorI = 0 ; actorI < N ; actorI++) actors[actorI] = actorI; } else { nActors = action.n; for (actorI = 0 ; actorI < nActors ; actorI++) actors[actorI] = (int)V(actorI); } break; case Action.CAMERA_TO: isDone &= cameraTo(time); break; case Action.FACE: for (actorI = 0 ; actorI < nActors ; actorI++) if (actors[actorI] != I(0)) isDone &= face(actors[actorI], I(0)); break; case Action.LEFT_ARM: for (actorI = 0 ; actorI < nActors ; actorI++) isDone &= leftArm(actors[actorI], V(0) * Math.PI / 180); break; case Action.LOOK_AT: for (actorI = 0 ; actorI < nActors ; actorI++) if (actors[actorI] != I(0)) isDone &= lookAt(actors[actorI], I(0)); break; case Action.MOVE_BY: for (actorI = 0 ; actorI < nActors ; actorI++) isDone &= moveBy(actors[actorI], V(0)/100, V(1)/100); break; case Action.MOVE_TO: for (actorI = 0 ; actorI < nActors ; actorI++) isDone &= moveTo(actors[actorI], V(0)/100, V(1)/100); break; case Action.PAUSE: isDone &= pause(time); break; case Action.RIGHT_ARM: for (actorI = 0 ; actorI < nActors ; actorI++) isDone &= rightArm(actors[actorI], V(0) * Math.PI / 180); break; case Action.SAY: if (nActors >= 0) { speaker = actors[0]; speech = action.s; thought = ""; } break; case Action.SYNC: if (isDone) { actionI = i+1; nSync++; } return; case Action.THINK: if (nActors >= 0) { thinker = actors[0]; thought = action.s; speech = ""; } break; } } } Geometry getTarget(int actor, int key) { Geometry target = null; switch (key) { case Script.CAMERA: return objectAtCamera; case Script.NOTHING: break; case Script.NEAREST: double dLo = 10000; for (int j = 0 ; j < N ; j++) if (j != actor) { double u=dollX(actor)-dollX(j), v=dollZ(actor)-dollZ(j), d=u*u+v*v; if (d < dLo) { dLo = d; target = doll[j]; } } break; default: if (key != actor) return doll[key]; } return target; } // DISPLAY SPEECH AND THOUGHT BUBBLES int speaker = 0, thinker = 0; String speech = "", thought = ""; double v[] = {0,0,0}; public void drawOverlay(Graphics g) { if (speech.length() > 0) { if (speaker == Script.NARRATOR) TextBubble.draw(g, TextBubble.NARRATION, W, W/2, H/2, speech, isTextMode); else { getSpeakerLocation(doll[speaker].head, v); int x = (int)v[0], y = (int)v[1]; TextBubble.draw(g, TextBubble.SPEECH , W, x, y, speech , isTextMode); } } if (thought.length() > 0) { getSpeakerLocation(doll[thinker].head, v); int x = (int)v[0], y = (int)v[1]; TextBubble.draw(g, TextBubble.THOUGHT, W, x, y, thought, isTextMode); } } void getSpeakerLocation(Geometry object, double v[]) { Matrix m = new Matrix(); m.copy(object.globalMatrix); m.postMultiply(renderer.getCamera()); m.translate(0,1.5,0); renderer.xf(m, m.get(0,3),m.get(1,3),m.get(2,3),1, v); renderer.projectPoint(v); } // INITIALIZATION (CALLED ONCE) public void initialize() { actions = Script.parse(getParameter("script")); // WE WILL SIMULATE 10 VEHICLES setMaxVehicles(N); // THIS IS USED ONLY WHEN SIMULATING connect(); N = getMaxVehicles(); // SO THAT THIS WILL WORK WITH THE REAL TABLE // SET VEHICLES INITIAL POSITION/DIRECTION for (int i = 0 ; i < N ; i++) { VehicleInfo vi = getVehicleInfo(i); vi.x = (i + .5 - .5 * N) / N; vi.y = .5 * vi.x; vi.theta = 0; setVehicleInfo(i, vi); } // CREATE EVERYTHING super.initialize(); setFOV(.4); //setFOV(.6); setFL(FL); renderer.headsUp(true); for (int j = 0 ; j < 3 ; j++) { double r = j==0?1:.1, g = j==1?1:.1, b = j==2?1:.1; rgb[j] = new Material(); rgb[j].setAmbient(.2*r,.2*g,.2*b); rgb[j].setDiffuse(.8*r,.8*g,.8*b); rgb[j].setSpecular(1,1,1,10); } // SET VEHICLES FIRST TARGET POSITION/DIRECTION for (int i = 0 ; i < N ; i++) { VehicleInfo vi = getVehicleInfo(i); vi.x = .9 * (i + .5 - .5 * N) / N; vi.y = .25 - Math.abs(vi.x); vi.theta = (i < N/2 ? -Math.PI/4 : -3*Math.PI/4); } objectAtCamera = new Geometry(); } void addDolls() { for (int i = 0 ; i < N ; i++) { world.add(doll[i] = new Doll()); int j = i % 3; doll[i].body.setMaterial(rgb[i%3]); doll[i].setSkin((1.*N-i)/(1.*N)); doll[i].setHeight(2+2.*(N-1-i)/N); doll[i].setScale(.05); doll[i].setGazeWeight(1); if (i % 2 == 1) doll[i].setGirl(); switch (i % 6){ case 0: doll[i].setSkin(Doll.gold); break; case 1: doll[i].setSkin(.6); break; case 2: doll[i].setSkin(Doll.silver); break; case 3: doll[i].setSkin(Doll.copper); break; case 4: doll[i].setSkin(1); break; case 5: doll[i].setSkin(Doll.violet); break; } } } double dTime = 1, oldTime = 0; public void animate(double time) { dTime = time - oldTime; oldTime = time; if (enableDolls && doll[0] == null) addDolls(); if (isRunningScript) runScript(time); double cx = renderer.getCamera().get(0,0); double cz = renderer.getCamera().get(2,0); push(); translate(FL * cz, 0, FL * cx); transform(objectAtCamera); pop(); for (int i = 0 ; i < N ; i++) { VehicleInfo vi = getVehicleInfo(i); if (doll[i] != null) { if (doll[i].facingObject != null) vi.theta = doll[i].getFacingAngle(doll[i].facingObject); setVehicleInfo(i, vi); doll[i].setAltitude(ActiveTableDisplay.Y_TOP); Matrix m = vehicle[i].matrix; doll[i].setPosition(m.get(0,3),m.get(2,3)); m = vehicle[i].child[0].matrix; doll[i].setDirection(Math.atan2(m.get(0,2),m.get(0,0))); doll[i].animate(time); } else setVehicleInfo(i, vi); } super.animate(time); } double dollX(int i) { return doll[i].matrix.get(0,3); } double dollZ(int i) { return doll[i].matrix.get(2,3); } boolean isTextMode = false; public boolean keyUp(Event e, int key) { if (isTextMode) { switch (key) { case '\'': isTextMode = false; break; case 8: if (speech.length() > 0) speech = speech.substring(0, speech.length()-1); break; default: speech += (char)key; break; } return true; } switch (key) { case 'd': enableDolls = true; return true; case 's': enableDolls = true; actionI = 0; isRunningScript = true; return true; case 'c': for (int j = 0 ; j < N ; j++) doll[j].setGaze(objectAtCamera); break; case '`': isTextMode = true; return true; } return super.keyUp(e, key); } double xyz[] = new double[3]; double tableX = 0, tableZ = 0; int selectI = -1, selectMode = 0; boolean dragged; public boolean mouseDown(Event e, int x, int y) { dragged = false; mx = x; my = y; Geometry g = getGeometry(x,y); selectMode = selectI = -1; if (g != null) { if (table.contains(g)) { getPoint(x,y,xyz); tableX = xyz[0]; tableZ = xyz[2]; } else for (int i = 0 ; i < N ; i++) if (vehicle[i].contains(g) || doll[i]!=null && doll[i].contains(g)) { selectI = i; selectMode = vehicle[i].contains(g) ? 0 : doll[i].head.contains(g) ? 2 : 1; return true; } } return super.mouseDown(e,x,y); } int mx, my; public boolean mouseDrag(Event e, int x, int y) { if (selectI >= 0) { if (! dragged) { dragged = true; if (my - y > Math.abs(mx - x)) { // PICK UP THE ACTOR } } return true; } return super.mouseDrag(e, x, y); } public boolean mouseUp(Event e, int x, int y) { if (selectMode == 0) { if (getPoint(x,y,xyz)) moveTo(selectI, xyz[0], xyz[2]); return true; } if (selectI > 0) { if (y >= H) { if (selectMode == 1) face(selectI, Script.CAMERA); else lookAt(selectI, Script.CAMERA); return true; } Geometry g = getGeometry(x,y); for (int j = 0 ; j < N ; j++) if (vehicle[j].contains(g) || doll[j].contains(g)) if (selectMode == 1) face(selectI, j); else lookAt(selectI, j); return true; } return super.mouseUp(e, x, y); } public boolean mouseMove(Event e, int x, int y) { isTextMode = false; return super.mouseMove(e, x, y); } boolean pause(double time) { if (nSync > V(2)) { setV(1, time); setV(2, nSync); } return time - V(1) >= V(0); } boolean cameraTo(double time) { double theta = -V(0) * Math.PI / 180; double phi = V(1) * Math.PI / 180; double timeOut = Math.max(.001, V(2)); if (nSync > V(6)) { Matrix camera = renderer.getCamera(); double theta0 = Math.atan2(camera.get(0,2),camera.get(0,0)); double phi0 = Math.asin(camera.get(2,1)); setV(3, theta0); setV(4, phi0); setV(5, time); setV(6, nSync); } double t = Math.min(1, (time - V(5)) / timeOut); renderer.setCamera(lerp(t, V(3), theta), lerp(t, V(4), phi)); return t>.99; } boolean lookAt(int i, int j) { return doll[i].setGaze(getTarget(i, j)); } boolean face(int i, int j) { doll[i].setFacing(getTarget(i, j)); double theta = doll[i].getFacingAngle(doll[i].facingObject); return Math.abs(getTheta(i,getVehicleInfo(i)) - theta) < .02; } boolean leftArm(int actor, double angle) { double L = doll[actor].getLeftArmAngle(); return doll[actor].setLeftArmAngle(lerp(2 * dTime, L, angle)); } boolean moveBy(int i, double x, double z) { VehicleInfo vi = getVehicleInfo(i); x += vi.x; z -= vi.y; Matrix m = vehicle[i].matrix; vi.x = Math.max(-.45, Math.min(.45, x)); vi.y = Math.max(-.45, Math.min(.45, -z)); vi.theta = Math.atan2(m.get(2,3)-z, x-m.get(0,3)); return isAtGoal(i); } boolean moveTo(int i, double x, double z) { VehicleInfo vi = getVehicleInfo(i); Matrix m = vehicle[i].matrix; vi.x = Math.max(-.45, Math.min(.45, x)); vi.y = Math.max(-.45, Math.min(.45, -z)); vi.theta = Math.atan2(m.get(2,3)-z, x-m.get(0,3)); return isAtGoal(i); } boolean rightArm(int actor, double angle) { double L = doll[actor].getRightArmAngle(); return doll[actor].setRightArmAngle(lerp(2 * dTime, L, angle)); } double lerp(double t,double a,double b) { return a + t * (b - a); } //------- RUN-TIME EVALUATION OF ACTION ARGS void evalArgs(Action action) { switch (action.argType) { case Action.HAS_NO_ARG: break; case Action.STRING_ARG: break; case Action.VECTOR_ARG: StringTokenizer st = new StringTokenizer(action.s, ","); action.n = 0; while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.charAt(0) == '{') action.n = eval(token, action.a, action.n); else action.a[action.n++] = Script.number(token); } break; } } static final double OPCODE = 123.456; static final double PLUS = OPCODE + 1; static final double MINUS = OPCODE + 2; static final double TIMES = OPCODE + 3; static final double OVER = OPCODE + 4; static final double ME = OPCODE + 5; static boolean isOp(double d) { return d >= OPCODE && d < OPCODE + 10; } double value[] = new double[100]; int eval(String s, double vec[], int n) { s = s.substring(1,s.length()-1); // REMOVE BRACES int k = 0; for (int i = 0 ; i < s.length() ; i++) switch (s.charAt(i)) { case 'm': value[k++] = actors[actorI]; i++; break; case '+': value[k++] = PLUS; break; case '*': value[k++] = TIMES; break; case '/': value[k++] = OVER; break; default: if (k>0 && !isOp(value[k-1]) && s.charAt(i) == '-') { value[k++] = MINUS; break; } String token = ""; int j = i; while (j < s.length() && isNumeric(s.charAt(j),j-i)) token += s.charAt(j++); value[k++] = (new Double(s.substring(i,j))).doubleValue(); i = j-1; break; } vec[n++] = eval(value, k); return n; } // RIGHT NOW ONLY PARSES SIMPLE EXPRESSIONS double eval(double value[], int k) { double result = value[0]; for (int j = 1 ; j < k ; j++) if (value[j] == PLUS) result += value[++j]; else if (value[j] == MINUS) result -= value[++j]; else if (value[j] == TIMES) result *= value[++j]; else if (value[j] == OVER) result /= value[++j]; return result; } boolean isNumeric(int i, int loc) { return loc==0 && i=='-' || i=='.' || i>='0' && i<='9'; } }