public override void Start() { base.Start(); if (AnimationComponent == null) throw new InvalidOperationException("The animation component is not set"); if (AnimationIdle == null) throw new InvalidOperationException("Idle animation is not set"); if (AnimationWalk == null) throw new InvalidOperationException("Walking animation is not set"); if (AnimationShoot == null) throw new InvalidOperationException("Shooting animation is not set"); if (AnimationReload == null) throw new InvalidOperationException("Reloading animation is not set"); // By setting a custom blend tree builder we can override the default behavior of the animation system // Instead, BuildBlendTree(FastList<AnimationOperation> blendStack) will be called each frame AnimationComponent.BlendTreeBuilder = this; animEvaluatorIdle = AnimationComponent.Blender.CreateEvaluator(AnimationIdle); animEvaluatorWalk = AnimationComponent.Blender.CreateEvaluator(AnimationWalk); animEvaluatorShoot = AnimationComponent.Blender.CreateEvaluator(AnimationShoot); animEvaluatorReload = AnimationComponent.Blender.CreateEvaluator(AnimationReload); currentEvaluator = animEvaluatorIdle; currentClip = AnimationIdle; }
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)); }
protected override async Task LoadContent() { await base.LoadContent(); var knightModel = Content.Load<Model>("knight Model"); knight = new Entity { new ModelComponent { Model = knightModel } }; knight.Transform.Position = new Vector3(0, 0f, 0f); var animationComponent = knight.GetOrCreate<AnimationComponent>(); animationComponent.Animations.Add("Run", Content.Load<AnimationClip>("knight Run")); animationComponent.Animations.Add("Idle", Content.Load<AnimationClip>("knight Idle")); // We will test both non-optimized and optimized clips megalodonClip = CreateModelChangeAnimation(new ProceduralModelDescriptor(new CubeProceduralModel { Size = Vector3.One, MaterialInstance = { Material = knightModel.Materials[0].Material } }).GenerateModel(Services)); knightOptimizedClip = CreateModelChangeAnimation(Content.Load<Model>("knight Model")); knightOptimizedClip.Optimize(); animationComponent.Animations.Add("ChangeModel1", megalodonClip); animationComponent.Animations.Add("ChangeModel2", knightOptimizedClip); Scene.Entities.Add(knight); camera = new TestCamera(); CameraComponent = camera.Camera; Script.Add(camera); camera.Position = new Vector3(6.0f, 2.5f, 1.5f); camera.SetTarget(knight, true); }
internal void Initialize(AnimationClip clip, List<AnimationBlender.Channel> channels) { this.BlenderChannels = channels; this.clip = clip; clip.Freeze(); // If there are optimized curve data, instantiate (first time) and initialize appropriate evaluators if (clip.OptimizedAnimationDatas != null) { foreach (var optimizedData in clip.OptimizedAnimationDatas) { var optimizedEvaluatorGroup = curveEvaluatorGroups.OfType<AnimationCurveEvaluatorOptimizedGroup>().FirstOrDefault(x => x.ElementType == optimizedData.ElementType); if (optimizedEvaluatorGroup == null) { optimizedEvaluatorGroup = optimizedData.CreateEvaluator(); curveEvaluatorGroups.Add(optimizedEvaluatorGroup); } optimizedEvaluatorGroup.Initialize(optimizedData); } } // Add already existing channels for (int index = 0; index < channels.Count; index++) { var channel = channels[index]; AddChannel(ref channel); } }
protected override async Task LoadContent() { await base.LoadContent(); var knightModel = Asset.Load<Model>("knight Model"); knight = new Entity { new ModelComponent { Model = knightModel } }; knight.Transform.Position = new Vector3(0, 0f, 0f); var animationComponent = knight.GetOrCreate<AnimationComponent>(); animationComponent.Animations.Add("Run", Asset.Load<AnimationClip>("knight Run")); animationComponent.Animations.Add("Idle", Asset.Load<AnimationClip>("knight Idle")); // We will test both non-optimized and optimized clips megalodonClip = CreateModelChangeAnimation(new ProceduralModelDescriptor(new CubeProceduralModel { Size = Vector3.One, MaterialInstance = { Material = knightModel.Materials[0].Material } }).GenerateModel(Services)); knightOptimizedClip = CreateModelChangeAnimation(Asset.Load<Model>("knight Model")); knightOptimizedClip.Optimize(); animationComponent.Animations.Add("ChangeModel1", megalodonClip); animationComponent.Animations.Add("ChangeModel2", knightOptimizedClip); Scene.Entities.Add(knight); camera = new TestCamera(); CameraComponent = camera.Camera; Script.Add(camera); LightingKeys.EnableFixedAmbientLight(GraphicsDevice.Parameters, true); GraphicsDevice.Parameters.Set(EnvironmentLightKeys.GetParameterKey(LightSimpleAmbientKeys.AmbientLight, 0), (Color3)Color.White); camera.Position = new Vector3(6.0f, 2.5f, 1.5f); camera.SetTarget(knight, true); }
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; }
internal void Cleanup() { foreach (var curveEvaluatorGroup in curveEvaluatorGroups) { curveEvaluatorGroup.Cleanup(); } Channels.Clear(); BlenderChannels = null; clip = null; }
public override void Start() { base.Start(); if (AnimationComponent == null) throw new InvalidOperationException("The animation component is not set"); if (AnimationIdle == null) throw new InvalidOperationException("Idle animation is not set"); if (AnimationWalk == null) throw new InvalidOperationException("Walking animation is not set"); if (AnimationRun == null) throw new InvalidOperationException("Running animation is not set"); if (AnimationJumpStart == null) throw new InvalidOperationException("Jumping animation is not set"); if (AnimationJumpMid == null) throw new InvalidOperationException("Airborne animation is not set"); if (AnimationJumpEnd == null) throw new InvalidOperationException("Landing animation is not set"); // By setting a custom blend tree builder we can override the default behavior of the animation system // Instead, BuildBlendTree(FastList<AnimationOperation> blendStack) will be called each frame AnimationComponent.BlendTreeBuilder = this; animEvaluatorIdle = AnimationComponent.Blender.CreateEvaluator(AnimationIdle); animEvaluatorWalk = AnimationComponent.Blender.CreateEvaluator(AnimationWalk); animEvaluatorRun = AnimationComponent.Blender.CreateEvaluator(AnimationRun); animEvaluatorJumpStart = AnimationComponent.Blender.CreateEvaluator(AnimationJumpStart); animEvaluatorJumpMid = AnimationComponent.Blender.CreateEvaluator(AnimationJumpMid); animEvaluatorJumpEnd = AnimationComponent.Blender.CreateEvaluator(AnimationJumpEnd); // Initial walk lerp walkLerpFactor = 0; animEvaluatorWalkLerp1 = animEvaluatorIdle; animEvaluatorWalkLerp2 = animEvaluatorWalk; animationClipWalkLerp1 = AnimationIdle; animationClipWalkLerp2 = AnimationWalk; }
private void UpdateWalking() { if (runSpeed < WalkThreshold) { walkLerpFactor = runSpeed / WalkThreshold; walkLerpFactor = (float)Math.Sqrt(walkLerpFactor); // Idle-Walk blend looks really werid, so skew the factor towards walking animEvaluatorWalkLerp1 = animEvaluatorIdle; animEvaluatorWalkLerp2 = animEvaluatorWalk; animationClipWalkLerp1 = AnimationIdle; animationClipWalkLerp2 = AnimationWalk; } else { walkLerpFactor = (runSpeed - WalkThreshold) / (1.0f - WalkThreshold); animEvaluatorWalkLerp1 = animEvaluatorWalk; animEvaluatorWalkLerp2 = animEvaluatorRun; animationClipWalkLerp1 = AnimationWalk; animationClipWalkLerp2 = AnimationRun; } // Use DrawTime rather than UpdateTime var time = Game.DrawTime; // This update function will account for animation with different durations, keeping a current time relative to the blended maximum duration long blendedMaxDuration = 0; blendedMaxDuration = (long)MathUtil.Lerp(animationClipWalkLerp1.Duration.Ticks, animationClipWalkLerp2.Duration.Ticks, walkLerpFactor); var currentTicks = TimeSpan.FromTicks((long)(currentTime * blendedMaxDuration)); currentTicks = blendedMaxDuration == 0 ? TimeSpan.Zero : TimeSpan.FromTicks((currentTicks.Ticks + (long)(time.Elapsed.Ticks * TimeFactor)) % blendedMaxDuration); currentTime = ((double)currentTicks.Ticks / (double)blendedMaxDuration); }
internal bool attached; // Is it part of a AnimationComponent.PlayingAnimations collection? internal PlayingAnimation(string name, AnimationClip clip) : this() { Name = name; Clip = clip; RepeatMode = Clip.RepeatMode; }
private unsafe object ExportAnimation(ICommandContext commandContext, AssetManager assetManager) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, assetManager); // we get model skeleton to compare it to real skeleton we need to map to var animationClips = LoadAnimation(commandContext, assetManager); 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 = assetManager.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; }
public AnimationClipEvaluator CreateEvaluator(AnimationClip clip) { // Check if this clip has already been used if (clips.Add(clip)) { // If new clip, let's scan its channel to add new ones. foreach (var curve in clip.Channels) { Channel channel; if (channelsByName.TryGetValue(curve.Key, out channel)) { // TODO: Check if channel matches } else { // New channel, add it to every evaluator // Find blend type BlendType blendType; var elementType = curve.Value.ElementType; if (elementType == typeof(Quaternion)) { blendType = BlendType.Quaternion; } else if (elementType == typeof(float)) { blendType = BlendType.Float1; } else if (elementType == typeof(Vector2)) { blendType = BlendType.Float2; } else if (elementType == typeof(Vector3)) { blendType = BlendType.Float3; } else if (elementType == typeof(Vector4)) { blendType = BlendType.Float4; } else { blendType = BlittableHelper.IsBlittable(elementType) ? BlendType.Blit : BlendType.Object; } // Create channel structure channel.BlendType = blendType; channel.Offset = blendType == BlendType.Object ? objectsSize : structureSize; channel.PropertyName = curve.Key; channel.Size = curve.Value.ElementSize; // Add channel channelsByName.Add(channel.PropertyName, channel); channels.Add(channel); if (blendType == BlendType.Object) { objectsSize++; } else { // Update new structure size // We also reserve space for a float that will specify channel existence and factor in case of subtree blending structureSize += sizeof(float) + channel.Size; } // Add new channel update info to every evaluator // TODO: Maybe it's better lazily done? (avoid need to store list of all evaluators) foreach (var currentEvaluator in evaluators) { currentEvaluator.AddChannel(ref channel); } } } } // Update results to fit the new data size lock (availableResultsPool) { foreach (var result in availableResultsPool) { if (result.DataSize < structureSize) { result.DataSize = structureSize; result.Data = new byte[structureSize]; } } } // Create evaluator and store it in list of instantiated evaluators AnimationClipEvaluator evaluator; lock (evaluatorPool) { if (evaluatorPool.Count > 0) { evaluator = evaluatorPool.Pop(); } else { evaluator = new AnimationClipEvaluator(); } } evaluator.Initialize(clip, channels); evaluators.Add(evaluator); return evaluator; }
private void SwitchToDefaultState() { currentTime = (state == defaultState) ? currentTime : 0; state = defaultState; if (state == AnimationState.Idle) { currentClip = AnimationIdle; currentEvaluator = animEvaluatorIdle; } else { currentClip = AnimationWalk; currentEvaluator = animEvaluatorWalk; } }
public override void Update() { runSpeedEvent.TryReceive(out runSpeed); defaultState = (runSpeed > 0.15f) ? AnimationState.Walking : AnimationState.Idle; WeaponFiredResult weaponResult; var didFire = weaponFiredEvent.TryReceive(out weaponResult); bool isReloading; var didReload = isReloadingEvent.TryReceive(out isReloading); isReloading |= didReload; // Update current animation var currentTicks = TimeSpan.FromTicks((long)(currentTime * currentClip.Duration.Ticks)); var updatedTicks = currentTicks.Ticks + (long)(Game.DrawTime.Elapsed.Ticks * TimeFactor); var currentClipFinished = (updatedTicks >= currentClip.Duration.Ticks); currentTicks = TimeSpan.FromTicks(updatedTicks % currentClip.Duration.Ticks); currentTime = ((double)currentTicks.Ticks / (double)currentClip.Duration.Ticks); // State change if necessary if (isReloading) { if (state != AnimationState.Reloading) { currentTime = 0; state = AnimationState.Reloading; currentClip = AnimationReload; currentEvaluator = animEvaluatorReload; } } else if (didFire) { if (state != AnimationState.Shooting) { currentTime = 0; state = AnimationState.Shooting; currentClip = AnimationShoot; currentEvaluator = animEvaluatorShoot; } } else if (currentClipFinished) { SwitchToDefaultState(); } else if ((state == AnimationState.Idle || state == AnimationState.Walking) && state != defaultState) { SwitchToDefaultState(); } }
public AnimationClipEvaluator CreateEvaluator(AnimationClip clip) { // Check if this clip has already been used if (clips.Add(clip)) { // If new clip, let's scan its channel to add new ones. foreach (var curve in clip.Channels) { Channel channel; if (channelsByName.TryGetValue(curve.Key, out channel)) { // TODO: Check if channel matches } else { // New channel, add it to every evaluator // Find blend type BlendType blendType; var elementType = curve.Value.ElementType; if (elementType == typeof(Quaternion)) { blendType = BlendType.Quaternion; } else if (elementType == typeof(float)) { blendType = BlendType.Float1; } else if (elementType == typeof(Vector2)) { blendType = BlendType.Float2; } else if (elementType == typeof(Vector3)) { blendType = BlendType.Float3; } else if (elementType == typeof(Vector4)) { blendType = BlendType.Float4; } else { blendType = BlittableHelper.IsBlittable(elementType) ? BlendType.Blit : BlendType.Object; } // Create channel structure channel.BlendType = blendType; channel.Offset = blendType == BlendType.Object ? objectsSize : structureSize; channel.PropertyName = curve.Key; channel.Size = curve.Value.ElementSize; // Add channel channelsByName.Add(channel.PropertyName, channel); channels.Add(channel); if (blendType == BlendType.Object) { objectsSize++; } else { // Update new structure size // We also reserve space for a float that will specify channel existence and factor in case of subtree blending structureSize += sizeof(float) + channel.Size; } // Add new channel update info to every evaluator // TODO: Maybe it's better lazily done? (avoid need to store list of all evaluators) foreach (var currentEvaluator in evaluators) { currentEvaluator.AddChannel(ref channel); } } } } // Update results to fit the new data size lock (availableResultsPool) { foreach (var result in availableResultsPool) { if (result.DataSize < structureSize) { result.DataSize = structureSize; result.Data = new byte[structureSize]; } } } // Create evaluator and store it in list of instantiated evaluators AnimationClipEvaluator evaluator; lock (evaluatorPool) { if (evaluatorPool.Count > 0) { evaluator = evaluatorPool.Pop(); } else { evaluator = new AnimationClipEvaluator(); } } evaluator.Initialize(clip, channels); evaluators.Add(evaluator); return(evaluator); }