animating curve bevel
__author__ = 'thoth' # # demonstrate the "instability" of matrix decomposition into quaternions. # """ http://blender.stackexchange.com/questions/28438/how-do-i-ensure-a-sequence-of-quaternions-from-matrix-decompose-is-continuous It seems that blender's Matrix decompose() function can exhibit some numerical "instability". What I mean is that orientation matrices that are relatively close can have significantly different elements in their quaternion matrix that makes interpolated keyframing unusable. The following simple python script will calculate a sequence of matrices representing rotations around the Z axis in a smooth manner. It then decomposes the matrix into a quaternion, and inserts that as a keyframe. When you view the resulting fcurves in blender you can see that the Z value jumps from -1 to 1 about halfway through the animation. Is there a technique for blender's python API to get a sequence of quaternions that can be keyframed and interpolated without discontinuity? For bonus points: link to an article that explains this mathematical oddity and why decomposition works this way. This technique should be usable with arbitrary orientation matrices, because they are calculated from something a little more complex than this simple Z rotation (in my specific case I'm flying along a bezier curve). """ import bpy from math import * from mathutils import * def matrix_for_time(t): theta = 2 * pi * t mat = Matrix([[cos(theta), sin(theta), 0], [-sin(theta), cos(theta), 0], [0, 0, 1]]).to_4x4() # for illustration we use rotation about Z axis, # but in the arbitrary case, the orientation could be for a pine cone bouncing down a hill. return mat def mission(obj): res = 36 qs = QuaternionStabilizer() for z in range(res): mat = matrix_for_time(z/res) (loc,quat,scale) = mat.decompose() obj.rotation_quaternion = qs.stabilize(quat) for ai in range(len(quat)): obj.keyframe_insert(frame=z*5, data_path="rotation_quaternion", index=ai) class QuaternionStabilizer: def __init__(self): self.old=None def stabilize(self, q): if self.old is None: rval = q else: # compute the distance between old and q d1 = (self.old-q).magnitude # compute the distance between old and -q d2 = (self.old+q).magnitude if (d1<d2): # q is closer to old rval = q else: # -q is closer to old rval = -q self.old = rval return rval mission(bpy.context.active_object)
Blender python API quick-start
Syntax highlighting by Pygments.