diff --git a/Common/Model/Animation/AnimatorBase.cs b/Common/Model/Animation/AnimatorBase.cs index 7e477cae..ad04d04f 100644 --- a/Common/Model/Animation/AnimatorBase.cs +++ b/Common/Model/Animation/AnimatorBase.cs @@ -1,292 +1,298 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Text; using Vintagestory.API.Common.Entities; -using Vintagestory.API.Config; using Vintagestory.API.MathTools; -#nullable disable +namespace Vintagestory.API.Common; -namespace Vintagestory.API.Common +#nullable enable + +public delegate double WalkSpeedSupplierDelegate(); + +public class AnimFrameCallback { - public delegate double WalkSpeedSupplierDelegate(); + public float Frame; + public string? Animation; + public Action? Callback; +} - public class AnimFrameCallback - { - public float Frame; - public string Animation; - public Action Callback; - } +/// +/// Syncs every frame with entity.ActiveAnimationsByAnimCode, starts, progresses and stops animations when necessary +/// +public abstract class AnimatorBase : IAnimator +{ + public const int DEFAULT_MAX_ANIM = 1; + protected RunningAnimation[] curAnims = new RunningAnimation[DEFAULT_MAX_ANIM]; + + public static readonly float[] identMat = Mat4f.Create(); + public static readonly HashSet logAntiSpam = []; + + private readonly WalkSpeedSupplierDelegate? walkSpeedSupplier; + private readonly Action? onAnimationStoppedListener = null; + + public ShapeElement[]? RootElements; + public List? RootPoses; + + private float accum = 0.25f; + private double walkSpeed; /// - /// Syncs every frame with entity.ActiveAnimationsByAnimCode, starts, progresses and stops animations when necessary + /// We skip the last row - https://stackoverflow.com/questions/32565827/whats-the-purpose-of-magic-4-of-last-row-in-matrix-4x4-for-3d-graphics /// - public abstract class AnimatorBase : IAnimator - { - public static readonly float[] identMat = Mat4f.Create(); - public static readonly HashSet logAntiSpam = new HashSet(); - - WalkSpeedSupplierDelegate WalkSpeedSupplier; - Action onAnimationStoppedListener = null; - protected int activeAnimCount = 0; - - public ShapeElement[] RootElements; - public List RootPoses; - /// - /// A RunningAnimation object for each of the possible Animations for this object - /// - public RunningAnimation[] anims; - private readonly Dictionary animsByCode; // For performance, allows quick lookups instead of enumerating the whole anims array - /// - /// We skip the last row - https://stackoverflow.com/questions/32565827/whats-the-purpose-of-magic-4-of-last-row-in-matrix-4x4-for-3d-graphics - /// - public float[] TransformationMatrices; - /// - /// The entity's default pose. Meaning for most elements this is the identity matrix, with exception of individually controlled elements such as the head. - /// - public float[] TransformationMatricesDefaultPose; - public Dictionary AttachmentPointByCode = new Dictionary();// StringComparer.OrdinalIgnoreCase); - breaks fp hands for some reason - public RunningAnimation[] CurAnims = new RunningAnimation[20]; - private readonly List activeOrRunning = new(2); // For performance, a short list of the anims which are currently or were recently Active or Running. We add to this List in the one place where .Active state is set to true - public Entity entityForLogging; - - public bool CalculateMatrices { get; set; } = true; - - public float[] Matrices - { - get { - return activeAnimCount > 0 ? TransformationMatrices : TransformationMatricesDefaultPose; - } - } + protected float[]? transformationMatrices; - public int ActiveAnimationCount - { - get { return activeAnimCount; } - } + /// + /// The entity's default pose. Meaning for most elements this is the identity matrix, with exception of individually controlled elements such as the head. + /// + protected float[]? transformationMatricesDefaultPose; - [Obsolete("Use Animations instead")] - public RunningAnimation[] RunningAnimations => Animations; - public RunningAnimation[] Animations => anims; + // Always assume these were filled out at some point. + public float[] Matrices => ActiveAnimationCount > 0 ? transformationMatrices! : transformationMatricesDefaultPose!; - public abstract int MaxJointId { get; } + /// + /// All possible animations mapped to their code. + /// + private readonly Dictionary animsByCode = []; - public RunningAnimation GetAnimationState(string code) - { - if (code == null) return null; - animsByCode.TryGetValue(code.ToLowerInvariant(), out var anim); - return anim; - } + /// + /// All attachment points mapped to their code. + /// + protected Dictionary attachmentPointByCode = []; // StringComparer.OrdinalIgnoreCase); - breaks fp hands for some reason. + + private readonly List activeOrRunning = new(2); // For performance, a short list of the anims which are currently or were recently Active or Running. We add to this List in the one place where .Active state is set to true. + + public bool CalculateMatrices { get; set; } = true; - public AnimatorBase(WalkSpeedSupplierDelegate WalkSpeedSupplier, Animation[] Animations, Action onAnimationStoppedListener = null) + public int ActiveAnimationCount { get; protected set; } + + /// + /// A RunningAnimation object for each of the possible Animations for this object. + /// + protected RunningAnimation[] allEntityAnimations = []; + + [Obsolete("Use Animations instead")] + public RunningAnimation[] RunningAnimations => Animations; + public RunningAnimation[] Animations => allEntityAnimations; + + public abstract int MaxJointId { get; } + protected Entity? entityForLogging; + + public RunningAnimation? GetAnimationState(string? code) + { + if (code == null) return null; + animsByCode.TryGetValue(code.ToLowerInvariant(), out RunningAnimation? anim); + return anim; + } + + public AnimatorBase(WalkSpeedSupplierDelegate walkSpeedSupplier, Animation[]? allEntityAnimations, Action? onAnimationStoppedListener = null) + { + this.walkSpeedSupplier = walkSpeedSupplier; + this.onAnimationStoppedListener = onAnimationStoppedListener; + + this.allEntityAnimations = allEntityAnimations == null ? [] : new RunningAnimation[allEntityAnimations.Length]; + Dictionary animsByCodeLocal = animsByCode; + + if (allEntityAnimations == null) return; + + for (int i = 0; i < this.allEntityAnimations.Length; i++) { - this.WalkSpeedSupplier = WalkSpeedSupplier; - this.onAnimationStoppedListener = onAnimationStoppedListener; + Animation anim = allEntityAnimations[i]; - var anims = this.anims = Animations == null ? Array.Empty() : new RunningAnimation[Animations.Length]; - animsByCode = new(anims.Length); - var animsByCodeLocal = animsByCode; + // Lol checking all the animation codes for lowercase EVERY TIME an entity is made. + anim.Code = anim.Code.ToLowerInvariant(); - for (int i = 0; i < anims.Length; i++) + RunningAnimation newAnim = new() { - var Anim = Animations[i]; - Anim.Code = Anim.Code.ToLowerInvariant(); + Animation = anim + }; - RunningAnimation newAnim = new RunningAnimation() - { - //Active = false, // No need to specify the default values - //Running = false, - //CurrentFrame = 0, - Animation = Anim - }; - anims[i] = newAnim; - animsByCodeLocal[Anim.Code] = newAnim; - } + this.allEntityAnimations[i] = newAnim; + animsByCodeLocal[anim.Code] = newAnim; } + } + + protected abstract void IncreaseAnimationCapacity(); + + public virtual void OnFrame(Dictionary activeAnimationsByAnimCode, float dt) + { + ActiveAnimationCount = 0; - float accum = 0.25f; - double walkSpeed; + if ((accum += dt) > 0.25f) + { + walkSpeed = walkSpeedSupplier == null ? 1f : walkSpeedSupplier(); + accum = 0; + } - public virtual void OnFrame(Dictionary activeAnimationsByAnimCode, float dt) + string? missingAnimCode = null; + foreach (string code in activeAnimationsByAnimCode.Keys) { - activeAnimCount = 0; + if (!animsByCode.TryGetValue(code.ToLowerInvariant(), out RunningAnimation? anim)) { missingAnimCode = code; continue; } - if ((accum += dt) > 0.25f) - { - walkSpeed = WalkSpeedSupplier == null ? 1f : WalkSpeedSupplier(); - accum = 0; - } - - string missingAnimCode = null; - foreach (var code in activeAnimationsByAnimCode.Keys) + // Animation got started. + if (!anim.Active) { - if (!animsByCode.TryGetValue(code.ToLowerInvariant(), out RunningAnimation anim)) { missingAnimCode = code; continue; } - // Animation got started - if (!anim.Active) - { - AnimNowActive(anim, activeAnimationsByAnimCode[code]); - } + AnimNowActive(anim, activeAnimationsByAnimCode[code]); } - if (missingAnimCode != null) + } + if (missingAnimCode != null) + { + activeAnimationsByAnimCode.Remove(missingAnimCode); + if (entityForLogging != null) { - activeAnimationsByAnimCode.Remove(missingAnimCode); - if (entityForLogging != null) + string entityCode = entityForLogging.Code.ToShortString(); + string logRecord = entityCode + "|" + missingAnimCode; + if (logAntiSpam.Add(logRecord)) { - string entityCode = entityForLogging.Code.ToShortString(); - string logRecord = entityCode + "|" + missingAnimCode; - if (logAntiSpam.Add(logRecord)) - { - entityForLogging.World.Logger.Debug(entityCode + " attempted to play an animation code which its shape does not have: \"" + missingAnimCode + "\""); - } + entityForLogging.World.Logger.Debug(entityCode + " attempted to play an animation code which its shape does not have: \"" + missingAnimCode + "\""); } } + } - var activeAnims = this.activeOrRunning; - for (int i = activeAnims.Count - 1; i >= 0; i--) + List activeAnims = activeOrRunning; + while (activeAnims.Count > curAnims.Length) + { + IncreaseAnimationCapacity(); + } + + for (int i = activeAnims.Count - 1; i >= 0; i--) + { + RunningAnimation anim = activeAnims[i]; + if (anim.Active && !activeAnimationsByAnimCode.ContainsKey(anim.Animation.Code)) // wasActive and now should not be active because it is no longer in activeAnimationsByCode. { - RunningAnimation anim = activeAnims[i]; - if (anim.Active && !activeAnimationsByAnimCode.ContainsKey(anim.Animation.Code)) // wasActive and now should not be active because it is no longer in activeAnimationsByCode + // Animation got stopped. + anim.Active = false; + + EnumEntityActivityStoppedHandling onActivityStop = anim.Animation.OnActivityStopped; + if (onActivityStop == EnumEntityActivityStoppedHandling.Rewind) { - // Animation got stopped - anim.Active = false; - - EnumEntityActivityStoppedHandling onActivityStop = anim.Animation.OnActivityStopped; - if (onActivityStop == EnumEntityActivityStoppedHandling.Rewind) - { - anim.ShouldRewind = true; - } - - if (onActivityStop == EnumEntityActivityStoppedHandling.Stop) - { - anim.Stop(); - onAnimationStoppedListener?.Invoke(anim.Animation.Code); - } - - if (onActivityStop == EnumEntityActivityStoppedHandling.PlayTillEnd) - { - anim.ShouldPlayTillEnd = true; - } + anim.ShouldRewind = true; } - if (anim.Running) // Note: an animation may still need to be running even after it is no longer active, if it has Rewind / PlayTillEnd etc + if (onActivityStop == EnumEntityActivityStoppedHandling.Stop) { - if (!ProgressRunningAnimation(anim, dt)) - { - var Code = anim.Animation.Code; - activeAnimationsByAnimCode.Remove(Code); - onAnimationStoppedListener?.Invoke(Code); - } + anim.Stop(); + onAnimationStoppedListener?.Invoke(anim.Animation.Code); } - if (!anim.Active && !anim.Running) + if (onActivityStop == EnumEntityActivityStoppedHandling.PlayTillEnd) { - activeAnims.RemoveAt(i); // No CME or other problems, as we are counting through the list backwards starting at the end, remove only affects this and the ones later than it + anim.ShouldPlayTillEnd = true; } } - calculateMatrices(dt); - } - - /// - /// The return value of false indicates the animation stopped, and requires removal from activeAnimationsByAnimCode this tick - /// (it could also stop and be removed NEXT tick...) - /// - /// - /// - /// False if the animation should immediately stop; true otherwise - private bool ProgressRunningAnimation(RunningAnimation anim, float dt) - { - EnumEntityAnimationEndHandling onAnimationEnd = anim.Animation.OnAnimationEnd; - EnumEntityActivityStoppedHandling onActivityStopped = anim.Animation.OnActivityStopped; - bool shouldStop = false; - if (anim.Iterations > 0) + if (anim.Running) // Note: an animation may still need to be running even after it is no longer active, if it has Rewind / PlayTillEnd etc. { - shouldStop = - (onAnimationEnd == EnumEntityAnimationEndHandling.Stop) || - (!anim.Active && (onActivityStopped == EnumEntityActivityStoppedHandling.PlayTillEnd || onActivityStopped == EnumEntityActivityStoppedHandling.EaseOut) && anim.EasingFactor < 0.002f) || - (onAnimationEnd == EnumEntityAnimationEndHandling.EaseOut && anim.EasingFactor < 0.002f) - ; + if (!ProgressRunningAnimation(anim, dt)) + { + string Code = anim.Animation.Code; + activeAnimationsByAnimCode.Remove(Code); + onAnimationStoppedListener?.Invoke(Code); + } } - else if (anim.Iterations < 0) + + if (!anim.Active && !anim.Running) { - shouldStop = !anim.Active && onActivityStopped == EnumEntityActivityStoppedHandling.Rewind && anim.EasingFactor < 0.002f; + activeAnims.RemoveAt(i); // No CME or other problems, as we are counting through the list backwards starting at the end, remove only affects this and the ones later than it. } + } - if (shouldStop) - { - anim.Stop(); - if (onAnimationEnd == EnumEntityAnimationEndHandling.Stop || onAnimationEnd == EnumEntityAnimationEndHandling.EaseOut) - { - return false; - } - return true; - } + CalculateOutputMatrices(dt); + } - CurAnims[activeAnimCount++] = anim; + /// + /// The return value of false indicates the animation stopped, and requires removal from activeAnimationsByAnimCode this tick. + /// (it could also stop and be removed NEXT tick...) + /// + /// False if the animation should immediately stop; true otherwise. + private bool ProgressRunningAnimation(RunningAnimation anim, float dt) + { + EnumEntityAnimationEndHandling onAnimationEnd = anim.Animation.OnAnimationEnd; + EnumEntityActivityStoppedHandling onActivityStopped = anim.Animation.OnActivityStopped; - if (anim.Iterations != 0 && ((!anim.Active && onAnimationEnd == EnumEntityAnimationEndHandling.Hold) || (onAnimationEnd == EnumEntityAnimationEndHandling.EaseOut))) - { - anim.EaseOut(dt); - } + bool shouldStop = false; - anim.Progress(dt, (float)walkSpeed); - return true; + if (anim.Iterations > 0) + { + shouldStop = + (onAnimationEnd == EnumEntityAnimationEndHandling.Stop) || + (!anim.Active && (onActivityStopped == EnumEntityActivityStoppedHandling.PlayTillEnd || onActivityStopped == EnumEntityActivityStoppedHandling.EaseOut) && anim.EasingFactor < 0.002f) || + (onAnimationEnd == EnumEntityAnimationEndHandling.EaseOut && anim.EasingFactor < 0.002f) + ; } - - - public virtual string DumpCurrentState() + else if (anim.Iterations < 0) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < anims.Length; i++) - { - RunningAnimation anim = anims[i]; - - if (anim.Active && anim.Running) sb.Append("Active&Running: " + anim.Animation.Code); - else if (anim.Active) sb.Append("Active: " + anim.Animation.Code); - else if (anim.Running) sb.Append("Running: " + anim.Animation.Code); - else continue; - - sb.Append(", easing: " + anim.EasingFactor); - sb.Append(", currentframe: " + anim.CurrentFrame); - sb.Append(", iterations: " + anim.Iterations); - sb.Append(", blendedweight: " + anim.BlendedWeight); - sb.Append(", animmetacode: " + anim.meta.Code); - sb.AppendLine(); - } - - return sb.ToString(); + shouldStop = !anim.Active && onActivityStopped == EnumEntityActivityStoppedHandling.Rewind && anim.EasingFactor < 0.002f; } - protected virtual void AnimNowActive(RunningAnimation anim, AnimationMetaData animData) + if (shouldStop) { - anim.Running = true; - anim.Active = true; - if (!activeOrRunning.Contains(anim)) activeOrRunning.Add(anim); - anim.meta = animData; - anim.ShouldRewind = false; - anim.ShouldPlayTillEnd = false; - anim.CurrentFrame = animData.StartFrameOnce; - animData.StartFrameOnce = 0; + anim.Stop(); + return onAnimationEnd is not EnumEntityAnimationEndHandling.Stop and not EnumEntityAnimationEndHandling.EaseOut; } - protected abstract void calculateMatrices(float dt); - + curAnims[ActiveAnimationCount++] = anim; - public AttachmentPointAndPose GetAttachmentPointPose(string code) + if (anim.Iterations != 0 && ((!anim.Active && onAnimationEnd == EnumEntityAnimationEndHandling.Hold) || (onAnimationEnd == EnumEntityAnimationEndHandling.EaseOut))) { - AttachmentPointByCode.TryGetValue(code, out AttachmentPointAndPose apap); - return apap; + anim.EaseOut(dt); } - public virtual ElementPose GetPosebyName(string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) - { - throw new NotImplementedException(); - } + anim.Progress(dt, (float)walkSpeed); + return true; + } + + public virtual string DumpCurrentState() + { + StringBuilder sb = new(); - public virtual void ReloadAttachmentPoints() + for (int i = 0; i < allEntityAnimations.Length; i++) { - throw new NotImplementedException(); + RunningAnimation anim = allEntityAnimations[i]; + + if (anim.Active && anim.Running) sb.Append("Active&Running: " + anim.Animation.Code); + else if (anim.Active) sb.Append("Active: " + anim.Animation.Code); + else if (anim.Running) sb.Append("Running: " + anim.Animation.Code); + else continue; + + sb.Append(", easing: " + anim.EasingFactor); + sb.Append(", currentframe: " + anim.CurrentFrame); + sb.Append(", iterations: " + anim.Iterations); + sb.Append(", blendedweight: " + anim.BlendedWeight); + sb.Append(", animmetacode: " + anim.meta.Code); + sb.AppendLine(); } + + return sb.ToString(); + } + + protected virtual void AnimNowActive(RunningAnimation anim, AnimationMetaData animData) + { + anim.Running = true; + anim.Active = true; + if (!activeOrRunning.Contains(anim)) activeOrRunning.Add(anim); + anim.meta = animData; + anim.ShouldRewind = false; + anim.ShouldPlayTillEnd = false; + anim.CurrentFrame = animData.StartFrameOnce; + animData.StartFrameOnce = 0; + } + + protected abstract void CalculateOutputMatrices(float dt); + + public AttachmentPointAndPose? GetAttachmentPointPose(string code) + { + attachmentPointByCode.TryGetValue(code, out AttachmentPointAndPose? apap); + return apap; + } + + public virtual ElementPose? GetPosebyName(string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + { + throw new NotImplementedException(); + } + + public virtual void ReloadAttachmentPoints() + { + throw new NotImplementedException(); } } diff --git a/Common/Model/Animation/ClientAnimator.cs b/Common/Model/Animation/ClientAnimator.cs index 64da685f..53879283 100644 --- a/Common/Model/Animation/ClientAnimator.cs +++ b/Common/Model/Animation/ClientAnimator.cs @@ -1,522 +1,511 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Vintagestory.API.Common.Entities; -using Vintagestory.API.Config; using Vintagestory.API.MathTools; -#nullable disable +namespace Vintagestory.API.Common; -namespace Vintagestory.API.Common +#nullable enable + +/// +/// Syncs every frame with entity.ActiveAnimationsByAnimCode, starts and stops animations when necessary +/// and does recursive interpolation on the rotation, position and scale value for each frame, for each element and for each active element. +/// This produces always correctly blended animations but is significantly more costly for the cpu when compared to the technique used by the . +/// +public class ClientAnimator : AnimatorBase { + public Dictionary jointsById; + protected HashSet jointsDone = new(); - /// - /// Syncs every frame with entity.ActiveAnimationsByAnimCode, starts and stops animations when necessary - /// and does recursive interpolation on the rotation, position and scale value for each frame, for each element and for each active element - /// this produces always correctly blended animations but is significantly more costly for the cpu when compared to the technique used by the . - /// - public class ClientAnimator : AnimatorBase + private int[] prevFrameAnimInd = new int[DEFAULT_MAX_ANIM]; + private int[] nextFrameAnimInd = new int[DEFAULT_MAX_ANIM]; + + public override int MaxJointId => jointsById?.Count + 1 ?? 0; + + // Set in InitFields. + private int maxDepth; + private List[][] frameByDepthByAnimation = null!; + private List[][] nextFrameTransformsByAnimation = null!; + private ShapeElementWeights[][][] weightsByAnimationAndElement = null!; + + private readonly float[] localTransformMatrix = Mat4f.Create(); + private readonly float[] tmpMatrix = Mat4f.Create(); + + private readonly Action? onShouldPlaySoundListener; + + private static bool eleWeightDebug = false; + private readonly Dictionary eleWeights = new(); + + public static ClientAnimator CreateForEntity(Entity entity, List rootPoses, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById) { - public Dictionary jointsById; - protected HashSet jointsDone = new HashSet(); + return entity is EntityAgent entityAgent + ? new ClientAnimator( + () => entityAgent.Controls.MovespeedMultiplier * entityAgent.GetWalkSpeedMultiplier(0.3), + rootPoses, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + entity.AnimManager.ShouldPlaySound + ) + : new ClientAnimator( + () => 1f, + rootPoses, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + entity.AnimManager.ShouldPlaySound + ); + } - public static int MaxConcurrentAnimations = 16; - int maxDepth; - List[][] frameByDepthByAnimation; - List[][] nextFrameTransformsByAnimation; - ShapeElementWeights[][][] weightsByAnimationAndElement; + public static ClientAnimator CreateForEntity(Entity entity, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById) + { + return entity is EntityAgent entityAgent + ? new ClientAnimator( + () => entityAgent.Controls.MovespeedMultiplier * entityAgent.GetWalkSpeedMultiplier(0.3), + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + entity.AnimManager.ShouldPlaySound + ) + : new ClientAnimator( + () => 1f, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + entity.AnimManager.ShouldPlaySound + ); + } - float[] localTransformMatrix = Mat4f.Create(); - float[] tmpMatrix = Mat4f.Create(); + // This constructor is called only on the server side, by ServerAnimator constructors. Note, no call to initMatrices, to save RAM on a server. + public ClientAnimator(WalkSpeedSupplierDelegate walkSpeedSupplier, Animation[] animations, Action? onAnimationStoppedListener = null, Action? onShouldPlaySoundListener = null) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + { + this.onShouldPlaySoundListener = onShouldPlaySoundListener; - Action onShouldPlaySoundListener = null; + // Not used on the server. + jointsById = null!; - public static ClientAnimator CreateForEntity(Entity entity, List rootPoses, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById) - { - if (entity is EntityAgent) - { - EntityAgent entityag = entity as EntityAgent; - return new ClientAnimator( - () => entityag.Controls.MovespeedMultiplier * entityag.GetWalkSpeedMultiplier(0.3), - rootPoses, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - entity.AnimManager.ShouldPlaySound - ); - } else - { - return new ClientAnimator( - () => 1, - rootPoses, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - entity.AnimManager.ShouldPlaySound - ); - } - } + InitFields(); + } - public static ClientAnimator CreateForEntity(Entity entity, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById) - { - if (entity is EntityAgent) - { - EntityAgent entityag = entity as EntityAgent; - - return new ClientAnimator( - () => entityag.Controls.MovespeedMultiplier * entityag.GetWalkSpeedMultiplier(0.3), - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - entity.AnimManager.ShouldPlaySound - ); - } - else - { - return new ClientAnimator( - () => 1, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - entity.AnimManager.ShouldPlaySound - ); - } - } + // This constructor is called only on the client side. + public ClientAnimator(WalkSpeedSupplierDelegate walkSpeedSupplier, List rootPoses, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, Action? onAnimationStoppedListener = null, Action? onShouldPlaySoundListener = null) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + { + RootElements = rootElements; + this.jointsById = jointsById; + RootPoses = rootPoses; + this.onShouldPlaySoundListener = onShouldPlaySoundListener; + LoadAttachmentPoints(RootPoses); + InitFields(); + InitMatrices(MaxJointId); + } - public ClientAnimator( // This constructor is called only on the server side, by ServerAnimator constructors. Note, no call to initMatrices, to save RAM on a server - WalkSpeedSupplierDelegate walkSpeedSupplier, - Animation[] animations, - Action onAnimationStoppedListener = null, - Action onShouldPlaySoundListener = null - ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) - { - this.onShouldPlaySoundListener = onShouldPlaySoundListener; - initFields(); - } + // This constructor is called only on the client side. + public ClientAnimator(WalkSpeedSupplierDelegate walkSpeedSupplier, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, Action? onAnimationStoppedListener = null, Action? onShouldPlaySoundListener = null) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + { + RootElements = rootElements; + this.jointsById = jointsById; + + RootPoses = new List(); + LoadPosesAndAttachmentPoints(rootElements, RootPoses); + this.onShouldPlaySoundListener = onShouldPlaySoundListener; + InitFields(); + InitMatrices(MaxJointId); + } - public ClientAnimator( // This constructor is called only on the client side - WalkSpeedSupplierDelegate walkSpeedSupplier, - List rootPoses, - Animation[] animations, - ShapeElement[] rootElements, - Dictionary jointsById, - Action onAnimationStoppedListener = null, - Action onShouldPlaySoundListener = null - ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) - { - this.RootElements = rootElements; - this.jointsById = jointsById; - this.RootPoses = rootPoses; - this.onShouldPlaySoundListener = onShouldPlaySoundListener; - LoadAttachmentPoints(RootPoses); - initFields(); - initMatrices(MaxJointId); - } + /// + /// Doubles the current animation capacity. + /// Default MAX_CONCURRENT_ANIMATIONS. + /// + protected override void IncreaseAnimationCapacity() + { + int newCapacity = prevFrameAnimInd.Length * 2; + Array.Resize(ref prevFrameAnimInd, newCapacity); + Array.Resize(ref nextFrameAnimInd, newCapacity); + Array.Resize(ref curAnims, newCapacity); - public ClientAnimator( // This constructor is called only on the client side - WalkSpeedSupplierDelegate walkSpeedSupplier, - Animation[] animations, - ShapeElement[] rootElements, - Dictionary jointsById, - Action onAnimationStoppedListener = null, - Action onShouldPlaySoundListener = null - ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + for (int i = 0; i < maxDepth; i++) { - this.RootElements = rootElements; - this.jointsById = jointsById; - - RootPoses = new List(); - LoadPosesAndAttachmentPoints(rootElements, RootPoses); - this.onShouldPlaySoundListener = onShouldPlaySoundListener; - initFields(); - initMatrices(MaxJointId); + Array.Resize(ref frameByDepthByAnimation[i], newCapacity); + Array.Resize(ref nextFrameTransformsByAnimation[i], newCapacity); + Array.Resize(ref weightsByAnimationAndElement[i], newCapacity); } + } - protected virtual void initFields() - { - maxDepth = 2 + (RootPoses == null ? 0 : getMaxDepth(RootPoses, 1)); - - frameByDepthByAnimation = new List[maxDepth][]; - nextFrameTransformsByAnimation = new List[maxDepth][]; - weightsByAnimationAndElement = new ShapeElementWeights[maxDepth][][]; + protected virtual void InitFields() + { + maxDepth = 2 + (RootPoses == null ? 0 : GetMaxDepth(RootPoses, 1)); - for (int i = 0; i < maxDepth; i++) - { - frameByDepthByAnimation[i] = new List[MaxConcurrentAnimations]; - nextFrameTransformsByAnimation[i] = new List[MaxConcurrentAnimations]; - weightsByAnimationAndElement[i] = new ShapeElementWeights[MaxConcurrentAnimations][]; - } - } + frameByDepthByAnimation = new List[maxDepth][]; + nextFrameTransformsByAnimation = new List[maxDepth][]; + weightsByAnimationAndElement = new ShapeElementWeights[maxDepth][][]; - protected virtual void initMatrices(int maxJointId) + for (int i = 0; i < maxDepth; i++) { - // Matrices are only required on client side; and from 1.20.5 limited to the MaxJointId (x16) in length to avoid wasting RAM - var identMat = ClientAnimator.identMat; - var defaultPoseMatrices = new float[16 * maxJointId]; - for (int i = 0; i < defaultPoseMatrices.Length; i++) - { - defaultPoseMatrices[i] = identMat[i % 16]; - } - TransformationMatricesDefaultPose = defaultPoseMatrices; - TransformationMatrices = new float[defaultPoseMatrices.Length]; + frameByDepthByAnimation[i] = new List[DEFAULT_MAX_ANIM]; + nextFrameTransformsByAnimation[i] = new List[DEFAULT_MAX_ANIM]; + weightsByAnimationAndElement[i] = new ShapeElementWeights[DEFAULT_MAX_ANIM][]; } + } - public override void ReloadAttachmentPoints() + protected virtual void InitMatrices(int maxJointId) + { + // Matrices are only required on client side; and from 1.20.5 limited to the MaxJointId (x16) in length to avoid wasting RAM. + float[] identMat = ClientAnimator.identMat; + float[] defaultPoseMatrices = new float[16 * maxJointId]; + for (int i = 0; i < defaultPoseMatrices.Length; i++) { - LoadAttachmentPoints(RootPoses); + defaultPoseMatrices[i] = identMat[i % 16]; } + transformationMatricesDefaultPose = defaultPoseMatrices; + transformationMatrices = new float[defaultPoseMatrices.Length]; + } - protected virtual void LoadAttachmentPoints(List cachedPoses) + public override void ReloadAttachmentPoints() + { + LoadAttachmentPoints(RootPoses); + } + + protected virtual void LoadAttachmentPoints(List? cachedPoses) + { + if (cachedPoses == null) return; + + for (int i = 0; i < cachedPoses.Count; i++) { - for (int i = 0; i < cachedPoses.Count; i++) - { - ElementPose elem = cachedPoses[i]; + ElementPose elem = cachedPoses[i]; - var attachmentPoints = elem.ForElement.AttachmentPoints; - if (attachmentPoints != null) + AttachmentPoint[] attachmentPoints = elem.ForElement.AttachmentPoints; + if (attachmentPoints != null) + { + for (int j = 0; j < attachmentPoints.Length; j++) { - for (int j = 0; j < attachmentPoints.Length; j++) + AttachmentPoint apoint = attachmentPoints[j]; + attachmentPointByCode[apoint.Code] = new AttachmentPointAndPose() { - AttachmentPoint apoint = attachmentPoints[j]; - AttachmentPointByCode[apoint.Code] = new AttachmentPointAndPose() { - AttachPoint = apoint, - CachedPose = elem - }; - } + AttachPoint = apoint, + CachedPose = elem + }; } + } - if (elem.ChildElementPoses != null) - { - LoadAttachmentPoints(elem.ChildElementPoses); - } + if (elem.ChildElementPoses != null) + { + LoadAttachmentPoints(elem.ChildElementPoses); } } + } - protected virtual void LoadPosesAndAttachmentPoints(ShapeElement[] elements, List intoPoses) + protected virtual void LoadPosesAndAttachmentPoints(ShapeElement[] elements, List intoPoses) + { + ElementPose pose; + for (int i = 0; i < elements.Length; i++) { - ElementPose pose; - for (int i = 0; i < elements.Length; i++) - { - ShapeElement elem = elements[i]; + ShapeElement elem = elements[i]; - intoPoses.Add(pose = new ElementPose()); - pose.AnimModelMatrix = Mat4f.Create(); - pose.ForElement = elem; + intoPoses.Add(pose = new ElementPose()); + pose.AnimModelMatrix = Mat4f.Create(); + pose.ForElement = elem; - if (elem.AttachmentPoints != null) + if (elem.AttachmentPoints != null) + { + for (int j = 0; j < elem.AttachmentPoints.Length; j++) { - for (int j = 0; j < elem.AttachmentPoints.Length; j++) + AttachmentPoint apoint = elem.AttachmentPoints[j]; + attachmentPointByCode[apoint.Code] = new AttachmentPointAndPose() { - AttachmentPoint apoint = elem.AttachmentPoints[j]; - AttachmentPointByCode[apoint.Code] = new AttachmentPointAndPose() { - AttachPoint = apoint, - CachedPose = pose - }; - } - } - - if (elem.Children != null) - { - pose.ChildElementPoses = new List(elem.Children.Length); - LoadPosesAndAttachmentPoints(elem.Children, pose.ChildElementPoses); + AttachPoint = apoint, + CachedPose = pose + }; } } - } - private int getMaxDepth(List poses, int depth) - { - for (int i = 0; i < poses.Count; i++) + if (elem.Children != null) { - var pose = poses[i]; - - if (pose.ChildElementPoses != null) - { - depth = getMaxDepth(pose.ChildElementPoses, depth); - } + pose.ChildElementPoses = new List(elem.Children.Length); + LoadPosesAndAttachmentPoints(elem.Children, pose.ChildElementPoses); } - - return depth + 1; } + } - public override ElementPose GetPosebyName(string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + private static int GetMaxDepth(List poses, int depth) + { + for (int i = 0; i < poses.Count; i++) { - return getPosebyName(RootPoses, name); - } + ElementPose pose = poses[i]; - private ElementPose getPosebyName(List poses, string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) - { - for (int i = 0; i < poses.Count; i++) + if (pose.ChildElementPoses != null) { - var pose = poses[i]; - if (pose.ForElement.Name.Equals(name, stringComparison)) return pose; - - if (pose.ChildElementPoses != null) - { - var foundPose = getPosebyName(pose.ChildElementPoses, name); - if (foundPose != null) return foundPose; - } + depth = GetMaxDepth(pose.ChildElementPoses, depth); } - - return null; } - protected override void AnimNowActive(RunningAnimation anim, AnimationMetaData animData) + return depth + 1; + } + + public override ElementPose? GetPosebyName(string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + { + return GetPosebyName(RootPoses, name); + } + + private static ElementPose? GetPosebyName(List? poses, string name, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + { + if (poses == null) return null; + + for (int i = 0; i < poses.Count; i++) { - base.AnimNowActive(anim, animData); + ElementPose pose = poses[i]; + if (pose.ForElement.Name.Equals(name, stringComparison)) return pose; - if (anim.Animation.PrevNextKeyFrameByFrame == null) + if (pose.ChildElementPoses != null) { - anim.Animation.GenerateAllFrames(RootElements, jointsById); + ElementPose? foundPose = GetPosebyName(pose.ChildElementPoses, name); + if (foundPose != null) return foundPose; } - - anim.LoadWeights(RootElements); } + return null; + } + protected override void AnimNowActive(RunningAnimation anim, AnimationMetaData animData) + { + base.AnimNowActive(anim, animData); + if (anim.Animation.PrevNextKeyFrameByFrame == null) + { + anim.Animation.GenerateAllFrames(RootElements, jointsById); + } - int[] prevFrame = new int[MaxConcurrentAnimations]; - int[] nextFrame = new int[MaxConcurrentAnimations]; - - public override int MaxJointId => jointsById.Count + 1; + anim.LoadWeights(RootElements); + } - public override void OnFrame(Dictionary activeAnimationsByAnimCode, float dt) + public override void OnFrame(Dictionary activeAnimationsByAnimCode, float dt) + { + for (int j = 0; j < ActiveAnimationCount; j++) { - for (int j = 0; j < activeAnimCount; j++) + RunningAnimation anim = curAnims[j]; + if (anim.Animation.PrevNextKeyFrameByFrame == null && anim.Animation.KeyFrames.Length > 0) { - RunningAnimation anim = CurAnims[j]; - if (anim.Animation.PrevNextKeyFrameByFrame == null && anim.Animation.KeyFrames.Length > 0) - { - anim.Animation.GenerateAllFrames(RootElements, jointsById); - } + anim.Animation.GenerateAllFrames(RootElements, jointsById); + } - if (anim.meta.AnimationSound != null && onShouldPlaySoundListener != null) + if (anim.meta.AnimationSound != null && onShouldPlaySoundListener != null) + { + if (anim.CurrentFrame >= anim.meta.AnimationSound.Frame && anim.SoundPlayedAtIteration != anim.Iterations && anim.Active) { - if (anim.CurrentFrame >= anim.meta.AnimationSound.Frame && anim.SoundPlayedAtIteration != anim.Iterations && anim.Active) - { - onShouldPlaySoundListener(anim.meta.AnimationSound); - anim.SoundPlayedAtIteration = anim.Iterations; - } + onShouldPlaySoundListener(anim.meta.AnimationSound); + anim.SoundPlayedAtIteration = anim.Iterations; } } - - base.OnFrame(activeAnimationsByAnimCode, dt); } - protected override void calculateMatrices(float dt) - { - if (!CalculateMatrices) return; + base.OnFrame(activeAnimationsByAnimCode, dt); + } - jointsDone.Clear(); + protected override void CalculateOutputMatrices(float dt) + { + if (!base.CalculateMatrices) return; + if (transformationMatrices == null || RootPoses == null) return; - int animVersion = 0; + jointsDone.Clear(); - for (int j = 0; j < activeAnimCount; j++) - { - RunningAnimation anim = CurAnims[j]; + int animVersion = 0; - weightsByAnimationAndElement[0][j] = anim.ElementWeights; + for (int j = 0; j < ActiveAnimationCount; j++) + { + RunningAnimation anim = curAnims[j]; - animVersion = Math.Max(animVersion, anim.Animation.Version); + weightsByAnimationAndElement[0][j] = anim.ElementWeights; - AnimationFrame[] prevNextFrame = anim.Animation.PrevNextKeyFrameByFrame[(int)anim.CurrentFrame % anim.Animation.QuantityFrames]; - frameByDepthByAnimation[0][j] = prevNextFrame[0].RootElementTransforms; - prevFrame[j] = prevNextFrame[0].FrameNumber; + animVersion = Math.Max(animVersion, anim.Animation.Version); - if (anim.Animation.OnAnimationEnd == EnumEntityAnimationEndHandling.Hold && (int)anim.CurrentFrame + 1 == anim.Animation.QuantityFrames) - { - nextFrameTransformsByAnimation[0][j] = prevNextFrame[0].RootElementTransforms; - nextFrame[j] = prevNextFrame[0].FrameNumber; - } - else - { - nextFrameTransformsByAnimation[0][j] = prevNextFrame[1].RootElementTransforms; - nextFrame[j] = prevNextFrame[1].FrameNumber; - } - } + AnimationFrame[] prevNextFrame = anim.Animation.PrevNextKeyFrameByFrame[(int)anim.CurrentFrame % anim.Animation.QuantityFrames]; + frameByDepthByAnimation[0][j] = prevNextFrame[0].RootElementTransforms; + prevFrameAnimInd[j] = prevNextFrame[0].FrameNumber; - calculateMatrices( - animVersion, - dt, - RootPoses, - weightsByAnimationAndElement[0], - Mat4f.Create(), - frameByDepthByAnimation[0], - nextFrameTransformsByAnimation[0], - 0 - ); + if (anim.Animation.OnAnimationEnd == EnumEntityAnimationEndHandling.Hold && (int)anim.CurrentFrame + 1 == anim.Animation.QuantityFrames) + { + nextFrameTransformsByAnimation[0][j] = prevNextFrame[0].RootElementTransforms; + nextFrameAnimInd[j] = prevNextFrame[0].FrameNumber; + } + else + { + nextFrameTransformsByAnimation[0][j] = prevNextFrame[1].RootElementTransforms; + nextFrameAnimInd[j] = prevNextFrame[1].FrameNumber; + } + } - var TransformationMatrices = this.TransformationMatrices; - if (TransformationMatrices != null) + CalculateOutputMatrices( + animVersion, + dt, + RootPoses, + weightsByAnimationAndElement[0], + Mat4f.Create(), + frameByDepthByAnimation[0], + nextFrameTransformsByAnimation[0], + 0 + ); + + float[] TransformationMatrices = transformationMatrices; + if (TransformationMatrices != null) + { + for (int jointidMul16 = 0; jointidMul16 < TransformationMatrices.Length; jointidMul16 += 16) // radfast 20.2.25: jointIdMul16 is (jointId * 16); I think this is the highest performance way to loop through this, taking account of the many array bounds checks. { - for (int jointidMul16 = 0; jointidMul16 < TransformationMatrices.Length; jointidMul16 += 16) // radfast 20.2.25: jointIdMul16 is (jointId * 16); I think this is the highest performance way to loop through this, taking account of the many array bounds checks - { - if (jointsById.ContainsKey(jointidMul16 / 16)) continue; + if (jointsById.ContainsKey(jointidMul16 / 16)) continue; for (int j = 0; j < 16; j++) { - TransformationMatrices[jointidMul16 + j] = identMat[j]; - } + TransformationMatrices[jointidMul16 + j] = identMat[j]; } } + } - foreach (var val in AttachmentPointByCode) + foreach (KeyValuePair val in attachmentPointByCode) + { + float[] cachedMatrix = val.Value.CachedPose.AnimModelMatrix; + float[] animMatrix = val.Value.AnimModelMatrix; + for (int i = 0; i < 16; i++) { - var cachedMatrix = val.Value.CachedPose.AnimModelMatrix; - var animMatrix = val.Value.AnimModelMatrix; - for (int i = 0; i < 16; i++) - { - animMatrix[i] = cachedMatrix[i]; - } + animMatrix[i] = cachedMatrix[i]; } } + } + // Careful when changing around stuff in here, this is a recursively called method. + private void CalculateOutputMatrices(int animVersion, float dt, List outFrame, ShapeElementWeights[][] weightsByAnimationAndElement, float[] modelMatrix, List[] nowKeyFrameByAnimation, List[] nextInKeyFrameByAnimation, int depth) + { + depth++; + + Debug.Assert(transformationMatrices != null); + Debug.Assert(transformationMatricesDefaultPose != null); + List[]? nowChildKeyFrameByAnimation = frameByDepthByAnimation[depth]; + List[]? nextChildKeyFrameByAnimation = nextFrameTransformsByAnimation[depth]; + ShapeElementWeights[][]? childWeightsByAnimationAndElement = this.weightsByAnimationAndElement[depth]; - // Careful when changing around stuff in here, this is a recursively called method - private void calculateMatrices( - int animVersion, - float dt, - List outFrame, - ShapeElementWeights[][] weightsByAnimationAndElement, - float[] modelMatrix, - List[] nowKeyFrameByAnimation, - List[] nextInKeyFrameByAnimation, - int depth - ) + for (int childPoseIndex = 0; childPoseIndex < outFrame.Count; childPoseIndex++) { - depth++; - List[] nowChildKeyFrameByAnimation = this.frameByDepthByAnimation[depth]; - List[] nextChildKeyFrameByAnimation = this.nextFrameTransformsByAnimation[depth]; - ShapeElementWeights[][] childWeightsByAnimationAndElement = this.weightsByAnimationAndElement[depth]; + ElementPose outFramePose = outFrame[childPoseIndex]; + ShapeElement elem = outFramePose.ForElement; + outFramePose.SetMat(modelMatrix); + Mat4f.Identity(localTransformMatrix); - for (int childPoseIndex = 0; childPoseIndex < outFrame.Count; childPoseIndex++) - { - ElementPose outFramePose = outFrame[childPoseIndex]; - ShapeElement elem = outFramePose.ForElement; + outFramePose.Clear(); - outFramePose.SetMat(modelMatrix); - Mat4f.Identity(localTransformMatrix); + float weightSum = 0f; - outFramePose.Clear(); - - float weightSum = 0f; #if DEBUG - StringBuilder sb = null; - if (EleWeightDebug) sb = new StringBuilder(); + StringBuilder? sb = null; + if (eleWeightDebug) sb = new StringBuilder(); #endif - for (int animIndex = 0; animIndex < activeAnimCount; animIndex++) - { - RunningAnimation anim = CurAnims[animIndex]; - ShapeElementWeights sew = weightsByAnimationAndElement[animIndex][childPoseIndex]; + for (int animIndex = 0; animIndex < ActiveAnimationCount; animIndex++) + { + RunningAnimation anim = curAnims[animIndex]; - if (sew.BlendMode != EnumAnimationBlendMode.Add) - { - weightSum += sew.Weight * anim.EasingFactor; - } + ShapeElementWeights sew = weightsByAnimationAndElement[animIndex][childPoseIndex]; + + if (sew.BlendMode != EnumAnimationBlendMode.Add) + { + weightSum += sew.Weight * anim.EasingFactor; + } #if DEBUG - if (EleWeightDebug) sb.Append(string.Format("{0:0.0} from {1} (blendmode {2}), ", sew.Weight * anim.EasingFactor, anim.Animation.Code, sew.BlendMode)); + if (eleWeightDebug) sb?.Append(string.Format("{0:0.0} from {1} (blendmode {2}), ", sew.Weight * anim.EasingFactor, anim.Animation.Code, sew.BlendMode)); #endif - } + } #if DEBUG - if (EleWeightDebug) - { - if (eleWeights.ContainsKey(elem.Name)) eleWeights[elem.Name] += sb.ToString(); - else eleWeights[elem.Name] = sb.ToString(); - } + if (eleWeightDebug) + { + if (eleWeights.ContainsKey(elem.Name)) eleWeights[elem.Name] += sb?.ToString() ?? ""; + else eleWeights[elem.Name] = sb?.ToString() ?? ""; + } #endif - for (int animIndex = 0; animIndex < activeAnimCount; animIndex++) - { - RunningAnimation anim = CurAnims[animIndex]; - ShapeElementWeights sew = weightsByAnimationAndElement[animIndex][childPoseIndex]; - anim.CalcBlendedWeight(weightSum / sew.Weight, sew.BlendMode); + for (int animIndex = 0; animIndex < ActiveAnimationCount; animIndex++) + { + RunningAnimation anim = curAnims[animIndex]; + ShapeElementWeights sew = weightsByAnimationAndElement[animIndex][childPoseIndex]; + anim.CalcBlendedWeight(weightSum / sew.Weight, sew.BlendMode); - ElementPose nowFramePose = nowKeyFrameByAnimation[animIndex][childPoseIndex]; - ElementPose nextFramePose = nextInKeyFrameByAnimation[animIndex][childPoseIndex]; + ElementPose nowFramePose = nowKeyFrameByAnimation[animIndex][childPoseIndex]; + ElementPose nextFramePose = nextInKeyFrameByAnimation[animIndex][childPoseIndex]; - int prevFrame = this.prevFrame[animIndex]; - int nextFrame = this.nextFrame[animIndex]; + int prevFrame = prevFrameAnimInd[animIndex]; + int nextFrame = nextFrameAnimInd[animIndex]; - // May loop around, so nextFrame can be smaller than prevFrame - float keyFrameDist = nextFrame > prevFrame ? (nextFrame - prevFrame) : (anim.Animation.QuantityFrames - prevFrame + nextFrame); - float curFrameDist = anim.CurrentFrame >= prevFrame ? (anim.CurrentFrame - prevFrame) : (anim.Animation.QuantityFrames - prevFrame + anim.CurrentFrame); + // May loop around, so nextFrame can be smaller than prevFrame + float keyFrameDist = nextFrame > prevFrame ? (nextFrame - prevFrame) : (anim.Animation.QuantityFrames - prevFrame + nextFrame); + float curFrameDist = anim.CurrentFrame >= prevFrame ? (anim.CurrentFrame - prevFrame) : (anim.Animation.QuantityFrames - prevFrame + anim.CurrentFrame); - float lerp = curFrameDist / keyFrameDist; + float lerp = curFrameDist / keyFrameDist; - outFramePose.Add(nowFramePose, nextFramePose, lerp, anim.BlendedWeight); + outFramePose.Add(nowFramePose, nextFramePose, lerp, anim.BlendedWeight); - nowChildKeyFrameByAnimation[animIndex] = nowFramePose.ChildElementPoses; - childWeightsByAnimationAndElement[animIndex] = sew.ChildElements; + nowChildKeyFrameByAnimation[animIndex] = nowFramePose.ChildElementPoses; + childWeightsByAnimationAndElement[animIndex] = sew.ChildElements; - nextChildKeyFrameByAnimation[animIndex] = nextFramePose.ChildElementPoses; - } + nextChildKeyFrameByAnimation[animIndex] = nextFramePose.ChildElementPoses; + } - elem.GetLocalTransformMatrix(animVersion, localTransformMatrix, outFramePose); - Mat4f.Mul(outFramePose.AnimModelMatrix, outFramePose.AnimModelMatrix, localTransformMatrix); + elem.GetLocalTransformMatrix(animVersion, localTransformMatrix, outFramePose); + Mat4f.Mul(outFramePose.AnimModelMatrix, outFramePose.AnimModelMatrix, localTransformMatrix); - if (TransformationMatrices != null) // It is null on a server, non-null on a client - { + if (transformationMatrices != null) // It is null on a server, non-null on a client. + { if (elem.JointId > 0 && !jointsDone.Contains(elem.JointId)) { Mat4f.Mul(tmpMatrix, outFramePose.AnimModelMatrix, elem.inverseModelTransform); int index = 16 * elem.JointId; - var transformationMatrices = TransformationMatrices; - var tmpMatrixLocal = tmpMatrix; - if (index + 16 > transformationMatrices.Length) // Check we have space for this joint: we normally should, if MaxJointId was correct, but a mod could have modified the shape or something - { - var transformationMatricesDefault = this.TransformationMatricesDefaultPose; - initMatrices(elem.JointId + 1); // This replaces the matrices in both fields, but our local references are still the old populated matrices - Array.Copy(transformationMatrices, this.TransformationMatrices, transformationMatrices.Length); - Array.Copy(transformationMatricesDefault, this.TransformationMatricesDefaultPose, transformationMatricesDefault.Length); - transformationMatrices = this.TransformationMatrices; - } + float[] transformationMatrices = base.transformationMatrices; + float[] tmpMatrixLocal = tmpMatrix; + if (index + 16 > transformationMatrices.Length) // Check we have space for this joint: we normally should, if MaxJointId was correct, but a mod could have modified the shape or something. + { + float[] transformationMatricesDefault = transformationMatricesDefaultPose; + InitMatrices(elem.JointId + 1); // This replaces the matrices in both fields, but our local references are still the old populated matrices. + Array.Copy(transformationMatrices, base.transformationMatrices, transformationMatrices.Length); + Array.Copy(transformationMatricesDefault, transformationMatricesDefaultPose, transformationMatricesDefault.Length); + transformationMatrices = base.transformationMatrices; + } for (int i = 0; i < 16; i++) { - transformationMatrices[index + i] = tmpMatrixLocal[i]; + transformationMatrices[index + i] = tmpMatrixLocal[i]; } jointsDone.Add(elem.JointId); } - } + } - if (outFramePose.ChildElementPoses != null) - { - calculateMatrices( - animVersion, - dt, - outFramePose.ChildElementPoses, - childWeightsByAnimationAndElement, - outFramePose.AnimModelMatrix, - nowChildKeyFrameByAnimation, - nextChildKeyFrameByAnimation, - depth - ); - } + if (outFramePose.ChildElementPoses != null) + { + CalculateOutputMatrices( + animVersion, + dt, + outFramePose.ChildElementPoses, + childWeightsByAnimationAndElement, + outFramePose.AnimModelMatrix, + nowChildKeyFrameByAnimation, + nextChildKeyFrameByAnimation, + depth + ); } } + } + public override string DumpCurrentState() + { + eleWeightDebug = true; + eleWeights.Clear(); + CalculateOutputMatrices(1 / 60f); + eleWeightDebug = false; - static bool EleWeightDebug=false; - Dictionary eleWeights = new Dictionary(); - public override string DumpCurrentState() - { - EleWeightDebug = true; - eleWeights.Clear(); - calculateMatrices(1 / 60f); - EleWeightDebug = false; - - return base.DumpCurrentState() + "\nElement weights:\n" + string.Join("\n", eleWeights.Select(x => x.Key + ": " + x.Value)); - } - + return base.DumpCurrentState() + "\nElement weights:\n" + string.Join("\n", eleWeights.Select(x => x.Key + ": " + x.Value)); } } diff --git a/Common/Model/Animation/ServerAnimator.cs b/Common/Model/Animation/ServerAnimator.cs index dafc2358..c0721691 100644 --- a/Common/Model/Animation/ServerAnimator.cs +++ b/Common/Model/Animation/ServerAnimator.cs @@ -1,117 +1,100 @@ -using System; +using System; using System.Collections.Generic; using Vintagestory.API.Common.Entities; -using Vintagestory.API.MathTools; -#nullable disable +#nullable enable -namespace Vintagestory.API.Common +namespace Vintagestory.API.Common; + +public class ServerAnimator : ClientAnimator { - public class ServerAnimator : ClientAnimator + public static ServerAnimator CreateForEntity(Entity entity, List rootPoses, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, bool requirePosesOnServer) { + return entity is EntityAgent entityAgent + ? new ServerAnimator( + () => entityAgent.Controls.MovespeedMultiplier * entityAgent.GetWalkSpeedMultiplier(0.3), + rootPoses, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + requirePosesOnServer + ) + : new ServerAnimator( + () => 1, + rootPoses, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + requirePosesOnServer + ); + } - public static ServerAnimator CreateForEntity(Entity entity, List rootPoses, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, bool requirePosesOnServer) - { - if (entity is EntityAgent) - { - EntityAgent entityag = entity as EntityAgent; - return new ServerAnimator( - () => entityag.Controls.MovespeedMultiplier * entityag.GetWalkSpeedMultiplier(0.3), - rootPoses, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - requirePosesOnServer - ); - } else - { - return new ServerAnimator( - () => 1, - rootPoses, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - requirePosesOnServer - ); - } - } - - public static ServerAnimator CreateForEntity(Entity entity, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, bool requirePosesOnServer) - { - ServerAnimator animator; - if (entity is EntityAgent) - { - EntityAgent entityag = entity as EntityAgent; - - animator = new ServerAnimator( - () => entityag.Controls.MovespeedMultiplier * entityag.GetWalkSpeedMultiplier(0.3), - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - requirePosesOnServer - ); - } else { - animator = new ServerAnimator( - () => 1, - animations, - rootElements, - jointsById, - entity.AnimManager.TriggerAnimationStopped, - requirePosesOnServer - ); - } - - animator.entityForLogging = entity; - return animator; - } - - - public ServerAnimator( - WalkSpeedSupplierDelegate walkSpeedSupplier, - Animation[] animations, - ShapeElement[] rootElements, - Dictionary jointsById, - Action onAnimationStoppedListener = null, - bool loadFully = false - ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) - { - this.RootElements = rootElements; - this.jointsById = jointsById; + public static ServerAnimator CreateForEntity(Entity entity, Animation[] animations, ShapeElement[] rootElements, Dictionary jointsById, bool requirePosesOnServer) + { + ServerAnimator animator = entity is EntityAgent entityAgent + ? new ServerAnimator( + () => entityAgent.Controls.MovespeedMultiplier * entityAgent.GetWalkSpeedMultiplier(0.3), + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + requirePosesOnServer + ) + : new ServerAnimator( + () => 1, + animations, + rootElements, + jointsById, + entity.AnimManager.TriggerAnimationStopped, + requirePosesOnServer + ); + animator.entityForLogging = entity; + return animator; + } - RootPoses = new List(); - LoadPosesAndAttachmentPoints(rootElements, RootPoses); - initFields(); - } + public ServerAnimator( + WalkSpeedSupplierDelegate walkSpeedSupplier, + Animation[] animations, + ShapeElement[] rootElements, + Dictionary jointsById, + Action? onAnimationStoppedListener = null, + bool loadFully = false + ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + { + RootElements = rootElements; + this.jointsById = jointsById; + RootPoses = new List(); + LoadPosesAndAttachmentPoints(rootElements, RootPoses); + InitFields(); + } - public ServerAnimator( - WalkSpeedSupplierDelegate walkSpeedSupplier, - List rootPoses, - Animation[] animations, - ShapeElement[] rootElements, - Dictionary jointsById, - Action onAnimationStoppedListener = null, - bool loadFully = false - ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) - { - this.RootElements = rootElements; - this.jointsById = jointsById; - this.RootPoses = rootPoses; + public ServerAnimator( + WalkSpeedSupplierDelegate walkSpeedSupplier, + List rootPoses, + Animation[] animations, + ShapeElement[] rootElements, + Dictionary jointsById, + Action? onAnimationStoppedListener = null, + bool loadFully = false + ) : base(walkSpeedSupplier, animations, onAnimationStoppedListener) + { + RootElements = rootElements; + this.jointsById = jointsById; + RootPoses = rootPoses; - LoadAttachmentPoints(RootPoses); - initFields(); - } + LoadAttachmentPoints(RootPoses); + InitFields(); + } - protected override void LoadPosesAndAttachmentPoints(ShapeElement[] elements, List intoPoses) - { - // Tyron Oct 3, 2024: We used to only load root poses and root attachment points here server side - // Its just not feasible anymore with the Center AP being at non-root elements and also its inconsistent with the Client side - // And also this optimization does not really help much i think. We only calculate matrices server side on dead creatures anyway - // So the loadFully argument is now obsolete - base.LoadPosesAndAttachmentPoints(elements, intoPoses); - } + protected override void LoadPosesAndAttachmentPoints(ShapeElement[] elements, List intoPoses) + { + // Tyron Oct 3, 2024: We used to only load root poses and root attachment points here server side. + // Its just not feasible anymore with the Center AP being at non-root elements and also its inconsistent with the Client side + // and also this optimization does not really help much I think. We only calculate matrices server side on dead creatures anyway + // so the loadFully argument is now obsolete. + base.LoadPosesAndAttachmentPoints(elements, intoPoses); } }