The Animated 3D SpriteFigure 19-5 shows the visible methods of AnimSprite3D. The interface of this class is almost identical to Sprite3D (from the Tour3D application in Chapter 18). The setPosition( ), moveBy( ), and doRotateY( ) operations adjust the position and orientation of the sprite, isActive( ) and setActive( ) relate to the sprite's activity (i.e., whether it is visible on the screen or not), getCurrLoc( ) returns the sprite's position, and getTG( ) returns its top-level transformGroup. The only new method is setPose( ), which takes a pose name as an argument and changes the displayed model accordingly. Its implementation is explained later in this section. Figure 19-4. Scene graph for the applicationFigure 19-5. The public methods of AnimSprite3DLoading the PosesThe choice of models is hardwired into AnimSprite3D, which makes things simpler than having to deal with arbitrary input. The names of the models are predefined in the poses[] array: private final static String poses[] = {"stand", "walk1", "walk2", "rev1", "rev2", "rotClock", "rotCC", "mleft", "mright", "punch1", "punch2"}; The names in poses[] are used by loadPoses( ) to load the same-named 3D Studio Max files using PropManager. The loaded models (the different sprite poses) are attached to the scene using a Java 3D Switch node: private void loadPoses( ) { PropManager propMan; imSwitch = new Switch(Switch.CHILD_MASK); imSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE); maxPoses = poses.length; for (int i=0; i < maxPoses; i++) { propMan = new PropManager(poses[i] + ".3ds", true); imSwitch.addChild( propMan.getTG( ) ); // add obj to switch } visIms = new BitSet( maxPoses ); // bitset used for switching currPoseNo = STAND_NUM; // sprite standing still setPoseNum( currPoseNo ); } The Switch node (imSwitch) is shown in the scene graph in Figure 19-4. It's a child of the objectTG TransformGroup, which is used for positioning and rotating the sprite. The purpose of the Switch node is to allow the sprite to strike up a different pose by choosing one from the selection hanging below the Switch. imSwitch is created with the CHILD_MASK value, which permits pose switching to be carried out using a Java 3D BitSet object (visIms). The bits of the BitSet are mapped to the children of the Switch: bit 0 corresponds to child 0, bit 1 to child 1, and so on. BitSet offers various methods for clearing bits and setting them. The bit manipulation is hidden inside the setPoseNum( ) method, which takes as its input the bit index that should be turned on in imSwitch: private void setPoseNum(int idx) { visIms.clear( ); visIms.set( idx ); // show child with index idx imSwitch.setChildMask( visIms ); currPoseNo = idx; } The model stored in the idx position below the Switch node is made visible when setChildMask( ) is called. The runtime adjustment of the Switch requires its write capability to be turned on. Where Did These Models Come From?I created the models using Poser (http://www.curiouslabs.com), which specializes in 3D figure creation and animation and includes a range of predefined models, poses, and animation sequences. Poser fans should check out the collection of links in the Google directory: http://directory.google.com/Top/Computers/Software/Graphics/3D/Animation_and_Design_Tools/Poser/. I used one of Poser's existing figures, the stick child, and exported different versions of it in various standard poses to 3DS files. Poser animation sequences weren't utilized; each file only contains a single figure.
The models were loaded into the Loader3D application (developed in Chapter 16) to adjust their position and orientation. Poser exports 3DS models orientated with the XZ plane as their base, which means that the model is lying flat on its back when loaded into Loader3D.
Setting a PoseA sprite's pose is changed by calling setPose( ), which takes a pose name as its input argument. The method determines the index position of that name in the poses[] array and calls setPoseNum( ): public boolean setPose(String name) { if (isActive( )) { int idx = getPoseIndex(name); if ((idx < 0) || (idx > maxPoses-1)) return false; setPoseNum( idx ); return true; } else return false; } The code is complicated by the need to check for sprite activity. An inactive sprite is invisible, so there's no point changing its pose.
Sprite ActivitySprite activity can be toggled on and off by calls to setActive( ) with a Boolean argument: public void setActive(boolean b) { isActive = b; if (!isActive) { visIms.clear( ); imSwitch.setChildMask( visIms ); // display nothing } else if (isActive) setPoseNum( currPoseNo ); // make visible } This approach requires a global integer, currPoseNo, which records the index of the current pose. It's used to make the sprite visible after a period of inactivity. Floor Boundary DetectionThe movement and rotation methods in AnimSprite3D are unchanged from Sprite3D except in the case of the moveBy( ) method. The decision not to use obstacles means there's no Obstacle object available for checking if the sprite is about to move off the floor. This is remedied by a beyondEdge( ) method, which determines if the sprite's (x, z) coordinate is outside the limits of the floor: public boolean moveBy(double x, double z) // move the sprite by an (x,z) offset { if (isActive( )) { Point3d nextLoc = tryMove( new Vector3d(x, 0, z)); if (beyondEdge(nextLoc.x) || beyondEdge(nextLoc.z)) return false; else { doMove( new Vector3d(x, 0, z) ); return true; } } else // not active return false;} // end of moveBy( ) private boolean beyondEdge(double pos) { if ((pos < -FLOOR_LEN/2) || (pos > FLOOR_LEN/2)) return true; return false; } |