public void MissingWeights() { var animation = new SkeletonKeyFrameAnimation(); animation.AddKeyFrame(0, TimeSpan.FromSeconds(0), new SrtTransform(QuaternionF.Identity)); animation.AddKeyFrame(1, TimeSpan.FromSeconds(0), new SrtTransform(QuaternionF.Identity)); Assert.AreEqual(1.0f, animation.GetWeight(0)); Assert.AreEqual(1.0f, animation.GetWeight(1)); animation.Freeze(); Assert.AreEqual(1.0f, animation.GetWeight(0)); }
public void MissingWeights() { var animation = new SkeletonKeyFrameAnimation(); animation.AddKeyFrame(0, TimeSpan.FromSeconds(0), new SrtTransform(Quaternion.Identity)); animation.AddKeyFrame(1, TimeSpan.FromSeconds(0), new SrtTransform(Quaternion.Identity)); Assert.AreEqual(1.0f, animation.GetWeight(0)); Assert.AreEqual(1.0f, animation.GetWeight(1)); animation.Freeze(); Assert.AreEqual(1.0f, animation.GetWeight(0)); }
public DudeWalkingSample(Microsoft.Xna.Framework.Game game) : base(game) { var modelNode = ContentManager.Load <ModelNode>("Dude/Dude"); var meshNode = modelNode.GetSubtree().OfType <MeshNode>().First().Clone(); meshNode.PoseLocal = new Pose(new Vector3F(0, 0, 0), Matrix33F.CreateRotationY(ConstantsF.Pi)); SampleHelper.EnablePerPixelLighting(meshNode); GraphicsScreen.Scene.Children.Add(meshNode); // The imported animations are stored in the mesh. Dictionary <string, SkeletonKeyFrameAnimation> animations = meshNode.Mesh.Animations; // The Dude model contains only one animation, which is a SkeletonKeyFrameAnimation with // a walk cycle. SkeletonKeyFrameAnimation walkAnimation = animations.Values.First(); // Wrap the walk animation in an animation clip that loops the animation forever. AnimationClip <SkeletonPose> loopingAnimation = new AnimationClip <SkeletonPose>(walkAnimation) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Start the animation and keep the created AnimationController. // We must cast the SkeletonPose to IAnimatableProperty because SkeletonPose implements // IAnimatableObject and IAnimatableProperty. We must tell the AnimationService if we want // to animate an animatable property of the SkeletonPose (IAnimatableObject), or if we want to // animate the whole SkeletonPose (IAnimatableProperty). _animationController = AnimationService.StartAnimation(loopingAnimation, (IAnimatableProperty)meshNode.SkeletonPose); // The animation will be applied the next time AnimationManager.ApplyAnimations() is called // in the main loop. ApplyAnimations() is called before this method is called, therefore // the model will be rendered in the bind pose in this frame and in the first animation key // frame in the next frame - this creates an annoying visual popping effect. // We can avoid this if we call AnimationController.UpdateAndApply(). This will immediately // change the model pose to the first key frame pose. _animationController.UpdateAndApply(); // (Optional) Enable Auto-Recycling: // After the animation is stopped, the animation service will recycle all // intermediate data structures. _animationController.AutoRecycle(); }
public CompressionSample(Microsoft.Xna.Framework.Game game) : base(game) { var modelNode = ContentManager.Load <ModelNode>("Dude/Dude"); SampleHelper.EnablePerPixelLighting(modelNode); _meshNodeUncompressed = modelNode.GetSubtree().OfType <MeshNode>().First().Clone(); _meshNodeUncompressed.PoseLocal = new Pose(new Vector3F(-0.5f, 0, 0), Matrix33F.CreateRotationY(ConstantsF.Pi)); GraphicsScreen.Scene.Children.Add(_meshNodeUncompressed); _meshNodeCompressed = _meshNodeUncompressed.Clone(); _meshNodeCompressed.PoseLocal = new Pose(new Vector3F(0.5f, 0, 0), Matrix33F.CreateRotationY(ConstantsF.Pi)); GraphicsScreen.Scene.Children.Add(_meshNodeCompressed); Dictionary <string, SkeletonKeyFrameAnimation> animations = _meshNodeUncompressed.Mesh.Animations; _animation = animations.Values.First(); RestartAnimations(); }
//-------------------------------------------------------------- #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); }
//-------------------------------------------------------------- #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); }
public FacialAnimationSample(Microsoft.Xna.Framework.Game game) : base(game) { _graphicsScreen = new DeferredGraphicsScreen(Services) { DrawReticle = false }; GraphicsService.Screens.Insert(0, _graphicsScreen); Services.Register(typeof(DebugRenderer), null, _graphicsScreen.DebugRenderer); Services.Register(typeof(IScene), null, _graphicsScreen.Scene); // Add a game object which adds some GUI controls for the deferred graphics // screen to the Options window. GameObjectService.Objects.Add(new DeferredGraphicsOptionsObject(Services)); // Use a fixed camera. var projection = new PerspectiveProjection(); projection.SetFieldOfView( ConstantsF.PiOver4, GraphicsService.GraphicsDevice.Viewport.AspectRatio, 0.1f, 10); var cameraNode = new CameraNode(new Camera(projection)); cameraNode.LookAt(new Vector3F(0.15f, 0.15f, 0.5f), new Vector3F(0.1f, 0.15f, 0), Vector3F.Up); _graphicsScreen.Scene.Children.Add(cameraNode); _graphicsScreen.ActiveCameraNode = cameraNode; // Lighting setup: var keyLight = new LightNode(new Spotlight { DiffuseIntensity = 0.6f, SpecularIntensity = 0.4f }); keyLight.LookAt(new Vector3F(-2, 2, 2), new Vector3F(), Vector3F.Up); _graphicsScreen.Scene.Children.Add(keyLight); var backLight = new LightNode(new Spotlight { DiffuseIntensity = 0.3f, SpecularIntensity = 0.3f }); backLight.LookAt(new Vector3F(1, 0.5f, -2), new Vector3F(), Vector3F.Up); _graphicsScreen.Scene.Children.Add(backLight); var fillLight = new LightNode(new AmbientLight { HemisphericAttenuation = 1, Intensity = 0.1f }); _graphicsScreen.Scene.Children.Add(fillLight); // The scene does not have a proper background. That's why the exposure is a // bit off. --> Reduce the max exposure. var hdrFilter = _graphicsScreen.PostProcessors.OfType <HdrFilter>().First(); hdrFilter.MaxExposure = 6; // Load the customized "Sintel" model (original: Durian Open Movie Project - http://www.sintel.org/). var model = ContentManager.Load <ModelNode>("Sintel/Sintel-Head").Clone(); model.PoseWorld = new Pose(new Vector3F(0, 0, 0), Matrix33F.CreateRotationY(MathHelper.ToRadians(10)) * Matrix33F.CreateRotationX(-MathHelper.ToRadians(90))); _graphicsScreen.Scene.Children.Add(model); // The model consists of a root node and a mesh node. // ModelNode "Sintel-Head" // MeshNode "Sintel" _sintel = (MeshNode)model.Children[0]; // The model contains two skeletal animations: // - "MOUTH-open" is just a single frame. // - "Test" is a short animation (250 frames). // In the Options window, we will add a slider to move the jaw. // Slider.Value = 0 ... mouth closed (default) _mouthClosedPose = SkeletonPose.Create(_sintel.Mesh.Skeleton); // Slider.Value = 1 ... mouth open (copied from the "MOUTH-open" animation) SkeletonKeyFrameAnimation mouthOpen = _sintel.Mesh.Animations["MOUTH-open"]; _mouthOpenPose = SkeletonPose.Create(_sintel.Mesh.Skeleton); mouthOpen.GetValue(TimeSpan.Zero, ref _mouthOpenPose, ref _mouthOpenPose, ref _mouthOpenPose); // Turn the "Test" animation into an endless loop. _skeletalAnimation = new AnimationClip <SkeletonPose>(_sintel.Mesh.Animations["Test"]) { Duration = TimeSpan.MaxValue, LoopBehavior = LoopBehavior.Cycle }; // Mesh has several morph targets for facial animation, which are imported // automatically via the content pipeline. Unfortunately, the XNA content // pipeline cannot import morph target animations automatically. // In this demo, we will create a morph target animation in code. _morphingAnimation = CreateMorphingAnimation(); CreateGuiControls(); }
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); }