Physics update after modifying height data of TerrainQuad / TerrainGrid

Hello



I have been trying to do the same as eemerge for a few night now but I have not been able to get pass a particular point.



I have untilised the TerrainTestModifyHeight example and applied this to a terrain grid landscape. So in the same way that eemerge has alterted the terrain I want to do this but have the collision match. I have the follow code to do this which I have been thinking is the same as eemerges

[java]

m_terrain.adjustHeight(locs, heights);

//System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms");

//m_terrain.updateModelBound();





TerrainQuad localTerrainQuad = m_terrain.getGridTileLoader().getTerrainQuadAt(loc);



m_bulletAppState.getPhysicsSpace().remove(localTerrainQuad);



while(localTerrainQuad.getControl(RigidBodyControl.class)!=null)

{

localTerrainQuad.removeControl(RigidBodyControl.class);

}



localTerrainQuad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(localTerrainQuad.getHeightMap(), m_terrain.getLocalScale()), 0));



m_bulletAppState.getPhysicsSpace().add(localTerrainQuad);

[/java]



But the problem I have is that when I get to the call

[java]

m_bulletAppState.getPhysicsSpace().remove(localTerrainQuad);

[/java]



it crashs with a null pointer exception.



Now I have been thinking that this might be because the call getTerrainQuadAt(loc); is not returning me the correct terrainQuad to perform this and therefore crash with null pointer?



If I wanted to get the terrainQuad from the CollisionResult hit that is returned inside the function getWorldIntersection() taken from the example, I am not sure how to traverse the parent scene grafe to get to the correct terrainQuad. I code in c++ by day but new to Jmonkeyengine and Java.



I would really like to know why I am getting a null pointer exception here, but have been think during the day is there no way of altering the HeightfieldCollisionShape directly to match the changes that can be made in the call terraingrid.adjustHeight it seems very expensive to do all this removing and recreate when the I think the collisions heightFeild just needs to be altered to match.



Any help would be most appreciated.

@mage said:
Any help would be most appreciated.

NPE is easy to track. Check the line where it occurred, find out which object thats used there is null and then find out why. To help you with that we'd need more of your code and the exception stack trace.

Posting the full stack trace of the null pointer exception will help track what is null. Otherwise assumptions on what are causing it are just guesswork. And if it is the line “m_bulletAppState.getPhysicsSpace().remove(localTerrainQuad);”, then break it out into two lines and see what is null.

pwnt :stuck_out_tongue:

Ok here is the whole function that is deforming the terrain.

[java]

private void adjustHeight(Vector3f loc, float radius, float height)

{



// offset it by radius because in the loop we iterate through 2 radii

int radiusStepsX = (int) (radius / m_terrain.getLocalScale().x);

int radiusStepsZ = (int) (radius / m_terrain.getLocalScale().z);



float xStepAmount = m_terrain.getLocalScale().x;

float zStepAmount = m_terrain.getLocalScale().z;

long start = System.currentTimeMillis();

List<Vector2f> locs = new ArrayList<Vector2f>();

List<Float> heights = new ArrayList<Float>();



for (int z = -radiusStepsZ; z < radiusStepsZ; z++) {

for (int x = -radiusStepsX; x < radiusStepsX; x++) {



float locX = loc.x + (x * xStepAmount);

float locZ = loc.z + (z * zStepAmount);



if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {

// see if it is in the radius of the tool

float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z);

locs.add(new Vector2f(locX, locZ));

heights.add(h);

}

}

}





m_terrain.adjustHeight(locs, heights);

//System.out.println(“Modified “+locs.size()+” points, took: " + (System.currentTimeMillis() - start)+” ms");

//m_terrain.updateModelBound();





TerrainQuad localTerrainQuad = m_terrain.getGridTileLoader().getTerrainQuadAt(loc);



PhysicsSpace physicSpaceLocal = m_bulletAppState.getPhysicsSpace();

physicSpaceLocal.remove(localTerrainQuad);



while(localTerrainQuad.getControl(RigidBodyControl.class)!=null)

{

localTerrainQuad.removeControl(RigidBodyControl.class);

}



localTerrainQuad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(localTerrainQuad.getHeightMap(), m_terrain.getLocalScale()), 0));



m_bulletAppState.getPhysicsSpace().add(localTerrainQuad);

}

[/java]



I have broken up the line “m_bulletAppState.getPhysicsSpace().remove(localTerrainQuad);”,

into

[java]

PhysicsSpace physicSpaceLocal = m_bulletAppState.getPhysicsSpace();

physicSpaceLocal.remove(localTerrainQuad);

[/java]



I put a break point in just before " physicSpaceLocal.remove(localTerrainQuad);" and found the variables “localTerrainQuad” and "physicSpaceLocal " are both valid. The exception happens inside com.jme3.bullet.PhysicsSpace.remove(PhysicsSpace.java:403)

[java]

public void remove(Object obj) {

if (obj instanceof PhysicsControl) {

((PhysicsControl) obj).setPhysicsSpace(null);

} else if (obj instanceof Spatial) {

Spatial node = (Spatial) obj;

PhysicsControl control = node.getControl(PhysicsControl.class);

line403 control.setPhysicsSpace(null);

} else if (obj instanceof PhysicsCollisionObject) {

removeCollisionObject((PhysicsCollisionObject) obj);

} else if (obj instanceof PhysicsJoint) {

removeJoint((PhysicsJoint) obj);

} else {

throw (new UnsupportedOperationException(“Cannot remove this kind of object from the physics space.”));

}

}

[/java]



After just copying and pasting this in I just reliased that there is a exception becuase the terrainQuad passed in does not have a PhysicsControl therefore it crashs here.



This is where I am not sure what to do now? Does this mean that the terrainquad passed is the wrong terrain quad ?



Here is the full exception trace.



13-Jun-2012 22:07:43 com.jme3.app.Application handleError

SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]

java.lang.NullPointerException

at com.jme3.bullet.PhysicsSpace.remove(PhysicsSpace.java:403)

at robothorde.LandScape.adjustHeight(LandScape.java:279)

at robothorde.LandScape.AdjustTerrainUpdate(LandScape.java:221)

at robothorde.LandScape.update(LandScape.java:363)

at robothorde.GameStates.Game.update(Game.java:228)

at com.jme3.app.state.AppStateManager.update(AppStateManager.java:255)

at com.jme3.app.SimpleApplication.update(SimpleApplication.java:241)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:149)

at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:182)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:223)

at java.lang.Thread.run(Thread.java:662)

13-Jun-2012 22:07:43 com.jme3.renderer.lwjgl.LwjglRenderer cleanup

INFO: Deleting objects and invalidating state

13-Jun-2012 22:07:43 com.jme3.scene.Node detachChildAt

INFO: Gui Node (Node): Child removed.

13-Jun-2012 22:07:43 com.jme3.scene.Node detachChildAt

INFO: Gui Node (Node): Child removed.

13-Jun-2012 22:07:43 com.jme3.input.lwjgl.LwjglMouseInput destroy

INFO: Mouse destroyed.

13-Jun-2012 22:07:43 com.jme3.input.lwjgl.LwjglKeyInput destroy

INFO: Keyboard destroyed.

13-Jun-2012 22:07:43 com.jme3.system.lwjgl.LwjglAbstractDisplay deinitInThread

INFO: Display destroyed.

Alright so if you want to get a terrain quad you do not want to use m_terrain.getGridTileLoader().getTerrainQuadAt(). The TerrainGridTileLoader will re-load that terrain tile from disk; you already have it there in memory though, so just use what you have.

[java]

TerrainQuad localTerrainQuad = m_terrain;

[/java]

TerrainGrid is a TerrainQuad, it will just load in the 4 top tiles as you move.

Hi Sploreg



Thanks for the reply,



I tryed the above and put “TerrainQuad localTerrainQuad = m_terrain;” into the adjustHeight function but I still get the same NPE as in th exception trace above.



Maybe I need to find the TerrainQuad from the CollisionResult hit inside getWorldIntersection()

[java]

private Vector3f getWorldIntersection()

{

Camera cam = m_app.getCamera();

Vector3f origin = cam.getWorldCoordinates(new Vector2f(m_ScreenWidth / 2, m_ScreenHeight / 2), 0.0f);

Vector3f direction = cam.getWorldCoordinates(new Vector2f(m_ScreenWidth / 2, m_ScreenHeight / 2), 0.3f);



direction.subtractLocal(origin).normalizeLocal();



Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

int numCollisions = m_terrain.collideWith(ray, results);

if (numCollisions > 0) {

CollisionResult hit = results.getClosestCollision();

return hit.getContactPoint();

}

return null;

}

[/java]



But I am not sure how to traversl upwards from the hit.geometry to the TerrainQuad that i need.

Nothing like sitting down with the debugger and working out whats actually happing. I final found out that the NPE was happening becuase I was not using the TerrainQuad that had the RigidBodyControl attached to it . All I need todo was find the TerrainQuad from the collisionHit geometry parents and use this in the adjustHeight function. It all works fine now.



[java]

private Vector3f getWorldIntersection()

{

Camera cam = m_app.getCamera();

Vector3f origin = cam.getWorldCoordinates(new Vector2f(m_ScreenWidth / 2, m_ScreenHeight / 2), 0.0f);

Vector3f direction = cam.getWorldCoordinates(new Vector2f(m_ScreenWidth / 2, m_ScreenHeight / 2), 0.3f);



direction.subtractLocal(origin).normalizeLocal();



Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

int numCollisions = m_terrain.collideWith(ray, results);

if (numCollisions > 0) {



CollisionResult hit = results.getClosestCollision();

Node Parent = hit.getGeometry().getParent();

while(Parent != null) {

if(Parent.getControl(RigidBodyControl.class)!= null){

m_hitTerrainQuad = (TerrainQuad)Parent;

Parent = null;

}else{

Parent = Parent.getParent();

}

}



return hit.getContactPoint();

}

return null;

}

[/java]



[java]

private void adjustHeight(Vector3f loc, float radius, float height)

{



// offset it by radius because in the loop we iterate through 2 radii

int radiusStepsX = (int) (radius / m_terrain.getLocalScale().x);

int radiusStepsZ = (int) (radius / m_terrain.getLocalScale().z);



float xStepAmount = m_terrain.getLocalScale().x;

float zStepAmount = m_terrain.getLocalScale().z;

long start = System.currentTimeMillis();

List<Vector2f> locs = new ArrayList<Vector2f>();

List<Float> heights = new ArrayList<Float>();



for (int z = -radiusStepsZ; z < radiusStepsZ; z++) {

for (int x = -radiusStepsX; x < radiusStepsX; x++) {



float locX = loc.x + (x * xStepAmount);

float locZ = loc.z + (z * zStepAmount);



if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {

// see if it is in the radius of the tool

float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z);

locs.add(new Vector2f(locX, locZ));

heights.add(h);

}

}

}



m_terrain.adjustHeight(locs, heights);

//System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms");

m_terrain.updateModelBound();



m_bulletAppState.getPhysicsSpace().remove(m_hitTerrainQuad);

m_hitTerrainQuad.removeControl(RigidBodyControl.class);



m_hitTerrainQuad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(m_hitTerrainQuad.getHeightMap(), m_terrain.getLocalScale()), 0));

m_bulletAppState.getPhysicsSpace().add(m_hitTerrainQuad);

}

[/java]

@Sploreg said: woa woa wait, why are you hitting the buffer directly to modify the terrain??? Use the terrain API to set the heights and everything will work out fine.

Hi, sorry for bringing this old thread up, but why is that so? I went the same route as @eemerge and was pulling my hair out because everything was displayed correctly (meaning the TerrainPatch was perfectly raised/lowered) and everything seemed fine until I realized the physics were the old heightMap’s and never got updated after I changed the buffers. I tried for hours until I come here and read that the only way to correctly update the TerrainQuad’s heightMap is by calling the TerrainQuad.adjustHeight() function. I actually went in source code to see why simply updating the mesh buffers didn’t do the job even tough (as per stated in the documentation) I was calling the appropriate vb.setUpdateNeeded(), Mesh.updateBound() and Geometry.updateModelBound() in the appropriate order… I have had luck with every other Spatials but not TerrainQuads. In the JME3 source code I can see that the only function we can’t call from the outside and that seems to be making a difference here is the TerrainQuad.setNormalRecalcNeeded() so if this was exposed, could we deal with the buffers instead? I had to do additional work to get the TerrainQuad’s coordinates right, because my system is all based on triangle indexes so I would have liked to use the buffers so that my application remains as uniform as possible throughout.

PS: There is a mistake in the advanced custom mesh shapes documentation. I fixed it… it’s not updateBounds() it’s: updateBound() here: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:custom_meshes#dynamic_meshes

@.Ben. said: Hi, sorry for bringing this old thread up, but why is that so? I went the same route as @eemerge and was pulling my hair out because everything was displayed correctly (meaning the TerrainPatch was perfectly raised/lowered) and everything seemed fine until I realized the physics were the old heightMap's and never got updated after I changed the buffers. I tried for hours until I come here and read that the only way to correctly update the TerrainQuad's heightMap is by calling the TerrainQuad.adjustHeight() function. I actually went in source code to see why simply updating the mesh buffers didn't do the job even tough (as per stated in the documentation) I was calling the appropriate vb.setUpdateNeeded(), Mesh.updateBound() and Geometry.updateModelBound() in the appropriate order... I have had luck with every other Spatials but not TerrainQuads. In the JME3 source code I can see that the only function we can't call from the outside and that seems to be making a difference here is the TerrainQuad.setNormalRecalcNeeded() so if this was exposed, could we deal with the buffers instead? I had to do additional work to get the TerrainQuad's coordinates right, because my system is all based on triangle indexes so I would have liked to use the buffers so that my application remains as uniform as possible throughout.

PS: There is a mistake in the advanced custom mesh shapes documentation. I fixed it… it’s not updateBounds() it’s: updateBound() here: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:custom_meshes#dynamic_meshes

Physics collision shapes and the mesh are always separate, you just create a collision shape from the mesh data at the beginning, so you have to update the other when one of them changes.

I know, the issue is not about having forgotten to update the physicsSpace. It just seems it’s impossible to update the physics or controls of a TerrainQuad after modifying its mesh through the vertex buffers directly. Therefore, it seems we HAVE TO use the TerrainQuad.adjustHeight() function for the physics to pick it up, else no matter what the physics will be the old mesh ones. What I’m inquirying about is the fact that it should be possible to call a function like TerrainQuad.setNormalRecalcNeeded() or some other protected function that would lead us to be able to edit the buffers instead.

@.Ben. said: I know, the issue is not about having forgotten to update the physicsSpace. It just seems it's impossible to update the physics or controls of a TerrainQuad after modifying its mesh through the vertex buffers directly. Therefore, it seems we *HAVE TO* use the TerrainQuad.adjustHeight() function for the physics to pick it up, else no matter what the physics will be the old mesh ones. What I'm inquirying about is the fact that it should be possible to call a function like TerrainQuad.setNormalRecalcNeeded() or some other protected function that would lead us to be able to edit the buffers instead.

The thing is that we use the more efficient heightmap for terrain physics which needs heightmap data,not mesh data.

Thank you for the explanation, makes sense I guess. At least, we have a solution, which is to convert the triangle positions to world positions and then we feed them to the TerrainQuad.adjustHeight() function and everything works fine. Thx.

Thanks for the wiki update.
As Normen said, you must update the collision mesh if you update the terrain.
You can edit the terrain by the mesh, nothing is stopping you from doing that. It is just not intended. You will have to seam the normals yourself and I won’t be exposing any more of that normal recalculation API as it is really just for the internal uses of normal recalculation with terrain height modification.

@normen said: The thing is that we use the more efficient heightmap for terrain physics which needs heightmap data,not mesh data.

This only applies for normally scaled objects btw,
eg a 100x100 cube one a heightmap will bring stuff to a crippling hold, as the main benefit of it is that it can use directly the heightmap data and that way saves several 100 mb of ram. (it would need to lookup 10000 points per tick then it sumy up to quite some time)

@Empire Phoenix said: This only applies for normally scaled objects btw, eg a 100x100 cube one a heightmap will bring stuff to a crippling hold, as the main benefit of it is that it can use directly the heightmap data and that way saves several 100 mb of ram. (it would need to lookup 10000 points per tick then it sumy up to quite some time)

Huge meshes don’t work exactly well either… Given the same amount of points a heightmap performs MUCH better than a mesh.

On contact yes, but hovering in boundingbox with no contact not. As Heightmap cannot early discard this, while the acceleration structure of the MeshShape is capable of doing this.
(For example when a spaceship flyes trough a canyon on a planet surface)

I did test exactly that scenario with a 4x4 points map, and found the heightmap to be unbearable slow, while I got nearly no slowdowns with a meshshape
Anyway that is probably not the topic of the thread anymore.