Files
2026-05-14 13:30:06 +08:00

205 lines
7.1 KiB
C++

#include "persistence/AnimationBinary.h"
#include "persistence/PersistentBinaryObject.h"
#include <QDataStream>
#include <QFile>
#include <algorithm>
namespace core {
namespace {
qint32 toInt(Project::AnimationTargetKind v) {
return static_cast<qint32>(v);
}
qint32 toInt(Project::AnimationProperty v) {
return static_cast<qint32>(v);
}
qint32 toInt(Project::AnimationValueType v) {
return static_cast<qint32>(v);
}
qint32 toInt(Project::AnimationInterpolation v) {
return static_cast<qint32>(v);
}
Project::AnimationTargetKind targetKindFromInt(qint32 v) {
if (v == toInt(Project::AnimationTargetKind::Tool)) return Project::AnimationTargetKind::Tool;
if (v == toInt(Project::AnimationTargetKind::Camera)) return Project::AnimationTargetKind::Camera;
return Project::AnimationTargetKind::Entity;
}
Project::AnimationProperty propertyFromInt(qint32 v) {
switch (v) {
case 1: return Project::AnimationProperty::UserScale;
case 2: return Project::AnimationProperty::Sprite;
case 3: return Project::AnimationProperty::Visibility;
case 4: return Project::AnimationProperty::DepthScale;
case 5: return Project::AnimationProperty::CameraScale;
default: return Project::AnimationProperty::Position;
}
}
Project::AnimationValueType valueTypeFromInt(qint32 v) {
switch (v) {
case 1: return Project::AnimationValueType::Double;
case 2: return Project::AnimationValueType::Bool;
case 3: return Project::AnimationValueType::ImagePath;
case 4: return Project::AnimationValueType::PngBytes;
default: return Project::AnimationValueType::Vec2;
}
}
Project::AnimationInterpolation interpolationFromInt(qint32 v) {
return v == 0 ? Project::AnimationInterpolation::Hold : Project::AnimationInterpolation::Linear;
}
void sortAndDedupe(QVector<Project::AnimationKey>& keys) {
std::sort(keys.begin(), keys.end(), [](const auto& a, const auto& b) {
return a.frame < b.frame;
});
QVector<Project::AnimationKey> out;
out.reserve(keys.size());
for (const auto& k : keys) {
if (!out.isEmpty() && out.last().frame == k.frame) {
out.last() = k;
} else {
out.push_back(k);
}
}
keys = std::move(out);
}
class AnimationRecord final : public PersistentBinaryObject {
public:
explicit AnimationRecord(const Project::Animation& animation) : m_src(&animation), m_dst(nullptr) {}
explicit AnimationRecord(Project::Animation& animation) : m_src(nullptr), m_dst(&animation) {}
quint32 recordMagic() const override { return AnimationBinary::kMagicAnimation; }
quint32 recordFormatVersion() const override { return AnimationBinary::kAnimationVersion; }
void writeBody(QDataStream& ds) const override {
const Project::Animation& a = *m_src;
ds << a.id << a.name << a.payloadPath;
ds << qint32(a.fps) << qint32(a.lengthFrames) << bool(a.loop);
ds << qint32(a.tracks.size());
for (const auto& t : a.tracks) {
ds << t.id << toInt(t.targetKind) << t.targetId << toInt(t.property)
<< toInt(t.valueType) << toInt(t.interpolation);
ds << qint32(t.keys.size());
for (const auto& k : t.keys) {
ds << qint32(k.frame);
switch (t.valueType) {
case Project::AnimationValueType::Vec2:
ds << double(k.vec2Value.x()) << double(k.vec2Value.y());
break;
case Project::AnimationValueType::Double:
ds << double(k.numberValue);
break;
case Project::AnimationValueType::Bool:
ds << bool(k.boolValue);
break;
case Project::AnimationValueType::ImagePath:
ds << k.stringValue;
break;
case Project::AnimationValueType::PngBytes:
ds << k.bytesValue;
break;
}
}
}
}
bool readBody(QDataStream& ds) override {
Project::Animation a;
qint32 fps = 30;
qint32 length = Project::kClipFixedFrames;
bool loop = true;
ds >> a.id >> a.name >> a.payloadPath;
ds >> fps >> length >> loop;
if (ds.status() != QDataStream::Ok || a.id.isEmpty()) return false;
a.fps = std::max(1, int(fps));
a.lengthFrames = std::max(1, int(length));
a.loop = loop;
qint32 nTracks = 0;
ds >> nTracks;
if (ds.status() != QDataStream::Ok || nTracks < 0 || nTracks > 100000) return false;
a.tracks.reserve(nTracks);
for (qint32 i = 0; i < nTracks; ++i) {
Project::AnimationTrack t;
qint32 kind = 0;
qint32 prop = 0;
qint32 valueType = 0;
qint32 interpolation = 1;
ds >> t.id >> kind >> t.targetId >> prop >> valueType >> interpolation;
if (ds.status() != QDataStream::Ok || t.targetId.isEmpty()) return false;
t.targetKind = targetKindFromInt(kind);
t.property = propertyFromInt(prop);
t.valueType = valueTypeFromInt(valueType);
t.interpolation = interpolationFromInt(interpolation);
qint32 nKeys = 0;
ds >> nKeys;
if (ds.status() != QDataStream::Ok || nKeys < 0 || nKeys > 1000000) return false;
t.keys.reserve(nKeys);
for (qint32 kidx = 0; kidx < nKeys; ++kidx) {
Project::AnimationKey k;
qint32 frame = 0;
ds >> frame;
k.frame = int(frame);
switch (t.valueType) {
case Project::AnimationValueType::Vec2: {
double x = 0.0;
double y = 0.0;
ds >> x >> y;
k.vec2Value = QPointF(x, y);
break;
}
case Project::AnimationValueType::Double:
ds >> k.numberValue;
break;
case Project::AnimationValueType::Bool:
ds >> k.boolValue;
break;
case Project::AnimationValueType::ImagePath:
ds >> k.stringValue;
break;
case Project::AnimationValueType::PngBytes:
ds >> k.bytesValue;
break;
}
if (ds.status() != QDataStream::Ok) return false;
t.keys.push_back(k);
}
sortAndDedupe(t.keys);
a.tracks.push_back(t);
}
*m_dst = std::move(a);
return true;
}
private:
const Project::Animation* m_src;
Project::Animation* m_dst;
};
} // namespace
bool AnimationBinary::save(const QString& absolutePath, const Project::Animation& animation) {
if (absolutePath.isEmpty() || animation.id.isEmpty()) {
return false;
}
return AnimationRecord(animation).saveToFile(absolutePath);
}
bool AnimationBinary::load(const QString& absolutePath, Project::Animation& animation) {
return AnimationRecord(animation).loadFromFile(absolutePath);
}
} // namespace core