diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index 7e961dc..33ee788 100644 --- a/korman/exporter/animation.py +++ b/korman/exporter/animation.py @@ -27,19 +27,103 @@ class AnimationConverter: self._exporter = weakref.ref(exporter) self._bl_fps = bpy.context.scene.render.fps - def convert_action2tm(self, action, default_xform): - """Converts a Blender Action to a plCompoundController.""" + def _convert_frame_time(self, frame_num): + return frame_num / self._bl_fps + + def convert_object_animations(self, bo, so): + anim = bo.animation_data + if anim is None: + return + action = anim.action + if action is None: + return fcurves = action.fcurves if not fcurves: + return + + # We're basically just going to throw all the FCurves at the controller converter (read: wall) + # and see what sticks. PlasmaMAX has some nice animation channel stuff that allows for some + # form of separation, but Blender's NLA editor is way confusing and appears to not work with + # things that aren't the typical position, rotation, scale animations. + applicators = [] + applicators.append(self._convert_transform_animation(bo.name, fcurves, bo.matrix_basis)) + + # Check to make sure we have some valid animation applicators before proceeding. + if not any(applicators): + return + + # There is a race condition in the client with animation loading. It expects for modifiers + # to be listed on the SceneObject in a specific order. D'OH! So, always use these funcs. + agmod, agmaster = self.get_anigraph_objects(bo, so) + atcanim = self._mgr.find_create_object(plATCAnim, so=so) + + # Add the animation data to the ATC + for i in applicators: + if i is not None: + atcanim.addApplicator(i) + agmod.channelName = bo.name + agmaster.addPrivateAnim(atcanim.key) + + # This was previously part of the Animation Modifier, however, there can be lots of animations + # Therefore we move it here. + markers = action.pose_markers + atcanim.name = "(Entire Animation)" + atcanim.start = self._convert_frame_time(action.frame_range[0]) + atcanim.end = self._convert_frame_time(action.frame_range[1]) + + # Marker points + for marker in markers: + atcanim.setMarker(marker.name, self._convert_frame_time(marker.frame)) + + # Fixme? Not sure if we really need to expose this... + atcanim.easeInMin = 1.0 + atcanim.easeInMax = 1.0 + atcanim.easeInLength = 1.0 + atcanim.easeOutMin = 1.0 + atcanim.easeOutMax = 1.0 + atcanim.easeOutLength = 1.0 + + def _convert_transform_animation(self, name, fcurves, xform): + pos = self.make_pos_controller(fcurves, xform) + rot = self.make_rot_controller(fcurves, xform) + scale = self.make_scale_controller(fcurves, xform) + if pos is None and rot is None and scale is None: return None - - # NOTE: plCompoundController is from Myst 5 and was backported to MOUL. - # Worry not however... libHSPlasma will do the conversion for us. tm = plCompoundController() - tm.X = self.make_pos_controller(fcurves, default_xform) - tm.Y = self.make_rot_controller(fcurves, default_xform) - tm.Z = self.make_scale_controller(fcurves, default_xform) - return tm + tm.X = pos + tm.Y = rot + tm.Z = scale + + applicator = plMatrixChannelApplicator() + applicator.enabled = True + applicator.channelName = name + channel = plMatrixControllerChannel() + channel.controller = tm + applicator.channel = channel + + # Decompose the matrix into the 90s-era 3ds max affine parts sillyness + # All that's missing now is something like "(c) 1998 HeadSpin" oh wait... + affine = hsAffineParts() + affine.T = hsVector3(*xform.to_translation()) + affine.K = hsVector3(*xform.to_scale()) + affine.F = -1.0 if xform.determinant() < 0.0 else 1.0 + rot = xform.to_quaternion() + affine.Q = utils.quaternion(rot) + rot.normalize() + affine.U = utils.quaternion(rot) + channel.affine = affine + + return applicator + + def get_anigraph_keys(self, bo=None, so=None): + mod = self._mgr.find_create_key(plAGModifier, so=so, bl=bo) + master = self._mgr.find_create_key(plAGMasterMod, so=so, bl=bo) + return mod, master + + def get_anigraph_objects(self, bo=None, so=None): + mod = self._mgr.find_create_object(plAGModifier, so=so, bl=bo) + master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo) + return mod, master def make_matrix44_controller(self, pos_fcurves, scale_fcurves, default_pos, default_scale): pos_keyframes, pos_bez = self._process_keyframes(pos_fcurves) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 7881992..4037088 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -207,6 +207,7 @@ class Exporter: sceneobject = self.mgr.find_create_object(plSceneObject, bl=bl_obj) self._export_actor(sceneobject, bl_obj) export_fn(sceneobject, bl_obj) + self.animation.convert_object_animations(bl_obj, sceneobject) # And now we puke out the modifiers... for mod in bl_obj.plasma_modifiers.modifiers: @@ -256,6 +257,8 @@ class Exporter: return True if bo.parent is not None: return True + if bo.animation_data is not None and bo.animation_data.action is not None: + return True if bo.name in self.actors: return True diff --git a/korman/nodes/node_messages.py b/korman/nodes/node_messages.py index e500637..6ea5be7 100644 --- a/korman/nodes/node_messages.py +++ b/korman/nodes/node_messages.py @@ -178,9 +178,7 @@ class PlasmaAnimCmdMsgNode(PlasmaMessageNode, bpy.types.Node): # (but obviously this is not wrong...) target = exporter.mgr.find_create_key(plMsgForwarder, bl=obj, name=group.key_name) else: - # remember, the AGModifier MUST exist first... so just in case... - exporter.mgr.find_create_key(plAGModifier, bl=obj, name=anim.key_name) - target = exporter.mgr.find_create_key(plAGMasterMod, bl=obj, name=anim.key_name) + _agmod_trash, target = exporter.animation.get_anigraph_keys(obj) else: material = bpy.data.materials.get(self.material_name, None) if material is None: diff --git a/korman/properties/modifiers/anim.py b/korman/properties/modifiers/anim.py index 77be132..e14dece 100644 --- a/korman/properties/modifiers/anim.py +++ b/korman/properties/modifiers/anim.py @@ -61,12 +61,9 @@ class PlasmaAnimationModifier(PlasmaModifierProperties): action = _get_blender_action(bo) markers = action.pose_markers - atcanim = exporter.mgr.find_create_object(plATCAnim, so=so, name=self.key_name) + atcanim = exporter.mgr.find_create_object(plATCAnim, so=so) atcanim.autoStart = self.auto_start atcanim.loop = self.loop - atcanim.name = "(Entire Animation)" - atcanim.start = _convert_frame_time(action.frame_range[0]) - atcanim.end = _convert_frame_time(action.frame_range[1]) # Simple start and loop info initial_marker = markers.get(self.initial_marker) @@ -86,51 +83,6 @@ class PlasmaAnimationModifier(PlasmaModifierProperties): else: atcanim.loopEnd = _convert_frame_time(action.frame_range[1]) - # Marker points - for marker in markers: - atcanim.setMarker(marker.name, _convert_frame_time(marker.frame)) - - # Fixme? Not sure if we really need to expose this... - atcanim.easeInMin = 1.0 - atcanim.easeInMax = 1.0 - atcanim.easeInLength = 1.0 - atcanim.easeOutMin = 1.0 - atcanim.easeOutMax = 1.0 - atcanim.easeOutLength = 1.0 - - # Now for the animation data. We're mostly just going to hand this off to the controller code - matrix = bo.matrix_basis - applicator = plMatrixChannelApplicator() - applicator.enabled = True - applicator.channelName = bo.name - channel = plMatrixControllerChannel() - channel.controller = exporter.animation.convert_action2tm(action, matrix) - applicator.channel = channel - atcanim.addApplicator(applicator) - - # Decompose the matrix into the 90s-era 3ds max affine parts sillyness - # All that's missing now is something like "(c) 1998 HeadSpin" oh wait... - affine = hsAffineParts() - affine.T = hsVector3(*matrix.to_translation()) - affine.K = hsVector3(*matrix.to_scale()) - affine.F = -1.0 if matrix.determinant() < 0.0 else 1.0 - rot = matrix.to_quaternion() - affine.Q = utils.quaternion(rot) - rot.normalize() - affine.U = utils.quaternion(rot) - channel.affine = affine - - # We need both an AGModifier and an AGMasterMod - # NOTE: mandatory order--otherwise the animation will not work in game! - agmod = exporter.mgr.find_create_object(plAGModifier, so=so, name=self.key_name) - agmod.channelName = bo.name - agmaster = exporter.mgr.find_create_object(plAGMasterMod, so=so, name=self.key_name) - agmaster.addPrivateAnim(atcanim.key) - - @property - def key_name(self): - return "{}_(Entire Animation)".format(self.id_data.name) - def _make_physical_movable(self, so): sim = so.sim if sim is not None: @@ -179,14 +131,11 @@ class PlasmaAnimationGroupModifier(PlasmaModifierProperties): action = _get_blender_action(bo) key_name = bo.plasma_modifiers.animation.key_name - # See above... AGModifier must always be inited first... - agmod = exporter.mgr.find_create_object(plAGModifier, so=so, name=key_name) - # The message forwarder is the guy that makes sure that everybody knows WTF is going on msgfwd = exporter.mgr.find_create_object(plMsgForwarder, so=so, name=self.key_name) # Now, this is da swhiz... - agmaster = exporter.mgr.find_create_object(plAGMasterMod, so=so, name=key_name) + agmod, agmaster = exporter.animation.get_anigraph_objects(bo, so) agmaster.msgForwarder = msgfwd.key agmaster.isGrouped, agmaster.isGroupMaster = True, True for i in self.children: @@ -204,9 +153,8 @@ class PlasmaAnimationGroupModifier(PlasmaModifierProperties): msg = "Animation Group '{}' specifies an object '{}' with no Plasma Animation modifier. Ignoring..." exporter.report.warn(msg.format(self.key_name, i.object_name), indent=2) continue - child_agmod = exporter.mgr.find_create_key(plAGModifier, bl=child_bo, name=child_animation.key_name) - child_agmaster = exporter.mgr.find_create_key(plAGMasterMod, bl=child_bo, name=child_animation.key_name) - msgfwd.addForwardKey(child_agmaster) + child_agmod, child_agmaster = exporter.animation.get_anigraph_objects(bo=child_bo) + msgfwd.addForwardKey(child_agmaster.key) msgfwd.addForwardKey(agmaster.key) @property @@ -241,8 +189,7 @@ class PlasmaAnimationLoopModifier(PlasmaModifierProperties): action = _get_blender_action(bo) markers = action.pose_markers - key_name = bo.plasma_modifiers.animation.key_name - atcanim = exporter.mgr.find_create_object(plATCAnim, so=so, name=key_name) + atcanim = exporter.mgr.find_create_object(plATCAnim, so=so) for loop in self.loops: start = markers.get(loop.loop_start) end = markers.get(loop.loop_end)