protected override void GetValueCore(TimeSpan time, ref SkeletonPose defaultSource, ref SkeletonPose defaultTarget, ref SkeletonPose result) { // Update time of the AvatarAnimation. _avatarAnimation.CurrentPosition = time; // Get bone transforms and convert to type SrtTransform. for (int i = 0; i < AvatarRenderer.BoneCount; i++) { result.SetBoneTransform(i, SrtTransform.FromMatrix(_avatarAnimation.BoneTransforms[i])); } }
/// <summary> /// Converts the bind pose matrices to SRT transforms. /// </summary> /// <param name="bindPoses">The bind pose matrices.</param> /// <returns>The equivalent SRT transforms.</returns> private static IList <SrtTransform> ConvertToSrt(IList <Matrix> bindPoses) { var numberOfBones = bindPoses.Count; var result = new SrtTransform[numberOfBones]; for (int i = 0; i < numberOfBones; i++) { result[i] = SrtTransform.FromMatrix(bindPoses[i]); } return(result); }
private void BuildSkeleton() { // Get an array of all bones in depth-first order. // (Same as MeshHelper.FlattenSkeleton(root).) var bones = TreeHelper.GetSubtree(_rootBone, n => n.Children.OfType <BoneContent>(), true) .ToList(); // Create list of parent indices, bind pose transformations and bone names. var boneParents = new List <int>(); var bindTransforms = new List <SrtTransform>(); var boneNames = new List <string>(); int numberOfWarnings = 0; foreach (var bone in bones) { int parentIndex = bones.IndexOf(bone.Parent as BoneContent); boneParents.Add(parentIndex); // Log warning for invalid transform matrices - but not too many warnings. if (numberOfWarnings < 2) { if (!SrtTransform.IsValid((Matrix)bone.Transform)) { if (numberOfWarnings < 1) { _context.Logger.LogWarning(null, _input.Identity, "Bone transform is not supported. Bone transform matrices may only contain scaling, rotation and translation."); } else { _context.Logger.LogWarning(null, _input.Identity, "More unsupported bone transform found."); } numberOfWarnings++; } } bindTransforms.Add(SrtTransform.FromMatrix(bone.Transform)); if (boneNames.Contains(bone.Name)) { string message = String.Format(CultureInfo.InvariantCulture, "Duplicate bone name (\"{0}\") found.", bone.Name); throw new InvalidContentException(message, _input.Identity); } boneNames.Add(bone.Name); } // Create and return a new skeleton instance. _skeleton = new Skeleton(boneParents, boneNames, bindTransforms); }
public void FromToMatrixTest() { var t = new Vector3F(1, 2, 3); var r = new QuaternionF(1, 2, 3, 4).Normalized; var s = new Vector3F(2, 7, 9); var m = Matrix44F.CreateTranslation(t) * Matrix44F.CreateRotation(r) * Matrix44F.CreateScale(s); var srt = SrtTransform.FromMatrix(m); Assert.IsTrue(Vector3F.AreNumericallyEqual(t, srt.Translation)); Assert.IsTrue(QuaternionF.AreNumericallyEqual(r, srt.Rotation)); Assert.IsTrue(Vector3F.AreNumericallyEqual(s, srt.Scale)); // XNA: srt = SrtTransform.FromMatrix((Matrix)m); Assert.IsTrue(Vector3F.AreNumericallyEqual(t, srt.Translation)); Assert.IsTrue(QuaternionF.AreNumericallyEqual(r, srt.Rotation)); Assert.IsTrue(Vector3F.AreNumericallyEqual(s, srt.Scale)); // With negative scale, the decomposition is not unique (many possible combinations of // axis mirroring + rotation). t = new Vector3F(1, 2, 3); r = new QuaternionF(1, 2, 3, 4).Normalized; s = new Vector3F(2, -7, 9); m = Matrix44F.CreateTranslation(t) * Matrix44F.CreateRotation(r) * Matrix44F.CreateScale(s); srt = SrtTransform.FromMatrix(m); var m2 = (Matrix44F)srt; Assert.IsTrue(Matrix44F.AreNumericallyEqual(m, m2)); m2 = srt.ToMatrix44F(); Assert.IsTrue(Matrix44F.AreNumericallyEqual(m, m2)); m2 = srt; Assert.IsTrue(Matrix44F.AreNumericallyEqual(m, m2)); Matrix mXna = srt.ToXna(); Assert.IsTrue(Matrix44F.AreNumericallyEqual(m, (Matrix44F)mXna)); mXna = srt; Assert.IsTrue(Matrix44F.AreNumericallyEqual(m, (Matrix44F)mXna)); }
//-------------------------------------------------------------- #region Avatar Skeleton Animation //-------------------------------------------------------------- /// <summary> /// Converts an intermediate-format content pipeline AnimationContent object /// to a SkeletonKeyFrameAnimation. /// </summary> private SkeletonKeyFrameAnimation ProcessSkeletonAnimation(AnimationContent animationContent, ContentProcessorContext context) { if (animationContent.Duration <= TimeSpan.Zero) { throw new InvalidContentException("Animation has a zero duration.", animationContent.Identity); } // Create a SkeletonPose key frame animation that will animate the SkeletonPose // property of an AvatarPose. var animation = new SkeletonKeyFrameAnimation { TargetProperty = "SkeletonPose", }; // Process each channel in the animation int numberOfKeyFrames = 0; foreach (KeyValuePair <string, AnimationChannel> item in animationContent.Channels) { var channelName = item.Key; channelName = CleanBoneName(channelName); var channel = item.Value; // Don't add animation nodes with "_END" in the name // -- These bones were removed from the skeleton already if (channelName.Contains("_END")) { continue; } // Look up what bone this channel is controlling. int boneIndex; if (!_boneNames.TryGetValue(channelName, out boneIndex)) { var message = string.Format("Found animation for bone '{0}', which is not part of the skeleton.", channelName); throw new InvalidContentException(message, animationContent.Identity); } // Convert and add the key frame data. foreach (AnimationKeyframe keyframe in channel) { var time = keyframe.Time; var matrix = CreateKeyFrameMatrix(keyframe, boneIndex); var srt = SrtTransform.FromMatrix(matrix); animation.AddKeyFrame(boneIndex, time, srt); numberOfKeyFrames++; } } if (numberOfKeyFrames == 0) { throw new InvalidContentException("Animation has no key frames.", animationContent.Identity); } // Compress animation to safe memory. float removedKeyFrames = animation.Compress( CompressionScaleThreshold, CompressionRotationThreshold, CompressionTranslationThreshold); if (removedKeyFrames > 0) { context.Logger.LogImportantMessage("Compression removed {0:P} of all key frames.", removedKeyFrames); } // Finalize the skeleton key frame animation. This optimizes the internal data structures. animation.Freeze(); 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, }); }
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); }