refactor: name M2 renderer magic constants, add why-comments

- Name portal spin wrap value as kTwoPi constant
- Name particle animTime wrap as kParticleWrapMs (3333ms) with
  why-comment: covers longest known emission cycle (~3s torch/campfire)
  while preventing float precision loss over hours of runtime
- Add FBlock interpolation documentation: explain what FBlocks are
  (particle lifetime curves) and note that float/vec3 variants share
  identical logic and must be updated together
This commit is contained in:
Kelsi 2026-03-30 14:14:27 -07:00
parent 6dfac314ee
commit 4574d203b5

View file

@ -1974,12 +1974,13 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
// --- Spin instance portals --- // --- Spin instance portals ---
static constexpr float PORTAL_SPIN_SPEED = 1.2f; // radians/sec static constexpr float PORTAL_SPIN_SPEED = 1.2f; // radians/sec
static constexpr float kTwoPi = 6.2831853f;
for (size_t idx : portalInstanceIndices_) { for (size_t idx : portalInstanceIndices_) {
if (idx >= instances.size()) continue; if (idx >= instances.size()) continue;
auto& inst = instances[idx]; auto& inst = instances[idx];
inst.portalSpinAngle += PORTAL_SPIN_SPEED * deltaTime; inst.portalSpinAngle += PORTAL_SPIN_SPEED * deltaTime;
if (inst.portalSpinAngle > 6.2831853f) if (inst.portalSpinAngle > kTwoPi)
inst.portalSpinAngle -= 6.2831853f; inst.portalSpinAngle -= kTwoPi;
inst.rotation.z = inst.portalSpinAngle; inst.rotation.z = inst.portalSpinAngle;
inst.updateModelMatrix(); inst.updateModelMatrix();
} }
@ -1990,13 +1991,17 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
for (auto& instance : instances) { for (auto& instance : instances) {
instance.animTime += dtMs; instance.animTime += dtMs;
} }
// Wrap animTime for particle-only instances so emission rate tracks keep looping // Wrap animTime for particle-only instances so emission rate tracks keep looping.
// 3333ms chosen as a safe wrap period: long enough to cover the longest known M2
// particle emission cycle (~3s for torch/campfire effects) while preventing float
// precision loss that accumulates over hours of runtime.
static constexpr float kParticleWrapMs = 3333.0f;
for (size_t idx : particleOnlyInstanceIndices_) { for (size_t idx : particleOnlyInstanceIndices_) {
if (idx >= instances.size()) continue; if (idx >= instances.size()) continue;
auto& instance = instances[idx]; auto& instance = instances[idx];
// Use iterative subtraction instead of fmod() to preserve precision // Use iterative subtraction instead of fmod() to preserve precision
while (instance.animTime > 3333.0f) { while (instance.animTime > kParticleWrapMs) {
instance.animTime -= 3333.0f; instance.animTime -= kParticleWrapMs;
} }
} }
@ -3155,11 +3160,14 @@ float M2Renderer::interpFloat(const pipeline::M2AnimationTrack& track, float ani
return glm::mix(keys.floatValues[i0], keys.floatValues[i1], frac); return glm::mix(keys.floatValues[i0], keys.floatValues[i1], frac);
} }
// Interpolate an M2 FBlock (particle lifetime curve) at a given life ratio [0..1].
// FBlocks store per-lifetime keyframes for particle color, alpha, and scale.
// NOTE: interpFBlockFloat and interpFBlockVec3 share identical interpolation logic —
// if you fix a bug in one, update the other to match.
float M2Renderer::interpFBlockFloat(const pipeline::M2FBlock& fb, float lifeRatio) { float M2Renderer::interpFBlockFloat(const pipeline::M2FBlock& fb, float lifeRatio) {
if (fb.floatValues.empty()) return 1.0f; if (fb.floatValues.empty()) return 1.0f;
if (fb.floatValues.size() == 1 || fb.timestamps.empty()) return fb.floatValues[0]; if (fb.floatValues.size() == 1 || fb.timestamps.empty()) return fb.floatValues[0];
lifeRatio = glm::clamp(lifeRatio, 0.0f, 1.0f); lifeRatio = glm::clamp(lifeRatio, 0.0f, 1.0f);
// Find surrounding timestamps
for (size_t i = 0; i < fb.timestamps.size() - 1; i++) { for (size_t i = 0; i < fb.timestamps.size() - 1; i++) {
if (lifeRatio <= fb.timestamps[i + 1]) { if (lifeRatio <= fb.timestamps[i + 1]) {
float t0 = fb.timestamps[i]; float t0 = fb.timestamps[i];