ThirdPersonController messing up my camera?

Hey folks. I wrote my own home-grown tile-loader (which I'm quite proud of).



The only thing is, I figured I'd be blocking players from "leaving" the "world" (meaning they wouldn't be able to exceed the X- and Z-coordinates that are mapped) so I didn't bother doing any error handling for that (I may implement it later, but right now I've got more important things to do).



Before I get into networking, I'd like to have a little sphere to represent my player running around. I attempted to implement the chase camera I found in TestThirdPersonController, but it's making my camera have ridiculous coordinates during the update cycle.



On the last test I ran, I included some debugging prints to show the coordinate of the camera itself. At each update, it printed where it was. Here's what I got:



cam: 1.0162675 0.0

cam: 3616.852 0.0

cam: -52140.98 0.0



Why is the camera jumping around sporadically? I'll attach as much code relating to the camera as I can.


float camMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation());
        if(!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight) && cam.getLocation().y <= camMinHeight)
        {
            cam.getLocation().y = camMinHeight;
            cam.update();
        }
       
        float characterMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(charNode.getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight))
   {
            charNode.getLocalTranslation().y = characterMinHeight;
        }



    private void setupChaseCamera()
    {
        Vector3f targetOffset = new Vector3f();
        targetOffset.y = ((BoundingBox)charNode.getWorldBound()).yExtent * 1.5f;
        chaser = new ChaseCamera(cam, charNode);
        chaser.setTargetOffset(targetOffset);
    }



Also worthy to note is the fact that the sphere which I'm using as a player didn't move at all. Just the camera. When it moves into negative territory, it crashes the game (since I don't tile under 0,0,0)

I cannot really make out from the code you posted what could be wrong… however some points I like to keep in mind myself:


  1. There is no camera… Move thy sphere and you will see that the view will move itself…
  2. Always make sure your world directions are set and consistent with everything else - left, up… the lot…
  3. Check with something simpler first - if the first person camera works, move on to 3rd person.
  4. Patience is a virtue



    :slight_smile:

I'm not sure if this is your issue, but I ran into something similar the other day my player node was bouncing around.  I finally realized that I needed to use .setLocalTranslation - because just setting the Y directly didnt update the worldTranslation - which iirc was what the TPH uses.  So basically my code was fighting the TPH for control of the node position.



it looks like I also call .updateWorldData on the player node each time, but I'm not sure if that is just a remnant from my testing or if it actually affects anything useful.  I don't have time at the moment to test it


unfair said:

I'm not sure if this is your issue, but I ran into something similar the other day my player node was bouncing around.  I finally realized that I needed to use .setLocalTranslation - because just setting the Y directly didnt update the worldTranslation - which iirc was what the TPH uses.  So basically my code was fighting the TPH for control of the node position.

it looks like I also call .updateWorldData on the player node each time, but I'm not sure if that is just a remnant from my testing or if it actually affects anything useful.  I don't have time at the moment to test it


Thank you very much for your reply! I'm currently in the process of rebuilding my code on a stripped foundation (i.e. BaseGame instead of SimplePassGame), but when I'm done I'll definitely give those things a shot and let you know how it works!

I just remembered I saved the files from the old test, so I tried changing the original section where everything gets updated to this:


        float camMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation());
        if(!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight) && cam.getLocation().y <= camMinHeight)
        {
            cam.setLocation(new Vector3f(cam.getLocation().x, camMinHeight, cam.getLocation().z));
            cam.update();
        }
       
        float characterMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(charNode.getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight))
   {
            charNode.setLocalTranslation(new Vector3f(charNode.getLocalTranslation().x, characterMinHeight, charNode.getLocalTranslation().z));
            charNode.updateWorldData(tpf);
        }



Is that what you meant? If so, that didn't work either :(

Yeah something similar to that.  What I'd suggest you do is grab the cam location at the beginning of your update function, and after every line print out the current location of the camera.  I suspect you'll see that one line changes the location majorly - it's probably something keeping a local reference, or somewhere you forgot to use .clone when copying a Vector3f.


do you call chaser.update() to update ? i didnt't see it in the code.

Yeah, I did it in my SimpleUpdate.

unfair said:

Yeah something similar to that.  What I'd suggest you do is grab the cam location at the beginning of your update function, and after every line print out the current location of the camera.  I suspect you'll see that one line changes the location majorly - it's probably something keeping a local reference, or somewhere you forgot to use .clone when copying a Vector3f.


Great advice, can't believe I didn't think of it myself. I can NOT figure out what's going on here.. I did what you said in two locations. One in the main update loop, and one in the updateChaser() method, which I use to keep my code tidy. In the main update loop, the position never changed. In the updateChaser loop, it went berzerk. I have NO clue how to fix this, and I've looked at every resource I know of.

Don't feel obligated to look through this entire code, but if you have time to take a quick glance, any hope of fixing this bug is appreciated.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package fyrestonev01;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.ThirdPersonHandler;
import com.jme.input.joystick.JoystickInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Plane;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.Node;
import com.jme.scene.PassNode;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.Timer;
import com.jmex.effects.water.WaterRenderPass;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Tyler
 */
public class FyrestoneClient extends BaseGame
{
   
    //Camera
    private Camera cam;
    private ChaseCamera chaser;
    //End Camera
   
    //Character
    private Node charNode;
    private Box character;
    //End character
   
    //Terrain
    private Node terrainNode;
    private Skybox skybox;
    private LightState lightState;
    private DirectionalLight light;
    private FogState fog;
    private TileSpace[] loadedTiles = new TileSpace[9];
    private PassNode[] tiles = new PassNode[9];
    //End Terrain
   
    //Statistics
    private Timer timer;
    private float tpf;
    //End statistics
   
    //Pass Management
    private BasicPassManager pManager;
    private RenderPass terrainPass;
    private WaterRenderPass waterPass;
    private Quad waterQuad;
    //End Pass Management
   
    //Input
    private InputHandler input;
    //End input
   
    //Display Settings
    private int width, height, depth, freq;
    private boolean fullscreen;
    //End display settings
   
    public static void main(String args[])
    {
        FyrestoneClient app = new FyrestoneClient();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    @Override
    protected void update(float interpolation)
    {
        timer.update();
        tpf = timer.getTimePerFrame();
        updateChaser();
        input.update(tpf);
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
        checkInput();
       
        Vector3f myLoc = cam.getLocation();
        skybox.setLocalTranslation(myLoc);
        updateTiles();
        Vector3f transVec = new Vector3f(cam.getLocation().x, waterPass.getWaterHeight(), cam.getLocation().z);
        setTextureCoords(0, transVec.y, -transVec.z, .07f);
        setVertexCoords(transVec.x, transVec.y, transVec.z);
       
        terrainNode.updateGeometricState(tpf, true);
        pManager.updatePasses(tpf);
    }

    @Override
    protected void render(float interpolation)
    {
        display.getRenderer().clearStatistics();
        display.getRenderer().clearBuffers();
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
        pManager.renderPasses(display.getRenderer());
    }

    @Override
    protected void initSystem()
    {
        try {createWindow();}
        catch(JmeException e) {e.printStackTrace(); System.exit(1);}
       
        cam = display.getRenderer().createCamera(width, height);
        setupCamera();
        display.getRenderer().setCamera(cam);
        timer = Timer.getTimer();
    }

    @Override
    protected void initGame()
    {
        createKeyBindings();
        terrainNode = new Node("Terrain");
        charNode = new Node("Character");
        createCharacter();
        setupChaser();
        input = new ThirdPersonHandler(charNode, cam);
        input.setActionSpeed(100f);
        pManager = new BasicPassManager();
        TileSpaceManager.startup();
        skybox = EnvironmentManager.makeSkybox();
        fog = EnvironmentManager.makeFog();
        terrainNode.attachChild(skybox);
        terrainNode.setRenderState(lightState);
        terrainNode.setRenderState(fog);
        setupZBufferState();
        setupBackfaceCulling();
        makeWater();
       
        terrainNode.updateGeometricState(tpf, true);
        terrainNode.updateRenderState();
       
        terrainPass = new RenderPass();
        terrainPass.add(terrainNode);
        pManager.add(terrainPass);
        pManager.updatePasses(tpf);
    }

    @Override
    protected void reinit()
    {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }

    @Override
    protected void cleanup()
    {
        display.getRenderer().cleanup();
        KeyInput.destroyIfInitalized();
        MouseInput.destroyIfInitalized();
        JoystickInput.destroyIfInitalized();
    }
   
    private void setupCamera()
    {
        cam.setFrustumPerspective(45.0f, (float)display.getWidth()/(float)display.getHeight(), 1, 1000);
        Vector3f loc = new Vector3f(100f, 0f, 100f);
        Vector3f left = new Vector3f(-1f, 0f, 0f);
        Vector3f up = new Vector3f(0f, 1f, 0f);
        Vector3f dir = new Vector3f(0f, 0f, -1f);
        cam.setFrame(loc, left, up, dir);
        cam.update();
    }
   
    private void createWindow()
    {
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
       
        display = DisplaySystem.getDisplaySystem(properties.getRenderer());
        display.createWindow(width, height, depth, freq, fullscreen);
        display.getRenderer().setBackgroundColor(ColorRGBA.blue);
       
        display.setTitle("Fyrestone: Developer's Edition 0.00");
        display.getRenderer().enableStatistics(true);
    }
   
    private void createKeyBindings()
    {
        KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);
    }
   
    private void checkInput()
    {
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit", false))
        {
            System.out.println("Pushed escape!");
            finished = true;
        }
    }
   
    private void makeWater()
    {
        waterPass = new WaterRenderPass(cam, 6, false, true);
        waterPass.setWaterPlane(new Plane(new Vector3f(0f, 1f, 0f), 1f));
        waterPass.setClipBias(-1f);
        waterPass.setReflectionThrottle(0f);
        waterPass.setRefractionThrottle(0f);
       
        waterQuad = new Quad("myWater", 1, 1);
        FloatBuffer normBuf = waterQuad.getNormalBuffer(0);
        normBuf.clear();
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
       
        waterPass.setWaterEffectOnSpatial(waterQuad);
       
        terrainNode.attachChild(waterQuad);
       
        waterPass.setReflectedScene(terrainNode);
        waterPass.setReflectedScene(skybox);
        waterPass.setSkybox(skybox);
       
        pManager.add(waterPass);
    }
   
    private void setupZBufferState()
    {
        ZBufferState buf = display.getRenderer().createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.CF_LEQUAL);
        terrainNode.setRenderState(buf);
    }
   
    private void setupBackfaceCulling()
    {
        CullState cs = display.getRenderer().createCullState();
        cs.setEnabled(true);
        cs.setCullMode(CullState.CS_BACK);
        terrainNode.setRenderState(cs);
    }
   
    private void updateTiles()
    {
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        ArrayList<TileSpace> mustStayLoaded = TileSpaceManager.getTileSpace(tile_x, tile_y).getNeighbors();
        mustStayLoaded.add(TileSpaceManager.getTileSpace(tile_x, tile_y));
       
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] != null)
            {
                boolean kill = true;
                for(TileSpace ts : mustStayLoaded)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                        kill = false;
                }
               
                if(kill)
                {
                    System.out.println("************************* Killing page at " + loadedTiles[i].x + " " + loadedTiles[i].y + " *************************");
                    loadedTiles[i] = null;
                    tiles[i].removeFromParent();
                    tiles[i] = null;
                }
            }
        }
       
        for(TileSpace ts : mustStayLoaded)
        {
            boolean present = false;
            for(int i=0; i<loadedTiles.length; i++)
            {
                if(loadedTiles[i] != null)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                        present = true;
                }
            }
            if(!present)
            {
                int x = getFirstNullPage();
                loadedTiles[x] = TileSpaceManager.getTileSpace(ts.x, ts.y);
                tiles[x] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(ts.x, ts.y), ts.x, ts.y);
                terrainNode.attachChild(tiles[x]);
                terrainNode.updateRenderState();
            }
        }
    }
   
        private int getFirstNullPage()
    {
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] == null)
                return i;
        }
        return 0;
    }
   
   
    private void setVertexCoords(float x, float y, float z)
    {
        FloatBuffer vertBuf = waterQuad.getVertexBuffer(0);
        vertBuf.clear();

        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
    }

    private void setTextureCoords(int buffer, float x, float y, float textureScale)
    {
        x *= textureScale * 0.5f;
        y *= textureScale * 0.5f;
        textureScale = EnvironmentManager.FOGEND * textureScale;
        FloatBuffer texBuf;
        texBuf = waterQuad.getTextureBuffer(0, buffer);
        texBuf.clear();
        texBuf.put(x).put(textureScale + y);
        texBuf.put(x).put(y);
        texBuf.put(textureScale + x).put(y);
        texBuf.put(textureScale + x).put(textureScale + y);
    }
   
    private void createCharacter()
    {
        character = new Box("character", new Vector3f(), 1f, 1f, 1f);
        character.setModelBound(new BoundingBox());
        character.updateModelBound();
        charNode.attachChild(character);
        terrainNode.attachChild(charNode);
        charNode.updateWorldBound();
        charNode.setLocalTranslation(new Vector3f(1000f,150f,1000f));
    }
   
    private void setupChaser()
    {
        Vector3f targetOffset = new Vector3f();
        targetOffset.y = ((BoundingBox)charNode.getWorldBound()).yExtent * 1.5f;
        HashMap props = new HashMap();
        props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
        props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
        props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD);
        props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD));
        props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        chaser = new ChaseCamera(cam, charNode, props);
        chaser.setMaxDistance(8);
        chaser.setMinDistance(2);
    }
   
    private void updateChaser()
    {
        chaser.update(tpf);
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        if(cam.getLocation().y < (TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation())))
        {
            cam.getLocation().y = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation());
            cam.update();
        }
        myLoc = charNode.getLocalTranslation();
        float characterMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(charNode.getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight))
        {
            charNode.getLocalTranslation().y = characterMinHeight;
        }

    }
}



There is the entire code, and here are the parts I think that matter:

    private void updateChaser()
    {
        chaser.update(tpf);
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.TERRAINSCALE * TileSpaceManager.MAPSIZE);
        if(cam.getLocation().y < (TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation())))
        {
            cam.getLocation().y = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(cam.getLocation());
            cam.update();
        }
        myLoc = charNode.getLocalTranslation();
        float characterMinHeight = TileSpaceManager.makeTerrainPage(tile_x, tile_y).getHeight(charNode.getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight))
        {
            charNode.getLocalTranslation().y = characterMinHeight;
        }

    }



   
    private void setupChaser()
    {
        Vector3f targetOffset = new Vector3f();
        targetOffset.y = ((BoundingBox)charNode.getWorldBound()).yExtent * 1.5f;
        HashMap props = new HashMap();
        props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
        props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
        props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD);
        props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD));
        props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        chaser = new ChaseCamera(cam, charNode, props);
        chaser.setMaxDistance(8);
        chaser.setMinDistance(2);
    }



    protected void update(float interpolation)
    {
        timer.update();
        tpf = timer.getTimePerFrame();
        updateChaser();
        input.update(tpf);
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
        checkInput();
       
        Vector3f myLoc = cam.getLocation();
        skybox.setLocalTranslation(myLoc);
        updateTiles();
        Vector3f transVec = new Vector3f(cam.getLocation().x, waterPass.getWaterHeight(), cam.getLocation().z);
        setTextureCoords(0, transVec.y, -transVec.z, .07f);
        setVertexCoords(transVec.x, transVec.y, transVec.z);
       
        terrainNode.updateGeometricState(tpf, true);
        pManager.updatePasses(tpf);
    }

Okay. I decided to change the height things (where the camera and char node are locked to the earth) to my own code, and when I did I included a lot of debugging stuff and I've narrowed it down to this. The character node never moves–it's always in place. The camera, for some reason, changes it's position radically (like in the hundreds-of-thousands of coordinate units) right after the chaser.update(tpf);



Does this method really do anything that would cause the problem? I believe I am setting my tpf correctly (see above code) and it doesn't seem like the chaser.update should ever move the camera to anywhere except right behind the character node.



Is it bad that the character node is attached to the terrain node? I'll try separating them now…



EDIT: Separated the nods, but still getting the same error… here's some output for you:


Cam loc pre-chaser-update: com.jme.math.Vector3f [X=108.28139, Y=25.644878, Z=108.28139]
update chaser
cam loc: com.jme.math.Vector3f [X=406820.06, Y=30708.787, Z=406820.06]
char loc: com.jme.math.Vector3f [X=1000.0, Y=50.0, Z=1000.0]

I'd definitely say it's your code fighting over a Vector3f reference.  Here is an example of what could be a problem - though in this case it is probably not…


        Vector3f myLoc = cam.getLocation();
        skybox.setLocalTranslation(myLoc);



You're getting a Vector3f reference from your camera, and passing that reference into a skybox.  Now both of them control a reference to the same Vector3f, so if you change the location on the cam it will change the skybox as well...

I would look through your code very carefully and make sure that you use clone for all the Vector3f objects that have similar situations.

When I strip out all the parts not included in your code (accessing Tiles mostly) and get it to run, it runs smoothly.  I notice though that you have code like this:


if(cam.getLocation().y < ([b]TileSpaceManager.makeTerrainPage[/b](tile_x, tile_y).getHeight(cam.getLocation())))



I do not know how your TileSpaceManager works, but I would be concerned about running a method called makeTerrainPage every frame...  Especially since you run that method more than once in your updateChaser (you should at least run it once and reuse the result.)

My guess is that your stability issues are due to incredibly low fps because of problems like above.  Try printing out your tpf.  If it is greater than about 0.2  (which is only 5fps) then you will likely get instability issues with the spring calculation used in the chase camera.
renanse said:

When I strip out all the parts not included in your code (accessing Tiles mostly) and get it to run, it runs smoothly.  I notice though that you have code like this:

if(cam.getLocation().y < ([b]TileSpaceManager.makeTerrainPage[/b](tile_x, tile_y).getHeight(cam.getLocation())))



I do not know how your TileSpaceManager works, but I would be concerned about running a method called makeTerrainPage every frame...  Especially since you run that method more than once in your updateChaser (you should at least run it once and reuse the result.)

My guess is that your stability issues are due to incredibly low fps because of problems like above.  Try printing out your tpf.  If it is greater than about 0.2  (which is only 5fps) then you will likely get instability issues with the spring calculation used in the chase camera.

I've modified the method to update a TileSpace variable and keep track of it's location in the array. It creates it at startup and it should not bog down anything with startup, yet it still doesn't run.  I monitored my TPF, and it's quite high, 1.2 at startup, and then it gets to .6 and then crashes. However, when I remove the updateChaser() method, it goes from 1.2 to .6 to .4 to .3 to .2 and all the way down until it's .007. Is this common?

EDIT: I added a line of code to only update the chaser if the TPF was below .2, and then I was able to watch it startup, see the cube, then the camera started going nuts again and disappeared off the land and crashed my TileLoader.

Here's my modified code:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package fyrestonev01;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.ThirdPersonHandler;
import com.jme.input.joystick.JoystickInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Plane;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.Node;
import com.jme.scene.PassNode;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.Timer;
import com.jmex.effects.water.WaterRenderPass;
import com.jmex.terrain.TerrainPage;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Tyler
 */
public class FyrestoneClient extends BaseGame
{
   
    //Camera
    private Camera cam;
    //End Camera
   
    //Character
    private Node charNode;
    private Box character;
    private ChaseCamera chaser;
    private TileSpace myTileSpace;
    private int myTileSpaceNumber;
    //End character
   
    //Terrain
    private Node terrainNode;
    private Skybox skybox;
    private LightState lightState;
    private DirectionalLight light;
    private FogState fog;
    private TileSpace[] loadedTiles = new TileSpace[9];
    private PassNode[] tiles = new PassNode[9];
    //End Terrain
   
    //Statistics
    private Timer timer;
    private float tpf;
    //End statistics
   
    //Pass Management
    private BasicPassManager pManager;
    private RenderPass terrainPass;
    private WaterRenderPass waterPass;
    private Quad waterQuad;
    //End Pass Management
   
    //Input
    private InputHandler input;
    //End input
   
    //Display Settings
    private int width, height, depth, freq;
    private boolean fullscreen;
    //End display settings
   
    public static void main(String args[])
    {
        FyrestoneClient app = new FyrestoneClient();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    @Override
    protected void update(float interpolation)
    {
        timer.update();
        tpf = timer.getTimePerFrame();
        System.out.println(tpf);
        input.update(tpf);
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
        checkInput();
       
        Vector3f myLoc = cam.getLocation();
        skybox.setLocalTranslation(myLoc);
        updateTiles();
        //updateChaser();
        Vector3f transVec = new Vector3f(cam.getLocation().x, waterPass.getWaterHeight(), cam.getLocation().z);
        setTextureCoords(0, transVec.y, -transVec.z, .07f);
        setVertexCoords(transVec.x, transVec.y, transVec.z);
       
        terrainNode.updateGeometricState(tpf, true);
        pManager.updatePasses(tpf);
    }

    @Override
    protected void render(float interpolation)
    {
        display.getRenderer().clearStatistics();
        display.getRenderer().clearBuffers();
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
        pManager.renderPasses(display.getRenderer());
    }

    @Override
    protected void initSystem()
    {
        try {createWindow();}
        catch(JmeException e) {e.printStackTrace(); System.exit(1);}
       
        cam = display.getRenderer().createCamera(width, height);
        setupCamera();
        display.getRenderer().setCamera(cam);
        timer = Timer.getTimer();
    }

    @Override
    protected void initGame()
    {
        terrainNode = new Node("Terrain");
        charNode = new Node("Character");
        createCharacter();
       
        setupChaser();
        setupInput();
       
        pManager = new BasicPassManager();
       
        TileSpaceManager.startup();
       
        skybox = EnvironmentManager.makeSkybox();
        fog = EnvironmentManager.makeFog();
       
        terrainNode.attachChild(skybox);
        makeFirstTile();
        terrainNode.setRenderState(lightState);
        terrainNode.setRenderState(fog);
       
        setupZBufferState();
        setupBackfaceCulling();
       
        makeWater();
       
        terrainNode.updateGeometricState(tpf, true);
        terrainNode.updateRenderState();
        terrainPass = new RenderPass();
        terrainPass.add(terrainNode);
       
        pManager.add(terrainPass);
        pManager.updatePasses(tpf);
       
        createKeyBindings();
    }

    @Override
    protected void reinit()
    {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }

    @Override
    protected void cleanup()
    {
        display.getRenderer().cleanup();
        KeyInput.destroyIfInitalized();
        MouseInput.destroyIfInitalized();
        JoystickInput.destroyIfInitalized();
    }
   
    private void setupLighting()
    {
        lightState = display.getRenderer().createLightState();
        light = EnvironmentManager.makeLight();
        lightState.attach(light);
       
    }
   
    private void setupInput()
    {
        HashMap<String, Object> handlerProps = new HashMap<String, Object>();
        handlerProps.put(ThirdPersonHandler.PROP_DOGRADUAL, "true");
        handlerProps.put(ThirdPersonHandler.PROP_TURNSPEED, ""+(1.0f * FastMath.PI));
        handlerProps.put(ThirdPersonHandler.PROP_LOCKBACKWARDS, "false");
        handlerProps.put(ThirdPersonHandler.PROP_CAMERAALIGNEDMOVE, "true");
        input = new ThirdPersonHandler(charNode, cam, handlerProps);
        input.setActionSpeed(100f);
    }
   
    private void setupCamera()
    {
        cam.setFrustumPerspective(45.0f, (float)display.getWidth()/(float)display.getHeight(), 1, 1000);
        Vector3f loc = new Vector3f(100f, 0f, 100f);
        Vector3f left = new Vector3f(-1f, 0f, 0f);
        Vector3f up = new Vector3f(0f, 1f, 0f);
        Vector3f dir = new Vector3f(0f, 0f, -1f);
        cam.setFrame(loc, left, up, dir);
        cam.update();
    }
   
    private void updateChaser()
    {
        chaser.update(tpf);
        float camMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(cam.getLocation()) + 2f;
        if (!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight)
                && cam.getLocation().y <= camMinHeight) {
            cam.getLocation().y = camMinHeight;
            cam.update();
        }

        float characterMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(charNode
                .getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
            charNode.getLocalTranslation().y = characterMinHeight;
        }
    }
   
    private void createWindow()
    {
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
       
        display = DisplaySystem.getDisplaySystem(properties.getRenderer());
        display.createWindow(width, height, depth, freq, fullscreen);
        display.getRenderer().setBackgroundColor(ColorRGBA.blue);
       
        display.setTitle("Fyrestone: Developer's Edition 0.00");
        display.getRenderer().enableStatistics(true);
    }
   
    private void createKeyBindings()
    {
        KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);
    }
   
    private void checkInput()
    {
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit", false))
        {
            System.out.println("Pushed escape!");
            finished = true;
        }
    }
   
    private void makeWater()
    {
        waterPass = new WaterRenderPass(cam, 6, false, true);
        waterPass.setWaterPlane(new Plane(new Vector3f(0f, 1f, 0f), 1f));
        waterPass.setClipBias(-1f);
        waterPass.setReflectionThrottle(0f);
        waterPass.setRefractionThrottle(0f);
       
        waterQuad = new Quad("myWater", 1, 1);
        FloatBuffer normBuf = waterQuad.getNormalBuffer(0);
        normBuf.clear();
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
       
        waterPass.setWaterEffectOnSpatial(waterQuad);
       
        terrainNode.attachChild(waterQuad);
       
        waterPass.setReflectedScene(terrainNode);
        waterPass.setReflectedScene(skybox);
        waterPass.setSkybox(skybox);
       
        pManager.add(waterPass);
    }
   
    private void setupZBufferState()
    {
        ZBufferState buf = display.getRenderer().createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.CF_LEQUAL);
        terrainNode.setRenderState(buf);
    }
   
    private void setupBackfaceCulling()
    {
        CullState cs = display.getRenderer().createCullState();
        cs.setEnabled(true);
        cs.setCullMode(CullState.CS_BACK);
        terrainNode.setRenderState(cs);
    }
   
    private void makeFirstTile()
    {
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        myTileSpace = TileSpaceManager.getTileSpace(tile_x, tile_y);
        loadedTiles[0] = myTileSpace;
        tiles[0] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(tile_x, tile_y), tile_x, tile_y);
        myTileSpaceNumber = 0;
        terrainNode.attachChild(tiles[0]);
    }
   
    private void updateTiles()
    {
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        ArrayList<TileSpace> mustStayLoaded = TileSpaceManager.getTileSpace(tile_x, tile_y).getNeighbors();
        mustStayLoaded.add(myTileSpace);
       
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] != null)
            {
                boolean kill = true;
                for(TileSpace ts : mustStayLoaded)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                        kill = false;
                }
               
                if(kill)
                {
                    System.out.println("************************* Killing page at " + loadedTiles[i].x + " " + loadedTiles[i].y + " *************************");
                    loadedTiles[i] = null;
                    tiles[i].removeFromParent();
                    tiles[i] = null;
                }
            }
        }
       
        for(TileSpace ts : mustStayLoaded)
        {
            boolean present = false;
            for(int i=0; i<loadedTiles.length; i++)
            {
                if(loadedTiles[i] != null)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                    {
                        present = true;
                    }
                    if(loadedTiles[i] == myTileSpace)
                        myTileSpaceNumber = i;
                }
            }
            if(!present)
            {
                int x = getFirstNullPage();
                loadedTiles[x] = TileSpaceManager.getTileSpace(ts.x, ts.y);
                tiles[x] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(ts.x, ts.y), ts.x, ts.y);
                terrainNode.attachChild(tiles[x]);
                terrainNode.updateRenderState();
            }
        }
    }
   
        private int getFirstNullPage()
    {
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] == null)
                return i;
        }
        return 0;
    }
   
   
    private void setVertexCoords(float x, float y, float z)
    {
        FloatBuffer vertBuf = waterQuad.getVertexBuffer(0);
        vertBuf.clear();

        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
    }

    private void setTextureCoords(int buffer, float x, float y, float textureScale)
    {
        x *= textureScale * 0.5f;
        y *= textureScale * 0.5f;
        textureScale = EnvironmentManager.FOGEND * textureScale;
        FloatBuffer texBuf;
        texBuf = waterQuad.getTextureBuffer(0, buffer);
        texBuf.clear();
        texBuf.put(x).put(textureScale + y);
        texBuf.put(x).put(y);
        texBuf.put(textureScale + x).put(y);
        texBuf.put(textureScale + x).put(textureScale + y);
    }
   
    private void createCharacter()
    {
        character = new Box("character", new Vector3f(), 1f, 1f, 1f);
        character.setModelBound(new BoundingBox());
        character.updateModelBound();
        charNode.attachChild(character);
        charNode.updateWorldBound();
        charNode.setLocalTranslation(new Vector3f(1000f,50f,1000f));
    }
   
    private void setupChaser()
    {
        Vector3f targetOffset = new Vector3f();
        targetOffset.y = ((BoundingBox) charNode.getWorldBound()).yExtent * 1.5f;
        chaser = new ChaseCamera(cam, charNode);
        chaser.setTargetOffset(targetOffset);
    }
}



and here's the new update method(s), specifically:

    private void makeFirstTile()
    {
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        myTileSpace = TileSpaceManager.getTileSpace(tile_x, tile_y);
        loadedTiles[0] = myTileSpace;
        tiles[0] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(tile_x, tile_y), tile_x, tile_y);
        myTileSpaceNumber = 0;
        terrainNode.attachChild(tiles[0]);
    }



private void updateTiles()
    {
        Vector3f myLoc = cam.getLocation();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        ArrayList<TileSpace> mustStayLoaded = TileSpaceManager.getTileSpace(tile_x, tile_y).getNeighbors();
        mustStayLoaded.add(myTileSpace);
       
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] != null)
            {
                boolean kill = true;
                for(TileSpace ts : mustStayLoaded)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                        kill = false;
                }
               
                if(kill)
                {
                    System.out.println("************************* Killing page at " + loadedTiles[i].x + " " + loadedTiles[i].y + " *************************");
                    loadedTiles[i] = null;
                    tiles[i].removeFromParent();
                    tiles[i] = null;
                }
            }
        }
       
        for(TileSpace ts : mustStayLoaded)
        {
            boolean present = false;
            for(int i=0; i<loadedTiles.length; i++)
            {
                if(loadedTiles[i] != null)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                    {
                        present = true;
                    }
                    if(loadedTiles[i] == myTileSpace)
                        myTileSpaceNumber = i;
                }
            }
            if(!present)
            {
                int x = getFirstNullPage();
                loadedTiles[x] = TileSpaceManager.getTileSpace(ts.x, ts.y);
                tiles[x] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(ts.x, ts.y), ts.x, ts.y);
                terrainNode.attachChild(tiles[x]);
                terrainNode.updateRenderState();
            }
        }
    }



    private void updateChaser()
    {
        chaser.update(tpf);
        float camMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(cam.getLocation()) + 2f;
        if (!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight)
                && cam.getLocation().y <= camMinHeight) {
            cam.getLocation().y = camMinHeight;
            cam.update();
        }

        float characterMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(charNode
                .getLocalTranslation())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
            charNode.getLocalTranslation().y = characterMinHeight;
        }
    }

If I don't set the charNode's local translation, the game starts. However, the water is flashing purple and I can't move my character.



BTW, Unfair, I tried yours too, and it didn't work either. The only thing that allows the game to start is if I don't update my Chaser or if I don't set the local translation of my character, and you can't even call that 'working'.



EDIT: Water flashing purple was due to the fact that I finally realized I was only reflecting the skybox. When I add in the terrain to the reflection, it starts flashing purple. I took it out until it can be fixed later (any suggestions on that are welcome, as well).

Trussell said:

I've modified the method to update a TileSpace variable and keep track of it's location in the array. It creates it at startup and it should not bog down anything with startup, yet it still doesn't run.  I monitored my TPF, and it's quite high, 1.2 at startup, and then it gets to .6 and then crashes. However, when I remove the updateChaser() method, it goes from 1.2 to .6 to .4 to .3 to .2 and all the way down until it's .007. Is this common?


Yeah, I'm not surprised that leaving the update in there is bogging it down.  Try replacing it with something simpler (iow, removing the creation code for tiles).  .007 is about what I'd expect given your scene...  (that's about 150 FPS):

    private void updateChaser()
    {
        chaser.update(tpf);
        float camMinHeight = 0;
        if (!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight)
                && cam.getLocation().y <= camMinHeight) {
            cam.getLocation().y = camMinHeight;
            cam.update();
        }

        float characterMinHeight = 0;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
            charNode.getLocalTranslation().y = characterMinHeight;
        }
    }

But what I was saying was that it's NOT bogging it down. It does the same thing every time, starting at 1.2 and getting down to really low. The only thing that makes it load up properly is if I don't set the local translation of the cube, but then my controls don't work. What's up with that?

Well, I’ve found out why the controls weren’t working. I wasn’t updating the geometric state of my node, so I was moving around and nothing was telling the computer to update where the guy was.



Now I’ve got another problem, stemming from I don’t know what. Everything works correctly-ish (the camera sometimes goes below the ground, but that’s not that big of a deal yet)



Either the program looks like this: Bad

Or the program looks like this: Worse



EDIT: Thought I’d include my source code, I may have missed something stupid while working with the camera and/or water.


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package fyrestonev01;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.ThirdPersonHandler;
import com.jme.input.joystick.JoystickInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Plane;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.Node;
import com.jme.scene.PassNode;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.Timer;
import com.jmex.effects.water.WaterRenderPass;
import com.jmex.terrain.TerrainPage;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Tyler
 */
public class FyrestoneClient extends BaseGame
{
   
    //Camera
    private Camera cam;
    //End Camera
   
    //Character
    private Node charNode;
    private Box character;
    private ChaseCamera chaser;
    private TileSpace myTileSpace;
    private int myTileSpaceNumber;
    private RenderPass charPass;
    //End character
   
    //Terrain
    private Node terrainNode;
    private Skybox skybox;
    private LightState lightState;
    private DirectionalLight light;
    private FogState fog;
    private TileSpace[] loadedTiles = new TileSpace[9];
    private PassNode[] tiles = new PassNode[9];
    //End Terrain
   
    //Statistics
    private Timer timer;
    private float tpf;
    //End statistics
   
    //Pass Management
    private BasicPassManager pManager;
    private RenderPass terrainPass;
    private WaterRenderPass waterPass;
    private Quad waterQuad;
    //End Pass Management
   
    //Input
    private InputHandler input;
    //End input
   
    //Display Settings
    private int width, height, depth, freq;
    private boolean fullscreen;
    //End display settings
   
    public static void main(String args[])
    {
        FyrestoneClient app = new FyrestoneClient();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    @Override
    protected void update(float interpolation)
    {
        timer.update();
        tpf = timer.getTimePerFrame();
        System.out.println(tpf);
        input.update(tpf);
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
        checkInput();
       
        Vector3f myLoc = cam.getLocation().clone();
        skybox.setLocalTranslation(myLoc);
        updateTiles();
        updateChaser();
        Vector3f transVec = new Vector3f(cam.getLocation().x, waterPass.getWaterHeight(), cam.getLocation().z);
        setTextureCoords(0, transVec.y, -transVec.z, .07f);
        setVertexCoords(transVec.x, transVec.y, transVec.z);
       
        charNode.updateGeometricState(tpf, true);
        charNode.updateRenderState();
        terrainNode.updateGeometricState(tpf, true);
        terrainNode.updateRenderState();
        pManager.updatePasses(tpf);
    }

    @Override
    protected void render(float interpolation)
    {
        display.getRenderer().clearStatistics();
        display.getRenderer().clearBuffers();
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
        pManager.renderPasses(display.getRenderer());
    }

    @Override
    protected void initSystem()
    {
        try {createWindow();}
        catch(JmeException e) {e.printStackTrace(); System.exit(1);}
       
        cam = display.getRenderer().createCamera(width, height);
        setupCamera();
        display.getRenderer().setCamera(cam);
        timer = Timer.getTimer();
    }

    @Override
    protected void initGame()
    {
        terrainNode = new Node("Terrain");
        charNode = new Node("Character");
        createCharacter();
       
        setupChaser();
        setupInput();
       
        pManager = new BasicPassManager();
       
        TileSpaceManager.startup();
       
        skybox = EnvironmentManager.makeSkybox();
        fog = EnvironmentManager.makeFog();
       
        terrainNode.attachChild(skybox);
        makeFirstTile();
        terrainNode.setRenderState(lightState);
        terrainNode.setRenderState(fog);
       
        setupZBufferState();
        setupBackfaceCulling();
       
        makeWater();
       
        charNode.updateGeometricState(tpf, true);
        charNode.updateRenderState();
        charPass = new RenderPass();
        charPass.add(charNode);
        terrainNode.updateGeometricState(tpf, true);
        terrainNode.updateRenderState();
        terrainPass = new RenderPass();
        terrainPass.add(terrainNode);
       
        pManager.add(terrainPass);
        pManager.add(charPass);
        pManager.updatePasses(tpf);
       
        createKeyBindings();
    }

    @Override
    protected void reinit()
    {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }

    @Override
    protected void cleanup()
    {
        display.getRenderer().cleanup();
        KeyInput.destroyIfInitalized();
        MouseInput.destroyIfInitalized();
        JoystickInput.destroyIfInitalized();
    }
   
    private void setupLighting()
    {
        lightState = display.getRenderer().createLightState();
        light = EnvironmentManager.makeLight();
        lightState.attach(light);
        lightState.setEnabled(true);
    }
   
    private void setupInput()
    {
        HashMap<String, Object> handlerProps = new HashMap<String, Object>();
        handlerProps.put(ThirdPersonHandler.PROP_DOGRADUAL, "true");
        handlerProps.put(ThirdPersonHandler.PROP_TURNSPEED, ""+(1.0f * FastMath.PI));
        handlerProps.put(ThirdPersonHandler.PROP_LOCKBACKWARDS, "false");
        handlerProps.put(ThirdPersonHandler.PROP_CAMERAALIGNEDMOVE, "true");
        input = new ThirdPersonHandler(charNode, cam, handlerProps);
        input.setActionSpeed(100f);
    }
   
    private void setupCamera()
    {
        cam.setFrustumPerspective(45.0f, (float)display.getWidth()/(float)display.getHeight(), 1, 1000);
        Vector3f loc = new Vector3f(100f, 0f, 100f);
        Vector3f left = new Vector3f(-1f, 0f, 0f);
        Vector3f up = new Vector3f(0f, 1f, 0f);
        Vector3f dir = new Vector3f(0f, 0f, -1f);
        cam.setFrame(loc, left, up, dir);
        cam.update();
    }
   
    private void updateChaser()
    {
        chaser.update(tpf);
        float camMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(cam.getLocation().clone()) + 2f;
        if (!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight)
                && cam.getLocation().y <= camMinHeight) {
            cam.getLocation().y = camMinHeight;
            cam.update();
        }

        float characterMinHeight = ((TerrainPage)tiles[myTileSpaceNumber].getChild(0)).getHeight(charNode
                .getLocalTranslation().clone())+((BoundingBox)charNode.getWorldBound()).yExtent;
        if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
            charNode.getLocalTranslation().y = characterMinHeight;
        }
    }
   
    private void createWindow()
    {
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
       
        display = DisplaySystem.getDisplaySystem(properties.getRenderer());
        display.createWindow(width, height, depth, freq, fullscreen);
        display.getRenderer().setBackgroundColor(ColorRGBA.blue);
       
        display.setTitle("Fyrestone: Developer's Edition 0.01");
        display.getRenderer().enableStatistics(true);
    }
   
    private void createKeyBindings()
    {
        KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);
    }
   
    private void checkInput()
    {
        if(KeyBindingManager.getKeyBindingManager().isValidCommand("exit", false))
        {
            System.out.println("Pushed escape!");
            finished = true;
        }
    }
   
    private void makeWater()
    {
        waterPass = new WaterRenderPass(cam, 6, false, true);
        waterPass.setWaterPlane(new Plane(new Vector3f(0f, 1f, 0f), 1f));
        waterPass.setClipBias(-1f);
        waterPass.setReflectionThrottle(0f);
        waterPass.setRefractionThrottle(0f);
       
        waterQuad = new Quad("myWater", 1, 1);
        FloatBuffer normBuf = waterQuad.getNormalBuffer(0);
        normBuf.clear();
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
        normBuf.put(0).put(1).put(0);
       
        terrainNode.attachChild(waterQuad);
        waterPass.setWaterEffectOnSpatial(waterQuad);
        waterPass.setReflectedScene(skybox);
        waterPass.setSkybox(skybox);
       
        pManager.add(waterPass);
    }
   
    private void setupZBufferState()
    {
        ZBufferState buf = display.getRenderer().createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.CF_LEQUAL);
        terrainNode.setRenderState(buf);
    }
   
    private void setupBackfaceCulling()
    {
        CullState cs = display.getRenderer().createCullState();
        cs.setEnabled(true);
        cs.setCullMode(CullState.CS_BACK);
        terrainNode.setRenderState(cs);
    }
   
    private void makeFirstTile()
    {
        Vector3f myLoc = cam.getLocation().clone();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        myTileSpace = TileSpaceManager.getTileSpace(tile_x, tile_y);
        loadedTiles[0] = myTileSpace;
        tiles[0] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(tile_x, tile_y), tile_x, tile_y);
        myTileSpaceNumber = 0;
        terrainNode.attachChild(tiles[0]);
    }
   
    private void updateTiles()
    {
        Vector3f myLoc = cam.getLocation().clone();
        int tile_x = (int)myLoc.x / (TileSpaceManager.MAPSCALE);
        int tile_y = (int)myLoc.z / (TileSpaceManager.MAPSCALE);
        ArrayList<TileSpace> mustStayLoaded = TileSpaceManager.getTileSpace(tile_x, tile_y).getNeighbors();
        mustStayLoaded.add(myTileSpace);
       
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] != null)
            {
                boolean kill = true;
                for(TileSpace ts : mustStayLoaded)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                        kill = false;
                }
               
                if(kill)
                {
                    System.out.println("************************* Killing page at " + loadedTiles[i].x + " " + loadedTiles[i].y + " *************************");
                    loadedTiles[i] = null;
                    tiles[i].removeFromParent();
                    tiles[i] = null;
                }
            }
        }
       
        for(TileSpace ts : mustStayLoaded)
        {
            boolean present = false;
            for(int i=0; i<loadedTiles.length; i++)
            {
                if(loadedTiles[i] != null)
                {
                    if(loadedTiles[i].x == ts.x && loadedTiles[i].y == ts.y)
                    {
                        present = true;
                    }
                    if(loadedTiles[i] == myTileSpace)
                        myTileSpaceNumber = i;
                }
            }
            if(!present)
            {
                int x = getFirstNullPage();
                loadedTiles[x] = TileSpaceManager.getTileSpace(ts.x, ts.y);
                tiles[x] = TileSpaceManager.makePassNode(TileSpaceManager.makeTerrainPage(ts.x, ts.y), ts.x, ts.y);
                terrainNode.attachChild(tiles[x]);
                terrainNode.updateRenderState();
            }
        }
    }
   
        private int getFirstNullPage()
    {
        for(int i=0; i<loadedTiles.length; i++)
        {
            if(loadedTiles[i] == null)
                return i;
        }
        return 0;
    }
   
   
    private void setVertexCoords(float x, float y, float z)
    {
        FloatBuffer vertBuf = waterQuad.getVertexBuffer(0);
        vertBuf.clear();

        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
        vertBuf.put(x - EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z + EnvironmentManager.FOGEND);
        vertBuf.put(x + EnvironmentManager.FOGEND).put(y).put(z - EnvironmentManager.FOGEND);
    }

    private void setTextureCoords(int buffer, float x, float y, float textureScale)
    {
        x *= textureScale * 0.5f;
        y *= textureScale * 0.5f;
        textureScale = EnvironmentManager.FOGEND * textureScale;
        FloatBuffer texBuf;
        texBuf = waterQuad.getTextureBuffer(0, buffer);
        texBuf.clear();
        texBuf.put(x).put(textureScale + y);
        texBuf.put(x).put(y);
        texBuf.put(textureScale + x).put(y);
        texBuf.put(textureScale + x).put(textureScale + y);
    }
   
    private void createCharacter()
    {
        character = new Box("character", new Vector3f(), 1f, 1f, 1f);
        character.setModelBound(new BoundingBox());
        character.updateModelBound();
        charNode.attachChild(character);
        charNode.updateWorldBound();
    }
   
    private void setupChaser()
    {
        Vector3f targetOffset = new Vector3f();
        targetOffset.y = ((BoundingBox) charNode.getWorldBound()).yExtent * 1.5f;
        chaser = new ChaseCamera(cam, charNode);
        chaser.setTargetOffset(targetOffset);
    }
}

Trussell said:

But what I was saying was that it's NOT bogging it down. It does the same thing every time, starting at 1.2 and getting down to really low. The only thing that makes it load up properly is if I don't set the local translation of the cube, but then my controls don't work. What's up with that?


Actually you said when you included the update it only got down to .6 then crashed, and if you took it out it went down to .007.  But anyway, I see in your latest code you are at least not recreating the tile 2-3x every frame, so now life should be a lot better. :)
Trussell said:

Either the program looks like this: Bad
Or the program looks like this: Worse


The first link looks like lighting is on, but there is no lights added...  And you have fog enabled.
The second link looks like missing textures (any log messages saying so?)

No. No logs said that. I did have lighting enabled though. My thoughts are that I accidentally left some piece of code for the FirstPersonHandler on when I turned on the ThirdPersonHandler. I had stuff switched off in my code so I could work with water.



I'll be experimenting further in just a bit. At every point which we have a fully stable source code, I upload it to our repository. I'll just grab the last good version and outfit that with the ThirdPersonHandler and remove the setup of the location of the character and we'll see how that works.