Files
hfut-bishe/client/core/animation/AnimationSampling.cpp
2026-04-07 20:55:30 +08:00

192 lines
5.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "animation/AnimationSampling.h"
#include <algorithm>
namespace core {
namespace {
template <typename KeyT, typename FrameGetter>
void sortKeysByFrame(QVector<KeyT>& 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<Project::Entity::KeyframeVec2>& keys,
int frame,
const QPointF& fallbackOrigin,
KeyInterpolation mode) {
QVector<Project::Entity::KeyframeVec2> 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<double>(frame - f0) / static_cast<double>(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<Project::Entity::KeyframeFloat01>& keys,
int frame,
double fallback01,
KeyInterpolation mode) {
QVector<Project::Entity::KeyframeFloat01> 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<double>(frame - f0) / static_cast<double>(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<Project::Entity::KeyframeDouble>& keys,
int frame,
double fallback,
KeyInterpolation mode) {
QVector<Project::Entity::KeyframeDouble> 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<double>(frame - f0) / static_cast<double>(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<Project::Entity::ImageFrame>& frames,
int frame,
const QString& fallbackPath) {
QVector<Project::Entity::ImageFrame> 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