Problem with collision detection between collisionSegment and CollisionBox

Hi guys,

Abstract:
I’m doing an emulator of drone flight and I have to use my own algorithm of drone driving but to build the way I need to find obstacles on my way to avoid them. I tried to use collisionSecmet to define a straightway between two points and then check if it collides with any obstacle so that I will be able to process a way around them.
Problem:
When I check the collision between points using CollisionSegment and CollisionBox as an obstacle, I get no events in the queue even though they are crossing.

I tried to use CollisionTube as well but it didn’t help.
bitmasks setting also does not work.

obstacle creation:
cube = loader.loadModel(“box”)
cube.reparentTo(base.render)
cube.setScale(self.area_config[‘obstacles’][‘autogenerated’][‘boxes’][‘scale’])
cube.node().setFinal(1)

            # Get the tight bounds of the model
            min_point, max_point = cube.getTightBounds()

            collision_cube = CollisionBox(min_point, max_point//self.area_config['obstacles']['autogenerated']['boxes']['scale'])
            collision_node_path = CollisionNode('collision')
            collision_node_path.setFromCollideMask(BitMask32.bit(0))
            collision_node_path.setIntoCollideMask(BitMask32.bit(0))
            collision_node_path.addSolid(collision_cube)
            collision_node_path = cube.attachNewNode(collision_node_path)
            cube.setPos(random.randint(self.area_x1, self.area_x2-int(max_point.getX())),
                        random.randint(self.area_y1, self.area_y2-int(max_point.getY())),
                        random.randint(self.area_z1, self.area_z2-int(max_point.getZ())))

checking collisions:

    def get_collision_between_points(self, start_point, end_point):
        segment = CollisionSegment(start_point, end_point)
        segment_node = CollisionNode('path_segment')
        segment_node.addSolid(segment)
        segment_node_path = self.render.attachNewNode(segment_node)

        segment_node.setFromCollideMask(BitMask32.bit(0))
        segment_node.setIntoCollideMask(BitMask32.bit(0))  

        base.cTrav.addCollider(segment_node_path, self.segmentQueue)
        base.cTrav.traverse(segment_node_path)

        if self.segmentQueue.getNumEntries() > 0:
            self.logger.debug("Collision detected!")
            self.segmentQueue.clearEntries()
            return True
        else
            return False

additionally I getting the next error:

:collide(error): Invalid attempt to detect collision from CollisionBox into CollisionSegment!

This means that a CollisionBox object attempted to test for an
intersection into a CollisionSegment object. This intersection
test has not yet been defined; it is possible the CollisionSegment
object is not intended to be collidable. Consider calling
set_into_collide_mask(0) on the CollisionSegment object, or
set_from_collide_mask(0) on the CollisionBox object.

Could you please help me to resolve the issue or suggest another way to check path for collisions?

First of all, welcome to the forum! I hope that you find your time here to be positive! :slight_smile:

As to your questions:

Hmm… It’s hard to be certain without trying the code myself, and what you posted doesn’t appear to be a full working program, but one potential problem is that you’re applying a scale to your collision-objects.

(More specifically, you’re applying a scale to the node that’s stored in the variable “cube”, and you’re attaching your box-collider to that cube. As a result, the scale applied “cube” is also applied to the collider.)

In short, scaling factors don’t always play well with collision-geometry; if feasible, I’d strongly recommend avoiding the application of scaling to colliders.

I do also note that you’re calling “setFinal” on the “cube” node. This, I believe, prevents any calculation of bounding volumes from children of the node in question–and it seems plausible that this might interfere with collision-detection.

If I may ask, why are you calling “setFinal” there?

This, I believe, is because you’re setting both the “into” and the “from” masks of your CollisionBox to something non-zero. As a result, the collision system thinks that it’s supposed to treat the box as a “from”-collider–an “active” object as opposed to a “passive” or “environmental” object.

And, well, while there is a collision-test for the case in which the “from”-object is a segment and the “into” object is a box, there isn’t one for the case in which the “from”-object is a box and the “into” object is a segment.

Hence the error!

Thank you Thaumaturge for your quick answer.

Scale is applied to the object at the moment of object creation to adjust its size, but a collision node is created and assigned later with the dimensions of the scaled object. So I don’t think the problem is here. I did the additional test by creating cubes using only vector graphics without any models; the problem is the same.

set Final is not required right now, I expected that it would be useful in the future, anyway removing it didn’t help.

I also played with bitmasks from and into and used all variants but it didn’t help the problem still present.

I have a question about into and from masks, My obstacles are generating dynamically and I need to assign to the boxes “into” and “from” masks together. This solution works fine during the generation environment but later as I understand I need to update then and set only “into” mask, right?

This doesn’t matter, I believe: You still have the collision object parented below the scaled node, and so the scale is applied to the collision object, inherited through the parent-child relationship, regardless of the size with which either is created or when either was created.

(In fact, by creating the collider at the size of the scaled node, you effectively end up with the scale being applied twice–once in creating the collider, and a second time as a result of the scaling factor being inherited.)

Note that the problem that I’m describing isn’t the actual size of the collider–it’s that any scaling factor at all is being applied to it.

I’m not sure that I understand: why do you need to assign both the “into” and “from” masks at generation time? Why not just set the “into” mask for your environmental colliders, and the “from” mask for your segments?

(Or, alternatively, set the “from” mask of the environmental colliders and the “into” mask of the segments to “0”–note, that’s “0”, not “bit(0)”. i.e. To an empty mask, rather than a mask with the first bit set.)

In short, unless you’re doing something pretty unusual, or something changes at a later point in the program, the masks that you assign at generation time should in general be the same masks that are used later.

So, what will be the best solution for having objects loaded from models with the ability to scale them and they must interact with the world using a collision model?

I will try to use boxes created using polygons without loaded models and scaling and will share result after it.

I would suggest that, instead of the collider being a child of the scaled models, the two share a common parent. That is, that the scaled model and the collider be both children of some third node.

This node could then be moved to move them both, and the model could be scaled without affecting the collider.

Or, in code terms, you currently have something like this, I believe (simplified):

myCube = loader.loadModel("modelFile")
myCube.setScale(0.2)

myCollider.reparentTo(myCube)

I’m suggesting that you instead have something like this (again, simplified):

myRoot = NodePath(PandaNode("root node"))

myCube = loader.loadModel("modelFile")
myCube.setScale(0.2)

myCube.reparentTo(myRoot)
myCollider.reparentTo(myRoot)

I have tried your solution, now I have 1 root node and under it 2 nodes, 1 with loaded from the model (box.egg) and the second with collision model (collisionBox).
The problem still remains the same:

:collide(error): Invalid attempt to detect collision from CollisionBox into CollisionSegment!
This means that a CollisionBox object attempted to test for an
intersection into a CollisionSegment object.  This intersection
test has not yet been defined; it is possible the CollisionSegment
object is not intended to be collidable.  Consider calling
set_into_collide_mask(0) on the CollisionSegment object, or
set_from_collide_mask(0) on the CollisionBox object.

it’s exist when

  1. box has into mask and segment from mask
  2. box has from mask and segment has into mask
  3. box has from mask and segment has from mask
  4. box into from mask and segment has into mask

I have also tried with box created using vector graphic without scale. problem is the same

Could you post the code for the first case (“box has into mask and segment from mask”), please? That should work, and I’d like to check what’s going on there.

(Ideally as a short, fully-working program, in case there’s something outside of the posted section that’s causing problems.)

the full program is too big, I will post only the parts:
Collision box:

                box_holder = NodePath(PandaNode("box root node"))
                box_holder.reparentTo(self.render)
                box = loader.loadModel("box")
                box.reparentTo(box_holder)

                box.setScale(4)

                min_point, max_point = box.getTightBounds()

                collision_cube = CollisionBox(min_point, max_point)
                collision_node_path = CollisionNode('collision')
                collision_node_path.setIntoCollideMask(BitMask32.bit(0))
                collision_node_path.addSolid(collision_cube)
                collision_node_path = box_holder.attachNewNode(collision_node_path)
                collision_node_path.show()   # Make the collision node visible

                box_holder.setPos(random.randint(self.area_x1, self.area_x2 - int(max_point.getX())),
                                   random.randint(self.area_y1, self.area_y2 - int(max_point.getY())),
                                   random.randint(self.area_z1, self.area_z2 - int(max_point.getZ())))
                self.logger.info("Box created: " + str(box))

Collision segment:

    def get_collision_between_points(self, start_point, end_point):
        segment = CollisionSegment(start_point, end_point)
        segment_node = CollisionNode('path_segment')
        segment_node.addSolid(segment)
        segment_node_path = self.render.attachNewNode(segment_node)

        segment_node.setFromCollideMask(BitMask32.bit(0))
        # segment_node.setIntoCollideMask(BitMask32.bit(0)) 

        base.cTrav.addCollider(segment_node_path, self.segmentQueue)
        base.cTrav.traverse(segment_node_path)

        if self.segmentQueue.getNumEntries() > 0:
            self.logger.debug("Collision detected!")
            self.draw_line(start_point, end_point, Vec4(1, 0, 0, 0.5))  # Draw red line when collision detected
            self.segmentQueue.clearEntries()
            return True

        self.draw_line(start_point, end_point, Vec4(0, 1, 0, 0.5))  # Draw green line when no collisions detected
        return False

That’s why I asked for a short, fully-working program. Which is to say, not the original program, but something brief that just includes the relevant parts.

It can help a lot in debugging these things, as it’s something that we can actually run and try out.

That said, a quick test on my side indicates that the default collide-mask is not zero, as I’d thought–my apologies for that!

As a result, I’d suggest explicitly setting the “from” mask of the environmental colliders to “0” and the “into” mask of the segments likewise to “0”.

(This can be done simply by passing “0” to the call to “setIntoCollideMask” and “setFromCollideMask”, as appropriate, rather than a BitMask.)

So, something like this:

segment_node.setFromCollideMask(BitMask32.bit(0)) # As you have it
segment_node.setIntoCollideMask(0) # <-- The new bit

And the other way around for the environmental colliders.

this is not resolved, and even more: boxes now not interacting with each other so now i’s possible to have several boxes in one place crossing.

Ah, I didn’t think that you would want environmental colliders to interact: often, I think, such colliders are left as only “into” objects.

In that case, you could leave the “from”-masks of the environmental colliders as you previously had them, and just set the “into”-masks of the segments to “0”.

But you say that it’s not resolved: could you be more specific, please?

And again, could you perhaps put together a simple (and complete) program that demonstrates the problem? It would be a lot easier to debug this with something runnable to hand, I imagine…

Here is short working program with the same problem:

from direct.showbase.ShowBase import ShowBase

from panda3d.core import Point3, NodePath, PandaNode, CollisionBox, CollisionNode, BitMask32, CollisionSegment, \
    LineSegs, CollisionTraverser, CollisionHandlerQueue, Vec4


class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.axis = None
        base.enableParticles()

        base.cTrav = CollisionTraverser()
        self.collisionQueue = CollisionHandlerQueue()
        base.mouseInterfaceNode.setPos(0, 30, -4.2)

        # Create and load obstacles
        self.create_box()

        self.init_collision_check()

    def create_box(self):
        box_holder = NodePath(PandaNode("box root node"))
        box_holder.reparentTo(self.render)
        box = loader.loadModel("box")
        box.reparentTo(box_holder)

        box.setScale(4)

        min_point, max_point = box.getTightBounds()

        collision_cube = CollisionBox(min_point, max_point)
        collision_node_path = CollisionNode('collision')
        collision_node_path.setIntoCollideMask(BitMask32.bit(0))
        collision_node_path.addSolid(collision_cube)
        collision_node_path = box_holder.attachNewNode(collision_node_path)
        collision_node_path.show()  # Make the collision node visible

        box_holder.setPos(10, 10, 10)

        base.cTrav.addCollider(collision_node_path, self.collisionQueue)
        base.cTrav.traverse(self.render)

    def init_collision_check(self):
        start_point = Point3(12, 5, 12)
        end_point = Point3(12, 20, 12)
        segment = CollisionSegment(start_point, end_point)
        segment_node = CollisionNode('path_segment')
        segment_node.addSolid(segment)
        segment_node_path = self.render.attachNewNode(segment_node)

        segment_node.setFromCollideMask(BitMask32.bit(0))
        # segment_node.setIntoCollideMask(BitMask32.bit(0))
        # segment_node.setIntoCollideMask(0)

        base.cTrav.addCollider(segment_node_path, self.collisionQueue)
        base.cTrav.traverse(segment_node_path)

        if self.collisionQueue.getNumEntries() > 0:
            self.draw_line(start_point, end_point, Vec4(1, 0, 0, 0.5))  # Draw red line when collision detected
            self.collisionQueue.clearEntries()

        self.draw_line(start_point, end_point, Vec4(0, 1, 0, 0.5))  # Draw green line when no collisions detected

    def draw_line(self, start_point, end_point, color):
        lines = LineSegs()
        lines.setColor(color)
        lines.moveTo(start_point)
        lines.drawTo(end_point)

        line_node = NodePath(lines.create())
        line_node.reparentTo(self.render)


app = MyApp()
app.run()

requirement panda3d==1.10.14

Okay, that helped a lot, thank you! :slight_smile:

So, I see a two problems:

First, is pretty much what I said above: you’re not setting the segment’s “into” collide-mask to zero.

When I uncomment the line that does this, the warning about colliding a cube into a segment goes away.

And second, when you call “traverse”, the parameter that you’re passing into the call is “segment_node_path”.

But what traverse does is to examine the node that it’s given to see whether anything within that node or below it has a collision. And since “segment_node_path” contains only the segment, and has nothing below it, there are no collisions found there.

Instead, what I suggest is to have the traversal examine “render”, instead. That is, to pass in “render” as the parameter to “traverse”.

Making this change causes the collision to be detected, at least on my end.

By the way, I note that you’re calling “traverse” right after adding your environmental colliders. I’m not sure that this is required: if your goal is to prevent those colliders from intersecting, could you not just let the automatic traversal that “base.cTrav” runs every frame in the background handle all of them on the next update?

(I also note that you’re using “base.cTrav”, which, as noted, automatically traverses in the background on each frame. This seems like it might be needless when you’re calling “traverse” manually–but then, I don’t know what you’re doing in your main program, so perhaps it makes sense there.)

Thank you a lot, you saved me from long hours of debugging and resolved the problem I had all last month.

“base.cTrav” runs every frame
I didn’t know that, I think I will replace this traverse with another because I need it on the application start (and that is why I call it manually because frames are not present yet), and later from time to time.

1 Like