Files
hfut-bishe/client/core/domain/Project.h
T
2026-05-14 13:30:06 +08:00

269 lines
11 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 <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.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"
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