Character Slipping Through The Floor

I’ve just started building a 3d platformer using Panda. I’ve got a blender world model imported and got a set of Bullet collision objects created to match the geometry. I’ve also got a nice bullet character controller barely working. I have two issues.

First, the collision seems to be working pretty good, but every once and a while the character falls about half a unit through the ground in areas that look like they are always around where two collision triangles meet. Here is a screenshot of it happening It is literally like there are tiny ditches in the ground. I’ve checked the character’s isOnGround() method and it appears he leaves the ground for a frame every once and a while when this happens.

This is the code where I setup the collision: = BulletWorld(), 0, -9.81))

self.environ = self.loader.loadModel("models/base")
self.environ.setScale(1, 1, 1)
self.environ.setPos(0, 0, 0)

self.geomCollect = self.environ.findAllMatches('**/+GeomNode')

self.col = BulletTriangleMesh()

for np in self.geomCollect:
	for i in range(np.node().getNumGeoms()):
		geom = np.node().getGeom(i)

shape = BulletTriangleMeshShape(self.col, dynamic=False)
node = BulletRigidBodyNode('Base')
self.taskMgr.add(self.update, 'update')

Secondly, the character controller is working fine for moving linearly, but when I set the angular movement it just responds in chunks of movement. I don’t see what is different between the two methods.

Here is the code for input:

	self.accept('w', self.processInput, ['w'])		
	self.accept('a', self.processInput, ['a'])		
	self.accept('s', self.processInput, ['s'])		
	self.accept('d', self.processInput, ['d'])
	self.accept('w-up', self.processInput, ['w-up'])
	self.accept('a-up', self.processInput, ['a-up'])	
	self.accept('s-up', self.processInput, ['s-up'])	
	self.accept('d-up', self.processInput, ['d-up'])
	self.accept('space', self.processInput, ['space'])

def processInput(self, key):
	if key == 'w':
		self.playerNP.node().setLinearMovement(Vec3(0, 10, 0), True)
	elif key == 's':
		self.playerNP.node().setLinearMovement(Vec3(0, -10, 0), True)
	elif key == 'a':
	elif key == 'd':
	elif key == 'a-up' or  key == 'd-up':
	elif key == 'w-up' or key == 's-up':
		self.playerNP.node().setLinearMovement(Vec3(0, 0, 0), True)
	elif key == 'space':

Any help is much appreciated. Panda has been really easy to work with so far. Just these few kinks.

About the chunky character rotation:

The reason for this is that angular movement and linear movement are handled a bit differently internally.

When calling setLinearMovement the velocity passed as argument is stored internally, and this internal value is not modified until setLinearMovement is called again.

When calling setAngularMovement the omega value is also stored internally, but this internal value is reset the next time the Panda3D scene graph and Bullet get synchronized (i. e. world.doPhysics is called).
So in your case the character rotates for one frame only when key down events get created.

I don’t remember if there has been a reason to handle linear and angular movement differently. I think the original intent has been to reset both after a scene graph sync.

Anyway, to fix this I would recomment to call both setAngularMovement and setLinearMovement once each frame, i. e. like this:

  def processInput(self):
    speed = Vec3(0, 0, 0)
    omega = 0.0

    if inputState.isSet('forward'): speed.setY( 2.0)
    if inputState.isSet('reverse'): speed.setY(-2.0)
    if inputState.isSet('left'):    speed.setX(-2.0)
    if inputState.isSet('right'):   speed.setX( 2.0)
    if inputState.isSet('turnLeft'):  omega =  120.0
    if inputState.isSet('turnRight'): omega = -120.0

    self.playerNP.node().setLinearMovement(speed, True)

About the triangle meshes:

Bullet is, like any other collision/physics system too, very touchy when it comes to tiny gaps between triangles, degenerated triangles or “thin and long” triangles.

First you might want to set the “remove_duplicate_vertices” parameter to TRUE when calling addGeom. On a sidenote you migh also want to pass the local transform between the model root NodePath and the Geom:

self.geomCollect = self.environ.findAllMatches('**/+GeomNode')

self.col = BulletTriangleMesh()

for np in self.geomCollect:
   ts = np.getTransform(self.environ)
   for i in range(np.node().getNumGeoms()):
      geom = np.node().getGeom(i)
      self.col.addGeom(geom, True, ts)

Next, you might want to experiment with welding, i. e. automatically merging nearby vertices into one vertex. This way you will get rid of most degenerated triangles. You have to provide a welding distance before adding triangles:

self.col = BulletTriangleMesh()
self.col.setWeldingDistance(0.2) # ??? depends on you linear scales, experiment yourself



Welding won’t help in all cases, and it might produce bumpy edges. The prefered way would be to create good collision meshes as part of your modelling process. a good collision mesh

  • has less vertices than the mesh used for rendering (better performance)
  • has only triangles, and no quads or higher polygons
  • does not have “thin” triangles
  • does not have degenerated triangles
  • does not have duplicate vertices or vertices very close to each other
  • does not have small gaps

Thank you. That was extremely helpful. It cleared up the chunky movement problem immediately. I’m still having a little trouble with the collision, but now I’ve got something to work with.

When I use the,

ts = np.getTransform(self.environ)

line and pass it to .addGeom it seems to consider it 2 arguments. I get an error that I’m passing 4 arguments to the method. Other than that, I got rid of a quad that was in model and things seem to be working much better.

Thanks for your help.

Everything seems to be working good now. I believe it may have been a scale issue. I was using extremely large triangles. Not necessarily sharp triangles, but they were huge. Now that I subdivided the model a little more he seems to have no problem staying on the ground.

The .getTransform call still doesn’t work right though. I actually don’t understand exactly what it was for so I’m not sure how to troubleshoot it ; )

Thanks again. Onwards and upwards!

Glad I could help.

About self.col.addGeom(geom, True, ts): Well, this has been my fault. I added the last, optional TransformState parameter 18 month ago. … h?view=log

Because of ABI compatibility this API change has not been picked up for PandaD 1.8.x. The first version of Panda3D which can pick it up will be 1.9.0. Of course you can always use the daily snapshot builds.