Panda PhysX


#201

Yes, it should work.

2.8.1 has been the last version before NVIDIA dropped Linux support. Some features are not working in Linux though, for example the hardware acceleration. There are rumors that NVIDIA will have Linux packages again, but when/which version is unknown.


#202

I just wanted to add that the Linux support from the last release actually works really well. I even got it running under 64-bit Arch using a 32-bit chroot. (instructions at http://projects.g33xnexus.com/rfi-damnation/wiki/Development/PhysX/)

So far, I’m really enjoying the PhysX support in Panda CVS. The only issue I’m running into right now is that I can’t get the debug visualization to work. I copied the following code from one of the examples, but it never shows up:

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

The full source for the PhysX branch of our project is at http://projects.g33xnexus.com/rfi-damnation/source/physx/; the PhysX-specific stuff lives in Classes/world.py and Classes/EntityMixins/physical.py, if that helps diagnose the issue any.


#203

Great news, and thanks for giving the instructions. I can’t give it a try right now, but I will attempt to set up a VM one of the next weeks.

Hmmm, sound like a problem I had with my recent Bullet work. I didn’t look at your source code, but this seems to happen if the debug geom is empty in the first frame. Can you try and create the debug geom node after the scene is set up?


#204

Actually, I’m calling that after the scene and all static actors have been created, and none of them are being shown. I could try moving it earlier in the loading process, if you think that would help, or I could move it after absolutely all actors (including dynamic actors) are created… I’ll give both a shot.


#205

Moving it after absolutely all of the actors had been created fixed it. Now I’m trying to figure out how to fix the cooked trimesh colliders from my level. Here’s what it currently looks like:

For some reason, it’s stringing collision polys between all the different buildings.


#206

Hmm… you could try to break down the huge mesh into smaller, convex meshes before cooking.


#207

It actually already is several different meshes. They’re not convex, but they are separate.

I’ve tried using our cooking script 2 different ways; either cooking the entire level egg as one, or cooking individual .nxb files for every mesh in the level. The latter seems to have no effect other than slowing it down to less than 1FPS. (between 0.7 and 0.9 normally) I’m going to try re-centering all the meshes in Blender and re-exporting it.


#208

This is strange. The .egg is in your source repository, right (level.egg or streetscene.egg)? I will have a look and see what results I get when cooking, but it won’t be before tomorrow evening (CET).


#209

that screenshots highly reminds me of my first attempts of loading quake3 maps.

in the end it was something like
+a list with vertexdata
+a list with offset values for the faces.
+a list of faces with the startpoit for the offset list + length

the final face was then made something like 3 or 4 points obtained by doing
myvertex = vertexdata[offsetlist[startpointFromFace+0/1/2/3]]

i figured out it had to do with ease of memory management so you dont have dynamic structures etc.

althoug i have no idea what kind of data physx is using,the error looks like something like that. partly working scene, and a lot of wrongly connected vertices. so it’s just a hint. maybe.


#210

Actually, I managed to figure it out late last night. I ended up needing to re-center all the objects (previously their centers were set to the center of the map because that was the only way I could get it working with ODE and our old collider-conversion code), cook separate .nxb files for each CollisionNode, and then, when loading the .nxb colliders, translate the actor for each loaded collider to match the position of the CollisionNode’s parent.

Our latest converter is in Tools/cook_colliders.py; it’s rather nice and full-featured, with command line options and everything! It even modifies the loaded .egg, adding ‘colliderFile’ tags on each node containing a CollisionNode, with the value being the path to the generated .nxb, and then saves the full hierarchy out to a .bam.

Our Bazaar browser seems a bit broken, so the easiest way to get it is to just pull the latest version from our repository:

bzr branch http://development.g33xnexus.com/bzr/damnation/physx/ damnation-physx

You may also be interested in our loading code for how to load the resulting .bam and .nxb’s; that code lives in Classes/loader.py and Classes/world.py.


#211

Also, would it be possible to add support for attaching custom objects to a PhysxActor in the next release? All of the standard PhysX classes have a ‘void* userData’ member to be used for exactly that purpose… it’d be nice to be able to attach an arbitrary Python object to each actor. (in my app, it’d be useful to associate the entity behavior object with each actor, instead of having to use a dict)


#212

I’m having some trouble with collision reporting. I have the following code in my initialization:

self.scene.enableControllerReporting(True)
self.scene.enableContactReporting(True)

# Collisions
self.scene.setActorGroupPairFlag(0, 1, PhysxEnums.CPFNotifyOnStartTouch, 1)
self.scene.setActorGroupPairFlag(0, 1, PhysxEnums.CPFNotifyOnEndTouch, 1)
self.scene.setActorGroupPairFlag(0, 1, PhysxEnums.CPFNotifyOnTouch, 1)
self.scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnStartTouch, 1)
self.scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnEndTouch, 1)
self.scene.setActorGroupPairFlag(1, 1, PhysxEnums.CPFNotifyOnTouch, 1)

base.accept('physx-contact-start', self.onContactStart)
base.accept('physx-contact-stop', self.onContactEnd)
base.accept('physx-contact-touch', self.onContactContinue)

Then, the handlers:

def onContactStart(self, pair):
    print "Collision:", pair.getActorA(), pair.getActorB(), pair.getContactPoints()
    self.callBehaviorMethod(pair.getActorA(), 'collisionStart', pair.getActorB(), pair.getContactPoints())
    self.callBehaviorMethod(pair.getActorB(), 'collisionStart', pair.getActorA(), pair.getContactPoints())
                                                                                                              
def onContactEnd(self, pair):
    self.callBehaviorMethod(pair.getActorA(), 'collisionEnd', pair.getActorB(), pair.getContactPoints())
    self.callBehaviorMethod(pair.getActorB(), 'collisionEnd', pair.getActorA(), pair.getContactPoints())
                                                                                                              
def onContactContinue(self, pair):
    self.callBehaviorMethod(pair.getActorA(), 'collisionContinue', pair.getActorB(), pair.getContactPoints())
    self.callBehaviorMethod(pair.getActorB(), 'collisionContinue', pair.getActorA(), pair.getContactPoints())

However, it seems that getActorA() and getActorB() always give me the same thing:

Collision: <libpandaphysx.PhysxActor object at 0x9bbd278> <libpandaphysx.PhysxActor object at 0x9bbd278> [<libpandaphysx.PhysxContactPoint object at 0x9bbd278>, <libpandaphysx.PhysxContactPoint object at 0x9bbd818>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd818> <libpandaphysx.PhysxActor object at 0x9bbd818> [<libpandaphysx.PhysxContactPoint object at 0x9bbd818>, <libpandaphysx.PhysxContactPoint object at 0x9bbd278>, <libpandaphysx.PhysxContactPoint object at 0x9bbd7e8>, <libpandaphysx.PhysxContactPoint object at 0x9bbd788>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd7d0> <libpandaphysx.PhysxActor object at 0x9bbd7d0> [<libpandaphysx.PhysxContactPoint object at 0x9bbd7d0>, <libpandaphysx.PhysxContactPoint object at 0x9bbd818>, <libpandaphysx.PhysxContactPoint object at 0x9bbd7e8>, <libpandaphysx.PhysxContactPoint object at 0x9bbd278>, <libpandaphysx.PhysxContactPoint object at 0x9be2590>, <libpandaphysx.PhysxContactPoint object at 0x9be2218>, <libpandaphysx.PhysxContactPoint object at 0x9be22c0>, <libpandaphysx.PhysxContactPoint object at 0x9be2518>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd3f8> <libpandaphysx.PhysxActor object at 0x9bbd3f8> [<libpandaphysx.PhysxContactPoint object at 0x9bbd3f8>, <libpandaphysx.PhysxContactPoint object at 0x9bbd7d0>, <libpandaphysx.PhysxContactPoint object at 0x9bbd7e8>, <libpandaphysx.PhysxContactPoint object at 0x9bbd818>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd7a0> <libpandaphysx.PhysxActor object at 0x9bbd7a0> [<libpandaphysx.PhysxContactPoint object at 0x9bbd7a0>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd7a0> <libpandaphysx.PhysxActor object at 0x9bbd7a0> [<libpandaphysx.PhysxContactPoint object at 0x9bbd7a0>, <libpandaphysx.PhysxContactPoint object at 0x9bbd3f8>]
Collision: <libpandaphysx.PhysxActor object at 0x9bbd278> <libpandaphysx.PhysxActor object at 0x9bbd278> [<libpandaphysx.PhysxContactPoint object at 0x9bbd278>]

#213

One by one…

I had a quick look at your .egg file. It contains many separate groups, and some with transforms. The cooking code does not honor these transforms. It just loops over all geoms, and then collects all vertices from these geoms. So this could be the reason for the strange mesh. Your solution seems right.

I was hoping that np.flattenStrong() before cooking would also solve the problem, but it didn’t work.


#214

Next one…

This is already possible. All objects deriving from PhysxObject (e. g. PhysxActor, PhysxShape, …) have setPythonTag / getPythonTa g/ hasPythonTag methods. These are what you are looking for.


#215

Last one…

Hmm… self collision should not be possible. I checked physxContactPair.cxx if there is a type, returning the same object for both getActorA and getActorB, but the code seems right.

I can’t reproduce this behaviour. Can you check if the script below works for you? Everything having to do with contact reporting using actor groups is in the last ten lines or so. Please note that the are other, orthogonal ways of setting up groups, for example “actor pairs” (not “actor group pairs”) and “shape groups”.

If not can you write up a small script which reporduces the issue?

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 PhysxPlaneShapeDesc
from panda3d.physx import PhysxEnums

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', 'w' )
inputState.watchWithModifiers( 'left', 'a' )
inputState.watchWithModifiers( 'reverse', 's' )
inputState.watchWithModifiers( 'right', 'd' )

# 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)

# Box-A
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-A')
actorDesc.addShape(shapeDesc)
actorDesc.setGlobalPos(Point3(-2, 0, 2))
actorA = scene.createActor(actorDesc)

# Box-B
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-B')
actorDesc.addShape(shapeDesc)
actorDesc.setGlobalPos(Point3(2, 0, 2))
actorB = scene.createActor(actorDesc)

# 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()

o = DirectObject()
o.accept('escape', doExit)
o.accept('f1', toggleWireframe)
o.accept('f3', toggleDebug)
o.accept('f5', doScreenshot)

# Contact reporting
actorP.setGroup(0)
actorA.setGroup(1)
actorB.setGroup(2)
scene.enableContactReporting(True)
scene.setActorGroupPairFlag(1, 2, PhysxEnums.CPFNotifyOnStartTouch, 1)

def onContactStart(pair):
  print 'START', pair.getActorA().getName(), pair.getActorB().getName()
  for contact in pair.getContactPoints():
    print '  ', contact.getPoint()

o.accept('physx-contact-start', onContactStart)

# Run
run()

#216

Looks like I was misinterpreting the output… getActorA() and getActorB() return objects with the same memory address, but they give different results for getName() etc.

I switched over to using get/setPythonTag() like you suggested, and it fixed my issues with calling methods on my behavior objects.

Thanks for the help!


#217

Well, they are different objects C++ objects. What you see is the memory address of a temporary Python object wrapping the C++ object. The first Python objects is recycled before the second one is constructed.

If both Python objects where alive at the same time they would have different memory addresses. Can you try to modify your code? Like this:

def onContactStart(self, pair):
    a = pair.getActorA()
    b = pair.getActorB()
    print "Collision:", a, b, pair.getContactPoints()

#218

I’m sure that would give me different objects… I don’t really need them, though… I got the functionality I needed already.


#219

I’m running into another odd issue with collisions: actors’ names seem to be lost if a reference to each actor is not kept in the Python code. The following app (a modified version of the collision demo you pasted several posts ago) demonstrates the issue:

#!/usr/bin/env python

"""Demonstrate issues with collision reporting.

All actors have names set on creation; the box actors each print their name as
they are created. However, whenever a collision is triggered, none of the box
actors have names.

There are 3 actor groups used in this app:
    1 - Generic box actors
    2 - The player-controlled box
    3 - The ground plane

You can put the player-controlled box back in group 1 by commenting out the
line "actorA.setGroup(2)" below; the problem still happens with that change.

"""

# Use Dvorak keyboard bindings (., o, e, u) instead of QWERTY. (w, a, s, d)
dvorak = True


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 PhysxPlaneShapeDesc
from panda3d.physx import PhysxEnums

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)

if dvorak:
    inputState.watchWithModifiers('forward', '.')
    inputState.watchWithModifiers('left', 'o')
    inputState.watchWithModifiers('reverse', 'e')
    inputState.watchWithModifiers('right', 'u')
else:
    inputState.watchWithModifiers('forward', 'w')
    inputState.watchWithModifiers('left', 'a')
    inputState.watchWithModifiers('reverse', 's')
    inputState.watchWithModifiers('right', 'd')

# 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)

# Boxes
def createBox(name, *pos):
    # Box-A
    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)
    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):
for x in range(-2, 4, 2):
    for y in [0]:
        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()

    for contact in pair.getContactPoints():
        print '  ', contact.getPoint()

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

# Run
run()

I’m also having issues getting a PhysxRay to collide with anything other than my player’s collider, but I’ll have to work up a separate example for that.


#220

Here’s the test case for the PhysxRay issue I’m experiencing. Basically, if there is a capsule collider, it always collides with the PhysxRay, even if its group is not in the ray test’s allowed groups mask.

#!/usr/bin/env python

"""Demonstrate issues with PhysxRay hit reporting.

When using a capsule collider, the PhysxRay always collides with it, even if
the allowed groups bitmask doesn't contain the group of the capsule collider.

There are 3 actor groups used in this app:
    1 - Generic box actors
    2 - The player-controlled actor
    3 - The ground plane

You can put the player-controlled box back in group 1 by commenting out the
line "actorA.setGroup(2)" below; the problem still happens with that change.

"""

# Use a capsule for the player. (False causes it to use a box instead)
capsulePlayer = True

# Use Dvorak keyboard bindings (., o, e, u) instead of QWERTY. (w, a, s, d)
dvorak = True


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)

if dvorak:
    inputState.watchWithModifiers('forward', '.')
    inputState.watchWithModifiers('left', 'o')
    inputState.watchWithModifiers('reverse', 'e')
    inputState.watchWithModifiers('right', 'u')
else:
    inputState.watchWithModifiers('forward', 'w')
    inputState.watchWithModifiers('left', 'a')
    inputState.watchWithModifiers('reverse', 's')
    inputState.watchWithModifiers('right', 'd')

# 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):
    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)
    print "Created actor '%s'." % (actor.getName(), )
    return actor

def createCapsule(name, *pos):
    shapeDesc = PhysxCapsuleShapeDesc()
    shapeDesc.setHeight(1)
    shapeDesc.setRadius(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)
    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:
            if capsulePlayer:
                actorA = createCapsule('Capsule-A', x, y, 2)
            else:
                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)


# Ray
rayLength = 2.0
ray = PhysxRay()
ray.setLength(rayLength)

# The ray's group mask
jumpRayMask = PhysxGroupsMask.allOn()
jumpRayMask.clearBit(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()

    p = actorA.getGlobalPos()
    d = actorA.getGlobalQuat().getForward()
    ray.setOrigin(p)
    ray.setDirection(d)

    hit = scene.raycastClosestShape(ray, PhysxEnums.STAll, PhysxMask.allOn(), jumpRayMask)
    if not hit.isEmpty():
        dist = hit.getDistance()
        name = hit.getShape().getActor().getName()
        print 'Hit: %f "%s"' % (dist, name)

    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()

    for contact in pair.getContactPoints():
        print '  ', contact.getPoint()

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

# Run
run()