Lesson 6: Movement!

Return to the home page

The goal: (Due to movement and randomness, these screenshots are just a few possible ways the program can look. The colors should not change during a single run of the program)

Click for full size. Hover over for more details.

Click here to download the code. (If the page opens in the broswer, you can right click on link and click "Save Link As", or right click on the page and click "Save As")

            
        // most applications will want to extend the SimpleGame class
        import com.jme.app.SimpleGame;
        
        // a vector containing three float numbers;
        //   used to store positions and translations
        import com.jme.math.Vector3f;
        import com.jme.math.Quaternion;
        import com.jme.math.Matrix3f;
        
        // geometric shapes used in program
        import com.jme.scene.shape.Box;
        import com.jme.scene.shape.Sphere;
        
        // use predefined colors and create custom colors
        import com.jme.renderer.ColorRGBA;
        
        // work with basic materials
        import com.jme.scene.state.MaterialState;
        
        // process user input
        import com.jme.input.InputHandler;
        import com.jme.input.KeyInput;
        import com.jme.input.KeyBindingManager;
        import com.jme.input.FirstPersonHandler;
        
        // display text
        import com.jme.scene.Text;
        import com.jmex.font2d.Text2D;
        import com.jmex.font2d.Font2D;
        
        // an internal vertex of the scenegraph tree;
        //  use to group together objects,
        //  apply render settings, which affects all child vertices
        import com.jme.scene.Node;
        import com.jme.scene.Spatial;
        import com.jme.scene.Geometry;
        
        // methods to change render settings
        import com.jme.renderer.Renderer;
        
        // camera and its controlling node
        import com.jme.renderer.Camera;
        import com.jme.scene.CameraNode;
        
        /**
         *   Demonstrate two types of movement:
         *   translation (uses Vectors)
         *   and rotation (using Quaternions).
         */
        public class MoveDemo extends SimpleGame
        {
           // a geometric shape that will be rendered
           Box myBox;
        
           // to control the camera movement, create a special node that controls the camera
           CameraNode cameraNode;
        
           public static void main(String[] args)
           {
               // create an instance of this program
               MoveDemo app = new MoveDemo();
        
               // before the main program starts, display the graphics configuration window
               //   where the user can choose among screen resolution, color depth, and full screen options
               app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        
               // The start() method runs simpleInitGame(),
               //   then starts the game loop: simpleUpdate(), then simpleRender()
               // To terminate game loop, call finish()
               app.start();
           }
        
           protected void simpleInitGame()
           {
               // make the standard cursor visible.
               org.lwjgl.input.Mouse.setGrabbed(false);
               // SimpleGame class defaults mouse behavior to rotate view; here we disable this.
               ((FirstPersonHandler)input).getMouseLookHandler().setEnabled(false);
        
               // create a material to apply to geometric objects, use objects' default color,
               //  apply to front and back, and assign rendering to root node
               MaterialState customMaterial = display.getRenderer().createMaterialState();
               customMaterial.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);
               customMaterial.setMaterialFace(MaterialState.MaterialFace.FrontAndBack);
               rootNode.setRenderState(customMaterial);
        
               myBox = new Box("s", new Vector3f(0,0,0), 1, 1, 1 );
               myBox.setRandomColors();
               rootNode.attachChild( myBox );
        
               buildInput();
        
               // set up and attach the camera node
               cameraNode = new CameraNode("cameraNode", cam);
               rootNode.attachChild( cameraNode );
               cameraNode.setLocalTranslation(0,0,-5);
               cameraNode.updateWorldData(0);
           }
        
           protected void simpleUpdate()
           {
               try {   Thread.sleep(1000/120);   } // force decrease in frames per second
               catch (Exception e) {}
        
               processInput();
           }
        
           protected void simpleRender()
           {
               // nothing to add
           }
        
           // create input bindings
           public void buildInput()
           {
               // remove all pre-existing key assignments
               //KeyBindingManager.getKeyBindingManager().removeAll();
        
               // exit the program
               KeyBindingManager.getKeyBindingManager().set( "exit", KeyInput.KEY_ESCAPE );
               // display statistics (fps, number of tri's and quad's, etc.)
               KeyBindingManager.getKeyBindingManager().set( "toggle_stats", KeyInput.KEY_LSHIFT );
        
               // WASDQZ typically moves the camera;
               //  we are reassigning them to move the object.
        
               //  Node local translation is relative to parent's coordinate system.
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateX+", KeyInput.KEY_Q );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateX-", KeyInput.KEY_W );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateY+", KeyInput.KEY_A );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateY-", KeyInput.KEY_S );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateZ+", KeyInput.KEY_Z );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateZ-", KeyInput.KEY_X );
               //  Node local rotation is in direction relative to the spatial's coordinate system.
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateX+", KeyInput.KEY_1 );
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateY+", KeyInput.KEY_2 );
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateZ+", KeyInput.KEY_3 );
        
               // camera translation
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateUp", KeyInput.KEY_I );
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateDown", KeyInput.KEY_K );
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateLeft", KeyInput.KEY_J );
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateRight", KeyInput.KEY_L );
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateForward", KeyInput.KEY_U );
               KeyBindingManager.getKeyBindingManager().set( "cameraTranslateBackward", KeyInput.KEY_M );
               // camera rotation
               KeyBindingManager.getKeyBindingManager().set( "cameraRotateUp", KeyInput.KEY_UP );
               KeyBindingManager.getKeyBindingManager().set( "cameraRotateDown", KeyInput.KEY_DOWN );
               KeyBindingManager.getKeyBindingManager().set( "cameraRotateLeft", KeyInput.KEY_LEFT );
               KeyBindingManager.getKeyBindingManager().set( "cameraRotateRight", KeyInput.KEY_RIGHT );
        
               // Node translation relative to camera
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateUp", KeyInput.KEY_T );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateDown", KeyInput.KEY_G );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateLeft", KeyInput.KEY_F );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateRight", KeyInput.KEY_H );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateForward", KeyInput.KEY_R );
               KeyBindingManager.getKeyBindingManager().set( "nodeTranslateBackward", KeyInput.KEY_V );
               // Node rotation relative to camera
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateUp", KeyInput.KEY_4 );
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateDown", KeyInput.KEY_5 );
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateLeft", KeyInput.KEY_6 );
               KeyBindingManager.getKeyBindingManager().set( "nodeRotateRight", KeyInput.KEY_7 );
        
           }
        
           // process input entered since last update.
           public void processInput()
           {
               // defining speed in terms of tpf (time per frame) helps keep speed constant
               //  across varying framerates
               float speed = 2*tpf;
        
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateX+", true))
                   myBox.getLocalTranslation().addLocal(speed,0,0);
               // alternatively, we could write
               // myBox.getLocalTranslation().addLocal( Vector3f.UNIT_X.mult(speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateX-", true))
                   myBox.getLocalTranslation().addLocal(-speed,0,0);
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateY+", true))
                   myBox.getLocalTranslation().addLocal(0,speed,0);
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateY-", true))
                   myBox.getLocalTranslation().addLocal(0,-speed,0);
        
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateZ+", true))
                   myBox.getLocalTranslation().addLocal(0,0,speed);
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateZ-", true))
                   myBox.getLocalTranslation().addLocal(0,0,-speed);
        
               //  Note: movement ROTATION is in direction relative to the spatial's coordinate system.
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateX+", true))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(speed, Vector3f.UNIT_X) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateY+", true))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(speed, Vector3f.UNIT_Y) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateZ+", true))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(speed, Vector3f.UNIT_Z) );
        
               // camera translation
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateLeft"))
                   cameraNode.getLocalTranslation().addLocal( cam.getLeft().mult( speed ) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateRight"))
                   cameraNode.getLocalTranslation().addLocal( cam.getLeft().mult( -speed ) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateUp"))
                   cameraNode.getLocalTranslation().addLocal( cam.getUp().mult( speed ) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateDown"))
                   cameraNode.getLocalTranslation().addLocal( cam.getUp().mult( -speed ) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateForward"))
                   cameraNode.getLocalTranslation().addLocal( cam.getDirection().mult( speed ) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraTranslateBackward"))
                   cameraNode.getLocalTranslation().addLocal( cam.getDirection().mult( -1 * speed ) );
               // camera rotation -- mathematics calculated in a separate method
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraRotateUp"))
                   cameraRotate( cameraNode, -speed / 3, cam.getLeft().clone() );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraRotateDown"))
                   cameraRotate( cameraNode, speed / 3, cam.getLeft().clone() );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraRotateLeft"))
                   cameraRotate( cameraNode, speed / 3, cam.getUp().clone() );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("cameraRotateRight"))
                   cameraRotate( cameraNode, -speed / 3, cam.getUp().clone() );
        
               // node translation relative to camera.
               //  NOTE: this code only works when node is attached directly to root,
               //        otherwise, a coordinate transformation is required.
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateUp"))
                   myBox.getLocalTranslation().addLocal( cam.getUp().clone().mult(speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateDown"))
                   myBox.getLocalTranslation().addLocal( cam.getUp().clone().mult(-speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateLeft"))
                   myBox.getLocalTranslation().addLocal( cam.getLeft().clone().mult(speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateRight"))
                   myBox.getLocalTranslation().addLocal( cam.getLeft().clone().mult(-speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateForward"))
                   myBox.getLocalTranslation().addLocal( cam.getDirection().clone().mult(speed) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeTranslateBackward"))
                   myBox.getLocalTranslation().addLocal( cam.getDirection().clone().mult(-speed) );
        
               // node rotation relative to camera
               //  NOTE: since rotation is relative to a spatials's orientation,
               //        we must convert a "global vector" (the vector around which we will rotate)
               //        into a "local vector" (coordinates converted according to spatial's orientation)
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateUp"))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(
                          speed, convertGlobalToLocal(cam.getLeft().clone(), myBox )) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateDown"))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(
                          -speed, convertGlobalToLocal(cam.getLeft().clone(), myBox )) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateLeft"))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(
                           -speed, convertGlobalToLocal(cam.getUp().clone(), myBox )) );
               if (KeyBindingManager.getKeyBindingManager().isValidCommand("nodeRotateRight"))
                   myBox.getLocalRotation().multLocal( new Quaternion().fromAngleAxis(
                           speed, convertGlobalToLocal(cam.getUp().clone(), myBox )) );
           }
        
           public void cameraRotate(CameraNode camNode, float rotateAngle, Vector3f rotateAxis)
           {
               Quaternion Q = new Quaternion();
               Matrix3f   M = new Matrix3f();
               Q.fromAngleAxis( rotateAngle, rotateAxis );
               M = Q.toRotationMatrix();
        
               Camera tempCamera = camNode.getCamera();
               M.multLocal( tempCamera.getDirection() );
               M.multLocal( tempCamera.getUp() );
               M.multLocal( tempCamera.getLeft() );
               tempCamera.normalize();
               camNode.updateFromCamera();
           }
        
           public Vector3f convertGlobalToLocal(Vector3f v, Spatial n)
           {
               // determine the global rotation of the node under consideration
               Quaternion Q = n.getWorldRotation().clone();
               // convert to a matrix
               Matrix3f M = Q.toRotationMatrix();
               // the next two lines contain the important math
               M = M.invert();
               Vector3f V = M.mult( v );
               return V;
           }
        }
     

Return to the home page