From 6347e78d72abb794a5011caeebb3840863b21d2c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 07:23:35 -0700 Subject: [PATCH] fix(wom): scrub NaN/inf bone keyframes at save time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The load side already scrubs keyframe translation/rotation/scale floats, but fromM2 → save → load is the typical path: a corrupt M2 source would write NaN keyframes that the load-time guard would have to clean up on every subsequent load. Symmetric scrub here ensures the file is clean from the start. movingSpeed also defaults to 0 if non-finite (matches load). --- src/pipeline/wowee_model.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/pipeline/wowee_model.cpp b/src/pipeline/wowee_model.cpp index b645b3bb..3e45569f 100644 --- a/src/pipeline/wowee_model.cpp +++ b/src/pipeline/wowee_model.cpp @@ -315,10 +315,20 @@ bool WoweeModelLoader::save(const WoweeModel& model, const std::string& basePath uint32_t animCount = static_cast(model.animations.size()); f.write(reinterpret_cast(&animCount), 4); + // Same NaN scrub as load — keyframes can carry corrupt source data + // straight through fromM2 without ever round-tripping a load, so the + // save side has to defend independently. + auto sanV3 = [](glm::vec3 v, float def) { + if (!std::isfinite(v.x)) v.x = def; + if (!std::isfinite(v.y)) v.y = def; + if (!std::isfinite(v.z)) v.z = def; + return v; + }; for (const auto& anim : model.animations) { f.write(reinterpret_cast(&anim.id), 4); f.write(reinterpret_cast(&anim.durationMs), 4); - f.write(reinterpret_cast(&anim.movingSpeed), 4); + float movingSpeed = std::isfinite(anim.movingSpeed) ? anim.movingSpeed : 0.0f; + f.write(reinterpret_cast(&movingSpeed), 4); for (size_t bi = 0; bi < model.bones.size(); bi++) { uint32_t kfCount = (bi < anim.boneKeyframes.size()) @@ -326,10 +336,17 @@ bool WoweeModelLoader::save(const WoweeModel& model, const std::string& basePath f.write(reinterpret_cast(&kfCount), 4); for (uint32_t ki = 0; ki < kfCount; ki++) { const auto& kf = anim.boneKeyframes[bi][ki]; + glm::vec3 t = sanV3(kf.translation, 0.0f); + glm::vec3 s = sanV3(kf.scale, 1.0f); + glm::quat q = kf.rotation; + if (!std::isfinite(q.x)) q.x = 0.0f; + if (!std::isfinite(q.y)) q.y = 0.0f; + if (!std::isfinite(q.z)) q.z = 0.0f; + if (!std::isfinite(q.w)) q.w = 1.0f; f.write(reinterpret_cast(&kf.timeMs), 4); - f.write(reinterpret_cast(&kf.translation), 12); - f.write(reinterpret_cast(&kf.rotation), 16); - f.write(reinterpret_cast(&kf.scale), 12); + f.write(reinterpret_cast(&t), 12); + f.write(reinterpret_cast(&q), 16); + f.write(reinterpret_cast(&s), 12); } } }