Panda PhysX

Sorry for the slow response - I have been away for the weeend.

You are right, the shapeDesc going out of scope is causing the problem. When call actorDesc.addShape is just reads the NxShapeDesc pointer and hands it over to the NxActorDesc. PhysxActorDesc does not retain a PT of the PhysxShapeDesc.

The descriptors are not meant to be kept for a longer time, and they do not inhierit e. g. from TypedReferenceCount. As a general rule you should keep around all descriptors until the actual object is created, and then throw them away.

The actual objects (PhysxActor, PhysxMaterial, PhysxShape, …) are reference counted, and I store a reference to these objects in whatever other object owns them, so e. g. a PhysxActor instance can go out of scope without the instance being deleted.

The PhysX SDK 3.0 is out now.

I had a quick glance at the documentation, and it seems quite interesting. However, it is also a full rerwrite of the API. Not only the names for all the classes have changed, but also the object hierarchies and several other details.

For now there is only a Windows SDK available. Linux and OSX will follow several months later. At least for far it has been like this.

The huge change in the PhysX API makes it unreasonable to just “upgrade” the existing Panda3D PhysX module. I think a full rewrite based on the lessons learned with the current PhysX integration and the emergin Bullet integration is the way to go.

Don’t try to compile the current Panda3D source code using the PhysX SDK 3.0. It won’t compile.

Hello! I’m having troubles with groups and groups masks.

import direct.ffi.panda3d

import direct.directbase.DirectStart

from panda3d.core import Vec3
from panda3d.core import Point3

from panda3d.physx import PhysxManager
from panda3d.physx import PhysxScene
from panda3d.physx import PhysxSceneDesc
from panda3d.physx import PhysxBodyDesc
from panda3d.physx import PhysxActorDesc
from panda3d.physx import PhysxBoxShapeDesc
from panda3d.physx import PhysxPlaneShapeDesc
from panda3d.physx import PhysxRay
from panda3d.physx import PhysxMask
from panda3d.physx import PhysxGroupsMask

from base import BaseWorld


class World(BaseWorld):

  def __init__(self):
    BaseWorld.__init__(self)

  def setup(self):
      
    # Scene
    sceneDesc = PhysxSceneDesc()
    sceneDesc.setGravity(Vec3(0, 0, -9.81))

    self.scene = PhysxManager.getGlobalPtr().createScene(sceneDesc)

    # Plane
    shapeDesc = PhysxPlaneShapeDesc()
    shapeDesc.setPlane(Vec3(0, 0, 1), -2)

    actorDesc = PhysxActorDesc()
    actorDesc.addShape(shapeDesc)

    self.planeActor = self.scene.createActor(actorDesc)

    # Box
    self.boxNP = loader.loadModel('models/box.egg')
    self.boxNP.reparentTo(render)

    shapeDesc = PhysxBoxShapeDesc()
    shapeDesc.setDimensions(Vec3(0.5, 0.5, 0.5))

    bodyDesc = PhysxBodyDesc()
    bodyDesc.setMass(10.0)

    actorDesc = PhysxActorDesc()
    actorDesc.setBody(bodyDesc)
    actorDesc.setName('Box')
    actorDesc.addShape(shapeDesc)
    actorDesc.setGlobalPos(Point3(0, 0, -1.5))

    self.boxActor = self.scene.createActor(actorDesc)
    self.boxActor.attachNodePath(self.boxNP)
    
    # Scene collision filtering expression: (G0 & G1) == True.
    self.scene.setFilterConstant0(PhysxGroupsMask.allOff())
    self.scene.setFilterConstant1(PhysxGroupsMask.allOff()
    self.scene.setFilterOps(op0 = self.scene.FOOr
                           ,op1 = self.scene.FOOr
                           ,op2 = self.scene.FOAnd)
    self.scene.setFilterBool(True)

    # Main groups mask.
    GROUPSMASK1 = PhysxGroupsMask().allOff()
    GROUPSMASK1.setBits0(1)
    
    # Another groups mask.
    GROUPSMASK2 = PhysxGroupsMask().allOff()
    GROUPSMASK2.setBits0(2)
    
    # Box in group 0.
    self.boxActor.setGroup(0)
        
    # Box and plane in groups mask 1.
    self.boxActor.getShape(0).setGroupsMask(GROUPSMASK1)
    self.planeActor.getShape(0).setGroupsMask(GROUPSMASK1)
    
    # Ray
    ray = PhysxRay()
    ray.setOrigin(Point3(0, -1, -1.5))
    ray.setDirection(Vec3(0, 1, 0))
    ray.setLength(5.0)
    
    # Raycast 1.
    group0 = PhysxMask.allOff()
    rayhit = self.scene.raycastClosestShape(ray
                                           ,shapesType=PhysxScene.STAll
                                           ,mask=group0
                                           ,groups=GROUPSMASK1)
    
    print "raycast 1 hit? ", not rayhit.isEmpty()
    
    # Raycast 2.
    group0 = PhysxMask.allOff()
    group0.setBit(0)
    rayhit = self.scene.raycastClosestShape(ray
                                           ,shapesType=PhysxScene.STAll
                                           ,mask=group0
                                           ,groups=GROUPSMASK1)
    print "raycast 2 hit? ", not rayhit.isEmpty()
    
    # Raycast 3.
    group0 = PhysxMask.allOff()
    group0.setBit(0)
    rayhit = self.scene.raycastClosestShape(ray
                                           ,shapesType=PhysxScene.STAll
                                           ,mask=group0
                                           ,groups=GROUPSMASK2)
    print "raycast 2 hit? ", not rayhit.isEmpty()

  def update(self, task):
    dt = globalClock.getDt()
    self.scene.simulate(dt)
    self.scene.fetchResults()
    return task.cont

  def cleanup(self):
    self.boxNP.removeNode()
    self.scene.release()
    render.ls()

if __name__ == '__main__':
  world = World()
  run()

I was expecting an opposite outcome for any of the three ray casts. What am I doing wrong? Thanks!

I would expect False, True, True.
Let’s have a look at the first one. You print out the boolean value rayhit.isEmpty(). So getting a hit will print ‘False’.

You set the custom filter expression like this

self.scene.setFilterConstant0(PhysxGroupsMask.allOff())
    self.scene.setFilterConstant1(PhysxGroupsMask.allOff())
    self.scene.setFilterOps(op0 = self.scene.FOOr, 
                            op1 = self.scene.FOOr,
                            op2 = self.scene.FOAnd)
    self.scene.setFilterBool(True)

From the NVIDIA docs:

Now, using your setting, you have:

(G0 or 0000...) and (G1 or 0000...) = True

For the box you set GROUPSMASK1, which is ‘100000000000…’,
and for the ray you also set GROUPSMAKS1. Inserting these values for G0 and G1 gives:

(1000.. or 0000...) and (1000... or 0000...) = True

or

true and true = true

This is true, so your ray scores a hit for the box. And raycase.isEmpty() will return ‘False’
This is what I get when running your script.

Hey thanks for replying! I’m actually printing “not rayhit.isEmpty()”… so the first ray cast doesn’t hit even though box and ray have same group and groups mask.

Ok, didn’t notice the “not”.
Something else I didn’t notice is that you use the 128-bit masks filtering (groupsMasks), but you don’t provide 1111111 for the mask itself. You use

group0 = PhysxMask.allOff()

If using 128-bit masks this should be the default value for the “mask” parameter

group0 = PhysxMask.allOn()

You can cose between two methods when raycasting:
(1) Use actor.getShape(i).setGroup() and use the “group” parameter of the raycast to pass a mask of groups.
(2) Use actor.getShape(i).setGroupsMask() and use the “groupsMask” parameter of the raycast. Leave the parameter group at it’s default value PhysxMask.allOn.

By the way:

self.boxActor.setGroup(0)

This sets the “actor group”, which is not related to shape group (first option above) or shape groupsMask (second option above). I know this might be a bit confusing since PhysX offers four or five different mechanisms to filter collisions.

Yeah that’s pretty confusing! I changed the code by removing the actor group and setting PhysxMask.allOn() for every ray cast. There two ray casts now, the first with GROUPSMASK1 and the second with GROUPSMASKS2. They both hit the box but the second shouldn’t. What else am I doing wrong? Thanks!

import direct.ffi.panda3d

import direct.directbase.DirectStart

from panda3d.core import Vec3
from panda3d.core import Point3

from panda3d.physx import PhysxManager
from panda3d.physx import PhysxScene
from panda3d.physx import PhysxSceneDesc
from panda3d.physx import PhysxBodyDesc
from panda3d.physx import PhysxActorDesc
from panda3d.physx import PhysxBoxShapeDesc
from panda3d.physx import PhysxPlaneShapeDesc
from panda3d.physx import PhysxRay
from panda3d.physx import PhysxMask
from panda3d.physx import PhysxGroupsMask

from base import BaseWorld


class World(BaseWorld):

  def __init__(self):
    BaseWorld.__init__(self)

  def setup(self):
      
    # Scene
    sceneDesc = PhysxSceneDesc()
    sceneDesc.setGravity(Vec3(0, 0, -9.81))

    self.scene = PhysxManager.getGlobalPtr().createScene(sceneDesc)

    # Plane
    shapeDesc = PhysxPlaneShapeDesc()
    shapeDesc.setPlane(Vec3(0, 0, 1), -2)

    actorDesc = PhysxActorDesc()
    actorDesc.addShape(shapeDesc)

    self.planeActor = self.scene.createActor(actorDesc)

    # Box
    self.boxNP = loader.loadModel('models/box.egg')
    self.boxNP.reparentTo(render)

    shapeDesc = PhysxBoxShapeDesc()
    shapeDesc.setDimensions(Vec3(0.5, 0.5, 0.5))

    bodyDesc = PhysxBodyDesc()
    bodyDesc.setMass(10.0)

    actorDesc = PhysxActorDesc()
    actorDesc.setBody(bodyDesc)
    actorDesc.setName('Box')
    actorDesc.addShape(shapeDesc)
    actorDesc.setGlobalPos(Point3(0, 0, -1.5))

    self.boxActor = self.scene.createActor(actorDesc)
    self.boxActor.attachNodePath(self.boxNP)
    
    # Scene collision filtering expression: (G0 & G1) == True.
    self.scene.setFilterConstant0(PhysxGroupsMask.allOff())
    self.scene.setFilterConstant1(PhysxGroupsMask.allOff())
    self.scene.setFilterOps(op0=self.scene.FOOr
                           ,op1=self.scene.FOOr
                           ,op2=self.scene.FOAnd)
    self.scene.setFilterBool(True)


    # Main groups mask.
    GROUPSMASK1 = PhysxGroupsMask().allOff()
    GROUPSMASK1.setBits0(1)
    
    # Another groups mask.
    GROUPSMASK2 = PhysxGroupsMask().allOff()
    GROUPSMASK2.setBits0(2)
    
    # Box and plane in groups mask 1.
    self.boxActor.getShape(0).setGroupsMask(GROUPSMASK1)
    self.planeActor.getShape(0).setGroupsMask(GROUPSMASK1)
    
    # Ray
    ray = PhysxRay()
    ray.setOrigin(Point3(0, -1, -1.5))
    ray.setDirection(Vec3(0, 1, 0))
    ray.setLength(5.0)

    # Raycast 1.
    rayhit = self.scene.raycastClosestShape(ray
                                           ,shapesType=PhysxScene.STAll
                                           ,mask=PhysxMask.allOn()
                                           ,groups=GROUPSMASK1)
    print "raycast 1, same groups mask, hit? ", not rayhit.isEmpty()
    
    # Raycast 2.
    rayhit = self.scene.raycastClosestShape(ray
                                           ,shapesType=PhysxScene.STAll
                                           ,mask=PhysxMask.allOn()
                                           ,groups=GROUPSMASK2)
    print "raycast 2, different groups mask, hit? ", not rayhit.isEmpty()


  def update(self, task):
    dt = globalClock.getDt()
    self.scene.simulate(dt)
    self.scene.fetchResults()
    return task.cont

  def cleanup(self):
    self.boxNP.removeNode()
    self.scene.release()
    render.ls()

if __name__ == '__main__':
  world = World()
  run()

Now I would expect True for the first raycast and False for the second. I get True and True.

You found a bug. I evaluated the groupsMask parameter for all of the raycast* methods, but I forgot to pass the evaluated groupsMask to PhysX. A fix is checked in already. Please try your sample again with the next snapshot build.

I tried with the last spanshot and now works fine. Thanks for fixing it!!

Hi Enn0x,

Which horse to bet on?

Bullet or PhysX?

I have to update a panda3D 1.6.2 program I use to visualize a real world stage where we fly a camera.
At the moment we use the native panda collision control which seems to be a little bit unstable so we are looking into upgrading.

please advice

with kind regards

Martin

I’m afraid that I can not answer this question, at least not based on any hard facts.

It seems you don’t need physics, but only collision detection. I assume you do collision response yourself. Neither Bullet nor PhysX have been designed with this in mind. PhysX probably has more function you might find useful, but current development focus is on Bullet. This is why I would go for Bullet.

Or probably stick with the native Panda3D collision system until you find something that definitely can not be handled by this system. Often it is just not being familiar enough with something that leads to the (false) impression that something is unstable.

Sorry, but it is impossible to give solid advice here.

Hi enn0x,

I know you’re primarily developing Bullet integration at the moment, but I was wondering if you have any plans to develop the PhysX integration up to a more recent SDK version. I’m guessing probably not V3 because of the API changes, but maybe 2.8.4.6?

If not, I don’t suppose you have a copy of the 2.8.4.4 SDK saved off somewhere that you could rehost? The PhysX site doesn’t appear to have it available anymore.

Thanks for any help.

Actually (now that Bullet is out) my current focus is on PhysX 3.2 and Havok 2011.3.0. Because of the huge changes in the PhysX API with 3.x it won’t be an upgrade, but a complete rewrite from scratch.

I am currently playing around with different ways to integrate, utilising the lessons learned with Bullet and trying out other, new concepts. The bad news is that it will be quite some time (half a year?) before any code will be moved into the Panda3D SDK builds.

As far as I know it is possible to build using the 2.8.4.6 SDK. This is the only version I have found in my archives, and I used this version when I did compile Panda3D with PhysX the last time.

That’s great to hear, and as always, thanks for the quick reply. =)

I’ve tried to get 2.8.4.6 working. By copying the deployment files into the root of my panda application it compiles fine, but python crashes upon execution. I’m running Panda 1.8.0. I recall having any issue running 2.8.4.5 a few months back too, where you recommended that I needed to use 2.8.4.4, hence my thinking that’s the issue.

You can verify this yourself if you like by running the following code segment:

from panda3d.physx import *

scene = PhysxManager.getGlobalPtr().createScene(PhysxSceneDesc())

Just to make sure: You did compile the Panda3D SDK source code (aprox. 30-60 minutes) by executing makepanda/makepanda.py? Because you don’t have to copy anything in oder to compile. Makepanda automatically detects the location of the PhysX SDK if installed in default locations.

Ah apologies, I misunderstood what you were suggesting. I’ve rebuilt from source now and it runs against 2.8.4.6 fine, thanks for your help. I feel like it might be worthwhile hosting the recompiled libpandaphysx.dll somewhere so that other people who want to use PhysX don’t need do a build from source in order to use it. Is there an appropriate place it can be hosted?

Panda3D Projects might be a possible place. But you could also try and push rdb into upgrading the build machines.

I’ve asked rdb if he could update the build machine SDK to 2.8.4.6, he said he’ll do it when he gets a chance. Thanks for your help enn0x. =)

Done. Try the latest build. I have only upgraded the Windows buildbot. The OSX SDK for 2.8.4 was not newer than the one I already had installed, and the Linux SDKs were only available for PhysX 3.

I’ve tried the latest Windows build snapshot (#544) and it still crashes when launching. The libpandaphysx.dll file looks to have been updated however, so I’m not sure what’s wrong. No error message, python just crashes (which is the problem I had before when using a the newer SDK with the older library).

The working libpandaphysx.dll I’ve been using is slightly larger than the packaged one, so maybe it’s not linking to the PhysX SDK 2.8.4.6 properly?