Browse Source

Support nonlocal animations

pull/290/head
Jrius 3 years ago
parent
commit
518312cff8
  1. 70
      korman/exporter/animation.py

70
korman/exporter/animation.py

@ -58,7 +58,7 @@ class AnimationConverter:
if isinstance(bo.data, bpy.types.Camera): if isinstance(bo.data, bpy.types.Camera):
applicators.append(self._convert_camera_animation(bo, so, obj_fcurves, data_fcurves, anim_name, start, end)) applicators.append(self._convert_camera_animation(bo, so, obj_fcurves, data_fcurves, anim_name, start, end))
else: else:
applicators.append(self._convert_transform_animation(bo, obj_fcurves, bo.matrix_basis, start=start, end=end)) applicators.append(self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse, start=start, end=end))
if bo.plasma_modifiers.soundemit.enabled: if bo.plasma_modifiers.soundemit.enabled:
applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit, start, end)) applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit, start, end))
if isinstance(bo.data, bpy.types.Lamp): if isinstance(bo.data, bpy.types.Lamp):
@ -143,7 +143,7 @@ class AnimationConverter:
# If we exported any FOV animation at all, then we need to ensure there is an applicator # If we exported any FOV animation at all, then we need to ensure there is an applicator
# returned from here... At bare minimum, we'll need the applicator with an empty # returned from here... At bare minimum, we'll need the applicator with an empty
# CompoundController. This should be sufficient to keep CWE from crashing... # CompoundController. This should be sufficient to keep CWE from crashing...
applicator = self._convert_transform_animation(bo, obj_fcurves, bo.matrix_basis, applicator = self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse,
allow_empty=has_fov_anim, start=start, end=end) allow_empty=has_fov_anim, start=start, end=end)
camera = self._mgr.find_create_object(plCameraModifier, so=so) camera = self._mgr.find_create_object(plCameraModifier, so=so)
camera.animated = applicator is not None camera.animated = applicator is not None
@ -338,9 +338,16 @@ class AnimationConverter:
applicator.channel = channel applicator.channel = channel
yield applicator yield applicator
def _convert_transform_animation(self, bo, fcurves, xform, *, allow_empty: Optional[bool] = False, def _convert_transform_animation(self, bo, fcurves, default_xform, adjust_xform, *, allow_empty: Optional[bool] = False,
start: Optional[int] = None, end: Optional[int] = None) -> Optional[plMatrixChannelApplicator]: start: Optional[int] = None, end: Optional[int] = None) -> Optional[plMatrixChannelApplicator]:
tm = self.convert_transform_controller(fcurves, bo.rotation_mode, xform, allow_empty=allow_empty, if adjust_xform != mathutils.Matrix.Identity(4):
self._exporter().report.warn(("{}: Transform animation is not local and may export incorrectly. " +
"Please use Ctrl-P -> Clear Parent Inverse before animating objects to avoid issues.").format(bo.name), indent=1)
else:
# Adjustment matrix is identity, just pass None instead...
adjust_xform = None
tm = self.convert_transform_controller(fcurves, bo.rotation_mode, default_xform, adjust_xform, allow_empty=allow_empty,
start=start, end=end) start=start, end=end)
if tm is None and not allow_empty: if tm is None and not allow_empty:
return None return None
@ -351,20 +358,62 @@ class AnimationConverter:
channel = plMatrixControllerChannel() channel = plMatrixControllerChannel()
channel.controller = tm channel.controller = tm
applicator.channel = channel applicator.channel = channel
channel.affine = utils.affine_parts(xform) channel.affine = utils.affine_parts(default_xform)
return applicator return applicator
def convert_transform_controller(self, fcurves, rotation_mode: str, xform, *, def convert_transform_controller(self, fcurves, rotation_mode: str, default_xform, adjust_xform, *,
allow_empty: Optional[bool] = False, allow_empty: Optional[bool] = False,
start: Optional[int] = None, start: Optional[int] = None,
end: Optional[int] = None) -> Union[None, plCompoundController]: end: Optional[int] = None) -> Union[None, plCompoundController]:
if not fcurves and not allow_empty: if not fcurves and not allow_empty:
return None return None
pos = self.make_pos_controller(fcurves, "location", xform.to_translation(), start=start, end=end) if adjust_xform is not None:
rot = self.make_rot_controller(fcurves, rotation_mode, xform, start=start, end=end) # We have to edit the keyframes to make the anim local..
scale = self.make_scale_controller(fcurves, "scale", xform.to_scale(), start=start, end=end) # In many cases this should work fine, but sometimes scale and rotation might
# still cause issues. Also, euler angles need to be converted to quaternion
# and back to eulers, which could cause issues. Not much we can do about it.
adjust_rotation = adjust_xform.to_quaternion()
adjust_scale = adjust_xform.to_scale()
# Helpers to adjust keyframes in case animation is not local (adjust_xform == identity)
def convert_pos_keyframe(pos):
# Position: can transform to local space without issues.
return tuple(adjust_xform * mathutils.Vector(pos))
def convert_rot_keyframe(rot):
# Rotation: may cause issues if scale is present.
if isinstance(rot, mathutils.Quaternion): # quaternion from an axis-angle
return adjust_rotation * rot
elif isinstance(rot, mathutils.Euler):
return (adjust_rotation * rot.to_quaternion()).to_euler(rot.order)
else: # tuple
if len(rot) == 4: # quat in a tuple
return (adjust_rotation * mathutils.Quaternion(rot))[:]
else: # XYZ euler in a tuple
rot = mathutils.Euler(rot, "XYZ").to_quaternion()
return (adjust_rotation * rot).to_euler("XYZ")[:]
def convert_scale_keyframe(scale):
# Scale: very likely to cause issues.
return (a * b for a, b in zip(adjust_scale, scale))
convert_pos = convert_pos_keyframe
convert_rot = convert_rot_keyframe
convert_scale = convert_scale_keyframe
else:
# Don't change the keyframes at all, so we don't risk screwing them up.
convert_pos = None
convert_rot = None
convert_scale = None
pos = self.make_pos_controller(fcurves, "location", default_xform.to_translation(),
convert=convert_pos, start=start, end=end)
rot = self.make_rot_controller(fcurves, rotation_mode, default_xform,
convert=convert_rot, start=start, end=end)
scale = self.make_scale_controller(fcurves, "scale", default_xform.to_scale(),
convert=convert_scale, start=start, end=end)
if pos is None and rot is None and scale is None: if pos is None and rot is None and scale is None:
if not allow_empty: if not allow_empty:
return None return None
@ -490,7 +539,8 @@ class AnimationConverter:
default_xform = (default_xform[1], default_xform[0].x, default_xform[0].y, default_xform[0].z) default_xform = (default_xform[1], default_xform[0].x, default_xform[0].y, default_xform[0].z)
if convert is not None: if convert is not None:
convert = lambda x: convert(mathutils.Quaternion(x[1:4], x[0]))[:] convert_original = convert
convert = lambda x: convert_original(mathutils.Quaternion(x[1:4], x[0]))[:]
else: else:
convert = lambda x: mathutils.Quaternion(x[1:4], x[0])[:] convert = lambda x: mathutils.Quaternion(x[1:4], x[0])[:]

Loading…
Cancel
Save