#pragma once #include "pipeline/m2_loader.hpp" #include #include #include #include #include namespace wowee::rendering::m2_track { struct SampleTime { int sequenceIndex = -1; float timeMs = 0.0f; }; inline SampleTime resolveTime(const pipeline::M2AnimationTrack& track, int animationSequenceIndex, float animationTimeMs, float globalTimeMs, const std::vector& globalSequenceDurations) { if (track.globalSequence >= 0 && static_cast(track.globalSequence) < globalSequenceDurations.size()) { const float duration = static_cast(globalSequenceDurations[track.globalSequence]); float time = duration > 0.0f ? std::fmod(globalTimeMs, duration) : 0.0f; if (time < 0.0f) time += duration; return {0, time}; } return {animationSequenceIndex, animationTimeMs}; } inline size_t lowerKeyIndex(const std::vector& timestamps, size_t keyCount, float timeMs) { keyCount = std::min(keyCount, timestamps.size()); if (keyCount <= 1 || timeMs <= static_cast(timestamps[0])) return 0; const auto end = timestamps.begin() + static_cast(keyCount); const auto upper = std::upper_bound( timestamps.begin(), end, timeMs, [](float time, uint32_t timestamp) { return time < static_cast(timestamp); }); if (upper == end) return keyCount - 1; return static_cast(upper - timestamps.begin() - 1); } inline float interpolationFraction(const pipeline::M2AnimationTrack& track, const std::vector& timestamps, size_t lower, size_t keyCount, float timeMs) { // Type 0 is discrete. The loader currently stores values but not the tangent // pairs required by Hermite/Bezier tracks, so types 1-3 use the stable linear // fallback that the renderers historically used. if (track.interpolationType == 0 || lower + 1 >= keyCount) return 0.0f; const float t0 = static_cast(timestamps[lower]); const float t1 = static_cast(timestamps[lower + 1]); return t1 > t0 ? glm::clamp((timeMs - t0) / (t1 - t0), 0.0f, 1.0f) : 0.0f; } inline float sampleFloat(const pipeline::M2AnimationTrack& track, int animationSequenceIndex, float animationTimeMs, float globalTimeMs, const std::vector& globalSequenceDurations, float defaultValue) { const auto sampleTime = resolveTime(track, animationSequenceIndex, animationTimeMs, globalTimeMs, globalSequenceDurations); if (sampleTime.sequenceIndex < 0 || static_cast(sampleTime.sequenceIndex) >= track.sequences.size()) { return defaultValue; } const auto& keys = track.sequences[static_cast(sampleTime.sequenceIndex)]; const size_t count = std::min(keys.timestamps.size(), keys.floatValues.size()); if (count == 0) return defaultValue; const size_t lower = lowerKeyIndex(keys.timestamps, count, sampleTime.timeMs); const float fraction = interpolationFraction(track, keys.timestamps, lower, count, sampleTime.timeMs); return lower + 1 < count ? glm::mix(keys.floatValues[lower], keys.floatValues[lower + 1], fraction) : keys.floatValues[lower]; } inline glm::vec3 sampleVec3(const pipeline::M2AnimationTrack& track, int animationSequenceIndex, float animationTimeMs, float globalTimeMs, const std::vector& globalSequenceDurations, const glm::vec3& defaultValue) { const auto sampleTime = resolveTime(track, animationSequenceIndex, animationTimeMs, globalTimeMs, globalSequenceDurations); if (sampleTime.sequenceIndex < 0 || static_cast(sampleTime.sequenceIndex) >= track.sequences.size()) { return defaultValue; } const auto& keys = track.sequences[static_cast(sampleTime.sequenceIndex)]; const size_t count = std::min(keys.timestamps.size(), keys.vec3Values.size()); if (count == 0) return defaultValue; const auto safe = [&](const glm::vec3& value) { return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) ? value : defaultValue; }; const size_t lower = lowerKeyIndex(keys.timestamps, count, sampleTime.timeMs); const float fraction = interpolationFraction(track, keys.timestamps, lower, count, sampleTime.timeMs); return lower + 1 < count ? safe(glm::mix(safe(keys.vec3Values[lower]), safe(keys.vec3Values[lower + 1]), fraction)) : safe(keys.vec3Values[lower]); } inline glm::quat sampleQuat(const pipeline::M2AnimationTrack& track, int animationSequenceIndex, float animationTimeMs, float globalTimeMs, const std::vector& globalSequenceDurations) { const glm::quat identity(1.0f, 0.0f, 0.0f, 0.0f); const auto sampleTime = resolveTime(track, animationSequenceIndex, animationTimeMs, globalTimeMs, globalSequenceDurations); if (sampleTime.sequenceIndex < 0 || static_cast(sampleTime.sequenceIndex) >= track.sequences.size()) { return identity; } const auto& keys = track.sequences[static_cast(sampleTime.sequenceIndex)]; const size_t count = std::min(keys.timestamps.size(), keys.quatValues.size()); if (count == 0) return identity; const auto safe = [&](const glm::quat& value) { const float lengthSquared = glm::dot(value, value); return std::isfinite(lengthSquared) && lengthSquared >= 0.000001f ? glm::normalize(value) : identity; }; const size_t lower = lowerKeyIndex(keys.timestamps, count, sampleTime.timeMs); const float fraction = interpolationFraction(track, keys.timestamps, lower, count, sampleTime.timeMs); return lower + 1 < count ? glm::normalize(glm::slerp(safe(keys.quatValues[lower]), safe(keys.quatValues[lower + 1]), fraction)) : safe(keys.quatValues[lower]); } } // namespace wowee::rendering::m2_track