TerrainPage and Physics

Im trying to use the physics with a terrainpage and got some problems. The article http://jmonkeyengine.com/jmeforum/viewtopic.php?t=1483&highlight=terrainpage+physics seems to be outdated because the StaticPhysicsObject now has a constructor for spatial.



I took the physics TerrainTest example and replaced the code which builds up the terrainblock with the code from the TestTerrainPage example, which creates a terrainpage. The initTerrain now looks like this:


   private void initTerrain() {
      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);
         
          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);
          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);

          Vector3f terrainTranslation = new Vector3f(0,-200,0);
            tb.setLocalTranslation(terrainTranslation);
          rootNode.attachChild(tb);
      
      world.addObject(new StaticPhysicsObject(tb));
   }



Now if I press space, the only thing that happens is that the spheres fall through the terrainpage. Did I forget to do something?

Thanks for your help (hopefully :o )

greets flo

I know about this bug. Its hard to explain, but i’l do my best.



ODE works on world positions/rotations. Jme works on local positions/rotations. Now the problem is getting those two to work together peacefully.



In essence, what TerrainPage is is TerrainBlocks in nodes. Now because nodes are there, the terrainBlock has a translation of (0, 0, 0).



Now when initialising the physics for it, we need to supply ODE with a position. Now the only feasible way to do this is to get the local translation of the object (being 0, 0, 0). Getting the worldTranslation is a problem because you need to have updated the scene which happens during the update. And you really dont want the physics to init during the update call…



There are ways around it, but not nice ones. Im trying to come up with a good and clean solution to it. Stay tuned.



Hope that helps, DP

Hi there.

I am sitting with the same problem.

Darkprophet, did you figure anything out yet?

I realy need a solution.

How about writing a specialized TerrainBlockPhysicsObject and add that to the PhysicsWorld?



   public TerrainBlockPhysicsObject(Geometry graphics) {
        super(graphics);
        phyObject = PhysicsWorld.getInstance().createPhysicalEntity(this);
    }

    public void syncWithGraphical() {
        phyObject.setPosition(jmeObject.getWorldTranslation());
        phyObject.setQuaternion(jmeObject.getWorldRotation());
    }

    public boolean isStatic() {
        return true;
    }



Something like that. Notice that the physics object uses world values!

Hmm…Definetly something to consider…



I’l get back to you on that one really soon.



DP

Thanks man.

O yes you also have to take into affect the scaling of the terrain. This is very important!



Is there a latest CVS version yet for the other stuff you did in the Physics.

What I mean is, how up to date is the CVS?

CVS is pretty much up to date. Besides ragdolls stuff, its all there…



DP

You are completely right. So we need to figure out a way to correct this problem for all the StaticPhysicsObjects.

StaticPhysicsObjects doesn’t move so I’m sure we can use the world translations.

What do you think?

Ok, update…it works if you dont move the terrain currently. I.e. keep the terrain page at (0, 0, 0) and move the objects around it. I didnt’ take into account the parents location when i stripped the nodes.



Im working on fixing this right now and the fix should be with you in a couple.



Edit: Committed the fix. Please see TerrainPageTest under jmextest.physics



DP :smiley:

yer, its in CVS.



Basically, i was stripping the nodes and getting their geometry fine. That was the no brainer. Then when creating the odeGeom, i was syncing it with the geom that i got, i completly forgot to add the translation of the superNode to the odeGeom. So i did that, change the terrainTest and solved it…



DP

The TerrainTest is still using a TerrainBlock.

Did you change it to be a terrain page on your local build?

Hi there, me again.

DarkProphet, I have a request for you.

It seems currently that the physics doesn’t support this but I think it is nessisary.

I want a dynamic physics object to update its world position instaid of its local position. The same goes for rotation!

How do you think of doing this or don’t you know?

TerrainTest does use TerrainBlock, but TerrainPageTest uses terrain page :slight_smile:



As for your request, sure, but there is no way of actually setting the world translation/rotation. Irrisor sent me a fix via email, your going to have to add those methods to spatial and then create an new controller to use those new methods, extend dynamic physics object and set the controller to be that…



The methods are:



//irrisor: introduced setter methods for world loc/rot

    /**
     * Computes the local translation from the parameter translation and sets it
as new
     * local translation.
     *
     * @param translation new world translation of this spatial.
     * @return the computed local translation
     */
    public Vector3f setWorldTranslation( Vector3f translation ) {
        Vector3f localTranslation = this.getLocalTranslation();
        if ( parent != null ) {
            localTranslation.set( translation ).subtractLocal(
parent.getWorldTranslation() );
            localTranslation.divideLocal( parent.getWorldScale() );
            tmp_inverseWorldRotation.set( parent.getWorldRotation()
).inverseLocal().multLocal( localTranslation );
        }
        else {
            localTranslation.set( translation );
        }
        return localTranslation;
    }

    /**
     * to flatline memory usage
     */
    private final Quaternion tmp_inverseWorldRotation = new Quaternion();

    /**
     * Computes the local rotation from the parameter rot and sets it as new
     * local rotation.
     *
     * @param rot new world rotation of this spatial.
     * @return the computed local rotation
     */
    public Quaternion setWorldRotation( Quaternion rot ) {
        Quaternion localRotation = getLocalRotation();
        if ( parent != null ) {
            tmp_inverseWorldRotation.set( parent.getWorldRotation()
).inverseLocal().mult( rot, localRotation );
        }
        else {
            localRotation.set( rot );
        }
        return localRotation;
    }



The only performance problem with those is the inversion of the rotation matrix...

Hope that helps!
DP

Hi.



Can I use this to create a tilt type game?

ie: mouse moves terrain and objects slide around?



I tried this a while back and there was some problem with doing this sort of thing and ODE, if I recall.



ty,

sv

Yer it can do that. Its simple really, init the terrain, add a ball. Create an input controller that takes mouse actions (delta’s probably) and change the rotation of the terrain. Make sure you call syncWithGraphics() on the StaticPhysicsObject of the terrain when you do so that Ode gets the new rotation.



Wait till the 5th of July, so you can enter the jme-physics competition! Which reminds me, we need to drum up more publicity for it!



DP

kk, thanks dp

Q: It seems that the center of a terrain block is located on one corner.

How can I adjust it so that the node handler rotates around the center of the terrainblock, and not a corner?



an offset, or something in node handler?

or does the terrainBlock need to be changed so that its origin is the center rather than a corner?



It should be center to be consistant with all other primitives, no?



ty,

sv

Its the way the TerrainBlock is built and the corner translation is needed really. You can use terrainpage if you want, thats no problem now.



But if are really insistant on using terrainblock, do this:


  1. Create a new node.
  2. Add the terrainblock to that node
  3. move the terrain block back by its (size/2) * scale.
  4. Create the input handler to function on the node and not on the terrainblock.



    To emphasis step 3:



TerrainBlock tb = ...
terrainNode.attachChild(tb);
tb.getLocalTranslation().x -= (heightmap.getSize().x/2) * terrainScale.x;
tb.getLocalTranslation().z -= (heightmap.getSize().x/2) * terrainScale.z;



Hope that helps,
DP

Well, I tried with terrainBlock first and it seems to work (with just the offset problem mentioned previously - I tried nodes but could not get it to work).



Then I tried terrainPage, but the synchWithGraphical does not seem to work.

You move the terrain and the position is not being updated.



Any thoughts?



Thanks for your help.

sv



Code for the two tests:



TerrainBlock - working with offset problem still in


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package jmetest.physics;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.OrientedBoundingBox;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.NodeHandler2;
import com.jmex.physics.*;
import jmextest.physics.*;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.CullState;
import com.jme.util.TextureManager;
import com.jme.scene.state.TextureState;
import com.jme.image.Texture;

import java.net.URL;
import javax.swing.ImageIcon;

/**
 * This test shows a bunch of boxes and spheres being dropped on a terrain block.
 * Demonstrates how static triangle meshes also can be added to the physics world.
 *
 * @author Per Thulin
 */
public class TerrainTest extends SimpleGame {
   
   private PhysicsWorld world;
   private DynamicPhysicsObject[] boxObjects;
   private DynamicPhysicsObject[] sphereObjects;
   private TerrainBlock tb;
    private StaticPhysicsObject terrain;
   /**
    * Inits our game, gets called on startup.
    */
   protected void simpleInitGame() {
      display.setTitle("Terrain Test");
      
        cam.getLocation().x = 170;
        cam.getLocation().y = 60;
        cam.getLocation().z = 270;
        cam.update();
      
      KeyBindingManager.getKeyBindingManager().set("reset", KeyInput.KEY_R);
      input.setKeySpeed(150);
      
      initLights();
      initCullState();
      initText();
      initPhysicsWorld();
      initTerrain();
      initSpheres();
      initBoxes();
      
      rootNode.addController(new BallThrower(rootNode, 10, 2, 1));
       
        super.input = new NodeHandler2(this, (Spatial)tb, properties.getRenderer());
        super.input.setMouseSpeed(3);
      
      reset();
   }
   
   /**
    * Creates the <code>PhysicsWorld</code> that will manage the physics.
    */
   private void initPhysicsWorld() {
      world = PhysicsWorld.create();
      world.setUpdateRate(100);
      world.setStepSize(2/100f);
   }
   
   /**
    * Detatches all the standard lightsources of the rootNode. Instead we will
    * add another lightsource, that looks more neat.
    */
   private void initLights() {
      lightState.detachAll();
      DirectionalLight dl = new DirectionalLight();
      dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setAmbient(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setDirection(new Vector3f(1, -.5f, 1));
      dl.setEnabled(true);
      lightState.attach(dl);
   }
   
   /**
    * Creates the text label(s).
    */
   private void initText() {      
      Text label1 = new Text("Label1", "SPACE = throw ball, R = reset");
      Text label2 = new Text("Label2", "W,S,A,D + mouse = navigation");
      
      label1.setLocalTranslation(new Vector3f(20, display.getHeight()-30, 0));
      label2.setLocalTranslation(new Vector3f(20, display.getHeight()-60, 0));
      
      fpsNode.attachChild(label1);
      fpsNode.attachChild(label2);
   }
   
   /**
    * Sets the cull state for the rooNode.
    *
    * @see com.jme.scene.state.CullState
    */
   private void initCullState() {
      CullState cs = display.getRenderer().createCullState();
      cs.setCullMode(CullState.CS_BACK);
      cs.setEnabled(true);
      
      rootNode.setRenderState(cs);
   }
   
   /**
    * Creates the terrain and adds it to the rootNode and physics system. It's
    * added as static geometry.
    */
   private void initTerrain() {
      // This grayscale image will be our terrain
      URL grayScale = TerrainTest.class.getClassLoader()
      .getResource("jmextest/data/texture/terrain.PNG");
      // These will be the textures of our terrain.
      URL grassImage = TerrainTest.class.getClassLoader()
      .getResource("jmetest/data/texture/grassb.png");
      URL dirtImage = TerrainTest.class.getClassLoader()
      .getResource("jmetest/data/texture/dirt.jpg");
      URL highest = TerrainTest.class.getClassLoader()
      .getResource("jmetest/data/texture/highest.jpg");
      URL detail = TerrainTest.class.getClassLoader()
      .getResource("jmetest/data/texture/Detail.jpg");
      
      // Create an image height map based on the gray scale of our image.
      ImageBasedHeightMap ib = new ImageBasedHeightMap(new ImageIcon(
            grayScale).getImage());
      // Create a terrain block from the image's grey scale
      tb = new TerrainBlock("image icon", ib.getSize(),
            new Vector3f(4f, 0.2f, 4f), ib.getHeightMap(),
            new Vector3f(0, 0, 0), false);
       
       
      tb.setModelBound(new BoundingBox());
      tb.updateModelBound();
      
      tb.setDetailTexture(1, 16);
      
      // Create an object to generate textured terrain from the image based
      // height map.
      ProceduralTextureGenerator pg = new ProceduralTextureGenerator(ib);
      
      pg.addTexture(new ImageIcon(grassImage), -128, 0, 128);
      
      pg.addTexture(new ImageIcon(dirtImage), 0, 128, 255);
      
      pg.addTexture(new ImageIcon(highest), 128, 255, 384);
      
      pg.createTexture(256);
      
      pg.createTexture(512);
      TextureState ts = display.getRenderer().createTextureState();
      ts.setEnabled(true);
      
      Texture t1 = TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR, true);
      
      ts.setTexture(t1, 0);
      
      Texture t2 = TextureManager.loadTexture(detail,
            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);
      
      ts.setTexture(TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR,
            true));
      
      tb.setRenderState(ts);
      
      rootNode.attachChild(tb);
      terrain = new StaticPhysicsObject(tb);
      world.addObject(terrain);
   }
   
   /**
    * Creates the boxes and attaches them to the rootNode and <code>PhysicsWorld</code>.
    */
   private void initBoxes() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");
      
      Box[] boxes = new Box[5];
      boxObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < boxes.length; i++) {
         boxes[i] = new Box("Box #" + i, new Vector3f(), 3f, 3f, 3f);
         boxes[i].setModelBound(new OrientedBoundingBox());
         boxes[i].updateModelBound();
         boxes[i].setRenderState(ts);
         world.addObject(boxObjects[i] = new DynamicPhysicsObject(boxes[i], 5f));
         rootNode.attachChild(boxes[i]);
      }
   }
   
   /**
    * Creates the spheres and attaches them to the rootNode and <code>PhysicsWorld</code>.
    */
   private void initSpheres() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");
      
      Sphere[] spheres = new Sphere[5];
      sphereObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < spheres.length; i++) {
         spheres[i] = new Sphere("Sphere #" + i, 20, 20, 3);
         spheres[i].setModelBound(new BoundingSphere());
         spheres[i].updateModelBound();
         spheres[i].setRenderState(ts);
         world.addObject(sphereObjects[i] = new DynamicPhysicsObject(spheres[i], 5f));
         rootNode.attachChild(spheres[i]);
      }
   }
   
   /**
    * Resets all the objects to their original location. Get's called one time
    * on startup and then every time the "reset" command gets executed.
    */
   private void reset() {
      // Set the location of all the spheres.
      Vector3f baseCoordinate = new Vector3f(200, 100, 100);
      for (int i = 0; i < sphereObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0,i*100,0));
         sphereObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         sphereObjects[i].syncWithGraphical();
         sphereObjects[i].resetForces();
      }
      
      // Set the location of all the boxes.
      baseCoordinate = new Vector3f(200, 1000, 100);
      for (int i = 0; i < boxObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0,i*20,0));
         boxObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         boxObjects[i].syncWithGraphical();
         boxObjects[i].resetForces();
      }
      
      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
   }
   
   /**
    * Gets called every frame from gameloop. Handles input checking and update
    * of the <code>PhysicsWorld</code>.
    */
   protected void simpleUpdate() {

        terrain.syncWithGraphical();   
        world.update(tpf);
      
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("reset",
            false)) {
         reset();
      }
   }
   
   /**
    * Overwritten to handle cleanup of physics.
    */
   protected void cleanup() {
      super.cleanup();
      PhysicsWorld.getInstance().cleanup();
   }
   
   public static void main(String[] args) {
      TerrainTest app = new TerrainTest();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG,
            TerrainTest.class
            .getClassLoader().getResource
            ("jmextest/data/images/jmephysics_logo.png"));
      app.start();
   }
}



TerrainPage - not working

/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package jmetest.physics;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.OrientedBoundingBox;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.NodeHandler2;
import com.jmex.physics.*;
import jmextest.physics.*;
import com.jmex.terrain.*;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.CullState;
import com.jme.util.TextureManager;
import com.jme.scene.state.TextureState;
import com.jme.image.Texture;

import java.net.URL;
import javax.swing.ImageIcon;

/**
 * This test shows a bunch of boxes and spheres being dropped on a terrain block.
 * Demonstrates how static triangle meshes also can be added to the physics world.
 *
 * @author Per Thulin
 */
public class TerrainPageTest2 extends SimpleGame {
   
   private PhysicsWorld world;
   private DynamicPhysicsObject[] boxObjects;
   private DynamicPhysicsObject[] sphereObjects;
   private TerrainPage tb;
    private StaticPhysicsObject terrain;
   /**
    * Inits our game, gets called on startup.
    */
   protected void simpleInitGame() {
      display.setTitle("Terrain Test");
      
        //cam.getLocation().x = 170;
        cam.getLocation().y = 60;
        cam.getLocation().z = 270;
        cam.update();
      
      KeyBindingManager.getKeyBindingManager().set("reset", KeyInput.KEY_R);
      input.setKeySpeed(150);
      
      initLights();
      initCullState();
      initText();
      initPhysicsWorld();
      initTerrain();
      initSpheres();
      initBoxes();
      
      rootNode.addController(new BallThrower(rootNode, 10, 2, 1));
       
        super.input = new NodeHandler2(this, (Spatial)tb, properties.getRenderer());
        super.input.setMouseSpeed(2);
      
      reset();
   }
   
   /**
    * Creates the <code>PhysicsWorld</code> that will manage the physics.
    */
   private void initPhysicsWorld() {
      world = PhysicsWorld.create();
      world.setUpdateRate(100);
      world.setStepSize(2/100f);
   }
   
   /**
    * Detatches all the standard lightsources of the rootNode. Instead we will
    * add another lightsource, that looks more neat.
    */
   private void initLights() {
      lightState.detachAll();
      DirectionalLight dl = new DirectionalLight();
      dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setAmbient(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setDirection(new Vector3f(1, -.5f, 1));
      dl.setEnabled(true);
      lightState.attach(dl);
   }
   
   /**
    * Creates the text label(s).
    */
   private void initText() {      
      Text label1 = new Text("Label1", "SPACE = throw ball, R = reset");
      Text label2 = new Text("Label2", "W,S,A,D + mouse = navigation");
      
      label1.setLocalTranslation(new Vector3f(20, display.getHeight()-30, 0));
      label2.setLocalTranslation(new Vector3f(20, display.getHeight()-60, 0));
      
      fpsNode.attachChild(label1);
      fpsNode.attachChild(label2);
   }
   
   /**
    * Sets the cull state for the rooNode.
    *
    * @see com.jme.scene.state.CullState
    */
   private void initCullState() {
      CullState cs = display.getRenderer().createCullState();
      cs.setCullMode(CullState.CS_BACK);
      cs.setEnabled(true);
      
      rootNode.setRenderState(cs);
   }
   
   /**
    * Creates the terrain and adds it to the rootNode and physics system. It's
    * added as static geometry.
    */
   private void initTerrain() {
      // This grayscale image will be our terrain
      URL grayScale = TerrainPageTest2.class.getClassLoader()
      .getResource("jmextest/data/texture/terrain2.PNG");
      // These will be the textures of our terrain.
      URL grassImage = TerrainPageTest2.class.getClassLoader()
      .getResource("jmetest/data/texture/grassb.png");
      URL dirtImage = TerrainPageTest2.class.getClassLoader()
      .getResource("jmetest/data/texture/dirt.jpg");
      URL highest = TerrainPageTest2.class.getClassLoader()
      .getResource("jmetest/data/texture/highest.jpg");
      URL detail = TerrainPageTest2.class.getClassLoader()
      .getResource("jmetest/data/texture/Detail.jpg");
      
      // Create an image height map based on the gray scale of our image.
      ImageBasedHeightMap ib = new ImageBasedHeightMap(new ImageIcon(
            grayScale).getImage());
      // Create a terrain block from the image's grey scale
        tb = new TerrainPage("imagine icon", 33, ib.getSize(), new Vector3f(4f, 0.2f, 4f), ib.getHeightMap(), false);
        tb.setLocalTranslation(new Vector3f(ib.getSize()/2, 0, 0));
       
      tb.setModelBound(new BoundingBox());
      tb.updateModelBound();
      
      tb.setDetailTexture(1, 16);
      
      // Create an object to generate textured terrain from the image based
      // height map.
      ProceduralTextureGenerator pg = new ProceduralTextureGenerator(ib);
      
      pg.addTexture(new ImageIcon(grassImage), -128, 0, 128);
      
      pg.addTexture(new ImageIcon(dirtImage), 0, 128, 255);
      
      pg.addTexture(new ImageIcon(highest), 128, 255, 384);
      
      pg.createTexture(256);
      
      pg.createTexture(512);
      TextureState ts = display.getRenderer().createTextureState();
      ts.setEnabled(true);
      
      Texture t1 = TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR, true);
      
      ts.setTexture(t1, 0);
      
      Texture t2 = TextureManager.loadTexture(detail,
            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);
      
      ts.setTexture(TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR,
            true));
      
      tb.setRenderState(ts);
      
      rootNode.attachChild(tb);
      terrain = new StaticPhysicsObject(tb);
 
      world.addObject(terrain);
   }
   
   /**
    * Creates the boxes and attaches them to the rootNode and <code>PhysicsWorld</code>.
    */
   private void initBoxes() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");
      
      Box[] boxes = new Box[5];
      boxObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < boxes.length; i++) {
         boxes[i] = new Box("Box #" + i, new Vector3f(), 3f, 3f, 3f);
         boxes[i].setModelBound(new OrientedBoundingBox());
         boxes[i].updateModelBound();
         boxes[i].setRenderState(ts);
         world.addObject(boxObjects[i] = new DynamicPhysicsObject(boxes[i], 5f));
         rootNode.attachChild(boxes[i]);
      }
   }
   
   /**
    * Creates the spheres and attaches them to the rootNode and <code>PhysicsWorld</code>.
    */
   private void initSpheres() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");
      
      Sphere[] spheres = new Sphere[5];
      sphereObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < spheres.length; i++) {
         spheres[i] = new Sphere("Sphere #" + i, 20, 20, 3);
         spheres[i].setModelBound(new BoundingSphere());
         spheres[i].updateModelBound();
         spheres[i].setRenderState(ts);
         world.addObject(sphereObjects[i] = new DynamicPhysicsObject(spheres[i], 5f));
         rootNode.attachChild(spheres[i]);
      }
   }
   
   /**
    * Resets all the objects to their original location. Get's called one time
    * on startup and then every time the "reset" command gets executed.
    */
   private void reset() {
      // Set the location of all the spheres.
      Vector3f baseCoordinate = new Vector3f(0, 100, 100);
      for (int i = 0; i < sphereObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0,i*100,0));
         sphereObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         sphereObjects[i].syncWithGraphical();
         sphereObjects[i].resetForces();
      }
      
      // Set the location of all the boxes.
      baseCoordinate = new Vector3f(0, 1000, 100);
      for (int i = 0; i < boxObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0,i*20,0));
         boxObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         boxObjects[i].syncWithGraphical();
         boxObjects[i].resetForces();
      }
      
      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
   }
   
   /**
    * Gets called every frame from gameloop. Handles input checking and update
    * of the <code>PhysicsWorld</code>.
    */
   protected void simpleUpdate() {

        terrain.syncWithGraphical();         
        world.update(tpf);

      if (KeyBindingManager.getKeyBindingManager().isValidCommand("reset",
            false)) {
         reset();
      }
   }
   
   /**
    * Overwritten to handle cleanup of physics.
    */
   protected void cleanup() {
      super.cleanup();
      PhysicsWorld.getInstance().cleanup();
   }
   
   public static void main(String[] args) {
      TerrainPageTest2 app = new TerrainPageTest2();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG,
            TerrainPageTest2.class
            .getClassLoader().getResource
            ("jmextest/data/images/jmephysics_logo.png"));
      app.start();
   }
}

There seems to be a problem with the locals/world vectors. I’l check it out ASAP.



DP

Heres the code to setup the terrain with moving left.



public class TerrainTest extends SimpleGame {

   private PhysicsWorld world;
   private DynamicPhysicsObject[] boxObjects;
   private DynamicPhysicsObject[] sphereObjects;
   private TerrainBlock tb;
   private StaticPhysicsObject terrain;
   
   private Node terrainNode;

   /**
    * Inits our game, gets called on startup.
    */
   protected void simpleInitGame() {
      display.setTitle("Terrain Test");

      cam.getLocation().x = 170;
      cam.getLocation().y = 60;
      cam.getLocation().z = 270;
      cam.update();
      
      terrainNode = new Node("TerrainNode");
      rootNode.attachChild(terrainNode);

      KeyBindingManager.getKeyBindingManager().set("reset", KeyInput.KEY_R);
      KeyBindingManager.getKeyBindingManager().set("left", KeyInput.KEY_1);
      input.setKeySpeed(150);

      initLights();
      initCullState();
      initText();
      initPhysicsWorld();
      initTerrain();
      initSpheres();
      initBoxes();

      rootNode.addController(new BallThrower(rootNode, 10, 2, 1));

      reset();
   }

   /**
    * Creates the <code>PhysicsWorld</code> that will manage the physics.
    */
   private void initPhysicsWorld() {
      world = PhysicsWorld.create();
      world.setUpdateRate(100);
      world.setStepSize(2 / 100f);
   }

   /**
    * Detatches all the standard lightsources of the rootNode. Instead we will
    * add another lightsource, that looks more neat.
    */
   private void initLights() {
      lightState.detachAll();
      DirectionalLight dl = new DirectionalLight();
      dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setAmbient(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dl.setDirection(new Vector3f(1, -.5f, 1));
      dl.setEnabled(true);
      lightState.attach(dl);
   }

   /**
    * Creates the text label(s).
    */
   private void initText() {
      Text label1 = new Text("Label1", "SPACE = throw ball, R = reset");
      Text label2 = new Text("Label2", "W,S,A,D + mouse = navigation");

      label1.setLocalTranslation(new Vector3f(20, display.getHeight() - 30, 0));
      label2.setLocalTranslation(new Vector3f(20, display.getHeight() - 60, 0));

      fpsNode.attachChild(label1);
      fpsNode.attachChild(label2);
   }

   /**
    * Sets the cull state for the rooNode.
    *
    * @see com.jme.scene.state.CullState
    */
   private void initCullState() {
      CullState cs = display.getRenderer().createCullState();
      cs.setCullMode(CullState.CS_BACK);
      cs.setEnabled(true);

      rootNode.setRenderState(cs);
   }

   /**
    * Creates the terrain and adds it to the rootNode and physics system. It's
    * added as static geometry.
    */
   private void initTerrain() {
      // This grayscale image will be our terrain
      URL grayScale = TerrainTest.class.getClassLoader().getResource(
            "jmextest/data/texture/terrain.PNG");
      // These will be the textures of our terrain.
      URL grassImage = TerrainTest.class.getClassLoader().getResource(
            "jmetest/data/texture/grassb.png");
      URL dirtImage = TerrainTest.class.getClassLoader().getResource(
            "jmetest/data/texture/dirt.jpg");
      URL highest = TerrainTest.class.getClassLoader().getResource(
            "jmetest/data/texture/highest.jpg");
      URL detail = TerrainTest.class.getClassLoader().getResource(
            "jmetest/data/texture/Detail.jpg");

      // Create an image height map based on the gray scale of our image.
      ImageBasedHeightMap ib = new ImageBasedHeightMap(new ImageIcon(grayScale).getImage());
      // Create a terrain block from the image's grey scale
      tb = new TerrainBlock("image icon", ib.getSize(), new Vector3f(4f, 0.2f, 4f), ib
            .getHeightMap(), new Vector3f(0, 0, 0), false);

      tb.setModelBound(new BoundingBox());
      tb.updateModelBound();

      tb.setDetailTexture(1, 16);

      // Create an object to generate textured terrain from the image based
      // height map.
      ProceduralTextureGenerator pg = new ProceduralTextureGenerator(ib);

      pg.addTexture(new ImageIcon(grassImage), -128, 0, 128);

      pg.addTexture(new ImageIcon(dirtImage), 0, 128, 255);

      pg.addTexture(new ImageIcon(highest), 128, 255, 384);

      pg.createTexture(256);

      pg.createTexture(512);
      TextureState ts = display.getRenderer().createTextureState();
      ts.setEnabled(true);

      Texture t1 = TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, true);

      ts.setTexture(t1, 0);

      Texture t2 = TextureManager
            .loadTexture(detail, 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);

      ts.setTexture(TextureManager.loadTexture(pg.getImageIcon().getImage(),
            Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, true));

      tb.setRenderState(ts);

      terrainNode.attachChild(tb);
      tb.getLocalTranslation().x -= (ib.getSize()/2) * 4;
      tb.getLocalTranslation().z -= (ib.getSize()/2) * 4;
      terrain = new StaticPhysicsObject(tb);
      terrainNode.getLocalTranslation().x += tb.getSize() + 50;
      terrainNode.getLocalTranslation().z += tb.getSize() + 50;
      world.addObject(terrain);
   }

   /**
    * Creates the boxes and attaches them to the rootNode and
    * <code>PhysicsWorld</code>.
    */
   private void initBoxes() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");

      Box[] boxes = new Box[5];
      boxObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < boxes.length; i++) {
         boxes[i] = new Box("Box #" + i, new Vector3f(), 3f, 3f, 3f);
         boxes[i].setModelBound(new OrientedBoundingBox());
         boxes[i].updateModelBound();
         boxes[i].setRenderState(ts);
         world.addObject(boxObjects[i] = new DynamicPhysicsObject(boxes[i], 5f));
         rootNode.attachChild(boxes[i]);
      }
   }

   /**
    * Creates the spheres and attaches them to the rootNode and
    * <code>PhysicsWorld</code>.
    */
   private void initSpheres() {
      TextureState ts = TextureLoader.loadTexture("jmetest/data/images/Monkey.jpg");

      Sphere[] spheres = new Sphere[5];
      sphereObjects = new DynamicPhysicsObject[5];
      for (int i = 0; i < spheres.length; i++) {
         spheres[i] = new Sphere("Sphere #" + i, 20, 20, 3);
         spheres[i].setModelBound(new BoundingSphere());
         spheres[i].updateModelBound();
         spheres[i].setRenderState(ts);
         world.addObject(sphereObjects[i] = new DynamicPhysicsObject(spheres[i], 5f));
         rootNode.attachChild(spheres[i]);
      }
   }

   /**
    * Resets all the objects to their original location. Get's called one time
    * on startup and then every time the "reset" command gets executed.
    */
   private void reset() {
      // Set the location of all the spheres.
      Vector3f baseCoordinate = new Vector3f(200, 100, 100);
      for (int i = 0; i < sphereObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0, i * 100, 0));
         sphereObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         sphereObjects[i].syncWithGraphical();
         sphereObjects[i].resetForces();
      }

      // Set the location of all the boxes.
      baseCoordinate = new Vector3f(200, 1000, 100);
      for (int i = 0; i < boxObjects.length; i++) {
         Vector3f newCoordinate = baseCoordinate.add(new Vector3f(0, i * 20, 0));
         boxObjects[i].getSpatial().setLocalTranslation(newCoordinate);
         boxObjects[i].syncWithGraphical();
         boxObjects[i].resetForces();
      }

      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
   }

   private static final Matrix3f incr = new Matrix3f();

   private static final Matrix3f tempMa = new Matrix3f();

   private static final Matrix3f tempMb = new Matrix3f();

   private static final Vector3f tempVa = new Vector3f();

   /**
    * Gets called every frame from gameloop. Handles input checking and update
    * of the <code>PhysicsWorld</code>.
    */
   protected void simpleUpdate() {

      terrain.syncWithGraphical();
      world.update(tpf);

      if (KeyBindingManager.getKeyBindingManager().isValidCommand("reset", false)) {
         reset();
      }

      if (KeyBindingManager.getKeyBindingManager().isValidCommand("left")) {
         incr.fromAxisAngle(terrainNode.getLocalRotation().getRotationColumn(1, tempVa), tpf * 0.01f);
         terrainNode.getLocalRotation().fromRotationMatrix(
                   incr.mult(terrainNode.getLocalRotation().toRotationMatrix(tempMa),
                           tempMb));
         terrainNode.getLocalRotation().normalize();
         terrain.syncWithGraphical();
      }
   }

   /**
    * Overwritten to handle cleanup of physics.
    */
   protected void cleanup() {
      super.cleanup();
      PhysicsWorld.getInstance().cleanup();
   }

   public static void main(String[] args) {
      TerrainTest app = new TerrainTest();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG, TerrainTest.class.getClassLoader()
            .getResource("jmextest/data/images/jmephysics_logo.png"));
      app.start();
   }
}



Press 1 to turn the terrain. See the simpleUpdate method and work out how to rotate the terrainNode using the rotation matrix (for different rotations)

DP