//-------------------------------------------------------------- #region General Methods //-------------------------------------------------------------- public override TimelineGroup Process(NodeContent input, ContentProcessorContext context) { // Uncomment this to attach and launch a debugger. //System.Diagnostics.Debugger.Launch(); // Get the skeleton node. NodeContent skeleton = FindSkeleton(input); if (skeleton == null) { throw new InvalidContentException("Avatar skeleton not found.", input.Identity); } if (skeleton.Animations.Count < 1) { throw new InvalidContentException("No animation was found in the file.", input.Identity); } if (skeleton.Animations.Count > 1) { throw new InvalidContentException("More than one animation was found.", input.Identity); } // Remove the extra bones that we are not using. RemoveEndBonesAndFixBoneNames(skeleton); // Create a list of the bones from the skeleton hierarchy. IList <NodeContent> bones = FlattenSkeleton(skeleton); if (bones.Count != AvatarRenderer.BoneCount) { throw new InvalidContentException("Invalid number of bones found.", input.Identity); } // Fill the bind pose array with the transforms from the bones. foreach (NodeContent bone in bones) { _bindPoses.Add(bone.Transform); } // Build up a table mapping bone names to indices. _boneNames = new Dictionary <string, int>(); for (int i = 0; i < bones.Count; i++) { string boneName = bones[i].Name; if (!string.IsNullOrEmpty(boneName)) { _boneNames.Add(boneName, i); } } // Create the custom animation data. // From the error-checking above, we know there will only be one animation. AnimationContent animationContent = skeleton.Animations.Values.First(); SkeletonKeyFrameAnimation skeletonAnimation = ProcessSkeletonAnimation(animationContent, context); AvatarExpressionKeyFrameAnimation expressionAnimation = ProcessExpressionAnimation(input, context); var timelineGroup = new TimelineGroup(); if (skeletonAnimation != null) { timelineGroup.Add(skeletonAnimation); } if (expressionAnimation != null) { timelineGroup.Add(expressionAnimation); } return(timelineGroup); }
private ITimeline BakeAnimation(AvatarAnimation avatarAnimation) { // Create an AvatarExpression key frame animation that will be applied to the Expression // property of an AvatarPose. AvatarExpressionKeyFrameAnimation expressionAnimation = new AvatarExpressionKeyFrameAnimation { TargetProperty = "Expression" }; // Create a SkeletonPose key frame animation that will be applied to the SkeletonPose // property of an AvatarPose. SkeletonKeyFrameAnimation skeletonKeyFrameAnimation = new SkeletonKeyFrameAnimation { TargetProperty = "SkeletonPose" }; // In the next loop, we sample the original animation with 30 Hz and store the key frames. int numberOfKeyFrames = 0; AvatarExpression previousExpression = new AvatarExpression(); TimeSpan time = TimeSpan.Zero; TimeSpan length = avatarAnimation.Length; TimeSpan step = new TimeSpan(333333); // 1/30 seconds; while (true) { // Advance time in AvatarAnimation. avatarAnimation.CurrentPosition = time; // Add expression key frame if this is the first key frame or if the key frame is // different from the last key frame. AvatarExpression expression = avatarAnimation.Expression; if (time == TimeSpan.Zero || !expression.Equals(previousExpression)) { expressionAnimation.KeyFrames.Add(new KeyFrame <AvatarExpression>(time, expression)); } previousExpression = expression; // Convert bone transforms to SrtTransforms and add key frames to the SkeletonPose // animation. for (int i = 0; i < avatarAnimation.BoneTransforms.Count; i++) { SrtTransform boneTransform = SrtTransform.FromMatrix(avatarAnimation.BoneTransforms[i]); skeletonKeyFrameAnimation.AddKeyFrame(i, time, boneTransform); numberOfKeyFrames++; } // Abort if we have arrived at the end time. if (time == length) { break; } // Increase time. We check that we do not step over the end time. if (time + step > length) { time = length; } else { time += step; } } // Compress animation to save memory. float numberOfRemovedKeyFrames = skeletonKeyFrameAnimation.Compress(0.1f, 0.1f, 0.001f); Debug.WriteLine("Compression removed " + numberOfRemovedKeyFrames * 100 + "% of the key frames."); // Finalize the skeleton key frame animation. This optimizes the internal data structures. skeletonKeyFrameAnimation.Freeze(); return(new TimelineGroup { expressionAnimation, skeletonKeyFrameAnimation, }); }
//-------------------------------------------------------------- #region Avatar Expression Animation //-------------------------------------------------------------- /// <summary> /// Converts the input expression animation file into expression animation keyframes. /// </summary> private AvatarExpressionKeyFrameAnimation ProcessExpressionAnimation(NodeContent input, ContentProcessorContext context) { if (string.IsNullOrEmpty(ExpressionFile)) { return(null); } // Create a AvatarExpression key frame animation that will animate the Expression // property of an AvatarPose. var animation = new AvatarExpressionKeyFrameAnimation { TargetProperty = "Expression", }; // Let the content pipeline know that we depend on this file and we need to rebuild the // content if the file is modified. string sourcePath = Path.GetDirectoryName(input.Identity.SourceFilename); string filePath = Path.GetFullPath(Path.Combine(sourcePath, ExpressionFile)); context.AddDependency(filePath); FileStream fs = File.OpenRead(filePath); StreamReader sr = new StreamReader(fs); while (!sr.EndOfStream) { string currentLine = sr.ReadLine(); // Skip comment lines if (currentLine.StartsWith("#")) { continue; } string[] Components = currentLine.Split(','); // Check for the correct number of components if (Components.Length != 6) { throw new InvalidContentException("Error processing facial expression file", input.Identity); } try { TimeSpan time = TimeSpan.FromMilliseconds(Convert.ToDouble(Components[0])); AvatarExpression avatarExpression = new AvatarExpression(); avatarExpression.LeftEye = (AvatarEye)Convert.ToInt32(Components[1]); avatarExpression.LeftEyebrow = (AvatarEyebrow)Convert.ToInt32(Components[2]); avatarExpression.Mouth = (AvatarMouth)Convert.ToInt32(Components[3]); avatarExpression.RightEye = (AvatarEye)Convert.ToInt32(Components[4]); avatarExpression.RightEyebrow = (AvatarEyebrow)Convert.ToInt32(Components[5]); // Add key frame. var keyFrame = new KeyFrame <AvatarExpression>(time, avatarExpression); animation.KeyFrames.Add(keyFrame); } catch (Exception) { throw new InvalidContentException("Error processing facial expression file", input.Identity); } } if (animation.KeyFrames.Count == 0) { return(null); } // Sort the animation frames animation.KeyFrames.Sort(); return(animation); }
private ITimeline BakeAnimation(AvatarAnimation avatarAnimation) { // Create an AvatarExpression key frame animation that will be applied to the Expression // property of an AvatarPose. AvatarExpressionKeyFrameAnimation expressionAnimation = new AvatarExpressionKeyFrameAnimation { TargetProperty = "Expression" }; // Create a SkeletonPose key frame animation that will be applied to the SkeletonPose // property of an AvatarPose. SkeletonKeyFrameAnimation skeletonKeyFrameAnimation = new SkeletonKeyFrameAnimation { TargetProperty = "SkeletonPose" }; // In the next loop, we sample the original animation with 30 Hz and store the key frames. int numberOfKeyFrames = 0; AvatarExpression previousExpression = new AvatarExpression(); TimeSpan time = TimeSpan.Zero; TimeSpan length = avatarAnimation.Length; TimeSpan step = new TimeSpan(333333); // 1/30 seconds; while (true) { // Advance time in AvatarAnimation. avatarAnimation.CurrentPosition = time; // Add expression key frame if this is the first key frame or if the key frame is // different from the last key frame. AvatarExpression expression = avatarAnimation.Expression; if (time == TimeSpan.Zero || !expression.Equals(previousExpression)) expressionAnimation.KeyFrames.Add(new KeyFrame<AvatarExpression>(time, expression)); previousExpression = expression; // Convert bone transforms to SrtTransforms and add key frames to the SkeletonPose // animation. for (int i = 0; i < avatarAnimation.BoneTransforms.Count; i++) { SrtTransform boneTransform = SrtTransform.FromMatrix(avatarAnimation.BoneTransforms[i]); skeletonKeyFrameAnimation.AddKeyFrame(i, time, boneTransform); numberOfKeyFrames++; } // Abort if we have arrived at the end time. if (time == length) break; // Increase time. We check that we do not step over the end time. if (time + step > length) time = length; else time += step; } // Compress animation to save memory. float numberOfRemovedKeyFrames = skeletonKeyFrameAnimation.Compress(0.1f, 0.1f, 0.001f); Debug.WriteLine("Compression removed " + numberOfRemovedKeyFrames * 100 + "% of the key frames."); // Finalize the skeleton key frame animation. This optimizes the internal data structures. skeletonKeyFrameAnimation.Freeze(); return new TimelineGroup { expressionAnimation, skeletonKeyFrameAnimation, }; }