269 lines
11 KiB
C++
269 lines
11 KiB
C++
#pragma once
|
||
|
||
#include "domain/EntityIntro.h"
|
||
|
||
#include <QRect>
|
||
#include <QString>
|
||
#include <QPointF>
|
||
#include <QHash>
|
||
#include <QByteArray>
|
||
#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;
|
||
/// 修复结果相对项目根路径;与 blackholeOverlayRect 一起用于画布叠加,不修改 background 原图
|
||
QString blackholeOverlayPath;
|
||
/// 覆盖层在背景图坐标系中的像素矩形;宽或高 <=0 表示无
|
||
QRect blackholeOverlayRect;
|
||
QPointF originWorld;
|
||
int depth = 0; // 0..255
|
||
int priority = 1; // 0=背景之上最底层;越大越靠上(同优先级再按距离缩放/深度)
|
||
QString imagePath; // 相对路径,例如 "assets/entities/entity-1.png"
|
||
QByteArray defaultImagePng; // 默认贴图 PNG bytes(不兼容旧路径工程时此为唯一来源)
|
||
QByteArray runtimeImagePng; // 运行时求值后的 PNG bytes(不序列化)
|
||
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;
|
||
|
||
// 父子关系:在「原点已与形心对齐」时,parentOffsetWorld 表示子形心相对父形心的世界偏移;否则为子原点相对父原点的偏移(与求值器一致)。
|
||
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.0(0.5..1.5 映射)
|
||
};
|
||
struct KeyframeDouble {
|
||
int frame = 0;
|
||
double value = 1.0;
|
||
};
|
||
struct ImageFrame {
|
||
int frame = 0;
|
||
QString imagePath; // 相对路径
|
||
};
|
||
|
||
// v2:project.json 仅存 id + payload,几何与动画在 entityPayloadPath(.hfe)中。
|
||
QString entityPayloadPath; // 例如 "assets/entities/entity-1.hfe"
|
||
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;
|
||
int priority = 10; // 默认高于实体(实体默认 1);越大越靠上
|
||
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; }
|
||
|
||
// —— 摄像机(正交俯视;centerWorld 为视口中心;viewScale 为相对参考分辨率 1600×900 的像素/世界单位比,
|
||
// 预览与视口框按该参考换算,不随实际窗口宽高改变「镜头」所覆盖的世界范围)——
|
||
struct Camera {
|
||
QString id;
|
||
QString displayName;
|
||
bool visible = true;
|
||
QPointF centerWorld;
|
||
double viewScale = 1.0;
|
||
QVector<Entity::KeyframeVec2> locationKeys;
|
||
QVector<Entity::KeyframeDouble> scaleKeys;
|
||
};
|
||
|
||
void setCameras(const QVector<Camera>& cameras) { m_cameras = cameras; }
|
||
const QVector<Camera>& cameras() const { return m_cameras; }
|
||
|
||
void setActiveCameraId(const QString& id) { m_activeCameraId = id; }
|
||
const QString& activeCameraId() const { return m_activeCameraId; }
|
||
|
||
// —— 动画系统(Godot AnimationPlayer 风格简化版)——
|
||
enum class AnimationTargetKind { Entity, Tool, Camera };
|
||
enum class AnimationProperty { Position, UserScale, Sprite, Visibility, DepthScale, CameraScale, Priority };
|
||
enum class AnimationValueType { Vec2, Double, Bool, ImagePath, PngBytes };
|
||
enum class AnimationInterpolation { Hold, Linear };
|
||
|
||
struct AnimationKey {
|
||
int frame = 0;
|
||
QPointF vec2Value;
|
||
double numberValue = 0.0;
|
||
bool boolValue = true;
|
||
QString stringValue;
|
||
QByteArray bytesValue;
|
||
};
|
||
|
||
struct AnimationTrack {
|
||
QString id;
|
||
AnimationTargetKind targetKind = AnimationTargetKind::Entity;
|
||
QString targetId;
|
||
AnimationProperty property = AnimationProperty::Position;
|
||
AnimationValueType valueType = AnimationValueType::Vec2;
|
||
AnimationInterpolation interpolation = AnimationInterpolation::Linear;
|
||
QVector<AnimationKey> keys;
|
||
};
|
||
|
||
struct Animation {
|
||
QString id;
|
||
QString name;
|
||
QString payloadPath; // 例如 "assets/animations/animation-1.hfa"
|
||
int fps = 30;
|
||
int lengthFrames = kClipFixedFrames;
|
||
bool loop = true;
|
||
QVector<AnimationTrack> tracks;
|
||
};
|
||
|
||
void setAnimations(const QVector<Animation>& animations) { m_animations = animations; }
|
||
const QVector<Animation>& animations() const { return m_animations; }
|
||
|
||
void setActiveAnimationId(const QString& id) { m_activeAnimationId = id; }
|
||
const QString& activeAnimationId() const { return m_activeAnimationId; }
|
||
|
||
const Animation* findAnimationById(const QString& id) const {
|
||
for (const auto& a : m_animations) {
|
||
if (a.id == id) return &a;
|
||
}
|
||
return nullptr;
|
||
}
|
||
Animation* findAnimationById(const QString& id) {
|
||
for (auto& a : m_animations) {
|
||
if (a.id == id) return &a;
|
||
}
|
||
return nullptr;
|
||
}
|
||
const Animation* activeAnimationOrNull() const {
|
||
const Animation* a = findAnimationById(m_activeAnimationId);
|
||
if (a) return a;
|
||
return m_animations.isEmpty() ? nullptr : &m_animations.front();
|
||
}
|
||
Animation* activeAnimationOrNull() {
|
||
Animation* a = findAnimationById(m_activeAnimationId);
|
||
if (a) return a;
|
||
return m_animations.isEmpty() ? nullptr : &m_animations.front();
|
||
}
|
||
|
||
struct PresentationHotspot {
|
||
QString id;
|
||
QPointF centerWorld;
|
||
QString targetAnimationId;
|
||
};
|
||
|
||
void setPresentationHotspots(const QVector<PresentationHotspot>& v) { m_presentationHotspots = v; }
|
||
const QVector<PresentationHotspot>& presentationHotspots() const { return m_presentationHotspots; }
|
||
QVector<PresentationHotspot>& presentationHotspotsRef() { return m_presentationHotspots; }
|
||
|
||
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<Camera> m_cameras;
|
||
QString m_activeCameraId;
|
||
|
||
QVector<Animation> m_animations;
|
||
QString m_activeAnimationId;
|
||
QVector<PresentationHotspot> m_presentationHotspots;
|
||
};
|
||
|
||
} // namespace core
|