0
$\begingroup$

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.

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

$\endgroup$
  • 1
    $\begingroup$ What do you mean by "get an object to roll in whatever direction its travelling"? It seems like you are going to rotate object in its yaw or pitch.I just skim your source code and see the definition of zUp, which will be crossed with vec to get your rotation axis. I advise that if you want a rotation about object's local axis, you should not use a global vector like (0, 0, 1). $\endgroup$ – TheBusyTypist Mar 9 '15 at 2:13
  • $\begingroup$ Hi thanks for your reply, If you get a mo, take a look at the linked blend file. The sphere has keyframed movement along the x axis (and later diagonally), the code starts it rolling like a ball however part way through the x axis move it starts to rotate in the reverse direction, like the w value of the quaternions gone wrong. Also If I shouldn't be using a global vector, can you recommend what I should be using? $\endgroup$ – Unitedfilmdom Mar 9 '15 at 11:05
  • 1
    $\begingroup$ It is ok to use (0, 0, 1) as your up vector if you are moving in XY plane. I found other potential problems: (a) If you want to accumulate rotations in every frame, you should use Quaternion((1, 0, 0, 0)) as your identity and use rot.rotate(rotDif) to accumulate rotation. (b) I noticed you compute angle use angle=360*dist/((r0)*(2*pi)). It seems very like a conversion from radians to degrees. But Quaternion accepts the rotation angle in radians. I am not quite sure of your intention. (c) You may put rot out of your handler function, or it will be reset to initial value every frame. $\endgroup$ – TheBusyTypist Mar 11 '15 at 2:19
  • $\begingroup$ I think its fair to say your my hero ! :) It still needs a little finesse but its finally rolling straight, thanks to your suggestions. $\endgroup$ – Unitedfilmdom Mar 13 '15 at 16:19
  • $\begingroup$ How do I select your answer as "the" answer? :) $\endgroup$ – Unitedfilmdom Mar 13 '15 at 17:55
1
$\begingroup$

So thanks to TheBusyTypist I was able to fix my code to work...

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']
rot = Quaternion((1,0,0,0))
# 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) )

    # 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
        angle=dist/r0*2
        vec = dif/-dist
        zUp = Vector((0, 0, 1))
        rotAxis = vec.cross(zUp)
        rotDif = Quaternion(rotAxis,angle)
        rot.rotate(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.scene_update_pre.clear()
bpy.app.handlers.scene_update_pre.append(roller_function)
#bpy.app.handlers.frame_change_pre.clear()
#bpy.app.handlers.frame_change_pre.append(roller_function)

The last 4 lines are interchangeable, the frame change set is more stable the scene_update allows you to see changes as you drag.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.