FPS cam going underground (not attached to the terrain)

Hi All,

I'm incrementally diving into JME, I modify flagrush to be FPS -

  1. removed chase cam
  2. add camNode (for cam)   and here starts the problems

    3.I have add input to be FirstPersonHandler -> then I was unable to move more then just first button pressed and cam look stucked

    4.I have changed input to NodeHandler - > much better(act like real FPS), but I'm NOT attached to the terrain
  3. I have input.update in the update method (and other updates - cam,tb,scene)
  4. I tryied to :

      - attach player model to camNode and attach camNode to scene (root)

      - attache camNode to player and attach player to scene

      - almost all other possible combination

    Still no luck, my final status is - I can move like FPS - WASD + mouse, but not attached to the terrain - can magically dive into it.



    Thanks in advance (I;m sure it is something small due developer blindness for its own code).

    P.S I delete some comments for space


package com.captiveimagination.jmenet.flagrush;

import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.ImageIcon;

import jmetest.flagrushtut.lesson8.Lesson8;
import jmetest.flagrushtut.lesson9.Flag;
import jmetest.flagrushtut.lesson9.ForceFieldFence;
import jmetest.flagrushtut.lesson9.Vehicle;
import jmetest.renderer.TestSkybox;
import jmetest.terrain.TestTerrain;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.NodeHandler;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.RenderPass;
import com.jme.renderer.pass.ShadowedRenderPass;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.Skybox;
import com.jme.scene.Spatial;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.terrain.TerrainBlock;

/**
 * Lesson 9
 * @author Mark Powell
 */
public class FlagRush extends BaseGame {
    private static final Logger logger = Logger.getLogger(FlagRush.class
            .getName());
    
    // the terrain we will drive over.
    private TerrainBlock tb;
    // fence that will keep us in.
    private ForceFieldFence fence;
    //Sky box (we update it each frame)
    private Skybox skybox;
    //the new player object
    //private Vehicle player;
    private Vehicle player;
    //the flag to grab
    private Flag flag;
    //private ChaseCamera chaser;
    protected InputHandler input;
    //the timer
    protected Timer timer;
    // Our camera object for viewing the scene
    private Camera cam;
    //The chase camera, this will follow our player as he zooms around the level
    private ChaseCamera chaser;
    // the root node of the scene graph
    private Node scene;

    //used to hold on the scene and not flying around
    private CameraNode camNode;

    // display attributes for the window. We will keep these values
    // to allow the user to change them
    private int width, height, depth, freq;
    private boolean fullscreen;
    
    //store the normal of the terrain
    private Vector3f normal = new Vector3f();
    
    //height above ground level
    private float agl;
    
    private static ShadowedRenderPass shadowPass = new ShadowedRenderPass();
    private BasicPassManager passManager;
    
    protected void update(float interpolation) {
       // Update the GameTaskQueue
       GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
       
        // update the time to get the framerate
        timer.update();
        interpolation = timer.getTimePerFrame();
        //update the keyboard input (move the player around)
        input.update(interpolation);
        //update the chase camera to handle the player moving around.
        //chaser.update(interpolation);
        //update the fence to animate the force field texture
        fence.update(interpolation);
        //update the flag to make it flap in the wind
        flag.update(interpolation);
        
        //we want to keep the skybox around our eyes, so move it with
        //the camera
        skybox.setLocalTranslation(cam.getLocation());
        skybox.updateGeometricState(0, true);
        
        // if escape was pressed, we exit
        if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
            finished = true;
        }
        
        //We don't want the chase camera to go below the world, so always keep
        //it 2 units above the level.
        if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) {
            cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2;
            cam.update();
        }
        
        //make sure that if the player left the level we don't crash. When we add collisions,
        //the fence will do its job and keep the player inside.
        float characterMinHeight = tb.getHeight(player
                .getLocalTranslation())+agl;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
            player.getLocalTranslation().y = characterMinHeight;
        }
        
        //get the normal of the terrain at our current location. We then apply it to the up vector
        //of the player.
        tb.getSurfaceNormal(player.getLocalTranslation(), normal);
        if(normal != null) {
            player.rotateUpTo(normal);
        }
        
        //Because we are changing the scene (moving the skybox and player) we need to update
        //the graph.
        scene.updateGeometricState(interpolation, true);
                
    }

    /**
     * draws the scene graph
     *
     * @see com.jme.app.BaseGame#render(float)
     */
    protected void render(float interpolation) {
       // Render the GameTaskQueue
       GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
       
        // Clear the screen
        display.getRenderer().clearBuffers();
        display.getRenderer().draw(scene);
        /** Have the PassManager render. */
        passManager.renderPasses(display.getRenderer());
    }

    /**
     * initializes the display and camera.
     *
     * @see com.jme.app.BaseGame#initSystem()
     */
    protected void initSystem() {
        // store the properties information
        width = settings.getWidth();
        height = settings.getHeight();
        depth = settings.getDepth();
        freq = settings.getFrequency();
        fullscreen = settings.isFullscreen();
        
        try {
            display = DisplaySystem.getDisplaySystem(settings.getRenderer());
            display.setMinStencilBits(8);
            display.createWindow(width, height, depth, freq, fullscreen);

            cam = display.getRenderer().createCamera(width, height);
        } catch (JmeException e) {
            logger.log(Level.SEVERE, "Could not create displaySystem", e);
            System.exit(1);
        }

        // set the background to black
        display.getRenderer().setBackgroundColor(ColorRGBA.black);

        // initialize the camera
        cam.setFrustum(1.0f, 1000.0f, -0.55f, 0.55f, 0.4125f, -0.4125f);
        Vector3f loc = new Vector3f(4.0f, 0.0f, 0.0f);
        Vector3f left = new Vector3f(0.0f, -1.0f, 0.0f);
        Vector3f up = new Vector3f(0.0f, 0.0f, 1.0f);
        Vector3f dir = new Vector3f(-1.0f, 0f, 0.0f);
        cam.setFrame(loc, left, up, dir);
                      
        
        /** Get a high resolution timer for FPS updates. */
        timer = Timer.getTimer();

        display.getRenderer().setCamera(cam);
        
        KeyBindingManager.getKeyBindingManager().set("exit",
                KeyInput.KEY_ESCAPE);
        
    }

    /**
     * initializes the scene
     *
     * @see com.jme.app.BaseGame#initGame()
     */
    protected void initGame() {
        display.setTitle("Flag Rush");
        
        scene = new Node("Scene graph node");
        /** Create a ZBuffer to display pixels closest to the camera above farther ones.  */
        ZBufferState buf = display.getRenderer().createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
        scene.setRenderState(buf);
        
        CullState cs = display.getRenderer().createCullState();
        cs.setCullFace(CullState.Face.Back);
        scene.setRenderState(cs);
        
        //Add terrain to the scene
        buildTerrain();
        //Add a flag randomly to the terrain
        buildFlag();
        //Light the world
        buildLighting();
        //add the force field fence
        buildEnvironment();
        //Add the skybox
        buildSkyBox();

        //Build the player
        buildPlayer();
        
        //build the chase cam
        //buildChaseCamera();
        //build the player input
        buildInput();
        
        //set up passes
        buildPassManager();

        

        cam.update();
        // update the scene graph for rendering
        scene.updateGeometricState(0.0f, true);
        scene.updateRenderState();
    }
    
    private void buildPassManager() {
        passManager = new BasicPassManager();

        // Add skybox first to make sure it is in the background
        RenderPass rPass = new RenderPass();
        rPass.add(skybox);
        passManager.add(rPass);

        shadowPass.add(scene);
        shadowPass.addOccluder(player);    
//        shadowPass.addOccluder(flag);
        shadowPass.setRenderShadows(true);
        shadowPass.setLightingMethod(ShadowedRenderPass.LightingMethod.Modulative);
        passManager.add(shadowPass);
    }

    private void buildFlag() {
        //create the flag and place it
        flag = new Flag(tb);
        scene.attachChild(flag);
        flag.placeFlag();
    }
    
    private void buildPlayer() {
        Spatial model = null;
        try {
            URL bikeFile = Lesson8.class.getClassLoader().getResource("jmetest/data/model/bike.jme");
            BinaryImporter importer = new BinaryImporter();
            model = (Spatial)importer.load(bikeFile.openStream());
            model.setModelBound(new BoundingBox());
            model.updateModelBound();
            //scale it to be MUCH smaller than it is originally
            model.setLocalScale(.0025f);
        } catch (IOException e) {
            logger
                    .throwing(this.getClass().toString(), "buildPlayer()",
                            e);
        }
        
        //set the vehicles attributes (these numbers can be thought
        //of as Unit/Second).
        player = new Vehicle("Player Node", model);
        player.setAcceleration(15);
        player.setBraking(15);
        player.setTurnSpeed(2.5f);
        player.setWeight(25);
        player.setMaxSpeed(25);
        player.setMinSpeed(15);
        
        player = new Vehicle(" Player ", model);
        player.setLocalTranslation(new Vector3f(0,0, 0));
        

        camNode = new CameraNode( "Camera Node", cam);
        camNode.setLocalTranslation(new Vector3f(100, 0, 100));
        
        camNode.attachChild(player);

        //camNode.setModelBound( new BoundingBox() );
        //camNode.updateModelBound();
        //camNode.updateGeometricState(0, true);
        //camNode.attachChild(player);
        
        scene.attachChild(camNode);
        player.updateGeometricState(0, true);

        agl = ((BoundingBox)player.getWorldBound()).yExtent;
        player.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

        //camNode.updateGeometricState(0,true);
    }
    

    private void buildEnvironment() {
        //This is the main node of our fence
        fence = new ForceFieldFence("fence");
        
        fence.setLocalScale(5);
        //now let's move the fence to to the height of the terrain and in a little bit.
        fence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25)+10, 25));
        
        scene.attachChild(fence);
    }

    /**
     * creates a light for the terrain.
     */
    private void buildLighting() {
        /** Set up a basic, default light. */
        DirectionalLight light = new DirectionalLight();
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
        light.setDirection(new Vector3f(1,-1,0));
        light.setShadowCaster(true);
        light.setEnabled(true);

          /** Attach the light to a lightState and the lightState to rootNode. */
        LightState lightState = display.getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f));
        lightState.attach(light);
        scene.setRenderState(lightState);
    }

    /**
     * build the height map and terrain block.
     */
    private void buildTerrain() {
        
//        MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
        
        int[] heightMap = null;
        ImageIcon imageIcon = null;
        try {
           ObjectInputStream ois = new ObjectInputStream(getClass().getClassLoader().getResourceAsStream("resource/heightmap.data"));
           heightMap = (int[])ois.readObject();
           imageIcon = (ImageIcon)ois.readObject();
           ois.close();
        } catch(Exception exc) {
           throw new RuntimeException(exc);
        }
        float[] tempMap = new float[heightMap.length];
        for(int i = 0; i < heightMap.length; i++){
            tempMap[i] = heightMap[i];
        }
        // Scale the data
        Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
        // create a terrainblock
         tb = new TerrainBlock("Terrain", 64, terrainScale, tempMap,
                new Vector3f(0, 0, 0));

        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
        
        // assign the texture to the terrain
        TextureState ts = display.getRenderer().createTextureState();
        Texture t1 = TextureManager.loadTexture(imageIcon.getImage(),
                Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true);
        ts.setTexture(t1, 0);
        
        //load a detail texture and set the combine modes for the two terrain textures.
        Texture t2 = TextureManager.loadTexture(
                TestTerrain.class.getClassLoader().getResource(
                "jmetest/data/texture/Detail.jpg"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear);

        ts.setTexture(t2, 1);
        t2.setWrap(Texture.WrapMode.Repeat);

        t1.setApply(Texture.ApplyMode.Combine);
        t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
        t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
        t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
        t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
        t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
        t1.setCombineScaleRGB(Texture.CombinerScale.One);

        t2.setApply(Texture.ApplyMode.Combine);
        t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
        t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
        t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
        t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
        t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
        t2.setCombineScaleRGB(Texture.CombinerScale.One);

        tb.setRenderState(ts);
        //set the detail parameters.
        tb.setDetailTexture(1, 16);
        tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);        
        scene.attachChild(tb);
        
        
    }
    
    private void buildSkyBox() {
        skybox = new Skybox("skybox", 10, 10, 10);

        Texture north = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/north.jpg"),
            Texture.MinificationFilter.BilinearNearestMipMap,
            Texture.MagnificationFilter.Bilinear);
        Texture south = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/south.jpg"),
            Texture.MinificationFilter.BilinearNoMipMaps,
            Texture.MagnificationFilter.Bilinear);
        Texture east = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/east.jpg"),
            Texture.MinificationFilter.BilinearNoMipMaps,
            Texture.MagnificationFilter.Bilinear);
        Texture west = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/west.jpg"),
            Texture.MinificationFilter.BilinearNoMipMaps,
            Texture.MagnificationFilter.Bilinear);
        Texture up = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/top.jpg"),
            Texture.MinificationFilter.BilinearNoMipMaps,
            Texture.MagnificationFilter.Bilinear);
        Texture down = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/bottom.jpg"),
            Texture.MinificationFilter.BilinearNoMipMaps,
            Texture.MagnificationFilter.Bilinear);

        skybox.setTexture(Skybox.Face.North, north);
        skybox.setTexture(Skybox.Face.West, west);
        skybox.setTexture(Skybox.Face.South, south);
        skybox.setTexture(Skybox.Face.East, east);
        skybox.setTexture(Skybox.Face.Up, up);
        skybox.setTexture(Skybox.Face.Down, down);
        skybox.preloadTextures();
        skybox.updateRenderState();
       // scene.attachChild(skybox);
    }
    
    private void buildInput() {
        //input = new CustomFirstPersonHandler(cam, 50, 20, player);
        input = new NodeHandler( camNode, 15f, 1 );
    }
    
    protected void reinit() {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }
    
 
    protected void quit() {
        super.quit();
        System.exit(0);
    }


    protected void cleanup() {

        KeyInput.destroyIfInitalized();
       MouseInput.destroyIfInitalized();
    }
}

In your update you are translating the player, you should be translating the cameraNode that the player is attached to using something like:


camNode.setLocalTranslation(camNode.getLocalTranslation().x,
            characterMinHeight+camYadjust,
            camNode.getLocalTranslation().z);



Hope this helps

Yep,that was it… I also tried to fix cam itself but didn't work, I really have to fix camNode.



Thanx komodo,that was the problem.



Kind Regards