#pragma once #include "domain/EntityIntro.h" #include #include #include #include #include #include #include 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 polygonLocal; // 从背景中抠洞的位置:固定在创建时的 world 坐标,不随实体移动 QVector 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 locationKeys; QVector depthScaleKeys; QVector userScaleKeys; QVector imageFrames; // 可见性轨道:布尔关键帧(显示/隐藏);渲染时会被解释为“10 帧淡入淡出”。 QVector visibilityKeys; EntityIntroContent intro; }; void setEntities(const QVector& entities) { m_entities = entities; } const QVector& 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 locationKeys; // 可见性轨道:布尔关键帧(显示/隐藏);渲染时会被解释为“10 帧淡入淡出”。 QVector 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& tools) { m_tools = tools; } const QVector& 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 locationKeys; QVector scaleKeys; }; void setCameras(const QVector& cameras) { m_cameras = cameras; } const QVector& 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 keys; }; struct Animation { QString id; QString name; QString payloadPath; // 例如 "assets/animations/animation-1.hfa" int fps = 30; int lengthFrames = kClipFixedFrames; bool loop = true; QVector tracks; }; void setAnimations(const QVector& animations) { m_animations = animations; } const QVector& 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& v) { m_presentationHotspots = v; } const QVector& presentationHotspots() const { return m_presentationHotspots; } QVector& 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 m_entities; QVector m_tools; QVector m_cameras; QString m_activeCameraId; QVector m_animations; QString m_activeAnimationId; QVector m_presentationHotspots; }; } // namespace core