Moving control joint moves visible objects, but, breaks CollisionHandlerQueue

Hi There,

I’m using controlJoints from my egg files to attach “arrows” to other objects and visibly tie them together. This seems to work. I also use the CollisionHandlerQueue and CollisionTraverser to detect clicks and mouseovers of my objects. The objects the arrows connect work as expected with mouse interaction, but, the “arrows” do not work with the traverser. In fact, the traverser finds them at their un-moved location, even though they are not rendered there. I’v also noticed that the arrows disappear from the screen when the camera no longer includes the origin (which is where the arrows “start” before they are connected to their objects). I’m assuming I’m mis-using the controlJoint somehow, or perhaps have not completed the linkage correctly… here is the code I use to create the joints:

class ConnEdge(Model):
    """The class which implements the edge."""

    # @funlog
    def __init__(self, uid, model, app, connection, space="nord"):
        """Call the super constructor and attaches the model bones."""
        super().__init__(uid, model, app, tag='clickable', space=space)
        self.connection = connection
        self.tgtEnd = self.model.controlJoint(None, "modelRoot", "tgtBone")
        self.srcEnd = self.model.controlJoint(None, "modelRoot", "srcBone")
        # self.data.listJoints()

    # @funlog
    def setSrcPos(self, pos):
        """Set the position of the source bone."""
        self.srcEnd.setPos(pos)

    # @funlog
    def setTgtPos(self, pos):
        """Set the position of the target bone."""
        self.tgtEnd.setPos(pos)

    # @funlog
    def updateConnection(self, srcPos, tgtPos):
        """Update the positions of this edge."""
        quat = Quat()

        vec = tgtPos - srcPos
        look_at(quat, vec, Vec3.up())
        self.srcEnd.setHpr(quat.getHpr())
        self.setSrcPos(srcPos)

        vec = srcPos - tgtPos
        look_at(quat, vec, Vec3.down())
        self.tgtEnd.setHpr(quat.getHpr())
        self.setTgtPos(tgtPos)


    def __init__(self, uid, name, app, tag='clickable', space="nord", hoverable=True):
        """The constructor."""
        super().__init__()
        self.uid = uid
        self.name = name
        self.app = app
        self.onMappedFuncs = []

        # self.physics = BulletRigidBodyNode(uid)
        # self.physics.setMass(1.0)
        self.model = mm.load_model(name, space)
        self.space = space
        # bounds = self.model.getTightBounds()
        # if bounds is not None:
        #     radius = (bounds[1] - bounds[0]).length() / 3.0
        # else:
        #     err("No size data available for : {}:{}".format(name, uid))
        #     radius = 0.01
        # shape = BulletSphereShape(radius)
        # self.physics.addShape(shape)
        self.data = cm.cwc().attachNewNode(uid)

        self.model.reparentTo(self.data)
        self.data.reparentTo(cm.cwc())
        self.data.setScale(1.0, 1.0, 1.0)
        self.data.setTag(tag, uid)
        if hoverable:
            self.data.setTag('hoverable', uid)
        # self.world = cm.cur_world()
        # self.world.attach(self.physics)

        self.tooltip = Tooltip()
        self.tooltip.add_updater(self.update_tooltip)

It’s hard to tell exactly what is going on without more code; I’m not actually seeing the code where you set up your collisions. I only see some commented-out Bullet code. I also don’t know how your actors are set up or what exactly is being moved.

I don’t know whether this is the problem, but it might be worth pointing out that controlJoint is not a two-way process; it just creates a dummy node but it doesn’t actually have relation to the rest of the actor. If you want to attach something to it, you may want to exposeJoint the parent of the controlled joint, and then reparent the result of controlJoint to it.

Sorry, I pretty much cut and pasted the collisions from the doc code. But, here it is:

class MouseHandler(object):
    """Class which interprets pointer events."""

    # @funlog
    def __init__(self, base, app):
        """Prepare the collision detection objects."""
        base.disableMouse()
        self.app = app

        self.dragging = False
        self.mouseDown = False
        self.last_mouse_x = None
        self.last_mouse_y = None
        self.over_node = None

        # set up the pointer to object "collision" detector
        self.pointerTraverser = CollisionTraverser("pointer traverser")
        self.pickerNode = CollisionNode('mouseRay')
        self.pointerCollisionHandler = CollisionHandlerQueue()
        picker_np = camera.attachNewNode(self.pickerNode)  # noqa: F821
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.pointerTraverser.addCollider(picker_np,
                                          self.pointerCollisionHandler)

    def mouse_task(self, task):
        """Deal with changes in mouse position."""
        if not base.mouseWatcherNode.has_mouse() and self.over_node is None:  # noqa: F821
            return Task.cont
        elif self.over_node is not None and not base.mouseWatcherNode.has_mouse():
            mpos = self.last_pos
        else:
            mpos = base.mouseWatcherNode.getMouse()  # noqa: F821
            self.last_pos = mpos


        self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())  # noqa: F821
        self.pointerTraverser.traverse(cm.cwc())

        if self.pointerCollisionHandler.getNumEntries() > 0:
            self.pointerCollisionHandler.sortEntries()
            picked_obj = self.pointerCollisionHandler.getEntry(0)\
                .getIntoNodePath()
            picked_obj = picked_obj.findNetTag('hoverable')

            if not picked_obj.isEmpty():
                if self.over_node is None:
                    self.over_node = objects.fetch(picked_obj.getTag('hoverable'))
                    verb("Got a mouseover: {}".format(self.over_node))
                    em.mouse_over(self.over_node, self.pickerRay.getOrigin())
                elif self.over_node != objects.fetch(picked_obj.getTag('hoverable')):
                    em.mouse_was_over(self.over_node, self.over_node.getPos())
                    self.over_node = objects.fetch(picked_obj.getTag('hoverable'))
                    verb("Got a mouseover: {}".format(self.over_node))
                    em.mouse_over(self.over_node, self.pickerRay.getOrigin())
            else:
                if self.over_node is not None:
                    em.mouse_was_over(self.over_node, self.over_node.getPos())
                    self.over_node = None

The setTgtPos and setSrcPos are called in updateConnection as follows:

    def updateConnection(self, srcPos, tgtPos):
        """Update the positions of this edge."""
        quat = Quat()

        vec = tgtPos - srcPos
        look_at(quat, vec, Vec3.up())
        self.srcEnd.setHpr(quat.getHpr())
        self.setSrcPos(srcPos)

        vec = srcPos - tgtPos
        look_at(quat, vec, Vec3.down())
        self.tgtEnd.setHpr(quat.getHpr())
        self.setTgtPos(tgtPos)

updateConnection is called in calcPos (which is called when the “arrowed to/from” object is moved):

    def calcPos(self):  # noqa: N802
        """
        Calculate member offset positions.

        Updates the member model's sizes, positions,
        orientations accordingly.
        """
        src_size = 0
        s_end_size = 0
        tgt_size = 0
        t_end_size = 0

        s_pos = self.source.getWorldPos()
        t_pos = self.target.getWorldPos()

        norm_conn_vec = s_pos - t_pos
        norm_conn_vec.normalize()

        n = self.source.getTightBounds()
        if n is not None:
            mn, mx = n[0], n[1]
            src_size = (mx - mn).length() / 6.0

        n = self.target.getTightBounds()
        if n is not None:
            mn, mx = n[0], n[1]
            tgt_size = (mx - mn).length() / 6.0

        n = self.cSrc.getTightBounds()
        if n is not None:
            mn, mx = n[0], n[1]
            s_end_size = (mx - mn).length() / 6.0

        n = self.cTgt.getTightBounds()
        if n is not None:
            mn, mx = n[0], n[1]
            t_end_size = (mx - mn).length() / 6.0

        s_offset = src_size + s_end_size
        t_offset = tgt_size + t_end_size

        sp = (norm_conn_vec * s_offset * -1.0) + s_pos
        tp = (norm_conn_vec * t_offset) + t_pos

        self.cSrc.data.setPos(sp)
        self.cTgt.data.setPos(tp)
        self.cEdge.updateConnection(sp, tp)
        self.reparentTo(self.source.getParent())

All that said, I’ll see if I can figure out the order of operations for exposeJoint like you’ve mentioned. Do I need to use both controlJoint and exposeJoint? I’m really just hacking my way through the joint/bone business…

I haven’t looked through your code extensively, so please forgive me if I’m missing something here. ^^; That said:

You say that you’re attaching an “arrow” to an object, and using “controlJoint”. Are you essentially controlling a joint in an armature that animates the arrow? And if so, is your collision geometry an object parented below the “arrow” model?

If so, then the problem may be that joints animate vertices, not nodes. That is, by moving the joint, you move the vertices of the model, but don’t affect its logical position. This explains not only why the collision geometry doesn’t move (because the logical position of its parent hasn’t changed), but also why the model vanishes when its initial origin is not longer visible (because its bounds are relative to that origin, not the location of its vertices).

If this is the case, and depending on what you want, might it be easier to use “exposeJoint” on appropriate joints of the target object’s armature instead, and to parent your arrow to that? Doing so should result in the arrow’s node moving with the joint in question–and both its vertices and any children that it has with it.

Fantastic explanation! Moving the vertices and not the nodes completely explains what I’m seeing. I’ll figure out how to transition to exposeJoint, and if anything seems noteworthy, I’ll post here for posterity.

I’m glad if it helped! Good luck with implementation, and with the rest of your project! :slight_smile:

OK, not as straightforward as I hoped… I think I have a problem with my hierarchy. I have a joint in each end of the arrow. when I use exposeJoint, the arrow does not move. When I parent the controlJoint to the exposedJoint the arrow dos not move. I’m not sure how I could parent the arrow to either of the joints, as there are two joints per arrow… Basically, what I am trying to do, is attach one end of the arrow to a particular location, and the other end to another, and then stretch the arrow between the two points (which I can move independently). …

And I should add, if I didn’t already, the joints are part of the arrow’s .egg file, not the objects it stretches between. I’m not sure if that is the wrong approach or not.

Ah, I see!

In that case, first of all, I was suggesting that you “expose joints” on the object to which the arrows were attached, imagining that you were attaching your arrows to a single object–like an enemy peppered with projectiles.

Now that I have a better idea of what you want, I might suggest a simpler approach:

If your arrows are intended to be straight, with no curves, bends, etc., then you could just place and scale them such that they stretch between your two points. If you don’t want the distortion that this might incur, you could perhaps have the head of the arrow (and any other parts that you want undistorted) as separate models, placed accordingly without scaling.

If you’re modelling your arrows, or are in a position to request or make changes to them, it might be easiest if you place one of their ends at their origin. This allows you to simply put that end at one of your target-points, and then orient and scale the arrow as appropriate.

As to the orientation, you might try something like the “lookAt” or “headsUp” methods of “NodePath”.

As to the scaling, if your arrow has a length of one unit, then you should be able to simply scale it along the appropriate axis by the distance between your two points.

Will this approach allow for collision traversal such that I can mouseover and click on the “arrow”? I’ll give it a try and see. I think my only concern with the scaling is that I really wanted to stretch the arrow, rather than make it bigger across all dimensions. This my be how I ended up with the joints…

The image below is what I want to visibly achieve (this approach is accomplished via controlJoint). With this I can move the cube or sphere and then the joints.

Ah, right, I forgot about the collision–sorry about that! What I suggest there is, if feasible, to construct a collision object after placing and scaling the arrow, thus allowing you to create it with the appropriate size, based on its length.

That can be done! Alongside the “setScale” method, there are also “setSx”, “setSy”, and “setSz” methods that allow you to scale a NodePath along the x-, y-, and z- axes respectively. And if you model your arrow such that it points along one of these axes, then, even with rotation, scaling it along that axis should stretch it as you describe.

So far, it looks like the collision still works after the scaling. Now I just need to fine tune my sizes as I’m skewering the target end of the arrow. Thanks so much for the help!

It’s my pleasure! I’m glad that you seem to be making progress. :slight_smile: