Road of the race game

Hi guys, I'm trying to make a race game and i was wondering how i build the roads of the game? With terrain? I was taking a look at the tutorials(http://www.jmonkeyengine.com/wiki/doku.php?id=the_tutorials) and the terrains are random and irregular. I want something like nascar of F1 with some high places to jump. What is used to control all this things?

Thanks.

Yes, you could use terrain - maybe have a look at MonkeyWorld3D which has a terrain editor. You might even consider altering your terrain programmatically if you have some more abstract description of your road and want to display it via terrain.

The problem I've been having with terrain is that it's very limiting.  For example, your terrain can only be rectangular.  Also, you can't do things like bridges and such because terrain lacks layers, although I guess you could layer multiple terrains on each other?  I would assume for something like a bridge you would need to tie an entire level together with a terrain to make that happen?



darkfrog

just go with a regular mesh built in max or maya…to minimize collision calculations a hybrid combo with both terrain and meshes is possible

OK, thanks. I

what behaviours that a terrain has do you want? collision detection?


what behaviours that a terrain has do you want? collision detection?

The height of my player, to keep him on the terrain. I am following the flag rush tutorial and there is used terrainBlock.getHeight() to do that. Is it possible to do something like that with my model?
Thanks.  :)

I guess it could be achieved with jme's picking, with a ray that shoots down from the player…have a look at TestOBBPick.java

there was a terrain follower that you could feed any trimesh to, but it was removed because it had  fuctionality that already existed in jme , I have asked several times for the part that allows for models to be treated as terrain to be reincluded or… something better :slight_smile: if they prefer. I have a working copy. it was plucked from the attic and used successful in another game project for brigdes and such


I guess it could be achieved with jme's picking, with a ray that shoots down from the player...have a look at TestOBBPick.java

This file has something about mouse on a model. Is that file?

there was a terrain follower that you could feed any trimesh to, but it was removed because it had  fuctionality that already existed in jme , I have asked several times for the part that allows for models to be treated as terrain to be reincluded or............ something better smiley if they prefer. I have a working copy. it was plucked from the attic and used successful in another game project for brigdes and such

Could you send me this? I stuck on this terrain problem.

I was trying to load a terrain made in MonkeyWorld3D but i don

here you go


package mgxutil;

import com.jme.scene.Spatial;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.math.Matrix3f;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.math.FastMath;
import mgxutil.MemPool;
import com.jme.util.LoggingSystem;
import com.jme.util.geom.*;
import java.util.logging.Level;

/** * Started Date: Aug 17, 2004<br><br> *
* This class takes either a trimesh or a node of trimesh objects and builds 2D
*float[][] of terrain Y values along the mesh's X/Z axis. These values are polled and
* an approximate Y value on the terrain is produced, which can be used for anything from
* updating an object's location to adjusting the camera. This class is not an all purpose terrain
 *follower. It assumes the terrain can't have more than one Y value for any X/Z. It
* also produces only approximates of distances above the terrain. Finally, it assumes the terrain
* is along the X/Z values with Y going up/down. If this class is used with a Node passed in
* its constructor, users should first update that Node and it's children's worldTranslations,rotation,scale
* before passing it to the TerrainFollower.<br> *
* @author Jack Lindamood */

public class TerrainFollower {

   private float minX;

   private float maxX;

   private float minZ;

   private float maxZ;

   private float deltaX;

   private float deltaZ;

   private float[][] terrainValues;

   private int xGridLen;

   private int zGridLen;

   private float gridDist;

   public TerrainFollower(Spatial terrain, int divisionsx, int divisionsy) {
      xGridLen = divisionsx;
      zGridLen = divisionsy;
      terrainValues = new float[divisionsx][];
      for (int i = 0; i < divisionsy; i++) {
         terrainValues[i] = new float[divisionsy];
         for (int j = 0; j < divisionsx; j++)
            terrainValues[i][j] = Float.NaN;
      }
      minX = minZ = Float.MAX_VALUE;
      maxX = maxZ = Float.MIN_VALUE;
      findLimits(terrain);
      deltaX = maxX - minX;
      deltaZ = maxZ - minZ;
      gridDist = (deltaX / xGridLen) * (deltaX / xGridLen)
            + (deltaZ / zGridLen) * (deltaZ / zGridLen);
      fillValues(terrain);

   }

   private void fillValues(Spatial terrain) {
      if (terrain instanceof Node) {
         Node parent = (Node) terrain;
         for (int i = parent.getQuantity() - 1; i >= 0; i--) {

            LoggingSystem.getLogger().log(Level.INFO,
                  "Begining to fill " + parent.getChild(i).getName());
            fillValues(parent.getChild(i));
         }
         return;
      }
      if (!(terrain instanceof TriMesh))
         return;
      Vector3f[] verts = BufferUtils.getVector3Array(((TriMesh)terrain).getVertexBuffer());
      int[] indexes = BufferUtils.getIntArray(((TriMesh)terrain).getIndexBuffer());

      Vector3f tri0 = MemPool.v3a, tri1 = MemPool.v3b, tri2 = MemPool.v3c;
      for (int tri = 0; tri < indexes.length; tri += 3) {

         terrain.getWorldRotation().mult(verts[indexes[tri + 0]], tri0)
               .multLocal(terrain.getWorldScale())

               .addLocal(terrain.getWorldTranslation());
         terrain.getWorldRotation().mult(verts[indexes[tri + 1]], tri1)
               .multLocal(terrain.getWorldScale())

               .addLocal(terrain.getWorldTranslation());
         terrain.getWorldRotation().mult(verts[indexes[tri + 2]], tri2)
               .multLocal(terrain.getWorldScale())

               .addLocal(terrain.getWorldTranslation());
         float maxX, minX, maxZ, minZ;
         maxX = tri0.x;
         if (tri1.x > maxX)
            maxX = tri1.x;
         if (tri2.x > maxX)
            maxX = tri2.x;

         maxZ = tri0.z;
         if (tri1.z > maxZ)
            maxZ = tri1.z;
         if (tri2.z > maxZ)
            maxZ = tri2.z;

         minX = tri0.x;
         if (tri1.x < minX)
            minX = tri1.x;
         if (tri2.x < minX)
            minX = tri2.x;

         minZ = tri0.z;
         if (tri1.z < minZ)
            minZ = tri1.z;
         if (tri2.z < minZ)
            minZ = tri2.z;

         fillForTriangle(tri0, tri1, tri2, smallestX(minX), smallestZ(minZ),
               biggestX(maxX), biggestZ(maxZ));
      }
   }

   private void fillForTriangle(Vector3f tri0, Vector3f tri1, Vector3f tri2,
         int x0, int z0, int x1, int z1) {
      float realX, realZ;
      if (x0 < 0)
         x0 = 0;
      if (z0 < 0)
         z0 = 0;
      if (x1 >= xGridLen)
         x1 = xGridLen - 1;
      if (z1 >= zGridLen)
         z1 = zGridLen - 1;
      MemPool.v2a.set(tri0.x, tri0.z);
      MemPool.v2b.set(tri1.x, tri1.z);
      MemPool.v2c.set(tri2.x, tri2.z);
      for (int x = x0; x < x1; x++) {
         realX = minX + (deltaX * x) / xGridLen;
         MemPool.v2d.x = realX;
         for (int z = z0; z < z1; z++) {
            realZ = minZ + (deltaZ * z) / zGridLen;
            MemPool.v2d.y = realZ;
            int i = FastMath.pointInsideTriangle(MemPool.v2a, MemPool.v2b,
                  MemPool.v2c, MemPool.v2d);
            if (i != 0) {
               float f = PlaneLineIntersection(tri0, tri1, tri2,

               MemPool.v3d.set(realX, 0, realZ), MemPool.v3e.set(realX, 1,
                     realZ));
               if (Float.isNaN(terrainValues[x][z])
                     || f > terrainValues[x][z])
                  terrainValues[x][z] = f;
            }
         }
      }
   }

   private float PlaneLineIntersection(Vector3f x1, Vector3f x2, Vector3f x3,
         Vector3f x4, Vector3f x5) {
      float denominator = FastMath.determinant(1, 1, 1, 0, x1.x, x2.x, x3.x,
            x5.x - x4.x, x1.y, x2.y, x3.y, x5.y - x4.y, x1.z, x2.z, x3.z,
            x5.z - x4.z);
      float numerator = FastMath.determinant(1, 1, 1, 1, x1.x, x2.x, x3.x,
            x4.x, x1.y, x2.y, x3.y, x4.y, x1.z, x2.z, x3.z, x4.z);
      float t = numerator / denominator;
      return x4.y + (x4.y - x5.y) * t;
   }

   private int smallestX(float gridValue) {
      return (int) FastMath.floor(((gridValue - minX) * xGridLen) / deltaX);
   }

   private int smallestZ(float gridValue) {
      return (int) FastMath.floor(((gridValue - minZ) * zGridLen) / deltaZ);
   }

   private int biggestX(float gridValue) {
      return (int) FastMath.ceil(((gridValue - minX) * xGridLen) / deltaX);
   }

   private int biggestZ(float gridValue) {
      return (int) FastMath.ceil(((gridValue - minZ) * zGridLen) / deltaZ);
   }

   private void findLimits(Spatial terrain) {
      if (terrain instanceof Node) {
         Node parent = (Node) terrain;
         for (int i = parent.getQuantity() - 1; i >= 0; i--) {
            findLimits(parent.getChild(i));
         }
         return;
      }
      if (!(terrain instanceof TriMesh))
         return;
      
      Vector3f[] verts = BufferUtils.getVector3Array(((TriMesh)terrain).getVertexBuffer());
      Vector3f tempRef = MemPool.v3a;
      for (int i = 0; i < verts.length; i++) {
         tempRef = terrain.getWorldRotation().mult(verts[i], MemPool.v3b)
               .multLocal(terrain.getWorldScale())

               .addLocal(terrain.getWorldTranslation());
         if (tempRef.x < minX)
            minX = tempRef.x;
         if (tempRef.x > maxX)
            maxX = tempRef.x;
         if (tempRef.z < minZ)
            minZ = tempRef.z;
         if (tempRef.z > maxZ)
            maxZ = tempRef.z;
      }
   }

   public float terrainPosition(float x, float z) {
      if (x > maxX || x < minX || z > maxZ || z < minZ)
         return Float.NaN;
      float xInGrid = realToGridX(x);
      float zInGrid = realToGridZ(z);

      int topX = (int) FastMath.ceil(xInGrid);
      if (topX >= xGridLen)
         return Float.NaN;
      float topXreal = gridToRealX(topX);

      int bottomX = (int) FastMath.floor(xInGrid);
      if (bottomX < 0)
         return Float.NaN;
      float bottomXreal = gridToRealX(bottomX);

      int topZ = (int) FastMath.ceil(zInGrid);
      if (topZ >= zGridLen)
         return Float.NaN;
      float topZreal = gridToRealZ(topZ);

      int bottomZ = (int) FastMath.floor(zInGrid);
      if (bottomZ < 0)
         return Float.NaN;
      float bottomZreal = gridToRealZ(bottomZ);

      float distSquare1 = gridDist - distSquare(x, z, topXreal, topZreal);
      float distSquare2 = gridDist - distSquare(x, z, topXreal, bottomZreal);
      float distSquare3 = gridDist - distSquare(x, z, bottomXreal, topZreal);
      float distSquare4 = gridDist
            - distSquare(x, z, bottomXreal, bottomZreal);
      float distSum = distSquare1 + distSquare2 + distSquare3 + distSquare4;
      return terrainValues[topX][topZ] * (distSquare1 / distSum) +

      terrainValues[topX][bottomZ] * (distSquare2 / distSum) +

      terrainValues[bottomX][topZ] * (distSquare3 / distSum) +

      terrainValues[bottomX][bottomZ] * (distSquare4 / distSum);
   }

   /**
    * Squared distance from X1,Y1 to X2,Y2
    *
    */
   private float distSquare(float x1, float y1, float x2, float y2) {
      return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
   }

   /**
    * Converts from grid to real world X coorindates.
    *
    */
   private float gridToRealX(float i) {
      return minX + (i / xGridLen) * deltaX;
   }

   /**
    * Converts from grid to real world Z coordinates.
    */
   private float gridToRealZ(float i) {
      return minZ + (i / zGridLen) * deltaZ;
   }

   /**
    * Converts from real world to grid Z coordinates.
    */
   private float realToGridZ(float f) {
      return ((f - minZ) * (zGridLen)) / deltaZ;
   }

   /**
    * Converts from real world to grid X cooridnates.
    */
   private float realToGridX(float f) {
      return ((f - minX) * (xGridLen)) / deltaX;
   }
}

class MemPool {

   private MemPool() {
   }

   public static Vector3f v3a = new Vector3f();

   public static Vector3f v3b = new Vector3f();

   public static Vector3f v3c = new Vector3f();

   public static Vector3f v3d = new Vector3f();

   public static Vector3f v3e = new Vector3f();

   public static Matrix3f m3a = new Matrix3f();

   public static Matrix3f m3b = new Matrix3f();

   public static Vector2f v2a = new Vector2f();

   public static Vector2f v2b = new Vector2f();

   public static Vector2f v2c = new Vector2f();

   public static Vector2f v2d = new Vector2f();
}



mcbeth, could you tell how it works? I


package game;

import mgxutil.TerrainFollower;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.scene.shape.Box;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;

/**
 * <code>TestLightState</code>
 * @author Mark Powell
 * @version $Id: TestBox.java,v 1.3 2005/09/19 23:11:16 renanse Exp $
 */
public class TestBox extends SimpleGame {

  /**
   * Entry point for the test,
   * @param args
   */
   
  TerrainFollower tf;
   
  public static void main(String[] args) {
    TestBox app = new TestBox();
    app.setDialogBehaviour(NEVER_SHOW_PROPS_DIALOG);
    app.start();

  }
 
  protected void simpleInitGame() {
    display.setTitle("trimesh terrain");
    lightState.setEnabled(false);
   
    Box floor = new Box("Floor", new Vector3f(), 1000, 1, 1000);
    floor.setModelBound(new BoundingBox());
    floor.updateModelBound();
    floor.getLocalTranslation().y = -20;
    TextureState ts = display.getRenderer().createTextureState();
    //Base texture, not environmental map.
    Texture t0 = TextureManager.loadTexture(
            TestBox.class.getClassLoader().getResource(
            "jmetest/data/images/Monkey.jpg"),
        Texture.MM_LINEAR_LINEAR,
        Texture.FM_LINEAR);
    t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
    ts.setTexture(t0);
    floor.setRenderState(ts);
    tf=new TerrainFollower(floor, 1000, 1000);
  
    floor.getTextureBuffer().put(16*2, 0).put(16*2+1, 5);
    floor.getTextureBuffer().put(17*2, 0).put(17*2+1, 0);
    floor.getTextureBuffer().put(18*2, 5).put(18*2+1, 0);
    floor.getTextureBuffer().put(19*2, 5).put(19*2+1, 5);
  
    rootNode.attachChild(floor);

  }
  protected void simpleUpdate(){
      Vector3f camLoc=display.getRenderer().getCamera().getLocation();
      float f=tf.terrainPosition(camLoc.x,camLoc.z);
      if (!Float.isNaN(f)){
          camLoc.y = f+9;
      }
  }
}




using this on a terrainpage I have to add an updategeom....state calll to my main update method

here is also the official test

package game;

import javax.swing.ImageIcon;

import jmetest.terrain.TestTerrain;

import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.FaultFractalHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import mgxutil.*;

public class TestTerrainFollower extends SimpleGame {

   TerrainFollower tf;

     public static void main(String[] args) {
       TestTerrainFollower app = new TestTerrainFollower();
       app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
       app.start();
     }

     protected void simpleInitGame() {
         rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
         fpsNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

       DirectionalLight dl = new DirectionalLight();
       dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
       dl.setDirection(new Vector3f(1, -0.5f, 1));
       dl.setEnabled(true);
       lightState.attach(dl);

       cam.setFrustum(1.0f, 1000.0f, -0.55f, 0.55f, 0.4125f, -0.4125f);
       cam.update();

       input.setKeySpeed(250f);
       input.setMouseSpeed(1f);
       display.setTitle("Terrain Test");
       display.getRenderer().setBackgroundColor(new ColorRGBA(0.5f,0.5f,0.5f,1));

       DirectionalLight dr = new DirectionalLight();
       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.setDirection(new Vector3f(0.5f, -0.5f, 0));

       CullState cs = display.getRenderer().createCullState();
       cs.setCullMode(CullState.CS_BACK);
       cs.setEnabled(true);
       rootNode.setRenderState(cs);

       lightState.setTwoSidedLighting(true);
       lightState.attach(dr);

//       MidPointHeightMap heightMap = new MidPointHeightMap(128, 1.9f);
       FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 32, 0, 255,
           0.75f);
       Vector3f terrainScale = new Vector3f(10,1,10);
       heightMap.setHeightScale( 0.001f);
       TerrainPage tb = new TerrainPage("Terrain", 33, heightMap.getSize(), terrainScale,
                                        heightMap.getHeightMap(), false);

       tb.setDetailTexture(1, 16);
       rootNode.attachChild(tb);
         tb.updateGeometricState(0,true);  // IMPORTANT CHANGE: You must first update any
         // TerrainFollower's Spatials before handing them to TerrainFollower's constructor.
         tf=new TerrainFollower(tb,heightMap.getSize()*5,heightMap.getSize()*5);

       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(512);

       TextureState ts = display.getRenderer().createTextureState();
       ts.setEnabled(true);
       Texture t1 = TextureManager.loadTexture(
           pt.getImageIcon().getImage(),
           Texture.MM_LINEAR_LINEAR,
           Texture.FM_LINEAR,
           true
           );
       ts.setTexture(t1, 0);

//       Texture t2 = TextureManager.loadTexture(TestTerrain.class.getClassLoader().
//                                               getResource(
//           "jmetest/data/texture/Detail.jpg"),
//                                               Texture.MM_LINEAR_LINEAR,
//                                               Texture.FM_LINEAR,
//                                               true);
//       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);
       rootNode.setRenderState(ts);

       FogState fs = display.getRenderer().createFogState();
       fs.setDensity(0.5f);
       fs.setEnabled(true);
       fs.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
       fs.setEnd(1000);
       fs.setStart(500);
       fs.setDensityFunction(FogState.DF_LINEAR);
       fs.setApplyFunction(FogState.AF_PER_VERTEX);
       rootNode.setRenderState(fs);
         display.getRenderer().getCamera().setLocation(new Vector3f(0,tf.terrainPosition(0,0)+5,0));
     }

       /**
        * This is where I update my camera's location.
        */
       protected void simpleUpdate(){
           Vector3f camLoc=display.getRenderer().getCamera().getLocation();
           float f=tf.terrainPosition(camLoc.x,camLoc.z);
           if (!Float.isNaN(f)){
               camLoc.y = f+9;
           }
       }
   }



have fun and sorry for the delay

Thanks mcbeth, i'm trying but i can

as far as I understand,  it requirres a near approximation of your models x,z dimensions and calculates based on the triangles to determine the y height



play around with the box example reduce the value passed to the tf and see for your self