//****************************************************************************** // gcPoser.java - Copyright 1997 - Robert M. Free - grafman@graphcomp.com // http://www.graphcomp.com/grafman http://www.graphcomp.com/vrml // // A Prototype Avatar Controller & EAI example // // This was originally created to test some simple Avatar controls, // and as such, is a preliminary prototype, not intended for general use. // // This is not intended to be an example of clean, efficient coding, // but is possibly useful as an example of how to use the // External Authoring Interface (EAI). // // It demonstrates how to acquire an interface with a VRML world, and // makes use of EAI's EventIn and EventOut controls. // This has been tested on NT using Navigator 3.01 and Cosmo 1.0 beta 3a // // You are welcome to use any or all of this code, provided that you // agree that Graphcomp and Robert M. Free are free from all // associated liabilities, and that you include the following notice // with the source code and in associated documentation: // // Some or all of the EAI code in this application is adapted from // gcPoser(tm) code by Graphcomp, and used by permission of // Robert M. Free - Copyright 1997 - grafman@graphcomp.com // gcPoser is a trademark of Robert M. Free // //****************************************************************************** import vrml.external.Browser; import vrml.external.Node; import vrml.external.field.*; import vrml.external.exception.*; import netscape.javascript.JSObject; import netscape.plugin.Plugin; import java.applet.*; import java.awt.*; import java.io.*; import java.net.*; /* ************************* */ /* Main Applet */ /* ************************* */ public class gcPoser extends Applet implements EventOutObserver { /* Static values */ public final int moveHOME = 0; public final int moveNORTH = 1; public final int moveSOUTH = 2; public final int moveEAST = 3; public final int moveWEST = 4; /* Note: need to make this extensible */ /* Should load parts from the model */ public final int partHEAD = 0; public final int partHAIR = 1; public final int partTORSO = 2; public final int partLARM = 3; public final int partLFORE = 4; public final int partLHAND = 5; public final int partRARM = 6; public final int partRFORE = 7; public final int partRHAND = 8; public final int partHIPS = 9; public final int partLLEG = 10; public final int partRLEG = 11; /* Parameter Names */ private final String PARAM_CTRL_SIZE = "CTRL_SIZE"; private final String PARAM_CTRL_INC = "CTRL_INC"; private final String PARAM_LIST_LEN = "LIST_LEN"; /* gcPoser's stack */ public gcPoserLoad loader = null; public JSObject m_Win = null; public JSObject m_Frm = null; public JSObject m_Par = null; public JSObject m_Fst = null; public JSObject m_Doc = null; public JSObject m_Plg = null; public Browser m_Mdl = null; public float m_Inc = (float).05; public int m_Row = 4; public int m_Hgt = 80; public boolean m_Loaded = false; public int m_Part = 0; public EventInSFFloat m_Turn = null; public EventInSFFloat m_Bend = null; private gcPoserControl grpMov; private gcPoserParts grpPrt; /* Note: need to store these parts in a list-bag for extensibility */ public Node body; public Node torso; public Node hips; public Node legs; public EventInSFFloat turnBody; public EventInSFFloat turnHead; public EventInSFFloat turnHair; public EventInSFFloat turnTorso; public EventInSFFloat turnLArm; public EventInSFFloat turnLFore; public EventInSFFloat turnLHand; public EventInSFFloat turnRArm; public EventInSFFloat turnRFore; public EventInSFFloat turnRHand; public EventInSFFloat turnHips; public EventInSFFloat turnLLeg; public EventInSFFloat turnRLeg; public EventInSFFloat bendBody; public EventInSFFloat bendHead; public EventInSFFloat bendHair; public EventInSFFloat bendTorso; public EventInSFFloat bendLArm; public EventInSFFloat bendLFore; public EventInSFFloat bendLHand; public EventInSFFloat bendRArm; public EventInSFFloat bendRFore; public EventInSFFloat bendRHand; public EventInSFFloat bendHips; public EventInSFFloat bendLLeg; public EventInSFFloat bendRLeg; public EventOutSFTime headClicked; public EventOutSFTime hairClicked; public EventOutSFTime torsoClicked; public EventOutSFTime leftArmClicked; public EventOutSFTime leftForeClicked; public EventOutSFTime leftHandClicked; public EventOutSFTime rightArmClicked; public EventOutSFTime rightForeClicked; public EventOutSFTime rightHandClicked; public EventOutSFTime hipsClicked; public EventOutSFTime leftLegClicked; public EventOutSFTime rightLegClicked; /* Constructor */ public gcPoser() { } /* Applet Info */ public String getAppletInfo() { return("Name: gcPoser\r\nCopyright 1997 - Robert M. Free - ALL RIGHTS RESERVED\r\n"); } /* Parameter Info */ public String[][] getParameterInfo() { String[][] info = { { PARAM_CTRL_SIZE, "String", "Size of Avatar Control" }, { PARAM_CTRL_INC, "String", "Avatar Control Increments" }, { PARAM_LIST_LEN, "String", "Parts List Displayed Rows" }, }; return(info); } /* Initialize Poser */ public void init() { showStatus("Loading applet..."); /* Get Parameters */ String param = getParameter(PARAM_CTRL_SIZE); if (param != null) { Integer num = new Integer(param); m_Hgt = num.intValue(); } param = getParameter(PARAM_CTRL_INC); if (param != null) { Float num = new Float(param); m_Inc = num.floatValue(); } param = getParameter(PARAM_LIST_LEN); if (param != null) { Integer num = new Integer(param); m_Row = num.intValue(); } setBackground( new Color(255,255,255) ); /* Add panels */ /* Note: disable controls until model is loaded */ grpMov = new gcPoserControl(this); grpMov.disable(); add(grpMov); grpPrt = new gcPoserParts(this); grpPrt.disable(); add(grpPrt); } /* public void paint(Graphics g) { } */ /* Get Browser Object */ public void start() { m_Loaded = false; m_Win = null; m_Frm = null; m_Par = null; m_Fst = null; m_Doc = null; m_Plg = null; m_Mdl = null; body = null; torso = null; hips = null; legs = null; turnBody = null; turnHead = null; turnHair = null; turnTorso = null; turnLArm = null; turnLFore = null; turnLHand = null; turnRArm = null; turnRFore = null; turnRHand = null; turnHips = null; turnLLeg = null; turnRLeg = null; bendBody = null; bendHead = null; bendHair = null; bendTorso = null; bendLArm = null; bendLFore = null; bendLHand = null; bendRArm = null; bendRFore = null; bendRHand = null; bendHips = null; bendLLeg = null; bendRLeg = null; headClicked = null; hairClicked = null; torsoClicked = null; leftArmClicked = null; leftForeClicked = null; leftHandClicked = null; rightArmClicked = null; rightForeClicked= null; rightHandClicked= null; hipsClicked = null; leftLegClicked = null; rightLegClicked = null; /* Start Loader Thread */ if (loader == null) { loader = new gcPoserLoad(this); loader.start(); } } /* Reset */ public void stop() { m_Loaded = false; if (loader != null) { if (loader.isAlive()) loader.stop(); loader = null; } } /* public void destroy() { } */ /* Node Loader is Done - update display */ public void DoneLoading() { /* Set Current Part */ m_Loaded = true; setPart(m_Part); grpPrt.select(m_Part); grpMov.enable(); grpPrt.enable(); showStatus("Ready to Pose!"); } /* Handle eventOut's from model */ public void callback(EventOut e, double ts, Object obj) { if ((m_Loaded) && (obj != null)) { int part = ((Integer)obj).intValue(); setPart(part); grpPrt.select(part); } } /* Set EventIn's for Current Part */ public void setPart(int part) { /* Exit if Parts not loaded */ if (!m_Loaded) return; /* Note: this should be done via a list/bag lookup */ switch (m_Part = part) { case partHEAD: { m_Turn = turnHead; m_Bend = bendHead; break; } case partHAIR: { m_Turn = turnHair; m_Bend = bendHair; break; } case partTORSO: { m_Turn = turnTorso; m_Bend = bendTorso; break; } case partLARM: { m_Turn = turnLArm; m_Bend = bendLArm; break; } case partLFORE: { m_Turn = turnLFore; m_Bend = bendLFore; break; } case partLHAND: { m_Turn = turnLHand; m_Bend = bendLHand; break; } case partRARM: { m_Turn = turnRArm; m_Bend = bendRArm; break; } case partRFORE: { m_Turn = turnRFore; m_Bend = bendRFore; break; } case partRHAND: { m_Turn = turnRHand; m_Bend = bendRHand; break; } case partHIPS: { m_Turn = turnHips; m_Bend = bendHips; break; } case partLLEG: { m_Turn = turnLLeg; m_Bend = bendLLeg; break; } case partRLEG: { m_Turn = turnRLeg; m_Bend = bendRLeg; break; } } } /* Move Current Part */ public void Move( int dir, float inc ) { /* Exit if Parts not loaded */ if (!m_Loaded) return; /* Do the groove thang */ try { switch (dir) { case moveNORTH: { m_Bend.setValue(inc); break; } case moveSOUTH: { m_Bend.setValue(-inc); break; } case moveEAST: { m_Turn.setValue(inc); break; } case moveWEST: { m_Turn.setValue(-inc); break; } case moveHOME: { m_Turn.setValue(0); m_Bend.setValue(0); break; } } } catch (InvalidEventInException e) { showStatus("Invalid EventIn Exception"); } } } /* ************************* */ /* Parts List */ /* ************************* */ class gcPoserParts extends Panel { public List lPrt; private gcPoser myParent; gcPoserParts(gcPoser parent) { myParent = parent; /* Note: should enumerate Avatar to generate this */ lPrt = new List(myParent.m_Row,false); lPrt.addItem("Head"); lPrt.addItem("Hair"); lPrt.addItem("Torso"); lPrt.addItem("Left Arm"); lPrt.addItem("Left Forearm"); lPrt.addItem("Left Hand"); lPrt.addItem("Right Arm"); lPrt.addItem("Right Forearm"); lPrt.addItem("Right Hand"); lPrt.addItem("Hips"); lPrt.addItem("Left Leg"); lPrt.addItem("Right Leg"); add(lPrt); validate(); } public void select(int part) { lPrt.select(part); lPrt.makeVisible(part); } /* public Dimension preferredSize() { } */ /* public void paint(Graphics g) { } */ /* User's Part Selection */ public boolean handleEvent(Event e) { if (e.id == Event.LIST_SELECT) { List lTmp = (List)e.target; myParent.setPart(lTmp.getSelectedIndex()); } return(false); } } /* ****************************** */ /* Movement Controller */ /* From Grafman's Java MUD Client */ /* ****************************** */ class gcPoserControl extends Panel { private Button bNorth, bSouth, bWest, bEast, bSend; private int nHgt, nWth; private gcPoser myParent; /* Layout Buttons */ gcPoserControl(gcPoser parent) { myParent = parent; nHgt = parent.m_Hgt; nWth = parent.m_Hgt; setFont(new Font("Helvetica", Font.BOLD, 14)); GridLayout layout = new GridLayout(3,3,3,3); setLayout(layout); /* yeah, I know - it's lame */ add(new Label("")); add("North", bNorth = new Button("^")); add(new Label("")); add("West", bWest = new Button("<")); add("Center", bSend = new Button("O")); add("East", bEast = new Button(">")); add(new Label("")); add("South", bSouth = new Button("v")); add(new Label("")); validate(); } /* Set Size of Control */ public Dimension preferredSize() { return(new Dimension(nWth,nHgt)); } /* public void paint(Graphics g) { } */ /* Get Button Clicks */ /* Note: add Key support? */ public boolean action(Event e, Object arg) { float inc = myParent.m_Inc; if(e.target == bNorth) { myParent.Move(myParent.moveNORTH, inc); } else if(e.target == bSouth) { myParent.Move(myParent.moveSOUTH, inc); } else if(e.target == bEast) { myParent.Move(myParent.moveEAST, inc); } else if(e.target == bWest) { myParent.Move(myParent.moveWEST, inc); } else if(e.target == bSend) { myParent.Move(myParent.moveHOME, inc); } /* else { return(false); } */ return(true); } } /* ************************* */ /* Loader Thread */ /* ************************* */ class gcPoserLoad extends Thread { private gcPoser p; public gcPoserLoad(gcPoser parent) { p = parent; } /* Note: EAI has no callback to tell us that the model is loaded, */ /* so we use a thread to get all the nodes and members. */ public void run() { Thread me = Thread.currentThread(); boolean bContinue; /* Note: this is a workaround for a cross-frame JSObject bug: */ /* this is not necessary for same-frame use. */ /* In cross frame use, getWindow can return a non-null, */ /* cached object reference, even if the plugin hasn't been loaded. */ try{Thread.sleep(4000);} catch (InterruptedException e) return; /* Note: next 3 sections can be skipped if frames support is not needed */ while ((me == p.loader) && (p.m_Win == null)) { p.showStatus("Waiting for VRML plugin to load..."); p.m_Win = JSObject.getWindow(p); try{Thread.sleep(1);} catch (InterruptedException e) return; } while ((me == p.loader) && (p.m_Par == null)) { p.showStatus("Loading parent"); p.m_Par = (JSObject)p.m_Win.getMember("parent"); try{Thread.sleep(1);} catch (InterruptedException e) return; } while ((me == p.loader) && (p.m_Fst == null)) { p.showStatus("Loading frameset"); p.m_Fst = (JSObject)p.m_Par.getMember("frames"); try{Thread.sleep(1);} catch (InterruptedException e) return; } while ((me == p.loader) && (p.m_Frm == null)) { p.showStatus("Loading frame"); p.m_Frm = (JSObject)p.m_Fst.getSlot(0); try{Thread.sleep(1);} catch (InterruptedException e) return; } while ((me == p.loader) && (p.m_Doc == null)) { p.showStatus("Loading document"); p.m_Doc = (JSObject)p.m_Frm.getMember("document"); try{Thread.sleep(1);} catch (InterruptedException e) return; } while ((me == p.loader) && (p.m_Plg == null)) { p.showStatus("Loading plugins"); p.m_Plg = (JSObject)p.m_Doc.getMember("embeds"); try{Thread.sleep(1);} catch (InterruptedException e) return; } /* Load Model */ while ((me == p.loader) && (p.m_Mdl == null)) { p.showStatus("Loading Avatar"); /* Note: need to add name/id support, when it becomes available */ p.m_Mdl = (Browser)p.m_Plg.getSlot(0); try{Thread.sleep(1);} catch (InterruptedException e) return; } /* Load Nodes */ bContinue = true; p.showStatus("Loading Nodes"); while ((me == p.loader) && bContinue) { try { bContinue = false; /* Note: these should be children of a body node, */ /* but Cosmo limits PROTO nesting to 5 levels deep (per VRML spec). */ if(p.torso == null) { p.showStatus("Loading torso"); p.torso = (Node)p.m_Mdl.getNode("torso"); } if(p.hips == null) { p.showStatus("Loading hips"); p.hips = (Node)p.m_Mdl.getNode("hips"); } if(p.legs == null) { p.showStatus("Loading legs"); p.legs = (Node)p.m_Mdl.getNode("legs"); } } catch (InvalidNodeException e) { bContinue = true; } try{Thread.sleep(1);} catch (InterruptedException e) return; } /* Get eventIn's */ bContinue = true; p.showStatus("Loading Methods"); while ((me == p.loader) && bContinue) { try { bContinue = false; /* Note: this could be done via model enumeration */ if (p.turnHead == null) { p.showStatus("Seeking turnHead"); p.turnHead = (EventInSFFloat)p.torso.getEventIn("turnHead"); } if(p.bendHead == null) { p.showStatus("Seeking bendHead"); p.bendHead = (EventInSFFloat)p.torso.getEventIn("bendHead"); } if(p.turnHair == null) { p.showStatus("Seeking turnHair"); p.turnHair = (EventInSFFloat)p.torso.getEventIn("turnHair"); } if(p.bendHair == null) { p.showStatus("Seeking bendHead"); p.bendHair = (EventInSFFloat)p.torso.getEventIn("bendHair"); } if(p.turnTorso == null) { p.showStatus("Seeking turnTorso"); p.turnTorso = (EventInSFFloat)p.torso.getEventIn("turnTorso"); } if(p.bendTorso == null) { p.showStatus("Seeking bendTorso"); p.bendTorso = (EventInSFFloat)p.torso.getEventIn("bendTorso"); } if(p.turnLArm == null) { p.showStatus("Seeking turnLeftArm"); p.turnLArm = (EventInSFFloat)p.torso.getEventIn("turnLeftArm"); } if(p.bendLArm == null) { p.showStatus("Seeking bendLeftArm"); p.bendLArm = (EventInSFFloat)p.torso.getEventIn("bendLeftArm"); } if(p.turnLFore == null) { p.showStatus("Seeking turnLeftFore"); p.turnLFore = (EventInSFFloat)p.torso.getEventIn("turnLeftFore"); } if(p.bendLFore == null) { p.showStatus("Seeking bendLeftFore"); p.bendLFore = (EventInSFFloat)p.torso.getEventIn("bendLeftFore"); } if(p.turnLHand == null) { p.showStatus("Seeking turnLeftHand"); p.turnLHand = (EventInSFFloat)p.torso.getEventIn("turnLeftHand"); } if(p.bendLHand == null) { p.showStatus("Seeking bendLeftHand"); p.bendLHand = (EventInSFFloat)p.torso.getEventIn("bendLeftHand"); } if(p.turnRArm == null) { p.showStatus("Seeking turnRightArm"); p.turnRArm = (EventInSFFloat)p.torso.getEventIn("turnRightArm"); } if(p.bendRArm == null) { p.showStatus("Seeking bendRightArm"); p.bendRArm = (EventInSFFloat)p.torso.getEventIn("bendRightArm"); } if(p.turnRFore == null) { p.showStatus("Seeking turnRightFore"); p.turnRFore = (EventInSFFloat)p.torso.getEventIn("turnRightFore"); } if(p.bendRFore == null) { p.showStatus("Seeking bendRightFore"); p.bendRFore = (EventInSFFloat)p.torso.getEventIn("bendRightFore"); } if(p.turnRHand == null) { p.showStatus("Seeking turnRightHand"); p.turnRHand = (EventInSFFloat)p.torso.getEventIn("turnRightHand"); } if(p.bendRHand == null) { p.showStatus("Seeking bendRightHand"); p.bendRHand = (EventInSFFloat)p.torso.getEventIn("bendRightHand"); } if(p.turnHips == null) { p.showStatus("Seeking turnHips"); p.turnHips = (EventInSFFloat)p.hips.getEventIn("turnHips"); } if(p.bendHips == null) { p.showStatus("Seeking bendHips"); p.bendHips = (EventInSFFloat)p.hips.getEventIn("bendHips"); } if(p.turnLLeg == null) { p.showStatus("Seeking turnLeftLeg"); p.turnLLeg = (EventInSFFloat)p.legs.getEventIn("turnLeftLeg"); } if(p.bendLLeg == null) { p.showStatus("Seeking bendLeftLeg"); p.bendLLeg = (EventInSFFloat)p.legs.getEventIn("bendLeftLeg"); } if(p.turnRLeg == null) { p.showStatus("Seeking turnRightLeg"); p.turnRLeg = (EventInSFFloat)p.legs.getEventIn("turnRightLeg"); } if(p.bendRLeg == null) { p.showStatus("Seeking bendRightLeg"); p.bendRLeg = (EventInSFFloat)p.legs.getEventIn("bendRightLeg"); } } catch (InvalidEventInException e) { bContinue = true; } try{Thread.sleep(1);} catch (InterruptedException e) return; } /* Get eventOut's */ bContinue = true; p.showStatus("Loading Sensors"); while ((me == p.loader) && bContinue) { try { bContinue = false; /* Note: this could be done via model enumeration */ if (p.headClicked == null) { p.showStatus("Testing headClicked"); p.headClicked = (EventOutSFTime)p.torso.getEventOut("headClicked"); } if (p.hairClicked == null) { p.showStatus("Testing hairClicked"); p.hairClicked = (EventOutSFTime)p.torso.getEventOut("hairClicked"); } if (p.torsoClicked == null) { p.showStatus("Testing torsoClicked"); p.torsoClicked = (EventOutSFTime)p.torso.getEventOut("torsoClicked"); } if (p.leftArmClicked == null) { p.showStatus("Testing leftArmClicked"); p.leftArmClicked = (EventOutSFTime)p.torso.getEventOut("leftArmClicked"); } if (p.leftForeClicked == null) { p.showStatus("Testing leftForeClicked"); p.leftForeClicked = (EventOutSFTime)p.torso.getEventOut("leftForeClicked"); } if (p.leftHandClicked == null) { p.showStatus("Testing leftHandClicked"); p.leftHandClicked = (EventOutSFTime)p.torso.getEventOut("leftHandClicked"); } if (p.rightArmClicked == null) { p.showStatus("Testing rightArmClicked"); p.rightArmClicked = (EventOutSFTime)p.torso.getEventOut("rightArmClicked"); } if (p.rightForeClicked == null) { p.showStatus("Testing rightForeClicked"); p.rightForeClicked = (EventOutSFTime)p.torso.getEventOut("rightForeClicked"); } if (p.rightHandClicked == null) { p.showStatus("Testing rightHandClicked"); p.rightHandClicked = (EventOutSFTime)p.torso.getEventOut("rightHandClicked"); } if (p.hipsClicked == null) { p.showStatus("Testing hipsClicked"); p.hipsClicked = (EventOutSFTime)p.hips.getEventOut("hipsClicked"); } if (p.leftLegClicked == null) { p.showStatus("Testing leftLegClicked"); p.leftLegClicked = (EventOutSFTime)p.legs.getEventOut("leftLegClicked"); } if (p.rightLegClicked == null) { p.showStatus("Testing rightLegClicked"); p.rightLegClicked = (EventOutSFTime)p.legs.getEventOut("rightLegClicked"); } } catch (InvalidEventOutException e) { bContinue = true; } try{Thread.sleep(1);} catch (InterruptedException e) return; } /* Enable observer notification for EventOut */ p.showStatus("Enabling notification"); p.headClicked.advise((EventOutObserver)p, (Object)new Integer(p.partHEAD)); p.hairClicked.advise((EventOutObserver)p, (Object)new Integer(p.partHAIR)); p.torsoClicked.advise((EventOutObserver)p, (Object)new Integer(p.partTORSO)); p.leftArmClicked.advise((EventOutObserver)p, (Object)new Integer(p.partLARM)); p.leftForeClicked.advise((EventOutObserver)p, (Object)new Integer(p.partLFORE)); p.leftHandClicked.advise((EventOutObserver)p, (Object)new Integer(p.partLHAND)); p.rightArmClicked.advise((EventOutObserver)p, (Object)new Integer(p.partRARM)); p.rightForeClicked.advise((EventOutObserver)p, (Object)new Integer(p.partRFORE)); p.rightHandClicked.advise((EventOutObserver)p, (Object)new Integer(p.partRHAND)); p.hipsClicked.advise((EventOutObserver)p, (Object)new Integer(p.partHIPS)); p.leftLegClicked.advise((EventOutObserver)p, (Object)new Integer(p.partLLEG)); p.rightLegClicked.advise((EventOutObserver)p, (Object)new Integer(p.partRLEG)); /* Done! */ p.showStatus("Model Loaded"); p.DoneLoading(); } }