static AnimationClip ReadClip(BinaryReader reader) { AnimationClip clip = new AnimationClip(); clip.Name = reader.ReadString(); clip.Duration = reader.ReadDouble(); int bonesCount = reader.ReadInt32(); while (bonesCount-- > 0) { AnimationClip.Bone bone = new AnimationClip.Bone(); bone.Name = reader.ReadString(); int keyframesCount = reader.ReadInt32(); while (keyframesCount-- > 0) { AnimationClip.Keyframe keyframe = new AnimationClip.Keyframe(); keyframe.Time = reader.ReadDouble(); keyframe.Rotation = ImportQuaternion(reader); keyframe.Translation = ImportVector3(reader); bone.Keyframes.Add(keyframe); } clip.Bones.Add(bone); } return(clip); }
/// <summary> /// This function filters out keyframes that can be approximated well with /// linear interpolation. /// </summary> /// <param name="keyframes"></param> private void LinearKeyframeReduction(LinkedList <AnimationClip.Keyframe> keyframes) { if (keyframes.Count < 3) { return; } for (LinkedListNode <AnimationClip.Keyframe> node = keyframes.First.Next; ;) { LinkedListNode <AnimationClip.Keyframe> next = node.Next; if (next == null) { break; } // Determine nodes before and after the current node. AnimationClip.Keyframe a = node.Previous.Value; AnimationClip.Keyframe b = node.Value; AnimationClip.Keyframe c = next.Value; float t = (float)((node.Value.Time - node.Previous.Value.Time) / (next.Value.Time - node.Previous.Value.Time)); Vector3 translation = Vector3.Lerp(a.Translation, c.Translation, t); Quaternion rotation = Quaternion.Slerp(a.Rotation, c.Rotation, t); if ((translation - b.Translation).LengthSquared() < TinyLength && Quaternion.Dot(rotation, b.Rotation) > TinyCosAngle) { keyframes.Remove(node); } node = next; } }
/// <summary> /// Set the keyframes to a valid value relative to /// the current keyframe /// </summary> void SetKeyframes() { if (ClipBone == null) { return; } if (ClipBone.Keyframes.Count > 0) { Keyframe1 = ClipBone.Keyframes[m_currentKeyframe]; if (m_currentKeyframe == ClipBone.Keyframes.Count - 1) { Keyframe2 = Keyframe1; } else { Keyframe2 = ClipBone.Keyframes[m_currentKeyframe + 1]; } } else { // If there are no keyframes, set both to null Keyframe1 = null; Keyframe2 = null; } }
/// <summary> /// Entry point for animation processing. /// </summary> /// <param name="model"></param> /// <param name="input"></param> /// <param name="context"></param> private void ProcessAnimations(ModelContent model, NodeContent input, ContentProcessorContext context) { // First build a lookup table so we can determine the // index into the list of bones from a bone name. for (int i = 0; i < model.Bones.Count; i++) { bones[model.Bones[i].Name] = i; } // For saving the bone transforms boneTransforms = new Matrix[model.Bones.Count]; // // Collect up all of the animation data // ProcessAnimationsRecursive(input); // Ensure there is always a clip, even if none is included in the FBX // That way we can create poses using FBX files as one-frame // animation clips if (modelExtra.Clips.Count == 0) { AnimationClip clip = new AnimationClip(); modelExtra.Clips.Add(clip); string clipName = "Take 001"; // Retain by name clips[clipName] = clip; clip.Name = clipName; foreach (ModelBoneContent bone in model.Bones) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = bone.Name; clip.Bones.Add(clipBone); } } // Ensure all animations have a first key frame for every bone foreach (AnimationClip clip in modelExtra.Clips) { for (int b = 0; b < bones.Count; b++) { List <AnimationClip.Keyframe> keyframes = clip.Bones[b].Keyframes; if (keyframes.Count == 0 || keyframes[0].Time > 0) { AnimationClip.Keyframe keyframe = new AnimationClip.Keyframe(); keyframe.Time = 0; keyframe.Transform = boneTransforms[b]; keyframes.Insert(0, keyframe); } } } }
/// <summary> /// Set the keyframes to a valid value relative to /// the current keyframe /// </summary> private void SetKeyframes() { if (ClipBone.Keyframes.Count > 0) { Keyframe1 = ClipBone.Keyframes[currentKeyframe]; Keyframe2 = currentKeyframe == ClipBone.Keyframes.Count - 1 ? Keyframe1 : ClipBone.Keyframes[currentKeyframe + 1]; } else { // If there are no keyframes, set both to null Keyframe1 = null; Keyframe2 = null; } }
/// <summary> /// Recursive function that processes the entire scene graph, collecting up /// all of the animation data. /// </summary> private void ProcessAnimationsRecursive(NodeContent input) { // Look up the bone for this input channel int inputBoneIndex; if (bones.TryGetValue(input.Name, out inputBoneIndex)) { // Save the transform boneTransforms[inputBoneIndex] = input.Transform; } foreach (KeyValuePair <string, AnimationContent> animation in input.Animations) { // Do we have this animation before? AnimationClip clip; string clipName = animation.Key; if (!clips.TryGetValue(clipName, out clip)) { // Never seen before clip clip = new AnimationClip(); modelExtra.Clips.Add(clip); // Retain by name clips[clipName] = clip; clip.Name = clipName; foreach (ModelBoneContent bone in model.Bones) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = bone.Name; clip.Bones.Add(clipBone); } } // Ensure the duration is always set if (animation.Value.Duration.TotalSeconds > clip.Duration) { clip.Duration = animation.Value.Duration.TotalSeconds; } // // For each channel, determine the bone and then process all of the // keyframes for that bone. // foreach (KeyValuePair <string, AnimationChannel> channel in animation.Value.Channels) { // What is the bone index? int boneIndex; if (!bones.TryGetValue(channel.Key, out boneIndex)) { continue; // Ignore if not a named bone } // An animation is useless if it is for a bone not assigned to any meshes at all if (UselessAnimationTest(boneIndex)) { continue; } // I'm collecting up in a linked list so we can process the data // and remove redundant keyframes LinkedList <AnimationClip.Keyframe> keyframes = new LinkedList <AnimationClip.Keyframe>(); foreach (AnimationKeyframe keyframe in channel.Value) { Matrix transform = keyframe.Transform; // Keyframe transformation AnimationClip.Keyframe newKeyframe = new AnimationClip.Keyframe(); newKeyframe.Time = keyframe.Time.TotalSeconds; newKeyframe.Transform = transform; keyframes.AddLast(newKeyframe); } LinearKeyframeReduction(keyframes); foreach (AnimationClip.Keyframe keyframe in keyframes) { clip.Bones[boneIndex].Keyframes.Add(keyframe); } } } foreach (NodeContent child in input.Children) { ProcessAnimationsRecursive(child); } }
static AnimationClip ReadClip(BinaryReader reader) { AnimationClip clip = new AnimationClip(); clip.Name = reader.ReadString(); clip.Duration = reader.ReadDouble(); int bonesCount = reader.ReadInt32(); while (bonesCount-- > 0) { AnimationClip.Bone bone = new AnimationClip.Bone(); bone.Name = reader.ReadString(); int keyframesCount = reader.ReadInt32(); while (keyframesCount-- > 0) { AnimationClip.Keyframe keyframe = new AnimationClip.Keyframe(); keyframe.Time = reader.ReadDouble(); keyframe.Rotation = ImportQuaternion(reader); keyframe.Translation = ImportVector3(reader); bone.Keyframes.Add(keyframe); } clip.Bones.Add(bone); int originalCount = bone.Keyframes.Count; int newCount = 0; if (originalCount > 3) { if (USE_LINEAR_KEYFRAME_REDUCTION) { LinkedList <AnimationClip.Keyframe> linkedList = new LinkedList <AnimationClip.Keyframe>(); foreach (var kf in bone.Keyframes) { linkedList.AddLast(kf); } //LinearKeyframeReduction(linkedList, 0.000001f, 0.985f); //PercentageKeyframeReduction(linkedList, 0.9f); LinearKeyframeReduction(linkedList, TinyLength, TinyCosAngle); bone.Keyframes.Clear(); bone.Keyframes.AddArray(linkedList.ToArray()); newCount = bone.Keyframes.Count; } if (LINEAR_KEYFRAME_REDUCTION_STATS) { ReductionInfo ri = new ReductionInfo() { BoneName = bone.Name, OriginalKeys = originalCount, OptimizedKeys = newCount }; List <ReductionInfo> riList; if (!ReductionStats.TryGetValue(m_debugAssetName, out riList)) { riList = new List <ReductionInfo>(); ReductionStats.Add(m_debugAssetName, riList); } riList.Add(ri); } } CalculateKeyframeDeltas(bone.Keyframes); } return(clip); }