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, }; }
/// <summary> /// Converts an AnimationContent to a SkeletonKeyFrameAnimation. /// </summary> private SkeletonKeyFrameAnimation ProcessAnimation(AnimationContent animationContent, Skeleton skeleton, ContentProcessorContext context) { var animation = new SkeletonKeyFrameAnimation { EnableInterpolation = true }; // Process all animation channels (each channel animates a bone). int numberOfKeyFrames = 0; foreach (var item in animationContent.Channels) { string channelName = item.Key; AnimationChannel channel = item.Value; int boneIndex = skeleton.GetIndex(channelName); if (boneIndex == -1) { var message = string.Format("Found animation for bone '{0}', which is not part of the skeleton.", channelName); throw new InvalidContentException(message, animationContent.Identity); } var bindPoseRelativeInverse = skeleton.GetBindPoseRelative(boneIndex).Inverse; foreach (AnimationKeyframe keyframe in channel) { TimeSpan time = keyframe.Time; SrtTransform transform = SrtTransform.FromMatrix(keyframe.Transform); // The matrix in the key frame is the transformation in the coordinate space of the // parent bone. --> Convert it to a transformation relative to the animated bone. transform = bindPoseRelativeInverse * transform; // To start with minimal numerical errors, we normalize the rotation quaternion. transform.Rotation.Normalize(); animation.AddKeyFrame(boneIndex, time, transform); numberOfKeyFrames++; } } if (numberOfKeyFrames == 0) throw new InvalidContentException("Animation has no keyframes.", animationContent.Identity); // Compress animation to safe memory. float removedKeyFrames = animation.Compress( CompressionScaleThreshold, CompressionRotationThreshold, CompressionTranslationThreshold); if (removedKeyFrames > 0) context.Logger.LogImportantMessage("{0}: Compression removed {1:P} of all key frames.", animationContent.Name, removedKeyFrames); // Finalize the animation. (Optimizes the animation data for fast runtime access.) animation.Freeze(); return animation; }
private SkeletonKeyFrameAnimation BuildAnimation(AnimationContent animationContent) { string name = animationContent.Name; // Add loop frame? bool addLoopFrame = false; if (_modelDescription != null) { var animationDescription = _modelDescription.Animation; if (animationDescription != null) { addLoopFrame = animationDescription.AddLoopFrame ?? false; if (animationDescription.Splits != null) { foreach (var split in animationDescription.Splits) { if (split.Name == name) { if (split.AddLoopFrame.HasValue) addLoopFrame = split.AddLoopFrame.Value; break; } } } } } var animation = new SkeletonKeyFrameAnimation { EnableInterpolation = true }; // Process all animation channels (each channel animates a bone). int numberOfKeyFrames = 0; foreach (var item in animationContent.Channels) { string channelName = item.Key; AnimationChannel channel = item.Value; int boneIndex = _skeleton.GetIndex(channelName); if (boneIndex != -1) { SrtTransform? loopFrame = null; var bindPoseRelativeInverse = _skeleton.GetBindPoseRelative(boneIndex).Inverse; foreach (AnimationKeyframe keyframe in channel) { TimeSpan time = keyframe.Time; SrtTransform transform = SrtTransform.FromMatrix(keyframe.Transform); // The matrix in the key frame is the transformation in the coordinate space of the // parent bone. --> Convert it to a transformation relative to the animated bone. transform = bindPoseRelativeInverse * transform; // To start with minimal numerical errors, we normalize the rotation quaternion. transform.Rotation.Normalize(); if (loopFrame == null) loopFrame = transform; if (!addLoopFrame || time < animationContent.Duration) animation.AddKeyFrame(boneIndex, time, transform); numberOfKeyFrames++; } if (addLoopFrame && loopFrame.HasValue) animation.AddKeyFrame(boneIndex, animationContent.Duration, loopFrame.Value); } else { _context.Logger.LogWarning( null, animationContent.Identity, "Found animation for bone \"{0}\", which is not part of the skeleton.", channelName); } } if (numberOfKeyFrames == 0) { _context.Logger.LogWarning(null, animationContent.Identity, "Animation is ignored because it has no keyframes."); return null; } // Compress animation to save memory. if (_modelDescription != null) { var animationDescription = _modelDescription.Animation; if (animationDescription != null) { float removedKeyFrames = animation.Compress( animationDescription.ScaleCompression, animationDescription.RotationCompression, animationDescription.TranslationCompression); if (removedKeyFrames > 0) { _context.Logger.LogImportantMessage("{0}: Compression removed {1:P} of all key frames.", string.IsNullOrEmpty(name) ? "Unnamed" : name, removedKeyFrames); } } } // Finalize the animation. (Optimizes the animation data for fast runtime access.) animation.Freeze(); return animation; }