Browse Source

Be smarter about animation subcontrols

Plasma assumes that any non-root level plCompoundController cannot have
any null subcontrollers. So, we now attempt to export leaf controllers
where possible, instead of compounds. This is hindered by the fact that
Blender loves bezier interpolation.
pull/10/head
Adam Johnson 10 years ago
parent
commit
0ceca75f0d
  1. 213
      korman/exporter/animation.py

213
korman/exporter/animation.py

@ -15,26 +15,17 @@
import bpy
import math
import mathutils
from PyHSPlasma import *
import weakref
from . import utils
class AnimationConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
self._bl_fps = bpy.context.scene.render.fps
def _check_scalar_subcontrollers(self, ctrl, default_xform):
"""Ensures that all scalar subcontrollers have at least one keyframe in the default state"""
for i in ("X", "Y", "Z"):
sub = getattr(ctrl, i)
if not sub.hasKeys():
keyframe = hsScalarKey()
keyframe.frame = 0
keyframe.frameTime = 0.0
keyframe.type = hsKeyFrame.kScalarKeyFrame
keyframe.value = getattr(default_xform, i.lower())
sub.keys = ([keyframe,], hsKeyFrame.kScalarKeyFrame)
def convert_action2tm(self, action, default_xform):
"""Converts a Blender Action to a plCompoundController."""
fcurves = action.fcurves
@ -49,70 +40,176 @@ class AnimationConverter:
tm.Z = self.make_scale_controller(fcurves, default_xform)
return tm
def _is_bezier_curve(self, keyframes):
for i in keyframes:
if i.interpolation == "BEZIER":
return True
return False
def make_pos_controller(self, fcurves, default_xform):
pos_curves = (i for i in fcurves if i.data_path == "location" and i.keyframe_points)
ctrl = self.make_scalar_controller(pos_curves)
if ctrl is not None and default_xform is not None:
self._check_scalar_subcontrollers(ctrl, default_xform.to_transpose())
keyframes, bez_chans = self._process_keyframes(pos_curves)
if not keyframes:
return None
# At one point, I had some... insanity here to try to crush bezier channels and hand off to
# blah blah blah... As it turns out, point3 keyframe's tangents are vector3s :)
ctrl = self._make_point3_controller(keyframes, bez_chans, default_xform.to_translation())
return ctrl
def make_rot_controller(self, fcurves, default_xform):
# TODO: support rotation_quaternion
rot_curves = (i for i in fcurves if i.data_path == "rotation_euler" and i.keyframe_points)
ctrl = self.make_scalar_controller(rot_curves)
if ctrl is not None and default_xform is not None:
self._check_scalar_subcontrollers(ctrl, default_xform.to_euler("XYZ"))
keyframes, bez_chans = self._process_keyframes(rot_curves)
if not keyframes:
return None
# Ugh. Unfortunately, it appears Blender's default interpolation is bezier. So who knows if
# many users will actually see the benefit here? Makes me sad.
if bez_chans:
ctrl = self._make_scalar_controller(keyframes, bez_chans, default_xform.to_euler("XYZ"))
else:
ctrl = self._make_quat_controller(keyframes, default_xform.to_euler("XYZ"))
return ctrl
def make_scale_controller(self, fcurves, default_xform):
# ... TODO ...
# who needs this anyway?
return None
def make_scalar_controller(self, fcurves):
ctrl = plCompoundController()
subctls = ("X", "Y", "Z")
def _make_point3_controller(self, keyframes, bezier, default_xform):
ctrl = plLeafController()
subctrls = ("X", "Y", "Z")
keyframe_type = hsKeyFrame.kBezPoint3KeyFrame if bezier else hsKeyFrame.kPoint3KeyFrame
exported_frames = []
last_xform = [default_xform[0], default_xform[1], default_xform[2]]
for keyframe in keyframes:
exported = hsPoint3Key()
exported.frame = keyframe.frame_num
exported.frameTime = keyframe.frame_time
exported.type = keyframe_type
in_tan = hsVector3()
out_tan = hsVector3()
value = hsVector3()
for i, subctrl in enumerate(subctrls):
fkey = keyframe.values.get(i, None)
if fkey is not None:
v = fkey.co[1]
last_xform[i] = v
setattr(value, subctrl, v)
setattr(in_tan, subctrl, keyframe.in_tans[i])
setattr(out_tan, subctrl, keyframe.out_tans[i])
else:
setattr(value, subctrl, last_xform[i])
setattr(in_tan, subctrl, 0.0)
setattr(out_tan, subctrl, 0.0)
exported.inTan = in_tan
exported.outTan = out_tan
exported.value = value
exported_frames.append(exported)
ctrl.keys = (exported_frames, keyframe_type)
return ctrl
# this ensures that all subcontrollers are populated -- otherwise KABLOOEY!
for i in subctls:
def _make_quat_controller(self, keyframes, default_xform):
ctrl = plLeafController()
keyframe_type = hsKeyFrame.kQuatKeyFrame
exported_frames = []
last_xform = [default_xform[0], default_xform[1], default_xform[2]]
for keyframe in keyframes:
exported = hsQuatKey()
exported.frame = keyframe.frame_num
exported.frameTime = keyframe.frame_time
exported.type = keyframe_type
# NOTE: quat keyframes don't do bezier nonsense
value = mathutils.Euler(last_xform, default_xform.order)
for i in range(3):
fkey = keyframe.values.get(i, None)
if fkey is not None:
v = fkey.co[1]
last_xform[i] = v
value[i] = v
quat = value.to_quaternion()
exported.value = utils.quaternion(quat)
exported_frames.append(exported)
ctrl.keys = (exported_frames, keyframe_type)
return ctrl
def _make_scalar_controller(self, keyframes, bez_chans, default_xform):
ctrl = plCompoundController()
subctrls = ("X", "Y", "Z")
for i in subctrls:
setattr(ctrl, i, plLeafController())
exported_frames = ([], [], [])
for keyframe in keyframes:
for i, subctrl in enumerate(subctrls):
fkey = keyframe.values.get(i, None)
if fkey is not None:
keyframe_type = hsKeyFrame.kBezScalarKeyFrame if i in bez_chans else hsKeyFrame.kScalarKeyFrame
exported = hsScalarKey()
exported.frame = keyframe.frame_num
exported.frameTime = keyframe.frame_time
exported.inTan = keyframe.in_tans[i]
exported.outTan = keyframe.out_tans[i]
exported.type = keyframe_type
exported.value = fkey.co[1]
exported_frames[i].append(exported)
for i, subctrl in enumerate(subctrls):
my_keyframes = exported_frames[i]
# ensure this controller has at least ONE keyframe
if not my_keyframes:
hack_frame = hsScalarKey()
hack_frame.frame = 0
hack_frame.frameTime = 0.0
hack_frame.type = hsKeyFrame.kScalarKeyFrame
hack_frame.value = default_xform[i]
my_keyframes.append(hack_frame)
getattr(ctrl, subctrl).keys = (my_keyframes, my_keyframes[0].type)
return ctrl
def _process_keyframes(self, fcurves):
"""Groups all FCurves for the same frame together"""
keyframe_data = type("KeyFrameData", (), {})
fps = self._bl_fps
pi = math.pi
keyframes = {}
bez_chans = set()
for fcurve in fcurves:
fcurve.update()
if self._is_bezier_curve(fcurve.keyframe_points):
key_type = hsKeyFrame.kScalarKeyFrame
else:
key_type = hsKeyFrame.kBezScalarKeyFrame
frames = []
pi = math.pi
fps = self._bl_fps
for i in fcurve.keyframe_points:
bl_frame_num, value = i.co
frame = hsScalarKey()
if i.interpolation == "BEZIER":
frame.inTan = -(value - i.handle_left[1]) / (bl_frame_num - i.handle_left[0]) / fps / (2 * pi)
frame.outTan = (value - i.handle_right[1]) / (bl_frame_num - i.handle_right[0]) / fps / (2 * pi)
for fkey in fcurve.keyframe_points:
frame_num, value = fkey.co
if fps == 30.0:
# hope you don't have a frame 29.9 and frame 30.0...
frame_num = int(frame_num)
else:
frame.inTan = 0.0
frame.outTan = 0.0
frame.type = key_type
frame.frame = int(bl_frame_num * (30.0 / fps))
frame.frameTime = bl_frame_num / fps
frame.value = value
frames.append(frame)
controller = plLeafController()
getattr(ctrl, subctls[fcurve.array_index]).keys = (frames, key_type)
# Compact this bamf
if not ctrl.X.hasKeys() and not ctrl.Y.hasKeys() and not ctrl.Z.hasKeys():
return None
else:
return ctrl
frame_num = int(frame_num * (30.0 / fps))
keyframe = keyframes.get(frame_num, None)
if keyframe is None:
keyframe = keyframe_data()
keyframe.frame_num = frame_num
keyframe.frame_time = frame_num / fps
keyframe.in_tans = {}
keyframe.out_tans = {}
keyframe.values = {}
keyframes[frame_num] = keyframe
idx = fcurve.array_index
keyframe.values[idx] = fkey
# Calculate the bezier interpolation nonsense
if fkey.interpolation == "BEZIER":
og_frame = fkey.co[0]
keyframe.in_tans[idx] = -(value - fkey.handle_left[1]) / (og_frame - fkey.handle_left[0]) / fps / (2 * pi)
keyframe.out_tans[idx] = (value - fkey.handle_right[1]) / (og_frame - fkey.handle_right[0]) / fps / (2 * pi)
else:
keyframe.in_tans[idx] = 0.0
keyframe.out_tans[idx] = 0.0
if keyframe.in_tans[idx] != 0.0 or keyframe.out_tans[idx] != 0.0:
bez_chans.add(idx)
# Return the keyframes in a sequence sorted by frame number
final_keyframes = [keyframes[i] for i in sorted(keyframes)]
return (final_keyframes, bez_chans)
@property
def _mgr(self):

Loading…
Cancel
Save