Collision masks not working :(


#1

HI guys. I am having trouble with collision masks in panda3d . I am doing this experiment. I have created two actors in the scene. Each one of them has a collision ray as a from object and a collision sphere. I am using the collision ray to check for intersections with the sphere of the other actor, so as to implement the object avoidance steering behavior. For each ray , I have a collision handler queue. The problem is that the intersections are not detected and the actors collide into one another.

import math
import sys
import time

from direct.actor.Actor import Actor, CollisionSphere
from direct.interval.FunctionInterval import Func, Wait, messenger
from direct.interval.MetaInterval import Sequence, Parallel

from direct.showbase.InputStateGlobal import inputState
from direct.showbase.ShowBase import ShowBase, CollisionNode, CollisionTraverser, CollisionHandlerEvent, CollisionRay, \
    CollisionHandlerFloor, NodePath, CollisionHandlerPusher, Vec2, CollisionHandlerQueue, CollisionEntry
from direct.showbase.ShowBaseGlobal import globalClock
from direct.interval.ActorInterval import ActorInterval
from panda3d.ai import AIWorld, AICharacter

from panda3d.core import AmbientLight, CollisionBox, CollisionCapsule, CollisionPlane, Plane
from panda3d.core import DirectionalLight
from panda3d.core import Vec3
from panda3d.core import Vec4
from panda3d.core import Point3
from panda3d.core import TransformState
from panda3d.core import BitMask32
from panda3d.core import Filename
from panda3d.core import PNMImage
from panda3d.physics import ForceNode, LinearVectorForce, ActorNode, PhysicsCollisionHandler
from sympy import Point







class AI_behaviour(object):

    def get_force(self):
        pass

    def is_complete(self):
        pass

    def get_weight(self):
        pass


class seek(AI_behaviour):

    def __init__(self,actor,target):
        self.actor = actor
        self.target = target
        self.threshold = 2
        self.target.set_excluded_obstacle()
        self.actor.activate_obstacle_avoidance()



    def get_force(self):
        print("actor pos = ",self.actor.get_pos())
        desired_velocity = self.target.get_pos() - self.actor.get_pos()
        desired_velocity.z = 0
        desired_velocity = desired_velocity.normalized()*self.actor.get_max_speed()
        velocity = self.actor.get_velocity()

        steering_force = desired_velocity - velocity
        force_mag = int(math.sqrt(steering_force.x*steering_force.x+steering_force.y*steering_force.y))
        if force_mag> self.actor.get_max_force():
            steering_force = steering_force.normalized()*self.actor.get_max_force()
        print("normalized seek force =  ",steering_force)
        return steering_force


    def is_complete(self):
        actor_pos = self.actor.get_pos()
        target_pos = self.target.get_pos()

        dx = actor_pos.x - target_pos.x
        dy = actor_pos.y - target_pos.y
        dist = int(math.sqrt(dx*dx + dy*dy))
        if dist < self.threshold:

            self.actor.deactivate_obstacle_avoidance()
            self.target.reset_excluded_obstacle()
            return True
        return False

    def get_weight(self):
        return 0.5















class AIChar(object):

    def __init__(self,name,actor,mass,speed,force,render,obs_avoid = False,obs_avoid_t = 5.0):
        self.maxspeed = speed
        self.maxforce = force
        self.actor = actor
        self.name = name
        self.AI_behaviours = []
        self.velocity = Vec3(0.0001,0.0001,0)
        self.force = Vec3(0,0,0)
        self.render = render
        self.mass = mass
        self.obstacleAvoidance = obs_avoid
        self.cnp = self.actor.attachNewNode(CollisionNode(name+"_ray"))
        self.collision_handler = CollisionHandlerQueue()
        self.obs_threshold = obs_avoid_t

    def add_ai_behaviour(self,behaviour):
        self.AI_behaviours.append(behaviour)

    def activate_obstacle_avoidance(self):
        self.obstacleAvoidance = True

    def deactivate_obstacle_avoidance(self):
        self.obstacleAvoidance = False

    def apply_force(self,f):
        f = f.normalized()
        f = f * self.maxforce
        old_velocity = -(self.render.getRelativeVector(self.actor,Vec3(0,1,0))).normalized()

        self.velocity = self.velocity + (f/self.mass)
        new_velocity = self.velocity.normalized()
        print("new velocity = ",new_velocity)


        angle = old_velocity.signedAngleDeg(new_velocity,Vec3(0,0,1))
        old_orientation = self.actor.getHpr()
        new_orientation = Vec3(old_orientation.x+angle,old_orientation.y,old_orientation.z)
        self.actor.setHpr(new_orientation)

        v_mag =int(math.sqrt( self.velocity.x*self.velocity.x + self.velocity.y*self.velocity.y))
        if v_mag>self.maxspeed:
            self.velocity = self.velocity.normalized()*self.maxspeed
        self.actor.set_pos(self.actor.getPos()+self.velocity)

    def get_ai_behaviours(self):
        return self.AI_behaviours

    def remove_ai_behaviour(self,behaviour):
        self.AI_behaviours.remove(behaviour)

    def get_velocity(self):
        return self.velocity

    def avoid_obstacles(self,obstacle):
        obstacle_pos = obstacle.getIntoNodePath().get_parent().get_pos()
        actor_pos = self.actor.get_pos()
        collision_contact_point = obstacle.getSurfacePoint(self.render)

        adj = collision_contact_point - obstacle_pos
        adj.z =0
        dx = collision_contact_point.x - obstacle_pos.x
        dy = collision_contact_point.y - obstacle_pos.y
        dist = int(math.sqrt(dx*dx+dy*dy))
        if dist < self.obs_threshold:
            diff = self.obs_threshold - dist
            adj = adj.normalized()
            force = adj*diff
            return force

        else:
            return Vec3(0,0,0)


    def create_collision_ray(self):

        self.cnp2=self.actor.attachNewNode(CollisionNode(self.name))

        self.cnp2.node().addSolid(CollisionSphere(Vec3(0.0,0.0,0.0),2.0))
        self.cnp2.node().setCollideMask(BitMask32.bit(2))

        self.cnp2.show()


        ray = CollisionRay(0,0,1,0,-1,0)



        self.cnp.node().clearSolids()

        self.cnp.node().addSolid(ray)
        self.cnp.node().setCollideMask(0)
        self.cnp.node().setFromCollideMask(BitMask32.bit(2))
        self.cnp.show()


        return self.cnp






    def get_collision_handler_queue(self):
        return self.collision_handler

    def get_pos(self):
        return self.actor.get_pos()







    def get_max_force(self):
        return self.maxforce

    def get_max_speed(self):
        return self.maxspeed






class MyAIWorld(object):

    def __init__(self,render):
        self.AIChars = []
        self.render = render
        self.obs_avoid_weight = 0.9


    def add_AI_char(self,char):
        self.AIChars.append(char)

    def combine_steering_forces(self,forces):
        force = Vec3(0,0,0)
        if(len(forces)==1):
            print("only seek force",forces[0][0])
            force = force + forces[0][0]
        else:
            for f,w in forces:
                force= force + (f*w)
        return  force






    def update(self):

        traverser = CollisionTraverser()
        queues = {}
        forces = {}

        for char in self.AIChars:
          forces[char]=[]
          if char.obstacleAvoidance:
            queue = char.get_collision_handler_queue()
            from_char_ray = char.create_collision_ray()
            traverser.addCollider(from_char_ray,queue)
            queues[char] = queue


          for behaviour in char.get_ai_behaviours():
              if behaviour.is_complete():
                  char.remove_ai_behaviour(behaviour)
              else:
                  force = behaviour.get_force()
                  forces[char].append((force,behaviour.get_weight()))


        traverser.traverse(self.render)
        traverser.showCollisions(self.render)
        for char,queue in queues.items():
            if queue.get_num_entries()!=0:

                queue.sortEntries()
                closest_obstacle = queue.get_entry(0)
                force = char.avoid_obstacles(closest_obstacle)
                forces[char].append((force,self.obs_avoid_weight))

        for char in self.AIChars:
            if len(forces[char])!=0:
                net_force = self.combine_steering_forces(forces[char])
                char.apply_force(net_force)





















class MObj(object):
    def __init__(self,name,loader):
        self.name = name
        m = loader.loadModel("./models/cube/cube")
        self.model=m
        self.add_bounding_box()
        self.add_collision_ray()
        self.excluded_obstacle = False






    def add_bounding_box(self):
        min,max = self.model.getTightBounds()
        sz = max-min
        cs = CollisionBox(Point3(0,0,0),2.0,2.0,2.0)
        self.cnodepath = self.model.attachNewNode(CollisionNode(self.name))
        self.cnodepath.node().addSolid(cs)
        self.cnodepath.show()
        self.cnodepath.node().setFromCollideMask(0)
        self.cnodepath.node().setIntoCollideMask(BitMask32.bit(2))

    def set_excluded_obstacle(self):
        self.cnodepath.node().setIntoCollideMask(0)

    def reset_excluded_obstacle(self):
        self.cnodepath.node().setIntoCollideMask(BitMask32.bit(2))


    def add_collision_ray(self):

        self.raynodepath = self.model.attachNewNode(CollisionNode(self.name+str(1)))


        self.raynodepath.node().addSolid(CollisionRay(0, 0, 0, 0, 0, -1))



    def set_pos(self,pos):
        self.model.set_pos(pos)

    def set_scale(self,scale):
        self.model.set_scale(scale)

    def get_pos(self):
        return self.model.get_pos()

    def reparent(self,parent):
        self.model.reparentTo(parent)

class Character(object):

    def __init__(self,name):
        self.name = name
        self.speed = 5
        self.mass = 100
        self.force = 0.05

        self.actorNP = Actor('models/boy3/jack_walk.egg', {
            'stand': 'models/boy3/jack-stand.egg',
            'fall':'models/boy3/jack-fall.egg',
            'walk':'models/boy3/jack_walk_origin-Armature|mixamo.com|Layer0.002.egg',
            'talk':'models/boy3/jack-talk-Armature|mixamo.com|Layer0.001.egg',
            'idle':'models/boy3/jack-idle-Armature|mixamo.com|Layer0.egg',
            'sit':'models/boy3/jack-sit-Armature|mixamo.com|Layer0.002.egg'})
        self.add_physics()









    def set_pos(self,pos):

        self.anp.set_pos(pos)



    def add_ai_behavior(self,render):
        self.AIChar = AIChar(self.name,self.anp,self.mass,self.speed,self.force,render,obs_avoid=True)





    def set_scale(self,scale):
        self.actorNP.set_scale(scale)

    def get_pos(self):
        return self.anp.get_pos()

    def add_physics(self):

        self.an = ActorNode()




    def get_speed(self):
        return self.speed




    def reparent(self,parent):
        self.anp = parent.attachNewNode(self.an)
        self.actorNP.reparentTo(self.anp)








class walk_towards(object):

     def __init__(self,actor1,object1,last= True):
       self.actor1 = actor1
       self.object = object1
       self.last = last
       self.pos1 = actor1.get_pos()
       self.pos2 = object1.get_pos()
       self.type = "pending"
       speed = actor1.AIChar.get_velocity()

       self.seq =  Sequence(self.actor1.actorNP.actorInterval("walk",loop = 1))

       self.isplaying = False




     def get_time(self):
         return self.time

     def play(self):

         self.isplaying = True

         print("walking")
         print("creating seek behaviour")
         self.seek_behav = seek(self.actor1.AIChar,self.object)
         self.actor1.AIChar.add_ai_behaviour(self.seek_behav)


         self.seq.loop()


     def stop(self):
         print("action stopped")
         self.seq.finish()
         if self.last==True:
             self.actor1.actorNP.loop("idle")
         print("finish called")

     def is_complete(self):
        if self.seek_behav.is_complete():
            return True
        else:
            return False






























class Scene_Manager(ShowBase):

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

        self.pending_actions = []

        base.setBackgroundColor(0.1, 0.1, 0.8, 1)
        base.setFrameRateMeter(True)

        base.cam.setPos(0,0,40)
        base.cam.setHpr(90,0,0)

        base.cam.lookAt(0, 0, 0)



        self.set_up_ai()

        self.taskMgr.add(self.update_ai_world,"updateAI")


        self.add_ground()

        self.object1 = MObj("Box1",self.loader)
        self.object1.set_pos(Point3(-8,-10,-4))
        self.object1.reparent(self.render)
        self.object1.set_scale(0.5)

        self.object2 = MObj("Box2",self.loader)
        self.object2.set_pos(Point3(8,-10,-5))
        self.object2.reparent(self.render)
        self.object2.set_scale(0.5)





        self.actor1 = Character("Jack")
        self.actor1.reparent(self.render)
        self.actor1.add_ai_behavior(self.render)

        self.AIWorld.add_AI_char(self.actor1.AIChar)

        self.actor1.set_pos(Point3(5,-10,-6))
        self.actor1.set_scale(2.0)

        self.actor2 = Character("Jack2")

        self.actor2.reparent(self.render)
        self.actor2.add_ai_behavior(self.render)

        self.AIWorld.add_AI_char(self.actor2.AIChar)
        self.actor2.set_pos(Point3(-5,-10,-6))
        self.actor2.set_scale(2.0)


        self.walk = walk_towards(self.actor1,self.object1)
        self.walk2 = walk_towards(self.actor2,self.object2)




        self.pending_actions.append([self.walk])
        self.pending_actions.append([self.walk2])














        self.taskMgr.add(self.check_state,"check")





        #base.cTrav.showCollisions(self.render)






    def check_state(self,task):


        for seq_action in self.pending_actions:
            if len(seq_action)!=0:
                if not seq_action[0].isplaying:
                    seq_action[0].play()

        for seq_action in self.pending_actions:
            if len(seq_action)!=0:
                if seq_action[0].is_complete():
                    seq_action[0].stop()
                    seq_action.pop(0)
                    print("action completed")




        return task.cont









    # def set_up_physics(self):
    #     base.enableParticles()
    #     gravityFN= ForceNode('world-forces')
    #     gravityFNP=self.render.attachNewNode(gravityFN)
    #     gravityFN.addForce(self.gravityForce)




    def set_up_ai(self):
        self.AIWorld = MyAIWorld(self.render)




    def update_ai_world(self,task):
        self.AIWorld.update()
        return task.cont



    def add_ground(self):
        ground = CollisionNode('ground')
        cs = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -6)))
        ground.addSolid(cs)
        cnp2=self.render.attachNewNode(ground)
        cnp2.show()
        cnp2.node().setIntoCollideMask(BitMask32.allOn())











if __name__=="__main__":

    scene = Scene_Manager()
    scene.run()

#2

Your code as-is is not runnable, since model files have not been included, so it’s hard to help you debug this issue. Also, for future reference, please simply surround your code with code fences instead of indenting it when posting in the forums:

```python
..code..
```

Do you intend for the spheres to be able to collide into each other? While you’ve given the spheres a “from” mask, you have not added their CollisionNodes (cnp2) to a handler, so there is nothing to respond to their collisions. Or are you only wanting the rays to collide into the spheres?


#3

I want the rays to collide with the spheres
Here is the model and its walk animation
https://drive.google.com/file/d/1A_GredEYmyjlLuAIYdBYSHSTN0IU-2uf/view?usp=sharing
https://drive.google.com/file/d/1C4eYEVnEfmkGXxeEuBbojmZBBGgjxQlo/view?usp=sharing

Here is the cube model
https://drive.google.com/file/d/1B4Aaw2pKi4QGSmt0CXET4Bh3cgyTQU1f/view?usp=sharing


#4

You really shouldn’t be creating a new CollisionTraverser every frame. Just create it once, call show_collisions() on it once, and you’ll see it is in fact producing collisions:

If I print out the closest_obstacle variable, I can also see that it is indeed producing CollisionEntry objects.


#5

yeah , you are right. Thank u . But still I feel something is not logical. It seems that each actor’s ray is colliding with its own sphere. I noticed that when printing the collision surface point of the closest obstacle. In addition, even though i set the collide mask of the ray to 0 , I still get the warning

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

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

#6

That warning comes from a ray you defined elsewhere in your code, using self.raynodepath = self.model.attachNewNode(CollisionNode(self.name+str(1))) in add_collision_ray, on which you didn’t change the collision masks from the default.

Also note that setCollideMask sets both the into and from collision masks. So you have set your spheres as being both “into” and “from” solids.