//
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';
}
}