Hi All,
Starting by saying I know this is the realm of dragons, but hopefully this is just something stupid with my code and not a mighty beast to slay.
Working on a world editor where my goal is to be able to click on the terrain and place an item down, drag it around, whatever. For terrain I have been using ShaderTerrainMesh because the performance is really good when dealing with large areas. Unfortunately because ShaderTerrainMesh is deformed on GPU, there is no easy way to do collision detection on it.
Enter CollisionHeightfield, oh, and dragons! I give this the same heightmap as my terrain and mostly it produces similar results (there are some differences). Should be easy, send ray at it, get intersection point, place item there. The problem is that collision detection is not reliable (only sometimes does the ray find CollisionHeightfield).
Below is a sample to reproduce the problem (running Panda3D-SDK-1.11.0pre-fb20b1b-x64). The ray can be moved around with up, down, left, right arrows. It prints the CollisionHandlerQueue to the console.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode, CollisionHandlerQueue, CollisionRay
from panda3d.core import CollisionHeightfield, CollisionSphere, PNMImage, GeomNode
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.count = 0
self.keyMap = {"arrow_up" : False,
"arrow_down" : False,
"arrow_left" : False,
"arrow_right" : False,
"shift" : False}
self.accept("arrow_up", self.setKey, ["arrow_up", True])
self.accept("arrow_up-up", self.setKey, ["arrow_up", False])
self.accept("arrow_down", self.setKey, ["arrow_down", True])
self.accept("arrow_down-up", self.setKey, ["arrow_down", False])
self.accept("arrow_left", self.setKey, ["arrow_left", True])
self.accept("arrow_left-up", self.setKey, ["arrow_left", False])
self.accept("arrow_right", self.setKey, ["arrow_right", True])
self.accept("arrow_right-up", self.setKey, ["arrow_right", False])
self.basicSphere()
self.basicHeightfield()
self.camera.setH(90)
self.cameraAnchor = render.attachNewNode("Camera Anchor")
self.cameraAnchor.setPos(0, 0, 10)
base.camera.reparentTo(self.cameraAnchor)
self.cTrav = CollisionTraverser()
self.terrainRay = CollisionRay()
self.terrainRay.setOrigin(self.cameraAnchor.getPos())
self.terrainRay.setDirection(0, 0, -1)
self.terrainCol = CollisionNode('terrainRay')
self.terrainCol.addSolid(self.terrainRay)
self.terrainCol.setFromCollideMask(GeomNode.getDefaultCollideMask())
self.terrainColNp = self.cameraAnchor.attachNewNode(self.terrainCol)
self.terrainHandler = CollisionHandlerQueue()
self.cTrav.addCollider(self.terrainColNp, self.terrainHandler)
self.terrainColNp.show()
taskMgr.add(self.update, "update")
def update(self, task):
self.terrainHeight()
self.cameraControl()
return task.cont
def setKey(self, key, value):
self.keyMap[key] = value
def terrainHeight(self):
self.count += 1
if self.count == 45:
#Do this so we don't print spam.
self.count = 0
print(self.terrainHandler.entries)
def basicSphere(self):
self.colSphere = CollisionSphere(0, 0, 0, 5)
self.colSphereNode = CollisionNode("Col Sphere")
self.colSphereNode.addSolid(self.colSphere)
self.colSphereNode.setIntoCollideMask(GeomNode.getDefaultCollideMask())
self.colSphereNp = render.attachNewNode(self.colSphereNode)
self.colSphereNp.show()
def basicHeightfield(self):
image = PNMImage()
image.clear(64, 64, 1, 64 * 64)
for r in range(64):
for c in range(64):
image.setGrayVal(r, c, r * c)
self.colField = CollisionHeightfield(image, max_height = 64 * 64 / 100, num_subdivisions = 10)
self.colFieldNode = CollisionNode("Col Field")
self.colFieldNode.addSolid(self.colField)
self.colFieldNode.setIntoCollideMask(GeomNode.getDefaultCollideMask())
self.colFieldNp = render.attachNewNode(self.colFieldNode)
self.colFieldNp.setPos(-32, -32, -32)
self.colFieldNp.show()
def cameraControl(self):
movementSpeed = 25
dt = globalClock.getDt()
if self.keyMap["arrow_up"]:
self.cameraAnchor.setY(self.cameraAnchor, movementSpeed * dt)
if self.keyMap["arrow_down"]:
self.cameraAnchor.setY(self.cameraAnchor, - movementSpeed * dt)
if self.keyMap["arrow_left"]:
self.cameraAnchor.setX(self.cameraAnchor, - movementSpeed * dt)
if self.keyMap["arrow_right"]:
self.cameraAnchor.setX(self.cameraAnchor, movementSpeed * dt)
app = MyApp()
app.run()
This is the output after a sample run, with the ray starting through CollisionSphere and CollisionHeightfield. It should start with two entries, but starts only with one (Sphere). Move the ray around and sometimes it detects both (two entries), or when only over Heightfield, zero or one entries.
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[0] of CollisionHandlerQueue, 0 entries>
<CollisionHandlerQueue.entries[2] of CollisionHandlerQueue, 2 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[2] of CollisionHandlerQueue, 2 entries>
<CollisionHandlerQueue.entries[2] of CollisionHandlerQueue, 2 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[1] of CollisionHandlerQueue, 1 entries>
<CollisionHandlerQueue.entries[0] of CollisionHandlerQueue, 0 entries>
Also there appears to be an error in the documentation. It says
If the number of subdivisions is too high, it will automatically be decremented.
but increasing the number above 10 throws an AssertionError. (AssertionError: num_subdivisions >= 0 && num_subdivisions <= 10 at line 70 of C:\buildslave\sdk-windows-amd64\build\panda\src\collide\collisionHeightfield.cxx) I think this is easy fix (but have no experience with C++), for example maybe something like.
nassertv(num_subdivisions >= 0);
if (num_subdivisions > 10) {
num_subdivisions = 10;
}
(or the documentation is changed).
Thank you everyone in advance.