Files
hfut-bishe/client/core/domain/Project.h
2026-04-09 23:13:33 +08:00

258 lines
9.5 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.
#pragma once
#include "domain/EntityIntro.h"
#include <QString>
#include <QPointF>
#include <QHash>
#include <QVector>
#include <algorithm>
namespace core {
class Project {
public:
static constexpr int kClipFixedFrames = 600;
void setName(const QString& name) { m_name = name; }
const QString& name() const { return m_name; }
// 背景图在项目目录内的相对路径,例如 "assets/background.png"
void setBackgroundImagePath(const QString& relativePath) { m_backgroundImagePath = relativePath; }
const QString& backgroundImagePath() const { return m_backgroundImagePath; }
// 背景在视口/预览中的显隐(默认显示)
void setBackgroundVisible(bool on) { m_backgroundVisible = on; }
bool backgroundVisible() const { return m_backgroundVisible; }
void setDepthComputed(bool on) { m_depthComputed = on; }
bool depthComputed() const { return m_depthComputed; }
// 深度图在项目目录内的相对路径,例如 "assets/depth.png"
void setDepthMapPath(const QString& relativePath) { m_depthMapPath = relativePath; }
const QString& depthMapPath() const { return m_depthMapPath; }
void setFrameStart(int f) { m_frameStart = f; }
int frameStart() const { return m_frameStart; }
void setFrameEnd(int f) { m_frameEnd = f; }
int frameEnd() const { return m_frameEnd; }
void setFps(int fps) { m_fps = std::max(1, fps); }
int fps() const { return m_fps; }
struct ToolKeyframeBool {
int frame = 0;
bool value = true;
};
struct Entity {
QString id;
QString displayName; // 显示名(空则界面用 id
bool visible = true; // 默认显隐(无 visibilityKeys 时使用)
// 可移动实体形状:存为局部坐标(相对 originWorld
QVector<QPointF> polygonLocal;
// 从背景中抠洞的位置:固定在创建时的 world 坐标,不随实体移动
QVector<QPointF> cutoutPolygonWorld;
// 背景空缺标识:用于项目树节点和交互,空则按 id 生成默认值
QString blackholeId;
// 背景空缺可见性:与实体可见性轨道解耦,控制背景空缺是否显示
bool blackholeVisible = true;
// 背景空缺修复方案copy_background / use_original_background / model_inpaint预留
QString blackholeResolvedBy;
QPointF originWorld;
int depth = 0; // 0..255
QString imagePath; // 相对路径,例如 "assets/entities/entity-1.png"
QPointF imageTopLeftWorld; // 贴图左上角 world 坐标
// 人为整体缩放,与深度驱动的距离缩放相乘(画布中 visualScale = distanceScale * userScale
// distanceScale 在有 distanceScaleCalibMult 时为 (0.5+depth01)/calib使抠图处为 1.0
double userScale = 1.0;
// 抠图创建时该位置对应的原始距离乘子0.5+depth01用于校准该处 distanceScale==1.0。0 表示未校准(兼容旧工程)
double distanceScaleCalibMult = 0.0;
// 距离缩放开关:为 true 时实体不受 depth->distanceScale 影响,仅受 userScale 影响。
// 约定:对话气泡等 UI 元素默认打开。
bool ignoreDistanceScale = false;
// 父子关系:当 parentId 非空时实体会保持相对父实体的偏移world 坐标)。
// parentOffsetWorld 表示「childOrigin - parentOrigin」在 world 中的偏移。
QString parentId;
QPointF parentOffsetWorld;
struct KeyframeVec2 {
int frame = 0;
QPointF value;
};
struct KeyframeFloat01 {
int frame = 0;
double value = 0.5; // 0..1,默认 0.5 -> scale=1.00.5..1.5 映射)
};
struct KeyframeDouble {
int frame = 0;
double value = 1.0;
};
struct ImageFrame {
int frame = 0;
QString imagePath; // 相对路径
};
// v2project.json 仅存 id + payload几何与动画在 entityPayloadPath.hfe中。
QString entityPayloadPath; // 例如 "assets/entities/entity-1.hfe"
// 仅打开 v1 项目时由 JSON 的 animationBundle 填入,用于合并旧 .anim保存 v2 前应为空。
QString legacyAnimSidecarPath;
QVector<KeyframeVec2> locationKeys;
QVector<KeyframeFloat01> depthScaleKeys;
QVector<KeyframeDouble> userScaleKeys;
QVector<ImageFrame> imageFrames;
// 可见性轨道:布尔关键帧(显示/隐藏渲染时会被解释为“10 帧淡入淡出”。
QVector<ToolKeyframeBool> visibilityKeys;
EntityIntroContent intro;
};
void setEntities(const QVector<Entity>& entities) { m_entities = entities; }
const QVector<Entity>& entities() const { return m_entities; }
// —— 工具(精简版实体,不含 intro/图片/视频)——
struct Tool {
enum class Type { Bubble };
QString id;
QString displayName;
bool visible = true; // 编辑模式显隐
// 父子关系同实体规则。parentId 可指向实体或工具的 id。
QString parentId;
QPointF parentOffsetWorld;
// 基准位置(无关键帧时使用)
QPointF originWorld;
QVector<Entity::KeyframeVec2> locationKeys;
// 可见性轨道:布尔关键帧(显示/隐藏渲染时会被解释为“10 帧淡入淡出”。
QVector<ToolKeyframeBool> visibilityKeys;
// 类型与 payload
Type type = Type::Bubble;
// Bubble payload
QString text;
int fontPx = 18;
enum class TextAlign { Left, Center, Right };
TextAlign align = TextAlign::Center;
// 气泡底边「平直段」上三角形附着位置0=靠左0.5=居中1=靠右;主体会水平平移,使该点始终位于 originWorld 尖端正上方
double bubblePointerT01 = 0.5;
};
void setTools(const QVector<Tool>& tools) { m_tools = tools; }
const QVector<Tool>& tools() const { return m_tools; }
// —— 动画系统Blender/NLA 风格简化版,工程级)——
struct AnimationClip {
QString id;
QString name;
// Entity channels (keyed by entity id)
QHash<QString, QVector<Entity::KeyframeVec2>> entityLocationKeys;
QHash<QString, QVector<Entity::KeyframeDouble>> entityUserScaleKeys;
QHash<QString, QVector<Entity::ImageFrame>> entityImageFrames;
QHash<QString, QVector<ToolKeyframeBool>> entityVisibilityKeys;
// Tool channels (keyed by tool id)
QHash<QString, QVector<Entity::KeyframeVec2>> toolLocationKeys;
QHash<QString, QVector<ToolKeyframeBool>> toolVisibilityKeys;
};
struct NlaStrip {
QString id;
QString clipId;
int startSlot = 0; // slot index; 1 slot = kClipFixedFrames frames
int slotLen = 1; // currently fixed to 1; reserved for future
bool enabled = true;
bool muted = false;
};
struct NlaTrack {
QString id;
QString name;
bool muted = false;
bool solo = false;
QVector<NlaStrip> strips;
};
struct AnimationScheme {
QString id;
QString name;
QVector<NlaTrack> tracks;
};
void setAnimationClips(const QVector<AnimationClip>& clips) { m_clips = clips; }
const QVector<AnimationClip>& animationClips() const { return m_clips; }
void setAnimationSchemes(const QVector<AnimationScheme>& schemes) { m_schemes = schemes; }
const QVector<AnimationScheme>& animationSchemes() const { return m_schemes; }
void setActiveSchemeId(const QString& id) { m_activeSchemeId = id; }
const QString& activeSchemeId() const { return m_activeSchemeId; }
void setSelectedStripId(const QString& id) { m_selectedStripId = id; }
const QString& selectedStripId() const { return m_selectedStripId; }
const AnimationScheme* findSchemeById(const QString& id) const {
for (const auto& s : m_schemes) {
if (s.id == id) return &s;
}
return nullptr;
}
AnimationScheme* findSchemeById(const QString& id) {
for (auto& s : m_schemes) {
if (s.id == id) return &s;
}
return nullptr;
}
const AnimationClip* findClipById(const QString& id) const {
for (const auto& c : m_clips) {
if (c.id == id) return &c;
}
return nullptr;
}
AnimationClip* findClipById(const QString& id) {
for (auto& c : m_clips) {
if (c.id == id) return &c;
}
return nullptr;
}
const AnimationScheme* activeSchemeOrNull() const {
const AnimationScheme* s = findSchemeById(m_activeSchemeId);
if (s) return s;
return m_schemes.isEmpty() ? nullptr : &m_schemes.front();
}
AnimationScheme* activeSchemeOrNull() {
AnimationScheme* s = findSchemeById(m_activeSchemeId);
if (s) return s;
return m_schemes.isEmpty() ? nullptr : &m_schemes.front();
}
private:
QString m_name;
QString m_backgroundImagePath;
bool m_backgroundVisible = true;
bool m_depthComputed = false;
QString m_depthMapPath;
int m_frameStart = 0;
int m_frameEnd = 600;
int m_fps = 60;
QVector<Entity> m_entities;
QVector<Tool> m_tools;
QVector<AnimationClip> m_clips;
QVector<AnimationScheme> m_schemes;
QString m_activeSchemeId;
QString m_selectedStripId;
};
} // namespace core