From 0ceca75f0daa8f503a678f2286f074c2ddc6b3c7 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sun, 5 Jul 2015 01:34:18 -0400 Subject: [PATCH] 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. --- korman/exporter/animation.py | 213 +++++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 58 deletions(-) diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index c14102f..5aae46f 100644 --- a/korman/exporter/animation.py +++ b/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):