// // Created by atdunbg on 2026/9/22. // // Components:内置组件集合。 // 组件是纯数据(POD 倾向),逻辑在系统/应用层。 // #ifndef HELLOVULKAN_COMPONENTS_H #define HELLOVULKAN_COMPONENTS_H #include #include #include #include #include #include #include #include #include #include #include namespace Engine { class Model; } namespace Engine::ECS { // 实体稳定身份:CreateEntity 时自动分配,用于序列化/跨引用。 // ECS 内部遍历不用它(用 index+generation 句柄,性能)。 struct IdComponent { UUID uuid; operator UUID() const { return uuid; } }; // 实体名(调试/查询用;大规模场景建议演进为哈希 id) struct NameComponent { std::string name; }; // 世界变换:UE USceneComponent 的 encapsulation 模式—— // SRT 分量私有,所有写入口标脏;world 矩阵由帧循环的 // Scene::UpdateTransforms() 只对脏的实体重算(transform-update // 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。 // 层级(父子变换)留给后续的层级同步系统。 // // 旋转内部存储 = 四元数(UE FTransform 语义,不存欧拉角): // 欧拉往返是漂移/万向锁之源(Gizmo 拖拽乱跳的根因)。欧拉只在 // 表皮出现——UI 显示(GetRotation 派生)与 UI 输入(SetRotation // 转存);Gizmo/物理热路径走 quat 直写(SetRotationQuat)零往返。 struct TransformComponent { // ---- 读取 ---- const glm::vec3& GetPosition() const { return m_Position; } const glm::quat& GetRotationQuat() const { return m_Rotation; } const glm::vec3& GetScale() const { return m_Scale; } const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算 // 欧拉角(度)视图:从 quat 派生(仅 UI 显示/编辑用,勿做存储)。 // 按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵反解;glm 列主序索引: // x = asin(-R[2][1])、y = atan2(R[2][0], R[2][2])、z = atan2(R[0][1], R[1][1])。 // cx 在 atan2 分子分母中约掉,pitch 贴 ±90° 依然精确 glm::vec3 GetRotation() const { const glm::mat3 R = glm::mat3_cast(m_Rotation); const f32 sp = std::clamp(-R[2][1], -1.0f, 1.0f); // = sin(pitch) if (std::abs(sp) < 1.0f) { return glm::degrees(glm::vec3( std::asin(sp), std::atan2(R[2][0], R[2][2]), std::atan2(R[0][1], R[1][1]))); } // ±90° 万向锁:yaw/roll 只剩组合角,约定 roll=0(UE 同策略) // 归入 yaw;两支组合角符号不同 return sp > 0.0f ? glm::vec3(90.0f, glm::degrees(std::atan2(-R[0][2], R[1][2])), 0.0f) : glm::vec3(-90.0f, glm::degrees(std::atan2(-R[0][2], -R[1][2])), 0.0f); } // ---- 写入(标脏,脏实体由 UpdateTransforms 重算)---- TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; } // 欧拉角(度)入口:UI/游戏逻辑用。转 quat 存(q = qy·qx·qz, // 与原 Ry·Rx·Rz 合成严格同构;不能用 glm::quat(euler) ctor—— // 那是 ZYX 航向序,语义不同) TransformComponent& SetRotation(const glm::vec3& r) { const glm::quat qx = glm::angleAxis(glm::radians(r.x), glm::vec3(1, 0, 0)); const glm::quat qy = glm::angleAxis(glm::radians(r.y), glm::vec3(0, 1, 0)); const glm::quat qz = glm::angleAxis(glm::radians(r.z), glm::vec3(0, 0, 1)); return SetRotationQuat(qy * qx * qz); } // 四元数直写(UE FTransform 语义):Gizmo 拖拽/物理写回用, // 不经欧拉往返 → 零漂移、无万向锁 TransformComponent& SetRotationQuat(const glm::quat& q) { if (m_Rotation != q) { m_Rotation = q; m_Dirty = true; } return *this; } TransformComponent& SetScale(const glm::vec3& s) { if (m_Scale != s) { m_Scale = s; m_Dirty = true; } return *this; } bool IsDirty() const { return m_Dirty; } // SRT 分量 → world 矩阵(T * R * S),清除脏标记。 // 由 Scene::UpdateTransforms 调用(或层级系统),勿手动 TransformComponent& Compose() { const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position); const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale); m_World = t * glm::mat4_cast(m_Rotation) * s; m_Dirty = false; return *this; } private: glm::vec3 m_Position{0.0f}; glm::quat m_Rotation{1.0f, 0.0f, 0.0f, 0.0f}; // 单位四元数(w,x,y,z) glm::vec3 m_Scale{1.0f}; glm::mat4 m_World{1.0f}; bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world }; // 网格渲染体:引用共享的 Model 资产。 // 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)—— // ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。 struct MeshRendererComponent { ModelRef model; }; // 骨骼动画播放状态(纯数据 + 便捷控制)。 // 采样/局部层混合逻辑在 SceneRenderer::BuildFrame(渲染侧唯一消费者); // ImGui 面板经 Play()/字段直接驱动。无此组件 = 按绑定姿势渲染。 struct AnimationComponent { int clipIndex = 0; // 当前 clip(Model::GetClips 下标) bool playing = true; bool loop = true; f32 speed = 1.0f; f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理) // ---- crossfade(clip 切换平滑过渡)---- // 来源 clip 淡出期间与目标 clip 并行采样,局部 TRS 层混合 //(palette 数量不变,只是求值端多采一组姿态) int fadeFrom = -1; // 来源 clip 下标(-1 = 无过渡) f32 fadeFromTime = 0.0f; // 来源 clip 播放头 f32 fadeElapsed = 0.0f; // 已过渡时长 f32 fadeDuration = 0.3f; // 过渡总时长(秒) // 切换 clip(带 crossfade)。越界/空 clip 保护在消费端。 void Play(int index, f32 fadeSeconds = 0.3f) { if (index == clipIndex) return; fadeFrom = clipIndex; fadeFromTime = time; fadeElapsed = 0.0f; fadeDuration = fadeSeconds; clipIndex = index; time = 0.0f; } }; // 相机。一个场景中 primary=true 的实体是当前渲染视角。 struct CameraComponent { Camera camera; bool primary = false; }; // 灯光。类型: // Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光) // Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减 struct LightComponent { enum class Type : u8 { Directional = 0, Point = 1 }; Type type = Type::Directional; glm::vec3 direction{0.0f, -1.0f, 0.0f}; // 仅方向光 glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; // rgb = 颜色,a = 强度 f32 radius = 10.0f; // 仅点光:衰减半径 bool castShadow = false; // 方向光可投影(引擎取第一个 castShadow 方向光为 CSM 光源) }; // 刚体(Jolt 后端)。纯数据 POD——头文件零 Jolt 依赖, // bodyId 存 JPH::BodyID 的裸位模式,翻译在 PhysicsWorld。 // 生命周期:PhysicsWorld::SyncScene 每帧对账(创建/销毁/参数同步), // 动态体的 Transform 由物理写回(Writeback 标脏),静态/运动学的 // Transform 由编辑器驱动同步进 body。 // 扩展路线:软体/布料 = SoftBodyComponent(Jolt SoftBodySharedSettings), // 角色 = CharacterControllerComponent(CharacterVirtual),载具 = VehicleComponent // (VehicleConstraint)——都走 PhysicsWorld 门面,不复用本组件。 struct RigidBodyComponent { enum class BodyType : u8 { Static = 0, Dynamic = 1, Kinematic = 2 }; // Mesh:用实体 MeshRenderer 的视觉网格建碰撞(Unity MeshCollider // 语义)——Static → Jolt MeshShape(精确三角网格,不可移动); // Dynamic/Kinematic → ConvexHullShape(凸包近似,MeshShape 太贵 // 不能动)。数据源 Mesh 的 CPU 位置副本,无 MeshRenderer 回退 Box enum class ColliderType : u8 { Box = 0, Sphere = 1, Mesh = 2, Capsule = 3, Cylinder = 4, Compound = 5 }; // Compound 子形状:局部 pos/rot 相对刚体原点,不递归嵌套 //(child 不能再是 Compound)。尺寸语义同顶层(× Transform 缩放烘焙) struct CompoundChild { enum class Shape : u8 { Box = 0, Sphere = 1, Capsule = 2 }; Shape shape = Shape::Box; glm::vec3 boxHalfExtents{0.25f, 0.25f, 0.25f}; f32 radius = 0.25f; // Sphere 半径 / Capsule 半径 f32 capsuleHalfHeight = 0.25f; // 仅 Capsule:圆柱段半高 glm::vec3 position{0.0f}; glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; bool operator==(const CompoundChild&) const = default; }; BodyType type = BodyType::Dynamic; // Unity 语义:加 Rigidbody 即受物理驱动 ColliderType collider = ColliderType::Box; // 碰撞体尺寸(× Transform 缩放,Unity BoxCollider.size 语义)—— // 程序化模型顶点烘焙尺寸,不能从 scale 反推。 // Capsule/Cylinder 的"半高"指圆柱段半高(不含半球端盖),总高 = // 2*(halfHeight + radius);Jolt 两 ctor 参数序相反见 PhysicsWorld glm::vec3 boxHalfExtents{0.5f, 0.5f, 0.5f}; f32 sphereRadius = 0.5f; f32 capsuleRadius = 0.5f; // Unity CapsuleCollider 默认值同构 f32 capsuleHalfHeight = 0.5f; f32 cylinderRadius = 0.5f; f32 cylinderHalfHeight = 0.5f; std::vector compound; // 仅 ColliderType::Compound // 动态体参数 f32 mass = 1.0f; f32 gravityScale = 1.0f; f32 friction = 0.5f; f32 restitution = 0.2f; // 传感器(Unity isTrigger 语义):只产生 Added/Removed 事件,无碰撞解算 bool isSensor = false; // 运行时(PhysicsWorld 维护,勿手改) u32 bodyId = 0; // JPH::BodyID 裸位 bool bodyValid = false; glm::vec3 velocity{0.0f}; // 只读调试视图:线速度(m/s) }; // 约束/关节(Jolt TwoBodyConstraint 家族)。纯数据 POD 零 Jolt 依赖; // 创建/对账在 PhysicsWorld::SyncConstraints(kPhyDbgConstraints 出图元)。 // target 存 UUID(跨存档稳定),惰性解析——SyncScene 每帧按 UUID 反查 // 实体:序列化无需两遍解析,UUID 再生后自然失联,加载顺序无关 struct JointComponent { enum class Type : u8 { Fixed = 0, Hinge = 1, Ball = 2 }; Type type = Type::Fixed; UUID target{}; // 目标实体(0 = 未设置,不建约束) // 锚点:各体 Transform 局部系(Unity connectedAnchor 语义, // × 本体缩放烘焙)。Fixed/Hinge/Ball 都连这两点 glm::vec3 anchorA{0.0f}; // 本体(挂组件的实体) glm::vec3 anchorB{0.0f}; // 目标体 // Hinge 专用:转轴(本体局部系,方向量不吃缩放)+ 角度限位(度) glm::vec3 hingeAxis{1.0f, 0.0f, 0.0f}; bool enableLimit = false; f32 limitMin = -90.0f; // Jolt 语义 min∈[-180,0] max∈[0,180] f32 limitMax = 90.0f; bool operator==(const JointComponent&) const = default; }; // 角色控制器参数(Jolt CharacterVirtual 扫掠运动学,非刚体——不受 // 物理模拟驱动,游戏逻辑经 PhysicsWorld 门面喂速度/跳跃)。本组件 // 只存参数(序列化:存参数不存状态),速度/着地等运行时状态在 // PhysicsWorld 的 CharacterController 里(对账重建) struct CharacterControllerComponent { f32 radius = 0.3f; // 胶囊半径 f32 capsuleHalfHeight = 0.4f; // 圆柱段半高(总高 = 2*(hh+r)) f32 mass = 70.0f; f32 maxSlopeAngle = 45.0f; // 度:可站立的最大坡角 f32 stepHeight = 0.4f; // 台阶自动攀爬高(WalkStairs) bool operator==(const CharacterControllerComponent&) const = default; // 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯 }; } // namespace Engine::ECS #endif //HELLOVULKAN_COMPONENTS_H