192 lines
5.8 KiB
C++
192 lines
5.8 KiB
C++
#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
|