jme2-adding_random_flags – Documentation Wiki

Adding Random Flags

Introduction

This tutorial, we are going to add the flags on the level. This will give us our “goal”, the object that we are going to try to obtain. In doing so, we will discuss the Cloth System that is in jME to render a realistic looking flag. We'll also make a few optimizations and a number of improvements to the animation of the bike to make it look better.

(NOTE: the model loading part of this tutorial is out of date with the latest development version of jME. An up to date version can be found here: http://www.student.cs.uwaterloo.ca/~eworosho/src/jmetest.flagrushtut.lesson8.Lesson8 , it is also available from CVS )

Optimizations

There are a couple optimizations we'll make this lesson. First, loading a 3DS model file, converting it to the jme format then loading it into the scene doesn't make much sense. We should just be loading a .jme file. So, first, I saved the converted file as bike.jme and changed the buildPlayer method to just load this file. This will speed up the start time a bit. To save as .jme I simply used the previous tutorial and wrote the converted buffer to file.

Node model = null;
try {
   URL bikeFile = Lesson8.class.getClassLoader().getResource("jmetest/data/model/bike.jme");
   JmeBinaryReader jbr = new JmeBinaryReader();
   jbr.setProperty("bound", "box");
   model = jbr.loadBinaryFormat(bikeFile.openStream());
   //scale it to be MUCH smaller than it is originally
   model.setLocalScale(.0025f);
} catch (IOException e) {
   e.printStackTrace();
}

While driving, I noticed a bit of a problem when I would drift to a stop (especially on lower frame rates). The bike would stop then seem to vibrate back and force. It was an issue with the drift alogrithm. It wasn't taking into account that if the time between frames was high enough, the bike could drift past zero. Thus, it could drift to a stop, then go backwards a bit, then forwards, etc. So, I added checks that if it went past zero, set it to zero.

        if(velocity < -FastMath.FLT_EPSILON) {
            velocity += ((weight/5) * time);
            //we are drifting to a stop, so we shouldn't go
            //above 0
            if(velocity > 0) {
                velocity = 0;
            }
        } else if(velocity > FastMath.FLT_EPSILON){
            velocity -= ((weight/5) * time);
            //we are drifting to a stop, so we shouldn't go
            //below 0
            if(velocity < 0) {
                velocity = 0;
            }
        }

I also wasn't very happy with the chase cam being above the bike, so I removed the target offset property from the buildChaseCam method. This will keep the camera lower, and make it easier to see things on the level.

That was it for optimizations this time around. Next, let's improve the game.

Leaning into Turns

I wasn't very happy with how the bike looked when we made tight turns, it was too rigid. To help make the bike look more fluid and like it belong, I want to add leaning so the bike appears to be leaning into a turn. The important concept to note here is I will be leaning the model node that Vehicle maintains, not Vehicle itself. This is because Vehicle is responsible for maintaining the bikes orientation on the terrain, the leaning is purely for visual effect.

First thing we will do is add a way for the vehicle to be told if we need to tilt. In VehicleRotateAction we add the following line to the end of the performAction method.

vehicle.setRotateOn(modifier);

This will tell the vehicle that we are turning left or right. And of course, we need to add the method to Vehicle.

public void setRotateOn(int modifier) {
        lean = modifier;
}

This will set a new class attribute to the modifier. Lean maintains what direction the bike is leaning in for a given frame.

Now that we know how the bike is leaning, we need to adjust the bike's orientation for the lean. If lean is 0, then we are not leaning. In this case, we see if the bike's leanAngle is something other than zero, if it is, we will slowly right the bike to stand upright. This gives the effect of making a turn and then bringing the bike up to the correct position. If lean is not 0, then we adjust the leanAngle by the lean value. We will lock it to -1 and 1 so the bike doesn't fall over. (We'll assume our driver knows how to ride a bike). We then set the rotation of the bike to the new leanAngle along the leanAxis (Vector3f(0,0,1)). It's important to set lean back to 0 so that we can right the bike next frame if the player stops the turn.

/**
* processlean will adjust the angle of the bike model based on 
* a lean factor. We angle the bike rather than the Vehicle, as the
* Vehicle is worried about position about the terrain.
* @param time the time between frames
*/
private void processLean(float time) {
        //check if we are leaning at all
        if(lean != 0) {
            if(lean == -1 && leanAngle < 0) {
                leanAngle += -lean * 4 * time;
            } else if(lean == 1 && leanAngle > 0) {
                leanAngle += -lean * 4 * time;
            } else {
                leanAngle += -lean * 2 * time;
            }
            //max lean is 1 and -1
            if(leanAngle > 1) {
                leanAngle = 1;
            } else if(leanAngle < -1) {
                leanAngle = -1;
            }
        } else { //we are not leaning, so right ourself back up.
            if(leanAngle < LEAN_BUFFER && leanAngle > -LEAN_BUFFER) {
                leanAngle = 0;
            }
            else if(leanAngle < -FastMath.FLT_EPSILON) {
                leanAngle += time * 4;
            } else if(leanAngle > FastMath.FLT_EPSILON) {
                leanAngle -= time * 4;
            } else {
                leanAngle = 0;
            }
        }
 
        q.fromAngleAxis(leanAngle, leanAxis);
        model.setLocalRotation(q);
 
        lean = 0;
}

Now, we need to call this method every frame to update the bike when needed. So, the update method is altered to include a call to the processLean.

processLean(time);

Simple as that, our bike now leans when turning! Really helps the visualization of the bike!

Rotate the Tires

Another visual cue of movement we want to add is the rotation of the tires. This is a subtle addition as the wheels don't have a lot of variation to show that they are rotating, but it is noticable for those paying close attention.

First, we need to get references to the part of the model that is the wheels. This simply requires you to have knowledge of the model structure. In the case of bike.jme the structure is set up like:

So, we need to obtain the Geometry backwheel and frontwheel. I happen to know that these are the children at index 1 and 5.

//obtain references to the front and back wheel
 
backwheel = ((Node)(((Node)model).getChild(0))).getChild(1);
frontwheel = ((Node)(((Node)model).getChild(0))).getChild(5);

Next, in Vehicle we'll add a method that handles rotating the tires. First, we determine if the Vehicle is moving by calling a new method vehicleIsMoving, if so, we alter the angle to rotate the tires by 1/2 of the velocity (scaled for the time).

/**
* rotateWheels will rotate the wheel (front and back) a certain angle based on
* the velocity of the bike.
* @param time the time between frames.
*/
private void rotateWheels(float time) {
        //Rotate the tires if the vehicle is moving.
        if (vehicleIsMoving()) {
            if(velocity > FastMath.FLT_EPSILON) {
                angle = angle - ((time) * velocity * 0.5f);
                if (angle < -360) {
                    angle += 360;
                }
            } else {
                angle = angle + ((time) * velocity * 0.5f);
                if (angle > 360) {
                    angle -= 360;
                }
            }
            rotQuat.fromAngleAxis(angle, wheelAxis);
            frontwheel.getLocalRotation().multLocal(rotQuat);
            backwheel.setLocalRotation(frontwheel.getLocalRotation());
        }
}

As mentioned above, the vehicleIsMoving method just determines if the velocity is close enough to zero to be considered stopped or not.

/**
* Convience method that determines if the vehicle is moving or not. This is
* given if the velocity is approximately zero, taking float point rounding
* errors into account.
* @return true if the vehicle is moving, false otherwise.
*/
public boolean vehicleIsMoving() {
        return velocity > FastMath.FLT_EPSILON
                || velocity < -FastMath.FLT_EPSILON;
}

We then simply call our new method in the update method for vehicle.

rotateWheels(time);

Add a Flag Object

Now, we'll add a flag. If you remember back the goal of this game is to collect a number of flags the fastest. Well, this will be that flag. The job of the flag is to handle it's own timer and position itself on the terrain randomly.

So, we'll create a new Flag class that extends Node.

package jmetest.flagrushtut.lesson8;
 
//imports removed for size
 
/**
 * Flag maintains the object that is the "goal" of the game. The
 * drivers are to try to grab the flags for points. The main job of 
 * the class is to build the flag geometry, and position itself randomly
 * within the level after a period of time.
 * @author Mark Powell
 *
 */
public class Flag extends Node{
    //10 second life time
    private static final int LIFE_TIME = 10;
    //start off with a full life time
    float countdown = LIFE_TIME;
    //reference to the level terrain for placement
    TerrainBlock tb;
 
    /**
     * Constructor builds the flag, taking the terrain as the parameter. This
     * is just the reference to the game's terrain object so that we can 
     * randomly place this flag on the level.
     * @param tb the terrain used to place the flag.
     */
    public Flag(TerrainBlock tb) {
        super("flag");
        this.tb = tb;
 
        //Create the flag pole
        Cylinder c = new Cylinder("pole", 10, 10, 0.5f, 50 );
        this.attachChild(c);
        Quaternion q = new Quaternion();
        //rotate the cylinder to be vertical
        q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0));
        c.setLocalRotation(q);
        c.setLocalTranslation(new Vector3f(-12.5f,-12.5f,0));
 
        this.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
        this.setLocalScale(0.25f);
 
    }
 
    /**
     * During the update, we decrement the time. When it reaches zero, we will
     * reset the flag.
     * @param time the time between frame.
     */
    public void update(float time) {
        countdown -= time;
 
        if(countdown <= 0) {
            reset();
        }
    }
 
    /**
     * reset sets the life time back to 10 seconds, and then randomly places the flag
     * on the terrain.
     *
     */
    public void reset() {
        countdown = LIFE_TIME;
        placeFlag();
    }
 
    /**
     * place flag picks a random point on the terrain and places the flag there. I
     * set the values to be between (45 and 175) which places it within the force field
     * level.
     *
     */
    public void placeFlag() {
        float x = 45 + FastMath.nextRandomFloat() * 130;
        float z = 45 + FastMath.nextRandomFloat() * 130;
        float y = tb.getHeight(x,z) + 7.5f;
        localTranslation.x = x;
        localTranslation.y = y;
        localTranslation.z = z;
 
    }
}

It's fairly straight forward. It takes the reference to our TerrainBlock class so that it can place itself on the level. It contains an update method to handle counting down to when it needs to reposition itself. And that is done via the placeFlag method. You'll notice in the place flag that the random value will be between 0-130 + 45 or 45-175. This is a number that I determined would put the flags inside the fence and make use of most of the area.

We use a simple cylinder to be the flag pole.

Now, to acutally use this Flag we modify Lesson8 to build the flag and update it. First add a new method buildFlag

/**
* we created a new Flag class, so we'll use it to add the flag to the world.
* This is the flag that we desire, the one to get.
*
*/
private void buildFlag() {
   //create the flag and place it
   flag = new Flag(tb);
   scene.attachChild(flag);
   flag.placeFlag();
}

This will create the new flag, attach it to our scene graph and then randomly place the flag.

Next we will add a call to buildFlag in initGame just under the buildTerrain call. We do it after buildTerrain because we use the terrain in Flag to place it.

//Add a flag randomly to the terrain
buildFlag();

We also need to update the flag each round to update the countdown. So add a call to the flag's update method in the Lesson8 update method:

//update the flag to make it flap in the wind
flag.update(interpolation);

With that, we now will have a flag pole randomly placed on the terrain, and moves every 10 seconds. Put, looking at a pole really isn't interesting, so lets add the flag part.

Add Cloth to the FlagPole

We want to make the flags look like… well… flags. To do so, we need to simulate a cloth flag attached to the pole. What better way to do this, than use the ClothPatch functionality of jME. This will allow us to build a matrix of spring points that are adjusted by various forces (gravity and wind). I've created my own wind force class for this tutorial, and we will discuss this below.

First, add the objects to the Flag class.

//the cloth that makes up the flag.
private ClothPatch cloth;
//parameters for the wind
private float windStrength = 15f;
private Vector3f windDirection = new Vector3f(0.8f, 0, 0.2f);
private SpringPointForce gravity, wind;

In the constructor of the Flag, we will build a ClothPatch. The cloth will be 25×25 matrix, giving it a fairly detailed cloth. The more points in the cloth, the slower it runs, so you'll need to balance the visual appearance of the flag and its affect on the game. 25×25 gives me an acceptable performance to appearance ratio. After creating the cloth, we will add our forces. The first force we add is the custom force we'll create for this tutorial, called RandomFlagWindForce. We will also add a default gravity force that is created by ClothUtils, this will help pull the flag down when the wind lets up a little bit.

//create a cloth patch that will handle the flag part of our flag.
cloth = new ClothPatch("cloth", 25, 25, 1f, 10);
// Add our custom flag wind force to the cloth
wind = new RandomFlagWindForce(windStrength, windDirection);
cloth.addForce(wind);
// Add a simple gravitational force:
gravity = ClothUtils.createBasicGravity();
cloth.addForce(gravity);

Since the cloth is a standard jME Spatial we apply RenderStates as normal. We'll add the texture for the flag, using the jME monkey logo. This is an attempt to have the monkey head seared into your brain.

//create a texture that the flag will display.
//Let's promote jME! 
TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
ts.setTexture(
            TextureManager.loadTexture(
            TestCloth.class.getClassLoader().getResource(
            "jmetest/data/images/Monkey.jpg"),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR));
cloth.setRenderState(ts);

When I started adding the flags to the scene I wasn't terribly happy with the lighting, because most of the flag was in shadow at any given time. Therefore, I wanted to add a light that only affected the flag. This light need to travel with the flag and position itself relative to the flag. Therefore, using a LightNode is the best solution. The LightNode allows you to treat lights as elements of the scene moving them with parent Nodes.

//We'll use a LightNode to give more lighting to the flag, we use the node because
//it will allow it to move with the flag as it hops around.
//first create the light
PointLight dr = new PointLight();
dr.setEnabled( true );
dr.setDiffuse( new ColorRGBA( 1.0f, 1.0f, 1.0f, 1.0f ) );
dr.setAmbient( new ColorRGBA( 0.5f, 0.5f, 0.5f, 1.0f ) );
dr.setLocation( new Vector3f( 0.5f, -0.5f, 0 ) );
//next the state
LightState lightState = DisplaySystem.getDisplaySystem().getRenderer().createLightState();
lightState.setEnabled(true);
lightState.setTwoSidedLighting( true );
//last the node
LightNode lightNode = new LightNode( "light", lightState );
lightNode.setLight( dr );
lightNode.setLocalTranslation(new Vector3f(15,10,0));
 
lightNode.setTarget( this );
 
this.attachChild(lightNode);

Another difference with the flag is we want to see both sides of the triangles. Previously, we set the CullState of the scene to cull backside triangles. Therefore, we will add another CullState to the cloth to not cull any triangles. It would look very odd to see the flag from only one side.

//We want to see both sides of the flag, so we will turn back facing culling OFF.
CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
cs.setCullMode(CullState.CS_NONE);
cloth.setRenderState(cs);
this.attachChild(cloth);

Next, we need to make a few points of the cloth be static. If we don't do this, the whole cloth system will “blow away” with the forces applied to it. We need to attach some of the points to the flag pole to hold it in place. We do this by setting the point's mass to infinity. Therefore, no force can move it. To simulate the way a flag is attached to a pole a few of the points at the top edge touching the pole will be attached, as well as a few at the bottom. The points are laid out in a basic two dimensional matrix, so we'll attach points (0, 1, 2, 3, 4) to the pole as well as points (500, 525, 550, 575, 600). We also adjust the y position of these points to help offset them to help bunch up the cloth a little bit.

//We need to attach a few points of the cloth to the poll. These points shouldn't
//ever move. So, we'll attach five points at the top and 5 at the bottom. 
//to make them not move the mass has to be high enough that no force can move it.
//I also move the position of these points slightly to help bunch up the flag to
//give it better realism.
for (int i = 0; i < 5; i++) {
            cloth.getSystem().getNode(i*25).position.y *= .8f;
            cloth.getSystem().getNode(i*25).setMass(Float.POSITIVE_INFINITY);
}
 
for (int i = 24; i > 19; i--) {
            cloth.getSystem().getNode(i*25).position.y *= .8f;
            cloth.getSystem().getNode(i*25).setMass(Float.POSITIVE_INFINITY);
}

Last, I create the custom wind force. To simulate the effects of a flag on a pole we will randomize the wind as well as the direction. The direction will adjust based on its previous position to let the flag wrap around the pole.

    private class RandomFlagWindForce extends SpringPointForce{
 
        private final float strength;
        private final Vector3f windDirection;
 
        /**
         * Creates a new force with a defined max strength and a starting direction.
         * @param strength the maximum strength of the wind.
         * @param direction the starting direction of the wind.
         */
        public RandomFlagWindForce(float strength, Vector3f direction) {
            this.strength = strength;
            this.windDirection = direction;
        }
 
        /**
         * called during the update of the cloth. Will adjust the direction slightly
         * and adjust the strength slightly.
         */
        public void apply(float dt, SpringPoint node) {
            windDirection.x += dt * (FastMath.nextRandomFloat() - 0.5f);
            windDirection.z += dt * (FastMath.nextRandomFloat() - 0.5f);
            windDirection.normalize();
            float tStr = FastMath.nextRandomFloat() * strength;
 
            node.acceleration.addLocal(windDirection.x * tStr, windDirection.y
                    * tStr, windDirection.z * tStr);
        }
    };

With that, we now have a realistic looking flag and we are on our way to having all the elements for our game. Next, we just need to be able to grab the flag.

SOURCE

package jmetest.flagrushtut.lesson8;
 
//imports removed for size
 
/**
 * Lesson 8 introduces a few visual enhancements and the goal of the game. The flag to grab.
 * Tilting of the bike to simulate leaning into turns is added, the wheels now rotate with the
 * speed of the bike and a flag is in making use of the jME cloth system.
 * @author Mark Powell
 */
public class Lesson8 extends BaseGame {
    // 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;
    //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;
 
    // 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;
 
    /**
     * Main entry point of the application
     */
    public static void main(String[] args) {
        Lesson8 app = new Lesson8();
        // We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG, Lesson8.class
                .getClassLoader().getResource(
                        "jmetest/data/images/FlagRush.png"));
        app.start();
    }
 
    /**
     * During an update we look for the escape button and update the timer
     * to get the framerate. Things are now starting to happen, so we will 
     * update 
     * 
     * @see com.jme.app.SimpleGame#update()
     */
    protected void update(float interpolation) {
        // 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());
 
        // 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.SimpleGame#render()
     */
    protected void render(float interpolation) {
        // Clear the screen
        display.getRenderer().clearBuffers();
        display.getRenderer().draw(scene);
    }
 
    /**
     * initializes the display and camera.
     * 
     * @see com.jme.app.SimpleGame#initSystem()
     */
    protected void initSystem() {
        // store the properties information
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
 
        try {
            display = DisplaySystem.getDisplaySystem(properties.getRenderer());
            display.createWindow(width, height, depth, freq, fullscreen);
 
            cam = display.getRenderer().createCamera(width, height);
        } catch (JmeException e) {
            e.printStackTrace();
            System.exit(1);
        }
 
        // set the background to black
        display.getRenderer().setBackgroundColor(ColorRGBA.black);
 
        // initialize the camera
        cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
                5000);
        cam.setLocation(new Vector3f(200,1000,200));
 
        /** Signal that we've changed our camera's location/frustum. */
        cam.update();
 
        /** Get a high resolution timer for FPS updates. */
        timer = Timer.getTimer(properties.getRenderer());
 
        display.getRenderer().setCamera(cam);
 
        KeyBindingManager.getKeyBindingManager().set("exit",
                KeyInput.KEY_ESCAPE);
    }
 
    /**
     * initializes the scene
     * 
     * @see com.jme.app.SimpleGame#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.CF_LEQUAL);
        scene.setRenderState(buf);
 
        //Time for a little optimization. We don't need to render back face triangles, so lets
        //not. This will give us a performance boost for very little effort.
        CullState cs = display.getRenderer().createCullState();
        cs.setCullMode(CullState.CS_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();
 
        // update the scene graph for rendering
        scene.updateGeometricState(0.0f, true);
        scene.updateRenderState();
    }
 
    /**
     * we created a new Flag class, so we'll use it to add the flag to the world.
     * This is the flag that we desire, the one to get.
     *
     */
    private void buildFlag() {
        //create the flag and place it
        flag = new Flag(tb);
        scene.attachChild(flag);
        flag.placeFlag();
    }
 
    /**
     * we are going to build the player object here. Now, we will load a .3ds model and convert it
     * to .jme in realtime. The next lesson will show how to store as .jme so this conversion doesn't
     * have to take place every time. 
     * 
     * We now have a Vehicle object that represents our player. The vehicle object will allow
     * us to have multiple vehicle types with different capabilities.
     *
     */
    private void buildPlayer() {
        Node model = null;
        try {
            URL bikeFile = Lesson8.class.getClassLoader().getResource("jmetest/data/model/bike.jme");
            JmeBinaryReader jbr = new JmeBinaryReader();
            jbr.setProperty("bound", "box");
            model = jbr.loadBinaryFormat(bikeFile.openStream());
            //scale it to be MUCH smaller than it is originally
            model.setLocalScale(.0025f);
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        //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.setLocalTranslation(new Vector3f(100,0, 100));
        scene.attachChild(player);
        scene.updateGeometricState(0, true);
        //we now store this initial value, because we are rotating the wheels the bounding box will
        //change each frame.
        agl = ((BoundingBox)player.getWorldBound()).yExtent;
        player.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
    }
 
    /**
     * buildEnvironment will create a fence. 
     */
    private void buildEnvironment() {
        //This is the main node of our fence
        fence = new ForceFieldFence("fence");
 
        //we will do a little 'tweaking' by hand to make it fit in the terrain a bit better.
        //first we'll scale the entire "model" by a factor of 5
        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, 1.0f));
        light.setDirection(new Vector3f(1,-1,0));
        light.setEnabled(true);
 
          /** Attach the light to a lightState and the lightState to rootNode. */
        LightState lightState = display.getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.attach(light);
        scene.setRenderState(lightState);
    }
 
    /**
     * build the height map and terrain block.
     */
    private void buildTerrain() {
 
 
        MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
        // Scale the data
        Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
        // create a terrainblock
         tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
                heightMap.getHeightMap(), new Vector3f(0, 0, 0), false);
 
        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
 
        // generate a terrain texture with 2 textures
        ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
                heightMap);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/highest.jpg")), 128, 255,
                384);
        pt.createTexture(32);
 
        // assign the texture to the terrain
        TextureState ts = display.getRenderer().createTextureState();
        Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
                Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, 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.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR);
 
        ts.setTexture(t2, 1);
        t2.setWrap(Texture.WM_WRAP_S_WRAP_T);
 
        t1.setApply(Texture.AM_COMBINE);
        t1.setCombineFuncRGB(Texture.ACF_MODULATE);
        t1.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t1.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineSrc1RGB(Texture.ACS_PRIMARY_COLOR);
        t1.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineScaleRGB(1.0f);
 
        t2.setApply(Texture.AM_COMBINE);
        t2.setCombineFuncRGB(Texture.ACF_ADD_SIGNED);
        t2.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t2.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineSrc1RGB(Texture.ACS_PREVIOUS);
        t2.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineScaleRGB(1.0f);
 
        tb.setRenderState(ts);
        //set the detail parameters.
        tb.setDetailTexture(1, 16);
        tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
        scene.attachChild(tb);
 
 
    }
 
    /**
     * buildSkyBox creates a new skybox object with all the proper textures. The
     * textures used are the standard skybox textures from all the tests.
     *
     */
    private void buildSkyBox() {
        skybox = new Skybox("skybox", 10, 10, 10);
 
        Texture north = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/north.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
        Texture south = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/south.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
        Texture east = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/east.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
        Texture west = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/west.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
        Texture up = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/top.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
        Texture down = TextureManager.loadTexture(
            TestSkybox.class.getClassLoader().getResource(
            "jmetest/data/texture/bottom.jpg"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR);
 
        skybox.setTexture(Skybox.NORTH, north);
        skybox.setTexture(Skybox.WEST, west);
        skybox.setTexture(Skybox.SOUTH, south);
        skybox.setTexture(Skybox.EAST, east);
        skybox.setTexture(Skybox.UP, up);
        skybox.setTexture(Skybox.DOWN, down);
        skybox.preloadTextures();
        scene.attachChild(skybox);
    }
 
    /**
     * set the basic parameters of the chase camera. This includes the offset. We want
     * to be behind the vehicle and a little above it. So we will the offset as 0 for
     * x and z, but be 1.5 times higher than the node.
     * 
     * We then set the roll out parameters (2 units is the closest the camera can get, and
     * 5 is the furthest).
     *
     */
    private void buildChaseCamera() {
        HashMap props = new HashMap();
        props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
        props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
        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_DAMPINGK, "4");
        props.put(ChaseCamera.PROP_SPRINGK, "9");
        chaser = new ChaseCamera(cam, player, props);
        chaser.setMaxDistance(8);
        chaser.setMinDistance(2);
    }
 
    /**
     * create our custom input handler.
     *
     */
    private void buildInput() {
        input = new FlagRushHandler(player, properties.getRenderer());
    }
 
 
 
    /**
     * will be called if the resolution changes
     * 
     * @see com.jme.app.SimpleGame#reinit()
     */
    protected void reinit() {
        display.recreateWindow(width, height, depth, freq, fullscreen);
    }
 
    /**
     * clean up the textures.
     * 
     * @see com.jme.app.SimpleGame#cleanup()
     */
    protected void cleanup() {
 
    }
}
/var/wwworg/wiki/data/pages/jme2-adding_random_flags.txt · Last modified: 2009/07/28 22:44 (external edit)