Polygon-Level raycasting

Thrakbad

15-11-2009 11:50:31

Is there any way to implement raycasting to the polygon level, like you can in c++. I looked at the code and at some point it contains pointer-magic, so I figured this would be hard to do with Python? Has anyone implemented such a thing with pyOgre?

andy

15-11-2009 12:57:13

If you want to take a go at an initial conversion (basically make a complete demo -- as much as possible) I'll be happy to help complete it..

There isn't anything I see in the C++ code that stops it being converted to python -- just need to use some ctypes buffers etc - a bit like Demo_Bezier and Demo_Grass..

Regards

Andy

Thrakbad

15-11-2009 21:19:38

Ah I don't know that much about ctypes. But I'll have a look in the two Demos you mentioned and give it a try :wink:

Thrakbad

16-11-2009 15:33:47

I took a shot at a conversion of the code and it seems to work nicely. Except when I try to select a small object, that is fully placed within a larger object's BB. Dunno why that doesn't seem to work. Maybe you can have a look at it and see if I missed something.

def RayCast(self, ray):

# variable to hold resulting entity
closestEntity = None

# variable to hold point of hit
closestResult = None

# create a query object
raySceneQuery = self.sceneManager.createRayQuery(ray)
raySceneQuery.setSortByDistance(True)

# execute the query, returns a vector of hits
result = raySceneQuery.execute();

if len(result) == 0:
# raycast did not hit an objects bounding box
return None;


# at this point we have raycast to a series of different objects bounding boxes.
# we need to test these different objects to see which is the first polygon hit.
# there are some minor optimizations (distance based) that mean we wont have to
# check all of the objects most of the time, but the worst case scenario is that
# we need to test every triangle of every object.
closestDistance = -1.0

for item in result:

# stop checking if we have found a raycast hit that is closer
# than all remaining entities
if closestDistance >= 0.0 and closestDistance < item.distance:
print "stopped"
print item.distance
print closestDistance
break

# only check this result if its a hit against an entity
if item.movable and item.movable.getMovableType() == "Entity":
if item.movable.getName() == self.groundName:
continue

# get the entity to check
entity = self.sceneManager.getEntity(item.movable.getName())

# get the buffers from the mesh
buffers = self.GetMeshInformation(entity.getMesh())
vertexBuffer = buffers[0]
indexBuffer = buffers[1]

# get the world position, orientation (as a Matrix) and scale
position = entity.getParentNode()._getDerivedPosition()
orientation = entity.getParentNode()._getDerivedOrientation()
matOrient = ogre.Matrix3()
orientation.ToRotationMatrix(matOrient)
scale = entity.getParentNode()._getDerivedScale()

# Now test for hitting individual triangles on the mesh
newClosestFound = False

# get the positions of the vertices from the vertex buffer
# three vertices each form a triangle
triangle = []
i = 1
for vertexNum in indexBuffer:
startIndex = vertexNum * 8
pt = ogre.Vector3(vertexBuffer[startIndex], vertexBuffer[startIndex + 1], vertexBuffer[startIndex + 2])
# factor in the world position, orientation and scale
pt = (matOrient * (pt * scale)) + position
triangle.append(pt)
if i % 3 == 0:
# check for a hit against this triangle
hit = ogre.Math.intersects(ray, triangle[0], triangle[1], triangle[2], True, False)
# if it was a hit check if its the closest
if hit.first:
print "New closest hit at: "
print str(triangle[0].x) + " " + str(triangle[0].y) + " " + str(triangle[0].z)
print str(triangle[1].x) + " " + str(triangle[1].y) + " " + str(triangle[1].z)
print str(triangle[2].x) + " " + str(triangle[2].y) + " " + str(triangle[2].z)
if closestDistance < 0.0 or hit.second < closestDistance:
# this is the closest so far, save it off
closestDistance = hit.second
newClosestFound = True

# reset the triangle
triangle = []
i = i + 1

# if we found a new closest raycast for this object, update the
# closestResult and closestEntity before moving on to the next object.
if newClosestFound:
closestEntity = entity
closestResult = ray.getPoint(closestDistance)

#destroy the query
self.sceneManager.destroyQuery(raySceneQuery)

# return the result
return [closestEntity, closestResult]

# ***** def RayCast(self, ray)


def GetMeshInformation(self, mesh):

addedShared = False

current_offset = 0
shared_offset = 0
next_offset = 0
index_offset = 0

vertexCount = 0
indexCount = 0

# Calculate how many vertices and indices we're going to need
numSubMeshes = mesh.getNumSubMeshes()
for i in range(0, numSubMeshes):
submesh = mesh.getSubMesh(i)

# We only need to add the shared vertices once
if submesh.useSharedVertices:
if not addedShared:
vertexCount += mesh.sharedVertexData.vertexCount
addedShared = True
else:
vertexCount += submesh.vertexData.vertexCount

# Add the indices
indexCount += submesh.indexData.indexCount

addedShared = False;

# Run through the submeshes again, adding the data into the arrays
for i in range(0, numSubMeshes):
submesh = mesh.getSubMesh(i)

vertexData = None
if submesh.useSharedVertices:
vertexData = mesh.sharedVertexData
else:
vertexData = submesh.vertexData

if not submesh.useSharedVertices or (submesh.useSharedVertices and not addedShared):
if submesh.useSharedVertices:
addedShared = True
shared_offset = current_offset

# retrieve index buffer for this submesh
indexData = submesh.indexData;
numTris = indexData.indexCount / 3;
ibuf = indexData.indexBuffer;
pointer = ibuf.lock(ogre.HardwareBuffer.HBL_READ_ONLY)
indexBuffer = None;

if bool(ibuf.getType() == ogre.HardwareIndexBuffer.IT_32BIT):
indexBuffer = ogre.getUint32(pointer, indexCount)
else:
indexBuffer = ogre.getUint16(pointer, indexCount)
ibuf.unlock()


# retrieve vertex buffer for this submesh
posElem = vertexData.vertexDeclaration.findElementBySemantic(ogre.VES_POSITION)
vbuf = vertexData.vertexBufferBinding.getBuffer(posElem.getSource())
pointer = vbuf.lock(ogre.HardwareBuffer.HBL_READ_ONLY)

# There are 8 float entries for each vertex in the buffer
# 3 for position, 3 for normal, 2 for texture coordinate.
# We only need the position.
vertexBuffer = ogre.getFloat(pointer, vertexCount * 8)
vbuf.unlock()

return [vertexBuffer, indexBuffer]
# ***** def GetMeshInformation(self, mesh, position, rotation, scale)


EDIT: I updated the code so that there are only two iterations throught the vertex buffer, might make it a tiny bit faster.

Thrakbad

17-11-2009 14:15:35

I think there is something wrong with the orientation calculation. Rotated objects don't seem to register correct hits. In the original code the orientation was given as a Quaternion, but that didn't bring correct results either, so I changed it to a rotation matrix. Afaik about 3D-Math that formula should give me the right results. I'm a bit stuck here.
pt = (matOrient * (pt * scale)) + position