Quaternion problems

Hi Guys and Gals. I’ve been trying to code my way out of a problem. Essentially, I’ve been trying to get an object to roll in whatever direction its travelling, so if moved on the x axis it’ll roll on the Y, and so on.
So here’s the code I’m trying to use. and below that is a dropbox link to the blend. As you can see i’m close. But the rotations all wrong. It looks like the quaternions misinterpreting things, and my brains to frazzled to unravel the issue. I would really appreciate it if some one could tell me where its all going wrong. :slight_smile:


import bpy
from mathutils import Vector 
from mathutils import Quaternion
from math import pi
# create variables
target = bpy.context.scene.objects['CTRLEmpty']
empty = bpy.context.scene.objects['offsetEmpty']
rotator = bpy.context.scene.objects['RotatorEmpty']
obj = bpy.data.objects['Sphere.001']
# create a custom function called delay on the empty
target['delay'] = 2
# create a custom function called delay on the empty
target['radius'] = 2


# create empty list
prev_locations = []


# define the function
def roller_function(scene):
    # add the location of the Target (variable) to the prev_locations list
    prev_locations.append( Vector(target.location) )
    rot = Quaternion((0,0,0,0))
    # create variable called loc_count that is equal to the amount of entries in prev_locations list.
    loc_count = len(prev_locations)
    # cull list to 10 items
    # whenever loc count is larger than the delay variable...
    while loc_count > target['delay']:
        # "pop" or remove the entrie at index position 0 (i.e.  the first entry)
        prev_locations.pop(0)
        # then subtract one from location count
        loc_count -= 1
    
    # create an average location vector and position vectors P0 is for previos frame position (or current frame of delayEmpty P1 is for current position
    loc_average = Vector()
    P0 = Vector()
    P1 = Vector()
    for loc in prev_locations:
        #  add together location average and loc
        loc_average += loc
    # divide loc_avrage by loc count to get the actual average location
    loc_average /= loc_count
    # place delayEmpty in the priorframe location
    empty.location = prev_locations[0]
    
    #the clever bit
    #create previous frame Position variable 
    P0 = loc_average
    #create current frame Position  variable 
    P1 = target.location
    #if no distance travelled rotation is the same as prior frame
    if(P0!=P1) :
        #find the difference in location between this frame and last
        dif = P1-P0
        #get the distance travelled
        dist = dif.length
        #set the radius from average dimension 
        r0 = (obj.dimensions[0] + obj.dimensions[1] + obj.dimensions[2])/3
        
        vec = dif/-dist
        zUp = Vector((0, 0, 1))
        rotAxis = vec.cross(zUp)
        angle = 360*dist/((r0)*(2*pi))
        rotDif = Quaternion(rotAxis,angle)
        rot += rotDif
        # print feedback in console
        print("locational difference is")           
        print(dif)
        print("distance travelled is")
        print(dist)   
        print("vec is")           
        print(vec)
        print("angle is")           
        print(angle)
        print("rotation is")           
        print(rot)
        print(loc_count)
        rotator.rotation_quaternion = rot
        obj.rotation_quaternion = rot        
    
    
    
# clear then add a function
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(roller_function)



https://dl.dropboxusercontent.com/u/35247393/troubleshooter.blend

Haven’t loaded your blend, but I think you multiply quaternions to apply a rotation, not add them


rotDif = Quaternion(rotAxis,angle)
 rot += rotDif

might instead be


rotDif = Quaternion(rotAxis,angle)
 rot *= rotDif

Hi Patmo, Thanks so much for taking a look, I tried your solution, But for some reason it results in a quaternion of 0 ,0 ,0 ,0.

I believe the order of the operations is backwards. You apply new translations to the left-hand side of the equation.

So you want to do:


rot = rotDiff * rot

Where as


rot *= rotDiff   # Is the same as "rot = rot * rotDiff"

The reason for this is that these rotation values are eventually multiplied against a Vector object that represents a vertex location. That is done like this:

finalPos = Quat * objectPos

What if you have 2 rotations applied. Say, “Rotate 45 degrees around the X-axis then rotate 5 degrees around the Y-axis”.
To make that happen, the quaternion math is the opposite of how you say it. You would like to write this because it matches the order of the rotations to how we think about them happening.


finalPos = x_rotation_quat * y_rotation_quat * obj_pos

But the math evaluates from left-to-right when we think right-to-left


# It is actually doing the Y-rotation first because that is how matrix/quat math is evaluated.
finalPos = x_rotation_quat * (y_rotation_quat * obj_pos)

So if you want to do “rotation 1, then rotation 2, then rotation 3” You math needs to be:


finalRotation = rotation3 * rotation2 * rotation1

Good call with the order of application. Forgot *= goes the other way!

Hi Kastoria, Thanks for the reply, sorry for the late response, I’ve been busy with family matters. I had a look at your suggestion, But couldn’t make it work, I have a long way to go understanding vectors I’m afraid. However thanks to a suggestion over on blenderstack I managed to get it to work.
I had to move the rot variable outside the function
change the
rot += rotdif to rot.rotate(rotDif)
and not to convert the radians to degrees.
so angle = dist/r0*2

I really appreciate you and Patmo’s help, hopefully this can help Anyone else who gets stuck with this.