Files
hfut-bishe/client/core/persistence/EntityBundleStore.cpp
T
2026-05-14 13:30:06 +08:00

363 lines
12 KiB
C++
Raw 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 "persistence/EntityBundleStore.h"
#include "persistence/JsonFileAtomic.h"
#include <QDir>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonObject>
namespace core::persistence {
namespace {
QJsonArray pointsToJson(const QVector<QPointF>& pts) {
QJsonArray arr;
for (const auto& p : pts) {
QJsonObject o;
o.insert("x", p.x());
o.insert("y", p.y());
arr.append(o);
}
return arr;
}
bool pointsFromJson(const QJsonValue& v, QVector<QPointF>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
out.push_back(QPointF(o.value("x").toDouble(0.0), o.value("y").toDouble(0.0)));
}
return true;
}
template <typename KeyT>
QJsonArray boolKeysToJson(const QVector<KeyT>& keys) {
QJsonArray arr;
for (const auto& k : keys) {
QJsonObject o;
o.insert("frame", k.frame);
o.insert("value", k.value);
arr.append(o);
}
return arr;
}
QJsonArray vec2KeysToJson(const QVector<Project::Entity::KeyframeVec2>& keys) {
QJsonArray arr;
for (const auto& k : keys) {
QJsonObject o;
o.insert("frame", k.frame);
o.insert("x", k.value.x());
o.insert("y", k.value.y());
arr.append(o);
}
return arr;
}
QJsonArray floatKeysToJson(const QVector<Project::Entity::KeyframeFloat01>& keys) {
QJsonArray arr;
for (const auto& k : keys) {
QJsonObject o;
o.insert("frame", k.frame);
o.insert("value", k.value);
arr.append(o);
}
return arr;
}
QJsonArray doubleKeysToJson(const QVector<Project::Entity::KeyframeDouble>& keys) {
QJsonArray arr;
for (const auto& k : keys) {
QJsonObject o;
o.insert("frame", k.frame);
o.insert("value", k.value);
arr.append(o);
}
return arr;
}
QJsonArray imageFramesToJson(const QVector<Project::Entity::ImageFrame>& keys) {
QJsonArray arr;
for (const auto& k : keys) {
QJsonObject o;
o.insert("frame", k.frame);
o.insert("path", k.imagePath);
arr.append(o);
}
return arr;
}
template <typename KeyT>
bool boolKeysFromJson(const QJsonValue& v, QVector<KeyT>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
KeyT k;
k.frame = o.value("frame").toInt(0);
k.value = o.value("value").toBool(true);
out.push_back(k);
}
return true;
}
bool vec2KeysFromJson(const QJsonValue& v, QVector<Project::Entity::KeyframeVec2>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
Project::Entity::KeyframeVec2 k;
k.frame = o.value("frame").toInt(0);
k.value = QPointF(o.value("x").toDouble(0.0), o.value("y").toDouble(0.0));
out.push_back(k);
}
return true;
}
bool floatKeysFromJson(const QJsonValue& v, QVector<Project::Entity::KeyframeFloat01>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
Project::Entity::KeyframeFloat01 k;
k.frame = o.value("frame").toInt(0);
k.value = o.value("value").toDouble(0.5);
out.push_back(k);
}
return true;
}
bool doubleKeysFromJson(const QJsonValue& v, QVector<Project::Entity::KeyframeDouble>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
Project::Entity::KeyframeDouble k;
k.frame = o.value("frame").toInt(0);
k.value = o.value("value").toDouble(1.0);
out.push_back(k);
}
return true;
}
bool imageFramesFromJson(const QJsonValue& v, QVector<Project::Entity::ImageFrame>& out) {
out.clear();
if (!v.isArray()) return true;
const QJsonArray arr = v.toArray();
out.reserve(arr.size());
for (const auto& it : arr) {
if (!it.isObject()) continue;
const QJsonObject o = it.toObject();
Project::Entity::ImageFrame k;
k.frame = o.value("frame").toInt(0);
k.imagePath = o.value("path").toString();
out.push_back(k);
}
return true;
}
QJsonObject introToJson(const core::EntityIntroContent& intro) {
QJsonObject o;
o.insert("title", intro.title);
o.insert("bodyText", intro.bodyText);
o.insert("videoPathRelative", intro.videoPathRelative);
QJsonArray imgs;
for (const auto& p : intro.imagePathsRelative) imgs.append(p);
o.insert("imagePathsRelative", imgs);
return o;
}
bool introFromJson(const QJsonObject& o, core::EntityIntroContent& out) {
out = core::EntityIntroContent{};
out.title = o.value("title").toString();
out.bodyText = o.value("bodyText").toString();
out.videoPathRelative = o.value("videoPathRelative").toString();
const QJsonValue imgsVal = o.value("imagePathsRelative");
if (imgsVal.isArray()) {
for (const auto& it : imgsVal.toArray()) {
const QString p = it.toString();
if (!p.isEmpty()) out.imagePathsRelative.push_back(p);
}
}
return true;
}
} // namespace
QString EntityBundleStore::bundleRelativeDir(const QString& entityId) {
if (entityId.isEmpty()) return {};
return QStringLiteral("assets/entities/%1").arg(entityId);
}
bool EntityBundleStore::save(const QString& projectDirAbs, const Project::Entity& entity) {
if (projectDirAbs.isEmpty() || entity.id.isEmpty()) {
return false;
}
const QString relDir = bundleRelativeDir(entity.id);
if (relDir.isEmpty()) return false;
const QString absDir = QDir(projectDirAbs).filePath(relDir);
QDir().mkpath(absDir);
// meta.json
QJsonObject meta;
meta.insert("id", entity.id);
meta.insert("displayName", entity.displayName);
meta.insert("visible", entity.visible);
meta.insert("originX", entity.originWorld.x());
meta.insert("originY", entity.originWorld.y());
meta.insert("depth", entity.depth);
meta.insert("priority", entity.priority);
meta.insert("imagePath", entity.imagePath);
meta.insert("imageTopLeftX", entity.imageTopLeftWorld.x());
meta.insert("imageTopLeftY", entity.imageTopLeftWorld.y());
meta.insert("userScale", entity.userScale);
meta.insert("distanceScaleCalibMult", entity.distanceScaleCalibMult);
meta.insert("ignoreDistanceScale", entity.ignoreDistanceScale);
meta.insert("parentId", entity.parentId);
meta.insert("parentOffsetX", entity.parentOffsetWorld.x());
meta.insert("parentOffsetY", entity.parentOffsetWorld.y());
meta.insert("polygonLocal", pointsToJson(entity.polygonLocal));
meta.insert("cutoutPolygonWorld", pointsToJson(entity.cutoutPolygonWorld));
meta.insert("blackholeId", entity.blackholeId);
meta.insert("blackholeVisible", entity.blackholeVisible);
meta.insert("blackholeResolvedBy", entity.blackholeResolvedBy);
meta.insert("blackholeOverlayPath", entity.blackholeOverlayPath);
meta.insert("blackholeOverlayRectX", entity.blackholeOverlayRect.x());
meta.insert("blackholeOverlayRectY", entity.blackholeOverlayRect.y());
meta.insert("blackholeOverlayRectW", entity.blackholeOverlayRect.width());
meta.insert("blackholeOverlayRectH", entity.blackholeOverlayRect.height());
if (!writeJsonAtomic(QDir(absDir).filePath(QStringLiteral("meta.json")), meta, true)) {
return false;
}
// anim.json
QJsonObject anim;
anim.insert("locationKeys", vec2KeysToJson(entity.locationKeys));
anim.insert("depthScaleKeys", floatKeysToJson(entity.depthScaleKeys));
anim.insert("userScaleKeys", doubleKeysToJson(entity.userScaleKeys));
anim.insert("visibilityKeys", boolKeysToJson<Project::ToolKeyframeBool>(entity.visibilityKeys));
anim.insert("imageFrames", imageFramesToJson(entity.imageFrames));
if (!writeJsonAtomic(QDir(absDir).filePath(QStringLiteral("anim.json")), anim, true)) {
return false;
}
// intro.json
if (!writeJsonAtomic(QDir(absDir).filePath(QStringLiteral("intro.json")), introToJson(entity.intro), true)) {
return false;
}
// default.png (可为空)
const QString pngAbs = QDir(absDir).filePath(QStringLiteral("default.png"));
if (!entity.defaultImagePng.isEmpty()) {
if (!writeBytesAtomic(pngAbs, entity.defaultImagePng)) {
return false;
}
} else {
// 清空时删掉,避免读到旧图
if (QFileInfo::exists(pngAbs)) {
QFile::remove(pngAbs);
}
}
return true;
}
bool EntityBundleStore::load(const QString& projectDirAbs, Project::Entity& inOutEntity) {
if (projectDirAbs.isEmpty() || inOutEntity.id.isEmpty()) {
return false;
}
const QString relDir = inOutEntity.entityPayloadPath;
if (relDir.isEmpty()) return false;
const QString absDir = QDir(projectDirAbs).filePath(relDir);
if (!QFileInfo(absDir).exists()) return false;
QJsonObject meta;
if (!readJsonObject(QDir(absDir).filePath(QStringLiteral("meta.json")), meta)) {
return false;
}
const QString id = meta.value("id").toString();
if (id.isEmpty() || id != inOutEntity.id) {
return false;
}
Project::Entity e = inOutEntity; // 保留 stubid/payloadPath/visible 等必要字段会被覆盖
e.displayName = meta.value("displayName").toString();
e.visible = meta.value("visible").toBool(true);
e.originWorld = QPointF(meta.value("originX").toDouble(0.0), meta.value("originY").toDouble(0.0));
e.depth = meta.value("depth").toInt(0);
e.priority = meta.value("priority").toInt(1);
e.imagePath = meta.value("imagePath").toString();
e.imageTopLeftWorld = QPointF(meta.value("imageTopLeftX").toDouble(0.0), meta.value("imageTopLeftY").toDouble(0.0));
e.userScale = meta.value("userScale").toDouble(1.0);
e.distanceScaleCalibMult = meta.value("distanceScaleCalibMult").toDouble(0.0);
e.ignoreDistanceScale = meta.value("ignoreDistanceScale").toBool(false);
e.parentId = meta.value("parentId").toString();
e.parentOffsetWorld = QPointF(meta.value("parentOffsetX").toDouble(0.0), meta.value("parentOffsetY").toDouble(0.0));
pointsFromJson(meta.value("polygonLocal"), e.polygonLocal);
pointsFromJson(meta.value("cutoutPolygonWorld"), e.cutoutPolygonWorld);
e.blackholeId = meta.value("blackholeId").toString();
e.blackholeVisible = meta.value("blackholeVisible").toBool(true);
e.blackholeResolvedBy = meta.value("blackholeResolvedBy").toString();
e.blackholeOverlayPath = meta.value("blackholeOverlayPath").toString();
e.blackholeOverlayRect = QRect(meta.value("blackholeOverlayRectX").toInt(0),
meta.value("blackholeOverlayRectY").toInt(0),
meta.value("blackholeOverlayRectW").toInt(0),
meta.value("blackholeOverlayRectH").toInt(0));
QJsonObject anim;
if (!readJsonObject(QDir(absDir).filePath(QStringLiteral("anim.json")), anim)) {
return false;
}
vec2KeysFromJson(anim.value("locationKeys"), e.locationKeys);
floatKeysFromJson(anim.value("depthScaleKeys"), e.depthScaleKeys);
doubleKeysFromJson(anim.value("userScaleKeys"), e.userScaleKeys);
boolKeysFromJson<Project::ToolKeyframeBool>(anim.value("visibilityKeys"), e.visibilityKeys);
imageFramesFromJson(anim.value("imageFrames"), e.imageFrames);
QJsonObject intro;
if (!readJsonObject(QDir(absDir).filePath(QStringLiteral("intro.json")), intro)) {
return false;
}
introFromJson(intro, e.intro);
// default.png
const QString pngAbs = QDir(absDir).filePath(QStringLiteral("default.png"));
if (QFileInfo::exists(pngAbs)) {
QFile f(pngAbs);
if (f.open(QIODevice::ReadOnly)) {
e.defaultImagePng = f.readAll();
} else {
return false;
}
} else {
e.defaultImagePng.clear();
}
inOutEntity = std::move(e);
return true;
}
} // namespace core::persistence