Measuring angles between bones in bge

Hi there!

I’m new to blender and to python, so please go easy on me!

I’ve built a human model, whose bones I move via mocap , and I want to get the angle values (X,Y, Z) between body limbs.
For example, I want the 3D orientation of the hand in reference to the forearm coordinate axis system.
I’ve searched for days and I have tried several methods, like “joint_rotation” and ‘‘rotation_difference’’, but I don’t get the desired result.

I think Blender gives me the angles in reference to the global axis, or other axis system.

How can I do this properly? It seemed to be simple and straightforward, but i’m finding a lot of difficulty here.

Thanks in advance,

Santi.

Any ideas about this? I can’t believe something so simple can give so much trouble…

Hmmmm, rotation is pretty much always tricky(at least for me : P), especially when computing them somehow.

If I recall correctly, the BGE uses quaternions to represent orientation, so you’ll have to get those(for the bones being measured between) and number crunch 'em a little to get your desired results.

I’ve done this before, but I won’t have access to that data for at least half a year(I’m away from home at the moment).

However, I’m not sure that’s really necessary - directly interacting with bones in the BGE is very expensive computationally, but since this is the BGE we have lots of options.

What do you intend to use this for? The more we know about your specific problem, the easier it is for us to help ; )

The BGE does not use quaternions when dealing with objects. But it can use them when dealing with rotation channels on armature bones.

I suggest to look at the armature channels


import bge

controller = bge.logic.getCurrentController()

armature = controller.owner

for channel in armature.channels:
    print("name:", channel.name)
    if channel.rotation_mode == bge.logic.ROT_MODE_QUAT:
        print("rotation mode: quaternion ")
        print("rotation_quaternion:", channel.rotation_quaternion)

Here’s a demo I whipped up for objects, I don’t think it will work for bones. But the logic should be similar. ; )

By the way, BlueprintRandom has done multiple experiments that should be of help, but they can be difficult to understand at times.

Attachments

Retrieving_Local_Orientation_00.blend (529 KB)

Thanks to both of you for your advice!

I’m getting the ABSOLUTE orientation of the bones (in quaternions) through some imu sensors, and with that quaternions the bones move to the desired orientation using rotation_quaternion (motion capture).

The purpose of the project is to measure the angles (X,Y,Z) between body limbs. I manage to capture the movements correctly, but I’m having trouble to display this angles. I used “joint_rotation” and ‘‘rotation_difference’’, but I didn’t get the desired result, and I think that’s because of the coordinate system.

I’d like to get the angles of one limb (for example a hand) in reference to the local axis of his parent (in this case, the forearm)

I haven’t found a solution to this issue and I’m getting desperate… I think this should be much more intuitive but I see that it isn’t…

As far as I know the rotation is relative to the parent bone. I do not understand the exact issue. Maybe you show some of your results (demo .blend, the values you get and the values you expect).

I have the absolute orientation of the body limbs (hand, forearm, arm, etc) in quaternions, given by inertial sensors put in these limbs. I want to get the orientation of each limb in respect to its parent. The blender armature moves correctly, so I KNOW the data is being transferred right. I’m not being able to get the relative orientation (in EULER angles) in real time during Blender Game.

I have tried “joint rotation” and “rotation difference” functions. The values I get are correct till I rotate the limb around Y axis. At that moment, Z and X values for orientation start to get wrong, so I think it’s a problem with coordinate systems.

i took a quick look at your problem and this is what i came up with, it gives the angle in radians.

the script runs on the armature.


import bge
import numpy as np

def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()

own = cont.owner

hand = own.channels["hand"]
forearm = own.channels["hand"].parent
upper = own.channels["forearm"].parent

v1 = hand.pose_tail - hand.pose_head
v2 = forearm.pose_head - hand.pose_head

ang = angle_between(v2,v1)
own["angle_hand"] = ang

v1 = forearm.pose_tail - forearm.pose_head
v2 = upper.pose_head - forearm.pose_head

ang = angle_between(v2,v1)
own["angle_elbow"] = ang


What do you mean with “absolute orientation”? Absolute typically mean it is relative to the world (scene). Then you talk about “orientation … in respect to its parent” which is the local orientation. This is what you already get when reading the channels. The channels define the transformation from parent space.

I run a test and I can confirm that changing the rotation of a parent bone (channel) does not change the rotation of the child bone (channel).

I have three bones:

  • Bone is turned 45° around Y (as quaternation)
  • Bone.001 is turned -90° around Y
  • Bone.002 is turned 45° around Y and 30° around X
Blender Game Engine Started

name: Bone
rotation mode: quaternion
rotation_quaternion: <Vector (0.9239, 0.3827, 0.0000, -0.0000)>


name: Bone.001
rotation mode: euler XYZ
rotation_euler (rad): <Vector (-1.5708, 0.0000, 0.0000)>
rotation_euler (deg): <Vector (-90.0000, 0.0000, 0.0000)>


name: Bone.002
rotation mode: euler XYZ
rotation_euler (rad): <Vector (0.7854, 0.5236, -0.0000)>
rotation_euler (deg): <Vector (45.0000, 30.0000, -0.0000)>
Blender Game Engine Finished

turned Bone.001 by 13° around Y (additional to the X turn):


Blender Game Engine Started


name: Bone
rotation mode: quaternion
rotation_quaternion: <Vector (0.9239, 0.3827, 0.0000, -0.0000)>


name: Bone.001
rotation mode: euler XYZ
rotation_euler (rad): <Vector (-1.5708, 0.2269, 0.0000)>
rotation_euler (deg): <Vector (-90.0000, 13.0000, 0.0000)>


name: Bone.002
rotation mode: euler XYZ
rotation_euler (rad): <Vector (0.7854, 0.5236, -0.0000)>
rotation_euler (deg): <Vector (45.0000, 30.0000, -0.0000)>
Blender Game Engine Finished

The provided channel of Bone.002 (child of Bone.001) stays the same.

Used code:


import bge
import converter
controller = bge.logic.getCurrentController()


armature = controller.owner


for channel in armature.channels:
    print()
    print("name:", channel.name)
    if channel.rotation_mode == bge.logic.ROT_MODE_QUAT:
        print("rotation mode: quaternion ")
        print("rotation_quaternion:", channel.rotation_quaternion)
    if channel.rotation_mode == bge.logic.ROT_MODE_XYZ:
        print("rotation mode: euler XYZ ")
        print("rotation_euler (rad):", channel.rotation_euler) 
        print("rotation_euler (deg):", converter.toDegree(channel.rotation_euler)) 

convert.py


import math


def toDegree(vectorInRadians):
    vector = vectorInRadians.copy()
    for i in range(len(vector)):
        vector[i] = math.degrees(vector[i])
    return vector

[–cut–unformatted–]

evidently i m almost the unique to know(/experiment) the API of armature :wink:
bone.rotation_quaternion is the rotation relative to the bone itself in rest pose

you can get the matrix4x4 of the bone(in armature space), convert in quaternion and get the difference


import bge
def get_angles_difference(bone1, bone2):    
    q1,q2 = [b.pose_matrix.to_quaternion() for b in [bone1,bone2]]    
    return q1.rotation_difference(q2).to_euler()

cont = bge.logic.getCurrentController()
arm = cont.owner
b1 = arm.channels["hand"]
b2 = b1.parent
x,y,z = get_angles_difference(b1,b2)
print(x,y,z)


Many thanks for all your replies!

I tried MarcoIT’s code but the results are the same. It measures the angles X and Z correctly, but when the limb rotates around Y axis, it doesn’t show the expected values for X and Z (I rotate around X and it changes the values for X and Z)

i not think is wrong, instead is not what you want to know, you do nothing with the rotation difference with the parent, im pretty sure.
i suppose you use constraint (copy trasform, world->world) to move the bones.
since you say you had already all quaternions of the bone :

I have the absolute orientation of the body limbs (hand, forearm, arm, etc) in quaternions, given by inertial sensors put in these limbs. I want to get the orientation of each limb in respect to its parent.

so basically the thing you have to do (to save the animation) should be:
remove all flags of “inerit Rotation” otherwise each bone “parent” force the childrens with it rotations and do a mess.
with constraint is not a problem since constraints overrite all.

after that , -i had made a bit of tests- you can read the offset correct (bone.rotation_quaternion) and is this that you should save(not the rotation difference with parent)

that is the code:


import bge


cont = bge.logic.getCurrentController()
arm = cont.owner


if not "action_quaternion" in arm:
    arm["action_quaternion"] = []




for b in arm.channels:
    m4 = (b.channel_matrix.inverted()*b.pose_matrix).inverted() *b.pose_matrix
    q_offset = m4.to_quaternion()
    arm["action_quaternion"].append(tuple(b.name, tuple(q_offset))) # this should be written on a file i guess
    x,y,z = [round(v,2) for v in q_offset.to_euler()]
    print(b.name, x,y,z)
    
arm.update()



i not understand fully this code, but simply work :wink:

but, remember, remove “inherit Rotation” to all bones

i made some changes “last minute” to the code that make it pretty hard to understand.you can do a test very simple (as was the original)use the original armature(ARMATURE MASTER) as is without change nothingand animate it normally as you do already(i guess with constraints)then do a duplicated armature using ALT+D.(ARMATURE SIM)on the duplicated armature remove the flags “inerit Rotation” and remove all constraints.then put an always true and this script on ARMATURE SIMthe armature sim read the armature masterand (if the script work) should animate in the same way as the master

import bgecont = bge.logic.getCurrentController()arm = cont.ownerNAME_ARMATURE_MASTER = "REAL_NAME_HERE" #

Thanks for your attention Marco!

I tried last code you posted and I get an error with append function, console shows a message telling that append function has too many arguments :confused:

Hi, sorry, it has also another error .

the correct code (this time tested without changes!)
this code run on the “armatureMocap” only, the one that run already well ,
it write on a property of itself the quaternions “ready to use” for other armatures.
it not modificate the behaviour of the armature, is just a “translation of rotations” for other armatures.
this can have or not the inerit rotation, no matter


import bge


cont = bge.logic.getCurrentController()


own = cont.owner


if not "init" in own:
    own["last_frame_quaternion"] = []
    own["action_quaternion"] = []
    own["init"] = 1
    
own["last_frame_quaternion"] = []
for b in own.channels:
    m4 = (b.channel_matrix.inverted()*b.pose_matrix).inverted()*b.pose_matrix
    q_offset = m4.to_quaternion()
    own["last_frame_quaternion"].append((b.name, tuple(q_offset)))
    
own["action_quaternion"].append(own["last_frame_quaternion"])



then, this is the code that run on the “others armatures” that want mimic the armatureMocap
the armatures need to be similar , the names of the bones have to match with the names of bones of armatureMocap (where i added the try/except) , and the “inerit Rotation” must be removed in the UI otherwise the rotations are “multiplicated”


import bge
import mathutils






cont = bge.logic.getCurrentController()


own = cont.owner


if not "arm_mocap" in own:
    arm_mocap = own.scene.objects["Armature_mocap"]
    own["arm_mocap"] = own.scene.objects["Armature_mocap"]


else:
    arm_mocap = own["arm_mocap"]
    if not arm_mocap.invalid:
        if "last_frame_quaternion" in arm_mocap:
            for keyframe in arm_mocap["last_frame_quaternion"]:
                bone_name, quaternion_offset = keyframe
                try:
                    bone = own.channels[bone_name]
                except:
                    print(bone_name, " not found")
                    continue
                bone.rotation_quaternion = mathutils.Quaternion(quaternion_offset)
                
    own.update()

if is not right this time i need to some more clarifications , because to me seem that work perfectly.
anyway keep in count that for trasformations global->global you can use constraints of the bone (as copy rotation(world->world or also world -> local, can be useful depend)):wink:

Hi, sorry I couldn’t test the code until today…

I get the following error:

KeyError: “CList[key]: “Armature_mocap” key not in list”

I am probably doing one or more things wrong, because I don’t understand that ‘Armature mocap’ thing.(I’m a total newby with blender, never used it before and I don’t think I will use it much in the future)