#include "persistence/AnimationBinary.h" #include "persistence/PersistentBinaryObject.h" #include #include #include namespace core { namespace { qint32 toInt(Project::AnimationTargetKind v) { return static_cast(v); } qint32 toInt(Project::AnimationProperty v) { return static_cast(v); } qint32 toInt(Project::AnimationValueType v) { return static_cast(v); } qint32 toInt(Project::AnimationInterpolation v) { return static_cast(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& keys) { std::sort(keys.begin(), keys.end(), [](const auto& a, const auto& b) { return a.frame < b.frame; }); QVector 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