update
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
#include "animation/AnimationEvaluator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace core {
|
||||
namespace {
|
||||
|
||||
QVector<Project::AnimationKey> sortedKeys(const QVector<Project::AnimationKey>& keys) {
|
||||
QVector<Project::AnimationKey> out = keys;
|
||||
std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; });
|
||||
return out;
|
||||
}
|
||||
|
||||
bool sampleBool(const QVector<Project::AnimationKey>& keys, int frame, bool fallback) {
|
||||
auto sorted = sortedKeys(keys);
|
||||
bool out = fallback;
|
||||
for (const auto& k : sorted) {
|
||||
if (k.frame <= frame) out = k.boolValue;
|
||||
else break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString sampleString(const QVector<Project::AnimationKey>& keys, int frame, const QString& fallback = {}) {
|
||||
auto sorted = sortedKeys(keys);
|
||||
QString out = fallback;
|
||||
for (const auto& k : sorted) {
|
||||
if (k.frame <= frame && !k.stringValue.isEmpty()) out = k.stringValue;
|
||||
else if (k.frame > frame) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QByteArray sampleBytes(const QVector<Project::AnimationKey>& keys, int frame, const QByteArray& fallback = {}) {
|
||||
auto sorted = sortedKeys(keys);
|
||||
QByteArray out = fallback;
|
||||
for (const auto& k : sorted) {
|
||||
if (k.frame <= frame && !k.bytesValue.isEmpty()) out = k.bytesValue;
|
||||
else if (k.frame > frame) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
double sampleDouble(const QVector<Project::AnimationKey>& keys,
|
||||
int frame,
|
||||
double fallback,
|
||||
Project::AnimationInterpolation interpolation) {
|
||||
auto sorted = sortedKeys(keys);
|
||||
if (sorted.isEmpty()) return fallback;
|
||||
if (interpolation == Project::AnimationInterpolation::Hold) {
|
||||
double out = fallback;
|
||||
for (const auto& k : sorted) {
|
||||
if (k.frame <= frame) out = k.numberValue;
|
||||
else break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (frame <= sorted.front().frame) return sorted.front().numberValue;
|
||||
if (frame >= sorted.back().frame) return sorted.back().numberValue;
|
||||
for (int i = 0; i + 1 < sorted.size(); ++i) {
|
||||
const auto& a = sorted[i];
|
||||
const auto& b = sorted[i + 1];
|
||||
if (frame >= a.frame && frame <= b.frame) {
|
||||
if (a.frame == b.frame) return a.numberValue;
|
||||
const double t = double(frame - a.frame) / double(b.frame - a.frame);
|
||||
return a.numberValue + (b.numberValue - a.numberValue) * t;
|
||||
}
|
||||
}
|
||||
return sorted.back().numberValue;
|
||||
}
|
||||
|
||||
QPointF sampleVec2(const QVector<Project::AnimationKey>& keys,
|
||||
int frame,
|
||||
const QPointF& fallback,
|
||||
Project::AnimationInterpolation interpolation) {
|
||||
auto sorted = sortedKeys(keys);
|
||||
if (sorted.isEmpty()) return fallback;
|
||||
if (interpolation == Project::AnimationInterpolation::Hold) {
|
||||
QPointF out = fallback;
|
||||
for (const auto& k : sorted) {
|
||||
if (k.frame <= frame) out = k.vec2Value;
|
||||
else break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (frame <= sorted.front().frame) return sorted.front().vec2Value;
|
||||
if (frame >= sorted.back().frame) return sorted.back().vec2Value;
|
||||
for (int i = 0; i + 1 < sorted.size(); ++i) {
|
||||
const auto& a = sorted[i];
|
||||
const auto& b = sorted[i + 1];
|
||||
if (frame >= a.frame && frame <= b.frame) {
|
||||
if (a.frame == b.frame) return a.vec2Value;
|
||||
const double t = double(frame - a.frame) / double(b.frame - a.frame);
|
||||
return QPointF(a.vec2Value.x() + (b.vec2Value.x() - a.vec2Value.x()) * t,
|
||||
a.vec2Value.y() + (b.vec2Value.y() - a.vec2Value.y()) * t);
|
||||
}
|
||||
}
|
||||
return sorted.back().vec2Value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AnimationSample evaluateAnimation(const Project::Animation& animation, int frame) {
|
||||
AnimationSample out;
|
||||
const int f = animation.loop && animation.lengthFrames > 0
|
||||
? (std::max(0, frame) % animation.lengthFrames)
|
||||
: std::clamp(frame, 0, std::max(0, animation.lengthFrames - 1));
|
||||
|
||||
for (const auto& t : animation.tracks) {
|
||||
if (t.targetId.isEmpty() || t.keys.isEmpty()) continue;
|
||||
if (t.targetKind == Project::AnimationTargetKind::Entity) {
|
||||
switch (t.property) {
|
||||
case Project::AnimationProperty::Position:
|
||||
out.entityPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation));
|
||||
break;
|
||||
case Project::AnimationProperty::UserScale:
|
||||
out.entityUserScale.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation));
|
||||
break;
|
||||
case Project::AnimationProperty::DepthScale:
|
||||
out.entityDepthScale01.insert(t.targetId, sampleDouble(t.keys, f, 0.5, t.interpolation));
|
||||
break;
|
||||
case Project::AnimationProperty::Sprite:
|
||||
// 贴图仅支持二进制 PNG bytes(不兼容路径关键帧)。
|
||||
if (t.valueType == Project::AnimationValueType::PngBytes) {
|
||||
out.entitySpritePng.insert(t.targetId, sampleBytes(t.keys, f));
|
||||
}
|
||||
break;
|
||||
case Project::AnimationProperty::Visibility:
|
||||
out.entityVisibility.insert(t.targetId, sampleBool(t.keys, f, true));
|
||||
break;
|
||||
case Project::AnimationProperty::Priority:
|
||||
out.entityPriority.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (t.targetKind == Project::AnimationTargetKind::Tool) {
|
||||
if (t.property == Project::AnimationProperty::Position) {
|
||||
out.toolPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation));
|
||||
} else if (t.property == Project::AnimationProperty::Visibility) {
|
||||
out.toolVisibility.insert(t.targetId, sampleBool(t.keys, f, true));
|
||||
} else if (t.property == Project::AnimationProperty::Priority) {
|
||||
out.toolPriority.insert(t.targetId, sampleDouble(t.keys, f, 10.0, t.interpolation));
|
||||
}
|
||||
} else if (t.targetKind == Project::AnimationTargetKind::Camera) {
|
||||
if (t.property == Project::AnimationProperty::Position) {
|
||||
out.cameraPosition.insert(t.targetId, sampleVec2(t.keys, f, QPointF(), t.interpolation));
|
||||
} else if (t.property == Project::AnimationProperty::CameraScale) {
|
||||
out.cameraScale.insert(t.targetId, sampleDouble(t.keys, f, 1.0, t.interpolation));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QVector<Project::Entity::ImageFrame> spriteFramesForEntity(const Project::Animation& animation,
|
||||
const QString& entityId) {
|
||||
QVector<Project::Entity::ImageFrame> out;
|
||||
for (const auto& t : animation.tracks) {
|
||||
if (t.targetKind != Project::AnimationTargetKind::Entity ||
|
||||
t.targetId != entityId ||
|
||||
t.property != Project::AnimationProperty::Sprite) {
|
||||
continue;
|
||||
}
|
||||
out.reserve(out.size() + t.keys.size());
|
||||
for (const auto& k : t.keys) {
|
||||
if (t.valueType == Project::AnimationValueType::PngBytes) {
|
||||
if (!k.bytesValue.isEmpty()) {
|
||||
out.push_back(Project::Entity::ImageFrame{k.frame, QString()});
|
||||
}
|
||||
} else if (t.valueType == Project::AnimationValueType::ImagePath) {
|
||||
// 旧格式:不再支持路径型 sprite 关键帧(按“无/回退默认贴图”处理)
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; });
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "domain/Project.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPointF>
|
||||
#include <QString>
|
||||
|
||||
namespace core {
|
||||
|
||||
struct AnimationSample {
|
||||
QHash<QString, QPointF> entityPosition;
|
||||
QHash<QString, double> entityUserScale;
|
||||
QHash<QString, double> entityDepthScale01;
|
||||
QHash<QString, QByteArray> entitySpritePng;
|
||||
QHash<QString, bool> entityVisibility;
|
||||
QHash<QString, double> entityPriority;
|
||||
|
||||
QHash<QString, QPointF> toolPosition;
|
||||
QHash<QString, bool> toolVisibility;
|
||||
QHash<QString, double> toolPriority;
|
||||
|
||||
QHash<QString, QPointF> cameraPosition;
|
||||
QHash<QString, double> cameraScale;
|
||||
};
|
||||
|
||||
[[nodiscard]] AnimationSample evaluateAnimation(const Project::Animation& animation, int frame);
|
||||
[[nodiscard]] QVector<Project::Entity::ImageFrame> spriteFramesForEntity(const Project::Animation& animation,
|
||||
const QString& entityId);
|
||||
|
||||
} // namespace core
|
||||
Reference in New Issue
Block a user