Bone track to vector without rolling

I have a bone-A that tracks to another armature’s bone-B, via a vector.

If bone B is below bone A, bone A is right side up, but if bone-B is above bone A, then bone A is upsidown.

I understand what’s happening, but have no idea how to limit bone A’s Y-rotation while tracking.

Here’s my code. Any help is appreciated.

import bge
import random
from bge import logic
from bge import render
from mathutils import Matrix, Vector, Quaternion
cont = logic.getCurrentController()
own = cont.owner
par = own.parent

### Position of the tracker Bone
if par['IK_head'] != 'null': 
    Look_Target = par.children[1]

    boneloc = own.worldTransform * own.channels["head_root"].pose_matrix * own.channels["head_root"].location 

    own.channels["head_root"].rotation_mode = 0
    ##   the Vector
    myVect = Vector(Look_Target.worldPosition - boneloc).normalized()

    ####   display Global directional vector with a red Ray
    render.drawLine(Look_Target.worldPosition, boneloc, [255,0,0] ) 

    ## apply on the Orientation Matrix, the tracking transformation as a Matrix
    ownParent = own.channels["neck_01"]
    # apply the armature and the parent's rotation to the direction vector
    myVect = myVect * own.worldOrientation * ownParent.pose_matrix

    own.channels["head_root"].rotation_quaternion = myVect.to_track_quat('Z','Y')
    ## useless option if an action is already playing ?
    own.update()

just add to bone - constraint - limit rotation - and assign limit degress for axis XYZ and select type - global, local, local with parent - its very simple constraint for objects but this work - if you need off assign constraint - add armature actuator and add to actuator constraint and use influence

On most cases, that works. That is in fact my current setup.
However, it has no effect on the bone at all.

If you know why this is, please share.

    ##   the Vector
    myVect = Vector(Look_Target.worldPosition - boneloc).norma

maybe mix myVect with your local Y axis vector ? I did also lot of stuff like that, but i don’t have the code on this device

tbh, an ik rig should be setup in the armature, so all you need to do is position the ik target bone and let the builtin constraints system do the work it was designed to.

in fact, you can add an empty world<>world constraint on the target bone and specify the constraint .target to an object in the scene.

for limit rotation work - assign rotation diapazone XYZ plus and minus degress - if you not select diapazone constraint dont work, if diapazone equal zero bone not work. Check way bone you assign constraint - maybe bone children another bone and use forward link to parent, and use run armature for recalculation armature

Correct, but I am spawning in NPCs, and trying to make their heads track to invisible points, that jump smoothly to points of interest.

When I use the build in constraints, the track to bone will indeed track to the reference point, but as soon as I spawn in more NPCs, the track to function jumps randomly through the track to points, making the NPC’s head violently spasm back and fourth.

Since the obj.name is read-only, I can’t change it to make it unique for each spawn.

If you know a workaround, I’d love to hear it. This way is by far the simplest.

A easy workaround would be to find a way to make the point the eye tracks to have a unique name or ID when it spawns.

bones constrained to bones in the same armature seem to handle themselves in my experience, but you are right about pointing to objects.

this is why i mentioned setting the target in python. basically each npc has an empty object that is used to control the bone. then all you have to do is move the object around.

head_pos = owner.worldPosition+owner.getAxisVect((0, 0, 0.6)) # offset the track pivot point
target = player.worldPostion-owner.worldPosition # look at player object
obj.woldPosition = head_pos+target.normalized() # normalized to limit floating point errors

you add a constraint to the track bone and then toggle the “visibility” to disable it. then you can fill the target attribute with python, and then enable it.

ch = rig.channels.get("bone", None)
ct = rig.constraints.get("bone"+":Constraint", None)

if ct != None:
    ct.active = True
    ct.target = obj

I’ve stared at your reply for a while and I don’t get it…

I think we’re on the same page that you can’t track to spawned objects since they all share the same name, so I assume you’re saying the head tracks to a bone (We’ll call it Eyepoint) in the same armature, and that bone jumps to the empty’s location?

If we can’t track to a specific spawned-empty, how can we return the spawned-empty’s location to move the Eyepoint bone to?

I can say:

cont = logic.getCurrentController()
own = cont.owner
par = own.parent
Look_Target = par.children[1]

And now, “Look_Target” is the object we need to be tracking to, but we can’t move the bone to Look_Target, as it always returns that the bone Eyepoint, defined as own.channels["Eyepoint"] has no attribute worldOrientation.

Even if I wanted to skip the object the bone Eyepoint is jumping to, I would still have no way to move the bone Eyepoint, as I get the same error “has no attribute worldOrientation.”

Am I confused?

objects are unique identities, the name is just one way of getting an object. like in your example, own.children[1]* you are getting the object itself. that is what you plug into the constraint.
https://shuvit.org/python_api/bge.types.BL_ArmatureConstraint.html#BL_ArmatureConstraint.target

i would recommend doing something like ct.target = own.children[ct.target.name]. what that does is gets the name of the object which you have picked in the blend file, but points it to the specific instance of that object relevant to the owner.

armature channels (bones) arent normal objects, they use a different api
https://shuvit.org/python_api/bge.types.BL_ArmatureChannel.html

*(fun fact, you can also do own.children["Look_Target"] to get the object by name, in context of the owner)

in the image below, im using a constraint to have a bone in a suspension rig track to a wheel. for each wheel i add a new wheel object newobj = scene.addObject("Wheel.Buggy") and assign it to the constraint ct.target = newobj
Untitled

I did that. I used:

constraint = own.constraints["head_root:Track To"]
constraint.target == par.children[1]

I can change the target successfully, but it still gets confused when multiple NPCs spawn in and spasms looking at all points.

When I printed what par.children[1] was, it returned the same string for every single NPC, hence the issue.

When I used constraint.target == par.children[1].name it does the same.

I also tried constraint.target == par.children["IK_eye_point_NPC"] where IK_eye_point_NPC is the name of the object the head bone tracks to, and it did not return a unique identity as well.

If I can change this one, single line to get the objects unique identity, everything wold work perfectly. What am I missing?

example_head_tracking.blend (573.7 KB)

I uploaded a blend if you want to see the issue live.

Assets are on layer 16 right below layer 1 FYI

Edit: Wait, if their tracking points are all on the same area, why are they still spasaming?
I’m starting to think the problem might not be what I thought it was…

Edit2: Update; I used a copy-location constraint on a bone, and made the head follow the bone instead of the object directly. Same exact glitch…

i think i know what your problem is. are you using upbge? vanilla bge has this issue and upbge fixed alot of constraint related issues. depending on the needs of the project, you might want to consider upbge 0.2.0 over the latest 0.2.5b. i find it to be much more stable.

WARNING: upbge files cant return to bge, make a BACKUP before testing in upbge. routinely backing up is always a good idea.

It seems not. I just tried it in the version of UPBGE you linked, and the same issue persists.

Can we at least find a way to limit a bone’s rotation via python on a single axis, so I can use the old, original code from the beginning? There’s no way it has to be this hard…

heres the fixed blend. the distance checker was broken (referencing owner, not parent) and i added the constraint target assign to the parent object (the code i posted earlier).

example_head_tracking_u020.blend (631.6 KB)

it was saved in upbge 020, you can try and test it in 2.79, i dont think theres anything that would break too bad going backward

Working on it, let me test for a bit and get back to you…
Thank you so far for all your help though. I really appreciate it.

It can’t even run on my machine. I have several plugins and complicated esoteric fixes and workaround that seem to be conflicting with UPBGE.

This is in fact why I never migrated toward UPBGE.

Worth a shot though.

Is there just a way to limit one single axis of rotation for my original code?

One step closer to a solution.

It seems a constraint, added to the bone must have a target pre-set when the game starts, else it returns an error.

However, that target cannot be fully scrubbed or re-set.

The error I’m experiencing is the tracker flipping between the new target and the old target, that cannot be removed.

This can be replicated by setting the starting target to something directly above the NPC, and setting a new target in python as soon as the game starts. It will flick between the new target and the old target above.

I was wrong to assume this was simply an issue of non specific object identities.

This is a genuine, downright glitch.

Still working on it… Any ideas are welcome.