Panda PhysX

I don’t know about the other troubles, but I can explain this one:

actually, it’s worse than that. The actor is getting completely destructed if you don’t keep a reference in the Python code. This is because the C++ PhysxActorDesc class doesn’t inherit from ReferenceCount, so it means that it isn’t a referenced-counted object, and whoever constructs it is responsible for keeping its pointer around for as long as it is needed.

So you must keep the reference.

David

enn0x said that PhysxActor etc. are just temporary wrapper objects anyway, so the underlying data in the PhysX layer should stay intact regardless, and this should include the name.

Also, I’m still having the issue with the missing names even if references are kept. (I added a ‘boxes’ list, and appended each non-actorA box to it)

I can confirm that on Windows 7, with the 1.7.0 release of Panda and PhysX SDK 2.8.3.21 that all of these are occurring exactly as reported.

(I just copied the scripts, pasted them, ran, and got the same issues.)

I also tried creating a list, and appending the boxes to it, and the same issue with the name occurred.

First about the “missing names” issue:

This is a bug. Let me explain some background first, so drwr is up to date too.

For each object there are two different classes, for example

  PhysxActorDesc
  PhysxActor

The “desc” objects are nothing but construction info. You create a desc, set some properties, pass the desc to the factory method (in this case “scene.createActor”), and then throw it away. The desc objects are intended to get destructed after setting up the object. Hence they don’t inherit from ReferenceCount.

The objects returned by the factory (here “PhysxActor”, which is created by “scene.createActor(PhysxActorDesc)”) are kept by their owners, and all of the these objects inherit from ReferenceCount. Here the owner is “PhysxScene”, which has a private collection of actor objects, in order to retain a PT() to them.

class EXPCL_PANDAPHYSX PhysxScene : ... {
  PhysxObjectCollection<PhysxActor> _actors;

class EXPCL_PANDAPHYSX PhysxObjectCollection {
  pvector<PT(T)> _objects;
...

This means you don’t have to keep a reference to an actor object in Python, since the scene stores a reference to it and thus it won’t get destructed. At least this is the way it is intended to be.

Back to the lost names. The PhysxActorDesc stores an “const char *”. I was assuming that the PhysX engine makes an internal copy of this pointer when calling the actor factory. Thought I have read this somewhere in the PhysX SDK doku. Anyway, this has been wrong.

So far I didn’t see this bug myself because I have been assigning names from Python string literals. These literals are Python objects (like everything in Python), and they don’t get deallocated until the code itself is destroyed. What you do is different. You create a Python string object dynamically, and this object is deallocated right after you are done with setting up the object.

How to fix this bug? I am a little bit reluctant to make copies of the “const char *”, since it is always trouble to get rid of them again, and I don’t want to have memory leaks. On the other hand I don’t see another way of fixing this. I will try to fix it this weekend.

There is a workaround: Keep a reference to the Python string objects somewhere, for example in actor.setPythonTag(“name”, name). Or simply don’t use setName/getName at all and only store the names in the python tags.

About the second issue, the “ray collision”:

The behaviour you see is exactly what is to be expected. It is NOT a bug. Please have a look at the PhysX documentation for the raycast methods. There are two parameters:

(1) First the 32-bit mask “groups”. This on is a mask for shape groups. You can set shape groups using shape.setGroup or actor.setShapeGroup (for all shapes in an actor).

(2) Second the 128-bit mask “groupsMask”. This parameter requires that group masks are set on the shapes, using shape.setGroupsMask(mask). Then filtering is done by PhysX using the following equation:

(G0 op0 K0) op2 (G1 op1 K1) == b

See also the documentation for scene.setFilterOps(op0, op0, op0), scene.setFilterConstant1/2, and scene.setFilterBool. This way of filtering is very powerful, but it requires a bit of thinking about the above equation.

What you probably wanted to do something like this:

actor.setShapeGroup(2)
...
groups = PhysxMask.allOn()
groups.clearBit(2)
scene.raycastClosestShape(ray, PhysxEnums.STAll, groups)

Oh, and to increase confusion even more: actor groups (the one you have been using a few posts above) are yet another way PhysX offer. The have nothing to do with shape groups or groups masks. It’s up to you to choose the right filtering technique for your code :slight_smile:

Thanks, that fixed it perfectly! We’re now able to jump around our level. :smiley:

We are having another issue with cleanup (calling actor.release() causes the game to crash) but I’ll work up a separate example for that issue.

Thanks for all the help on this!

I noticed something strange, regarding the “missing names” (3 box actors).

This is the way you create the boxes:

for x in range(-2, 4, 2):
  for y in [0]:
    if x == 0 and y == 0:
      actorA = createBox('Box-A', x, y, 2)
      actorA.setGroup(2)
    else:
      createBox('Box%+d%+d' % (x, y), x, y, 2)

“Box-A” retains it’s name, while the names of “Box-2+0” and “Box+2+0” get lost.

Now I modify your code just a little bit:

for x in range(-2, 4, 2):
  for y in [0]:
    if x == 0 and y == 0:
      actorA = createBox('Box-A', x, y, 2)
      actorA.setGroup(2)
    else:
      tmp = 'Box%+d%+d' % (x, y) # <-- new line
      createBox(tmp, x, y, 2)

Now all three boxes retain their name. Can you confirm this behaviour?

Actually, only the one box (the last one to be created) retains its name:

Created actor 'Box-2+0'.
Created actor 'Box-A'.
Created actor 'Box+2+0'.
START Box+2+0 Box-A 1 2
   Point3(1.525, -0.500001, -2.05)
   Point3(1.525, 0.499999, -2.05)
   Point3(1.525, -0.499999, -1.05)
   Point3(1.525, 0.500001, -1.05)
START Box-A  2 1
   Point3(-1.5, 0.499999, -2.05)
   Point3(-1.5, -0.499996, -2.05)
   Point3(-1.5, 0.500001, -1.05)
   Point3(-1.5, -0.499994, -1.05)

Yep, my fault. The last name stays only because nobody else has been overwriting these memory location.

Ok, now for another bug; on Linux only, when you remove an actor that is colliding when it is removed, and you have collision stop reporting enabled, it crashes. I had trouble debugging this because I couldn’t get gdb to show me any information about the Python stack. (I even tried using the gdb macros from python.org, and they didn’t work for me)

Here’s the test case:

#!/usr/bin/env python

"""Demonstrate issues with PhysxActor.release() and collision stop.

If an actor is colliding when it is released, and the
PhysxEnums.CPFNotifyOnEndTouch flag is enabled for that collision, it will
crash the application.

Use the arrow keys to move the center box. ("Box-A")

"""


import direct.directbase.DirectStart

from direct.showbase.DirectObject import DirectObject
from direct.showbase.InputStateGlobal import inputState

from panda3d.core import AmbientLight
from panda3d.core import DirectionalLight
from panda3d.core import Vec3
from panda3d.core import Vec4
from panda3d.core import Point3

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

import sys


# Base
base.setBackgroundColor(0, 0, 0.6, 1)
base.setFrameRateMeter(True)
base.cam.setPos(0, -20, 4)
base.cam.lookAt(0, 0, 0)

alight = AmbientLight('ambientLight')
alight.setColor(Vec4(0.5, 0.5, 0.5, 1))
alightNP = render.attachNewNode(alight)

dlight = DirectionalLight('directionalLight')
dlight.setDirection(Vec3(1, 1, -1))
dlight.setColor(Vec4(0.7, 0.7, 0.7, 1))
dlightNP = render.attachNewNode(dlight)

render.clearLight()
render.setLight(alightNP)
render.setLight(dlightNP)

inputState.watchWithModifiers('forward', 'arrow_up')
inputState.watchWithModifiers('left', 'arrow_left')
inputState.watchWithModifiers('reverse', 'arrow_down')
inputState.watchWithModifiers('right', 'arrow_right')

# Scene
sceneDesc = PhysxSceneDesc()
sceneDesc.setGravity(Vec3(0, 0, -9.81))
scene = PhysxManager.getGlobalPtr().createScene(sceneDesc)

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

actorDesc = PhysxActorDesc()
actorDesc.setName('Plane')
actorDesc.addShape(shapeDesc)
actorP = scene.createActor(actorDesc)
actorP.setGroup(3)

# Actors
def createBox(name, *pos):
    boxNode = loader.loadModel('box')
    boxNode.reparentTo(render)
    boxNode.setPos(Vec3(-0.5, -0.5, -0.5))
    boxNode.flattenStrong()

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

    bodyDesc = PhysxBodyDesc()
    bodyDesc.setMass(10.0)

    actorDesc = PhysxActorDesc()
    actorDesc.setBody(bodyDesc)
    actorDesc.setName(name)
    actorDesc.addShape(shapeDesc)
    #actorDesc.setGlobalPos(Point3(*pos))

    actor = scene.createActor(actorDesc)
    actor.setGroup(1)
    actor.attachNodePath(boxNode)
    actor.setGlobalPos(Point3(*pos))
    actor.setPythonTag('node', boxNode)

    print "Created actor '%s'." % (actor.getName(), )
    return actor

# Create box actors
for x in range(-8, 10, 2):
    for y in range(-8, 10, 2):
        if x == 0 and y == 0:
            actorA = createBox('Box-A', x, y, 2)

            # Set the player-controlled actor to have a different group.
            actorA.setGroup(2)

        else:
            createBox('Box%+d%+d' % (x, y), x, y, 2)


# Debug
debugNP = render.attachNewNode(scene.getDebugGeomNode())
debugNP.node().on()
debugNP.node().visualizeWorldAxes(True)

def updateWorld(task):
    dt = globalClock.getDt()

    force = Vec3(0, 0, 0)

    if inputState.isSet('forward'):
        force.setY(90)
    if inputState.isSet('reverse'):
        force.setY(-90)
    if inputState.isSet('left'):
        force.setX(-90)
    if inputState.isSet('right'):
        force.setX(90)

    actorA.addForce(force)

    scene.simulate(dt)
    scene.fetchResults()

    return task.cont

taskMgr.add(updateWorld, 'updateWorld')

def doExit():
    sys.exit(1)

def doScreenshot():
    base.screenshot('test')

def toggleWireframe():
    base.toggleWireframe()

def toggleDebug():
    debugNP.node().toggle()

base.accept('escape', doExit)
base.accept('f1', toggleWireframe)
base.accept('f3', toggleDebug)
base.accept('f5', doScreenshot)

# Contact reporting
scene.enableContactReporting(True)
scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnStartTouch, 1)
scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnEndTouch, 1)
scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnTouch, 1)
scene.setActorGroupPairFlag(2, 1, PhysxEnums.CPFNotifyOnStartTouch, 1)
scene.setActorGroupPairFlag(2, 1, PhysxEnums.CPFNotifyOnEndTouch, 1)
scene.setActorGroupPairFlag(2, 1, PhysxEnums.CPFNotifyOnTouch, 1)

def onContactStart(pair):
    print 'START', pair.getActorA().getName(), pair.getActorB().getName(), pair.getActorA().getGroup(), pair.getActorB().getGroup()
    if pair.getActorA().getGroup() == pair.getActorB().getGroup():
        print '  Releasing actor A...'
        node = pair.getActorA().getNodePath()
        node.removeNode()
        node = None
        pair.getActorA().release()

def onContactStop(pair):
    print 'STOP', pair.getActorA().getName(), pair.getActorB().getName(), pair.getActorA().getGroup(), pair.getActorB().getGroup()

base.accept('physx-contact-start', onContactStart)
base.accept('physx-contact-stop', onContactStop)

# Run
run()

Just committed a quick fix to the “missing names” problem. However, it is a quick fix, and it comes at a high cost. There has been no problem on the C++ side. In C++ the objects and descriptors take a “const char *”, and just store the pointer. This means the user is responsible for allocating AND de-allocating the char-array. On the Python layer this is tricky, since Pythons garbage collector de-allocates the string, too early in this case. The only solution has been to create copies of the whole strings, and store them as members of my wrappers for descriptors and objects.

Thanks for pointing me at this issue. The cause is, of course, that one of the two actors has already been deleted, and thus is invalid. On Windows PhysX returns NULL for the NxActor*, and I am catching this case already. PhysX doesn’t seem to be behave the same on Linux (can’t test right now).

I have added two methods in CVS:

pair.isDeletedA()
pair.isDeletedB()

They return True is the actor has been deleted. getActorA/B now also return NULL (Python None) if the actor has been released previously, and a warning is logged.

I can’t test on Linux this week or the next. So could you please report if the issue is fixed in current CVS?

I just got a build of panda CVS working, and both the name issue and the crash on collision end are fixed! Thanks!

As you said, on physx-collision-stop, one of the actors will be None, but that’s easily worked around with an if.

Thank you for the feedback. I checked in a reworked fix for the missing names issue half a day ago (rdb pointed me at a better way of solving it).

I’m running into a problem with kinematic actors now; none of the moveGlobal* methods seem to exist on PhysxActor. (Panda CVS checked out on June 18 )

I’ve tried using moveGlobalPosition(), moveGlobalPos(), and move(), but I get a traceback:

AttributeError: 'libpandaphysx.PhysxActor' object has no attribute 'moveGlobalPosition'

I’ve also done “print dir(actor)” but it doesn’t show any methods starting with “move”, and I can’t find “move” in physxActor.h. How do I go about controlling kinematic actors?

Right. There are no wrappers for these functions so far.
This is not a bug, but a feature request!
Quite easy to do. Check CVS.

Awesome, thanks! I thought they had been implemented, from what I had seen in the conversation earlier in this thread.

Hi,
I am using Linux 64-bit Kubuntu jaunty and I tried to install Physx and your panda physx. I installed Nvdia Physx using dpkg --force-all, It seems to be installed successfully.
But when I try to install your Panda Physx using “scons”, it returns:

scons: Reading SConscript files ...
KeyError: 'PROGRAMFILES':
  File "/home/filip/Dokumente/libpandaphysx_0.4.8/SConstruct", line 14:
    SConscript( [ 'SConscript.%s' % PLATFORM, ] )
  File "/usr/lib/scons/SCons/Script/SConscript.py", line 612:
    return apply(method, args, kw)
  File "/usr/lib/scons/SCons/Script/SConscript.py", line 549:
    return apply(_SConscript, [self.fs,] + files, subst_kw)
  File "/usr/lib/scons/SCons/Script/SConscript.py", line 259:
    exec _file_ in call_stack[-1].globals
  File "/home/filip/Dokumente/libpandaphysx_0.4.8/SConscript.VC9", line 5:
    PANDADIR = os.environ['PROGRAMFILES'] + '/Panda3D-1.6.2'
  File "/usr/lib/python2.6/UserDict.py", line 22:
    raise KeyError(key)

Is your libpandaphysx not supported for my OS, is there a way to build it for me?
Or did I just used the wrong command to install ^^

Thanks in advance

blenderkid

I am not aware of any PhysX SDK which runs on 64-bit Linux. Even 32-bit Linux SDK is withrawn by NVIDIA, and it’s not clear when Linux support will be back. So be prepared for any number of problems.

Anyway, what you try to compile is an ancient version of Panda3D PhysX integration. By now the PhysX integration is integrated in Panda3D CVS.

Whitelynx has posted instructions on how to compile Panda3d+PhysX on Linux:
[url]Panda PhysX]