[1114] | 1 | using System;
|
---|
| 2 | using System.Diagnostics;
|
---|
| 3 | using System.Collections.Generic;
|
---|
| 4 |
|
---|
| 5 | namespace Oni.Totoro
|
---|
| 6 | {
|
---|
| 7 | internal static class AnimationDaeWriter
|
---|
| 8 | {
|
---|
| 9 | public static void AppendFrames(Animation anim1, Animation anim2)
|
---|
| 10 | {
|
---|
| 11 | var isOverlay = (anim2.Flags & AnimationFlags.Overlay) != 0;
|
---|
| 12 |
|
---|
| 13 | if (isOverlay)
|
---|
| 14 | {
|
---|
| 15 | Console.Error.WriteLine("Cannot merge {0} because it's an overlay animation", anim2.Name);
|
---|
| 16 | return;
|
---|
| 17 | }
|
---|
| 18 |
|
---|
| 19 | if (anim1.FrameSize == 0)
|
---|
| 20 | {
|
---|
| 21 | anim1.FrameSize = anim2.FrameSize;
|
---|
| 22 | }
|
---|
| 23 | else if (anim1.FrameSize != anim2.FrameSize)
|
---|
| 24 | {
|
---|
| 25 | Console.Error.WriteLine("Cannot merge {0} because its frame size doesn't match the frame size of the previous animation", anim2.Name);
|
---|
| 26 | return;
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 29 | anim1.Velocities.AddRange(anim2.Velocities);
|
---|
| 30 | anim1.Heights.AddRange(anim2.Heights);
|
---|
| 31 |
|
---|
| 32 | if (anim1.Rotations.Count == 0)
|
---|
| 33 | {
|
---|
| 34 | anim1.Rotations.AddRange(anim2.Rotations);
|
---|
| 35 | }
|
---|
| 36 | else
|
---|
| 37 | {
|
---|
| 38 | for (int i = 0; i < anim1.Rotations.Count; i++)
|
---|
| 39 | anim1.Rotations[i].AddRange(anim2.Rotations[i]);
|
---|
| 40 | }
|
---|
| 41 | }
|
---|
| 42 |
|
---|
| 43 | public static void Write(Dae.Node root, Animation animation, int startFrame = 0)
|
---|
| 44 | {
|
---|
| 45 | var velocities = animation.Velocities;
|
---|
| 46 | var heights = animation.Heights;
|
---|
| 47 | var rotations = animation.Rotations;
|
---|
| 48 | var isCompressed = animation.FrameSize == 6;
|
---|
| 49 | var isOverlay = (animation.Flags & AnimationFlags.Overlay) != 0;
|
---|
| 50 | var isRealWorld = (animation.Flags & AnimationFlags.RealWorld) != 0;
|
---|
| 51 | var activeBones = (uint)(animation.OverlayUsedBones | animation.OverlayReplacedBones);
|
---|
| 52 |
|
---|
| 53 | var nodes = FindNodes(root);
|
---|
| 54 |
|
---|
| 55 | if (!isOverlay && !isRealWorld)
|
---|
| 56 | {
|
---|
| 57 | var pelvis = nodes[0];
|
---|
| 58 |
|
---|
| 59 | //
|
---|
| 60 | // Write pelvis position animation
|
---|
| 61 | //
|
---|
| 62 |
|
---|
| 63 | var positions = new Vector2[velocities.Count + 1];
|
---|
| 64 |
|
---|
| 65 | for (int i = 1; i < positions.Length; i++)
|
---|
| 66 | positions[i] = positions[i - 1] + velocities[i - 1];
|
---|
| 67 |
|
---|
| 68 | CreateAnimationCurve(startFrame, positions.Select(p => p.X).ToList(), pelvis, "pos", "X");
|
---|
| 69 | CreateAnimationCurve(startFrame, positions.Select(p => p.Y).ToList(), pelvis, "pos", "Z");
|
---|
| 70 |
|
---|
| 71 | //
|
---|
| 72 | // Write pelvis height animation
|
---|
| 73 | //
|
---|
| 74 |
|
---|
| 75 | CreateAnimationCurve(startFrame, heights.ToList(), pelvis, "pos", "Y");
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | //
|
---|
| 79 | // Write rotation animations for all bones
|
---|
| 80 | //
|
---|
| 81 |
|
---|
| 82 | bool plot = true;
|
---|
| 83 |
|
---|
| 84 | for (int i = 0; i < rotations.Count; i++)
|
---|
| 85 | {
|
---|
| 86 | if (isOverlay && (activeBones & (1u << i)) == 0)
|
---|
| 87 | continue;
|
---|
| 88 |
|
---|
| 89 | var node = nodes[i];
|
---|
| 90 | var keys = rotations[i];
|
---|
| 91 |
|
---|
| 92 | int length;
|
---|
| 93 |
|
---|
| 94 | if (plot)
|
---|
| 95 | length = keys.Sum(k => k.Duration);
|
---|
| 96 | else
|
---|
| 97 | length = keys.Count;
|
---|
| 98 |
|
---|
| 99 | var times = new float[length];
|
---|
| 100 | var xAngles = new float[length];
|
---|
| 101 | var yAngles = new float[length];
|
---|
| 102 | var zAngles = new float[length];
|
---|
| 103 |
|
---|
| 104 | if (plot)
|
---|
| 105 | {
|
---|
| 106 | //
|
---|
| 107 | // Transform key frames to quaternions.
|
---|
| 108 | //
|
---|
| 109 |
|
---|
| 110 | var quats = new Quaternion[keys.Count];
|
---|
| 111 |
|
---|
| 112 | for (int k = 0; k < keys.Count; k++)
|
---|
| 113 | {
|
---|
| 114 | var key = keys[k];
|
---|
| 115 |
|
---|
| 116 | if (isCompressed)
|
---|
| 117 | {
|
---|
| 118 | quats[k] = Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.ToRadians(key.Rotation.X))
|
---|
| 119 | * Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathHelper.ToRadians(key.Rotation.Y))
|
---|
| 120 | * Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.ToRadians(key.Rotation.Z));
|
---|
| 121 | }
|
---|
| 122 | else
|
---|
| 123 | {
|
---|
| 124 | quats[k] = new Quaternion(key.Rotation);
|
---|
| 125 | }
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | //
|
---|
| 129 | // Interpolate the quaternions.
|
---|
| 130 | //
|
---|
| 131 |
|
---|
| 132 | int frame = 0;
|
---|
| 133 |
|
---|
| 134 | for (int k = 0; k < keys.Count; k++)
|
---|
| 135 | {
|
---|
| 136 | var duration = keys[k].Duration;
|
---|
| 137 |
|
---|
| 138 | var q1 = quats[k];
|
---|
| 139 | var q2 = (k == keys.Count - 1) ? quats[k] : quats[k + 1];
|
---|
| 140 |
|
---|
| 141 | for (int t = 0; t < duration; t++)
|
---|
| 142 | {
|
---|
| 143 | var q = Quaternion.Lerp(q1, q2, (float)t / (float)duration);
|
---|
| 144 | var euler = q.ToEulerXYZ();
|
---|
| 145 |
|
---|
| 146 | times[frame] = (frame + startFrame) * (1.0f / 60.0f);
|
---|
| 147 |
|
---|
| 148 | xAngles[frame] = euler.X;
|
---|
| 149 | yAngles[frame] = euler.Y;
|
---|
| 150 | zAngles[frame] = euler.Z;
|
---|
| 151 |
|
---|
| 152 | frame++;
|
---|
| 153 | }
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | MakeRotationCurveContinuous(xAngles);
|
---|
| 157 | MakeRotationCurveContinuous(yAngles);
|
---|
| 158 | MakeRotationCurveContinuous(zAngles);
|
---|
| 159 | }
|
---|
| 160 | else
|
---|
| 161 | {
|
---|
| 162 | int frame = 0;
|
---|
| 163 |
|
---|
| 164 | for (int k = 0; k < keys.Count; k++)
|
---|
| 165 | {
|
---|
| 166 | var key = keys[k];
|
---|
| 167 |
|
---|
| 168 | times[k] = (frame + startFrame) * (1.0f / 60.0f);
|
---|
| 169 | frame += key.Duration;
|
---|
| 170 |
|
---|
| 171 | if (isCompressed)
|
---|
| 172 | {
|
---|
| 173 | xAngles[k] = key.Rotation.X;
|
---|
| 174 | yAngles[k] = key.Rotation.Y;
|
---|
| 175 | zAngles[k] = key.Rotation.Z;
|
---|
| 176 | }
|
---|
| 177 | else
|
---|
| 178 | {
|
---|
| 179 | var euler = new Quaternion(key.Rotation).ToEulerXYZ();
|
---|
| 180 |
|
---|
| 181 | xAngles[k] = euler.X;
|
---|
| 182 | yAngles[k] = euler.Y;
|
---|
| 183 | zAngles[k] = euler.Z;
|
---|
| 184 | }
|
---|
| 185 | }
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | CreateAnimationCurve(times, xAngles, node, "rotX", "ANGLE");
|
---|
| 189 | CreateAnimationCurve(times, yAngles, node, "rotY", "ANGLE");
|
---|
| 190 | CreateAnimationCurve(times, zAngles, node, "rotZ", "ANGLE");
|
---|
| 191 | }
|
---|
| 192 | }
|
---|
| 193 |
|
---|
| 194 | private static void MakeRotationCurveContinuous(float[] curve)
|
---|
| 195 | {
|
---|
| 196 | for (int i = 1; i < curve.Length; i++)
|
---|
| 197 | {
|
---|
| 198 | float v1 = curve[i - 1];
|
---|
| 199 | float v2 = curve[i];
|
---|
| 200 |
|
---|
| 201 | if (Math.Abs(v2 - v1) > 180.0f)
|
---|
| 202 | {
|
---|
| 203 | if (v2 > v1)
|
---|
| 204 | v2 -= 360.0f;
|
---|
| 205 | else
|
---|
| 206 | v2 += 360.0f;
|
---|
| 207 |
|
---|
| 208 | curve[i] = v2;
|
---|
| 209 | }
|
---|
| 210 | }
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | private static void CreateAnimationCurve(int startFrame, IList<float> values, Dae.Node targetNode, string targetSid, string targetValue)
|
---|
| 214 | {
|
---|
| 215 | if (values.Count == 0)
|
---|
| 216 | return;
|
---|
| 217 |
|
---|
| 218 | var times = new float[values.Count];
|
---|
| 219 |
|
---|
| 220 | for (int i = 0; i < times.Length; i++)
|
---|
| 221 | times[i] = (i + startFrame) * (1.0f / 60.0f);
|
---|
| 222 |
|
---|
| 223 | CreateAnimationCurve(times, values, targetNode, targetSid, targetValue);
|
---|
| 224 | }
|
---|
| 225 |
|
---|
| 226 | private static void CreateAnimationCurve(IList<float> times, IList<float> values, Dae.Node targetNode, string targetSid, string targetValue)
|
---|
| 227 | {
|
---|
| 228 | Debug.Assert(times.Count > 0);
|
---|
| 229 | Debug.Assert(times.Count == values.Count);
|
---|
| 230 |
|
---|
| 231 | var interpolations = new string[times.Count];
|
---|
| 232 |
|
---|
| 233 | for (int i = 0; i < interpolations.Length; i++)
|
---|
| 234 | interpolations[i] = "LINEAR";
|
---|
| 235 |
|
---|
| 236 | var targetTransform = targetNode.Transforms.Find(x => x.Sid == targetSid);
|
---|
| 237 |
|
---|
| 238 | targetTransform.BindAnimation(targetValue, new Dae.Sampler {
|
---|
| 239 | Inputs = {
|
---|
| 240 | new Dae.Input(Dae.Semantic.Input, new Dae.Source(times, 1)),
|
---|
| 241 | new Dae.Input(Dae.Semantic.Output, new Dae.Source(values, 1)),
|
---|
| 242 | new Dae.Input(Dae.Semantic.Interpolation, new Dae.Source(interpolations, 1))
|
---|
| 243 | }
|
---|
| 244 | });
|
---|
| 245 | }
|
---|
| 246 |
|
---|
| 247 | private static List<Dae.Node> FindNodes(Dae.Node root)
|
---|
| 248 | {
|
---|
| 249 | var nodes = new List<Dae.Node>(19);
|
---|
| 250 | FindNodesRecursive(root, nodes);
|
---|
| 251 | return nodes;
|
---|
| 252 | }
|
---|
| 253 |
|
---|
| 254 | private static void FindNodesRecursive(Dae.Node node, List<Dae.Node> result)
|
---|
| 255 | {
|
---|
| 256 | result.Add(node);
|
---|
| 257 |
|
---|
| 258 | foreach (var child in node.Nodes)
|
---|
| 259 | FindNodesRecursive(child, result);
|
---|
| 260 | }
|
---|
| 261 | }
|
---|
| 262 | }
|
---|