TerrainPage for JBullet-JME

normen said:

SomethingNew said:

Are there any thoughts on adding the capabilities (or at least a demonstration) of using the JME terrain for this physics implementation?

That would be a nice test but it would require me to include the jme data classes to the project, or is there something in the jars I already included? Anyway, if I understand correctly what you want it is already working. In my project I have a terrain that I can drive on with a vehicle, heres the code (just a quick cut and paste);


        RawHeightMap heightMap = new RawHeightMap(ActionGame.class
                .getClassLoader().getResource("heights.raw"),
                129, RawHeightMap.FORMAT_16BITLE, false);
        TerrainBlock tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
                                           heightMap.getHeightMap(),
                                           new Vector3f(0, 0, 0));
        MeshCollisionShape coll=new MeshCollisionShape(tb);
        terrainPnode=new PhysicsNode(tb,coll,0);
        terrainPnode.setLocalTranslation(new Vector3f(-300,-10,-300));
        terrainPnode.rotateUpTo(new Vector3f(0,1,0));
        PhysicsSpace.getPhysicsSpace().add(terrainPnode);





According to this post from a long time ago I can create a static physics object from a Terrain Block with JBullet-JME.  What I was wondering is, is it possible to do the same for a Terrain Page?  I know it's possible with JME Physics 2 (see TestGeneratedTerrain.java).

No, that is not implemented right now. You could just create a TerrainBlock from the same height data once to create a physics collision shape from it.



Hope this helps,

Normen

:frowning: Ok, Maybe I'll at implementing it, since it's already implemented in JME Physics 2.  I think it just has to create the bounding mesh objects off of the children Terrain Blocks.

Ok, it's been awhile, but I implemented it.  It was pretty easy, just a simple recursive method:

    private void addTerrainPagePhysics(TerrainPage terrainPage, Vector3f translation) {
       for (Spatial childTerrain : terrainPage.getChildren()) {
         if (childTerrain instanceof TerrainBlock) {
            MeshCollisionShape collisionShape = new MeshCollisionShape((TriMesh)childTerrain);
            PhysicsNode terrainPhysicsNode = new PhysicsNode(null, collisionShape, 0);
            terrainPhysicsNode.setLocalTranslation(childTerrain.getLocalTranslation().add(translation));
            physicsSpace.add(terrainPhysicsNode);            
         } else {
            addTerrainPagePhysics((TerrainPage)childTerrain, translation.add(childTerrain.getLocalTranslation()));
         }
      }
    }



Use something like:


        terrain = new TerrainPage("Terrain", 33, heightMap.getSize(),
                terrainScale, heightMap.getHeightMap());
addTerrainPagePhysics(terrain, terrain.getLocalTranslation());    



To use it.  :wink:

EDIT: Changed from creating a empty new Node() in the PhysicsNode() to null.

have u tested it fully, I know jbullet is a different beast, but jmephysics(fixed I think) and simple-physics(not fixed :// ) has issues with terrainpages, collisions are not registered with the inner edges of the pages

No I haven't tested it that way fully, but we'll see.

This is a bad way to do this. Bullet contains a terrain shape that uses the raw data, its just not implemented in JBullet. Ive made a conversion that works:



One note tho, I use ardor3D and terrain clipmap that requires moving the physics mesh to the terrain mesh like so:



t.origin.x = t.origin.x+ half;
t.origin.z = t.origin.z+ half;
         
float y = Math.abs(minHeight*heightScale);
float y2 = Math.abs(maxHeight*heightScale);
         
t.origin.y = (y2-y)/2;



I havnt tested it using JME's terrain page but you may have to do something similar.


/*
 * Port of btHeightfieldTerrainShape by Dominic Browne <dominic.browne@hotmail.co.uk>
 *
 * Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz>
 *
 * Bullet Continuous Collision Detection and Physics Library
 * Copyright (c) 2003-2008 Erwin Coumans  http://www.bulletphysics.com/
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package com.bulletphysics.dom;

import javax.vecmath.Matrix3f;
import javax.vecmath.Vector3f;

import com.bulletphysics.collision.broadphase.BroadphaseNativeType;
import com.bulletphysics.collision.shapes.ConcaveShape;
import com.bulletphysics.collision.shapes.ScalarType;
import com.bulletphysics.collision.shapes.TriangleCallback;
import com.bulletphysics.linearmath.MatrixUtil;
import com.bulletphysics.linearmath.Transform;

import cz.advel.stack.Stack;

public class HeightfieldTerrainShape extends ConcaveShape {

   public static final int XAXIS = 0;
   public static final int YAXIS = 1;
   public static final int ZAXIS = 2;

   protected Vector3f m_localAabbMin = new Vector3f();
   protected Vector3f m_localAabbMax = new Vector3f();
   protected Vector3f m_localOrigin = new Vector3f();

   // /terrain data
   protected int m_heightStickWidth;
   protected int m_heightStickLength;
   protected float m_minHeight;
   protected float m_maxHeight;
   protected float m_width;
   protected float m_length;
   protected float m_heightScale;
   protected float[] m_heightfieldDataFloat;
   protected ScalarType m_heightDataType;
   protected boolean m_flipQuadEdges;
   protected boolean m_useDiamondSubdivision;
   protected int m_upAxis;
   protected Vector3f m_localScaling = new Vector3f();

   public HeightfieldTerrainShape(int heightStickWidth, int heightStickLength, float[] heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, boolean flipQuadEdges) {

      initialize(heightStickWidth, heightStickLength, heightfieldData, heightScale, minHeight, maxHeight, upAxis, ScalarType.FLOAT, flipQuadEdges);
   }

   private void initialize(int heightStickWidth, int heightStickLength, float[] heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, ScalarType f, boolean flipQuadEdges) {
      m_heightStickWidth = heightStickWidth;
      m_heightStickLength = heightStickLength;
      m_minHeight = minHeight*heightScale;
      m_maxHeight = maxHeight*heightScale;
      m_width = (heightStickWidth - 1);
      m_length = (heightStickLength - 1);
      m_heightScale = heightScale;
      m_heightfieldDataFloat = heightfieldData;
      m_heightDataType = ScalarType.FLOAT;
      m_flipQuadEdges = flipQuadEdges;
      m_useDiamondSubdivision = false;
      m_upAxis = upAxis;
      m_localScaling.set(1.f, 1.f, 1.f);

      // determine min/max axis-aligned bounding box (aabb) values
      switch (m_upAxis) {
      case 0: {
         m_localAabbMin.set(m_minHeight, 0, 0);
         m_localAabbMax.set(m_maxHeight, m_width, m_length);
         break;
      }
      case 1: {
         m_localAabbMin.set(0, m_minHeight, 0);
         m_localAabbMax.set(m_width, m_maxHeight, m_length);
         break;
      }
      case 2: {
         m_localAabbMin.set(0, 0, m_minHeight);
         m_localAabbMax.set(m_width, m_length, m_maxHeight);
         break;
      }
      }

      // remember origin (defined as exact middle of aabb)
      // m_localOrigin = btScalar(0.5) * (m_localAabbMin + m_localAabbMax);

      m_localOrigin.set(m_localAabbMin);
      m_localOrigin.add(m_localAabbMax);
      m_localOrigin.x = m_localOrigin.x * 0.5f;
      m_localOrigin.y = m_localOrigin.y * 0.5f;
      m_localOrigin.z = m_localOrigin.z * 0.5f;

   }

   @Override
   public void processAllTriangles(TriangleCallback callback, Vector3f aabbMin, Vector3f aabbMax) {
      Vector3f localAabbMin = Stack.alloc(Vector3f.class);

      localAabbMin.x = aabbMin.x * (1.f / m_localScaling.x);
      localAabbMin.y = aabbMin.y * (1.f / m_localScaling.y);
      localAabbMin.z = aabbMin.z * (1.f / m_localScaling.z);

      Vector3f localAabbMax = Stack.alloc(Vector3f.class);
      localAabbMax.x = aabbMax.x * (1.f / m_localScaling.x);
      localAabbMax.y = aabbMax.y * (1.f / m_localScaling.y);
      localAabbMax.z = aabbMax.z * (1.f / m_localScaling.z);

      localAabbMin.add(m_localOrigin);
      localAabbMax.add(m_localOrigin);

      // quantize the aabbMin and aabbMax, and adjust the start/end ranges
      int[] quantizedAabbMin = new int[3];
      int[] quantizedAabbMax = new int[3];
      quantizeWithClamp(quantizedAabbMin, localAabbMin);
      quantizeWithClamp(quantizedAabbMax, localAabbMax);

      // expand the min/max quantized values
      // this is to catch the case where the input aabb falls between grid points!
      for (int i = 0; i < 3; ++i) {
         quantizedAabbMin[i]--;
         quantizedAabbMax[i]++;
      }

      int startX = 0;
      int endX = m_heightStickWidth - 1;
      int startJ = 0;
      int endJ = m_heightStickLength - 1;

      switch (m_upAxis) {
      case 0: {
         if (quantizedAabbMin[1] > startX)
            startX = quantizedAabbMin[1];
         if (quantizedAabbMax[1] < endX)
            endX = quantizedAabbMax[1];
         if (quantizedAabbMin[2] > startJ)
            startJ = quantizedAabbMin[2];
         if (quantizedAabbMax[2] < endJ)
            endJ = quantizedAabbMax[2];
         break;
      }
      case 1: {
         if (quantizedAabbMin[0] > startX)
            startX = quantizedAabbMin[0];
         if (quantizedAabbMax[0] < endX)
            endX = quantizedAabbMax[0];
         if (quantizedAabbMin[2] > startJ)
            startJ = quantizedAabbMin[2];
         if (quantizedAabbMax[2] < endJ)
            endJ = quantizedAabbMax[2];
         break;
      }

      case 2: {
         if (quantizedAabbMin[0] > startX)
            startX = quantizedAabbMin[0];
         if (quantizedAabbMax[0] < endX)
            endX = quantizedAabbMax[0];
         if (quantizedAabbMin[1] > startJ)
            startJ = quantizedAabbMin[1];
         if (quantizedAabbMax[1] < endJ)
            endJ = quantizedAabbMax[1];
         break;
      }
      }

      for (int j = startJ; j < endJ; j++) {
         for (int x = startX; x < endX; x++) {
            // Vector3f vertices[3];
            Vector3f[] vertices = new Vector3f[3];
            vertices[0] = Stack.alloc(Vector3f.class);
            vertices[1] = Stack.alloc(Vector3f.class);
            vertices[2] = Stack.alloc(Vector3f.class);
            if (m_flipQuadEdges || (m_useDiamondSubdivision && (((j + x) & 1) != 0))) {// XXX
               // first triangle
               getVertex(x, j, vertices[0]);
               getVertex(x + 1, j, vertices[1]);
               getVertex(x + 1, j + 1, vertices[2]);
               callback.processTriangle(vertices, x, j);
               // callback->processTriangle(vertices,x,j);
               // second triangle
               getVertex(x, j, vertices[0]);
               getVertex(x + 1, j + 1, vertices[1]);
               getVertex(x, j + 1, vertices[2]);
               // callback->processTriangle(vertices,x,j);
                                        callback.processTriangle(vertices, x, j);
            } else {
               // first triangle
               getVertex(x, j, vertices[0]);
               getVertex(x, j + 1, vertices[1]);
               getVertex(x + 1, j, vertices[2]);
               // callback->processTriangle(vertices,x,j);
                                        callback.processTriangle(vertices, x, j);
               // second triangle
               getVertex(x + 1, j, vertices[0]);
               getVertex(x, j + 1, vertices[1]);
               getVertex(x + 1, j + 1, vertices[2]);
               // callback->processTriangle(vertices,x,j);
                                        callback.processTriangle(vertices, x, j);
            }
         }
      }

   }

   // / this returns the vertex in bullet-local coordinates
   private void getVertex(int x, int y, Vector3f vertex) {
      float height = getRawHeightFieldValue(x, y);

      switch (m_upAxis) {
      case 0: {
         vertex.set(height - m_localOrigin.getX(), (-m_width / 2.0f) + x, (-m_length / 2.0f) + y);
         break;
      }
      case 1: {
         vertex.set((-m_width / 2.0f) + x, height - m_localOrigin.getY(), (-m_length / 2.0f) + y);
         break;
      }

      case 2: {
         vertex.set((-m_width / 2.0f) + x, (-m_length / 2.0f) + y, height - m_localOrigin.getZ());
         break;
      }
      }

      vertex.x = vertex.x * m_localScaling.x;
      vertex.y = vertex.y * m_localScaling.y;
      vertex.z = vertex.z * m_localScaling.z;
   }

   @Override
   public void calculateLocalInertia(float arg0, Vector3f inertia) {
      inertia.set(0.f, 0.f, 0.f);
   }

   @Override
   public void getAabb(Transform t, Vector3f aabbMin, Vector3f aabbMax) {
      Vector3f halfExtents = Stack.alloc(Vector3f.class);
      halfExtents.set(m_localAabbMax);
      halfExtents.sub(m_localAabbMin);
      halfExtents.x = halfExtents.x * m_localScaling.x * 0.5f;
      halfExtents.y = halfExtents.y * m_localScaling.y * 0.5f;
      halfExtents.z = halfExtents.z * m_localScaling.z * 0.5f;

      /*Vector3f localOrigin(0, 0, 0);
      localOrigin[m_upAxis] = (m_minHeight + m_maxHeight) * 0.5f; XXX
      localOrigin *= m_localScaling;*/

      Matrix3f abs_b = Stack.alloc(t.basis);
      MatrixUtil.absolute(abs_b);

      Vector3f tmp = Stack.alloc(Vector3f.class);

      Vector3f center = Stack.alloc(t.origin);
      Vector3f extent = Stack.alloc(Vector3f.class);
      abs_b.getRow(0, tmp);
      extent.x = tmp.dot(halfExtents);
      abs_b.getRow(1, tmp);
      extent.y = tmp.dot(halfExtents);
      abs_b.getRow(2, tmp);
      extent.z = tmp.dot(halfExtents);

      Vector3f margin = Stack.alloc(Vector3f.class);
      margin.set(getMargin(), getMargin(), getMargin());
      extent.add(margin);

      aabbMin.sub(center, extent);
      aabbMax.add(center, extent);
   }

   @Override
   public Vector3f getLocalScaling(Vector3f arg0) {
      return m_localScaling;
   }

   @Override
   public String getName() {
      return "Terrain";
   }

   @Override
   public BroadphaseNativeType getShapeType() {
      return BroadphaseNativeType.TERRAIN_SHAPE_PROXYTYPE;
   }

   @Override
   public void setLocalScaling(Vector3f scaling) {
      m_localScaling = scaling;
   }

   // / This returns the "raw" (user's initial) height, not the actual height.
   // / The actual height needs to be adjusted to be relative to the center
   // / of the heightfield's AABB.

   private float getRawHeightFieldValue(int x, int y) {
      return m_heightfieldDataFloat[(y * m_heightStickWidth) + x] * m_heightScale;
   }

   public static int getQuantized(float x) {
      if (x < 0.0) {
         return (int) (x - 0.5);
      }
      return (int) (x + 0.5);
   }

   // / given input vector, return quantized version
   /**
    * This routine is basically determining the gridpoint indices for a given input vector, answering the question: "which gridpoint is closest to the provided point?".
    *
    * "with clamp" means that we restrict the point to be in the heightfield's axis-aligned bounding box.
    */
   private void quantizeWithClamp(int[] out, Vector3f clampedPoint) {

      /*
       * btVector3 clampedPoint(point); XXX
      clampedPoint.setMax(m_localAabbMin);
      clampedPoint.setMin(m_localAabbMax);
      
       * clampedPoint.clampMax(m_localAabbMax,);
      clampedPoint.clampMax(m_localAabbMax);
      clampedPoint.clampMax(m_localAabbMax);
      
      clampedPoint.clampMin(m_localAabbMin);
      clampedPoint.clampMin(m_localAabbMin); ///CLAMPS
      clampedPoint.clampMin(m_localAabbMin);*/

      out[0] = getQuantized(clampedPoint.getX());
      out[1] = getQuantized(clampedPoint.getY());
      out[2] = getQuantized(clampedPoint.getZ());
   }
}



d0md0md0m said:

This is a bad way to do this. Bullet contains a terrain shape that uses the raw data, its just not implemented in JBullet. Ive made a conversion that works:


Cool, thanks for the code! I will test this and put it in both the jme2 and jme3 versions.

Cheers,
Normen

added to all versions of jme-bullet and to the jbullet branch on jbullet-jme.googlecode.com

This is a newbie question.



I'm unable to add a terrain to my code. I'm using compiled version of jme-jbullet, because of the firewall I can't use CVS, SVN.



Here is my code.



2

  3 try {

  4  grassURL = new File("data/texture/grassb.png").toURI().toURL();

  5  pyramidURL = new File("data/texture/pyramid").toURI().toURL();

  6

  7 } catch (MalformedURLException e) {

  8  e.printStackTrace();

  9 }

10

11 MidPointHeightMap mph = new MidPointHeightMap(1024, 1.5f);

12 TerrainBlock terrainBlock = new TerrainBlock("midpoint block", mph.getSize(), new Vector3f(10, 1, 10), mph.getHeightMap(),

13 new Vector3f(100f, -50f, -100f));

14

15 terrainBlock.setDetailTexture(1, 16);

16

17   

18 TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();

19 ts.setTexture(TextureManager.loadTexture(grassURL,

20    Texture.MinificationFilter.BilinearNearestMipMap,

21    Texture.MagnificationFilter.Bilinear));

22 terrainBlock.setRenderState(ts);

23

24

25 terrainBlock.setModelBound(new BoundingBox());

26 terrainBlock.updateModelBound();

27

28

29 state.getRootNode().attachChild(terrainBlock);

30

31 MeshCollisionShape collisionShape = new MeshCollisionShape(terrainBlock);

32

33

34 PhysicsNode terrainPhysicsNode = new PhysicsNode(terrainBlock,

35 collisionShape, 0);

36 terrainPhysicsNode.setLocalTranslation(new Vector3f(0, -6, 0));

37

38 terrainPhysicsNode.rotateUpTo(new Vector3f(0, 1, 0));





I also wonder how add textures and colours to my physical cube. I tried to set colour like below but it didn't work, I see just a gray box.



                Box box = new Box("test box", Vector3f.ZERO, 1f, 1f, 1f);

box.setSolidColor(ColorRGBA.blue);



PhysicsNode boxPhysics = new PhysicsNode(box,

CollisionShape.ShapeTypes.MESH);

boxPhysics.setLocalTranslation(new Vector3f(3f, 0f, 0f));



state.getRootNode().attachChild(boxPhysics);

boxPhysics.updateRenderState();

PhysicsSpace.getPhysicsSpace().add(boxPhysics);



Sorry if I'm posting in wrong place.

Got the problem it's becoz I missed following 2 methods.

gameState.getRootNode().attachChild(terrainPhysics) and terrainPhysics.updateRenderState().



JBullet-Jme wiki is missing these methods. What wiki says is



RawHeightMap heightMap = new RawHeightMap(ActionGame.class
                .getClassLoader().getResource("heights.raw"),
                129, RawHeightMap.FORMAT_16BITLE, false);

TerrainBlock terrainBlock = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
                                           heightMap.getHeightMap(),
                                           new Vector3f(0, 0, 0));

MeshCollisionShape collisionShape=new MeshCollisionShape(terrainBlock);
PhysicsNode terrainPhysicsNode=new PhysicsNode(terrainBlock, collisionShape,0);

terrainPhysicsNode.setLocalTranslation(new Vector3f(-300,-10,-300));
terrainPhysicsNode.rotateUpTo(new Vector3f(0,1,0));
PhysicsSpace.getPhysicsSpace().add(terrainPhysicsNode);




Sorry again for double posting
I would recommend it to change


RawHeightMap heightMap = new RawHeightMap(ActionGame.class
                .getClassLoader().getResource("heights.raw"),
                129, RawHeightMap.FORMAT_16BITLE, false);

TerrainBlock terrainBlock = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
                                           heightMap.getHeightMap(),
                                           new Vector3f(0, 0, 0));

MeshCollisionShape collisionShape=new MeshCollisionShape(terrainBlock);
PhysicsNode terrainPhysicsNode=new PhysicsNode(terrainBlock, collisionShape,0);

terrainPhysicsNode.setLocalTranslation(new Vector3f(-300,-10,-300));
terrainPhysicsNode.rotateUpTo(new Vector3f(0,1,0));

gameState.getRootNode().attachChild(terrainPhysicsNode); // Missing in the wiki
terrainPhysicsNode.updateRenderState();                            // Missing in the wiki

PhysicsSpace.getPhysicsSpace().add(terrainPhysicsNode);



 
I think it should be fixed because it's very confusing for new beginners.

Hi,



is it possible that anybody can make an example how to handle the HeightfieldTerrainShape? Maybe I'm to stupid but I can't get it into my game. I'm using JME2.



greetz

Bastard