/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> public static ModelAnimationClip ProcessRootAnimation(AnimationContent animation, string name) { List <ModelKeyframe> keyframes = new List <ModelKeyframe>(); // The root animation is controlling the root of the bones AnimationChannel channel = animation.Channels[name]; // Add the transformations on the root of the model foreach (AnimationKeyframe keyframe in channel) { keyframes.Add(new ModelKeyframe(0, keyframe.Time, keyframe.Transform)); } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) { throw new InvalidContentException("Animation has no keyframes."); } if (animation.Duration <= TimeSpan.Zero) { throw new InvalidContentException("Animation has a zero duration."); } return(new ModelAnimationClip(animation.Duration, keyframes)); }
static SerializableAnimation ProcessAnimation(AnimationContent xnaAnimation) { SerializableAnimation animation = new SerializableAnimation(); animation.name = xnaAnimation.Name; animation.length = (float)xnaAnimation.Duration.TotalSeconds; foreach (KeyValuePair <string, AnimationChannel> xnaAnimationChannelPair in xnaAnimation.Channels) { AnimationChannel xnaAnimationChannel = xnaAnimationChannelPair.Value; string xnaAnimationChannelName = xnaAnimationChannelPair.Key; SerializableTrack track = new SerializableTrack(); track.name = xnaAnimationChannelName; animation.tracks.Add(track); for (int i = 0; i < xnaAnimationChannel.Count; i++) { AnimationKeyframe xnaKeyframe = xnaAnimationChannel[i]; SerializableKeyFrame keyframe = new SerializableKeyFrame((float)xnaKeyframe.Time.TotalSeconds, xnaKeyframe.Transform); track.AddKeyFrame(keyframe); } } return(animation); }
private glTFLoader.Schema.Animation CreateAnimation(AoMEngineLibrary.Graphics.Model.Animation animation, int weightCount, Stream bufferStream) { var sampler = new AnimationSampler(); sampler.Interpolation = AnimationSampler.InterpolationEnum.LINEAR; CreateKeysBuffer(animation, bufferStream); sampler.Input = accessors.Count - 1; CreateWeightsBuffer(animation, weightCount, bufferStream); sampler.Output = accessors.Count - 1; var target = new AnimationChannelTarget(); target.Node = 0; target.Path = AnimationChannelTarget.PathEnum.weights; var channel = new AnimationChannel(); channel.Sampler = 0; channel.Target = target; var gltfAnimation = new glTFLoader.Schema.Animation(); gltfAnimation.Samplers = new[] { sampler }; gltfAnimation.Channels = new[] { channel }; return(gltfAnimation); }
public void SelectChannel(string name) { //Debug.Log ("Selecting channel. Available channels: " + channels.Keys.Count); currentChannel = channels[name]; totalFrames = currentChannel.sprites.Length; //Reset(); }
public void TestFitting() { // Make a sinus between T = 0s to 10s at 60 FPS var animationChannel = new AnimationChannel(); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.Zero, Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(10.0), Value = 0.0f }); var maxErrorThreshold = 0.05f; var timeStep = CompressedTimeSpan.FromSeconds(1.0f / 60.0f); Func <CompressedTimeSpan, float> curve = x => { if (x.Ticks == 196588) { } return((float)Math.Sin(x.Ticks / (double)CompressedTimeSpan.FromSeconds(10.0).Ticks *Math.PI * 2.0)); }; animationChannel.Fitting( curve, CompressedTimeSpan.FromSeconds(1.0f / 60.0f), maxErrorThreshold); var evaluator = new AnimationChannel.Evaluator(animationChannel.KeyFrames); for (var time = CompressedTimeSpan.Zero; time < CompressedTimeSpan.FromSeconds(10.0); time += timeStep) { var diff = Math.Abs(curve(time) - evaluator.Evaluate(time)); Assert.That(diff, Is.LessThanOrEqualTo(maxErrorThreshold)); } }
public void TestFitting() { // Make a sinus between T = 0s to 10s at 60 FPS var animationChannel = new AnimationChannel(); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.Zero, Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(10.0), Value = 0.0f }); var maxErrorThreshold = 0.05f; var timeStep = CompressedTimeSpan.FromSeconds(1.0f / 60.0f); Func<CompressedTimeSpan, float> curve = x => { if (x.Ticks == 196588) { } return (float)Math.Sin(x.Ticks / (double)CompressedTimeSpan.FromSeconds(10.0).Ticks * Math.PI * 2.0); }; animationChannel.Fitting( curve, CompressedTimeSpan.FromSeconds(1.0f / 60.0f), maxErrorThreshold); var evaluator = new AnimationChannel.Evaluator(animationChannel.KeyFrames); for (var time = CompressedTimeSpan.Zero; time < CompressedTimeSpan.FromSeconds(10.0); time += timeStep) { var diff = Math.Abs(curve(time) - evaluator.Evaluate(time)); Assert.That(diff, Is.LessThanOrEqualTo(maxErrorThreshold)); } }
} // ProcessRigidAnimation static void AddTransformationNodes(string animationName, Dictionary<string, int> boneMap, NodeContent input, List<ModelKeyframe> keyframes, ref TimeSpan duration) { // Add the transformation on each of the meshes foreach (NodeContent childNode in input.Children) { // If this node doesn't have keyframes for this animation we should just skip it if (childNode.Animations.ContainsKey(animationName)) { AnimationChannel childChannel = childNode.Animations[animationName].Channels[childNode.Name]; if (childNode.Animations[animationName].Duration != duration) { if (duration < childNode.Animations[animationName].Duration) duration = childNode.Animations[animationName].Duration; } int boneIndex; if (!boneMap.TryGetValue(childNode.Name, out boneIndex)) { throw new InvalidContentException(string.Format("Found animation for bone '{0}', which is not part of the model.", childNode.Name)); } foreach (AnimationKeyframe keyframe in childChannel) { keyframes.Add(new ModelKeyframe((ushort)boneIndex, (float)(keyframe.Time.TotalSeconds), keyframe.Transform)); } } AddTransformationNodes(animationName, boneMap, childNode, keyframes, ref duration); } } // AddTransformationNodes
/// <summary> /// Retrieves and interpolates the pose of an animation channel. /// </summary> /// <param name="animationChannel">Name of the animation channel.</param> /// <param name="animationTime">Current animation clip time.</param> /// <param name="outPose">The output interpolated pose.</param> private void InterpolateChannelPose(AnimationChannel animationChannel, TimeSpan animationTime, out Pose outPose) { if (translationInterpolation == InterpolationMode.None && orientationInterpolation == InterpolationMode.None && scaleInterpolation == InterpolationMode.None) { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); outPose = animationChannel[keyframeIndex].Pose; } else { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); int nextKeyframeIndex; // If we are looping then the next frame may wrap around to // the beginning. If not we should just clamp it at the last frame if (loopEnabled) { nextKeyframeIndex = (keyframeIndex + 1) % animationChannel.Count; } else { nextKeyframeIndex = Math.Min(keyframeIndex + 1, animationChannel.Count - 1); } AnimationChannelKeyframe keyframe1 = animationChannel[keyframeIndex]; AnimationChannelKeyframe keyframe2 = animationChannel[nextKeyframeIndex]; // Calculate the time between the keyframes considering loop long keyframeDuration; if (keyframeIndex == (animationChannel.Count - 1)) { keyframeDuration = animationClip.Duration.Ticks - keyframe1.Time.Ticks; } else { keyframeDuration = keyframe2.Time.Ticks - keyframe1.Time.Ticks; } // Interpolate when duration higher than zero if (keyframeDuration > 0) { long elapsedKeyframeTime = animationTime.Ticks - keyframe1.Time.Ticks; float lerpFactor = elapsedKeyframeTime / (float)keyframeDuration; outPose = Pose.Interpolate(keyframe1.Pose, keyframe2.Pose, lerpFactor, translationInterpolation, orientationInterpolation, scaleInterpolation); } // Otherwise don't interpolate else { outPose = keyframe1.Pose; } } }
private void LoadAnimation(GLTF.Schema.Animation gltfAnimation, int index, AnimationClip clip) { clip.name = gltfAnimation.Name != null && gltfAnimation.Name.Length > 0 ? gltfAnimation.Name : "GLTFAnimation_" + index; for (int i = 0; i < gltfAnimation.Channels.Count; ++i) { AnimationChannel channel = gltfAnimation.Channels[i]; addGLTFChannelDataToClip(gltfAnimation.Channels[i], clip); } clip.EnsureQuaternionContinuity(); }
// template AnimationSet // { // [ Animation ] // } /// <summary> /// Imports an animation set that is added to the AnimationContentDictionary of /// the root frame. /// </summary> private void ImportAnimationSet() { AnimationContent animSet = new AnimationContent(); animSet.Name = tokens.ReadName(); // Give each animation a unique name if (animSet.Name == null) { animSet.Name = "Animation" + root.Animations.Count.ToString(); } // Fill in all the channels of the animation. Each channel refers to // a single bone's role in the animation. for (string next = tokens.NextToken(); next != "}"; next = tokens.NextToken()) { if (next == "Animation") { string boneName; AnimationChannel anim = ImportAnimationChannel(out boneName); // Every channel must be attached to a bone! if (boneName == null) { throw new Exception("Animation in file is not attached to any joint"); } // Make sure that the duration of the animation is set to the // duration of the longest animation channel if (anim[anim.Count - 1].Time > animSet.Duration) { animSet.Duration = anim[anim.Count - 1].Time; } animSet.Channels.Add(boneName, anim); } // skip nodes we are uninterested in else if (next == "{") { tokens.SkipNode(); } } //skeletonRoot = MeshHelper.FindSkeleton(root); //skeletonRoot.Animations.Add(animSet.Name, animSet); if (root.Animations.ContainsKey(animSet.Name)) { string error = "Attempting to add " + animSet.Name + " but it already exists."; throw new Exception(error); } root.Animations.Add(animSet.Name, animSet); }
private AnimationContent CreateAnimation(Assimp.Animation aiAnimation) { var animation = new AnimationContent { Name = FixupAnimationName(aiAnimation.Name), Duration = TimeSpan.FromSeconds(aiAnimation.DurationInTicks / aiAnimation.TicksPerSecond) }; foreach (var aiChannel in aiAnimation.NodeAnimationChannels) { var channel = new AnimationChannel(); // We can have different numbers of keyframes for each, so find the max index. var keyCount = Math.Max(aiChannel.PositionKeyCount, Math.Max(aiChannel.RotationKeyCount, aiChannel.ScalingKeyCount)); // Get all unique keyframe times var times = aiChannel.PositionKeys.Select(k => k.Time) .Union(aiChannel.RotationKeys.Select(k => k.Time)) .Union(aiChannel.ScalingKeys.Select(k => k.Time)) .Distinct().ToList(); // The rest of this loop is almost certainly wrong. Don't trust it. // There's some magical combination, ordering, or transposition we have // to figure out to translate FBX->Assimp->XNA. // Possibilities: matrix offset transform, missing a base transform, an extra base transform, etc. var toBoneSpace = _objectToBone.ContainsKey(aiChannel.NodeName) ? _objectToBone[aiChannel.NodeName] * _skeletonRoot.Transform : _skeletonRoot.Transform; foreach (var aiKeyTime in times) { var time = TimeSpan.FromSeconds(aiKeyTime / aiAnimation.TicksPerSecond); var translation = Matrix4x4.FromTranslation(aiChannel.PositionKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value); var rotation = new Matrix4x4(aiChannel.RotationKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value.GetMatrix()); var scale = Matrix4x4.FromScaling(aiChannel.ScalingKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value); var nodeTransform = translation * rotation * scale; var xform = toBoneSpace * nodeTransform * _globalInverseXform; channel.Add(new AnimationKeyframe(time, ToXna(xform))); } animation.Channels.Add(aiChannel.NodeName, channel); } return(animation); }
} // AddAnimationNodes #endregion #region Process Root Animation /// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> internal static RootAnimationClip ProcessRootAnimation(AnimationContent animation, string name) { List<RootKeyframe> keyframes = new List<RootKeyframe>(); // The root animation is controlling the root of the bones AnimationChannel channel = animation.Channels[name]; // Add the transformations on the root of the model foreach (AnimationKeyframe keyframe in channel) { keyframes.Add(new RootKeyframe((float)(keyframe.Time.TotalSeconds), keyframe.Transform)); } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); #region Key Frame Reduction // We drop key frame data where the bone transformation is equal to the previous key frame. List<RootKeyframe> keyframesReduced = new List<RootKeyframe>(); keyframesReduced.Add(keyframes[0]); for (int i = 1; i < keyframes.Count; i++) { if (keyframes[i - 1].Transform != keyframes[i].Transform) { keyframesReduced.Add(keyframes[i]); } } keyframes = keyframesReduced; // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); #endregion if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); RootKeyframe[] keyframesArray = new RootKeyframe[keyframes.Count]; for (int i = 0; i < keyframes.Count; i++) { keyframesArray[i] = keyframes[i]; } return new RootAnimationClip((float)(animation.Duration.TotalSeconds), keyframesArray); } // ProcessRootAnimation
/// <summary> /// Retrieves and interpolates the pose of an animation channel. /// </summary> /// <param name="animationChannel">Name of the animation channel.</param> /// <param name="animationTime">Current animation clip time.</param> /// <param name="outPose">The output interpolated pose.</param> private void InterpolateChannelPose(AnimationChannel animationChannel, TimeSpan animationTime, out Pose outPose) { if (translationInterpolation == InterpolationMode.None && orientationInterpolation == InterpolationMode.None && scaleInterpolation == InterpolationMode.None) { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); outPose = animationChannel[keyframeIndex].Pose; } else { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); int nextKeyframeIndex = (keyframeIndex + 1) % animationChannel.Count; AnimationChannelKeyframe keyframe1 = animationChannel[keyframeIndex]; AnimationChannelKeyframe keyframe2 = animationChannel[nextKeyframeIndex]; // Calculate the time between the keyframes considering loop long keyframeDuration; if (keyframeIndex == (animationChannel.Count - 1)) { keyframeDuration = animationClip.Duration.Ticks - keyframe1.Time.Ticks; } else { keyframeDuration = keyframe2.Time.Ticks - keyframe1.Time.Ticks; } // Interpolate when duration higher than zero if (keyframeDuration > 0) { long elapsedKeyframeTime = animationTime.Ticks - keyframe1.Time.Ticks; float lerpFactor = elapsedKeyframeTime / (float)keyframeDuration; outPose = Pose.Interpolate(keyframe1.Pose, keyframe2.Pose, lerpFactor, translationInterpolation, orientationInterpolation, scaleInterpolation); } // Otherwise don't interpolate else { outPose = keyframe1.Pose; } } }
public void TestDiscontinuity() { var animationChannel = new AnimationChannel(); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.Zero, Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData <float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 0.0f }); var evaluator = new AnimationChannel.Evaluator(animationChannel.KeyFrames); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(0.0)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(0.999999)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.0)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.000001)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.999999)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.0)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.000001)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.5)), Is.EqualTo(0.0f)); }
private AnimationClip[] ExtractAnimations( AnimationContentDictionary animationDictionary, List <string> boneNameList, ContentProcessorContext context) { if (animationDictionary.Count < 1) { context.Logger.LogImportantMessage("Warning: No animations found."); } AnimationClip[] animations = new AnimationClip[animationDictionary.Count]; int animationCount = 0; foreach (AnimationContent animationContent in animationDictionary.Values) { List <Keyframe> keyframes = new List <Keyframe>(); // Each bone has its own channel foreach (string bone in animationContent.Channels.Keys) { AnimationChannel animationChannel = animationContent.Channels[bone]; int boneIndex = boneNameList.IndexOf(bone); foreach (AnimationKeyframe keyframe in animationChannel) { keyframes.Add(new Keyframe( keyframe.Time, boneIndex, keyframe.Transform)); } } // Sort all animation frames by time keyframes.Sort(); animations[animationCount++] = new AnimationClip( animationContent.Name, animationContent.Duration, keyframes.ToArray()); } return(animations); }
internal static AnimationClip Read(ContentReader input) { string animationName = input.ReadString(); TimeSpan animationDuration = input.ReadObject<TimeSpan>(); // Read animation clip channels Dictionary<string, AnimationChannel> animationChannelDictionary = new Dictionary<string, AnimationChannel>(); int numAnimationChannels = input.ReadInt32(); for (int i = 0; i < numAnimationChannels; i++) { string channelName = input.ReadString(); // Read animation channel keyframes int numChannelKeyframes = input.ReadInt32(); List<AnimationChannelKeyframe> keyframeList = new List<AnimationChannelKeyframe>(numChannelKeyframes); for (int j = 0; j < numChannelKeyframes; j++) { TimeSpan keyframeTime = input.ReadObject<TimeSpan>(); // Read keyframe pose Pose keyframePose; keyframePose.Translation = input.ReadVector3(); keyframePose.Orientation = input.ReadQuaternion(); keyframePose.Scale = input.ReadVector3(); keyframeList.Add(new AnimationChannelKeyframe(keyframeTime, keyframePose)); } AnimationChannel animationChannel = new AnimationChannel(keyframeList); // Add the animation channel to the dictionary animationChannelDictionary.Add(channelName, animationChannel); } return new AnimationClip(animationName, animationDuration, new AnimationChannelDictionary(animationChannelDictionary)); }
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); var indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; var targetObjects = property.serializedObject.targetObjects; var target = property.serializedObject.targetObject; var field = GetField(target.GetType(), property.name); AnimationChannel val = (AnimationChannel)field.GetValue(target); val = (AnimationChannel)EditorGUI.EnumMaskField(position, "Animation channels", val); for (int i = 0; i < targetObjects.Length; i++) { target = targetObjects[i]; field.SetValue(target, val); } EditorGUI.indentLevel = indent; EditorGUI.EndProperty(); }
private AnimationData[] ExtractAnimations(AnimationContentDictionary animationDictionary, List <string> boneNameList, ContentProcessorContext context) { context.Logger.LogImportantMessage("{0} animations found.", animationDictionary.Count); AnimationData[] animations = new AnimationData[animationDictionary.Count]; int count = 0; foreach (AnimationContent animationContent in animationDictionary.Values) { // Store all keyframes of the animation List <Keyframe> keyframes = new List <Keyframe>(); // Go through all animation channels (Each bone has it's own channel) foreach (string animationKey in animationContent.Channels.Keys) { AnimationChannel animationChannel = animationContent.Channels[animationKey]; int boneIndex = boneNameList.IndexOf(animationKey); //context.Logger.LogImportantMessage("{0} - Bone: ", animationKey, boneIndex); foreach (AnimationKeyframe keyframe in animationChannel) { keyframes.Add(new Keyframe(keyframe.Time, boneIndex, keyframe.Transform)); } } context.Logger.LogImportantMessage("Animation {0}: {1} channels found, {2} keyframes found.", animationContent.Name, animationContent.Channels.Count, keyframes.Count); // Sort all animation frames by time keyframes.Sort(); animations[count++] = new AnimationData(animationContent.Name, animationContent.Duration, keyframes.ToArray()); } return(animations); }
public void TestDiscontinuity() { var animationChannel = new AnimationChannel(); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.Zero, Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(1.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 1.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 0.0f }); animationChannel.KeyFrames.Add(new KeyFrameData<float> { Time = CompressedTimeSpan.FromSeconds(2.0), Value = 0.0f }); var evaluator = new AnimationChannel.Evaluator(animationChannel.KeyFrames); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(0.0)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(0.999999)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.0)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.000001)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(1.999999)), Is.EqualTo(1.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.0)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.000001)), Is.EqualTo(0.0f)); Assert.That(evaluator.Evaluate(CompressedTimeSpan.FromSeconds(2.5)), Is.EqualTo(0.0f)); }
List <AnimationContent> CreateAnimations() { var animations = new List <AnimationContent>(); for (int i = 0; i < collada.JointAnimations.Count; i++) { var sourceAnim = collada.JointAnimations[i]; AnimationContent animation = new AnimationContent(); animation.Name = sourceAnim.Name ?? String.Format("Take {0:000}", (i + 1)); animation.Duration = TimeSpan.FromSeconds(sourceAnim.EndTime - sourceAnim.StartTime); foreach (var sourceChannel in sourceAnim.Channels) { AnimationChannel channel = new AnimationChannel(); // Adds the different keyframes to the animation channel // NOTE: Might be better to sample the keyframes foreach (var sourceKeyframe in sourceChannel.Sampler.Keyframes) { TimeSpan time = TimeSpan.FromSeconds(sourceKeyframe.Time); Matrix transform = sourceKeyframe.Transform; AnimationKeyframe keyframe = new AnimationKeyframe(time, transform); channel.Add(keyframe); } String key = GetJointKey(sourceChannel.Target); animation.Channels.Add(key, channel); } animation.OpaqueData.Add("FPS", sourceAnim.FramesPerSecond); animations.Add(animation); } return(animations); }
/// <summary> /// SampleChannel will be called to sample the transformation of a given channel /// at a given time. The given index parameter is for use by the sample function, /// to avoid having to look for the "base" frame each call. It will start out as /// zero in the first call for a given channel. "time" will be monotonically /// increasing for a given channel. /// </summary> /// <param name="achan">The channel to sample from.</param> /// <param name="time">The time to sample at (monotonically increasing).</param> /// <param name="ix">For use by SampleChannel (starts at 0 for each new channel).</param> /// <returns>The sampled keyframe output (allocated by this function).</returns> protected virtual Keyframe SampleChannel(AnimationChannel achan, float time, ref int ix) { Keyframe ret = new Keyframe(); AnimationKeyframe akf0 = achan[ix]; float scale = CalcTransformScale(akf0.Transform); //todo: really should be done in world space, but I'm giving up now float offset = akf0.Transform.Translation.Length(); if (scale > maxScale_) { maxScale_ = scale; } if (offset > maxOffset_) { maxOffset_ = offset; } again: if (ix == achan.Count - 1) { return(KeyframeFromMatrix(akf0.Transform, ret)); } AnimationKeyframe akf1 = achan[ix + 1]; if (akf1.Time.TotalSeconds <= time) { akf0 = akf1; ++ix; goto again; } KeyframeFromMatrix(akf0.Transform, tmpA_); KeyframeFromMatrix(akf1.Transform, tmpB_); Keyframe.Interpolate(tmpA_, tmpB_, (float)((time - akf0.Time.TotalSeconds) / (akf1.Time.TotalSeconds - akf0.Time.TotalSeconds)), ret); return(ret); }
/* * template Animation * { * [...] * } */ /// <summary> /// Fills in all the channels of an animation. Each channel refers to /// a single bone's role in the animation. /// </summary> /// <param name="boneName">The name of the bone associated with the channel</param> /// <returns>The imported animation channel</returns> private AnimationChannel ImportAnimationChannel(out string boneName) { AnimationChannel anim = new AnimationChannel(); // Store the frames in an array, which acts as an intermediate data set // This will allow us to more easily provide support for non Matrix // animation keys at a later time AnimationKeyframe[] rotFrames = null; AnimationKeyframe[] transFrames = null; AnimationKeyframe[] scaleFrames = null; List <AnimationKeyframe> matrixFrames = null; boneName = null; tokens.SkipName(); for (string next = tokens.NextToken(); next != "}"; next = tokens.NextToken()) { // A set of animation keys if (next == "AnimationKey") { // These keys can be rotation (0),scale(1),translation(2), or matrix(3 or 4) keys. int keyType; AnimationKeyframe[] frames = ImportAnimationKey(out keyType); if (keyType == 0) { rotFrames = frames; } else if (keyType == 1) { scaleFrames = frames; } else if (keyType == 2) { transFrames = frames; } else { matrixFrames = new List <AnimationKeyframe>(frames); } } // A possible bone name else if (next == "{") { string token = tokens.NextToken(); if (tokens.NextToken() != "}") { tokens.SkipNode(); } else { boneName = token; } } } // Fill in the channel with the frames if (matrixFrames != null) { matrixFrames.Sort(new Comparison <AnimationKeyframe>(delegate(AnimationKeyframe one, AnimationKeyframe two) { return(one.Time.CompareTo(two.Time)); })); if (matrixFrames[0].Time != TimeSpan.Zero) { matrixFrames.Insert(0, new AnimationKeyframe(new TimeSpan(), matrixFrames[0].Transform)); } for (int i = 0; i < matrixFrames.Count; i++) { Matrix m = matrixFrames[i].Transform; ContentUtil.ReflectMatrix(ref m); matrixFrames[i].Transform = m; anim.Add(matrixFrames[i]); } } else { List <AnimationKeyframe> combinedFrames = ContentUtil.MergeKeyFrames( scaleFrames, transFrames, rotFrames); for (int i = 0; i < combinedFrames.Count; i++) { Matrix m = combinedFrames[i].Transform; ContentUtil.ReflectMatrix(ref m); combinedFrames[i].Transform = m; //* Matrix.CreateRotationX(MathHelper.PiOver2); anim.Add(combinedFrames[i]); } } return(anim); }
public static void Split(AnimationContentDictionary animationDictionary, IList <AnimationSplitDefinition> splits, ContentIdentity contentIdentity, ContentProcessorContext context) { if (splits == null || splits.Count == 0) { return; } if (animationDictionary == null) { return; } if (contentIdentity == null) { throw new ArgumentNullException("contentIdentity"); } if (context == null) { throw new ArgumentNullException("context"); } if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) { context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); } // Get first animation. var originalAnimation = animationDictionary.First().Value; // Clear animation dictionary. - We do not keep the original animations! animationDictionary.Clear(); // Add an animation to animationDictionary for each split. foreach (var split in splits) { TimeSpan startTime = split.StartTime; TimeSpan endTime = split.EndTime; var newAnimation = new AnimationContent { Name = split.Name, Duration = endTime - startTime }; // Process all channels. foreach (var item in originalAnimation.Channels) { string channelName = item.Key; AnimationChannel originalChannel = item.Value; if (originalChannel.Count == 0) { return; } AnimationChannel newChannel = new AnimationChannel(); // Add all key frames to the channel that are in the split interval. foreach (AnimationKeyframe keyFrame in originalChannel) { TimeSpan time = keyFrame.Time; if (startTime <= time && time <= endTime) { newChannel.Add(new AnimationKeyframe(keyFrame.Time - startTime, keyFrame.Transform)); } } // Add channel if it contains key frames. if (newChannel.Count > 0) { newAnimation.Channels.Add(channelName, newChannel); } } if (newAnimation.Channels.Count == 0) { var message = string.Format(CultureInfo.InvariantCulture, "The split animation '{0}' is empty.", split.Name); throw new InvalidContentException(message, contentIdentity); } if (animationDictionary.ContainsKey(split.Name)) { var message = string.Format(CultureInfo.InvariantCulture, "Cannot add split animation '{0}' because an animation with the same name already exits.", split.Name); throw new InvalidContentException(message, contentIdentity); } animationDictionary.Add(split.Name, newAnimation); } }
public Animation_SamplerType(List <string> imageList) { var baseColorTexture = new Texture { Source = UseTexture(imageList, "BaseColor_Cube") }; CommonProperties.Add(new Property(PropertyName.Target, "Rotation")); CommonProperties.Add(new Property(PropertyName.Interpolation, "Linear")); Model CreateModel(DataType outputType) { var properties = new List <Property>(); var cubeMeshPrimitive = MeshPrimitive.CreateCube(); // Apply the common properties to the gltf. cubeMeshPrimitive.Material = new Runtime.Material { PbrMetallicRoughness = new PbrMetallicRoughness { BaseColorTexture = new TextureInfo { Texture = baseColorTexture }, }, }; var node = new Node { Mesh = new Runtime.Mesh { MeshPrimitives = new[] { cubeMeshPrimitive } } }; var channel = new AnimationChannel { Target = new AnimationChannelTarget { Node = node, Path = AnimationChannelTargetPath.Rotation, }, Sampler = new AnimationSampler { Interpolation = AnimationSamplerInterpolation.Linear, Input = Data.Create(new[] { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, }), Output = Data.Create(new[] { Quaternion.CreateFromYawPitchRoll(FloatMath.ToRadians(90.0f), 0.0f, 0.0f), Quaternion.Identity, Quaternion.CreateFromYawPitchRoll(FloatMath.ToRadians(-90.0f), 0.0f, 0.0f), Quaternion.Identity, Quaternion.CreateFromYawPitchRoll(FloatMath.ToRadians(90.0f), 0.0f, 0.0f), }, outputType), }, }; // Apply the properties that are specific to this gltf. properties.Add(new Property(PropertyName.SamplerOutputComponentType, outputType.ToReadmeString())); // Create the gltf object. GLTF gltf = CreateGLTF(() => new Scene { Nodes = new[] { node }, }); gltf.Animations = new[] { new Animation { Channels = new List <AnimationChannel> { channel } } }; return(new Model { Properties = properties, GLTF = gltf, Animated = true, }); } Models = new List <Model> { CreateModel(DataType.Float), CreateModel(DataType.NormalizedByte), CreateModel(DataType.NormalizedShort), }; GenerateUsedPropertiesList(); }
public static void Split(AnimationContentDictionary animationDictionary, IList<AnimationSplitDefinition> splits, ContentIdentity contentIdentity, ContentProcessorContext context) { if (splits == null || splits.Count == 0) return; if (animationDictionary == null) return; if (contentIdentity == null) throw new ArgumentNullException("contentIdentity"); if (context == null) throw new ArgumentNullException("context"); if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); // Get first animation. var originalAnimation = animationDictionary.First().Value; // Clear animation dictionary. - We do not keep the original animations! animationDictionary.Clear(); // Add an animation to animationDictionary for each split. foreach (var split in splits) { TimeSpan startTime = split.StartTime; TimeSpan endTime = split.EndTime; var newAnimation = new AnimationContent { Name = split.Name, Duration = endTime - startTime }; // Process all channels. foreach (var item in originalAnimation.Channels) { string channelName = item.Key; AnimationChannel originalChannel = item.Value; if (originalChannel.Count == 0) return; AnimationChannel newChannel = new AnimationChannel(); // Add all key frames to the channel that are in the split interval. foreach (AnimationKeyframe keyFrame in originalChannel) { TimeSpan time = keyFrame.Time; if (startTime <= time && time <= endTime) { newChannel.Add(new AnimationKeyframe(keyFrame.Time - startTime, keyFrame.Transform)); } } // Add channel if it contains key frames. if (newChannel.Count > 0) newAnimation.Channels.Add(channelName, newChannel); } if (newAnimation.Channels.Count == 0) { var message = string.Format(CultureInfo.InvariantCulture, "The split animation '{0}' is empty.", split.Name); throw new InvalidContentException(message, contentIdentity); } if (animationDictionary.ContainsKey(split.Name)) { var message = string.Format(CultureInfo.InvariantCulture, "Cannot add split animation '{0}' because an animation with the same name already exits.", split.Name); throw new InvalidContentException(message, contentIdentity); } animationDictionary.Add(split.Name, newAnimation); } }
/// <summary> /// SampleChannel will be called to sample the transformation of a given channel /// at a given Clock. The given index parameter is for use by the sample function, /// to avoid having to look for the "base" frame each call. It will start out as /// zero in the first call for a given channel. "Clock" will be monotonically /// increasing for a given channel. /// </summary> /// <param name="achan">The channel to sample from.</param> /// <param name="Clock">The Clock to sample at (monotonically increasing).</param> /// <param name="ix">For use by SampleChannel (starts at 0 for each new channel).</param> /// <returns>The sampled keyframe output (allocated by this function).</returns> protected virtual Keyframe SampleChannel(AnimationChannel achan, float time, ref int ix) { Keyframe ret = new Keyframe(); AnimationKeyframe akf0 = achan[ix]; float scale = CalcTransformScale(akf0.Transform); //todo: really should be done in world space, but I'm giving up now float offset = akf0.Transform.Translation.Length(); if (scale > maxScale_) maxScale_ = scale; if (offset > maxOffset_) maxOffset_ = offset; again: if (ix == achan.Count - 1) return KeyframeFromMatrix(akf0.Transform, ret); AnimationKeyframe akf1 = achan[ix+1]; if (akf1.Time.TotalSeconds <= time) { akf0 = akf1; ++ix; goto again; } KeyframeFromMatrix(akf0.Transform, tmpA_); KeyframeFromMatrix(akf1.Transform, tmpB_); Keyframe.Interpolate(tmpA_, tmpB_, (float)((time - akf0.Time.TotalSeconds) / (akf1.Time.TotalSeconds - akf0.Time.TotalSeconds)), ret); return ret; }
/// <summary> /// The workhorse of the animation processor. It loops through all /// animations, all tracks, and all keyframes, and converts to the format /// expected by the runtime animation classes. /// </summary> /// <param name="input">The NodeContent to process. Comes from the base ModelProcessor.</param> /// <param name="output">The ModelContent that was produced. You don't typically change this.</param> /// <param name="context">The build context (logger, etc).</param> /// <returns>An allocated AnimationSet with the animations to include.</returns> public virtual AnimationSet BuildAnimationSet(NodeContent input, ref ModelContent output, ContentProcessorContext context) { AnimationSet ret = new AnimationSet(); if (!DoAnimations) { context.Logger.LogImportantMessage("DoAnimation is set to false for {0}; not generating animations.", input.Name); return(ret); } // go from name to index Dictionary <string, ModelBoneContent> nameToIndex = new Dictionary <string, ModelBoneContent>(); foreach (ModelBoneContent mbc in output.Bones) { nameToIndex.Add(GetBoneName(mbc), mbc); } AnimationContentDictionary adict = MergeAnimatedBones(input); if (adict == null || adict.Count == 0) { context.Logger.LogWarning("http://kwxport.sourceforge.net/", input.Identity, "Model processed with AnimationProcessor has no animations."); return(ret); } foreach (AnimationContent ac in adict.Values) { if (!IncludeAnimation(ac)) { context.Logger.LogImportantMessage(String.Format("Not including animation named {0}.", ac.Name)); continue; } context.Logger.LogImportantMessage( "Processing animation {0} duration {1} sample rate {2} reduction tolerance {3}.", ac.Name, ac.Duration, SampleRate, Tolerance); AnimationChannelDictionary acdict = ac.Channels; AnimationTrackDictionary tracks = new AnimationTrackDictionary(); foreach (string name in acdict.Keys) { if (!IncludeTrack(ac, name)) { context.Logger.LogImportantMessage(String.Format("Not including track named {0}.", name)); continue; } int ix = 0; AnimationChannel achan = acdict[name]; int bix = nameToIndex[name].Index; context.Logger.LogMessage("Processing bone {0}:{1}.", name, bix); AnimationTrack at; if (tracks.TryGetValue(bix, out at)) { throw new System.ArgumentException( String.Format("Bone index {0} is used by multiple animations in the same clip (name {1}).", bix, name)); } // Sample at given frame rate from 0 .. Duration List <Keyframe> kfl = new List <Keyframe>(); int nFrames = (int)Math.Floor(ac.Duration.TotalSeconds * SampleRate + 0.5); for (int i = 0; i < nFrames; ++i) { Keyframe k = SampleChannel(achan, i / SampleRate, ref ix); kfl.Add(k); } // Run keyframe elimitation Keyframe[] frames = kfl.ToArray(); int nReduced = 0; if (tolerance_ > 0) { nReduced = ReduceKeyframes(frames, tolerance_); } if (nReduced > 0) { context.Logger.LogMessage("Reduced '{2}' from {0} to {1} frames.", frames.Length, frames.Length - nReduced, name); } // Create an AnimationTrack at = new AnimationTrack(bix, frames); tracks.Add(bix, at); } Animation a = new Animation(ac.Name, tracks, SampleRate); ret.AddAnimation(a); } // build the special "identity" and "bind pose" animations AnimationTrackDictionary atd_id = new AnimationTrackDictionary(); AnimationTrackDictionary atd_bind = new AnimationTrackDictionary(); foreach (KeyValuePair <string, ModelBoneContent> nip in nameToIndex) { Keyframe[] frames_id = new Keyframe[2]; frames_id[0] = new Keyframe(); frames_id[1] = new Keyframe(); AnimationTrack at_id = new AnimationTrack(nip.Value.Index, frames_id); atd_id.Add(nip.Value.Index, at_id); Keyframe[] frames_bind = new Keyframe[2]; Matrix mat = nip.Value.Transform; frames_bind[0] = Keyframe.CreateFromMatrix(mat); frames_bind[1] = new Keyframe(); frames_bind[1].CopyFrom(frames_bind[0]); AnimationTrack at_bind = new AnimationTrack(nip.Value.Index, frames_bind); atd_bind.Add(nip.Value.Index, at_bind); } ret.AddAnimation(new Animation("$id$", atd_id, 1.0f)); ret.AddAnimation(new Animation("$bind$", atd_bind, 1.0f)); return(ret); }
/// <summary> /// Retrieves and interpolates the pose of an animation channel. /// </summary> /// <param name="animationChannel">Name of the animation channel.</param> /// <param name="animationTime">Current animation clip time.</param> /// <param name="outPose">The output interpolated pose.</param> private void InterpolateChannelPose(AnimationChannel animationChannel, TimeSpan animationTime, out Pose outPose) { if (translationInterpolation == InterpolationMode.None && orientationInterpolation == InterpolationMode.None && scaleInterpolation == InterpolationMode.None) { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); outPose = animationChannel[keyframeIndex].Pose; } else { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); int nextKeyframeIndex; // If we are looping then the next frame may wrap around to // the beginning. If not we should just clamp it at the last frame if (loopEnabled) { nextKeyframeIndex = (keyframeIndex + 1) % animationChannel.Count; } else { nextKeyframeIndex = Math.Min(keyframeIndex + 1, animationChannel.Count - 1); } AnimationChannelKeyframe keyframe1 = animationChannel[keyframeIndex]; AnimationChannelKeyframe keyframe2 = animationChannel[nextKeyframeIndex]; // Calculate the time between the keyframes considering loop long keyframeDuration; if (keyframeIndex == (animationChannel.Count - 1)) keyframeDuration = animationClip.Duration.Ticks - keyframe1.Time.Ticks; else keyframeDuration = keyframe2.Time.Ticks - keyframe1.Time.Ticks; // Interpolate when duration higher than zero if (keyframeDuration > 0) { long elapsedKeyframeTime = animationTime.Ticks - keyframe1.Time.Ticks; float lerpFactor = elapsedKeyframeTime / (float)keyframeDuration; outPose = Pose.Interpolate(keyframe1.Pose, keyframe2.Pose, lerpFactor, translationInterpolation, orientationInterpolation, scaleInterpolation); } // Otherwise don't interpolate else outPose = keyframe1.Pose; } }
internal override void Reset(bool newUsage, bool keepStored) { base.Reset(newUsage, keepStored); readFrameIndex = -1; currentFrameTime = 0; previousFrameTime = 0; if (channels != null) { for (int i = 0; i < channels.Length; i++) { if (!keepStored) { channels[i] = new AnimationChannel(); channels[i].storeTransform = Transform.Identity; } channels[i].frameReader = new CompressedTransformReader(); channels[i].lerpedTransform = Transform.Identity; channels[i].sourceData = animation.GetBoneCompressedTransformData(i); channels[i].sourceTransformData = animation.GetBoneDecompressedTransformData(i); channels[i].boneIndex = animation.BoneIndices[i]; channels[i].readIndex = 0; } } }
/// <summary> /// Called when an XML document is read that specifies how animations /// should be split. /// </summary> /// <param name="animDict">The dictionary of animation name/AnimationContent /// pairs. </param> /// <param name="doc">The Xml document that contains info on how to split /// the animations.</param> protected virtual void SubdivideAnimations( AnimationContentDictionary animDict, XmlDocument doc) { string[] animNames = new string[animDict.Keys.Count]; animDict.Keys.CopyTo(animNames, 0); if (animNames.Length == 0) { return; } // Traverse each xml node that represents an animation to be subdivided foreach (XmlNode node in doc) { XmlElement child = node as XmlElement; if (child == null || child.Name != "animation") { continue; } string animName = null; if (child["name"] != null) { // The name of the animation to be split animName = child["name"].InnerText; } else if (child["index"] != null) { animName = animNames[int.Parse(child["index"].InnerText)]; } else { animName = animNames[0]; } // If the tickspersecond node is filled, use that to calculate seconds per tick double animTicksPerSecond = 1.0, secondsPerTick = 0; try { if (child["tickspersecond"] != null) { animTicksPerSecond = double.Parse(child["tickspersecond"].InnerText); } } catch { throw new Exception("Error parsing tickspersecond in xml file."); } if (animTicksPerSecond <= 0) { throw new InvalidDataException("AnimTicksPerSecond in XML file must be " + "a positive number."); } secondsPerTick = 1.0 / animTicksPerSecond; AnimationContent anim = null; // Get the animation and remove it from the dict // Check to see if the animation specified in the xml file exists try { anim = animDict[animName]; } catch { throw new Exception("Animation named " + animName + " specified in XML file does not exist in model."); } animDict.Remove(anim.Name); // Get the list of new animations XmlNodeList subAnimations = child.GetElementsByTagName("animationsubset"); foreach (XmlElement subAnim in subAnimations) { // Create the new sub animation AnimationContent newAnim = new AnimationContent(); XmlElement subAnimNameElement = subAnim["name"]; if (subAnimNameElement != null) { newAnim.Name = subAnimNameElement.InnerText; } // If a starttime node exists, use that to get the start time long startTime, endTime; if (subAnim["starttime"] != null) { try { startTime = TimeSpan.FromSeconds(double.Parse(subAnim["starttime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing starttime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["startframe"] != null)// else use the secondspertick combined with the startframe node value { try { double seconds = double.Parse(subAnim["startframe"].InnerText) * secondsPerTick; startTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing startframe node in XML file. Node inner text " + "must be a non negative number."); } } else { throw new Exception("Sub animation in XML file must have either a starttime or startframe node."); } // Same with endtime/endframe if (subAnim["endtime"] != null) { try { endTime = TimeSpan.FromSeconds(double.Parse(subAnim["endtime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing endtime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["endframe"] != null) { try { double seconds = double.Parse(subAnim["endframe"].InnerText) * secondsPerTick; endTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing endframe node in XML file. Node inner text " + "must be a non negative number."); } } else { throw new Exception("Sub animation in XML file must have either an endtime or endframe node."); } if (endTime < startTime) { throw new Exception("Start time must be <= end time in XML file."); } // Now that we have the start and end times, we associate them with // start and end indices for each animation track/channel foreach (KeyValuePair <string, AnimationChannel> k in anim.Channels) { // The current difference between the start time and the // time at the current index long currentStartDiff; // The current difference between the end time and the // time at the current index long currentEndDiff; // The difference between the start time and the time // at the start index long bestStartDiff = long.MaxValue; // The difference between the end time and the time at // the end index long bestEndDiff = long.MaxValue; // The start and end indices int startIndex = -1; int endIndex = -1; // Create a new channel and reference the old channel AnimationChannel newChan = new AnimationChannel(); AnimationChannel oldChan = k.Value; // Iterate through the keyframes in the channel for (int i = 0; i < oldChan.Count; i++) { // Update the startIndex, endIndex, bestStartDiff, // and bestEndDiff long ticks = oldChan[i].Time.Ticks; currentStartDiff = Math.Abs(startTime - ticks); currentEndDiff = Math.Abs(endTime - ticks); if (startIndex == -1 || currentStartDiff < bestStartDiff) { startIndex = i; bestStartDiff = currentStartDiff; } if (endIndex == -1 || currentEndDiff < bestEndDiff) { endIndex = i; bestEndDiff = currentEndDiff; } } // Now we have our start and end index for the channel for (int i = startIndex; i <= endIndex; i++) { AnimationKeyframe frame = oldChan[i]; long time; // Clamp the time so that it can't be less than the // start time if (frame.Time.Ticks < startTime) { time = 0; } // Clamp the time so that it can't be greater than the // end time else if (frame.Time.Ticks > endTime) { time = endTime - startTime; } else // Else get the time { time = frame.Time.Ticks - startTime; } // Finally... create the new keyframe and add it to the new channel AnimationKeyframe keyframe = new AnimationKeyframe( TimeSpan.FromTicks(time), frame.Transform); newChan.Add(keyframe); } // Add the channel and update the animation duration based on the // length of the animation track. newAnim.Channels.Add(k.Key, newChan); if (newChan[newChan.Count - 1].Time > newAnim.Duration) { newAnim.Duration = newChan[newChan.Count - 1].Time; } } try { // Add the subdived animation to the dictionary. animDict.Add(newAnim.Name, newAnim); } catch { throw new Exception("Attempt to add an animation when one by the same name already exists. " + "Name: " + newAnim.Name); } } } }
private void _exportAnimation(UnityEngine.AnimationClip animationClip) { // var frameCount = (int)Math.Floor(animationClip.length * animationClip.frameRate) + 1; var curveBinds = UnityEditor.AnimationUtility.GetCurveBindings(animationClip); var ignoreCurves = new List <UnityEditor.EditorCurveBinding>(); var glTFAnimation = new GLTF.Schema.Animation() { Name = animationClip.name, Channels = new List <AnimationChannel>(), Samplers = new List <AnimationSampler>(), Extensions = new Dictionary <string, IExtension>() { { AnimationExtensionFactory.EXTENSION_NAME, new AnimationExtension() { frameRate = animationClip.frameRate, clips = new List <AnimationClip>() { new AnimationClip() { name = animationClip.name, playTimes = _getPlayTimes(animationClip), position = 0.0f, duration = (float)Math.Round(animationClip.length, 6), } } } }, }, }; var ext = glTFAnimation.Extensions[AnimationExtensionFactory.EXTENSION_NAME] as AnimationExtension; this._root.Animations.Add(glTFAnimation); // Input. var inputAccessor = new Accessor(); inputAccessor.Count = frameCount; inputAccessor.Type = GLTFAccessorAttributeType.SCALAR; inputAccessor.ComponentType = GLTFComponentType.Float; inputAccessor.BufferView = new BufferViewId { Id = 0, Root = _root }; this._root.Accessors.Add(inputAccessor); // Write input. for (var i = 0; i < frameCount; ++i) { //_bufferWriter.Write(Math.Round(Math.Min(animationClip.length * i / (frameCount - 1), animationClip.length), 6)); // TODO _bufferWriter.Write(i / animationClip.frameRate); } var MainTex_STy = new List <float>(); foreach (var curveBind in curveBinds) { // Curve has been parsed. if (ignoreCurves.Contains(curveBind)) { continue; } // No target. var animationTarget = _target.Find(curveBind.path); if (animationTarget == null) { continue; } // Create node. var nodeIndex = _animationTargets.IndexOf(animationTarget); if (nodeIndex < 0) { _animationTargets.Add(animationTarget); nodeIndex = _root.Nodes.Count; _root.Nodes.Add(new Node() { Name = _target == animationTarget ? "__root__" : animationTarget.name, }); if (animationTarget.transform.parent == _target) { _root.Scenes[0].Nodes.Add( new NodeId() { Id = nodeIndex, Root = _root, } ); } } // Output. var outputAccessor = new Accessor(); outputAccessor.Count = frameCount; outputAccessor.ComponentType = GLTFComponentType.Float; outputAccessor.BufferView = inputAccessor.BufferView; outputAccessor.ByteOffset = (int)_bufferWriter.BaseStream.Position; this._root.Accessors.Add(outputAccessor); // var animationSampler = new AnimationSampler() { Input = new AccessorId() { Id = this._root.Accessors.IndexOf(inputAccessor), Root = _root, }, Interpolation = InterpolationType.LINEAR, Output = new AccessorId() { Id = this._root.Accessors.IndexOf(outputAccessor), Root = _root, }, }; glTFAnimation.Samplers.Add(animationSampler); // var animationChannel = new AnimationChannel() { Sampler = new SamplerId() { Id = glTFAnimation.Samplers.IndexOf(animationSampler), Root = _root, }, Target = new AnimationChannelTarget() { Node = new NodeId() { Id = nodeIndex, Root = _root, } } }; glTFAnimation.Channels.Add(animationChannel); if (curveBind.type == typeof(Transform)) { var curveGroup = _getCurveGroup(curveBinds, curveBind); ignoreCurves.AddRange(curveGroup); switch (curveBind.propertyName) { case "m_LocalPosition.x": case "m_LocalPosition.y": case "m_LocalPosition.z": animationChannel.Target.Path = GLTFAnimationChannelPath.translation; outputAccessor.Type = GLTFAccessorAttributeType.VEC3; for (var i = 0; i < frameCount; ++i) { var time = i / animationClip.frameRate; var curveX = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[0]); var curveY = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[1]); var curveZ = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[2]); var value = curveX != null?curveX.Evaluate(time) : animationTarget.transform.localPosition.x; _bufferWriter.Write(value); value = curveY != null?curveY.Evaluate(time) : animationTarget.transform.localPosition.y; _bufferWriter.Write(value); value = curveZ != null?curveZ.Evaluate(time) : animationTarget.transform.localPosition.z; _bufferWriter.Write(value); } break; case "m_LocalRotation.x": case "m_LocalRotation.y": case "m_LocalRotation.z": case "m_LocalRotation.w": animationChannel.Target.Path = GLTFAnimationChannelPath.rotation; outputAccessor.Type = GLTFAccessorAttributeType.VEC4; for (var i = 0; i < frameCount; ++i) { var time = i / animationClip.frameRate; var curveX = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[0]); var curveY = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[1]); var curveZ = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[2]); var curveW = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[3]); var valueX = curveX != null?curveX.Evaluate(time) : animationTarget.transform.localRotation.x; var valueY = curveY != null?curveY.Evaluate(time) : animationTarget.transform.localRotation.y; var valueZ = curveZ != null?curveZ.Evaluate(time) : animationTarget.transform.localRotation.z; var valueW = curveW != null?curveW.Evaluate(time) : animationTarget.transform.localRotation.w; _bufferWriter.Write(valueX); _bufferWriter.Write(valueY); _bufferWriter.Write(valueZ); _bufferWriter.Write(valueW); } break; case "localEulerAnglesRaw.x": case "localEulerAnglesRaw.y": case "localEulerAnglesRaw.z": animationChannel.Target.Path = GLTFAnimationChannelPath.rotation; outputAccessor.Type = GLTFAccessorAttributeType.VEC4; for (var i = 0; i < frameCount; ++i) { var time = i / animationClip.frameRate; var curveX = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[0]); var curveY = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[1]); var curveZ = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[2]); var valueX = curveX != null?curveX.Evaluate(time) : animationTarget.transform.localEulerAngles.x; var valueY = curveY != null?curveY.Evaluate(time) : animationTarget.transform.localEulerAngles.y; var valueZ = curveZ != null?curveZ.Evaluate(time) : animationTarget.transform.localEulerAngles.z; var quaternion = Quaternion.Euler(valueX, valueY, valueZ); _bufferWriter.Write(quaternion.x); _bufferWriter.Write(quaternion.y); _bufferWriter.Write(quaternion.z); _bufferWriter.Write(quaternion.w); } break; case "m_LocalScale.x": case "m_LocalScale.y": case "m_LocalScale.z": animationChannel.Target.Path = GLTFAnimationChannelPath.scale; outputAccessor.Type = GLTFAccessorAttributeType.VEC3; for (var i = 0; i < frameCount; ++i) { var time = i / animationClip.frameRate; var curveX = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[0]); var curveY = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[1]); var curveZ = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveGroup[2]); var value = curveX != null?curveX.Evaluate(time) : animationTarget.transform.localScale.x; _bufferWriter.Write(value); value = curveY != null?curveY.Evaluate(time) : animationTarget.transform.localScale.y; _bufferWriter.Write(value); value = curveZ != null?curveZ.Evaluate(time) : animationTarget.transform.localScale.z; _bufferWriter.Write(value); } break; } } else { animationChannel.Target.Path = GLTFAnimationChannelPath.custom; outputAccessor.Type = GLTFAccessorAttributeType.SCALAR; var type = ""; var property = ""; var uri = ""; var needUpdate = -1; if (curveBind.type == typeof(GameObject)) { type = "paper.GameObject"; switch (curveBind.propertyName) { case "m_IsActive": property = "activeSelf"; animationSampler.Interpolation = InterpolationType.STEP; break; } // for (var i = 0; i < frameCount; ++i) // TODO // { // var time = animationClip.length * i / frameCountSO; // TODO // var curve = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveBind); // var value = curve.Evaluate(time); // _bufferWriter.Write(value); // } } else if (curveBind.type == typeof(UnityEngine.MeshRenderer)) { type = "egret3d.MeshRenderer"; uri = "materials/0/$/_uvTransform"; needUpdate = 1; // animationSampler.Interpolation = InterpolationType.STEP; switch (curveBind.propertyName) { case "material._MainTex_ST.z": property = "0"; break; case "material._MainTex_ST.w": property = "1"; break; case "material._MainTex_ST.x": property = "2"; break; case "material._MainTex_ST.y": property = "3"; break; } } else { Debug.Log("Unknown type and property." + curveBind.type.ToString() + curveBind.propertyName); } // Extensions. animationChannel.Extensions = new Dictionary <string, IExtension>() { { AnimationExtensionFactory.EXTENSION_NAME, new AnimationChannelExtension() { type = type, property = property, uri = uri, needUpdate = needUpdate, } }, }; for (var i = 0; i < frameCount; ++i) { var curve = UnityEditor.AnimationUtility.GetEditorCurve(animationClip, curveBind); if (curve != null) { var value = curve.Evaluate(i / animationClip.frameRate); if (curveBind.propertyName == "material._MainTex_ST.w") { if (i < MainTex_STy.Count) { _bufferWriter.Write(1.0f - value - MainTex_STy[i]); } else { _bufferWriter.Write(value); } } else { _bufferWriter.Write(value); if (curveBind.propertyName == "material._MainTex_ST.y") { MainTex_STy.Add(value); } } } } } } foreach (var evt in animationClip.events) { var glTFFrameEvent = new AnimationFrameEvent(); glTFFrameEvent.name = evt.functionName; glTFFrameEvent.position = evt.time; glTFFrameEvent.intVariable = evt.intParameter; glTFFrameEvent.floatVariable = evt.floatParameter; glTFFrameEvent.stringVariable = evt.stringParameter; ext.events.Add(glTFFrameEvent); } ext.events.Sort(); }
public Buffer_Misc(List <string> imageList) { Model CreateModel(Action <List <Property>, AnimationChannel, Node> setProperties) { var properties = new List <Property>(); // Apply the common properties to the glTF. var node = new Node { Mesh = new Runtime.Mesh { MeshPrimitives = new[] { MeshPrimitive.CreateSinglePlane(includeTextureCoords: false) } }, Scale = new Vector3(0.8f) }; var channel = new AnimationChannel(); // Apply the proerties that are specific to this glTF setProperties(properties, channel, node); // Create the glTF object GLTF gltf = CreateGLTF(() => new Scene { Nodes = new[] { node }, }); gltf.Animations = new[] { new Animation { Channels = new List <AnimationChannel> { channel } } }; return(new Model { Properties = properties, GLTF = gltf, Animated = true, SeparateBuffers = true, }); } void setTranslationChanneltarget(AnimationChannel channel, Node node) { channel.Target = new AnimationChannelTarget { Node = node, Path = AnimationChannelTargetPath.Translation, }; } void SetLinearSamplerForTranslation(AnimationChannel channel) { channel.Sampler = new AnimationSampler { Interpolation = AnimationSamplerInterpolation.Linear, Input = Data.Create(new[] { 0.0f, 2.0f, 4.0f, }), Output = Data.Create(new[] { new Vector3(-0.1f, 0.0f, 0.0f), new Vector3(0.1f, 0.0f, 0.0f), new Vector3(-0.1f, 0.0f, 0.0f), }), }; } Models = new List <Model> { CreateModel((properties, channel, node) => { setTranslationChanneltarget(channel, node); SetLinearSamplerForTranslation(channel); properties.Add(new Property(PropertyName.Description, "The mesh primitive and animation data are stored in separate buffers.")); }) }; GenerateUsedPropertiesList(); }
public static bool Cross(AnimationChannel a, AnimationChannel b) { return((a & b) != 0); }
/// <summary> /// Converts the specified animation to XNA. /// </summary> /// <param name="aiAnimation">The animation.</param> /// <returns>The animation converted to XNA.</returns> private AnimationContent ImportAnimation(Animation aiAnimation) { var animation = new AnimationContent { Name = GetAnimationName(aiAnimation.Name), Identity = _identity, Duration = TimeSpan.FromSeconds(aiAnimation.DurationInTicks / aiAnimation.TicksPerSecond) }; // In Assimp animation channels may be split into separate channels. // "nodeXyz" --> "nodeXyz_$AssimpFbx$_Translation", // "nodeXyz_$AssimpFbx$_Rotation", // "nodeXyz_$AssimpFbx$_Scaling" // Group animation channels by name (strip the "_$AssimpFbx$" part). var channelGroups = aiAnimation.NodeAnimationChannels .GroupBy(channel => GetNodeName(channel.NodeName)); foreach (var channelGroup in channelGroups) { var boneName = channelGroup.Key; var channel = new AnimationChannel(); // Get transformation pivot for current bone. FbxPivot pivot; if (!_pivots.TryGetValue(boneName, out pivot)) { pivot = FbxPivot.Default; } var scaleKeys = EmptyVectorKeys; var rotationKeys = EmptyQuaternionKeys; var translationKeys = EmptyVectorKeys; foreach (var aiChannel in channelGroup) { if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Scaling")) { scaleKeys = aiChannel.ScalingKeys; Debug.Assert(pivot.Scaling.HasValue); Debug.Assert(!aiChannel.HasRotationKeys || (aiChannel.RotationKeyCount == 1 && (aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(1, 0, 0, 0) || aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(0, 0, 0, 0)))); Debug.Assert(!aiChannel.HasPositionKeys || (aiChannel.PositionKeyCount == 1 && aiChannel.PositionKeys[0].Value == new Vector3D(0, 0, 0))); } else if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Rotation")) { rotationKeys = aiChannel.RotationKeys; Debug.Assert(pivot.Rotation.HasValue); Debug.Assert(!aiChannel.HasScalingKeys || (aiChannel.ScalingKeyCount == 1 && aiChannel.ScalingKeys[0].Value == new Vector3D(1, 1, 1))); Debug.Assert(!aiChannel.HasPositionKeys || (aiChannel.PositionKeyCount == 1 && aiChannel.PositionKeys[0].Value == new Vector3D(0, 0, 0))); } else if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Translation")) { translationKeys = aiChannel.PositionKeys; Debug.Assert(pivot.Translation.HasValue); Debug.Assert(!aiChannel.HasScalingKeys || (aiChannel.ScalingKeyCount == 1 && aiChannel.ScalingKeys[0].Value == new Vector3D(1, 1, 1))); Debug.Assert(!aiChannel.HasRotationKeys || (aiChannel.RotationKeyCount == 1 && (aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(1, 0, 0, 0) || aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(0, 0, 0, 0)))); } else { scaleKeys = aiChannel.ScalingKeys; rotationKeys = aiChannel.RotationKeys; translationKeys = aiChannel.PositionKeys; } } // Get all unique keyframe times. (Assuming that no two key frames // have the same time, which is usually a safe assumption.) var times = scaleKeys.Select(k => k.Time) .Union(rotationKeys.Select(k => k.Time)) .Union(translationKeys.Select(k => k.Time)) .OrderBy(t => t) .ToList(); Debug.Assert(times.Count == times.Distinct().Count(), "Sequences combined with Union() should not have duplicates."); int prevScaleIndex = -1; int prevRotationIndex = -1; int prevTranslationIndex = -1; double prevScaleTime = 0.0; double prevRotationTime = 0.0; double prevTranslationTime = 0.0; Vector3? prevScale = null; Quaternion?prevRotation = null; Vector3? prevTranslation = null; foreach (var time in times) { // Get scaling. Vector3?scale; int scaleIndex = scaleKeys.FindIndex(k => k.Time == time); if (scaleIndex != -1) { // Scaling key found. scale = ToXna(scaleKeys[scaleIndex].Value); prevScaleIndex = scaleIndex; prevScaleTime = time; prevScale = scale; } else { // No scaling key found. if (prevScaleIndex != -1 && prevScaleIndex + 1 < scaleKeys.Count) { // Lerp between previous and next scaling key. var nextScaleKey = scaleKeys[prevScaleIndex + 1]; var nextScaleTime = nextScaleKey.Time; var nextScale = ToXna(nextScaleKey.Value); var amount = (float)((time - prevScaleTime) / (nextScaleTime - prevScaleTime)); scale = Vector3.Lerp(prevScale.Value, nextScale, amount); } else { // Hold previous scaling value. scale = prevScale; } } // Get rotation. Quaternion?rotation; int rotationIndex = rotationKeys.FindIndex(k => k.Time == time); if (rotationIndex != -1) { // Rotation key found. rotation = ToXna(rotationKeys[rotationIndex].Value); prevRotationIndex = rotationIndex; prevRotationTime = time; prevRotation = rotation; } else { // No rotation key found. if (prevRotationIndex != -1 && prevRotationIndex + 1 < rotationKeys.Count) { // Lerp between previous and next rotation key. var nextRotationKey = rotationKeys[prevRotationIndex + 1]; var nextRotationTime = nextRotationKey.Time; var nextRotation = ToXna(nextRotationKey.Value); var amount = (float)((time - prevRotationTime) / (nextRotationTime - prevRotationTime)); rotation = Quaternion.Slerp(prevRotation.Value, nextRotation, amount); } else { // Hold previous rotation value. rotation = prevRotation; } } // Get translation. Vector3?translation; int translationIndex = translationKeys.FindIndex(k => k.Time == time); if (translationIndex != -1) { // Translation key found. translation = ToXna(translationKeys[translationIndex].Value); prevTranslationIndex = translationIndex; prevTranslationTime = time; prevTranslation = translation; } else { // No translation key found. if (prevTranslationIndex != -1 && prevTranslationIndex + 1 < translationKeys.Count) { // Lerp between previous and next translation key. var nextTranslationKey = translationKeys[prevTranslationIndex + 1]; var nextTranslationTime = nextTranslationKey.Time; var nextTranslation = ToXna(nextTranslationKey.Value); var amount = (float)((time - prevTranslationTime) / (nextTranslationTime - prevTranslationTime)); translation = Vector3.Lerp(prevTranslation.Value, nextTranslation, amount); } else { // Hold previous translation value. translation = prevTranslation; } } // Apply transformation pivot. var transform = Matrix.Identity; if (pivot.GeometricScaling.HasValue) { transform *= pivot.GeometricScaling.Value; } if (pivot.GeometricRotation.HasValue) { transform *= pivot.GeometricRotation.Value; } if (pivot.GeometricTranslation.HasValue) { transform *= pivot.GeometricTranslation.Value; } if (pivot.ScalingPivotInverse.HasValue) { transform *= pivot.ScalingPivotInverse.Value; } if (scale.HasValue) { transform *= Matrix.CreateScale(scale.Value); } else if (pivot.Scaling.HasValue) { transform *= pivot.Scaling.Value; } if (pivot.ScalingPivot.HasValue) { transform *= pivot.ScalingPivot.Value; } if (pivot.ScalingOffset.HasValue) { transform *= pivot.ScalingOffset.Value; } if (pivot.RotationPivotInverse.HasValue) { transform *= pivot.RotationPivotInverse.Value; } if (pivot.PostRotation.HasValue) { transform *= pivot.PostRotation.Value; } if (rotation.HasValue) { transform *= Matrix.CreateFromQuaternion(rotation.Value); } else if (pivot.Rotation.HasValue) { transform *= pivot.Rotation.Value; } if (pivot.PreRotation.HasValue) { transform *= pivot.PreRotation.Value; } if (pivot.RotationPivot.HasValue) { transform *= pivot.RotationPivot.Value; } if (pivot.RotationOffset.HasValue) { transform *= pivot.RotationOffset.Value; } if (translation.HasValue) { transform *= Matrix.CreateTranslation(translation.Value); } else if (pivot.Translation.HasValue) { transform *= pivot.Translation.Value; } channel.Add(new AnimationKeyframe(TimeSpan.FromSeconds(time / aiAnimation.TicksPerSecond), transform)); } animation.Channels[channelGroup.Key] = channel; } return(animation); }
/// <summary> /// Called when an XML document is read that specifies how animations /// should be split. /// </summary> /// <param name="animDict">The dictionary of animation name/AnimationContent /// pairs. </param> /// <param name="doc">The Xml document that contains info on how to split /// the animations.</param> protected virtual void SubdivideAnimations( AnimationContentDictionary animDict, XmlDocument doc) { string[] animNames = new string[animDict.Keys.Count]; animDict.Keys.CopyTo(animNames, 0); if (animNames.Length == 0) return; // Traverse each xml node that represents an animation to be subdivided foreach (XmlNode node in doc) { XmlElement child = node as XmlElement; if (child == null || child.Name != "animation") continue; string animName = null; if (child["name"] != null) { // The name of the animation to be split animName = child["name"].InnerText; } else if (child["index"] != null) { animName = animNames[int.Parse(child["index"].InnerText)]; } else { animName = animNames[0]; } // If the tickspersecond node is filled, use that to calculate seconds per tick double animTicksPerSecond = 1.0, secondsPerTick = 0; try { if (child["tickspersecond"] != null) { animTicksPerSecond = double.Parse(child["tickspersecond"].InnerText); } } catch { throw new Exception("Error parsing tickspersecond in xml file."); } if (animTicksPerSecond <= 0) throw new InvalidDataException("AnimTicksPerSecond in XML file must be " + "a positive number."); secondsPerTick = 1.0 / animTicksPerSecond; AnimationContent anim = null; // Get the animation and remove it from the dict // Check to see if the animation specified in the xml file exists try { anim = animDict[animName]; } catch { throw new Exception("Animation named " + animName + " specified in XML file does not exist in model."); } animDict.Remove(anim.Name); // Get the list of new animations XmlNodeList subAnimations = child.GetElementsByTagName("animationsubset"); foreach (XmlElement subAnim in subAnimations) { // Create the new sub animation AnimationContent newAnim = new AnimationContent(); XmlElement subAnimNameElement = subAnim["name"]; if (subAnimNameElement != null) newAnim.Name = subAnimNameElement.InnerText; // If a starttime node exists, use that to get the start time long startTime, endTime; if (subAnim["starttime"] != null) { try { startTime = TimeSpan.FromSeconds(double.Parse(subAnim["starttime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing starttime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["startframe"] != null)// else use the secondspertick combined with the startframe node value { try { double seconds = double.Parse(subAnim["startframe"].InnerText) * secondsPerTick; startTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing startframe node in XML file. Node inner text " + "must be a non negative number."); } } else throw new Exception("Sub animation in XML file must have either a starttime or startframe node."); // Same with endtime/endframe if (subAnim["endtime"] != null) { try { endTime = TimeSpan.FromSeconds(double.Parse(subAnim["endtime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing endtime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["endframe"] != null) { try { double seconds = double.Parse(subAnim["endframe"].InnerText) * secondsPerTick; endTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing endframe node in XML file. Node inner text " + "must be a non negative number."); } } else throw new Exception("Sub animation in XML file must have either an endtime or endframe node."); if (endTime < startTime) throw new Exception("Start time must be <= end time in XML file."); // Now that we have the start and end times, we associate them with // start and end indices for each animation track/channel foreach (KeyValuePair<string, AnimationChannel> k in anim.Channels) { // The current difference between the start time and the // time at the current index long currentStartDiff; // The current difference between the end time and the // time at the current index long currentEndDiff; // The difference between the start time and the time // at the start index long bestStartDiff=long.MaxValue; // The difference between the end time and the time at // the end index long bestEndDiff=long.MaxValue; // The start and end indices int startIndex = -1; int endIndex = -1; // Create a new channel and reference the old channel AnimationChannel newChan = new AnimationChannel(); AnimationChannel oldChan = k.Value; // Iterate through the keyframes in the channel for (int i = 0; i < oldChan.Count; i++) { // Update the startIndex, endIndex, bestStartDiff, // and bestEndDiff long ticks = oldChan[i].Time.Ticks; currentStartDiff = Math.Abs(startTime - ticks); currentEndDiff = Math.Abs(endTime - ticks); if (startIndex == -1 || currentStartDiff<bestStartDiff) { startIndex = i; bestStartDiff = currentStartDiff; } if (endIndex == -1 || currentEndDiff<bestEndDiff) { endIndex = i; bestEndDiff = currentEndDiff; } } // Now we have our start and end index for the channel for (int i = startIndex; i <= endIndex; i++) { AnimationKeyframe frame = oldChan[i]; long time; // Clamp the time so that it can't be less than the // start time if (frame.Time.Ticks < startTime) time = 0; // Clamp the time so that it can't be greater than the // end time else if (frame.Time.Ticks > endTime) time = endTime - startTime; else // Else get the time time = frame.Time.Ticks - startTime; // Finally... create the new keyframe and add it to the new channel AnimationKeyframe keyframe = new AnimationKeyframe( TimeSpan.FromTicks(time), frame.Transform); newChan.Add(keyframe); } // Add the channel and update the animation duration based on the // length of the animation track. newAnim.Channels.Add(k.Key, newChan); if (newChan[newChan.Count - 1].Time > newAnim.Duration) newAnim.Duration = newChan[newChan.Count - 1].Time; } try { // Add the subdived animation to the dictionary. animDict.Add(newAnim.Name, newAnim); } catch { throw new Exception("Attempt to add an animation when one by the same name already exists. " + "Name: " + newAnim.Name); } } } }
/// <summary> /// Retrieves and interpolates the pose of an animation channel. /// </summary> /// <param name="animationChannel">Name of the animation channel.</param> /// <param name="animationTime">Current animation clip time.</param> /// <param name="outPose">The output interpolated pose.</param> private void InterpolateChannelPose(AnimationChannel animationChannel, TimeSpan animationTime, out Pose outPose) { if (translationInterpolation == InterpolationMode.None && orientationInterpolation == InterpolationMode.None && scaleInterpolation == InterpolationMode.None) { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); outPose = animationChannel[keyframeIndex].Pose; } else { int keyframeIndex = animationChannel.GetKeyframeIndexByTime(animationTime); int nextKeyframeIndex = (keyframeIndex + 1) % animationChannel.Count; AnimationChannelKeyframe keyframe1 = animationChannel[keyframeIndex]; AnimationChannelKeyframe keyframe2 = animationChannel[nextKeyframeIndex]; // Calculate the time between the keyframes considering loop long keyframeDuration; if (keyframeIndex == (animationChannel.Count - 1)) keyframeDuration = animationClip.Duration.Ticks - keyframe1.Time.Ticks; else keyframeDuration = keyframe2.Time.Ticks - keyframe1.Time.Ticks; // Interpolate when duration higher than zero if (keyframeDuration > 0) { long elapsedKeyframeTime = animationTime.Ticks - keyframe1.Time.Ticks; float lerpFactor = elapsedKeyframeTime / (float)keyframeDuration; outPose = Pose.Interpolate(keyframe1.Pose, keyframe2.Pose, lerpFactor, translationInterpolation, orientationInterpolation, scaleInterpolation); } // Otherwise don't interpolate else outPose = keyframe1.Pose; } }
void ProcessAnimation(AnimationContent anim, ContentProcessorContext context, List <AnimationData> animations, Dictionary <string, int> indices, SkeletonData skeleton) { SortedDictionary <TimeSpan, bool> allFrameTimes = new SortedDictionary <TimeSpan, bool>(); SortedDictionary <TimeSpan, Matrix[]> transforms = new SortedDictionary <TimeSpan, Matrix[]>(); int totalChannels = 0; foreach (KeyValuePair <string, AnimationChannel> channelKVP in anim.Channels) { if (indices.ContainsKey(CleanBoneName(channelKVP.Key)) == false) { continue; } AnimationChannel channel = channelKVP.Value; foreach (AnimationKeyframe frame in channel) { if (allFrameTimes.ContainsKey(frame.Time) == false) { allFrameTimes.Add(frame.Time, true); } } totalChannels++; } foreach (TimeSpan time in allFrameTimes.Keys) { transforms.Add(time, new Matrix[totalChannels]); } SortedDictionary <TimeSpan, Matrix> keyFrames = new SortedDictionary <TimeSpan, Matrix>(); List <KeyValuePair <TimeSpan, Matrix> > newFrames = new List <KeyValuePair <TimeSpan, Matrix> >(); int index = 0; foreach (KeyValuePair <string, AnimationChannel> channelKVP in anim.Channels) { int boneIndex = 0; if (indices.TryGetValue(CleanBoneName(channelKVP.Key), out boneIndex) == false) { continue; } AnimationChannel channel = channelKVP.Value; Matrix transform; foreach (AnimationKeyframe frame in channel) { transform = frame.Transform; if (boneIndex == 0) { CorrectRootBoneTransform(ref transform, skeleton); } else { //remove translation transform.Translation = new Vector3(); } keyFrames.Add(frame.Time, transform); } SortedDictionary <TimeSpan, Matrix> .Enumerator frames = keyFrames.GetEnumerator(); SortedDictionary <TimeSpan, bool> .Enumerator times = allFrameTimes.GetEnumerator(); if (!times.MoveNext()) { continue; } if (!frames.MoveNext()) { continue; } TimeSpan time = frames.Current.Key; transform = frames.Current.Value; while (true) { Matrix previousTransform = transform; TimeSpan previousTime = time; time = frames.Current.Key; transform = frames.Current.Value; if (times.Current.Key.Ticks == frames.Current.Key.Ticks) { if (!times.MoveNext()) { break; } if (!frames.MoveNext()) { //frames ends early... while (true) { newFrames.Add(new KeyValuePair <TimeSpan, Matrix>(times.Current.Key, transform)); if (!times.MoveNext()) { break; } } break; } continue; } if (times.Current.Key.Ticks > frames.Current.Key.Ticks) { //frame is behind if (!frames.MoveNext()) { //frames ends early... while (true) { newFrames.Add(new KeyValuePair <TimeSpan, Matrix>(times.Current.Key, transform)); if (!times.MoveNext()) { break; } } break; } continue; } else { //frame is ahead.. create an inbetween double amount = (times.Current.Key - previousTime).TotalSeconds / (frames.Current.Key - previousTime).TotalSeconds; Matrix newTransform = Matrix.Lerp(previousTransform, frames.Current.Value, (float)amount); //Matrix newTransform = frames.Current.Value.Interpolate(ref previousTransform, (float)(1 - amount)); newFrames.Add(new KeyValuePair <TimeSpan, Matrix>(times.Current.Key, newTransform)); transform = newTransform; time = times.Current.Key; if (!times.MoveNext()) { break; } } } foreach (KeyValuePair <TimeSpan, Matrix> newFrame in newFrames) { keyFrames.Add(newFrame.Key, newFrame.Value); } foreach (KeyValuePair <TimeSpan, Matrix> kvp in keyFrames) { Matrix[] array; if (transforms.TryGetValue(kvp.Key, out array)) { array[index] = kvp.Value; } else { continue; } } newFrames.Clear(); keyFrames.Clear(); index++; } KeyFrameData[] boneKeyFrames = new KeyFrameData[transforms.Count]; int[] boneIndices = new int[totalChannels]; index = 0; foreach (KeyValuePair <string, AnimationChannel> channelKVP in anim.Channels) { if (indices.ContainsKey(CleanBoneName(channelKVP.Key)) == false) { continue; } boneIndices[index++] = indices[CleanBoneName(channelKVP.Key)]; } //compute world space bone default skeleton Matrix[] worldBoneTransforms = skeleton.BoneLocalMatrices.ToArray(); skeleton.TransformHierarchy(worldBoneTransforms); index = 0; float duration = 0; foreach (KeyValuePair <TimeSpan, Matrix[]> kvp in transforms) { float seconds = (float)kvp.Key.TotalSeconds; /* * //make transforms realtive to skeleton * //first get static bone transforms * Matrix[] worldTransform = new Matrix[skeleton.BoneCount]; * for (int i = 0; i < worldTransform.Length; i++) * worldTransform[i] = skeleton.BoneLocalMatrices[i]; * * //replace the animated bones.. * for (int i = 0; i < kvp.Value.Length; i++) * worldTransform[boneIndices[i]] = kvp.Value[i]; * * //transform into a world space skeleton * skeleton.TransformHierarchy(worldTransform); * * //multiply the world space transforms with the inverse of the world space static skeleton * for (int i = 0; i < worldTransform.Length; i++) * { * Matrix m = worldBoneTransforms[i]; * Matrix.Invert(ref m, out m); * * worldTransform[i] = m * worldTransform[i]; * } * * //transform back out of world space into joint space * skeleton.TransformHierarchyInverse(worldTransform); * * * for (int i = 0; i < kvp.Value.Length; i++) * kvp.Value[i] = worldTransform[boneIndices[i]]; */ boneKeyFrames[index++] = new KeyFrameData(seconds, kvp.Value); duration = Math.Max(seconds, duration); } if (boneIndices.Length > 0 && boneKeyFrames.Length > 0) { AnimationData animation = new AnimationData(anim.Name, boneIndices, boneKeyFrames, duration, animationCompressionTolerancePercent); animations.Add(animation); } }
/// <summary> /// Interpolates an AnimationContent object to 60 fps. /// </summary> /// <param name="input">The AnimationContent to interpolate.</param> /// <returns>The interpolated AnimationContent.</returns> public virtual AnimationContent Interpolate(AnimationContent input) { AnimationContent output = new AnimationContent(); long time = 0; long animationDuration = input.Duration.Ticks; // default XNA importers, due to floating point errors or TimeSpan // estimation, sometimes have channels with a duration slightly longer than // the animation duration. So, set the animation duration to its true // value foreach (KeyValuePair <string, AnimationChannel> c in input.Channels) { if (c.Value[c.Value.Count - 1].Time.Ticks > animationDuration) { animationDuration = c.Value[c.Value.Count - 1].Time.Ticks; } } foreach (KeyValuePair <string, AnimationChannel> c in input.Channels) { time = 0; string channelName = c.Key; AnimationChannel channel = c.Value; AnimationChannel outChannel = new AnimationChannel(); int currentFrame = 0; // Step through time until the time passes the animation duration while (time <= animationDuration) { AnimationKeyframe keyframe; // Clamp the time to the duration of the animation and make this // keyframe equal to the last animation frame. if (time >= animationDuration) { time = animationDuration; keyframe = new AnimationKeyframe(new TimeSpan(time), channel[channel.Count - 1].Transform); } else { // If the channel only has one keyframe, set the transform for the current time // to that keyframes transform if (channel.Count == 1 || time < channel[0].Time.Ticks) { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[0].Transform); } // If the current track duration is less than the animation duration, // use the last transform in the track once the time surpasses the duration else if (channel[channel.Count - 1].Time.Ticks <= time) { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[channel.Count - 1].Transform); } else // proceed as normal { // Go to the next frame that is less than the current time while (channel[currentFrame + 1].Time.Ticks < time) { currentFrame++; } // Numerator of the interpolation factor double interpNumerator = (double)(time - channel[currentFrame].Time.Ticks); // Denominator of the interpolation factor double interpDenom = (double)(channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks); // The interpolation factor, or amount to interpolate between the current // and next frame double interpAmount = interpNumerator / interpDenom; // If the frames are roughly 60 frames per second apart, use linear interpolation if (channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks <= ContentUtil.TICKS_PER_60FPS * 1.05) { keyframe = new AnimationKeyframe(new TimeSpan(time), Matrix.Lerp( channel[currentFrame].Transform, channel[currentFrame + 1].Transform, (float)interpAmount)); } else // else if the transforms between the current frame and the next aren't identical // decompose the matrix and interpolate the rotation separately if (channel[currentFrame].Transform != channel[currentFrame + 1].Transform) { keyframe = new AnimationKeyframe(new TimeSpan(time), ContentUtil.SlerpMatrix( channel[currentFrame].Transform, channel[currentFrame + 1].Transform, (float)interpAmount)); } else // Else the adjacent frames have identical transforms and we can use // the current frames transform for the current keyframe. { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[currentFrame].Transform); } } } // Add the interpolated keyframe to the new channel. outChannel.Add(keyframe); // Step the time forward by 1/60th of a second time += ContentUtil.TICKS_PER_60FPS; } // Compensate for the time error,(animation duration % TICKS_PER_60FPS), // caused by the interpolation by setting the last keyframe in the // channel to the animation duration. if (outChannel[outChannel.Count - 1].Time.Ticks < animationDuration) { outChannel.Add(new AnimationKeyframe( TimeSpan.FromTicks(animationDuration), channel[channel.Count - 1].Transform)); } outChannel.Add(new AnimationKeyframe(input.Duration, channel[channel.Count - 1].Transform)); // Add the interpolated channel to the animation output.Channels.Add(channelName, outChannel); } // Set the interpolated duration to equal the inputs duration for consistency output.Duration = TimeSpan.FromTicks(animationDuration); return(output); }
internal static Mesh3D LoadFromFileSUB(Device device, Assimp.Scene pScene, InputElement[] ieLayout, List <SceneObject> meshArr) { string sourceTextures = ConfigurationSettings.AppSettings["SourceTextures"]; int iVBufferSize = 0; int iIBufferSize = 0; foreach (SceneObject so in meshArr) { iVBufferSize += (int)so.pMesh.VertexCount; iIBufferSize += (int)so.pMesh.FaceCount * 3; } MeshInputElements10.sNormalMesh[] tVertex = new MeshInputElements10.sNormalMesh[iVBufferSize]; //short[] tIndex = new short[iIBufferSize]; uint[] tIndex = new uint[iIBufferSize]; MeshAttributeRange[] tAttibutes = new MeshAttributeRange[meshArr.Count]; MeshNode[] nodes = new MeshNode[meshArr.Count]; string[] tTextures = new string[meshArr.Count]; // Monitor global poisition in the vertex, index and attribute buffers. int iAttribute = 0; int iBVertex = 0; int iBIndex = 0; int prevVertCount = 0; foreach (SceneObject so in meshArr) { MeshNode n = new MeshNode(); n.attributeId = iAttribute; n.nodeName = so.nodeName; n.parentNodeName = so.parentNodeName; n.transform = toDXMat(so.transform); nodes[iAttribute] = n; MeshAttributeRange pAttrib = new MeshAttributeRange(); pAttrib.Id = iAttribute; pAttrib.VertexStart = iBVertex; pAttrib.FaceStart = iBIndex / 3; Assimp.Material mat = pScene.Materials[so.pMesh.MaterialIndex]; if (mat.GetTextureCount(Assimp.TextureType.Diffuse) > 0) { string inputFilePath = mat.GetTexture(Assimp.TextureType.Diffuse, 0).FilePath.Replace("\\\\", "\\"); string inputFileName = Path.GetFileName(inputFilePath); string ext = Path.GetExtension(inputFileName); string subFolder = inputFilePath.Replace(sourceTextures, "").Split('\\')[1]; string diffOutFile = subFolder + "\\" + inputFileName.Replace(ext, ".dds"); tTextures[iAttribute] = diffOutFile; } // Copy verticies. int iMeshVerts = (int)so.pMesh.VertexCount; for (int iVertex = 0; iVertex < iMeshVerts; ++iVertex) { tVertex[iVertex + iBVertex] = getVert(so, iVertex); } // Increment the vertex count by the number of verticies we just looped over. iBVertex += iMeshVerts; // Copy indicies. int iMeshFaces = (int)so.pMesh.FaceCount; for (int iFace = 0; iFace < iMeshFaces; ++iFace) { uint[] tIndices = so.pMesh.Faces[iFace].Indices; //tIndex[iBIndex++] = Convert.ToInt16(tIndices[0] + (iAttribute == 0 ? 0 : prevVertCount)); //tIndex[iBIndex++] = Convert.ToInt16(tIndices[1] + (iAttribute == 0 ? 0 : prevVertCount)); //tIndex[iBIndex++] = Convert.ToInt16(tIndices[2] + (iAttribute == 0 ? 0 : prevVertCount)); tIndex[iBIndex++] = Convert.ToUInt32(tIndices[0] + (iAttribute == 0 ? 0 : prevVertCount)); tIndex[iBIndex++] = Convert.ToUInt32(tIndices[1] + (iAttribute == 0 ? 0 : prevVertCount)); tIndex[iBIndex++] = Convert.ToUInt32(tIndices[2] + (iAttribute == 0 ? 0 : prevVertCount)); } // Increment the face count by the number of faces we just looped over. prevVertCount += iMeshVerts; pAttrib.FaceCount = iMeshFaces; pAttrib.VertexCount = iMeshVerts; tAttibutes[iAttribute] = pAttrib; iAttribute++; } Mesh3D gm = new Mesh3D(); Mesh output = new Mesh(device, ieLayout, ieLayout[0].SemanticName, iVBufferSize, iIBufferSize / 3, MeshFlags.Has32BitIndices); output.SetAttributeTable(tAttibutes); DataStream verts = new DataStream(iVBufferSize * Marshal.SizeOf(typeof(MeshInputElements10.sNormalMesh)), true, true); //DataStream indicies = new DataStream(iIBufferSize * Marshal.SizeOf(typeof(short)), true, true); DataStream indicies = new DataStream(iIBufferSize * Marshal.SizeOf(typeof(uint)), true, true); verts.WriteRange(tVertex); indicies.WriteRange(tIndex); verts.Position = 0; indicies.Position = 0; output.SetVertexData(0, verts); output.SetIndexData(indicies, iIBufferSize); output.Commit(); verts.Dispose(); indicies.Dispose(); gm.attrTable = tAttibutes; gm.meshObj = output; gm.NumAttributes = meshArr.Count; gm.materials = tTextures; gm.nodes = nodes; if (pScene.HasAnimations) { gm.animations = new List <Mesh3DAnimation>(); foreach (Assimp.Animation a in pScene.Animations) { Mesh3DAnimation anim = new Mesh3DAnimation(); anim.animName = a.Name; anim.duration = a.DurationInTicks; anim.channels = new AnimationChannel[a.NodeAnimationChannelCount]; int x = 0; foreach (Assimp.NodeAnimationChannel c in a.NodeAnimationChannels) { if (c == null) { continue; } AnimationChannel ac = new AnimationChannel(); ac.nodeName = c.NodeName; ac.animKeys = new AnimationKey[c.RotationKeyCount]; for (int i = 0; i < c.PositionKeys.Length; i++) { double time = 0; SceneObject sObj = meshArr[0]; foreach (SceneObject so in meshArr) { if (so.nodeName == c.NodeName) { sObj = so; break; } } time = c.PositionKeys[i].Time; Matrix mat = toDXMat(sObj.transform); Vector3 rotationCenter = new Vector3(mat.M41, mat.M42, mat.M43); mat.Invert(); Vector3 pos = Vector3.TransformCoordinate(toDX(c.PositionKeys[i].Value), mat); Matrix matAnim = Matrix.Transformation(Vector3.Zero, Quaternion.Identity, new Vector3(1, 1, 1), rotationCenter, toDX(c.RotationKeys[i].Value), pos); AnimationKey ak = new AnimationKey(); ak.animMat = matAnim; ak.time = time; ac.animKeys[i] = ak; } anim.channels[x++] = ac; } gm.animations.Add(anim); } } return(gm); }