Detect collision points between a ray and boxes

How do I detect the collision points between this ray

    def cast_ray(self, a, b):
        self.draw_line(a, b)
        self.ray = CollisionRay()
        self.ray.setOrigin(a)
        self.ray.setDirection(b-a)

        rayNode = CollisionNode("ray")
        rayNode.addSolid(self.ray)

        self.rayNodePath = self.scene.render.attachNewNode(rayNode)
        self.rayQueue = CollisionHandlerQueue()

        self.scene.cTrav.addCollider(self.rayNodePath, self.rayQueue)

        if self.rayQueue.getNumEntries() > 0:
            self.rayQueue.sortEntries()
            rayHit = self.rayQueue.getEntry(0)
            hitPos = rayHit.getSurfacePoint(self.scene.render)
            hitNodePath = rayHit.getIntoNodePath()

            print(hitPos)
            print(hitNodePath)
        else: print("No entries found")

And these boxes

    def add_box(self, position, tag):
        box_n = self.scene.loader.loadModel("models/box")
        box_n.setScale(1, 1, 1)
        box_n.setPos(position)
        box_n.flattenLight()
        box_n.setTextureOff()
        box_n.setTag('myObjectTag', tag)

        colliderNode = CollisionNode("box")
        colliderNode.addSolid(CollisionBox(position, 2.5, 5, 2))
        collider = box_n.attachNewNode(colliderNode)
        collider.show()
        box_n.reparentTo(self.scene.render)

With

self.scene.cTrav = CollisionTraverser()

In fact, this topic is described in detail in the manual. From which you can output such a code.

import direct.directbase.DirectStart
from panda3d.core import *

collision_traverser = CollisionTraverser()
collision_handler = CollisionHandlerQueue()

ray_node = CollisionNode('ray_node')
ray_node.setFromCollideMask(BitMask32.bit(1))
ray_node.addSolid(CollisionRay(0,0,0,0,0,-1))

ray_path = NodePath(ray_node)
ray_path.setPos(0.5, 0.5, 5)
ray_path.show()
ray_path.reparentTo(render)

collision_traverser.addCollider(ray_path, collision_handler)

box = loader.loadModel("box")
box.setCollideMask(BitMask32.bit(1)) 
box.reparentTo(render)

collision_traverser.traverse(render)
collision_handler.sortEntries()
if (collision_handler.getNumEntries() > 0):
    print (collision_handler.getEntry(0).getSurfacePoint(render))

base.run()

Specifically, it looks to me that the main problem with the code as you currently have it is that you’re not allowing an opportunity for collision-traversal between setting up your ray and attempting to read its collisions.

You see, “base.cTrav” (when assigned a traverser), automatically performs collision-traversals on each update.

However, the code for “cast_ray”, being in a single (non-threaded) method, will all run within the space of a single update–any other (non-threaded) code would presumably be run either entirely before or entirely after the method. There will be no new update between the start of the method and its end.

As a result, “base.cTrav” won’t have an opportunity to perform collision-traversal before the code of “cast_ray” examines the collision-queue for collision-entries.

Now, there are two potential solutions to this that I see offhand:

  • First, you could split the creation of the ray and the examination of the queue into different parts of your code, such that an update might happen between them.
    • Of course, if you want immediate results then this might not be desirable.
  • And second, you could use a traverser not assigned to “base.cTrav”, and then call its “traverse” method yourself just before examining the queue for results.

A quick partial demonstration of the latter approach:

# Within cast_ray...

myTraverser = CollisionTraverser()
# It would likely be better to create this just once and then
# reuse it here, but for the sake of demonstration I'll leave
# it as above.

myTraverser.addCollider(self.rayNodePath, self.rayQueue)

# Traverse!
myTraverser.traverser(render)

if self.rayQueue.getNumEntries() > 0:
# ... And so on...

By the way, note that as things currently stand, each call to “cast_ray” will result in a new ray being made–and being left in the scene-graph, I believe. Thus the more you call the method, the more rays there will be, all still attempting to collide.

If that’s the desired behaviour, then fair enough!

If not, however, then it might be worth removing your rays once you’re done with them. (Or constructing elsewhere and keeping just one ray that you re-use in “cast_ray”.)

1 Like

Right before the line if self.rayQueue.getNumEntries() > 0:, I added the following self.scene.cTrav.traverse(self.scene.render), and it worked :slight_smile:

1 Like