Fix animation timing precision loss by replacing fmod with iterative subtraction

Floating-point fmod() loses precision with large accumulated time values, causing
subtle jumps/hitches in animation loops. Replace with iterative duration subtraction
to keep animationTime bounded and maintain precision, consistent with the fix
applied to character_renderer.cpp.

Applies to:
- M2 creature/object animation loops (main update)
- M2 particle-only instance wrapping (3333ms limit)
- M2 global sequence timing resolution
- M2 animated particle tile indexing
- Mount bobbing motion (sinusoidal rider motion)
- Character footstep trigger timing
- Mount footstep trigger timing

All timing computations now use the same precision-preserving approach.
This commit is contained in:
Kelsi 2026-03-11 18:14:25 -07:00
parent f6f072a957
commit 68a379610e
2 changed files with 43 additions and 11 deletions

View file

@ -2008,7 +2008,12 @@ void Renderer::updateCharacterAnimation() {
// Rider bob: sinusoidal motion synced to mount's run animation (only used in fallback positioning)
mountBob = 0.0f;
if (moving && haveMountState && curMountDur > 1.0f) {
float norm = std::fmod(curMountTime, curMountDur) / curMountDur;
// Wrap mount time preserving precision via subtraction instead of fmod
float wrappedTime = curMountTime;
while (wrappedTime >= curMountDur) {
wrappedTime -= curMountDur;
}
float norm = wrappedTime / curMountDur;
// One bounce per stride cycle
float bobSpeed = taxiFlight_ ? 2.0f : 1.0f;
mountBob = std::sin(norm * 2.0f * 3.14159f * bobSpeed) * 0.12f;
@ -2580,8 +2585,13 @@ bool Renderer::shouldTriggerFootstepEvent(uint32_t animationId, float animationT
return false;
}
float norm = std::fmod(animationTimeMs, animationDurationMs) / animationDurationMs;
if (norm < 0.0f) norm += 1.0f;
// Wrap animation time preserving precision via subtraction instead of fmod
float wrappedTime = animationTimeMs;
while (wrappedTime >= animationDurationMs) {
wrappedTime -= animationDurationMs;
}
if (wrappedTime < 0.0f) wrappedTime += animationDurationMs;
float norm = wrappedTime / animationDurationMs;
if (animationId != footstepLastAnimationId) {
footstepLastAnimationId = animationId;
@ -2875,8 +2885,13 @@ void Renderer::update(float deltaTime) {
float animTimeMs = 0.0f, animDurationMs = 0.0f;
if (characterRenderer->getAnimationState(mountInstanceId_, animId, animTimeMs, animDurationMs) &&
animDurationMs > 1.0f && cameraController->isMoving()) {
float norm = std::fmod(animTimeMs, animDurationMs) / animDurationMs;
if (norm < 0.0f) norm += 1.0f;
// Wrap animation time preserving precision via subtraction instead of fmod
float wrappedTime = animTimeMs;
while (wrappedTime >= animDurationMs) {
wrappedTime -= animDurationMs;
}
if (wrappedTime < 0.0f) wrappedTime += animDurationMs;
float norm = wrappedTime / animDurationMs;
if (animId != mountFootstepLastAnimId) {
mountFootstepLastAnimId = animId;