public override void Start() { // Create an AnimationClip. Make sure to properly set it's duration. var animationClip = new AnimationClip { Duration = TimeSpan.FromSeconds(1) }; // Add some curves, specifying the path to the properties to animate. // - Components can be index using a special syntax to their key. // - Properties can be qualified with a type name in parenthesis // - If a type is not serializable, it's fully qualified name must be used var colorLightBaseName = typeof(ColorLightBase).AssemblyQualifiedName; var colorRgbProviderName = typeof(ColorRgbProvider).AssemblyQualifiedName; animationClip.AddCurve("[TransformComponent.Key].Rotation", CreateLightRotationCurve()); animationClip.AddCurve(string.Format("[LightComponent.Key].Type.({0})Color.({1})Value", colorLightBaseName, colorRgbProviderName), CreateLightColorCurve()); // Optional: Pack all animation channels into an optimized interleaved format animationClip.Optimize(); // Add an AnimationComponent to the current entity and register our custom clip const string animationName = "MyCustomAnimation"; var animationComponent = Entity.GetOrCreate <AnimationComponent>(); animationComponent.Animations.Add(animationName, animationClip); // Start playing the animation right away and keep repeating it var playingAnimation = animationComponent.Play(animationName); playingAnimation.RepeatMode = AnimationRepeatMode.LoopInfinite; playingAnimation.TimeFactor = 0.1f; // slow down playingAnimation.CurrentTime = TimeSpan.FromSeconds(0.6f); // start at different time }
public override void Start() { // Create an AnimationClip. Make sure to properly set it's duration. var animationClip = new AnimationClip { Duration = TimeSpan.FromSeconds(1) }; animationClip.AddCurve("[TransformComponent.Key].Rotation", CreateLightRotationCurve()); // Optional: Pack all animation channels into an optimized interleaved format animationClip.Optimize(); // Add an AnimationComponent to the current entity and register our custom clip const string animationName = "MyCustomAnimation"; var animationComponent = Entity.GetOrCreate <AnimationComponent>(); animationComponent.Animations.Add(animationName, animationClip); // Start playing the animation right away and keep repeating it var playingAnimation = animationComponent.Play(animationName); playingAnimation.RepeatMode = AnimationRepeatMode.LoopInfinite; playingAnimation.TimeFactor = TimeFactor; playingAnimation.CurrentTime = TimeSpan.FromSeconds(0.6f); // start at different time }
public void TestAnimationClip() { var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(2.0f), RepeatMode = AnimationRepeatMode.LoopInfinite }; var testCurve = new AnimationCurve<float>(); clip.AddCurve("posx[TestNode]", testCurve); testCurve.InterpolationType = AnimationCurveInterpolationType.Linear; var time = CompressedTimeSpan.FromSeconds(0.0f); var value = 0.0f; var frame0 = new KeyFrameData<float>(time, value); testCurve.KeyFrames.Add(frame0); time = CompressedTimeSpan.FromSeconds(1.0f); value = 1.0f; var frame1 = new KeyFrameData<float>(time, value); testCurve.KeyFrames.Add(frame1); clip.Optimize(); //we should have 3 frames at this point. the last one will be added by the optimization process... Assert.That(clip.OptimizedCurvesFloat.AnimationSortedValueCount, Is.EqualTo(1)); //And 2 initial frames Assert.That(clip.OptimizedCurvesFloat.AnimationInitialValues[0].Value1, Is.EqualTo(frame0)); Assert.That(clip.OptimizedCurvesFloat.AnimationInitialValues[0].Value2, Is.EqualTo(frame1)); Assert.That(clip.OptimizedCurvesFloat.AnimationSortedValues.Length, Is.EqualTo(1)); Assert.That(clip.OptimizedCurvesFloat.AnimationSortedValues[0].Length, Is.EqualTo(1)); Assert.That(clip.OptimizedCurvesFloat.AnimationSortedValues[0][0].Value, Is.EqualTo(frame1)); }
private AnimationClip CreateModelChangeAnimation(Model model) { var changeMegalodonAnimClip = new AnimationClip(); var modelCurve = new AnimationCurve <object>(); modelCurve.KeyFrames.Add(new KeyFrameData <object>(CompressedTimeSpan.Zero, model)); changeMegalodonAnimClip.AddCurve("[ModelComponent.Key].Model", modelCurve); return(changeMegalodonAnimClip); }
// Declared public member fields and properties will show in the game studio public override void Start() { var lightC = Entity.Get <LightComponent>(); var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(1) }; var colorLightBaseName = typeof(ColorLightBase).AssemblyQualifiedName; var colorRgbProviderName = typeof(ColorRgbProvider).AssemblyQualifiedName; clip.AddCurve($"[LightComponent.Key].Type.({colorLightBaseName})Color.({colorRgbProviderName})Value", CreateLightColorCurve()); clip.RepeatMode = AnimationRepeatMode.LoopInfinite; var animC = Entity.GetOrCreate <AnimationComponent>(); animC.Animations.Add("LightCurve", clip); animC.Play("LightCurve"); }
public void TestAnimationClip() { var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(2.0f), RepeatMode = AnimationRepeatMode.LoopInfinite }; var testCurve = new AnimationCurve <float>(); clip.AddCurve("posx[TestNode]", testCurve); testCurve.InterpolationType = AnimationCurveInterpolationType.Linear; var time = CompressedTimeSpan.FromSeconds(0.0f); var value = 0.0f; var frame0 = new KeyFrameData <float>(time, value); testCurve.KeyFrames.Add(frame0); time = CompressedTimeSpan.FromSeconds(1.0f); value = 1.0f; var frame1 = new KeyFrameData <float>(time, value); testCurve.KeyFrames.Add(frame1); clip.Optimize(); var optimizedCurvesFloat = (AnimationData <float>)clip.OptimizedAnimationDatas.First(); //we should have 3 frames at this point. the last one will be added by the optimization process... Assert.That(optimizedCurvesFloat.AnimationSortedValueCount, Is.EqualTo(1)); //And 2 initial frames Assert.That(optimizedCurvesFloat.AnimationInitialValues[0].Value1, Is.EqualTo(frame0)); Assert.That(optimizedCurvesFloat.AnimationInitialValues[0].Value2, Is.EqualTo(frame1)); Assert.That(optimizedCurvesFloat.AnimationSortedValues.Length, Is.EqualTo(1)); Assert.That(optimizedCurvesFloat.AnimationSortedValues[0].Length, Is.EqualTo(1)); Assert.That(optimizedCurvesFloat.AnimationSortedValues[0][0].Value, Is.EqualTo(frame1)); }
public static Dictionary <string, AnimationClip> ConvertAnimations(SharpGLTF.Schema2.ModelRoot root) { var animations = root.LogicalAnimations; var clips = animations .Select(x => { //Create animation clip with var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(x.Duration) }; clip.RepeatMode = AnimationRepeatMode.LoopInfinite; // Add Curve ConvertCurves(x.Channels, root).ToList().ForEach(x => clip.AddCurve(x.Key, x.Value)); return(x.Name, clip); } ) .ToList() .ToDictionary(x => x.Name, x => x.clip); return(clips); }
private unsafe object ExportAnimation(ICommandContext commandContext, ContentManager contentManager, bool failOnEmptyAnimation) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, contentManager); // we get model skeleton to compare it to real skeleton we need to map to AdjustSkeleton(modelSkeleton); TimeSpan duration; var animationClips = LoadAnimation(commandContext, contentManager, out duration); // Fix the animation frames double startFrameSeconds = StartFrame.TotalSeconds; double endFrameSeconds = EndFrame.TotalSeconds; var startTime = CompressedTimeSpan.FromSeconds(-startFrameSeconds); foreach (var clip in animationClips) { foreach (var animationCurve in clip.Value.Curves) { animationCurve.ShiftKeys(startTime); } } var durationTimeSpan = TimeSpan.FromSeconds((endFrameSeconds - startFrameSeconds)); if (duration > durationTimeSpan) { duration = durationTimeSpan; } var animationClip = new AnimationClip { Duration = duration }; if (animationClips.Count > 0) { AnimationClip rootMotionAnimationClip = null; // If root motion is explicitely enabled, or if there is no skeleton, try to find root node and apply animation directly on TransformComponent if ((AnimationRootMotion || SkeletonUrl == null) && modelSkeleton.Nodes.Length >= 1) { // No skeleton, map root node only // TODO: For now, it seems to be located on node 1 in FBX files. Need to check if always the case, and what happens with Assimp var rootNode0 = modelSkeleton.Nodes.Length >= 1 ? modelSkeleton.Nodes[0].Name : null; var rootNode1 = modelSkeleton.Nodes.Length >= 2 ? modelSkeleton.Nodes[1].Name : null; if ((rootNode0 != null && animationClips.TryGetValue(rootNode0, out rootMotionAnimationClip)) || (rootNode1 != null && animationClips.TryGetValue(rootNode1, out rootMotionAnimationClip))) { foreach (var channel in rootMotionAnimationClip.Channels) { var curve = rootMotionAnimationClip.Curves[channel.Value.CurveIndex]; // Root motion var channelName = channel.Key; if (channelName.StartsWith("Transform.")) { animationClip.AddCurve($"[TransformComponent.Key]." + channelName.Replace("Transform.", string.Empty), curve); } // Also apply Camera curves // TODO: Add some other curves? if (channelName.StartsWith("Camera.")) { animationClip.AddCurve($"[CameraComponent.Key]." + channelName.Replace("Camera.", string.Empty), curve); } } } } // Load asset reference skeleton if (SkeletonUrl != null) { var skeleton = contentManager.Load <Skeleton>(SkeletonUrl); var skeletonMapping = new SkeletonMapping(skeleton, modelSkeleton); // Process missing nodes foreach (var nodeAnimationClipEntry in animationClips) { var nodeName = nodeAnimationClipEntry.Key; var nodeAnimationClip = nodeAnimationClipEntry.Value; var nodeIndex = modelSkeleton.Nodes.IndexOf(x => x.Name == nodeName); // Node doesn't exist in skeleton? skip it if (nodeIndex == -1 || skeletonMapping.SourceToSource[nodeIndex] != nodeIndex) { continue; } // Skip root motion node (if any) if (nodeAnimationClip == rootMotionAnimationClip) { continue; } // Find parent node var parentNodeIndex = modelSkeleton.Nodes[nodeIndex].ParentIndex; if (parentNodeIndex != -1 && skeletonMapping.SourceToSource[parentNodeIndex] != parentNodeIndex) { // Some nodes were removed, we need to concat the anim curves var currentNodeIndex = nodeIndex; var nodesToMerge = new List <Tuple <ModelNodeDefinition, AnimationBlender, AnimationClipEvaluator> >(); while (currentNodeIndex != -1 && currentNodeIndex != skeletonMapping.SourceToSource[parentNodeIndex]) { AnimationClip animationClipToMerge; AnimationClipEvaluator animationClipEvaluator = null; AnimationBlender animationBlender = null; if (animationClips.TryGetValue(modelSkeleton.Nodes[currentNodeIndex].Name, out animationClipToMerge)) { animationBlender = new AnimationBlender(); animationClipEvaluator = animationBlender.CreateEvaluator(animationClipToMerge); } nodesToMerge.Add(Tuple.Create(modelSkeleton.Nodes[currentNodeIndex], animationBlender, animationClipEvaluator)); currentNodeIndex = modelSkeleton.Nodes[currentNodeIndex].ParentIndex; } // Put them in proper parent to children order nodesToMerge.Reverse(); // Find all key times // TODO: We should detect discontinuities and keep them var animationKeysSet = new HashSet <CompressedTimeSpan>(); foreach (var node in nodesToMerge) { if (node.Item3 != null) { foreach (var curve in node.Item3.Clip.Curves) { foreach (CompressedTimeSpan time in curve.Keys) { animationKeysSet.Add(time); } } } } // Sort key times var animationKeys = animationKeysSet.ToList(); animationKeys.Sort(); var animationOperations = new FastList <AnimationOperation>(); var combinedAnimationClip = new AnimationClip(); var translationCurve = new AnimationCurve <Vector3>(); var rotationCurve = new AnimationCurve <Quaternion>(); var scaleCurve = new AnimationCurve <Vector3>(); // Evaluate at every key frame foreach (var animationKey in animationKeys) { var matrix = Matrix.Identity; // Evaluate node foreach (var node in nodesToMerge) { // Needs to be an array in order for it to be modified by the UpdateEngine, otherwise it would get passed by value var modelNodeDefinitions = new ModelNodeDefinition[1] { node.Item1 }; if (node.Item2 != null && node.Item3 != null) { // Compute AnimationClipResult animationClipResult = null; animationOperations.Clear(); animationOperations.Add(AnimationOperation.NewPush(node.Item3, animationKey)); node.Item2.Compute(animationOperations, ref animationClipResult); var updateMemberInfos = new List <UpdateMemberInfo>(); foreach (var channel in animationClipResult.Channels) { if (channel.IsUserCustomProperty) { continue; } updateMemberInfos.Add(new UpdateMemberInfo { Name = "[0]." + channel.PropertyName, DataOffset = channel.Offset }); } // TODO: Cache this var compiledUpdate = UpdateEngine.Compile(typeof(ModelNodeDefinition[]), updateMemberInfos); fixed(byte *data = animationClipResult.Data) { UpdateEngine.Run(modelNodeDefinitions, compiledUpdate, (IntPtr)data, null); } } Matrix localMatrix; var transformTRS = modelNodeDefinitions[0].Transform; Matrix.Transformation(ref transformTRS.Scale, ref transformTRS.Rotation, ref transformTRS.Position, out localMatrix); matrix = Matrix.Multiply(localMatrix, matrix); } // Done evaluating, let's decompose matrix TransformTRS transform; matrix.Decompose(out transform.Scale, out transform.Rotation, out transform.Position); // Create a key translationCurve.KeyFrames.Add(new KeyFrameData <Vector3>(animationKey, transform.Position)); rotationCurve.KeyFrames.Add(new KeyFrameData <Quaternion>(animationKey, transform.Rotation)); scaleCurve.KeyFrames.Add(new KeyFrameData <Vector3>(animationKey, transform.Scale)); } combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Position)}", translationCurve); combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Rotation)}", rotationCurve); combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Scale)}", scaleCurve); nodeAnimationClip = combinedAnimationClip; } var transformStart = $"{nameof(ModelNodeTransformation.Transform)}."; var transformPosition = $"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Position)}"; foreach (var channel in nodeAnimationClip.Channels) { var curve = nodeAnimationClip.Curves[channel.Value.CurveIndex]; // TODO: Root motion var channelName = channel.Key; if (channelName.StartsWith(transformStart)) { if (channelName == transformPosition) { // Translate node with parent 0 using PivotPosition var keyFrames = ((AnimationCurve <Vector3>)curve).KeyFrames; for (int i = 0; i < keyFrames.Count; ++i) { if (parentNodeIndex == 0) { keyFrames.Items[i].Value -= PivotPosition; } keyFrames.Items[i].Value *= ScaleImport; } } animationClip.AddCurve($"[ModelComponent.Key].Skeleton.NodeTransformations[{skeletonMapping.SourceToTarget[nodeIndex]}]." + channelName, curve); } } } } if (ImportCustomAttributes) { // Add clips clips animating other properties than node transformations foreach (var nodeAnimationClipPair in animationClips) { var nodeName = nodeAnimationClipPair.Key; var nodeAnimationClip = nodeAnimationClipPair.Value; foreach (var channel in nodeAnimationClip.Channels) { var channelName = channel.Key; var channelValue = channel.Value; if (channelValue.IsUserCustomProperty) { animationClip.AddCurve(nodeName + "_" + channelName, nodeAnimationClip.Curves[channel.Value.CurveIndex], true); } } } } } if (animationClip.Channels.Count == 0) { var logString = $"File {SourcePath} doesn't have any animation information."; if (failOnEmptyAnimation) { commandContext.Logger.Error(logString); return(null); } commandContext.Logger.Info(logString); } else { if (animationClip.Duration.Ticks == 0) { commandContext.Logger.Verbose($"File {SourcePath} has a 0 tick long animation."); } // Optimize and set common parameters animationClip.RepeatMode = AnimationRepeatMode; animationClip.Optimize(); } return(animationClip); }
private AnimationClip SubtractAnimations(AnimationClip baseAnimation, AnimationClip sourceAnimation) { if (baseAnimation == null) { throw new ArgumentNullException("baseAnimation"); } if (sourceAnimation == null) { throw new ArgumentNullException("sourceAnimation"); } var animationBlender = new AnimationBlender(); var baseEvaluator = animationBlender.CreateEvaluator(baseAnimation); var sourceEvaluator = animationBlender.CreateEvaluator(sourceAnimation); // Create a result animation with same channels var resultAnimation = new AnimationClip(); foreach (var channel in sourceAnimation.Channels) { // Create new instance of curve var newCurve = (AnimationCurve)Activator.CreateInstance(typeof(AnimationCurve <>).MakeGenericType(channel.Value.ElementType)); // Quaternion curve are linear, others are cubic if (newCurve.ElementType != typeof(Quaternion)) { newCurve.InterpolationType = AnimationCurveInterpolationType.Cubic; } resultAnimation.AddCurve(channel.Key, newCurve); } var resultEvaluator = animationBlender.CreateEvaluator(resultAnimation); var animationOperations = new FastList <AnimationOperation>(); // Perform animation blending for each frame and upload results in a new animation // Note that it does a simple per-frame sampling, so animation discontinuities will be lost. // TODO: Framerate is hardcoded at 30 FPS. var frameTime = TimeSpan.FromSeconds(1.0f / 30.0f); for (var time = TimeSpan.Zero; time < sourceAnimation.Duration + frameTime; time += frameTime) { // Last frame, round it to end of animation if (time > sourceAnimation.Duration) { time = sourceAnimation.Duration; } TimeSpan baseTime; switch (Parameters.Mode) { case AdditiveAnimationBaseMode.FirstFrame: baseTime = TimeSpan.Zero; break; case AdditiveAnimationBaseMode.Animation: baseTime = TimeSpan.FromTicks(time.Ticks % baseAnimation.Duration.Ticks); break; default: throw new ArgumentOutOfRangeException(); } // Generates result = source - base animationOperations.Clear(); animationOperations.Add(AnimationOperation.NewPush(sourceEvaluator, time)); animationOperations.Add(AnimationOperation.NewPush(baseEvaluator, baseTime)); animationOperations.Add(AnimationOperation.NewBlend(CoreAnimationOperation.Subtract, 1.0f)); animationOperations.Add(AnimationOperation.NewPop(resultEvaluator, time)); // Compute AnimationClipResult animationClipResult = null; animationBlender.Compute(animationOperations, ref animationClipResult); } resultAnimation.Duration = sourceAnimation.Duration; resultAnimation.RepeatMode = sourceAnimation.RepeatMode; return(resultAnimation); }
protected override Task <ResultStatus> DoCommandOverride(ICommandContext commandContext) { var nodes = new List <SpriteStudioNode>(); string modelName; if (!SpriteStudioXmlImport.ParseModel(AssetParameters.Source, nodes, out modelName)) { return(null); } var anims = new List <SpriteStudioAnim>(); if (!SpriteStudioXmlImport.ParseAnimations(AssetParameters.Source, anims)) { return(null); } var assetManager = new AssetManager(); var anim = anims.First(x => x.Name == AssetParameters.AnimationName); //Compile the animations var animation = new AnimationClip { Duration = TimeSpan.FromSeconds((1.0 / anim.Fps) * anim.FrameCount), RepeatMode = AssetParameters.RepeatMode }; foreach (var pair in anim.NodesData) { var data = pair.Value; var nodeName = pair.Key; if (data.Data.Count == 0) { continue; } if (data.Data.ContainsKey("POSX")) { var posxCurve = new AnimationCurve <float>(); animation.AddCurve("posx[" + nodeName + "]", posxCurve); posxCurve.InterpolationType = data.Data["POSX"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["POSX"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); posxCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("POSY")) { var posyCurve = new AnimationCurve <float>(); animation.AddCurve("posy[" + nodeName + "]", posyCurve); posyCurve.InterpolationType = data.Data["POSY"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["POSY"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); posyCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("ROTZ")) { var anglCurve = new AnimationCurve <float>(); animation.AddCurve("rotz[" + nodeName + "]", anglCurve); anglCurve.InterpolationType = data.Data["ROTZ"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["ROTZ"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = MathUtil.DegreesToRadians(float.Parse(nodeData["value"], CultureInfo.InvariantCulture)); anglCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("PRIO")) { var prioCurve = new AnimationCurve <int>(); animation.AddCurve("prio[" + nodeName + "]", prioCurve); prioCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["PRIO"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); prioCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("SCLX")) { var scaxCurve = new AnimationCurve <float>(); animation.AddCurve("sclx[" + nodeName + "]", scaxCurve); scaxCurve.InterpolationType = data.Data["SCLX"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["SCLX"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); scaxCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("SCLY")) { var scayCurve = new AnimationCurve <float>(); animation.AddCurve("scly[" + nodeName + "]", scayCurve); scayCurve.InterpolationType = data.Data["SCLY"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["SCLY"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); scayCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("ALPH")) { var tranCurve = new AnimationCurve <float>(); animation.AddCurve("alph[" + nodeName + "]", tranCurve); tranCurve.InterpolationType = data.Data["ALPH"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["ALPH"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); tranCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("HIDE")) { var hideCurve = new AnimationCurve <int>(); animation.AddCurve("hide[" + nodeName + "]", hideCurve); hideCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["HIDE"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); hideCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("FLPH")) { var flphCurve = new AnimationCurve <int>(); animation.AddCurve("flph[" + nodeName + "]", flphCurve); flphCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["FLPH"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); flphCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("FLPV")) { var flpvCurve = new AnimationCurve <int>(); animation.AddCurve("flpv[" + nodeName + "]", flpvCurve); flpvCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["FLPV"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); flpvCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("CELL")) { var cellCurve = new AnimationCurve <int>(); animation.AddCurve("cell[" + nodeName + "]", cellCurve); cellCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["CELL"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); cellCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("COLV")) { var colvCurve = new AnimationCurve <Vector4>(); animation.AddCurve("colv[" + nodeName + "]", colvCurve); colvCurve.InterpolationType = AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["COLV"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var color = new Color4(Color.FromBgra(int.Parse(nodeData["value"], CultureInfo.InvariantCulture))); color = colorSpace == ColorSpace.Linear ? color.ToLinear() : color; colvCurve.KeyFrames.Add(new KeyFrameData <Vector4>(time, color.ToVector4())); } } if (data.Data.ContainsKey("COLB")) { var colbCurve = new AnimationCurve <int>(); animation.AddCurve("colb[" + nodeName + "]", colbCurve); colbCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["COLB"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); colbCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("COLF")) { var colfCurve = new AnimationCurve <float>(); animation.AddCurve("colf[" + nodeName + "]", colfCurve); colfCurve.InterpolationType = data.Data["COLF"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["COLF"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); colfCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } } animation.Optimize(); assetManager.Save(Url, animation); return(Task.FromResult(ResultStatus.Successful)); }
protected override Task <ResultStatus> DoCommandOverride(ICommandContext commandContext) { var nodes = new List <SpriteStudioNode>(); string modelName; if (!SpriteStudioXmlImport.ParseModel(Parameters.Source, nodes, out modelName)) { return(null); } var anims = new List <SpriteStudioAnim>(); if (!SpriteStudioXmlImport.ParseAnimations(Parameters.Source, anims)) { return(null); } var assetManager = new ContentManager(); var anim = anims.First(x => x.Name == Parameters.AnimationName); //Compile the animations var animation = new AnimationClip { Duration = TimeSpan.FromSeconds((1.0 / anim.Fps) * anim.FrameCount), RepeatMode = Parameters.RepeatMode }; var nodeMapping = nodes.Select((x, i) => new { Name = x.Name, Index = i }).ToDictionary(x => x.Name, x => x.Index); foreach (var pair in anim.NodesData) { int nodeIndex; if (!nodeMapping.TryGetValue(pair.Key, out nodeIndex)) { continue; } var data = pair.Value; if (data.Data.Count == 0) { continue; } var keyPrefix = $"[SpriteStudioComponent.Key].Nodes[{nodeIndex}]"; if (data.Data.ContainsKey("POSX")) { var posxCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Position)}.{nameof(Vector2.X)}", posxCurve); posxCurve.InterpolationType = data.Data["POSX"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["POSX"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); posxCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("POSY")) { var posyCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Position)}.{nameof(Vector2.Y)}", posyCurve); posyCurve.InterpolationType = data.Data["POSY"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["POSY"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); posyCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("ROTZ")) { var anglCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.RotationZ)}", anglCurve); anglCurve.InterpolationType = data.Data["ROTZ"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["ROTZ"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = MathUtil.DegreesToRadians(float.Parse(nodeData["value"], CultureInfo.InvariantCulture)); anglCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("PRIO")) { var prioCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Priority)}", prioCurve); prioCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["PRIO"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); prioCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("SCLX")) { var scaxCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Scale)}.{nameof(Vector2.X)}", scaxCurve); scaxCurve.InterpolationType = data.Data["SCLX"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["SCLX"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); scaxCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("SCLY")) { var scayCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Scale)}.{nameof(Vector2.Y)}", scayCurve); scayCurve.InterpolationType = data.Data["SCLY"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["SCLY"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); scayCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("ALPH")) { var tranCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Transparency)}", tranCurve); tranCurve.InterpolationType = data.Data["ALPH"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["ALPH"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); tranCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } if (data.Data.ContainsKey("HIDE")) { var hideCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.Hide)}", hideCurve); hideCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["HIDE"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); hideCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("FLPH")) { var flphCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.HFlipped)}", flphCurve); flphCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["FLPH"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); flphCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("FLPV")) { var flpvCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.VFlipped)}", flpvCurve); flpvCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["FLPV"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); flpvCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("CELL")) { var cellCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.SpriteId)}", cellCurve); cellCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["CELL"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); cellCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("COLV")) { var colvCurve = new AnimationCurve <Vector4>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.BlendColor)}", colvCurve); colvCurve.InterpolationType = AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["COLV"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var color = new Color4(Color.FromBgra(int.Parse(nodeData["value"], CultureInfo.InvariantCulture))); color = colorSpace == ColorSpace.Linear ? color.ToLinear() : color; colvCurve.KeyFrames.Add(new KeyFrameData <Vector4>(time, color.ToVector4())); } } if (data.Data.ContainsKey("COLB")) { var colbCurve = new AnimationCurve <int>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.BlendType)}", colbCurve); colbCurve.InterpolationType = AnimationCurveInterpolationType.Constant; foreach (var nodeData in data.Data["COLB"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = int.Parse(nodeData["value"], CultureInfo.InvariantCulture); colbCurve.KeyFrames.Add(new KeyFrameData <int>(time, value)); } } if (data.Data.ContainsKey("COLF")) { var colfCurve = new AnimationCurve <float>(); animation.AddCurve($"{keyPrefix}.{nameof(SpriteStudioNodeState.BlendFactor)}", colfCurve); colfCurve.InterpolationType = data.Data["COLF"].Any(x => x["curve"] != "linear") ? AnimationCurveInterpolationType.Cubic : AnimationCurveInterpolationType.Linear; foreach (var nodeData in data.Data["COLF"]) { var time = CompressedTimeSpan.FromSeconds((1.0 / anim.Fps) * int.Parse(nodeData["time"], CultureInfo.InvariantCulture)); var value = float.Parse(nodeData["value"], CultureInfo.InvariantCulture); colfCurve.KeyFrames.Add(new KeyFrameData <float>(time, value)); } } } animation.Optimize(); assetManager.Save(Url, animation); return(Task.FromResult(ResultStatus.Successful)); }
private unsafe object ExportAnimation(ICommandContext commandContext, ContentManager contentManager) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, contentManager); // we get model skeleton to compare it to real skeleton we need to map to var animationClips = LoadAnimation(commandContext, contentManager); AnimationClip animationClip = null; if (animationClips.Count > 0) { animationClip = new AnimationClip(); AnimationClip rootMotionAnimationClip = null; // If root motion is explicitely enabled, or if there is no skeleton, try to find root node and apply animation directly on TransformComponent if ((AnimationRootMotion || SkeletonUrl == null) && modelSkeleton.Nodes.Length >= 1) { // No skeleton, map root node only // TODO: For now, it seems to be located on node 1 in FBX files. Need to check if always the case, and what happens with Assimp var rootNode0 = modelSkeleton.Nodes.Length >= 1 ? modelSkeleton.Nodes[0].Name : null; var rootNode1 = modelSkeleton.Nodes.Length >= 2 ? modelSkeleton.Nodes[1].Name : null; if ((rootNode0 != null && animationClips.TryGetValue(rootNode0, out rootMotionAnimationClip)) || (rootNode1 != null && animationClips.TryGetValue(rootNode1, out rootMotionAnimationClip))) { foreach (var channel in rootMotionAnimationClip.Channels) { var curve = rootMotionAnimationClip.Curves[channel.Value.CurveIndex]; // Root motion var channelName = channel.Key; if (channelName.StartsWith("Transform.")) { animationClip.AddCurve($"[TransformComponent.Key]." + channelName.Replace("Transform.", string.Empty), curve); } // Also apply Camera curves // TODO: Add some other curves? if (channelName.StartsWith("Camera.")) { animationClip.AddCurve($"[CameraComponent.Key]." + channelName.Replace("Camera.", string.Empty), curve); } } // Take max of durations if (animationClip.Duration < rootMotionAnimationClip.Duration) { animationClip.Duration = rootMotionAnimationClip.Duration; } } } // Load asset reference skeleton if (SkeletonUrl != null) { var skeleton = contentManager.Load <Skeleton>(SkeletonUrl); var skeletonMapping = new SkeletonMapping(skeleton, modelSkeleton); // Process missing nodes foreach (var nodeAnimationClipEntry in animationClips) { var nodeName = nodeAnimationClipEntry.Key; var nodeAnimationClip = nodeAnimationClipEntry.Value; var nodeIndex = modelSkeleton.Nodes.IndexOf(x => x.Name == nodeName); // Node doesn't exist in skeleton? skip it if (nodeIndex == -1 || skeletonMapping.SourceToSource[nodeIndex] != nodeIndex) { continue; } // Skip root motion node (if any) if (nodeAnimationClip == rootMotionAnimationClip) { continue; } // Find parent node var parentNodeIndex = modelSkeleton.Nodes[nodeIndex].ParentIndex; if (parentNodeIndex != -1 && skeletonMapping.SourceToSource[parentNodeIndex] != parentNodeIndex) { // Some nodes were removed, we need to concat the anim curves var currentNodeIndex = nodeIndex; var nodesToMerge = new List <Tuple <ModelNodeDefinition, AnimationBlender, AnimationClipEvaluator> >(); while (currentNodeIndex != -1 && currentNodeIndex != skeletonMapping.SourceToSource[parentNodeIndex]) { AnimationClip animationClipToMerge; AnimationClipEvaluator animationClipEvaluator = null; AnimationBlender animationBlender = null; if (animationClips.TryGetValue(modelSkeleton.Nodes[currentNodeIndex].Name, out animationClipToMerge)) { animationBlender = new AnimationBlender(); animationClipEvaluator = animationBlender.CreateEvaluator(animationClipToMerge); } nodesToMerge.Add(Tuple.Create(modelSkeleton.Nodes[currentNodeIndex], animationBlender, animationClipEvaluator)); currentNodeIndex = modelSkeleton.Nodes[currentNodeIndex].ParentIndex; } // Put them in proper parent to children order nodesToMerge.Reverse(); // Find all key times // TODO: We should detect discontinuities and keep them var animationKeysSet = new HashSet <CompressedTimeSpan>(); foreach (var node in nodesToMerge) { foreach (var curve in node.Item3.Clip.Curves) { foreach (CompressedTimeSpan time in curve.Keys) { animationKeysSet.Add(time); } } } // Sort key times var animationKeys = animationKeysSet.ToList(); animationKeys.Sort(); var animationOperations = new FastList <AnimationOperation>(); var combinedAnimationClip = new AnimationClip(); var translationCurve = new AnimationCurve <Vector3>(); var rotationCurve = new AnimationCurve <Quaternion>(); var scaleCurve = new AnimationCurve <Vector3>(); // Evaluate at every key frame foreach (var animationKey in animationKeys) { var matrix = Matrix.Identity; // Evaluate node foreach (var node in nodesToMerge) { // Get default position var modelNodeDefinition = node.Item1; // Compute AnimationClipResult animationClipResult = null; animationOperations.Clear(); animationOperations.Add(AnimationOperation.NewPush(node.Item3, animationKey)); node.Item2.Compute(animationOperations, ref animationClipResult); var updateMemberInfos = new List <UpdateMemberInfo>(); foreach (var channel in animationClipResult.Channels) { updateMemberInfos.Add(new UpdateMemberInfo { Name = channel.PropertyName, DataOffset = channel.Offset }); } // TODO: Cache this var compiledUpdate = UpdateEngine.Compile(typeof(ModelNodeDefinition), updateMemberInfos); unsafe { fixed(byte *data = animationClipResult.Data) UpdateEngine.Run(modelNodeDefinition, compiledUpdate, (IntPtr)data, null); } Matrix localMatrix; TransformComponent.CreateMatrixTRS(ref modelNodeDefinition.Transform.Position, ref modelNodeDefinition.Transform.Rotation, ref modelNodeDefinition.Transform.Scale, out localMatrix); matrix = Matrix.Multiply(localMatrix, matrix); } // Done evaluating, let's decompose matrix TransformTRS transform; matrix.Decompose(out transform.Scale, out transform.Rotation, out transform.Position); // Create a key translationCurve.KeyFrames.Add(new KeyFrameData <Vector3>(animationKey, transform.Position)); rotationCurve.KeyFrames.Add(new KeyFrameData <Quaternion>(animationKey, transform.Rotation)); scaleCurve.KeyFrames.Add(new KeyFrameData <Vector3>(animationKey, transform.Scale)); } combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Position)}", translationCurve); combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Rotation)}", rotationCurve); combinedAnimationClip.AddCurve($"{nameof(ModelNodeTransformation.Transform)}.{nameof(TransformTRS.Scale)}", scaleCurve); nodeAnimationClip = combinedAnimationClip; } foreach (var channel in nodeAnimationClip.Channels) { var curve = nodeAnimationClip.Curves[channel.Value.CurveIndex]; // TODO: Root motion var channelName = channel.Key; if (channelName.StartsWith("Transform.")) { animationClip.AddCurve($"[ModelComponent.Key].Skeleton.NodeTransformations[{skeletonMapping.SourceToTarget[nodeIndex]}]." + channelName, curve); } } // Take max of durations if (animationClip.Duration < nodeAnimationClip.Duration) { animationClip.Duration = nodeAnimationClip.Duration; } } } } if (animationClip == null) { commandContext.Logger.Info("File {0} has an empty animation.", SourcePath); } else { if (animationClip.Duration.Ticks == 0) { commandContext.Logger.Warning("File {0} has a 0 tick long animation.", SourcePath); } // Optimize and set common parameters animationClip.RepeatMode = AnimationRepeatMode; animationClip.Optimize(); } return(animationClip); }