[solved] ode physics with a heightfield terrain

greetings,

Was trying to set up a scene where a box falls down against a heightfield terrain generated by a GeoMipTerrain. This using solely ODE physics. I’ve been unable, so far, to get a collision between the box and the trimesh generated for the terrain. Searched around for quite a while and couldn’t find something to solve it so far. Any ideas bout what i’m doing wrong or what could be the issue? My suspicions are on the generated trimesh although it does print out to have 30k + triangles.

the code is based on the boxes manual example.

import math, os, sys
from direct.directbase import DirectStart
from pandac.PandaModules import Filename

# finds what path we're in
PATH = os.path.abspath(sys.path[0])
PATH = Filename.fromOsSpecific(PATH).getFullpath()
from pandac.PandaModules import OdeWorld, OdeSimpleSpace, OdeJointGroup
from pandac.PandaModules import OdeBody, OdeMass, Quat, CardMaker, Vec4
from pandac.PandaModules import OdePlaneGeom, BitMask32, OdeBoxGeom, GeoMipTerrain,OdeTriMeshData,OdeTriMeshGeom

myworld = OdeWorld()
myworld.setGravity(0, 0, -9.81)

#Cube Construction and placing in scene graph
#cube is 1 unit size
cube = loader.loadModel("box.egg")
cube.reparentTo(render)
cube.setColor(0.2, 0.5, 0)
cube.setPos(70, 60 , 70)
cube.setScale(2)

bbody = OdeBody(myworld)
bbodyMass = OdeMass()
bbodyMass.setBox(11340, 2, 2, 2)
bbody.setMass(bbodyMass)
bbody.setPosition(cube.getPos(render))
bbody.setQuaternion(cube.getQuat(render))

#creates terrain
terrain = GeoMipTerrain("terrain")
terrain.setHeightfield(Filename(PATH + "/resources/terrains/height2.png"))
terrain.getRoot().reparentTo(render)
terrain.getRoot().setSz(30)
terrain.setColorMap(Filename(PATH + "/resources/models/ground.jpg"))
terrain.generate()

#collisions setup
space = OdeSimpleSpace()
space.setAutoCollideWorld(myworld)
contactGroup = OdeJointGroup()
space.setAutoCollideJointGroup(contactGroup)

#colisionGeoms
modelTrimesh = OdeTriMeshData(terrain.getRoot(), True)
modelGeom = OdeTriMeshGeom(space, modelTrimesh)
modelGeom.setCollideBits(BitMask32(0x00000001))
modelGeom.setCategoryBits(BitMask32(0x00000002))

boxGeom = OdeBoxGeom(space,0.5, 0.5, 0.5)
boxGeom.setCollideBits(BitMask32(0x00000002))
boxGeom.setCategoryBits(BitMask32(0x00000001))
boxGeom.setBody(bbody)

#only 1 material type for now
myworld.initSurfaceTable(1)
#surfaceId1 collides with surfaceId2 (physical properties of that collision)
#setSurfaceEntry(surface Id1, surfaceId2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen)
myworld.setSurfaceEntry(0, 0, 0.0, 1.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

#camera placing
base.cam.setPos(50, -30 , 70)
base.cam.lookAt(50, -30 , 70)

stepSize = 1.0/90.0

#task for running the simulation
def simTask(task):
    contactGroup.empty()
    print "contacts number: %d" %space.autoCollide() #setup the contact joints
    #updates positions
    cube.setPosQuat(render, bbody.getPosition(), Quat(bbody.getQuaternion()))
    #runs a physic simulation step
    myworld.quickStep(stepSize)   
    return task.cont

#task for physical simulation test added to taskManager
taskMgr.doMethodLater(1.0, simTask,"simulation")

run()

i dont know about ode. but i still think i know why it doesnt work for you.
geomipterrain is not a static trimesh. i changes its level of detail and triangulations depending on your focal node. since ode might(i dunno about) require to use static meshes this will not work well together.
you can try to create 2nd geomipterrain with the same heighfield but with brute-force-settings. and pass this one to ode. decreasing blocksize for the collision mesh might help if the collision is too slow.

if some ode guru would join in here and continue it would be apprechiated

had tried using brute force before but not decreasing the block size. Tried with brute force on and block size of 1 (yep, got slow as hell hehe) but, the box still got through. A collision was never detected and so the contact joints group was always at 0 count.
Thanks for the tips though, even if i get this problem solved those tips still help for later on :slight_smile:

based on the manual example of the boxes falling, altered it to insert an heightfield instead of a cardmaker. After fooling around a bit, the boxes collided with something. changed the position of the OdeTriMeshGeom of the terrain and it seemed to coincide with the changes. The thing is, it’s in a different odeworld position than the scene graph visual one and it also seems much smaller (can’t know decently bout the shape cause it’s invisible :slight_smile:, so it might be off position and just be a piece of the bigger mesh ). (it was done with ‘true’ bruteforce)
So am pretty much lost on the underlying workings so far :slight_smile:, any more insight that could help figure it out is appreciated.

edit: does the OdeJointGroup has a limit/size? can it be changed if it has?

ok, after researching a bit more everywhere seems i found part of the issues and uncovered others :stuck_out_tongue:. You were right, it has to do with the differences in meshes treatment from ode and geomip. Summarizing - what i’ve been writing down about to later remember - apparently only the first block is being passed down as a colidable area, the heightfield is 256x256. Creating it with a block size of 256 created a first block of 128x128 “quads?” and only that area was passed to ode, if i moved the region so the box hitted another block, no colision happened. Thought it could be related to the focal point then somehow but seems unrelated. Also the simulation detects the colision fine, just only on that first block.
just an update :slight_smile:. Still, more ideas on approaches and such are welcome.

oh, another thing. Scaling the heightfield in Z wasn’t passed to the trimesh data for the geom, it kept the original z (making a collision happen under the terrain surface). Haven’t searched much bout this part yet.

What about making a copy of the terrain root, then call flattenStrong on that, then passing that to ODE? flattenStrong merges all the blocks into one, and also applies the Z scale on the vertices.

might work, will test some stuff out and say something sometime soon. Was rereading about flatten and i still dont quite understand completly what it does. And thats probably the reason behind creating a copy of terrain root that i didnt understood why :slight_smile:.
using the render.ls() and terrain.getRoot().ls() to see the node hierarchy showed it with only 1 node.

If you call ls() and it has just one node, you probably didn’t call generate() yet.

terrain = GeoMipTerrain("terrain")
terrain.setHeightfield(Filename("./resources/terrains/height2.png"))
terrain.getRoot().reparentTo(render)
terrain.setFocalPoint(boxes[0][0])
terrain.setAutoFlatten(GeoMipTerrain.AFMOff)
terrain.setBlockSize(32)
terrain.getRoot().setPos(-70, -90, -20)
terrain.getRoot().setSz(15)
terrain.setColorMap(Filename("./resources/models/ground.jpg"))
terrain.generate()

terrainToOde = terrain.getRoot().copyTo(render)
terrainToOde.hide()
terrainToOde.flattenStrong()

modelTrimesh = OdeTriMeshData(terrainToOde, True)
modelGeom = OdeTriMeshGeom(space, modelTrimesh)
modelGeom.setPosition(terrainToOde.getPos(render))
modelGeom.setCollideBits(BitMask32(0x00000001))
modelGeom.setCategoryBits(BitMask32(0x00000002))

this seemed to pretty much work fine. thanks :slight_smile:.
ok, disregard the previous post about flatten. was listing before generating “oops”. Will try to understand it better in the meanwhile.
edit: yep, was that (was posting at the same time as you :stuck_out_tongue:.

edit2: So… passing the unflattened terrain first was making the trimeshdata to only pick up the first child ? and flatten strong combined all nodes into 1, applying the individual node transforms respectively, turning it into “1 node”/“1 static geometry”?

Dunno how the ODE trimesh thing works, but I think it just grabs the first GeomNode it can find. With flattenStrong, you make sure there is just one Geom instead of one per chunk.

I recommend to even remove the terrainToOde mesh when you’ve converted it into a trimesh, using removeNode().