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);
}
}