/// <summary> /// Constructor for the animation player. It makes the /// association between a clip and a model and sets up for playing /// </summary> /// <param name="clip"></param> public AnimationPlayer(AnimationClip clip, AnimatedModel model) { this.clip = clip; this.model = model; // Create the bone information classes boneCnt = clip.Bones.Count; boneInfos = new BoneInfo[boneCnt]; for (int b = 0; b < boneInfos.Length; b++) { // Create it boneInfos[b] = new BoneInfo(clip.Bones[b]); // Assign it to a model bone boneInfos[b].SetModel(model); } Rewind(); }
/// <summary> /// Constructor /// </summary> /// <param name="bone"></param> public BoneInfo(AnimationClip.Bone bone) { this.ClipBone = bone; SetKeyframes(); SetPosition(0); }
/// <summary> /// Play an animation clip /// </summary> /// <param name="clip">The clip to play</param> /// <returns>The player that will play this clip</returns> public AnimationPlayer PlayClip(AnimationClip clip) { // Create a clip player and assign it to this model player = new AnimationPlayer(clip, this); return player; }
/// <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(clipName,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); } }
/// <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, ContentIdentity sourceIdentity) { // 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); // Check to see if there's an animation clip definition // Here, we're checking for a file with the _Anims suffix. // So, if your model is named dude.fbx, we'd add dude_Anims.xml in the same folder // and the pipeline will see the file and use it to override the animations in the // original model file. string SourceModelFile = sourceIdentity.SourceFilename; string SourcePath = Path.GetDirectoryName(SourceModelFile); string AnimFilename = Path.GetFileNameWithoutExtension(SourceModelFile); AnimFilename += "_Anims.xml"; string AnimPath = Path.Combine(SourcePath, AnimFilename); if (File.Exists(AnimPath)) { // Add the filename as a dependency, so if it changes, the model is rebuilt context.AddDependency(AnimPath); // Load the animation definition from the XML file AnimationDefinition AnimDef = context.BuildAndLoadAsset<XmlImporter, AnimationDefinition>(new ExternalReference<XmlImporter>(AnimPath), null); if (modelExtra.Clips.Count > 0) //if there are some animations in our model { foreach (AnimationDefinition.ClipPart Part in AnimDef.ClipParts) { // Grab the main clip that we are using and copy to MainClip AnimationClip MainClip = new AnimationClip(); float StartTime = GetTimeSpanForFrame(Part.StartFrame, AnimDef.OriginalFrameCount, modelExtra.Clips[AnimDef.OriginalClipName].Duration); float EndTime = GetTimeSpanForFrame(Part.EndFrame, AnimDef.OriginalFrameCount, modelExtra.Clips[AnimDef.OriginalClipName].Duration); MainClip.Duration = EndTime-StartTime; MainClip.Name = modelExtra.Clips[AnimDef.OriginalClipName].Name; for (int i = 0; i < modelExtra.Clips[AnimDef.OriginalClipName].Bones.Count; i++) { AnimationClip.Bone clipBone = new AnimationClip.Bone(); clipBone.Name = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Name; if (modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes.Count != 0) { for (int j = 0; j < modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes.Count; j++) { if ((modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time >= StartTime) && (modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time <= EndTime)) { AnimationClip.Keyframe frame = new AnimationClip.Keyframe(); frame.Rotation = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Rotation; frame.Time = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Time - StartTime; frame.Translation = modelExtra.Clips[AnimDef.OriginalClipName].Bones[i].Keyframes[j].Translation; clipBone.Keyframes.Add(frame); } } } MainClip.Bones.Add(clipBone); } modelExtra.Clips.Add(Part.ClipName, MainClip); } // Process each of our new animation clip parts //foreach (AnimationDefinition.ClipPart Part in AnimDef.ClipParts) //{ // // Calculate the frame times // float StartTime = GetTimeSpanForFrame(Part.StartFrame, AnimDef.OriginalFrameCount, MainClip.Duration); // float EndTime = GetTimeSpanForFrame(Part.EndFrame, AnimDef.OriginalFrameCount, MainClip.Duration); // //prepare new animation // LinkedList<AnimationClip.Keyframe> keyframes = new LinkedList<AnimationClip.Keyframe>(); // AnimationClip.Bone NewBone = new AnimationClip.Bone(); // NewBone.Keyframes = new List<AnimationClip.Keyframe>(); // AnimationClip clip = new AnimationClip(); // modelExtra.Clips.Add(Part.ClipName, clip); // modelExtra.Clips[Part.ClipName].Duration = (Part.EndFrame - Part.StartFrame + 1) * MainClip.Duration / AnimDef.OriginalFrameCount; // string clipName = Part.ClipName; // // Retain by name // clips[clipName] = clip; // modelExtra.Clips[Part.ClipName].Name = clipName; // foreach (AnimationClip.Bone bone in MainClip.Bones) // { // if (bone.Keyframes.Count != 0) // { // int ilosc = Part.EndFrame - Part.StartFrame + 1; // NewBone.Keyframes = bone.Keyframes.GetRange(Part.StartFrame, ilosc); // NewBone.Name = bone.Name; // modelExtra.Clips[Part.ClipName].Bones.Add(NewBone); // } // else // { // AnimationClip.Bone clipBone = new AnimationClip.Bone(); // clipBone.Name = bone.Name; // modelExtra.Clips[Part.ClipName].Bones.Add(clipBone); // } // } //} } } // 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("Take 001",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 (KeyValuePair<string,AnimationClip> clip in modelExtra.Clips) { for (int b = 0; b < bones.Count; b++) { List<AnimationClip.Keyframe> keyframes = clip.Value.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> /// 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); } } } }