import render.*; import java.awt.*; import java.util.*; public class PollymorphActor extends NewGeometry { double eyeColorData[][] = { {.2,.0,.0, .9,.0,.0}, // RED {.3,.15,.05,.2,.15,.05},// DARK {.3,.2,.1, .3,.2,.1}, // HAZEL {.3,.1,.0, .1,.3,.0}, // GREEN {.0,.0,.4, .1,.2,.4}, // BLUE {.2,.1,.0, .5,.0,.5}, // VIOLET }; double colorData[][] = { {.3,.1,.1, .5,.2,.3}, // PINK {.2,.2,.0, .5,.0,.0}, // RED {.6,.2,.0, .5,.2,.0}, // ORANGE {.4,.0,.1, .5,.5,.0}, // YELLOW {.3,.1,.0, .0,.5,.0}, // GREEN {.0,.0,.4, .1,.2,.4}, // BLUE {.2,.1,.0, .5,.0,.5}, // VIOLET }; public PollymorphActor(RenderApplet applet) { super(applet); for (int i = 0 ; i < colors.length ; i++) { double c[] = colorData[i]; colors[i] = new Material(); colors[i].setAmbient(c[0],c[1],c[2]); colors[i].setDiffuse(c[3],c[4],c[5],.5); colors[i].setSpecular(.5,.5,.5, 5); } for (int i = 0 ; i < eyeColors.length ; i++) { double c[] = eyeColorData[i]; eyeColors[i] = new Material(); eyeColors[i].setAmbient(c[0],c[1],c[2]); eyeColors[i].setDiffuse(c[3],c[4],c[5],.5); eyeColors[i].setSpecular(.5,.5,.5, 5); } } public Pollymorph polly = new Pollymorph(); double TY = -.1, TZ = .5; double HW = .3, LW = .10; double IW = 1; String msg = "", choices = ""; double msgFader = 0; Material colors[] = new Material[colorData.length]; Material eyeColors[] = new Material[eyeColorData.length]; public String getMsg() { return msg + (msgFader == 1 && pTop == 0 ? " ..." : "") + (!recording ? "" : "\n{" + (rSeq==0 ? "" : (char)(rSeq+'@') + " " + seq[rSeq])); } public String getChoices() { return pTop == 0 ? choices : ""; } public double getMsgFader() { return msgFader; } void fadeMsg() { msgFader = .99; defaultState = true; } void msg_(String s) { msg(s, 1); } // DON'T FADE YET void msg(String s ) { msg(s, .99); } // START TO FADE AFTER THIS void msg(String s, double fade) { if (! loading && pTop == 0) { defaultState = fade < 1; if (msgFader < 1) msg = ""; msg += s + " "; msgFader = fade; } } double gazeTarget[][] = new double[20][3]; public void setGazeTarget(int i, double x, double y, double z) { gazeTarget[i][0] = x; gazeTarget[i][1] = y; gazeTarget[i][2] = z; } public void setGaze(double x, double y, double z, double w) { polly.setGaze(x, y, z, w); } //////////////////////////////////////////////////// ///////// ////////////////// ///////// INITIALIZE EVERYTHING ////////////////// ///////// ////////////////// //////////////////////////////////////////////////// double time = 0, x = 0, y = 0, z = 0; boolean showExoskeleton = false; boolean showActor = true; ////////////////////////////////////////////////// ///////// ////////////////// ///////// ANIMATE EVERY FRAME ////////////////// ///////// ////////////////// ////////////////////////////////////////////////// public boolean talking = false; public void animate(double time) { if (lookAtCamera != 0) { double g[] = gazeTarget[0]; setGaze(g[0],g[1],g[2],lookAtCamera); } polly.animate(time); push(); translate(polly.getX(), polly.getY(), polly.getZ()); rotateY(polly.getDirection()); transform(this); pop(); double elapsed = time - this.time; this.time = time; if (msgFader < 1) { choices = "no- <- >- smile frown grr oh mm wide pucker vacant\n" + "bold- timid- up down left right kneel extend \\tilt /tilt\n" + "zen change- action- interest- )turn (turn " + (recording ? "1.. 9 A.. Z }" : "{new- A.. Z"); msgFader = Math.max(0, msgFader - elapsed); if (msgFader == 0) { msg = recording ? (char)(rSeq + '@') + ": " : ""; msgFader = 1; } } //////////////////// ADVANCE TIME-DEPENDENT PARAMETERS double t = Math.min(.5, 10 * elapsed); leftSmile = misc.lerp(t, leftSmile , leftSmileTarget); leftGrimace = misc.lerp(t, leftGrimace , leftGrimaceTarget); open = misc.lerp(t, open , openTarget); pucker = misc.lerp(t, pucker , puckerTarget); rightSmile = misc.lerp(t, rightSmile , rightSmileTarget); rightGrimace = misc.lerp(t, rightGrimace , rightGrimaceTarget); direction = misc.lerp(t, direction , directionTarget); if (talking) { double T = ImprovedNoise.noise(0,0,7 * time); open = (T < 0 ? -1 : 1) * Math.pow(Math.abs(T), 0.2); T = ImprovedNoise.noise(100,0,2.5 * time); leftGrimace = rightGrimace = T; } //////////////////// KEEP ADVANCING ANY SEQUENCES if (resumeTime <= time) advanceSequence(); } ///////////////////////// TIME VARYING PARAMETERS ///////////////////////////// double leftGrimaceTarget = 0, leftGrimace = 0; double leftSmileTarget = 0, leftSmile = 0; double openTarget = -1, open = -1; double puckerTarget = 0, pucker = 0; double rightSmileTarget = 0, rightSmile = 0; double rightGrimaceTarget = 0, rightGrimace = 0; double directionTarget = 0, direction = 0; //////////////////// HANDLE COMMANDS AND SEQUENCES /////////////////////////// double bold = 0; boolean neutralize = false; boolean animation = false; boolean color = false; boolean eyecolor = false; boolean leftOnly = false; boolean rightOnly = false; boolean change = false; boolean irisSize = false; boolean legWidth = false; boolean interest = false; double lookAtCamera = 0; boolean defaultState = true; int rSeq = 0; boolean loading = true, recording = false; String seq[] = new String[27]; int pTop = 0, pS[] = new int[50], pI[] = new int[50]; double resumeTime = 0; public void loadCmds(String s) { loading = true; for (int i = 0 ; i < s.length() ; i++) cmd(s.charAt(i)); loading = false; neutralize = false; } String rseq() { return seq[rSeq]; } int len() { return rseq().length(); } int last() { return rseq().charAt(len()-1); } String bspace() { return rseq().substring(0,len()-1); } public boolean cmd(int key) { // RECORDING A SEQUENCE if (recording) { // NAME THE SEQUENCE 'A', B', ... or 'Z'. if (rSeq == 0 && seq[rSeq].length() == 0 && key >= 'A' && key <= 'Z') { msg("" + (char)key); rSeq = key - '@'; seq[rSeq] = ""; return true; } // BACKSPACE OVER LAST KEY COMBINATION IN SEQUENCE else if (key == '\b') { if (defaultState && len() > 0) { while (len() > 0 && last() != 127) seq[rSeq] = bspace(); seq[rSeq] = bspace(); } return true; } // CLOSE BRACE FINISHES RECORDING THE SEQUENCE. if (key == '}') { msg("end the new sequence"); recording = false; return true; } // APPEND A KEY TO THE SEQUENCE BEING RECORDED. else if (! (key == ' ' && rSeq == 0 && seq[0].length() == 0)) { if (defaultState) seq[rSeq] += (char)127; seq[rSeq] += (char)key; if (loading || key >= 'A' && key <= 'Z') // IF IT'S A SEQ NAME, DON'T PLAY IT NOW. return true; } else recording = false; } if (key == ' ') { msgFader = 0; bold = 0; change = false; neutralize = false; animation = false; leftOnly = false; eyecolor = false; legWidth = false; irisSize = false; interest = false; rightOnly = false; defaultState = true; return true; } // START TO PLAY A SEQUENCE if (key >= 'A' && key <= 'Z') { if (seq[key - '@'] != null) { if (pTop == 0) msg("play sequence " + (char)key); pS[pTop] = key - '@'; pI[pTop] = 0; pTop++; } return advanceSequence(); } // SELECT A POLLY ANIMATION if (animation == true) { animation = false; if (key >= 'a' && key <= 'z') { int i = actionMap[key - 'a']; polly.setAction(i); msg(actionName[i]); } return true; } if (change) { change = false; switch (key) { case 'c': color = true; msg_("color to"); choices = "pink red orange yellow green blue violet"; break; case 'e': eyecolor = true; msg_("eye color to"); choices = "red dark hazel green blue violet"; break; case 'i': irisSize = true; msg_("iris size to"); choices = "small medium large"; break; case 'w': legWidth = true; msg_("the width of my legs to"); choices = "thin medium stout"; break; default: fadeMsg(); break; } return true; } if (irisSize) { irisSize = false; switch (key) { case 's': IW = 0.6; msg("small") ; break; case 'm': IW = 1.0; msg("medium"); break; case 'l': IW = 1.3; msg("large") ; break; default: fadeMsg(); break; } return true; } if (legWidth) { legWidth = false; switch (key) { case 't': LW = 0.08; msg("thin") ; break; case 'm': LW = 0.10; msg("medium"); break; case 's': LW = 0.16; msg("stout") ; break; default: fadeMsg(); break; } return true; } if (interest) { interest = false; switch (key) { case 'c': lookAtCamera = 1; msg("the camera"); break; case 'a': lookAtCamera = -1; msg("avoiding the camera"); break; case 'n': lookAtCamera = 0; setGaze(0,0,0,0); msg("nothing"); break; default: fadeMsg(); break; } return true; } if (color) { color = false; if (! set_color(key)) fadeMsg(); return true; } if (eyecolor) { eyecolor = false; if (!set_eyecolor(key)) fadeMsg(); return true; } if (key >= '1' && key <= '9') { msg("wait 0." + (char)key + " seconds"); return true; } defaultState = true; switch (key) { // OPEN BRACE FLAGS THAT A NEW SEQUENCE IS ABOUT TO BE RECORDED. case '{': recording = true; seq[rSeq = 0] = ""; msg_("make a new sequence named"); choices = "A B C D E F G H I J K L M\n"; choices += "N O P Q R S T U V W X Y Z"; return true; case 'n': if (!neutralize) msg_("no"); choices = "smile frown grr oh mm wide pucker\n"; choices += "up down left right kneel extend \\tilt /tilt"; neutralize = true; return true; // NEUTRAL VALUE (PREFIX) case '<': leftOnly = true; msg_("left sided"); choices = "smile frown grimace"; return true; // LEFT SIDE ONLY (PREFIX) case '>': rightOnly = true; msg_("right sided"); choices = "smile frown grimace"; return true; // RIGHT SIDE ONLY (PREFIX) case 'i': interest = true; msg_("my interest is"); choices = "camera avoid-camera nothing"; return true; // INTERESTED IN WHAT? case 'b': bold = M( 1); msg_("boldly"); choices = "up down left right \\ /"; return true; // BOLDLY (PREFIX) case 't': bold = M(-1); msg_("timidly"); choices = "up down left right \\ /"; return true; // TIMIDLY (PREFIX) case 'a': animation = true; msg_("my action is"); choices = "idle trot strut jump prowl lumber\nmope no yes dance run hop"; return true; // ANIMATION SELECTION case 'c': change = true; msg_("change my"); choices = "color- eyecolor- iris-size- width-of-legs-"; return true;// VARY THINGS case '(': polly.changeTurning(-Math.PI/4); msg("turn right"); break; // TURN RIGHT case ')': polly.changeTurning( Math.PI/4); msg("turn left" ); break; // TURN LEFT case 'u': polly.setNod(M( 1),M(bold)); msg("look up"); break; // UPWARD GAZE case 'd': polly.setNod(M(-1),M(bold)); msg("look down"); break; // DOWNWARD GAZE case 'l': polly.setTurn(M(-1),M(bold)); msg("look left"); break; // LEFTWARD GAZE case 'r': polly.setTurn(M( 1),M(bold)); msg("look right"); break; // RIGHTWARD GAZE case 'e': polly.setLift(M( 1)); msg("extend"); break; case 'k': polly.setLift(M(-1)); msg("kneel"); break; case 'o': openTarget = M( 1); msg("open mouth"); break; // OPEN MOUTH case 'm': openTarget = M(-1); msg("mouth closed"); break; // MOUTH CLOSED case 'w': puckerTarget = M(-1); msg("wide mouth"); break; // WIDE MOUTH case 'p': puckerTarget = M( 1); msg("pucker"); break; // PUCKER case 'v': openTarget = -1; puckerTarget = 0; // NEUTRAL FACE EXPRESSION leftSmileTarget = rightSmileTarget = 0; leftGrimaceTarget = rightGrimaceTarget = 0; msg("vacant look"); break; case 'z': polly.setNod(0,0); polly.setTilt(0,0); polly.setTurn(0,0); polly.setLift(0); // CLEAR BODY OF ATTITUDE msg("zen"); break; case 's': if (!rightOnly) leftSmileTarget = M( 1); // SMILE if (!leftOnly ) rightSmileTarget = M( 1); msg("smile"); break; case 'f': if (!rightOnly) leftSmileTarget = M(-1); // FROWN if (!leftOnly ) rightSmileTarget = M(-1); msg("frown"); break; case '\\': polly.setTilt(M(1),M(bold)); msg("tilt right"); break; // TILT LEFT case '/' : polly.setTilt(M(-1),M(bold)); msg("tilt left"); break; // TILT RIGHT case 'g': if (!rightOnly) leftGrimaceTarget = M( 1); // GRIMACE if (!leftOnly ) rightGrimaceTarget = M( 1); msg("grimace"); break; case 'x': if (!showExoskeleton) showExoskeleton = true; else if (showActor) showActor = false; else { showExoskeleton = false; showActor = true; } break; // SHOW/HIDE POLLYMORPH case '-': polly.changeSpeed(-1); break; case '+': polly.changeSpeed( 1); break; case '!': polly.setSpeed(0); break; default: return false; } neutralize = false; animation = false; leftOnly = false; rightOnly = false; bold = 0; return advanceSequence(); } boolean set_color(int key) { switch (key) { case 'p': setColor(0); msg("pink") ; break; case 'r': setColor(1); msg("red") ; break; case 'o': setColor(2); msg("orange"); break; case 'y': setColor(3); msg("yellow"); break; case 'g': setColor(4); msg("green") ; break; case 'b': setColor(5); msg("blue") ; break; case 'v': setColor(6); msg("violet"); break; default : return false; } return true; } boolean set_eyecolor(int key) { switch (key) { case 'r': setEyecolor(0); msg("red") ; break; case 'd': setEyecolor(1); msg("dark") ; break; case 'h': setEyecolor(2); msg("hazel") ; break; case 'g': setEyecolor(3); msg("green") ; break; case 'b': setEyecolor(4); msg("blue") ; break; case 'v': setEyecolor(5); msg("violet"); break; default : return false; } return true; } void setColor(int i) { } void setEyecolor(int i) { } static int actionMap[] = {0,0,0,9,0,0,0,11,0,3,0,5,6,7,0,4,0,10,2,1,0,0,0,0,8,0}; static String actionName[] = {"idle","trot","swagger","jump","prowl","lumber", "mope","no","yes","dance","run","hop"}; double M(double i) { return neutralize ? 0 : i; } boolean advanceSequence() { // DON'T DO ANYTHING IF SEQUENCE IS NOT DEFINED if (loading || pTop == 0) return true; int p = pS[pTop-1]; if (seq[p] == null) return true; // RUN THE NEXT CMD IN THE SEQUENCE int i = pI[pTop-1]++; if (i < seq[p].length()) { int key = seq[p].charAt(i); // PAUSING SEQUENCE PLAYBACK FOR .1 TO .9 SECONDS if (key >= '1' && key <= '9') resumeTime = time + .1 * (key - '0'); return cmd(key); } // IF SEQUENCE IS DONE, POP BACK OUT TO CALLING SEQUENCE if (i >= seq[p].length()-1) { pTop--; if (pTop == 0) { msgFader = 0; } } return true; } public void drawMsg(Graphics g, int X, int Y) { String seq = null, msg = getMsg(); Color msgColor = inks[51-(int)(51 * getMsgFader())]; int i = msg.indexOf('\n'); if (i >= 0) { seq = msg.substring(i+1,msg.length()); msg = msg.substring(0,i); } g.setFont(msgFont); g.setColor(msgColor); g.drawString(msg, X, Y); if (seq != null) { g.setFont(sequenceFont); g.setColor(sequenceColor); int x = X + 2; for (int j = 0 ; j < seq.length() ; j++) { int c = seq.charAt(j); if (c == 127) x += 6; else { String s = "" + (char)c; g.drawString(s, x, 63); if (j >= 3) g.drawLine(x-1, 70, x+16, Y + 37); x += misc.stringWidth(s, g); } } } } public void drawChoices(Graphics g, int X, int Y) { g.setColor(Color.black); g.setFont(smallFont); String choices = getChoices(); int n = 0, i = 0; for (String s = choices ; (i=s.indexOf('\n')) != -1 ; s = s.substring(i+1,s.length())) n++; for (int k = 0 ; k <= n ; k++) { i = choices.indexOf('\n'); String str = i==-1 ? choices : choices.substring(0,i); StringTokenizer st = new StringTokenizer(str); int x = X, y = Y - 24 * (n - k); while (st.hasMoreTokens()) { String token = st.nextToken(); boolean prefix = token.charAt(token.length()-1) == '-'; g.setColor(prefix ? prefixColor : choicesColor); if (prefix) token = token.substring(0,token.length()-1); g.drawString(token, x, y); g.setColor(prefix ? prefixColor : Color.black); g.drawString(token.substring(0,1), x-1, y); g.drawString(token.substring(0,1), x , y); g.drawString(token.substring(0,1), x-1, y-1); g.drawString(token.substring(0,1), x , y-1); x += misc.stringWidth(token, g) + 8; } choices = choices.substring(i+1,choices.length()); } } Font msgFont = new Font("TimesRoman", Font.BOLD, 22); Font sequenceFont = new Font("Courier", Font.BOLD, 22); Font smallFont = new Font("TimesRoman", Font.PLAIN, 22); Color choicesColor = new Color(0,0,0,160); Color prefixColor = new Color(100,0,80); Color lastKeyColor = new Color(200,0,160); Color sequenceColor = new Color(255,255,0); static Color inks[] = new Color[52]; static { for (int i = 0 ; i < 52 ; i++) inks[i] = new Color((float)(.4*i/51),(float)(.4*i/51),(float)(.8*i/51)); } }