#include "animation/AnimationSampling.h" #include namespace core { namespace { template void sortKeysByFrame(QVector& keys, FrameGetter getFrame) { std::sort(keys.begin(), keys.end(), [&](const KeyT& a, const KeyT& b) { return getFrame(a) < getFrame(b); }); } } // namespace QPointF sampleLocation(const QVector& keys, int frame, const QPointF& fallbackOrigin, KeyInterpolation mode) { QVector sorted = keys; sortKeysByFrame(sorted, [](const Project::Entity::KeyframeVec2& k) { return k.frame; }); if (sorted.isEmpty()) { return fallbackOrigin; } if (mode == KeyInterpolation::Hold) { QPointF out = fallbackOrigin; int best = -1; for (const auto& k : sorted) { if (k.frame <= frame && k.frame >= best) { best = k.frame; out = k.value; } } return out; } // Linear:区间外夹持到端点;中间在相邻关键帧间线性插值(对 x、y 分别 lerp) const auto& first = sorted.front(); const auto& last = sorted.back(); if (frame <= first.frame) { return first.value; } if (frame >= last.frame) { return last.value; } for (int i = 0; i + 1 < sorted.size(); ++i) { const int f0 = sorted[i].frame; const int f1 = sorted[i + 1].frame; if (frame < f0) { continue; } if (frame <= f1) { if (f1 == f0 || frame == f0) { return sorted[i].value; } const double t = static_cast(frame - f0) / static_cast(f1 - f0); const QPointF& a = sorted[i].value; const QPointF& b = sorted[i + 1].value; return QPointF(a.x() + (b.x() - a.x()) * t, a.y() + (b.y() - a.y()) * t); } } return last.value; } double sampleDepthScale01(const QVector& keys, int frame, double fallback01, KeyInterpolation mode) { QVector sorted = keys; sortKeysByFrame(sorted, [](const Project::Entity::KeyframeFloat01& k) { return k.frame; }); const double fb = std::clamp(fallback01, 0.0, 1.0); if (sorted.isEmpty()) { return fb; } if (mode == KeyInterpolation::Hold) { double out = fb; int best = -1; for (const auto& k : sorted) { if (k.frame <= frame && k.frame >= best) { best = k.frame; out = k.value; } } return std::clamp(out, 0.0, 1.0); } const auto& first = sorted.front(); const auto& last = sorted.back(); if (frame <= first.frame) { return std::clamp(first.value, 0.0, 1.0); } if (frame >= last.frame) { return std::clamp(last.value, 0.0, 1.0); } for (int i = 0; i + 1 < sorted.size(); ++i) { const int f0 = sorted[i].frame; const int f1 = sorted[i + 1].frame; if (frame < f0) { continue; } if (frame <= f1) { if (f1 == f0 || frame == f0) { return std::clamp(sorted[i].value, 0.0, 1.0); } const double t = static_cast(frame - f0) / static_cast(f1 - f0); const double a = sorted[i].value; const double b = sorted[i + 1].value; return std::clamp(a + (b - a) * t, 0.0, 1.0); } } return std::clamp(last.value, 0.0, 1.0); } double sampleUserScale(const QVector& keys, int frame, double fallback, KeyInterpolation mode) { QVector sorted = keys; sortKeysByFrame(sorted, [](const Project::Entity::KeyframeDouble& k) { return k.frame; }); const double fb = std::max(fallback, 1e-6); if (sorted.isEmpty()) { return fb; } if (mode == KeyInterpolation::Hold) { double out = fb; int best = -1; for (const auto& k : sorted) { if (k.frame <= frame && k.frame >= best) { best = k.frame; out = k.value; } } return std::max(out, 1e-6); } const auto& first = sorted.front(); const auto& last = sorted.back(); if (frame <= first.frame) { return std::max(first.value, 1e-6); } if (frame >= last.frame) { return std::max(last.value, 1e-6); } for (int i = 0; i + 1 < sorted.size(); ++i) { const int f0 = sorted[i].frame; const int f1 = sorted[i + 1].frame; if (frame < f0) { continue; } if (frame <= f1) { if (f1 == f0 || frame == f0) { return std::max(sorted[i].value, 1e-6); } const double t = static_cast(frame - f0) / static_cast(f1 - f0); const double a = sorted[i].value; const double b = sorted[i + 1].value; return std::max(a + (b - a) * t, 1e-6); } } return std::max(last.value, 1e-6); } QString sampleImagePath(const QVector& frames, int frame, const QString& fallbackPath) { QVector sorted = frames; sortKeysByFrame(sorted, [](const Project::Entity::ImageFrame& k) { return k.frame; }); QString out = fallbackPath; int best = -1; for (const auto& k : sorted) { if (k.frame <= frame && k.frame >= best && !k.imagePath.isEmpty()) { best = k.frame; out = k.imagePath; } } return out; } } // namespace core