using System; using System.Diagnostics; using System.Collections.Generic; namespace Oni.Totoro { internal static class AnimationDaeWriter { public static void AppendFrames(Animation anim1, Animation anim2) { var isOverlay = (anim2.Flags & AnimationFlags.Overlay) != 0; if (isOverlay) { Console.Error.WriteLine("Cannot merge {0} because it's an overlay animation", anim2.Name); return; } if (anim1.FrameSize == 0) { anim1.FrameSize = anim2.FrameSize; } else if (anim1.FrameSize != anim2.FrameSize) { Console.Error.WriteLine("Cannot merge {0} because its frame size doesn't match the frame size of the previous animation", anim2.Name); return; } anim1.Velocities.AddRange(anim2.Velocities); anim1.Heights.AddRange(anim2.Heights); if (anim1.Rotations.Count == 0) { anim1.Rotations.AddRange(anim2.Rotations); } else { for (int i = 0; i < anim1.Rotations.Count; i++) anim1.Rotations[i].AddRange(anim2.Rotations[i]); } } public static void Write(Dae.Node root, Animation animation, int startFrame = 0) { var velocities = animation.Velocities; var heights = animation.Heights; var rotations = animation.Rotations; var isCompressed = animation.FrameSize == 6; var isOverlay = (animation.Flags & AnimationFlags.Overlay) != 0; var isRealWorld = (animation.Flags & AnimationFlags.RealWorld) != 0; var activeBones = (uint)(animation.OverlayUsedBones | animation.OverlayReplacedBones); var nodes = FindNodes(root); if (!isOverlay && !isRealWorld) { var pelvis = nodes[0]; // // Write pelvis position animation // var positions = new Vector2[velocities.Count + 1]; for (int i = 1; i < positions.Length; i++) positions[i] = positions[i - 1] + velocities[i - 1]; CreateAnimationCurve(startFrame, positions.Select(p => p.X).ToList(), pelvis, "pos", "X"); CreateAnimationCurve(startFrame, positions.Select(p => p.Y).ToList(), pelvis, "pos", "Z"); // // Write pelvis height animation // CreateAnimationCurve(startFrame, heights.ToList(), pelvis, "pos", "Y"); } // // Write rotation animations for all bones // bool plot = true; for (int i = 0; i < rotations.Count; i++) { if (isOverlay && (activeBones & (1u << i)) == 0) continue; var node = nodes[i]; var keys = rotations[i]; int length; if (plot) length = keys.Sum(k => k.Duration); else length = keys.Count; var times = new float[length]; var xAngles = new float[length]; var yAngles = new float[length]; var zAngles = new float[length]; if (plot) { // // Transform key frames to quaternions. // var quats = new Quaternion[keys.Count]; for (int k = 0; k < keys.Count; k++) { var key = keys[k]; if (isCompressed) { quats[k] = Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.ToRadians(key.Rotation.X)) * Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathHelper.ToRadians(key.Rotation.Y)) * Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.ToRadians(key.Rotation.Z)); } else { quats[k] = new Quaternion(key.Rotation); } } // // Interpolate the quaternions. // int frame = 0; for (int k = 0; k < keys.Count; k++) { var duration = keys[k].Duration; var q1 = quats[k]; var q2 = (k == keys.Count - 1) ? quats[k] : quats[k + 1]; for (int t = 0; t < duration; t++) { var q = Quaternion.Lerp(q1, q2, (float)t / (float)duration); var euler = q.ToEulerXYZ(); times[frame] = (frame + startFrame) * (1.0f / 60.0f); xAngles[frame] = euler.X; yAngles[frame] = euler.Y; zAngles[frame] = euler.Z; frame++; } } MakeRotationCurveContinuous(xAngles); MakeRotationCurveContinuous(yAngles); MakeRotationCurveContinuous(zAngles); } else { int frame = 0; for (int k = 0; k < keys.Count; k++) { var key = keys[k]; times[k] = (frame + startFrame) * (1.0f / 60.0f); frame += key.Duration; if (isCompressed) { xAngles[k] = key.Rotation.X; yAngles[k] = key.Rotation.Y; zAngles[k] = key.Rotation.Z; } else { var euler = new Quaternion(key.Rotation).ToEulerXYZ(); xAngles[k] = euler.X; yAngles[k] = euler.Y; zAngles[k] = euler.Z; } } } CreateAnimationCurve(times, xAngles, node, "rotX", "ANGLE"); CreateAnimationCurve(times, yAngles, node, "rotY", "ANGLE"); CreateAnimationCurve(times, zAngles, node, "rotZ", "ANGLE"); } } private static void MakeRotationCurveContinuous(float[] curve) { for (int i = 1; i < curve.Length; i++) { float v1 = curve[i - 1]; float v2 = curve[i]; if (Math.Abs(v2 - v1) > 180.0f) { if (v2 > v1) v2 -= 360.0f; else v2 += 360.0f; curve[i] = v2; } } } private static void CreateAnimationCurve(int startFrame, IList values, Dae.Node targetNode, string targetSid, string targetValue) { if (values.Count == 0) return; var times = new float[values.Count]; for (int i = 0; i < times.Length; i++) times[i] = (i + startFrame) * (1.0f / 60.0f); CreateAnimationCurve(times, values, targetNode, targetSid, targetValue); } private static void CreateAnimationCurve(IList times, IList values, Dae.Node targetNode, string targetSid, string targetValue) { Debug.Assert(times.Count > 0); Debug.Assert(times.Count == values.Count); var interpolations = new string[times.Count]; for (int i = 0; i < interpolations.Length; i++) interpolations[i] = "LINEAR"; var targetTransform = targetNode.Transforms.Find(x => x.Sid == targetSid); targetTransform.BindAnimation(targetValue, new Dae.Sampler { Inputs = { new Dae.Input(Dae.Semantic.Input, new Dae.Source(times, 1)), new Dae.Input(Dae.Semantic.Output, new Dae.Source(values, 1)), new Dae.Input(Dae.Semantic.Interpolation, new Dae.Source(interpolations, 1)) } }); } private static List FindNodes(Dae.Node root) { var nodes = new List(19); FindNodesRecursive(root, nodes); return nodes; } private static void FindNodesRecursive(Dae.Node node, List result) { result.Add(node); foreach (var child in node.Nodes) FindNodesRecursive(child, result); } } }