diff --git a/MVS/VolumeRenderer/CMakeLists.txt b/MVS/VolumeRenderer/CMakeLists.txt index c11d5a6c..4d099822 100644 --- a/MVS/VolumeRenderer/CMakeLists.txt +++ b/MVS/VolumeRenderer/CMakeLists.txt @@ -1,6 +1,12 @@ cmake_minimum_required(VERSION 3.15) project(XCVolumeRenderer VERSION 1.0 LANGUAGES CXX) +# ============================================================ +# Vcpkg Integration +# ============================================================ +set(VCPKG_ROOT "D:/vcpkg") +set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) @@ -28,6 +34,7 @@ target_compile_options(${PROJECT_NAME} PRIVATE /utf-8 /MT) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR} + ${VCPKG_ROOT}/installed/x64-windows/include ) target_link_libraries(${PROJECT_NAME} PRIVATE diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 00000000..27223a40 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,285 @@ +# XCEngine 测试体系文档 + +> **版本**: 1.0 +> **日期**: 2026-03-13 + +--- + +## 1. 测试架构 + +``` +tests/ +├── CMakeLists.txt # 测试构建配置 +├── run_tests.cmake # 测试运行脚本 +├── fixtures/ # 测试夹具 +│ └── MathFixtures.h +├── math/ # Math 单元测试 +│ ├── CMakeLists.txt +│ ├── test_vector.cpp +│ ├── test_matrix.cpp +│ ├── test_quaternion.cpp +│ └── test_geometry.cpp +├── core/ # Core 测试 +├── threading/ # 线程测试 +├── memory/ # 内存测试 +├── containers/ # 容器测试 +└── rendering/ # 渲染测试 + ├── unit/ # 单元测试 + ├── integration/ # 集成测试 + └── screenshots/ # 参考图 +``` + +--- + +## 2. 测试分类 + +| 类型 | 目录 | 目的 | 运行频率 | +|------|------|------|---------| +| **Unit Test** | `tests/*/` | 验证单个函数/类 | 每次提交 | +| **Integration Test** | `tests/rendering/integration/` | 验证多模块协作 | 每次提交 | +| **Benchmark** | `tests/benchmark/` | 性能回归检测 | 每日/每周 | +| **Screenshot Test** | `tests/rendering/screenshots/` | 渲染正确性 | 每次提交 | + +--- + +## 3. 测试命名规范 + +```cpp +// 格式: test_<模块>_<功能>_<场景> +TEST(Math_Vector3, Dot_TwoVectors_ReturnsCorrectValue) { } +TEST(Math_Vector3, Normalize_ZeroVector_ReturnsZeroVector) { } +TEST(Math_Matrix4, Inverse_Identity_ReturnsIdentity) { } +TEST(Math_Matrix4, TRS_Decompose_RecoversOriginalValues) { } +TEST(Math_Quaternion, Slerp_ShortestPath_InterpolatesCorrectly) { } + +// 边界情况 +TEST(Math_Vector3, Normalize_ZeroVector_DoesNotCrash) { } +TEST(Math_Matrix4, Inverse_SingularMatrix_ReturnsIdentity) { } +``` + +--- + +## 4. 断言规范 + +### 4.1 浮点数比较 + +```cpp +// 必须使用容差 +EXPECT_NEAR(actual, expected, 1e-5f); +ASSERT_FLOAT_EQ(actual, expected); // gtest 内部有容差 + +// 数组比较 +for (int i = 0; i < 4; i++) { + EXPECT_NEAR(actual.m[i], expected.m[i], 1e-5f); +} +``` + +### 4.2 常用断言 + +```cpp +EXPECT_TRUE(condition); +EXPECT_FALSE(condition); +EXPECT_EQ(actual, expected); +EXPECT_NE(actual, expected); +EXPECT_STREQ(actual, expected); +EXPECT_THROW(expression, exception_type); +``` + +--- + +## 5. 测试夹具 (Fixture) + +```cpp +class MathFixture : public ::testing::Test { +protected: + void SetUp() override { + v1 = Vector3(1, 0, 0); + v2 = Vector3(0, 1, 0); + v3 = Vector3(1, 1, 1); + + m1 = Matrix4x4::Identity(); + m2 = Matrix4x4::Translation(Vector3(1, 2, 3)); + } + + Vector3 v1, v2, v3; + Matrix4x4 m1, m2; + const float epsilon = 1e-5f; +}; + +TEST_F(MathFixture, Dot_OrthogonalVectors_ReturnsZero) { + EXPECT_FLOAT_EQ(Vector3::Dot(v1, v2), 0.0f); +} +``` + +--- + +## 6. 参数化测试 + +```cpp +class MatrixInverseTest : public ::testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P( + InverseCases, + MatrixInverseTest, + testing::Values( + Matrix4x4::Identity(), + Matrix4x4::Translation(Vector3(1,2,3)), + Matrix4x4::Scale(Vector3(2,2,2)) + ) +); + +TEST_P(MatrixInverseTest, InverseOfInverse_EqualsOriginal) { + Matrix4x4 original = GetParam(); + Matrix4x4 inverted = original.Inverse(); + Matrix4x4 recovered = inverted.Inverse(); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + EXPECT_NEAR(original.m[i][j], recovered.m[i][j], 1e-4f); + } + } +} +``` + +--- + +## 7. Math 测试用例 + +### 7.1 Vector2/3/4 测试 + +| 测试类别 | 测试用例 | +|---------|---------| +| **构造** | 默认构造、参数构造、从 Vector3 构造 | +| **运算** | 加、减、乘、除、点积、叉积 | +| **归一化** | Normalize、Normalized、Magnitude、SqrMagnitude | +| **插值** | Lerp、MoveTowards | +| **投影** | Project、ProjectOnPlane | +| **角度** | Angle、Reflect | + +### 7.2 Matrix 测试 + +| 测试类别 | 测试用例 | +|---------|---------| +| **构造** | Identity、Zero | +| **变换** | Translation、Rotation、Scale、TRS | +| **相机** | LookAt、Perspective、Orthographic | +| **运算** | 乘法、点乘、叉乘 | +| **分解** | Inverse、Transpose、Determinant、Decompose | + +### 7.3 Quaternion 测试 + +| 测试类别 | 测试用例 | +|---------|---------| +| **构造** | Identity、FromAxisAngle、FromEulerAngles | +| **转换** | ToEulerAngles、ToMatrix4x4、FromRotationMatrix | +| **插值** | Slerp | +| **运算** | 乘法和逆 | + +### 7.4 几何测试 + +| 测试类型 | 测试用例 | +|---------|---------| +| **Ray** | GetPoint、Intersects(Sphere/Box/Plane) | +| **Sphere** | Contains、Intersects | +| **Box** | Contains、Intersects | +| **Plane** | FromPoints、GetDistanceToPoint、Intersects | +| **Frustum** | Contains(Point/Sphere/Bounds)、Intersects | +| **Bounds** | GetMinMax、Intersects、Contains、Encapsulate | + +--- + +## 8. 构建与运行 + +### 8.1 构建测试 + +```bash +# 创建构建目录 +mkdir build && cd build + +# 配置 CMake +cmake .. -G "Visual Studio 17 2022" -A x64 + +# 构建测试 +cmake --build . --config Debug --target xcengine_math_tests +``` + +### 8.2 运行测试 + +```bash +# 运行所有测试 +ctest --output-on-failure + +# 运行 Math 测试 +./tests/xcengine_math_tests.exe + +# 运行特定测试 +./tests/xcengine_math_tests.exe --gtest_filter=Math_Vector3.* + +# 运行测试并显示详细信息 +./tests/xcengine_math_tests.exe --gtest_also_run_disabled_tests --gtest_print_time=1 +``` + +### 8.3 测试过滤器 + +```bash +# 运行所有 Vector3 测试 +--gtest_filter=Math_Vector3.* + +# 运行除某测试外的所有测试 +--gtest_filter=-Math_Matrix4.SingularMatrix* + +# 运行多个测试 +--gtest_filter=Math_Vector3.*:Math_Matrix4.* +``` + +--- + +## 9. 覆盖率要求 + +| 模块 | 最低覆盖率 | 关键测试 | +|------|-----------|---------| +| Math | 90% | 所有公开 API | +| Core | 80% | 智能指针、Event | +| Containers | 85% | 边界、迭代器 | +| Memory | 90% | 分配/泄漏 | +| Threading | 70% | 基本功能 | + +--- + +## 10. 持续集成 + +```yaml +# .github/workflows/test.yml +name: Test + +on: [push, pull_request] + +jobs: + test: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure + run: cmake -B build -DENABLE_COVERAGE=ON + + - name: Build + run: cmake --build build --config Debug + + - name: Test + run: ctest --test-dir build --output-on-failure + + - name: Coverage + run: cmake --build build --target coverage +``` + +--- + +## 11. 注意事项 + +1. **浮点数比较** - 必须使用容差 (通常 1e-5 或 1e-6) +2. **边界条件** - 必须测试零向量、奇异矩阵等 +3. **随机性** - 如需固定 seed 保证确定性 +4. **线程安全** - 线程测试需设置超时 +5. **内存泄漏** - 使用 Valgrind 或 CRT 检测 diff --git a/docs/XCEngine渲染引擎架构设计.md b/docs/XCEngine渲染引擎架构设计.md new file mode 100644 index 00000000..faff46f4 --- /dev/null +++ b/docs/XCEngine渲染引擎架构设计.md @@ -0,0 +1,4564 @@ +# XCEngine - 渲染引擎架构设计文档 + +> **借鉴 Unity 渲染架构概念设计** + +--- + +## 渲染流程图 + +``` +┌─────────────────────────────────────────────────────────────────────────────────────┐ +│ 渲染流程 │ +└─────────────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Scene │────▶│ Culling │────▶│ RenderQueue │────▶│ Renderer │ +│ 场景数据 │ │ System │ │ 渲染队列 │ │ 渲染器 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + ▼ │ ▼ + ┌─────────────┐ │ ┌─────────────┐ + │CullingResults│◀──────────┘ │ CommandList │ + │ 剔除结果 │ PrepareRender │ 命令列表 │ + └─────────────┘ QueueEntries └─────────────┘ + │ + ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Camera │────▶│ LightManager│────▶│ Pass │────▶│ GPU │ +│ 相机 │ │ 光照管理 │ │ 渲染通道 │ │ GPU执行 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ ShadowAtlas │ │ Material │ + │ 阴影图集 │ │ 材质 │ + └─────────────┘ └─────────────┘ + +流程说明: +1. Scene 提供场景数据(GameObjects, Components) +2. CullingSystem 执行视锥剔除,产出 CullingResults +3. RenderQueue 根据 CullingResults 准备渲染项(Renderer::PrepareRenderQueueEntries) +4. Renderer 遍历 RenderQueue,通过 CommandList 提交绘制命令 +5. LightManager/ShadowAtlas 处理光照和阴影 +6. 最终 GPU 执行渲染命令 +``` + +``` +┌─────────────────────────────────────────────────────────────────────────────────────┐ +│ RHI 抽象层架构 │ +└─────────────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ RHISystem │ 渲染系统入口 + └────────┬────────┘ + │ + ┌───────────────────────┼───────────────────────┐ + │ │ │ + ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ + │ D3D12Device │ │ D3D11Device │ │ VulkanDevice │ + └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └───────────────────────┼───────────────────────┘ + │ + ┌────────▼────────┐ + │ IRHIDevice │ 抽象设备接口 + └────────┬────────┘ + │ + ┌──────────────────────────────────┼──────────────────────────────────┐ + │ │ │ │ │ │ │ + ┌────▼────┐ ┌───▼────┐ ┌────▼┐ ┌──▼────┐ ┌───▼───┐ ┌───▼────┐ ┌──▼────┐ + │CommandQueue│ │Descriptor│ │Fence│ │SwapChain│ │RootSig│ │PSO │ │Texture│ + │ 命令队列 │ │Heap │ │围栏 │ │交换链 │ │根签名 │ │管线状态│ │纹理 │ + └──────────┘ └────────┘ └─────┘ └────────┘ └───────┘ └───────┘ └───────┘ +``` + +> **重要声明**:本架构借鉴 Unity 渲染系统的核心概念与设计模式,包括: +> - `CullingResults` / `CullingSystem` - 剔除系统(概念来自Unity SRP API) +> - `RenderPipeline` / `RenderPipelineManager` - 渲染管线 +> - `RenderQueue` / `RenderQueueEntry` - 渲染队列 +> - `Renderer` - 渲染器核心 +> - `ScriptableRenderContext` - 渲染上下文 +> - `Shader` / `Pass` / `Material` - 着色器与材质 +> - `LightManager` / `ShadowAtlas` - 光照与阴影管理 +> +> **注**:Unity底层C++渲染架构未公开,本设计基于公开API概念与通用渲染引擎模式实现。 +> +> 版本: 1.0 +> 日期: 2026-03-13 +> 目标: 构建专业级实时体积渲染引擎 + +--- + +## 第一章 核心基础层 + +### 1.1 数学库 (Math Library) + +```cpp +namespace XCEngine { +namespace Math { + +struct Vector2 { + float x, y; + + static Vector2 Zero() { return Vector2{0, 0}; } + static Vector2 One() { return Vector2{1, 1}; } + static Vector2 Up() { return Vector2{0, 1}; } + static Vector2 Down() { return Vector2{0, -1}; } + static Vector2 Right() { return Vector2{1, 0}; } + static Vector2 Left() { return Vector2{-1, 0}; } + + static float Dot(const Vector2& a, const Vector2& b); + static float Cross(const Vector2& a, const Vector2& b); + static Vector2 Normalize(const Vector2& v); + static float Magnitude(const Vector2& v); + static Vector2 Lerp(const Vector2& a, const Vector2& b, float t); +}; + +struct Vector3 { + float x, y, z; + + static Vector3 Zero() { return Vector3{0, 0, 0}; } + static Vector3 One() { return Vector3{1, 1, 1}; } + static Vector3 Forward() { return Vector3{0, 0, 1}; } + static Vector3 Back() { return Vector3{0, 0, -1}; } + static Vector3 Up() { return Vector3{0, 1, 0}; } + static Vector3 Down() { return Vector3{0, -1, 0}; } + static Vector3 Right() { return Vector3{1, 0, 0}; } + static Vector3 Left() { return Vector3{-1, 0, 0}; } + + static Vector3 Cross(const Vector3& a, const Vector3& b); + static float Dot(const Vector3& a, const Vector3& b); + static Vector3 Normalize(const Vector3& v); + static float Magnitude(const Vector3& v); + static float SqrMagnitude(const Vector3& v); + static Vector3 Lerp(const Vector3& a, const Vector3& b, float t); + static Vector3 MoveTowards(const Vector3& current, const Vector3& target, float maxDistance); +}; + +struct Vector4 { float x, y, z, w; }; + +struct Matrix3x3 { + float m[3][3]; + + static Matrix3x3 Identity() { + Matrix3x3 result{}; + result.m[0][0] = 1.0f; result.m[1][1] = 1.0f; result.m[2][2] = 1.0f; + return result; + } + + static Matrix3x3 Zero() { + Matrix3x3 result{}; + return result; + } + + Matrix3x3 operator*(const Matrix3x3& other) const; + Vector3 operator*(const Vector3& v) const; + Matrix3x3 Transpose() const; + Matrix3x3 Inverse() const; + float Determinant() const; +}; + +struct Matrix4x4 { + float m[4][4]; + + static Matrix4x4 Identity() { + Matrix4x4 result{}; + result.m[0][0] = 1.0f; result.m[1][1] = 1.0f; + result.m[2][2] = 1.0f; result.m[3][3] = 1.0f; + return result; + } + + static Matrix4x4 Zero() { + Matrix4x4 result{}; + return result; + } + + static Matrix4x4 Translation(const Vector3& v); + static Matrix4x4 Rotation(const Quaternion& q); + static Matrix4x4 Scale(const Vector3& v); + static Matrix4x4 TRS(const Vector3& translation, const Quaternion& rotation, const Vector3& scale); + static Matrix4x4 LookAt(const Vector3& eye, const Vector3& target, const Vector3& up); + static Matrix4x4 Perspective(float fov, float aspect, float near, float far); + static Matrix4x4 Orthographic(float left, float right, float bottom, float top, float near, float far); + + Matrix4x4 operator*(const Matrix4x4& other) const; + Vector4 operator*(const Vector4& v) const; + Vector3 MultiplyPoint(const Vector3& v) const; + Vector3 MultiplyVector(const Vector3& v) const; + + Matrix4x4 Transpose() const; + Matrix4x4 Inverse() const; + float Determinant() const; + + Vector3 GetTranslation() const; + Quaternion GetRotation() const; + Vector3 GetScale() const; + + void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const; +}; + +struct Quaternion { + float x, y, z, w; + + static Quaternion Identity() { return Quaternion{0, 0, 0, 1}; } + + static Quaternion FromAxisAngle(const Vector3& axis, float radians); + static Quaternion FromEulerAngles(float pitch, float yaw, float roll); + static Quaternion FromRotationMatrix(const Matrix4x4& matrix); + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float t); + static Quaternion LookRotation(const Vector3& forward, const Vector3& up = Vector3::Up()); + + Vector3 ToEulerAngles() const; + Matrix4x4 ToMatrix4x4() const; + Vector3 operator*(const Vector3& v) const; + + Quaternion operator*(const Quaternion& other) const; + Quaternion Inverse() const; + float Dot(const Quaternion& other) const; +}; + +struct Transform { + Vector3 position = Vector3::Zero(); + Quaternion rotation = Quaternion::Identity(); + Vector3 scale = Vector3::One(); + + Matrix4x4 ToMatrix() const; + Transform Inverse() const; + Transform operator*(const Transform& other) const; + Vector3 TransformPoint(const Vector3& point) const; + Vector3 TransformDirection(const Vector3& direction) const; +}; + +enum class Space { + Self, + World +}; + +struct Color { + float r, g, b, a; + + static Color White() { return Color{1, 1, 1, 1}; } + static Color Black() { return Color{0, 0, 0, 1}; } + static Color Red() { return Color{1, 0, 0, 1}; } + static Color Green() { return Color{0, 1, 0, 1}; } + static Color Blue() { return Color{0, 0, 1, 1}; } + static Color Yellow() { return Color{1, 1, 0, 1}; } + static Color Cyan() { return Color{0, 1, 1, 1}; } + static Color Magenta() { return Color{1, 0, 1, 1}; } + static Color Clear() { return Color{0, 0, 0, 0}; } + + static Color Lerp(const Color& a, const Color& b, float t); +}; + +struct Rect { + float x, y, width, height; + + Rect() : x(0), y(0), width(0), height(0) {} + Rect(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} + + float GetLeft() const { return x; } + float GetRight() const { return x + width; } + float GetTop() const { return y; } + float GetBottom() const { return y + height; } + + Vector2 GetPosition() const { return Vector2{x, y}; } + Vector2 GetSize() const { return Vector2{width, height}; } + + bool Contains(float px, float py) const { + return px >= x && px < x + width && py >= y && py < y + height; + } +}; + +struct RectInt { + int32_t x, y, width, height; + + RectInt() : x(0), y(0), width(0), height(0) {} + RectInt(int32_t x, int32_t y, int32_t w, int32_t h) : x(x), y(y), width(w), height(h) {} +}; + +struct Viewport { + float x, y, width, height; + float minDepth = 0.0f; + float maxDepth = 1.0f; + + Viewport() : x(0), y(0), width(0), height(0) {} + Viewport(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} +}; + +struct Ray { + Vector3 origin; + Vector3 direction; + + Vector3 GetPoint(float t) const; + bool Intersects(const Sphere& sphere, float& t) const; + bool Intersects(const Box& box, float& t) const; + bool Intersects(const Plane& plane, float& t) const; +}; + +struct Sphere { Vector3 center; float radius; }; +struct Box { Vector3 center; Vector3 extents; Matrix4x4 transform; }; +struct Plane { Vector3 normal; float distance; }; +struct Frustum; +struct OBB; +struct AABB; + +} // namespace Math +} // namespace XCEngine +``` + +### 1.2 内存管理 (Memory Management) + +```cpp +namespace XCEngine { +namespace Memory { + +class IAllocator { +public: + virtual ~IAllocator() = default; + + virtual void* Allocate(size_t size, size_t alignment = 0) = 0; + virtual void Free(void* ptr) = 0; + virtual void* Reallocate(void* ptr, size_t newSize) = 0; + + virtual size_t GetTotalAllocated() const = 0; + virtual size_t GetTotalFreed() const = 0; + virtual size_t GetPeakAllocated() const = 0; + virtual size_t GetAllocationCount() const = 0; + + virtual const char* GetName() const = 0; +}; + +class LinearAllocator : public IAllocator { +public: + explicit LinearAllocator(size_t size, IAllocator* parent = nullptr); + ~LinearAllocator(); + + void* Allocate(size_t size, size_t alignment = 8) override; + void Free(void* ptr) override; + void Clear(); + + void* GetMarker() const; + void SetMarker(void* marker); + +private: + byte* m_buffer = nullptr; + size_t m_capacity = 0; + size_t m_offset = 0; + IAllocator* m_parent = nullptr; +}; + +class PoolAllocator : public IAllocator { +public: + PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment = 8); + ~PoolAllocator(); + + void* Allocate(size_t size, size_t alignment = 0) override; + void Free(void* ptr) override; + + bool Contains(void* ptr) const; + size_t GetBlockSize() const { return m_blockSize; } + size_t GetFreeBlockCount() const; + +private: + struct FreeNode { + FreeNode* next; + }; + + size_t m_blockSize = 0; + size_t m_alignment = 0; + void* m_memory = nullptr; + FreeNode* m_freeList = nullptr; + size_t m_totalBlocks = 0; + size_t m_freeBlocks = 0; +}; + +class ProxyAllocator : public IAllocator { +public: + ProxyAllocator(IAllocator* underlying, const char* name); + + void* Allocate(size_t size, size_t alignment = 0) override; + void Free(void* ptr) override; + void* Reallocate(void* ptr, size_t newSize) override; + + struct Stats { + size_t totalAllocated; + size_t totalFreed; + size_t peakAllocated; + size_t allocationCount; + size_t memoryOverhead; + }; + const Stats& GetStats() const; + +private: + IAllocator* m_underlying; + const char* m_name; + Stats m_stats; + Mutex m_mutex; +}; + +class MemoryManager { +public: + static MemoryManager& Get(); + + void Initialize(); + void Shutdown(); + + IAllocator* GetSystemAllocator(); + + std::unique_ptr CreateLinearAllocator(size_t size); + std::unique_ptr CreatePoolAllocator(size_t blockSize, size_t count); + std::unique_ptr CreateProxyAllocator(const char* name); + + void SetTrackAllocations(bool track); + void DumpMemoryLeaks(); + void GenerateMemoryReport(); +}; + +#define XE_ALLOC(allocator, size, ...) allocator->Allocate(size, ##__VA_ARGS__) +#define XE_FREE(allocator, ptr) allocator->Free(ptr) + +} // namespace Memory +} // namespace XCEngine +``` + +### 1.3 容器库 (Containers) + +```cpp +namespace XCEngine { +namespace Containers { + +template +class Array { +public: + using Iterator = T*; + using ConstIterator = const T*; + + Array() = default; + explicit Array(size_t capacity); + Array(size_t count, const T& value); + Array(std::initializer_list init); + ~Array(); + + Array(const Array& other); + Array(Array&& other) noexcept; + Array& operator=(const Array& other); + Array& operator=(Array&& other) noexcept; + + T& operator[](size_t index); + const T& operator[](size_t index) const; + + T* Data() { return m_data; } + const T* Data() const { return m_data; } + + size_t Size() const { return m_size; } + size_t Capacity() const { return m_capacity; } + + void Clear(); + void PushBack(const T& value); + void PushBack(T&& value); + template + T& EmplaceBack(Args&&... args); + void PopBack(); + +private: + T* m_data = nullptr; + size_t m_size = 0; + size_t m_capacity = 0; + IAllocator* m_allocator = nullptr; +}; + +class String { +public: + String(); + String(const char* str); + String(const char* str, size_t len); + ~String(); + + String& operator+=(const String& other); + String& operator+=(const char* str); + + String Substring(size_t pos, size_t len = npos) const; + String Trim() const; + String ToLower() const; + String ToUpper() const; + + size_t Find(const char* str, size_t pos = 0) const; + bool StartsWith(const String& prefix) const; + bool EndsWith(const String& suffix) const; + + const char* CStr() const { return m_data; } + size_t Length() const { return m_length; } + +private: + char* m_data = nullptr; + size_t m_length = 0; + size_t m_capacity = 0; +}; + +template +class HashMap { +public: + struct Pair { + Key first; + Value second; + }; + + HashMap() = default; + explicit HashMap(size_t bucketCount, IAllocator* allocator = nullptr); + + Value& operator[](const Key& key); + Value* Find(const Key& key); + const Value* Find(const Key& key) const; + bool Contains(const Key& key) const; + + bool Insert(const Key& key, const Value& value); + bool Erase(const Key& key); + void Clear(); + + size_t Size() const { return m_size; } + +private: + size_t GetBucketIndex(const Key& key) const; + void Resize(); + + struct Bucket { + Array pairs; + }; + Array m_buckets; + size_t m_bucketCount = 0; + size_t m_size = 0; + float m_loadFactor = 0.75f; + IAllocator* m_allocator = nullptr; +}; + +} // namespace Containers +} // namespace XCEngine +``` + +### 1.4 线程系统 (Threading) + +```cpp +namespace XCEngine { +namespace Threading { + +enum class TaskPriority : uint8_t { + Critical = 0, + High = 1, + Normal = 2, + Low = 3, + Idle = 4 +}; + +enum class TaskStatus : uint8_t { + Pending, + Scheduled, + Running, + Completed, + Failed, + Canceled +}; + +template +using Func = std::function; + +class ITask { +public: + virtual ~ITask() = default; + + virtual void Execute() = 0; + virtual void OnComplete() {} + virtual void OnCancel() {} + + TaskPriority GetPriority() const { return m_priority; } + TaskStatus GetStatus() const { return m_status; } + uint64 GetId() const { return m_id; } + +protected: + TaskPriority m_priority = TaskPriority::Normal; + TaskStatus m_status = TaskStatus::Pending; + uint64 m_id = 0; + std::atomic m_refCount{1}; +}; + +template +class LambdaTask : public ITask { +public: + explicit LambdaTask(Func&& func, TaskPriority priority = TaskPriority::Normal) + : m_func(std::move(func)), m_priority(priority) {} + + void Execute() override { + m_func(); + } + +private: + Func m_func; +}; + +class TaskGroup { +public: + using Callback = std::function; + + TaskGroup(); + ~TaskGroup(); + + uint64 AddTask(std::unique_ptr task); + uint64 AddTask(Func&& func, TaskPriority priority = TaskPriority::Normal); + + void AddDependency(uint64 taskId, uint64 dependsOn); + void Wait(); + bool WaitFor(std::chrono::milliseconds timeout); + + void SetCompleteCallback(Callback&& callback); + bool IsComplete() const; + float GetProgress() const; +}; + +class TaskSystem { +public: + static TaskSystem& Get(); + + void Initialize(const TaskSystemConfig& config); + void Shutdown(); + + uint64 Submit(std::unique_ptr task); + uint64 Submit(Func&& func, TaskPriority priority = TaskPriority::Normal); + + TaskGroup* CreateTaskGroup(); + void DestroyTaskGroup(TaskGroup* group); + + void Wait(uint64 taskId); + uint32 GetWorkerThreadCount() const; + + void Update(); + + template + void ParallelFor(int32 start, int32 end, Func&& func); + + void RunOnMainThread(Func&& func); +}; + +class Mutex { +public: + Mutex() = default; + ~Mutex() = default; + + void Lock(); + void Unlock(); + bool TryLock(); + +private: + std::mutex m_mutex; +}; + +class SpinLock { +public: + void Lock(); + void Unlock(); + bool TryLock(); + +private: + std::atomic_flag m_flag = ATOMIC_FLAG_INIT; +}; + +class ReadWriteLock { +public: + void ReadLock(); + void ReadUnlock(); + void WriteLock(); + void WriteUnlock(); + +private: + std::mutex m_mutex; + std::condition_variable m_readCondition; + std::condition_variable m_writeCondition; + int32_t m_readers = 0; + int32_t m_writersWaiting = 0; + bool m_writerActive = false; +}; + +class Thread { +public: + using Id = uint64_t; + + Thread(); + ~Thread(); + + template + void Start(Func&& func, const String& name = "Thread"); + void Join(); + void Detach(); + + Id GetId() const { return m_id; } + const String& GetName() const { return m_name; } + + static Id GetCurrentId(); + static void Sleep(uint32_t milliseconds); + static void Yield(); + +private: + Id m_id = 0; + String m_name; + std::thread m_thread; +}; + +} // namespace Threading +} // namespace XCEngine +``` + +### 1.5 日志与调试系统 + +```cpp +namespace XCEngine { +namespace Core { + +template +class Event { +public: + using Callback = std::function; + using Listener = std::pair; + using Iterator = typename std::vector::iterator; + + uint64_t Subscribe(Callback callback) { + std::lock_guard lock(m_mutex); + uint64_t id = ++m_nextId; + m_listeners.emplace_back(id, std::move(callback)); + return id; + } + + void Unsubscribe(uint64_t id) { + std::lock_guard lock(m_mutex); + m_pendingUnsubscribes.push_back(id); + } + + void ProcessUnsubscribes() { + std::lock_guard lock(m_mutex); + for (uint64_t id : m_pendingUnsubscribes) { + m_listeners.erase( + std::remove_if(m_listeners.begin(), m_listeners.end(), + [id](const auto& pair) { return pair.first == id; }), + m_listeners.end() + ); + } + m_pendingUnsubscribes.clear(); + } + + void Invoke(Args... args) const { + std::vector listenersCopy; + { + std::lock_guard lock(m_mutex); + if (!m_pendingUnsubscribes.empty()) { + for (uint64_t id : m_pendingUnsubscribes) { + m_listeners.erase( + std::remove_if(m_listeners.begin(), m_listeners.end(), + [id](const auto& pair) { return pair.first == id; }), + m_listeners.end() + ); + } + m_pendingUnsubscribes.clear(); + } + listenersCopy = m_listeners; + } + for (const auto& [id, callback] : listenersCopy) { + callback(args...); + } + } + + void Clear() { + std::lock_guard lock(m_mutex); + m_listeners.clear(); + } + + Iterator begin() { return m_listeners.begin(); } + Iterator end() { return m_listeners.end(); } + +private: + mutable std::mutex m_mutex; + std::vector m_listeners; + std::vector m_pendingUnsubscribes; + uint64_t m_nextId = 0; +}; + +using int8 = int8_t; +using int16 = int16_t; +using int32 = int32_t; +using int64 = int64_t; +using uint8 = uint8_t; +using uint16 = uint16_t; +using uint32 = uint32_t; +using uint64 = uint64_t; +using byte = uint8_t; + +class RefCounted { +public: + RefCounted() : m_refCount(1) {} + virtual ~RefCounted() = default; + + void AddRef() { ++m_refCount; } + void Release() { + if (--m_refCount == 0) { + delete this; + } + } + + uint32_t GetRefCount() const { return m_refCount.load(); } + +protected: + std::atomic m_refCount; +}; + +template +using Ref = std::shared_ptr; + +template +using UniqueRef = std::unique_ptr; + +} // namespace Core +} // namespace XCEngine + +namespace XCEngine { +namespace Debug { + +enum class LogLevel : uint8_t { + Verbose = 0, + Debug = 1, + Info = 2, + Warning = 3, + Error = 4, + Fatal = 5 +}; + +enum class LogCategory { + General, + Rendering, + Physics, + Audio, + Scripting, + Network, + Memory, + Threading, + FileSystem, + Custom +}; + +struct LogEntry { + LogLevel level; + LogCategory category; + String message; + String file; + int32 line; + String function; + uint64 timestamp; + uint32 threadId; +}; + +class ILogSink { +public: + virtual ~ILogSink() = default; + virtual void Log(const LogEntry& entry) = 0; + virtual void Flush() = 0; +}; + +class ConsoleLogSink : public ILogSink { +public: + void Log(const LogEntry& entry) override; + void Flush() override; + void SetColorOutput(bool enable); + void SetMinimumLevel(LogLevel level); +}; + +class FileLogSink : public ILogSink { +public: + explicit FileLogSink(const String& filePath); + ~FileLogSink(); + void Log(const LogEntry& entry) override; + void Flush() override; +private: + String m_filePath; + FileWriter m_writer; +}; + +class Logger { +public: + static Logger& Get(); + + void Initialize(); + void Shutdown(); + + void AddSink(std::unique_ptr sink); + void RemoveSink(ILogSink* sink); + + void Log(LogLevel level, LogCategory category, + const String& message, const char* file = nullptr, + int32 line = 0, const char* function = nullptr); + + void Verbose(LogCategory category, const String& message); + void Debug(LogCategory category, const String& message); + void Info(LogCategory category, const String& message); + void Warning(LogCategory category, const String& message); + void Error(LogCategory category, const String& message); + void Fatal(LogCategory category, const String& message); + + void SetMinimumLevel(LogLevel level); + void SetCategoryEnabled(LogCategory category, bool enabled); +}; + +#define XE_LOG(category, level, message) \ + XCEngine::Debug::Logger::Get().Log(level, category, message, __FILE__, __LINE__, __FUNCTION__) + +#define XE_ASSERT(condition, message) \ + if (!(condition)) { \ + XCEngine::Debug::Logger::Get().Fatal(XCEngine::Debug::LogCategory::General, message); \ + __debugbreak(); \ + } + +class Profiler { +public: + static Profiler& Get(); + + void Initialize(); + void Shutdown(); + + void BeginProfile(const char* name); + void EndProfile(); + + void BeginFrame(); + void EndFrame(); + + void MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId); + void SetMarker(const char* name, uint32_t color); + + void ExportChromeTracing(const String& filePath); +}; + +#define XE_PROFILE_BEGIN(name) XCEngine::Debug::Profiler::Get().BeginProfile(name) +#define XE_PROFILE_END() XCEngine::Debug::Profiler::Get().EndProfile() +#define XE_PROFILE_FUNCTION() XE_PROFILE_BEGIN(__FUNCTION__) + +} // namespace Debug +} // namespace XCEngine +``` + +--- + +## 第二章 组件系统 + +### 2.1 组件基类 + +```cpp +namespace XCEngine { + +class Scene; +class GameObject; +class TransformComponent; + +// 组件类型注册(用于运行时类型识别) +class ComponentTypeRegistry { +public: + static ComponentTypeRegistry& Get(); + + template + static uint32_t GetTypeId() { + static uint32_t id = Get().Register(typeid(T).name(), static_cast(-1)); + return id; + } + + template + static uint32_t GetTypeId(const char* typeName) { + static uint32_t id = Get().Register(typeName, static_cast(-1)); + return id; + } + + static uint32_t GetTypeIdFromName(const char* typeName) { + return Get().GetIdByName(typeName); + } + + static const char* GetTypeName(uint32_t typeId) { + return Get().GetNameById(typeId); + } + +private: + uint32_t Register(const char* typeName, uint32_t suggestedId); + uint32_t GetIdByName(const char* typeName) const; + const char* GetNameById(uint32_t typeId) const; + + std::atomic m_nextTypeId{0}; + std::unordered_map m_idToName; + std::unordered_map m_nameToId; + Mutex m_mutex; +}; + +// 组件基类(类Unity MonoBehaviour) +class Component { +public: + Component(); + virtual ~Component(); + + GameObject* gameObject() const { return m_gameObject; } + TransformComponent& transform() const { return m_gameObject->GetTransform(); } + + bool IsEnabled() const { return m_enabled; } + void SetEnabled(bool enabled) { m_enabled = enabled; } + + Scene* GetScene() const; + + template + T* GetComponent() const { return m_gameObject->GetComponent(); } + + template + std::vector GetComponents() const { return m_gameObject->GetComponents(); } + + virtual void Awake() {} + virtual void Start() {} + virtual void Update(float deltaTime) {} + virtual void FixedUpdate() {} + virtual void LateUpdate(float deltaTime) {} + virtual void OnDestroy() {} + virtual void OnEnable() {} + virtual void OnDisable() {} + +protected: + GameObject* m_gameObject = nullptr; + bool m_enabled = true; + + friend class GameObject; +}; + +} // namespace XCEngine +``` + +### 2.2 Transform组件 + +```cpp +namespace XCEngine { + +class TransformComponent : public Component { +public: + Vector3 GetLocalPosition() const { return m_localPosition; } + void SetLocalPosition(const Vector3& position) { m_localPosition = position; SetDirty(); } + + Quaternion GetLocalRotation() const { return m_localRotation; } + void SetLocalRotation(const Quaternion& rotation) { m_localRotation = rotation; SetDirty(); } + + Vector3 GetLocalScale() const { return m_localScale; } + void SetLocalScale(const Vector3& scale) { m_localScale = scale; SetDirty(); } + + Vector3 GetPosition() const; + void SetPosition(const Vector3& position); + + Quaternion GetRotation() const; + void SetRotation(const Quaternion& rotation); + + Vector3 GetScale() const; + void SetScale(const Vector3& scale); + + Vector3 GetForward() const { return GetRotation() * Vector3::Forward(); } + Vector3 GetRight() const { return GetRotation() * Vector3::Right(); } + Vector3 GetUp() const { return GetRotation() * Vector3::Up(); } + + const Matrix4x4& GetLocalToWorldMatrix() const; + Matrix4x4 GetWorldToLocalMatrix() const; + + TransformComponent* GetParent() const { return m_parent; } + void SetParent(TransformComponent* parent, bool worldPositionStays = true); + + int GetChildCount() const { return static_cast(m_children.size()); } + TransformComponent* GetChild(int index) const; + TransformComponent* Find(const String& name) const; + + void DetachChildren(); + void SetAsFirstSibling(); + void SetAsLastSibling(); + void SetSiblingIndex(int index); + int GetSiblingIndex() const; + + void LookAt(const Vector3& target); + void LookAt(const Vector3& target, const Vector3& up); + void Rotate(const Vector3& eulers); + void Rotate(const Vector3& axis, float angle); + void Translate(const Vector3& translation); + void Translate(const Vector3& translation, Math::Space relativeTo); + + Vector3 TransformPoint(const Vector3& point) const; + Vector3 InverseTransformPoint(const Vector3& point) const; + Vector3 TransformDirection(const Vector3& direction) const; + Vector3 InverseTransformDirection(const Vector3& direction) const; + + void SetDirty() { m_dirty = true; } + +private: + Vector3 m_localPosition = Vector3::Zero(); + Quaternion m_localRotation = Quaternion::Identity(); + Vector3 m_localScale = Vector3::One(); + + TransformComponent* m_parent = nullptr; + std::vector m_children; + + mutable Matrix4x4 m_localToWorldMatrix; + mutable Matrix4x4 m_worldToLocalMatrix; + mutable Vector3 m_worldPosition; + mutable Quaternion m_worldRotation; + mutable Vector3 m_worldScale; + mutable bool m_dirty = true; + + void UpdateWorldTransform() const; + + friend class GameObject; +}; + +} // namespace XCEngine +``` + +### 2.3 GameObject + +```cpp +namespace XCEngine { + +class Scene; + +class GameObject { +public: + GameObject(); + ~GameObject(); + + struct ConstructParams { + String name = "GameObject"; + GameObject* parent = nullptr; + bool active = true; + }; + + static GameObject* Create(const ConstructParams& params = {}); + static void Destroy(GameObject* obj); + + String name; + bool active = true; + + TransformComponent& GetTransform() { return *m_transform; } + const TransformComponent& GetTransform() const { return *m_transform; } + + Scene* GetScene() const { return m_scene; } + + template + T* AddComponent(); + + template + void RemoveComponent(); + + template + T* GetComponent() const; + + template + std::vector GetComponents() const; + + template + T* GetComponentInChildren() const; + + template + std::vector GetComponentsInChildren() const; + + template + T* GetComponentInParent() const; + + GameObject* GetParent() const; + void SetParent(GameObject* parent); + void SetParent(GameObject* parent, bool worldPositionStays); + const std::vector& GetChildren() const; + GameObject* GetChild(int index) const; + int GetChildCount() const; + int GetSiblingIndex() const; + void SetSiblingIndex(int index); + + void SetActive(bool active); + bool IsActive() const; + bool IsActiveInHierarchy() const; + + static GameObject* Find(const String& name); + static std::vector FindObjectsOfType(); + static std::vector FindGameObjectsWithTag(const String& tag); + + void Destroy(); + +private: + void AddComponentInternal(Component* component); + void RemoveComponentInternal(Component* component); + + std::vector> m_components; + std::unordered_map m_componentTypeIndex; + + std::vector m_children; + GameObject* m_parent = nullptr; + Scene* m_scene = nullptr; + + std::unique_ptr m_transform; + + void Initialize(const ConstructParams& params); + + friend class Scene; + friend class TransformComponent; +}; + +inline GameObject::GameObject() { +} + +inline GameObject::~GameObject() { + for (auto* child : m_children) { + delete child; + } +} + +inline GameObject* GameObject::Create(const ConstructParams& params) { + GameObject* obj = new GameObject(); + obj->Initialize(params); + return obj; +} + +inline void GameObject::Destroy(GameObject* obj) { + delete obj; +} + +inline void GameObject::Initialize(const ConstructParams& params) { + name = params.name; + active = params.active; + + m_transform = std::make_unique(); + m_transform->m_gameObject = this; + + if (params.parent) { + params.parent->m_children.push_back(this); + m_parent = params.parent; + } +} + +inline void GameObject::Destroy() { + if (m_scene) { + m_scene->DestroyGameObject(this); + } else { + delete this; + } +} + +inline GameObject* GameObject::Find(const String& name) { + return SceneManager::Get().GetActiveScene()->Find(name); +} + +inline std::vector GameObject::FindObjectsOfType() { + return SceneManager::Get().GetActiveScene()->FindObjectsOfType(); +} + +inline std::vector GameObject::FindGameObjectsWithTag(const String& tag) { + return SceneManager::Get().GetActiveScene()->FindGameObjectsWithTag(tag); +} + +} // namespace XCEngine +``` + +--- + +## 第三章 场景系统 + +### 3.1 Scene + +```cpp +namespace XCEngine { + +class Scene { +public: + Scene(); + ~Scene(); + + const String& GetName() const { return m_name; } + void SetName(const String& name) { m_name = name; } + + GameObject* CreateGameObject(const String& name = "GameObject") { + GameObject::ConstructParams params; + params.name = name; + params.parent = nullptr; + params.active = true; + + GameObject* obj = GameObject::Create(params); + obj->m_scene = this; + AddGameObject(obj); + return obj; + } + + GameObject* CreateGameObject(const String& name, GameObject* parent) { + GameObject::ConstructParams params; + params.name = name; + params.parent = parent; + params.active = true; + + GameObject* obj = GameObject::Create(params); + obj->m_scene = this; + AddGameObject(obj); + return obj; + } + + void DestroyGameObject(GameObject* obj); + + std::vector GetRootGameObjects() const { + std::vector roots; + roots.reserve(m_gameObjects.size() / 2); + for (auto& obj : m_gameObjects) { + if (obj->GetParent() == nullptr) { + roots.push_back(obj.get()); + } + } + return roots; + } + + GameObject* Find(const String& name) const; + GameObject* FindGameObjectWithTag(const String& tag) const; + std::vector FindGameObjectsWithTag(const String& tag) const; + template + std::vector FindObjectsOfType() const; + template + T* FindObjectOfType() const; + + bool IsActive() const { return m_isActive; } + void SetActive(bool active); + + void Load(const String& filePath); + void LoadAsync(const String& filePath, std::function callback); + void Save(const String& filePath); + + void Update(float deltaTime); + void FixedUpdate(float fixedDeltaTime); + void LateUpdate(float deltaTime); + + void DebugDraw(); + int GetObjectCount() const { return m_gameObjects.size(); } + +private: + void AddGameObject(GameObject* obj); + void RemoveGameObject(GameObject* obj); + + String m_name; + std::vector> m_gameObjects; + bool m_isActive = true; + + friend class GameObject; + friend class SceneManager; +}; + +class SceneManager { +public: + static SceneManager& Get(); + + void Initialize(); + void Shutdown(); + + Scene* CreateScene(const String& name); + void LoadScene(const String& filePath); + void LoadSceneAsync(const String& filePath, std::function callback); + void UnloadScene(Scene* scene); + void UnloadScene(const String& sceneName); + + void SetActiveScene(Scene* scene); + void SetActiveScene(const String& sceneName); + Scene* GetActiveScene() const; + + Scene* GetScene(const String& name) const; + std::vector GetAllScenes() const; + + void Update(float deltaTime); + void FixedUpdate(float fixedDeltaTime); + void LateUpdate(float deltaTime); + + Event OnSceneLoaded; + Event OnSceneUnloaded; + Event OnActiveSceneChanged; + +private: + SceneManager() = default; + + std::vector> m_scenes; + Scene* m_activeScene = nullptr; + Scene* m_loadingScene = nullptr; + + std::unordered_map m_sceneNameMap; + std::function m_loadCallback; + bool m_loading = false; +}; + +// GameObject创建辅助类 +class GameObjectBuilder { +public: + explicit GameObjectBuilder(const String& name = "GameObject"); + ~GameObjectBuilder() = default; + + template + GameObjectBuilder& AddComponent(Args&&... args); + + GameObject* Build(); + +private: + String m_name; + std::vector> m_components; +}; + +} // namespace XCEngine +``` + +--- + +## 第四章 渲染系统 + +> **借鉴 Unity 渲染架构设计** + +### 4.0 公共类型定义 + +```cpp +namespace XCEngine { +namespace Rendering { + +// ============================================ +// 前置类型定义 - 资源基类 +// ============================================ + +enum class ResourceLoadState { + Undefined, + Unloaded, + Loading, + Loaded, + Unloading, + Failed +}; + +enum class ResourceType { + Mesh, + Texture, + Shader, + Material, + ComputeBuffer, + GraphicsBuffer, + Sampler, + RenderTarget, + DepthStencil, + VertexBuffer, + IndexBuffer, + Unknown +}; + +class IResource { +public: + virtual ~IResource() = default; + + virtual void SetName(const std::string& name) { m_name = name; } + virtual const std::string& GetName() const { return m_name; } + + virtual ResourceType GetType() const = 0; + virtual ResourceLoadState GetLoadState() const { return m_loadState; } + + virtual bool IsValid() const { return m_loadState == ResourceLoadState::Loaded; } + virtual void Load() = 0; + virtual void Unload() = 0; + + virtual int GetRefCount() const { return m_refCount.load(); } + virtual void AddRef() { m_refCount.fetch_add(1); } + virtual void Release() { + if (m_refCount.fetch_sub(1) == 1) { + delete this; + } + } + +protected: + IResource() : m_loadState(ResourceLoadState::Unloaded), m_memoryUsage(0) {} + + std::string m_name; + ResourceLoadState m_loadState; + uint64_t m_memoryUsage; + std::atomic m_refCount{1}; +}; + +// ============================================ +// 缓冲区类型定义 +// ============================================ + +class VertexBuffer : public IResource { +public: + ResourceType GetType() const override { return ResourceType::VertexBuffer; } + + void Load() override {} + void Unload() override {} + + uint32_t GetVertexCount() const { return m_vertexCount; } + uint32_t GetStride() const { return m_stride; } + uint64_t GetSize() const { return m_size; } + + void SetData(const void* data, uint64_t size); + void* Map(); + void Unmap(); + +private: + uint32_t m_vertexCount = 0; + uint32_t m_stride = 0; + uint64_t m_size = 0; + void* m_nativeHandle = nullptr; +}; + +class IndexBuffer : public IResource { +public: + ResourceType GetType() const override { return ResourceType::IndexBuffer; } + + void Load() override {} + void Unload() override {} + + uint32_t GetIndexCount() const { return m_indexCount; } + bool Is32Bit() const { return m_is32Bit; } + uint64_t GetSize() const { return m_size; } + + void SetData(const void* data, uint64_t size); + void* Map(); + void Unmap(); + +private: + uint32_t m_indexCount = 0; + bool m_is32Bit = false; + uint64_t m_size = 0; + void* m_nativeHandle = nullptr; +}; + +// ============================================ +// 渲染目标类型定义 +// ============================================ + +struct RenderTargetDesc { + uint32_t width = 0; + uint32_t height = 0; + Format format = Format::R8G8B8A8_UNorm; + SampleCount sampleCount = SampleCount::Count1; + uint32_t mipLevels = 1; + uint32_t arraySize = 1; + bool enableUAV = false; + String name; +}; + +class RenderTarget : public IResource { +public: + ResourceType GetType() const override { return ResourceType::RenderTarget; } + + void Load() override {} + void Unload() override {} + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + Format GetFormat() const { return m_format; } + + void* GetNativeHandle() const { return m_nativeHandle; } + + IShaderResourceView* GetSRV() { return m_srv.get(); } + IRenderTargetView* GetRTV() { return m_rtv.get(); } + +private: + uint32_t m_width = 0; + uint32_t m_height = 0; + Format m_format; + void* m_nativeHandle = nullptr; + std::unique_ptr m_srv; + std::unique_ptr m_rtv; +}; + +struct DepthStencilDesc { + uint32_t width = 0; + uint32_t height = 0; + Format format = Format::D24_UNorm_S8_UInt; + SampleCount sampleCount = SampleCount::Count1; + bool bindAsShaderResource = false; + String name; +}; + +class DepthStencil : public IResource { +public: + ResourceType GetType() const override { return ResourceType::DepthStencil; } + + void Load() override {} + void Unload() override {} + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + Format GetFormat() const { return m_format; } + + void* GetNativeHandle() const { return m_nativeHandle; } + + IDepthStencilView* GetDSV() { return m_dsv.get(); } + IShaderResourceView* GetSRV() { return m_srv.get(); } + +private: + uint32_t m_width = 0; + uint32_t m_height = 0; + Format m_format; + void* m_nativeHandle = nullptr; + std::unique_ptr m_dsv; + std::unique_ptr m_srv; +}; + +// ============================================ +// 渲染通道描述 +// ============================================ + +struct RenderPassAttachmentDesc { + RenderTarget* renderTarget = nullptr; + DepthStencil* depthStencil = nullptr; + LoadAction loadAction = LoadAction::Clear; + StoreAction storeAction = StoreAction::Store; + Color clearColor = Color::Black(); + float clearDepth = 1.0f; + uint8_t clearStencil = 0; +}; + +struct RenderPassDesc { + RenderPassAttachmentDesc* colorAttachments = nullptr; + uint32_t colorAttachmentCount = 0; + RenderPassAttachmentDesc* depthStencilAttachment = nullptr; + Rect viewport; + Rect scissor; +}; + +// ============================================ +// 基础枚举类型 +// ============================================ + +enum class Format { + Unknown = 0, + R8_UNorm = 61, + R8G8_UNorm = 49, + R8G8B8A8_UNorm = 28, + R16G16B16A16_Float = 10, + R32G32B32A32_Float = 2, + R16_Float = 54, + R32_Float = 41, + D16_UNorm = 55, + D24_UNorm_S8_UInt = 45, + D32_Float = 40, + BC1_UNorm = 71, + BC2_UNorm = 74, + BC3_UNorm = 77, + BC4_UNorm = 80, + BC5_UNorm = 83, + BC6H_UF16 = 95, + BC7_UNorm = 98 +}; + +enum class SampleCount : uint32_t { + Count1 = 1, + Count2 = 2, + Count4 = 4, + Count8 = 8 +}; + +enum class CubemapFace : uint8_t { + PositiveX = 0, + NegativeX = 1, + PositiveY = 2, + NegativeY = 3, + PositiveZ = 4, + NegativeZ = 5 +}; + +struct Frustum { + Plane planes[6]; + + bool Contains(const Vector3& point) const; + bool Contains(const Sphere& sphere) const; + bool Contains(const Bounds& bounds) const; + bool Intersects(const Bounds& bounds) const; +}; + +enum class FilterMode { + Point = 0, + Bilinear = 1, + Trilinear = 2 +}; + +enum class TextureWrapMode { + Repeat = 0, + Clamp = 1, + Mirror = 2, + MirrorOnce = 3 +}; + +enum class MeshTopology { + Points, + Lines, + LineStrip, + Triangles, + TriangleStrip, + Quads +}; + +struct Bounds { + Vector3 center; + Vector3 extents; + Vector3 min; + Vector3 max; + + Bounds() : center(Vector3::Zero()), extents(Vector3::Zero()) {} + Bounds(const Vector3& center, const Vector3& size) + : center(center), extents(size * 0.5f) { + min = center - extents; + max = center + extents; + } + + void SetMinMax(const Vector3& min, const Vector3& max) { + this->min = min; + this->max = max; + center = (min + max) * 0.5f; + extents = (max - min) * 0.5f; + } + + bool Intersects(const Bounds& other) const; + bool Contains(const Vector3& point) const; + + void Encapsulate(const Vector3& point); + void Encapsulate(const Bounds& bounds); + + void Expand(float amount); + void Expand(const Vector3& amount); + + Vector3 GetClosestPoint(const Vector3& point) const; +}; + +struct QualityLevel { + enum { + Fastest = 0, + Fast = 1, + Simple = 2, + Good = 3, + High = 4, + Ultra = 5 + }; +}; + +class StringView { +public: + StringView() : m_data(nullptr), m_length(0) {} + StringView(const char* str); + StringView(const char* str, size_t len); + StringView(const String& str); + + const char* Data() const { return m_data; } + size_t Length() const { return m_length; } + + bool Empty() const { return m_length == 0; } + + char operator[](size_t index) const { return m_data[index]; } + + int Compare(const StringView& other) const; + bool operator==(const StringView& other) const; + bool operator!=(const StringView& other) const; + +private: + const char* m_data; + size_t m_length; +}; + +class Texture { +public: + virtual ~Texture() = default; + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + uint32_t GetDepth() const { return m_depth; } + uint32_t GetMipLevels() const { return m_mipLevels; } + Format GetFormat() const { return m_format; } + + FilterMode GetFilterMode() const { return m_filterMode; } + void SetFilterMode(FilterMode mode) { m_filterMode = mode; } + + TextureWrapMode GetWrapMode() const { return m_wrapMode; } + void SetWrapMode(TextureWrapMode mode) { m_wrapMode = mode; } + + void* GetNativeHandle() const { return m_nativeHandle; } + +protected: + uint32_t m_width = 0; + uint32_t m_height = 0; + uint32_t m_depth = 1; + uint32_t m_mipLevels = 1; + Format m_format = Format::R8G8B8A8_UNorm; + FilterMode m_filterMode = FilterMode::Bilinear; + TextureWrapMode m_wrapMode = TextureWrapMode::Clamp; + void* m_nativeHandle = nullptr; +}; + +class RenderTargetIdentifier { +public: + RenderTargetIdentifier(); + RenderTargetIdentifier(int nameID); + RenderTargetIdentifier(const String& name); + RenderTargetIdentifier(Texture* texture); + + bool IsValid() const { return m_type != Buffer || m_nameID != -1; } + bool operator==(const RenderTargetIdentifier& other) const; + bool operator!=(const RenderTargetIdentifier& other) const { return !(*this == other); } + + int GetType() const { return m_type; } + int GetNameID() const { return m_nameID; } + Texture* GetTexture() const { return m_texture; } + +private: + int m_type = Buffer; + int m_nameID = -1; + Texture* m_texture = nullptr; + + enum { Buffer, Texture2D, RenderTarget }; +}; + +class CommandBuffer { +public: + CommandBuffer(); + ~CommandBuffer(); + + void Clear(); + void BeginSample(const char* name); + void EndSample(const char* name); + void BeginDebugGroup(const char* name); + void EndDebugGroup(); + + void SetRenderTarget( + RenderTargetIdentifier colorTarget, + RenderTargetIdentifier depthTarget, + int mipLevel = 0, + CubemapFace cubemapFace = CubemapFace::PositiveX, + bool depthIsWritable = true + ); + + void SetRenderTarget( + const std::vector& colorTargets, + RenderTargetIdentifier depthTarget, + int mipLevel = 0, + CubemapFace cubemapFace = CubemapFace::PositiveX, + bool depthIsWritable = true + ); + + void SetViewport(const Rect& rect); + void SetScissor(const Rect& rect); + + void SetViewMatrix(const Matrix4x4& matrix); + void SetProjectionMatrix(const Matrix4x4& matrix); + + void DrawMeshInstanced( + const Mesh& mesh, + int submeshIndex, + Material* material, + int shaderPass, + const std::vector& matrices + ); + + void DrawMeshInstancedIndirect( + const Mesh& mesh, + int submeshIndex, + Material* material, + int shaderPass, + const ComputeBuffer& argsBuffer, + int argsOffset + ); + + void DrawProcedural( + Material* material, + int shaderPass, + MeshTopology topology, + int vertexCount, + int instanceCount + ); + + void Blit( + Material* material, + int pass, + RenderTargetIdentifier source, + RenderTargetIdentifier dest + ); + + void CopyTexture( + RenderTargetIdentifier source, + RenderTargetIdentifier dest + ); + + void GetTemporaryRT( + const String& name, + int width, + int height, + Format format = Format::R8G8B8A8_UNorm, + FilterMode filter = FilterMode::Bilinear, + TextureWrapMode wrap = TextureWrapMode::Clamp, + int depthBufferBits = 0 + ); + + void ReleaseTemporaryRT(const String& name); + + void GetCommandBufferPtr(void** ptr); + +private: + std::vector m_data; + std::vector m_sampleNames; + int m_sampleDepth = 0; +}; + +class Mesh : public IResource { +public: + Mesh(); + ~Mesh(); + + void SetVertices(const std::vector& vertices); + void SetNormals(const std::vector& normals); + void SetTangents(const std::vector& tangents); + void SetUVs(int channel, const std::vector& uvs); + void SetUVs(int channel, const std::vector& uvs); + void SetTriangles(const std::vector& triangles, int submesh); + void SetIndices(const std::vector& indices, int submesh); + void SetIndices(const std::vector& indices, int submesh); + + const std::vector& GetVertices() const { return m_vertices; } + const std::vector& GetNormals() const { return m_normals; } + const std::vector& GetTangents() const { return m_tangents; } + + int GetSubmeshCount() const { return m_submeshes.size(); } + const struct SubmeshDescriptor& GetSubmesh(int index) const; + + int GetVertexCount() const { return static_cast(m_vertices.size()); } + int GetIndexCount(int submesh) const; + + MeshTopology GetTopology(int submesh) const; + void SetTopology(MeshTopology topology, int submesh); + + void RecalculateNormals(); + void RecalculateTangents(); + void RecalculateBounds(); + + void Optimize(); + + bool IsUse32BitIndexBuffer() const { return m_use32BitIndex; } + + struct MeshData { + std::vector vertices; + std::vector normals; + std::vector tangents; + std::vector uvs[8]; + std::vector indices32; + std::vector indices16; + }; + void GetMeshData(MeshData& data) const; + +private: + std::vector m_vertices; + std::vector m_normals; + std::vector m_tangents; + std::vector m_uvs[8]; + + struct SubmeshDescriptor { + int indexStart; + int indexCount; + int baseVertex; + MeshTopology topology; + }; + std::vector m_submeshes; + + Bounds m_bounds; + bool m_use32BitIndex = false; + bool m_hasVertices = false; + bool m_hasNormals = false; + bool m_hasTangents = false; + + friend class MeshCollider; + friend class SkinnedMeshRenderer; +}; + +class ComputeBuffer : public IResource { +public: + enum class Type { + Default, + Raw, + Append, + Counter, + Structured, + ByteAddress + }; + + ComputeBuffer(Type type, int count, int stride); + ~ComputeBuffer(); + + int GetCount() const { return m_count; } + int GetStride() const { return m_stride; } + Type GetType() const { return m_type; } + + void SetData(const void* data); + void GetData(void* data) const; + + void SetCounterValue(uint32_t value); + uint32_t GetCounterValue() const; + +private: + Type m_type; + int m_count; + int m_stride; + void* m_buffer; +}; + +class GraphicsBuffer : public IResource { +public: + enum class Target { + Constant, + IndexBuffer, + VertexBuffer, + Structured, + Append, + Counter, + ReadWrite + }; + + GraphicsBuffer(Target target, uint32_t count, uint32_t stride); + virtual ~GraphicsBuffer(); + + Target GetTarget() const { return m_target; } + uint32_t GetCount() const { return m_count; } + uint32_t GetStride() const { return m_stride; } + uint64_t GetSize() const { return m_size; } + + void SetData(const void* data); + void GetData(void* data) const; + + void* Map(); + void Unmap(); + + void SetCounterValue(uint32_t value); + uint32_t GetCounterValue() const; + +private: + Target m_target; + uint32_t m_count = 0; + uint32_t m_stride = 0; + uint64_t m_size = 0; + void* m_buffer; +}; + +struct ShaderProgramDesc { + enum class Stage { + Vertex, + Fragment, + Compute, + Geometry, + Hull, + Domain + }; + + Stage stage; + String source; + String entryPoint; + + bool debug = false; + bool optimize = true; +}; + +} // namespace Rendering +} // namespace XCEngine +``` + +### 4.1 RHI 抽象层接口 + +```cpp +namespace XCEngine { +namespace RHI { + +// ============================================ +// 跨平台枚举类型 - 不暴露平台特定类型 +// ============================================ + +enum class GraphicsAPI { + Unknown, + Direct3D11, + Direct3D12, + Vulkan, + Metal, + OpenGL +}; + +enum class CommandListType { + Direct, + Compute, + Copy, + Bundle +}; + +enum class ShaderVisibility { + All = 0, + Vertex = 1, + Hull = 2, + Domain = 3, + Geometry = 4, + Pixel = 5, + Amplification = 6, + Mesh = 7 +}; + +enum class ResourceStateFlag : uint32_t { + Common = 0, + VertexBuffer = 1 << 0, + ConstantBuffer = 1 << 1, + IndexBuffer = 1 << 2, + RenderTarget = 1 << 3, + UnorderedAccess = 1 << 4, + DepthWrite = 1 << 5, + DepthRead = 1 << 6, + ShaderResource = 1 << 7, + IndirectArgument = 1 << 8, + CopyDest = 1 << 9, + CopySource = 1 << 10, + ResolveDest = 1 << 11, + ResolveSource = 1 << 12, + Present = 1 << 13, + RaytracingAccelerationStructure = 1 << 14, + ShadingRateSource = 1 << 15 +}; + +inline ResourceStateFlag operator|(ResourceStateFlag a, ResourceStateFlag b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline ResourceStateFlag operator&(ResourceStateFlag a, ResourceStateFlag b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +enum class DescriptorHeapType { + CBV_SRV_UAV, + Sampler, + RTV, + DSV +}; + +enum class QueryType { + Occlusion, + Timestamp, + PipelineStatistics +}; + +// ============================================ +// 描述结构体 +// ============================================ + +struct CommandQueueDesc { + CommandListType type = CommandListType::Direct; + int priority = 0; + const char* name = nullptr; +}; + +struct DescriptorHeapDesc { + DescriptorHeapType type; + uint32_t count; + bool shaderVisible; + const char* name; +}; + +struct QueryHeapDesc { + QueryType type; + uint32_t count; + const char* name; +}; + +struct SwapChainDesc { + uint32_t width; + uint32_t height; + Format format; + uint32_t bufferCount; + bool vsync; + bool fullscreen; +}; + +class ICommandQueue { +public: + virtual ~ICommandQueue() = default; + + virtual void ExecuteCommandLists(ICommandList** lists, uint32_t count) = 0; + virtual void Signal(IFence* fence, uint64_t value) = 0; + virtual void Wait(IFence* fence, uint64_t value) = 0; + + virtual uint64_t GetTimestampFrequency() const = 0; +}; + +class ICommandAllocator { +public: + virtual ~ICommandAllocator() = default; + virtual void Reset() = 0; +}; + +class IFence { +public: + virtual ~IFence() = default; + + virtual uint64_t GetCompletedValue() const = 0; + virtual void Signal(uint64_t value) = 0; + virtual void Wait(uint64_t value) = 0; + virtual void Wait(uint64_t value, uint64_t timeoutMs) = 0; +}; + +class IDescriptorHeap { +public: + virtual ~IDescriptorHeap() = default; + + virtual DescriptorHeapType GetType() const = 0; + virtual uint32_t GetDescriptorCount() const = 0; + + virtual void* GetCPUDescriptorHandle(uint32_t index) const = 0; + virtual uint64_t GetGPUDescriptorHandle(uint32_t index) const = 0; + + virtual void SetName(const String& name) = 0; +}; + +class IQueryHeap { +public: + virtual ~IQueryHeap() = default; + + virtual QueryType GetType() const = 0; + virtual uint32_t GetCount() const = 0; + + virtual void Begin(ICommandList* cmdList, uint32_t index) = 0; + virtual void End(ICommandList* cmdList, uint32_t index) = 0; + virtual void GetData(uint32_t index, void* data, uint32_t dataSize) = 0; +}; + +class ISwapChain { +public: + virtual ~ISwapChain() = default; + + virtual bool Initialize(const SwapChainDesc& desc, void* windowHandle) = 0; + virtual void Shutdown() = 0; + + virtual bool Present() = 0; + virtual bool Resize(uint32_t width, uint32_t height) = 0; + + virtual uint32_t GetCurrentBufferIndex() const = 0; + virtual RenderTexture* GetBuffer(uint32_t index) = 0; + + virtual void SetFullscreen(bool fullscreen) = 0; + virtual bool IsFullscreen() const = 0; +}; + +struct RootParameter { + enum class Type { DescriptorTable, Constants, CBV, SRV, UAV, Sampler }; + Type type; + uint32_t shaderRegister; + uint32_t registerSpace; + ShaderVisibility visibility = ShaderVisibility::All; +}; + +struct RootSignatureDesc { + RootParameter* parameters; + uint32_t parameterCount; + uint32_t flags; +}; + +class RootSignature { +public: + virtual ~RootSignature() = default; + virtual bool Initialize(const RootSignatureDesc& desc) = 0; + virtual void SetName(const String& name) = 0; +}; + +struct PipelineDesc { + RootSignature* rootSignature; + Shader* vertexShader; + Shader* pixelShader; + Shader* computeShader; + RenderState renderState; + uint32_t numRenderTargets; + Format rtvFormats[8]; + Format dsvFormat; +}; + +class PipelineStateObject { +public: + virtual ~PipelineStateObject() = default; + virtual void SetName(const String& name) = 0; +}; + +class ICommandList { +public: + virtual ~ICommandList() = default; + + virtual void Reset(ICommandAllocator* allocator) = 0; + virtual void Close() = 0; + + virtual void BeginRenderPass(const RenderPassDesc& desc) = 0; + virtual void EndRenderPass() = 0; + + virtual void SetPipelineState(PipelineStateObject* pso) = 0; + virtual void SetRootSignature(RootSignature* signature) = 0; + + virtual void SetVertexBuffer(uint32_t slot, VertexBuffer* buffer, uint32_t offset = 0) = 0; + virtual void SetIndexBuffer(IndexBuffer* buffer, uint32_t offset = 0) = 0; + + virtual void SetDescriptorHeap(IDescriptorHeap* heap) = 0; + virtual void SetGraphicsDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) = 0; + virtual void SetComputeDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) = 0; + + virtual void DrawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, + uint32_t startVertex, uint32_t startInstance) = 0; + virtual void DrawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, + uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) = 0; + virtual void Dispatch(uint32_t x, uint32_t y, uint32_t z) = 0; + + virtual void SetViewports(const Viewport* viewports, uint32_t count) = 0; + virtual void SetScissorRects(const Rect* rects, uint32_t count) = 0; + virtual void SetRenderTargets(const RenderTarget* const* targets, uint32_t count, + const DepthStencil* depthStencil) = 0; + + virtual void ClearRenderTargetView(const RenderTarget* target, const float color[4]) = 0; + virtual void ClearDepthStencilView(const DepthStencil* depth, float depthValue, uint8_t stencil) = 0; + + virtual void CopyResource(IResource* dst, const IResource* src) = 0; + virtual void CopyBuffer(IResource* dst, uint64_t dstOffset, const IResource* src, + uint64_t srcOffset, uint64_t size) = 0; + + virtual void ResourceBarrier(IResource* resource, ResourceStateFlag before, + ResourceStateFlag after) = 0; +}; + +class IShaderResourceView { +public: + virtual ~IShaderResourceView() = default; +}; + +class IRenderTargetView { +public: + virtual ~IRenderTargetView() = default; +}; + +class IDepthStencilView { +public: + virtual ~IDepthStencilView() = default; +}; + +class IRHIDevice { +public: + virtual ~IRHIDevice() = default; + + virtual GraphicsAPI GetAPI() const = 0; + virtual const char* GetAPIName() const = 0; + + virtual bool Initialize() = 0; + virtual void Shutdown() = 0; + + virtual bool CreateCommandQueue(ICommandQueue** queue, const CommandQueueDesc& desc) = 0; + virtual bool CreateCommandAllocator(ICommandAllocator** allocator) = 0; + virtual bool CreateCommandList(ICommandList** list, ICommandAllocator* allocator) = 0; + virtual bool CreateFence(IFence** fence) = 0; + + virtual bool CreateDescriptorHeap(IDescriptorHeap** heap, const DescriptorHeapDesc& desc) = 0; + virtual bool CreateQueryHeap(IQueryHeap** heap, const QueryHeapDesc& desc) = 0; + + virtual bool CreateRootSignature(RootSignature** signature, const RootSignatureDesc& desc) = 0; + virtual bool CreatePipelineState(PipelineStateObject** pso, const PipelineDesc& desc) = 0; + + virtual bool CreateSwapChain(ISwapChain** swapChain, const SwapChainDesc& desc, void* windowHandle) = 0; + virtual bool CreateRenderTarget(RenderTarget** target, const RenderTargetDesc& desc) = 0; + virtual bool CreateDepthStencil(DepthStencil** depthStencil, const DepthStencilDesc& desc) = 0; + + virtual void* GetNativeDevice() const = 0; +}; + +} // namespace RHI +} // namespace XCEngine +``` + + + +### 4.3 Direct3D 12 特有的 RHI 实现 + +```cpp +// ============================================ +// D3D12 前向声明 +// ============================================ +struct ID3D12Device; +struct ID3D12CommandQueue; +struct ID3D12CommandAllocator; +struct ID3D12GraphicsCommandList; +struct ID3D12DescriptorHeap; +struct ID3D12RootSignature; +struct ID3D12PipelineState; +struct ID3D12Fence; +struct ID3D12QueryHeap; +struct IDXGISwapChain3; + +template +class ComPtr; + +namespace XCEngine { +namespace RHI { +namespace D3D12 { + +enum class D3D12_COMMAND_LIST_TYPE { + DIRECT = 0, + BUNDLE = 1, + COMPUTE = 2, + COPY = 3 +}; + +enum class D3D12_DESCRIPTOR_HEAP_TYPE { + CBV_SRV_UAV = 0, + SAMPLER = 1, + RTV = 2, + DSV = 3, + NUM_TYPES = 4 +}; + +enum class D3D12_DESCRIPTOR_HEAP_FLAGS { + NONE = 0, + SHADER_VISIBLE = 1 +}; + +enum class D3D12_RESOURCE_STATES { + COMMON = 0, + VERTEX_AND_CONSTANT_BUFFER = 0x1, + INDEX_BUFFER = 0x2, + RENDER_TARGET = 0x4, + UNORDERED_ACCESS = 0x8, + DEPTH_WRITE = 0x10, + DEPTH_READ = 0x20, + NON_PIXEL_SHADER_RESOURCE = 0x40, + PIXEL_SHADER_RESOURCE = 0x80, + STREAM_OUT = 0x100, + INDIRECT_ARGUMENT = 0x200, + COPY_DEST = 0x400, + COPY_SOURCE = 0x800, + RESOLVE_DEST = 0x1000, + RESOLVE_SOURCE = 0x2000, + RAYTRACING_ACCELERATION_STRUCTURE = 0x4000, + SHADING_RATE_COARSE = 0x8000, + SHADING_RATE_FINE = 0x10000, + PRESENT = 0x0 +}; + +enum class D3D12_SRV_DIMENSION { + UNKNOWN = 0, + BUFFER = 1, + TEXTURE1D = 2, + TEXTURE1DARRAY = 3, + TEXTURE2D = 4, + TEXTURE2DARRAY = 5, + TEXTURE2DMS = 6, + TEXTURE2DMSARRAY = 7, + TEXTURE3D = 8, + TEXTURECUBE = 9, + TEXTURECUBEARRAY = 10 +}; + +enum class D3D12_UAV_DIMENSION { + UNKNOWN = 0, + BUFFER = 1, + TEXTURE1D = 2, + TEXTURE1DARRAY = 3, + TEXTURE2D = 4, + TEXTURE2DARRAY = 5, + TEXTURE3D = 8 +}; + +enum class D3D12_RTV_DIMENSION { + UNKNOWN = 0, + BUFFER = 1, + TEXTURE1D = 2, + TEXTURE1DARRAY = 3, + TEXTURE2D = 4, + TEXTURE2DARRAY = 5, + TEXTURE2DMS = 6, + TEXTURE2DMSARRAY = 7, + TEXTURE3D = 8 +}; + +enum class D3D12_DSV_DIMENSION { + UNKNOWN = 0, + TEXTURE1D = 1, + TEXTURE1DARRAY = 2, + TEXTURE2D = 3, + TEXTURE2DARRAY = 4, + TEXTURE2DMS = 5, + TEXTURE2DMSARRAY = 6 +}; + +enum class D3D12_ROOT_SIGNATURE_FLAGS { + NONE = 0, + ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT = 0x1, + DENY_VERTEX_SHADER_ROOT_ACCESS = 0x2, + DENY_HULL_SHADER_ROOT_ACCESS = 0x4, + DENY_DOMAIN_SHADER_ROOT_ACCESS = 0x8, + DENY_GEOMETRY_SHADER_ROOT_ACCESS = 0x10, + DENY_PIXEL_SHADER_ROOT_ACCESS = 0x20, + ALLOW_STREAM_PIPELINE = 0x40, + LOCAL_ROOT_SIGNATURE = 0x80 +}; + +enum class D3D12_ROOT_PARAMETER_TYPE { + DESCRIPTOR_TABLE = 0, + _32BIT_CONSTANTS = 1, + CBV = 2, + SRV = 3, + UAV = 4, + SAMPLER = 5 +}; + +enum class D3D12_SHADER_VISIBILITY { + ALL = 0, + VERTEX = 1, + HULL = 2, + DOMAIN = 3, + GEOMETRY = 4, + PIXEL = 5 +}; + +enum class D3D12_FILTER { + MIN_MAG_MIP_POINT = 0, + MIN_MAG_POINT_MIP_LINEAR = 0x1, + MIN_POINT_MAG_LINEAR_MIP_POINT = 0x2, + MIN_POINT_MAG_MIP_LINEAR = 0x3, + MIN_LINEAR_MAG_MIP_POINT = 0x4, + MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x5, + MIN_MAG_LINEAR_MIP_POINT = 0x6, + MIN_MAG_MIP_LINEAR = 0x7, + ANISOTROPIC = 0x15, + COMPARISON_MIN_MAG_MIP_POINT = 0x80, + COMPARISON_MIN_MAG_POINT_MIP_LINEAR = 0x81, + COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x82, + COMPARISON_MIN_POINT_MAG_MIP_LINEAR = 0x83, + COMPARISON_MIN_LINEAR_MAG_MIP_POINT = 0x84, + COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x85, + COMPARISON_MIN_MAG_LINEAR_MIP_POINT = 0x86, + COMPARISON_MIN_MAG_MIP_LINEAR = 0x87, + COMPARISON_ANISOTROPIC = 0x95 +}; + +enum class D3D12_TEXTURE_ADDRESS_MODE { + WRAP = 1, + MIRROR = 2, + CLAMP = 3, + BORDER = 4, + MIRROR_ONCE = 5 +}; + +enum class D3D12_COMPARISON_FUNC { + NEVER = 1, + LESS = 2, + EQUAL = 3, + LESS_EQUAL = 4, + GREATER = 5, + NOT_EQUAL = 6, + GREATER_EQUAL = 7, + ALWAYS = 8 +}; + +enum class D3D12_BLEND { + ZERO = 1, + ONE = 2, + SRC_COLOR = 3, + INV_SRC_COLOR = 4, + SRC_ALPHA = 5, + INV_SRC_ALPHA = 6, + DST_ALPHA = 7, + INV_DST_ALPHA = 8, + DST_COLOR = 9, + INV_DST_COLOR = 10, + SRC_ALPHA_SAT = 11, + BLEND_FACTOR = 14, + INV_BLEND_FACTOR = 15, + SRC1_COLOR = 16, + INV_SRC1_COLOR = 17, + SRC1_ALPHA = 18, + INV_SRC1_ALPHA = 19 +}; + +enum class D3D12_BLEND_OP { + ADD = 1, + SUBTRACT = 2, + REV_SUBTRACT = 3, + MIN = 4, + MAX = 5 +}; + +enum class D3D12_COLOR_WRITE_ENABLE { + RED = 1, + GREEN = 2, + BLUE = 4, + ALPHA = 8, + ALL = 15 +}; + +enum class D3D12_CULL_MODE { + NONE = 1, + FRONT = 2, + BACK = 3 +}; + +enum class D3D12_FILL_MODE { + WIREFRAME = 2, + SOLID = 3 +}; + +enum class D3D12_PRIMITIVE_TOPOLOGY_TYPE { + UNDEFINED = 0, + POINT = 1, + LINE = 2, + TRIANGLE = 3, + PATCH = 4 +}; + +enum class D3D12_PRIMITIVE_TOPOLOGY { + UNDEFINED = 0, + POINTLIST = 1, + LINELIST = 2, + LINESTRIP = 3, + TRIANGLELIST = 4, + TRIANGLESTRIP = 5, + TRIANGLEFAN = 6, + LINELIST_ADJ = 10, + LINESTRIP_ADJ = 11, + TRIANGLELIST_ADJ = 12, + TRIANGLESTRIP_ADJ = 13, + PATCHLIST_1 = 33, + PATCHLIST_2 = 34, + PATCHLIST_3 = 35, + PATCHLIST_4 = 36, + PATCHLIST_5 = 37, + PATCHLIST_6 = 38, + PATCHLIST_7 = 39, + PATCHLIST_8 = 40, + PATCHLIST_9 = 41, + PATCHLIST_10 = 42, + PATCHLIST_11 = 43, + PATCHLIST_12 = 44, + PATCHLIST_13 = 45, + PATCHLIST_14 = 46, + PATCHLIST_15 = 47, + PATCHLIST_16 = 48, + PATCHLIST_17 = 49, + PATCHLIST_18 = 50, + PATCHLIST_19 = 51, + PATCHLIST_20 = 52, + PATCHLIST_21 = 53, + PATCHLIST_22 = 54, + PATCHLIST_23 = 55, + PATCHLIST_24 = 56, + PATCHLIST_25 = 57, + PATCHLIST_26 = 58, + PATCHLIST_27 = 59, + PATCHLIST_28 = 60, + PATCHLIST_29 = 61, + PATCHLIST_30 = 62, + PATCHLIST_31 = 63, + PATCHLIST_32 = 64 +}; + +enum class D3D12_QUERY_TYPE { + OCCLUSION = 0, + TIMESTAMP = 1, + PIPELINE_STATISTICS = 2, + DEPTH_STENCIL_CLIP_CUT = 3 +}; + +enum class D3D12_QUERY_HEAP_TYPE { + OCCLUSION = 0, + TIMESTAMP = 1, + PIPELINE_STATISTICS = 2, + SO_STATISTICS = 3, + VIDEO_DECODE_STATISTICS = 4, + COPY_QUEUE_TIMESTAMP = 5, + TIME_STAMP = 6 +}; + +struct D3D12_CPU_DESCRIPTOR_HANDLE { + void* ptr; +}; + +struct D3D12_GPU_DESCRIPTOR_HANDLE { + uint64_t ptr; +}; + +struct D3D12_RESOURCE_BARRIER { + enum class Type { + TRANSITION, + ALIASING, + UDV + }; + + Type Type; + + union { + struct { + IResource* pResource; + int32_t Subresource; + D3D12_RESOURCE_STATES StateBefore; + D3D12_RESOURCE_STATES StateAfter; + } Transition; + + struct { + IResource* pResourceBefore; + IResource* pResourceAfter; + } Aliasing; + + struct { + IResource* pResource; + uint32_t NumRoadmapEntries; + uint32_t* pRoadmap; + D3D12_RESOURCE_STATES StateBefore; + D3D12_RESOURCE_STATES StateAfter; + } UAV; + }; +}; + +struct D3D12_VIEWPORT { + float TopLeftX; + float TopLeftY; + float Width; + float Height; + float MinDepth; + float MaxDepth; +}; + +struct D3D12_RECT { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct D3D12_INPUT_ELEMENT_DESC { + const char* SemanticName; + uint32_t SemanticIndex; + Format Format; + uint32_t InputSlot; + uint32_t AlignedByteOffset; + D3D12_INPUT_CLASSIFICATION InputSlotClass; + uint32_t InstanceDataStepRate; +}; + +enum class D3D12_INPUT_CLASSIFICATION { + PER_VERTEX_DATA = 0, + PER_INSTANCE_DATA = 1 +}; + +struct D3D12_SO_DECLARATION_ENTRY { + const char* SemanticName; + uint32_t SemanticIndex; + uint8_t StartComponent; + uint8_t ComponentCount; + uint8_t OutputSlot; +}; + +struct D3D12_SHADER_RESOURCE_VIEW_DESC { + D3D12_SRV_DIMENSION ViewDimension; + Format Format; + uint32_t Shader4ComponentMapping; + + union { + struct { + uint32_t MostDetailedMip; + uint32_t MipLevels; + uint32_t PlaneSlice; + float ResourceMinLODClamp; + } Texture2D; + + struct { + uint32_t MostDetailedMip; + uint32_t MipLevels; + uint32_t FirstArraySlice; + uint32_t ArraySize; + uint32_t PlaneSlice; + float ResourceMinLODClamp; + } Texture2DArray; + + struct { + uint32_t FirstWSlice; + uint32_t WSize; + uint32_t MostDetailedMip; + uint32_t MipLevels; + float ResourceMinLODClamp; + } Texture3D; + + struct { + uint32_t MostDetailedMip; + uint32_t MipLevels; + uint32_t FirstArraySlice; + uint32_t ArraySize; + uint32_t PlaneSlice; + float ResourceMinLODClamp; + } TextureCube; + }; +}; + +struct D3D12_UNORDERED_ACCESS_VIEW_DESC { + D3D12_UAV_DIMENSION ViewDimension; + Format Format; + uint32_t Shader4ComponentMapping; + + union { + struct { + uint32_t MipSlice; + } Buffer; + + struct { + uint32_t MipSlice; + uint32_t FirstArraySlice; + uint32_t ArraySize; + uint32_t PlaneSlice; + } Texture2D; + + struct { + uint32_t MipSlice; + uint32_t FirstWSlice; + uint32_t WSize; + } Texture3D; + }; +}; + +struct D3D12_RENDER_TARGET_VIEW_DESC { + D3D12_RTV_DIMENSION ViewDimension; + Format Format; + + union { + struct { + uint32_t MipSlice; + uint32_t FirstArraySlice; + uint32_t ArraySize; + } Texture2D; + + struct { + uint32_t MipSlice; + uint32_t FirstArraySlice; + uint32_t ArraySize; + uint32_t PlaneSlice; + } Texture2DMS; + }; +}; + +struct D3D12_DEPTH_STENCIL_VIEW_DESC { + D3D12_DSV_DIMENSION ViewDimension; + Format Format; + uint32_t MipSlice; + uint32_t FirstArraySlice; + uint32_t ArraySize; + uint32_t PlaneSlice; +}; + +struct D3D12_CONSTANT_BUFFER_VIEW_DESC { + uint64_t BufferLocation; + uint32_t SizeInBytes; +}; + +struct D3D12_SAMPLER_DESC { + D3D12_FILTER Filter; + D3D12_TEXTURE_ADDRESS_MODE AddressU; + D3D12_TEXTURE_ADDRESS_MODE AddressV; + D3D12_TEXTURE_ADDRESS_MODE AddressW; + float MipLODBias; + uint32_t MaxAnisotropy; + D3D12_COMPARISON_FUNC ComparisonFunc; + float BorderColor[4]; + float MinLOD; + float MaxLOD; +}; + +struct InputLayout { + D3D12_INPUT_ELEMENT_DESC* pInputElementDescs = nullptr; + uint32_t NumInputElementDescs = 0; +}; + +struct StreamOutput { + D3D12_SO_DECLARATION_ENTRY* pSODeclaration = nullptr; + uint32_t NumSODeclarationEntries = 0; + uint64_t* pBufferStrides = nullptr; + uint32_t NumBufferStrides = 0; + uint32_t RasterizedStream = 0; +}; + +struct BlendState { + bool AlphaToCoverageEnable = false; + bool IndependentBlendEnable = false; + + struct RenderTargetBlendDesc { + bool BlendEnable = false; + D3D12_BLEND SrcBlend = D3D12_BLEND::ONE; + D3D12_BLEND DestBlend = D3D12_BLEND::ZERO; + D3D12_BLEND_OP BlendOp = D3D12_BLEND_OP::ADD; + D3D12_BLEND SrcBlendAlpha = D3D12_BLEND::ONE; + D3D12_BLEND DestBlendAlpha = D3D12_BLEND::ZERO; + D3D12_BLEND_OP BlendOpAlpha = D3D12_BLEND_OP::ADD; + D3D12_COLOR_WRITE_ENABLE RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE::ALL; + }; + RenderTargetBlendDesc RenderTarget[8]; +}; + +struct RasterizerState { + D3D12_FILL_MODE FillMode = D3D12_FILL_MODE::SOLID; + D3D12_CULL_MODE CullMode = D3D12_CULL_MODE::BACK; + bool FrontCounterClockwise = false; + int32_t DepthBias = 0; + float DepthBiasClamp = 0.0f; + float SlopeScaledDepthBias = 0.0f; + bool DepthClipEnable = true; + bool MultisampleEnable = false; + bool AntialiasedLineEnable = false; + uint32_t ForcedSampleCount = 0; + bool ConservativeRaster = false; +}; + +struct DepthStencilState { + bool DepthEnable = true; + D3D12_DEPTH_WRITE_MASK DepthWriteMask = D3D12_DEPTH_WRITE_MASK::ALL; + D3D12_COMPARISON_FUNC DepthFunc = D3D12_COMPARISON_FUNC::LESS; + bool StencilEnable = false; + uint8_t StencilReadMask = 0xFF; + uint8_t StencilWriteMask = 0xFF; + + struct DepthStencilOpDesc { + D3D12_STENCIL_OP StencilFailOp = D3D12_STENCIL_OP::KEEP; + D3D12_STENCIL_OP StencilDepthFailOp = D3D12_STENCIL_OP::KEEP; + D3D12_STENCIL_OP StencilPassOp = D3D12_STENCIL_OP::KEEP; + D3D12_COMPARISON_FUNC StencilFunc = D3D12_COMPARISON_FUNC::ALWAYS; + }; + DepthStencilOpDesc FrontFace; + DepthStencilOpDesc BackFace; +}; + +enum class D3D12_DEPTH_WRITE_MASK { + ZERO = 0, + ALL = 1 +}; + +enum class D3D12_STENCIL_OP { + KEEP = 1, + ZERO = 2, + REPLACE = 3, + INCR_SAT = 4, + DECR_SAT = 5, + INVERT = 6, + INCR = 7, + DECR = 8 +}; + +struct DXGI_SAMPLE_DESC { + uint32_t Count = 1; + uint32_t Quality = 0; +}; + +struct CachedPSO { + void* pCachedBlob = nullptr; + size_t CachedBlobSizeInBytes = 0; +}; + +enum class D3D12_PIPELINE_STATE_FLAGS { + NONE = 0, + TOOL_DEBUG = 1 +}; + +struct D3D12_GRAPHICS_PIPELINE_STATE_DESC { + InputLayout* InputLayout; + RootSignature* pRootSignature; + Shader* VS; + Shader* HS; + Shader* DS; + Shader* GS; + Shader* PS; + StreamOutput* StreamOutput; + BlendState* BlendState; + uint32_t SampleMask; + RasterizerState* RasterizerState; + DepthStencilState* DepthStencilState; + D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType; + uint32_t NumRenderTargets; + Format RTVFormats[8]; + Format DSVFormat; + DXGI_SAMPLE_DESC SampleDesc; + uint32_t NodeMask; + CachedPSO* cachedPSO; + D3D12_PIPELINE_STATE_FLAGS Flags; +}; + +struct D3D12_COMPUTE_PIPELINE_STATE_DESC { + RootSignature* pRootSignature; + Shader* CS; + uint32_t NodeMask; + CachedPSO* cachedPSO; + D3D12_PIPELINE_STATE_FLAGS Flags; +}; + +class D3D12Device : public IRHIDevice { +public: + D3D12Device(); + virtual ~D3D12Device(); + + GraphicsAPI GetAPI() const override { return GraphicsAPI::Direct3D12; } + const char* GetAPIName() const override { return "Direct3D 12"; } + + bool Initialize() override; + void Shutdown() override; + + ID3D12Device* GetD3D12Device() { return m_device.Get(); } + ID3D12CommandQueue* GetCommandQueue(D3D12_COMMAND_LIST_TYPE type); + + bool CreateCommandQueue(ICommandQueue** queue, const CommandQueueDesc& desc) override; + bool CreateCommandAllocator(ICommandAllocator** allocator) override; + bool CreateCommandList(ICommandList** list, ICommandAllocator* allocator) override; + bool CreateFence(IFence** fence) override; + + bool CreateDescriptorHeap(IDescriptorHeap** heap, const DescriptorHeapDesc& desc) override; + bool CreateQueryHeap(IQueryHeap** heap, const QueryHeapDesc& desc) override; + + bool CreateRootSignature(RootSignature** signature, const RootSignatureDesc& desc) override; + bool CreatePipelineState(PipelineStateObject** pso, const PipelineDesc& desc) override; + + bool CreateSwapChain(ISwapChain** swapChain, const SwapChainDesc& desc, void* windowHandle) override; + + bool CreateRenderTarget(RenderTarget** target, const RenderTargetDesc& desc) override; + bool CreateDepthStencil(DepthStencil** depthStencil, const DepthStencilDesc& desc) override; + + void* GetNativeDevice() const override { return m_device.Get(); } + +private: + ComPtr m_device; + ComPtr m_directQueue; + ComPtr m_computeQueue; + ComPtr m_copyQueue; + + D3D_FEATURE_LEVEL m_featureLevel; + bool m_raytracingSupported; + bool m_meshShadersSupported; +}; + +class D3D12CommandList : public ICommandList { +public: + D3D12CommandList(ID3D12CommandAllocator* allocator); + virtual ~D3D12CommandList(); + + void Reset(ICommandAllocator* allocator) override; + void Close() override; + + void BeginRenderPass(const RenderPassDesc& desc) override; + void EndRenderPass() override; + + void SetPipelineState(PipelineStateObject* pso) override; + void SetRootSignature(RootSignature* signature) override; + + void SetVertexBuffer(uint32_t slot, VertexBuffer* buffer, uint32_t offset = 0) override; + void SetIndexBuffer(IndexBuffer* buffer, uint32_t offset = 0) override; + + void SetDescriptorHeap(IDescriptorHeap* heap) override; + void SetGraphicsDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) override; + void SetComputeDescriptorTable(uint32_t rootParameterIndex, uint64_t baseDescriptor) override; + + void DrawInstanced( + uint32_t vertexCountPerInstance, + uint32_t instanceCount, + uint32_t startVertex, + uint32_t startInstance + ) override; + + void DrawIndexedInstanced( + uint32_t indexCountPerInstance, + uint32_t instanceCount, + uint32_t startIndex, + int32_t baseVertex, + uint32_t startInstance + ) override; + + void Dispatch(uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) override; + + void SetViewports(const Viewport* viewports, uint32_t count) override; + void SetScissorRects(const Rect* rects, uint32_t count) override; + + void SetRenderTargets( + const RenderTarget* const* renderTargets, + uint32_t renderTargetCount, + const DepthStencil* depthStencil + ) override; + + void ClearRenderTargetView(const RenderTarget* target, const float color[4]) override; + void ClearDepthStencilView(const DepthStencil* depthStencil, float depth, uint8_t stencil) override; + + void CopyResource(IResource* dst, const IResource* src) override; + void CopyBuffer(IResource* dst, uint64_t dstOffset, const IResource* src, uint64_t srcOffset, uint64_t size) override; + + void ResourceBarrier( + IResource* resource, + ResourceStateFlag stateBefore, + ResourceStateFlag stateAfter + ); + + ID3D12GraphicsCommandList* GetD3D12CommandList() { return m_commandList.Get(); } + +private: + ComPtr m_commandList; + D3D12_PRIMITIVE_TOPOLOGY m_topology = D3D12_PRIMITIVE_TOPOLOGY::UNDEFINED; +}; + +class D3D12DescriptorHeap : public IDescriptorHeap { +public: + D3D12DescriptorHeap(ID3D12DescriptorHeap* heap, DescriptorHeapType type, uint32_t count); + virtual ~D3D12DescriptorHeap(); + + DescriptorHeapType GetType() const override { return m_type; } + uint32_t GetDescriptorCount() const override { return m_descriptorCount; } + + void* GetCPUDescriptorHandle(uint32_t index) const override; + uint64_t GetGPUDescriptorHandle(uint32_t index) const override; + + void SetName(const String& name) override; + + ID3D12DescriptorHeap* GetD3D12DescriptorHeap() { return m_heap.Get(); } + +private: + ComPtr m_heap; + DescriptorHeapType m_type; + uint32_t m_descriptorCount; + uint32_t m_descriptorSize; +}; + +class D3D12RootSignature : public RootSignature { +public: + D3D12RootSignature(); + virtual ~D3D12RootSignature(); + + bool Initialize(const RootSignatureDesc& desc) override; + void SetName(const String& name) override; + + ID3D12RootSignature* GetD3D12RootSignature() { return m_rootSignature.Get(); } + +private: + bool SerializeRootSignature( + const RootSignatureDesc& desc, + std::vector& serializedData + ); + + ComPtr m_rootSignature; +}; + +class D3D12PipelineState : public PipelineStateObject { +public: + D3D12PipelineState(); + virtual ~D3D12PipelineState(); + + bool Initialize(const PipelineDesc& desc); + void SetName(const String& name); + + ID3D12PipelineState* GetD3D12PipelineState() { return m_pipelineState.Get(); } + +private: + ComPtr m_pipelineState; +}; + +class D3D12Fence : public IFence { +public: + D3D12Fence(ID3D12Fence* fence); + virtual ~D3D12Fence(); + + uint64_t GetCompletedValue() const override; + void Signal(uint64_t value) override; + void Wait(uint64_t value) override; + void Wait(uint64_t value, uint64_t timeout) override; + + ID3D12Fence* GetD3D12Fence() { return m_fence.Get(); } + + HANDLE GetEventHandle() { return m_event; } + +private: + ComPtr m_fence; + HANDLE m_event; +}; + +class D3D12QueryHeap : public IQueryHeap { +public: + D3D12QueryHeap(ID3D12QueryHeap* heap, QueryType type, uint32_t count); + virtual ~D3D12QueryHeap(); + + QueryType GetType() const override { return m_type; } + uint32_t GetCount() const override { return m_count; } + + void Begin(ICommandList* cmdList, uint32_t index) override; + void End(ICommandList* cmdList, uint32_t index) override; + + void GetData(uint32_t index, void* data, uint32_t dataSize) override; + + ID3D12QueryHeap* GetD3D12QueryHeap() { return m_queryHeap.Get(); } + +private: + ComPtr m_queryHeap; + QueryType m_type; + uint32_t m_count; +}; + +class D3D12SwapChain : public ISwapChain { +public: + D3D12SwapChain(IDXGISwapChain3* swapChain); + virtual ~D3D12SwapChain(); + + bool Initialize(const SwapChainDesc& desc, void* windowHandle) override; + void Shutdown() override; + + bool Present() override; + bool Resize(uint32_t width, uint32_t height) override; + + uint32_t GetCurrentBufferIndex() const override; + RenderTexture* GetBuffer(uint32_t index) override; + + void SetFullscreen(bool fullscreen) override; + bool IsFullscreen() const override; + + IDXGISwapChain3* GetD3D12SwapChain() { return m_swapChain.Get(); } + +private: + ComPtr m_swapChain; + std::vector m_buffers; + uint32_t m_bufferCount; + bool m_fullscreen; +}; + +} // namespace D3D12 +} // namespace RHI +} // namespace XCEngine + +namespace XCEngine { +namespace Rendering { + +using ICommandList = RHI::ICommandList; +using IBuffer = RHI::IResource; + +enum class BufferUsage { + Default, + Immutable, + Dynamic, + Staging +}; + +enum class MemoryUsage { + Default, + GPU_Only, + CPU_Only, + CPU_GPU +}; + +enum class PassType { + Graphics, + Compute, + Copy, + Raytracing +}; + +enum class LoadAction { + Load, + Clear, + DontCare +}; + +enum class StoreAction { + Store, + Resolve, + StoreAndResolve, + DontCare +}; + +enum class ResolveMode { + None, + Min, + Average, + Sample0, + Sample1, + Sample2, + Sample3, + Sample4, + Sample5, + Sample6, + Sample7 +}; + +enum class ComparisonFunc { + Never, + Less, + Equal, + LessEqual, + Greater, + NotEqual, + GreaterEqual, + Always +}; + +enum class BorderColor { + TransparentBlack, + OpaqueBlack, + OpaqueWhite +}; + +struct IndexType { + static constexpr uint8_t Uint16 = 0; + static constexpr uint8_t Uint32 = 1; +}; + +enum class ResourceState { + Undefined = 0, + Common = 1, + VertexBuffer = 2, + ConstantBuffer = 3, + IndexBuffer = 4, + RenderTarget = 5, + UnorderedAccess = 6, + DepthWrite = 7, + DepthRead = 8, + ShaderResource = 9, + IndirectArgument = 10, + CopyDest = 11, + CopySource = 12, + ResolveDest = 13, + ResolveSource = 14, + Present = 15, + GenericRead = 16 +}; + +struct ResourceDesc { + ResourceType type; + uint32_t width = 1; + uint32_t height = 1; + uint32_t depth = 1; + uint32_t mipLevels = 1; + uint32_t arraySize = 1; + Format format = Format::Unknown; + SampleCount sampleCount = SampleCount::Count1; + ResourceState initialState = ResourceState::Undefined; + bool cpuAccessible = false; + bool randomAccess = false; + + MemoryUsage memoryUsage = MemoryUsage::Default; + + String name; +}; + +class RenderContext { +public: + class GBufferPass; + class LightingPass; + class ShadowPass; + class PostProcessPass; + + static RenderContext* Create(const RenderContextDesc& desc); + static void Destroy(RenderContext* context); + + static RenderContext* GetMain(); + + void Initialize(const RenderContextDesc& desc); + void Shutdown(); + + RHI::IRHIDevice* GetDevice() { return m_device.get(); } + + void BeginFrame(); + void EndFrame(); + + ICommandList* GetMainCommandList() { return m_mainCommandList.get(); } + + void SetRenderTarget(const RenderTarget& target); + void SetViewport(const Viewport& viewport); + void SetScissorRect(const Rect& rect); + + void RenderScene(const SceneRenderDesc& desc); + void ApplyPostProcessing(const PostProcessDesc& desc); + + void CaptureFrame(const String& filePath); + void ToggleDebugView(); + +private: + std::unique_ptr m_device; + std::unique_ptr m_mainCommandList; + std::unique_ptr m_swapChain; + + std::unique_ptr m_gBufferPass; + std::unique_ptr m_lightingPass; + std::unique_ptr m_shadowPass; + std::unique_ptr m_postProcessPass; + + uint32_t m_frameIndex = 0; + uint64_t m_frameCount = 0; + + static RenderContext* s_mainContext; +}; + +struct ResourceGUID { + static constexpr uint64_t INVALID = 0; + + uint64_t value = INVALID; + + bool IsValid() const { return value != INVALID; } + bool operator==(const ResourceGUID& other) const { return value == other.value; } + bool operator!=(const ResourceGUID& other) const { return value != other.value; } + + String ToString() const; + static ResourceGUID FromString(const String& str); + + struct Hash { + size_t operator()(const ResourceGUID& guid) const { + return std::hash{}(guid.value); + } + }; +}; + +class RenderTexture { +public: + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + uint32_t GetMipLevels() const { return m_mipLevels; } + Format GetFormat() const { return m_format; } + + void* GetNativeHandle() const { return m_nativeHandle; } + + IShaderResourceView* GetSRV() { return m_srv.get(); } + IRenderTargetView* GetRTV() { return m_rtv.get(); } + IDepthStencilView* GetDSV() { return m_dsv.get(); } + + void* Map(); + void Unmap(); + +private: + uint32_t m_width, m_height; + uint32_t m_mipLevels; + Format m_format; + void* m_nativeHandle = nullptr; + + std::unique_ptr m_srv; + std::unique_ptr m_rtv; + std::unique_ptr m_dsv; +}; + +class RenderBuffer { +public: + uint64_t GetSize() const { return m_size; } + BufferUsage GetUsage() const { return m_usage; } + + void* GetNativeHandle() const { return m_nativeHandle; } + + void SetData(const void* data, uint64_t size, uint64_t offset = 0); + void* Map(); + void Unmap(); + +private: + uint64_t m_size; + BufferUsage m_usage; + void* m_nativeHandle = nullptr; +}; + +} // namespace Rendering +} // namespace XCEngine +``` + +### 4.4 相机组件 + +```cpp +namespace XCEngine { + +class CameraComponent : public Component { +public: + void Awake() override; + void Start() override; + void Update(float deltaTime) override; + + enum class ProjectionType : uint8_t { + Perspective, + Orthographic + }; + + void SetProjectionType(ProjectionType type) { m_projectionType = type; } + ProjectionType GetProjectionType() const { return m_projectionType; } + + void SetFieldOfView(float fov) { m_fieldOfView = fov; } + float GetFieldOfView() const { return m_fieldOfView; } + + void SetOrthographicSize(float size) { m_orthographicSize = size; } + float GetOrthographicSize() const { return m_orthographicSize; } + + void SetNearClipPlane(float near) { m_nearPlane = near; } + float GetNearClipPlane() const { return m_nearPlane; } + + void SetFarClipPlane(float far) { m_farPlane = far; } + float GetFarClipPlane() const { return m_farPlane; } + + void SetViewport(const Rect& rect) { m_viewportRect = rect; } + const Rect& GetViewport() const { return m_viewportRect; } + + void SetCullingMask(int32_t mask) { m_cullingMask = mask; } + int32_t GetCullingMask() const { return m_cullingMask; } + + void SetDepth(float depth) { m_depth = depth; } + float GetDepth() const { return m_depth; } + + Matrix4x4 GetViewMatrix() const; + Matrix4x4 GetProjectionMatrix() const; + Matrix4x4 GetViewProjectionMatrix() const; + + Vector3 ScreenToWorldPoint(const Vector3& screenPoint) const; + Vector3 WorldToScreenPoint(const Vector3& worldPoint) const; + Ray ScreenPointToRay(const Vector2& screenPoint) const; + + Frustum& GetFrustum() { return m_frustum; } + +private: + ProjectionType m_projectionType = ProjectionType::Perspective; + float m_fieldOfView = 60.0f; + float m_orthographicSize = 5.0f; + float m_nearPlane = 0.1f; + float m_farPlane = 1000.0f; + float m_aspectRatio = 16.0f / 9.0f; + Rect m_viewportRect = Rect(0, 0, 1, 1); + float m_depth = 0.0f; + int32_t m_cullingMask = -1; + Frustum m_frustum; +}; + +} // namespace XCEngine +``` + +### 4.5 光照组件 + +```cpp +namespace XCEngine { + +class LightComponent : public Component { +public: + void Awake() override; + void Update(float deltaTime) override; + + enum class LightType : uint8_t { + Directional, + Point, + Spot, + Area + }; + + void SetLightType(LightType type) { m_type = type; } + LightType GetLightType() const { return m_type; } + + void SetColor(const Vector3& color) { m_color = color; } + Vector3 GetColor() const { return m_color; } + + void SetIntensity(float intensity) { m_intensity = intensity; } + float GetIntensity() const { return m_intensity; } + + void SetRange(float range) { m_range = range; } + float GetRange() const { return m_range; } + + void SetSpotAngle(float angle) { m_spotAngle = angle; } + float GetSpotAngle() const { return m_spotAngle; } + + void SetCastShadows(bool cast) { m_castShadows = cast; } + bool GetCastShadows() const { return m_castShadows; } + + void SetShadowResolution(uint32_t resolution) { m_shadowResolution = resolution; } + uint32_t GetShadowResolution() const { return m_shadowResolution; } + + void SetCullingMask(int32_t mask) { m_cullingMask = mask; } + int32_t GetCullingMask() const { return m_cullingMask; } + +private: + LightType m_type = LightType::Point; + Vector3 m_color = Vector3::One(); + float m_intensity = 1.0f; + float m_range = 10.0f; + float m_spotAngle = 30.0f; + float m_penumbraAngle = 5.0f; + bool m_castShadows = true; + uint32_t m_shadowResolution = 1024; + float m_shadowBias = 0.005f; + float m_normalOffsetBias = 0.001f; + float m_nearPlane = 0.1f; + int32_t m_cullingMask = -1; + float m_intensityVariation = 0.0f; + ResourceGUID m_cookieTextureGuid; + float m_cookieSize = 5.0f; +}; + +} // namespace XCEngine +``` + +### 4.6 渲染网格组件 + +```cpp +namespace XCEngine { + +class RenderMeshComponent : public Component { +public: + void Awake() override; + void Start() override; + void Update(float deltaTime) override; + + ResourceGUID GetMesh() const { return m_meshGuid; } + void SetMesh(const ResourceGUID& guid); + + ResourceGUID GetMaterial() const { return m_materialGuid; } + void SetMaterial(const ResourceGUID& guid); + + bool GetCastShadows() const { return m_castShadows; } + void SetCastShadows(bool cast) { m_castShadows = cast; } + + bool GetReceiveShadows() const { return m_receiveShadows; } + void SetReceiveShadows(bool receive) { m_receiveShadows = receive; } + +private: + ResourceGUID m_meshGuid; + ResourceGUID m_materialGuid; + bool m_castShadows = true; + bool m_receiveShadows = true; +}; + +} // namespace XCEngine +``` + +### 4.7 材质系统 + +```cpp +namespace XCEngine { +namespace Rendering { + +enum class ShaderType { + Vertex, + Fragment, + Compute, + Geometry, + Hull, + Domain +}; + +enum class UniformType { + None, + Float, + Float2, + Float3, + Float4, + Int, + Int2, + Int3, + Int4, + Bool, + Sampler2D, + Sampler3D, + SamplerCube, + Sampler2DArray, + Matrix3x3, + Matrix4x4 +}; + +struct UniformDesc { + String name; + UniformType type; + uint32_t arraySize = 1; + uint32_t offset = 0; +}; + +struct ShaderUniformBlock { + String name; + uint32_t bindingSlot = 0; + uint32_t size = 0; + std::vector uniforms; +}; + +class Shader : public IResource { +public: + const String& GetSource() const { return m_source; } + ShaderType GetType() const { return m_type; } + + const std::vector& GetUniformBlocks() const { return m_uniformBlocks; } + const std::vector& GetResources() const { return m_resources; } + + bool HasVariant(const String& variantName) const; + Shader* GetVariant(const HashMap& defines); + + bool IsCompiled() const { return m_compiled; } + const String& GetCompileErrors() const { return m_compileErrors; } + +private: + String m_source; + ShaderType m_type; + bool m_compiled = false; + String m_compileErrors; + + std::vector m_uniformBlocks; + std::vector m_resources; + HashMap> m_variants; +}; + +enum class BlendMode { + Opaque, + Transparent, + Additive, + Multiply, + AlphaTest +}; + +enum class CullMode { + None, + Front, + Back +}; + +enum class ZWriteMode { + On, + Off +}; + +enum class DepthTest { + Never, + Less, + Equal, + LessEqual, + Greater, + NotEqual, + GreaterEqual, + Always +}; + +struct RenderState { + BlendMode blendMode = BlendMode::Opaque; + CullMode cullMode = CullMode::Back; + ZWriteMode zWriteMode = ZWriteMode::On; + DepthTest depthTest = DepthTest::LessEqual; + bool depthClip = true; + bool scissorTest = false; + bool stencilTest = false; + + uint8_t stencilReadMask = 0xFF; + uint8_t stencilWriteMask = 0xFF; + int32_t stencilRef = 0; + + uint32_t colorWriteMask = 0xF; + + bool operator==(const RenderState& other) const; +}; + +struct MaterialPropBlock { + HashMap floatProps; + HashMap intProps; + HashMap matrixProps; + HashMap textureProps; + + void SetFloat(const String& name, float value); + void SetFloat(const String& name, const Vector4& value); + void SetInt(const String& name, int32_t value); + void SetMatrix(const String& name, const Matrix4x4& value); + void SetTexture(const String& name, const ResourceGUID& guid); + + float GetFloat(const String& name, float defaultValue = 0.0f) const; + Vector4 GetFloat4(const String& name, const Vector4& defaultValue = Vector4::Zero()) const; + int32_t GetInt(const String& name, int32_t defaultValue = 0) const; + Matrix4x4 GetMatrix(const String& name, const Matrix4x4& defaultValue = Matrix4x4::Identity()) const; + ResourceGUID GetTexture(const String& name, ResourceGUID defaultValue = ResourceGUID::INVALID) const; +}; + +class Material : public IResource { +public: + Material(); + explicit Material(Shader* shader); + + Shader* GetShader() const { return m_shader; } + void SetShader(Shader* shader); + + const RenderState& GetRenderState() const { return m_renderState; } + void SetRenderState(const RenderState& state) { m_renderState = state; } + + MaterialPropBlock& GetPropBlock() { return m_props; } + const MaterialPropBlock& GetPropBlock() const { return m_props; } + + int32_t GetRenderQueue() const { return m_renderQueue; } + void SetRenderQueue(int32_t queue) { m_renderQueue = queue; } + + void SetFloat(const String& name, float value); + void SetFloat(const String& name, const Vector4& value); + void SetInt(const String& name, int32_t value); + void SetTexture(const String& name, const ResourceGUID& guid); + + float GetFloat(const String& name, float defaultValue = 0.0f) const; + int32_t GetInt(const String& name, int32_t defaultValue = 0) const; + ResourceGUID GetTexture(const String& name) const; + + bool IsInstanced() const { return m_instanced; } + void SetInstanced(bool instanced) { m_instanced = instanced; } + +private: + Shader* m_shader = nullptr; + RenderState m_renderState; + MaterialPropBlock m_props; + int32_t m_renderQueue = 0; + bool m_instanced = false; +}; + +class MaterialManager { +public: + static MaterialManager& Get(); + + void Initialize(); + void Shutdown(); + + Material* CreateMaterial(Shader* shader); + Material* GetMaterial(const ResourceGUID& guid) const; + void DestroyMaterial(Material* material); + + Material* FindMaterialByName(const String& name) const; + +private: + HashMap> m_materials; + uint64_t m_nextId = 1; +}; + +class MaterialPropertyBlock { +public: + MaterialPropertyBlock(); + ~MaterialPropertyBlock(); + + void Clear(); + + void SetFloat(const String& name, float value); + void SetVector(const String& name, const Vector4& value); + void SetColor(const String& name, const Color& value); + void SetMatrix(const String& name, const Matrix4x4& matrix); + void SetTexture(const String& name, Texture* texture); + + bool HasProperty(const String& name) const; + float GetFloat(const String& name, float defaultValue = 0.0f) const; + Vector4 GetVector(const String& name, const Vector4& defaultValue = Vector4::Zero()) const; + Color GetColor(const String& name, const Color& defaultValue = Color::White()) const; + Matrix4x4 GetMatrix(const String& name, const Matrix4x4& defaultValue = Matrix4x4::Identity()) const; + Texture* GetTexture(const String& name) const; + + bool IsEmpty() const { return m_properties.empty(); } + size_t GetPropertyCount() const { return m_properties.size(); } + + void ApplyToMaterial(Material* material) const; + void CopyFrom(const MaterialPropertyBlock& other); + +private: + struct PropertyValue { + enum class Type { Float, Vector, Matrix, Texture }; + Type type; + union { + float floatValue; + Vector4 vectorValue; + Matrix4x4 matrixValue; + Texture* textureValue; + }; + }; + HashMap m_properties; +}; + +class RenderSettings { +public: + static RenderSettings& Get(); + + void Load(); + void Save(); + + enum class AmbientMode { + Skybox, + Trilight, + Flat, + Custom + }; + + AmbientMode GetAmbientMode() const { return m_ambientMode; } + void SetAmbientMode(AmbientMode mode) { m_ambientMode = mode; } + + Color GetAmbientSkyColor() const { return m_ambientSkyColor; } + void SetAmbientSkyColor(const Color& color) { m_ambientSkyColor = color; } + + Color GetAmbientEquatorColor() const { return m_ambientEquatorColor; } + void SetAmbientEquatorColor(const Color& color) { m_ambientEquatorColor = color; } + + Color GetAmbientGroundColor() const { return m_ambientGroundColor; } + void SetAmbientGroundColor(const Color& color) { m_ambientGroundColor = color; } + + float GetAmbientIntensity() const { return m_ambientIntensity; } + void SetAmbientIntensity(float intensity) { m_ambientIntensity = intensity; } + + bool GetFogEnabled() const { return m_fogEnabled; } + void SetFogEnabled(bool enabled) { m_fogEnabled = enabled; } + + enum class FogMode { + Linear, + Exponential, + ExponentialSquared + }; + + FogMode GetFogMode() const { return m_fogMode; } + void SetFogMode(FogMode mode) { m_fogMode = mode; } + + Color GetFogColor() const { return m_fogColor; } + void SetFogColor(const Color& color) { m_fogColor = color; } + + float GetFogDensity() const { return m_fogDensity; } + void SetFogDensity(float density) { m_fogDensity = density; } + + float GetFogStartDistance() const { return m_fogStartDistance; } + void SetFogStartDistance(float distance) { m_fogStartDistance = distance; } + + float GetFogEndDistance() const { return m_fogEndDistance; } + void SetFogEndDistance(float distance) { m_fogEndDistance = distance; } + + float GetShadowDistance() const { return m_shadowDistance; } + void SetShadowDistance(float distance) { m_shadowDistance = distance; } + + uint32_t GetShadowCascadeCount() const { return m_shadowCascadeCount; } + void SetShadowCascadeCount(uint32_t count) { m_shadowCascadeCount = count; } + + float GetShadowResolution() const { return m_shadowResolution; } + void SetShadowResolution(float resolution) { m_shadowResolution = resolution; } + + Texture* GetSkybox() const { return m_skybox; } + void SetSkybox(Texture* skybox) { m_skybox = skybox; } + +private: + RenderSettings() = default; + + AmbientMode m_ambientMode = AmbientMode::Skybox; + Color m_ambientSkyColor = Color{0.5f, 0.5f, 0.5f, 1.0f}; + Color m_ambientEquatorColor = Color{0.2f, 0.2f, 0.2f, 1.0f}; + Color m_ambientGroundColor = Color{0.1f, 0.1f, 0.1f, 1.0f}; + float m_ambientIntensity = 1.0f; + + bool m_fogEnabled = false; + FogMode m_fogMode = FogMode::Linear; + Color m_fogColor = Color{0.5f, 0.5f, 0.5f, 1.0f}; + float m_fogDensity = 0.01f; + float m_fogStartDistance = 10.0f; + float m_fogEndDistance = 100.0f; + + float m_shadowDistance = 150.0f; + uint32_t m_shadowCascadeCount = 4; + float m_shadowResolution = 1024.0f; + + Texture* m_skybox = nullptr; +}; + +class GraphicsSettings { +public: + static GraphicsSettings& Get(); + + void Load(); + void Save(); + + enum class ColorSpace { + Gamma, + Linear + }; + + ColorSpace GetColorSpace() const { return m_colorSpace; } + void SetColorSpace(ColorSpace space) { m_colorSpace = space; } + + enum class HDRSetting { + Off, + On, + Auto + }; + + HDRSetting GetHDR() const { return m_hdr; } + void SetHDR(HDRSetting hdr) { m_hdr = hdr; } + + uint32_t GetMSAASamples() const { return m_msaaSamples; } + void SetMSAASamples(uint32_t samples) { m_msaaSamples = samples; } + + bool GetRealtimeReflectionProbes() const { return m_realtimeReflectionProbes; } + void SetRealtimeReflectionProbes(bool enabled) { m_realtimeReflectionProbes = enabled; } + + float GetLODBias() const { return m_lodBias; } + void SetLODBias(float bias) { m_lodBias = bias; } + + int GetMaximumLODLevel() const { return m_maximumLODLevel; } + void SetMaximumLODLevel(int level) { m_maximumLODLevel = level; } + + enum class ShaderQuality { + Low, + Medium, + High + }; + + ShaderQuality GetShaderQuality() const { return m_shaderQuality; } + void SetShaderQuality(ShaderQuality quality) { m_shaderQuality = quality; } + +private: + GraphicsSettings() = default; + + ColorSpace m_colorSpace = ColorSpace::Linear; + HDRSetting m_hdr = HDRSetting::Auto; + uint32_t m_msaaSamples = 4; + bool m_realtimeReflectionProbes = true; + float m_lodBias = 1.0f; + int m_maximumLODLevel = -1; + ShaderQuality m_shaderQuality = ShaderQuality::High; +}; + +} // namespace Rendering +} // namespace XCEngine +``` + +### 4.8 渲染管线 + +```cpp +namespace XCEngine { +namespace Rendering { + +enum class RenderPipelineType { + Forward, + Deferred, + Hybrid, + Custom +}; + +struct RenderPipelineDesc { + RenderPipelineType type = RenderPipelineType::Forward; + uint32_t maxLights = 16; + bool shadowEnabled = true; + uint32_t shadowMapSize = 2048; + bool ssaoEnabled = false; + bool bloomEnabled = false; + bool toneMappingEnabled = true; + String toneMappingCurve = "ACES"; +}; + +struct RenderItem { + ResourceGUID meshGuid; + Material* material = nullptr; + Matrix4x4 worldMatrix; + uint32_t instanceId = 0; + int32_t renderQueue = 0; + uint32_t subMeshIndex = 0; + uint32_t lightingHash = 0; + + float GetDistanceSq(const Vector3& cameraPos) const; + bool operator<(const RenderItem& other) const; +}; + +struct CullingParams { + Vector3 position; + float sphereCulling; + + Matrix4x4 viewMatrix; + Matrix4x4 projectionMatrix; + + float nearClipPlane; + float farClipPlane; + + Rect viewport; + + int32_t cullingPlaneFlags; + int32_t cullingMask; + int32_t ortho; + + float shadowDistance; + float shadowNearPlaneDistance; + + bool isOrthographic; + bool cullDynamicObjects; + int32_t LODStripping; + int32_t forceCullingMode; + int32_t maximumLODLevel; + int32_t minimumLODLevel; +}; + +struct CullingResults { + struct VisibleObject { + uint64_t instanceID; + int32_t referenceID; + + Matrix4x4 localToWorldMatrix; + Matrix4x4 localToWorldMatrixPrevious; + + uint64_t staticBatchRootID; + + uint32_t subMeshIndex; + + Vector4 lightmapScaleOffset; + uint32_t lightmapIndex; + + bool staticShadowCaster; + bool motionVectors; + }; + + std::vector visibleRenderers; + + struct LightData { + uint64_t lightID; + int32_t uniqueID; + + Vector3 position; + Vector3 color; + Vector3 forward; + + float range; + float intensity; + float spotAngle; + + int32_t lightType; + int32_t renderMode; + + bool enabled; + bool shadowsEnabled; + + int32_t cullingMask; + + Matrix4x4 viewMatrix; + Matrix4x4 projectionMatrix; + }; + + std::vector visibleLights; + + Frustum frustumPlanes[2]; +}; + +class CullingSystem { +public: + static CullingSystem& Get(); + + void Initialize(); + void Shutdown(); + + bool PerformCulling( + const CullingParams& params, + CullingResults& results, + Scene* scene + ); + + void ComputeFrustumPlanes( + const Matrix4x4& viewMatrix, + const Matrix4x4& projectionMatrix, + Frustum& frustum + ); + +private: + std::vector m_visibleRenderers; + Frustum m_frustum; + std::vector m_cullingPlanes; +}; + +class ScriptableRenderContext { +public: + ScriptableRenderContext(); + ~ScriptableRenderContext(); + + void SetRenderTarget( + RenderTargetIdentifier colorTarget, + RenderTargetIdentifier depthTarget + ); + + void PushDebugGroup(const char* name); + void PopDebugGroup(); + + void BeginSample(const char* name); + void EndSample(const char* name); + + void DrawRenderer( + RenderItem* renderers, + size_t count, + Material* material, + int passIndex + ); + + void ExecuteAndPresent(); + + void Submit(); + +private: + CommandBuffer* m_commandBuffer; + RenderTargetIdentifier m_activeColorTarget; + RenderTargetIdentifier m_activeDepthTarget; +}; + +class LightManager { +public: + static LightManager& Get(); + + void Initialize(); + void Shutdown(); + + void UpdateLights( + Scene* scene, + CameraComponent* camera, + CullingResults& cullingResults + ); + + void GetMainLight(LightData& light); + void GetVisibleLights(std::vector& lights); + + int GetVisibleLightCount() const { return m_visibleLights.size(); } + bool HasShadows() const { return m_shadowsEnabled; } + + void SetShadowDistance(float distance) { m_shadowDistance = distance; } + float GetShadowDistance() const { return m_shadowDistance; } + +private: + std::vector m_visibleLights; + int m_mainLightIndex = -1; + + float m_shadowDistance = 50.0f; + bool m_shadowsEnabled = true; +}; + +class ShadowAtlas { +public: + ShadowAtlas(); + ~ShadowAtlas(); + + void Initialize(int width, int height); + void Shutdown(); + + void Update( + const std::vector& lights, + CullingResults& cullingResults + ); + + RenderTexture* GetTexture() const { return m_texture.get(); } + +private: + struct ShadowSlice { + int x, y; + int width, height; + bool allocated; + LightData* light; + }; + + std::unique_ptr m_texture; + int m_width = 0; + int m_height = 0; + + std::vector m_slices; +}; + +class Renderer { +public: + Renderer(); + ~Renderer(); + + void Initialize(); + void Shutdown(); + + void Render( + ScriptableRenderContext& context, + CameraComponent* camera, + CullingResults& cullingResults, + RenderQueue& renderQueue + ); + + void RenderOpaque( + ScriptableRenderContext& context, + CullingResults& cullingResults, + RenderQueue& renderQueue + ); + + void RenderTransparent( + ScriptableRenderContext& context, + CullingResults& cullingResults, + RenderQueue& renderQueue + ); + + void RenderShadowCasters( + ScriptableRenderContext& context, + CullingResults& cullingResults + ); + + void SortRenderers(RenderQueue& renderQueue); + +private: + void PrepareRenderQueueEntries( + RenderQueue& renderQueue, + CullingResults& cullingResults + ); + + Material* m_defaultMaterial = nullptr; +}; + +class RenderQueue { +public: + void Clear(); + void Sort(); + + void Add(const RenderItem& item); + void AddRange(const RenderItem* items, size_t count); + + const std::vector& GetOpaque() const { return m_opaque; } + const std::vector& GetTransparent() const { return m_transparent; } + const std::vector& GetAlphaTest() const { return m_alphaTest; } + + size_t GetOpaqueCount() const { return m_opaque.size(); } + size_t GetTransparentCount() const { return m_transparent.size(); } + size_t GetAlphaTestCount() const { return m_alphaTest.size(); } + +private: + std::vector m_opaque; + std::vector m_transparent; + std::vector m_alphaTest; +}; + +struct CameraData { + Matrix4x4 viewMatrix; + Matrix4x4 projectionMatrix; + Matrix4x4 viewProjectionMatrix; + Vector3 position; + Vector3 forward; + Vector3 up; + Vector3 right; + float nearPlane; + float farPlane; + float fov; + float aspectRatio; + Rect viewport; + bool isOrthographic; +}; + +struct RenderContextData { + CameraData camera; + std::vector lights; + std::vector visibleLights; + float deltaTime; + float time; + uint32_t frameIndex; + uint64_t frameCount; +}; + +class RenderPipeline { +public: + virtual ~RenderPipeline() = default; + + virtual void Initialize(const RenderPipelineDesc& desc); + virtual void Shutdown(); + + virtual void Prepare(RenderContextData& context); + virtual void Execute(RHI::ICommandList* cmdList, RenderContextData& context); + virtual void Present(); + + RenderQueue& GetRenderQueue() { return m_renderQueue; } + const RenderQueue& GetRenderQueue() const { return m_renderQueue; } + + RenderTexture* GetColorTarget() const { return m_colorTarget.get(); } + RenderTexture* GetDepthTarget() const { return m_depthTarget.get(); } + + virtual void Resize(uint32_t width, uint32_t height); + +protected: + virtual void CollectRenderItems(Scene* scene, CameraComponent* camera); + virtual void SortRenderItems(); + virtual void SetupLights(Scene* scene, CameraComponent* camera); + + RenderPipelineDesc m_desc; + RenderQueue m_renderQueue; + + std::unique_ptr m_colorTarget; + std::unique_ptr m_depthTarget; + + std::vector m_mainLightData; + std::vector m_additionalLightData; +}; + +class ForwardPipeline : public RenderPipeline { +public: + void Initialize(const RenderPipelineDesc& desc) override; + void Execute(RHI::ICommandList* cmdList, RenderContextData& context) override; + +private: + void RenderOpaque(RHI::ICommandList* cmdList, RenderContextData& context); + void RenderTransparent(RHI::ICommandList* cmdList, RenderContextData& context); + void RenderAlphaTest(RHI::ICommandList* cmdList, RenderContextData& context); + void RenderShadowMaps(RHI::ICommandList* cmdList); + void RenderLighting(RHI::ICommandList* cmdList, RenderContextData& context); +}; + +class DeferredPipeline : public RenderPipeline { +public: + void Initialize(const RenderPipelineDesc& desc) override; + void Execute(RHI::ICommandList* cmdList, RenderContextData& context) override; + +private: + void RenderGBuffer(RHI::ICommandList* cmdList); + void RenderDeferredLighting(RHI::ICommandList* cmdList); + void RenderDeferredShading(RHI::ICommandList* cmdList); + + std::unique_ptr m_gBufferAlbedo; + std::unique_ptr m_gBufferNormal; + std::unique_ptr m_gBufferMetallicRoughness; + std::unique_ptr m_gBufferDepth; +}; + +class RenderPipelineManager { +public: + static RenderPipelineManager& Get(); + + void Initialize(const RenderPipelineDesc& desc); + void Shutdown(); + + RenderPipeline* GetPipeline() const { return m_pipeline.get(); } + + void SetPipeline(std::unique_ptr pipeline); + void CreatePipeline(RenderPipelineType type); + + void Resize(uint32_t width, uint32_t height); + +private: + std::unique_ptr m_pipeline; + RenderPipelineDesc m_desc; +}; + +} // namespace Rendering +} // namespace XCEngine +``` + +--- + +## 第五章 目录结构(与 Unity 引擎目录结构一致) + +``` +XCVolumeRenderer/ +├── engine/ # 引擎核心库(静态库) +│ ├── CMakeLists.txt +│ ├── include/ +│ │ └── XCEngine/ +│ │ ├── Core/ # 核心基础 +│ │ │ ├── Assert.h +│ │ │ ├── Event.h +│ │ │ ├── TypeTraits.h +│ │ │ └── UniquePtr.h +│ │ ├── Math/ # 数学库(与 Unity Mathf 对应) +│ │ │ ├── Vector2.h +│ │ │ ├── Vector3.h +│ │ │ ├── Vector4.h +│ │ │ ├── Matrix3.h +│ │ │ ├── Matrix4.h +│ │ │ ├── Quaternion.h +│ │ │ ├── Transform.h +│ │ │ ├── Color.h +│ │ │ ├── Ray.h +│ │ │ ├── Plane.h +│ │ │ ├── Sphere.h +│ │ │ ├── Box.h +│ │ │ ├── Bounds.h +│ │ │ └── Frustum.h +│ │ ├── Containers/ # 容器(与 Unity Collections 对应) +│ │ │ ├── Array.h +│ │ │ ├── String.h +│ │ │ ├── StringView.h +│ │ │ └── HashMap.h +│ │ ├── Memory/ # 内存管理 +│ │ │ ├── Allocator.h +│ │ │ ├── LinearAllocator.h +│ │ │ ├── PoolAllocator.h +│ │ │ └── ProxyAllocator.h +│ │ ├── Threading/ # 线程系统 +│ │ │ ├── Thread.h +│ │ │ ├── Mutex.h +│ │ │ ├── SpinLock.h +│ │ │ ├── ReadWriteLock.h +│ │ │ ├── TaskSystem.h +│ │ │ └── Atomic.h +│ │ ├── Debug/ # 调试系统 +│ │ │ ├── Logger.h +│ │ │ ├── Profiler.h +│ │ │ └── LogSink.h +│ │ ├── Components/ # 组件系统(与 Unity Component 对应) +│ │ │ ├── GameObject.h +│ │ │ ├── Component.h +│ │ │ ├── ComponentTypeRegistry.h +│ │ │ ├── TransformComponent.h +│ │ │ ├── RenderMeshComponent.h +│ │ │ ├── LightComponent.h +│ │ │ └── CameraComponent.h +│ │ ├── Scene/ # 场景系统(与 Unity SceneManager 对应) +│ │ │ ├── Scene.h +│ │ │ ├── SceneManager.h +│ │ │ └── GameObjectBuilder.h +│ │ ├── Renderer/ # 渲染系统(核心,与 Unity Graphics/Shader 对应) +│ │ │ ├── Device.h # 设备抽象 +│ │ │ ├── Context.h # 渲染上下文 +│ │ │ ├── SwapChain.h # 交换链 +│ │ │ ├── Buffer.h # 缓冲区 +│ │ │ ├── Texture.h # 纹理 +│ │ │ ├── Shader.h # Shader +│ │ │ ├── Material.h # 材质 +│ │ │ ├── Pipeline.h # 渲染管线 +│ │ │ ├── RenderPass.h # 渲染通道 +│ │ │ ├── CommandList.h # 命令列表 +│ │ │ ├── RenderQueue.h # 渲染队列 +│ │ │ ├── RenderPipeline.h # 渲染管线基类 +│ │ │ ├── CullingSystem.h # 剔除系统 +│ │ │ ├── Renderer.h # 渲染器 +│ │ │ ├── LightManager.h # 光照管理器 +│ │ │ ├── ShadowAtlas.h # 阴影图集 +│ │ │ └── RenderModule.h # 渲染模块 +│ │ ├── RHI/ # 渲染硬件抽象层(与 Unity GraphicsDevice 对应) +│ │ │ ├── RHISystem.h # RHI 系统入口 +│ │ │ ├── RHIDevice.h # 抽象设备接口 +│ │ │ ├── CommandQueue.h # 命令队列 +│ │ │ ├── CommandList.h # 命令列表 +│ │ │ ├── CommandAllocator.h # 命令分配器 +│ │ │ ├── Fence.h # 同步围栏 +│ │ │ ├── DescriptorHeap.h # 描述符堆 +│ │ │ ├── QueryHeap.h # 查询堆 +│ │ │ ├── RootSignature.h # 根签名 +│ │ │ ├── PipelineState.h # 管线状态 +│ │ │ ├── Sampler.h # 采样器 +│ │ │ └── SwapChain.h # 交换链 +│ │ ├── RHI/D3D12/ # D3D12 后端实现 +│ │ │ ├── D3D12Device.h +│ │ │ ├── D3D12CommandList.h +│ │ │ ├── D3D12CommandQueue.h +│ │ │ ├── D3D12DescriptorHeap.h +│ │ │ ├── D3D12RootSignature.h +│ │ │ ├── D3D12PipelineState.h +│ │ │ ├── D3D12Fence.h +│ │ │ ├── D3D12QueryHeap.h +│ │ │ ├── D3D12SwapChain.h +│ │ │ ├── D3D12Texture.h +│ │ │ └── D3D12Buffer.h +│ │ └── XCEngine.h # 主头文件 +│ ├── src/ +│ │ ├── Core/ +│ │ ├── Math/ +│ │ ├── Components/ +│ │ ├── Scene/ +│ │ ├── Renderer/ +│ │ ├── RHI/ +│ │ │ ├── CMakeLists.txt +│ │ │ └── D3D12/ +│ │ └── ... +│ └── third_party/ +│ ├── DirectX/ # DirectX SDK +│ ├── stb/ # stb 图像库 +│ ├── json/ # json 解析库 +│ └── NanoVDB/ # NanoVDB 体积渲染库 +│ +├── runtime/ # 游戏运行时(独立可执行文件) +│ ├── CMakeLists.txt +│ ├── src/ +│ │ ├── GameMain.cpp +│ │ ├── EntryPoint.cpp +│ │ └── Application.cpp +│ └── resources/ +│ ├── Shaders/ +│ ├── Models/ +│ ├── Textures/ +│ └── Volumes/ +│ +├── tools/ # 工具链 +│ ├── ShaderCompiler/ # 着色器编译器 +│ │ ├── ShaderCompiler.h +│ │ └── ShaderCompileOptions.h +│ ├── BuildTool/ # 构建打包工具 +│ └── AssetProcessor/ # 资源处理工具 +│ +├── content/ # 资源内容 +│ ├── Assets/ # 资源文件夹 +│ ├── Scenes/ # 场景文件 +│ └── Packages/ # 资源包 +│ +└── projects/ # 项目文件夹 + └── .. + +# Unity 引擎目录结构对照 +# Unity/Editor/Data/Resources/ +# Unity/Editor/Data/Modules/ +# Unity/Editor/Data/Tools/ +``` + +--- + +## 附录:待扩展功能 + +以下功能将在后续迭代中添加: + +1. **物理系统** - RigidBodyComponent, ColliderComponent, PhysicsWorld +2. **音频系统** - AudioSourceComponent, AudioListenerComponent, AudioEngine +3. **动画系统** - AnimatorComponent, AnimationClip, Skeleton +4. **粒子系统** - ParticleSystemComponent, GPUParticles +5. **UI系统** - CanvasComponent, ImageComponent, TextComponent, ButtonComponent +6. **网络系统** - NetworkIdentityComponent, NetworkTransformComponent +7. **完整编辑器** - HierarchyPanel, InspectorPanel, SceneViewPanel diff --git a/docs/XCVolumeRenderer渲染引擎架构设计.md b/docs/XCVolumeRenderer渲染引擎架构设计.md deleted file mode 100644 index 799315e4..00000000 --- a/docs/XCVolumeRenderer渲染引擎架构设计.md +++ /dev/null @@ -1,1845 +0,0 @@ -# XCVolumeRenderer - 渲染引擎架构设计文档 - -> **基于 XCGameEngine 架构提取的渲染引擎子集** -> 版本: 1.0 -> 日期: 2026-03-13 -> 目标: 构建专业级实时体积渲染引擎 - ---- - -## 第一章 核心基础层 - -### 1.1 数学库 (Math Library) - -```cpp -namespace XCEngine { -namespace Math { - -struct Vector2 { - float x, y; - - static Vector2 Zero() { return Vector2{0, 0}; } - static Vector2 One() { return Vector2{1, 1}; } - static Vector2 Up() { return Vector2{0, 1}; } - static Vector2 Down() { return Vector2{0, -1}; } - static Vector2 Right() { return Vector2{1, 0}; } - static Vector2 Left() { return Vector2{-1, 0}; } - - static float Dot(const Vector2& a, const Vector2& b); - static float Cross(const Vector2& a, const Vector2& b); - static Vector2 Normalize(const Vector2& v); - static float Magnitude(const Vector2& v); - static Vector2 Lerp(const Vector2& a, const Vector2& b, float t); -}; - -struct Vector3 { - float x, y, z; - - static Vector3 Zero() { return Vector3{0, 0, 0}; } - static Vector3 One() { return Vector3{1, 1, 1}; } - static Vector3 Forward() { return Vector3{0, 0, 1}; } - static Vector3 Back() { return Vector3{0, 0, -1}; } - static Vector3 Up() { return Vector3{0, 1, 0}; } - static Vector3 Down() { return Vector3{0, -1, 0}; } - static Vector3 Right() { return Vector3{1, 0, 0}; } - static Vector3 Left() { return Vector3{-1, 0, 0}; } - - static Vector3 Cross(const Vector3& a, const Vector3& b); - static float Dot(const Vector3& a, const Vector3& b); - static Vector3 Normalize(const Vector3& v); - static float Magnitude(const Vector3& v); - static float SqrMagnitude(const Vector3& v); - static Vector3 Lerp(const Vector3& a, const Vector3& b, float t); - static Vector3 MoveTowards(const Vector3& current, const Vector3& target, float maxDistance); -}; - -struct Vector4 { float x, y, z, w; }; - -struct Matrix3x3; -struct Matrix4x4; - -struct Quaternion { - float x, y, z, w; - - static Quaternion Identity() { return Quaternion{0, 0, 0, 1}; } - - static Quaternion FromAxisAngle(const Vector3& axis, float radians); - static Quaternion FromEulerAngles(float pitch, float yaw, float roll); - static Quaternion FromRotationMatrix(const Matrix4x4& matrix); - static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float t); - static Quaternion LookRotation(const Vector3& forward, const Vector3& up = Vector3::Up()); - - Vector3 ToEulerAngles() const; - Matrix4x4 ToMatrix4x4() const; - Vector3 operator*(const Vector3& v) const; - - Quaternion operator*(const Quaternion& other) const; - Quaternion Inverse() const; - float Dot(const Quaternion& other) const; -}; - -struct Transform { - Vector3 position = Vector3::Zero(); - Quaternion rotation = Quaternion::Identity(); - Vector3 scale = Vector3::One(); - - Matrix4x4 ToMatrix() const; - Transform Inverse() const; - Transform operator*(const Transform& other) const; - Vector3 TransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; -}; - -enum class Space { - Self, - World -}; - -struct Color { - float r, g, b, a; - - static Color White() { return Color{1, 1, 1, 1}; } - static Color Black() { return Color{0, 0, 0, 1}; } - static Color Red() { return Color{1, 0, 0, 1}; } - static Color Green() { return Color{0, 1, 0, 1}; } - static Color Blue() { return Color{0, 0, 1, 1}; } - static Color Yellow() { return Color{1, 1, 0, 1}; } - static Color Cyan() { return Color{0, 1, 1, 1}; } - static Color Magenta() { return Color{1, 0, 1, 1}; } - static Color Clear() { return Color{0, 0, 0, 0}; } - - static Color Lerp(const Color& a, const Color& b, float t); -}; - -struct Rect { - float x, y, width, height; - - Rect() : x(0), y(0), width(0), height(0) {} - Rect(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} - - float GetLeft() const { return x; } - float GetRight() const { return x + width; } - float GetTop() const { return y; } - float GetBottom() const { return y + height; } - - Vector2 GetPosition() const { return Vector2{x, y}; } - Vector2 GetSize() const { return Vector2{width, height}; } - - bool Contains(float px, float py) const { - return px >= x && px < x + width && py >= y && py < y + height; - } -}; - -struct RectInt { - int32_t x, y, width, height; - - RectInt() : x(0), y(0), width(0), height(0) {} - RectInt(int32_t x, int32_t y, int32_t w, int32_t h) : x(x), y(y), width(w), height(h) {} -}; - -struct Viewport { - float x, y, width, height; - float minDepth = 0.0f; - float maxDepth = 1.0f; - - Viewport() : x(0), y(0), width(0), height(0) {} - Viewport(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} -}; - -struct Ray { - Vector3 origin; - Vector3 direction; - - Vector3 GetPoint(float t) const; - bool Intersects(const Sphere& sphere, float& t) const; - bool Intersects(const Box& box, float& t) const; - bool Intersects(const Plane& plane, float& t) const; -}; - -struct Sphere { Vector3 center; float radius; }; -struct Box { Vector3 center; Vector3 extents; Matrix4x4 transform; }; -struct Plane { Vector3 normal; float distance; }; -struct Frustum; -struct OBB; -struct AABB; - -} // namespace Math -} // namespace XCEngine -``` - -### 1.2 内存管理 (Memory Management) - -```cpp -namespace XCEngine { -namespace Memory { - -class IAllocator { -public: - virtual ~IAllocator() = default; - - virtual void* Allocate(size_t size, size_t alignment = 0) = 0; - virtual void Free(void* ptr) = 0; - virtual void* Reallocate(void* ptr, size_t newSize) = 0; - - virtual size_t GetTotalAllocated() const = 0; - virtual size_t GetTotalFreed() const = 0; - virtual size_t GetPeakAllocated() const = 0; - virtual size_t GetAllocationCount() const = 0; - - virtual const char* GetName() const = 0; -}; - -class LinearAllocator : public IAllocator { -public: - explicit LinearAllocator(size_t size, IAllocator* parent = nullptr); - ~LinearAllocator(); - - void* Allocate(size_t size, size_t alignment = 8) override; - void Free(void* ptr) override; - void Clear(); - - void* GetMarker() const; - void SetMarker(void* marker); - -private: - byte* m_buffer = nullptr; - size_t m_capacity = 0; - size_t m_offset = 0; - IAllocator* m_parent = nullptr; -}; - -class PoolAllocator : public IAllocator { -public: - PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment = 8); - ~PoolAllocator(); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - - bool Contains(void* ptr) const; - size_t GetBlockSize() const { return m_blockSize; } - size_t GetFreeBlockCount() const; - -private: - struct FreeNode { - FreeNode* next; - }; - - size_t m_blockSize = 0; - size_t m_alignment = 0; - void* m_memory = nullptr; - FreeNode* m_freeList = nullptr; - size_t m_totalBlocks = 0; - size_t m_freeBlocks = 0; -}; - -class ProxyAllocator : public IAllocator { -public: - ProxyAllocator(IAllocator* underlying, const char* name); - - void* Allocate(size_t size, size_t alignment = 0) override; - void Free(void* ptr) override; - void* Reallocate(void* ptr, size_t newSize) override; - - struct Stats { - size_t totalAllocated; - size_t totalFreed; - size_t peakAllocated; - size_t allocationCount; - size_t memoryOverhead; - }; - const Stats& GetStats() const; - -private: - IAllocator* m_underlying; - const char* m_name; - Stats m_stats; - Mutex m_mutex; -}; - -class MemoryManager { -public: - static MemoryManager& Get(); - - void Initialize(); - void Shutdown(); - - IAllocator* GetSystemAllocator(); - - std::unique_ptr CreateLinearAllocator(size_t size); - std::unique_ptr CreatePoolAllocator(size_t blockSize, size_t count); - std::unique_ptr CreateProxyAllocator(const char* name); - - void SetTrackAllocations(bool track); - void DumpMemoryLeaks(); - void GenerateMemoryReport(); -}; - -#define XE_ALLOC(allocator, size, ...) allocator->Allocate(size, ##__VA_ARGS__) -#define XE_FREE(allocator, ptr) allocator->Free(ptr) - -} // namespace Memory -} // namespace XCEngine -``` - -### 1.3 容器库 (Containers) - -```cpp -namespace XCEngine { -namespace Containers { - -template -class Array { -public: - using Iterator = T*; - using ConstIterator = const T*; - - Array() = default; - explicit Array(size_t capacity); - Array(size_t count, const T& value); - Array(std::initializer_list init); - ~Array(); - - Array(const Array& other); - Array(Array&& other) noexcept; - Array& operator=(const Array& other); - Array& operator=(Array&& other) noexcept; - - T& operator[](size_t index); - const T& operator[](size_t index) const; - - T* Data() { return m_data; } - const T* Data() const { return m_data; } - - size_t Size() const { return m_size; } - size_t Capacity() const { return m_capacity; } - - void Clear(); - void PushBack(const T& value); - void PushBack(T&& value); - template - T& EmplaceBack(Args&&... args); - void PopBack(); - -private: - T* m_data = nullptr; - size_t m_size = 0; - size_t m_capacity = 0; - IAllocator* m_allocator = nullptr; -}; - -class String { -public: - String(); - String(const char* str); - String(const char* str, size_t len); - ~String(); - - String& operator+=(const String& other); - String& operator+=(const char* str); - - String Substring(size_t pos, size_t len = npos) const; - String Trim() const; - String ToLower() const; - String ToUpper() const; - - size_t Find(const char* str, size_t pos = 0) const; - bool StartsWith(const String& prefix) const; - bool EndsWith(const String& suffix) const; - - const char* CStr() const { return m_data; } - size_t Length() const { return m_length; } - -private: - char* m_data = nullptr; - size_t m_length = 0; - size_t m_capacity = 0; -}; - -template -class HashMap { -public: - struct Pair { - Key first; - Value second; - }; - - HashMap() = default; - explicit HashMap(size_t bucketCount, IAllocator* allocator = nullptr); - - Value& operator[](const Key& key); - Value* Find(const Key& key); - const Value* Find(const Key& key) const; - bool Contains(const Key& key) const; - - bool Insert(const Key& key, const Value& value); - bool Erase(const Key& key); - void Clear(); - - size_t Size() const { return m_size; } - -private: - size_t GetBucketIndex(const Key& key) const; - void Resize(); - - struct Bucket { - Array pairs; - }; - Array m_buckets; - size_t m_bucketCount = 0; - size_t m_size = 0; - float m_loadFactor = 0.75f; - IAllocator* m_allocator = nullptr; -}; - -} // namespace Containers -} // namespace XCEngine -``` - -### 1.4 线程系统 (Threading) - -```cpp -namespace XCEngine { -namespace Threading { - -enum class TaskPriority : uint8_t { - Critical = 0, - High = 1, - Normal = 2, - Low = 3, - Idle = 4 -}; - -enum class TaskStatus : uint8_t { - Pending, - Scheduled, - Running, - Completed, - Failed, - Canceled -}; - -template -using Func = std::function; - -class ITask { -public: - virtual ~ITask() = default; - - virtual void Execute() = 0; - virtual void OnComplete() {} - virtual void OnCancel() {} - - TaskPriority GetPriority() const { return m_priority; } - TaskStatus GetStatus() const { return m_status; } - uint64 GetId() const { return m_id; } - -protected: - TaskPriority m_priority = TaskPriority::Normal; - TaskStatus m_status = TaskStatus::Pending; - uint64 m_id = 0; - std::atomic m_refCount{1}; -}; - -template -class LambdaTask : public ITask { -public: - explicit LambdaTask(Func&& func, TaskPriority priority = TaskPriority::Normal) - : m_func(std::move(func)), m_priority(priority) {} - - void Execute() override { - m_func(); - } - -private: - Func m_func; -}; - -class TaskGroup { -public: - using Callback = std::function; - - TaskGroup(); - ~TaskGroup(); - - uint64 AddTask(std::unique_ptr task); - uint64 AddTask(Func&& func, TaskPriority priority = TaskPriority::Normal); - - void AddDependency(uint64 taskId, uint64 dependsOn); - void Wait(); - bool WaitFor(std::chrono::milliseconds timeout); - - void SetCompleteCallback(Callback&& callback); - bool IsComplete() const; - float GetProgress() const; -}; - -class TaskSystem { -public: - static TaskSystem& Get(); - - void Initialize(const TaskSystemConfig& config); - void Shutdown(); - - uint64 Submit(std::unique_ptr task); - uint64 Submit(Func&& func, TaskPriority priority = TaskPriority::Normal); - - TaskGroup* CreateTaskGroup(); - void DestroyTaskGroup(TaskGroup* group); - - void Wait(uint64 taskId); - uint32 GetWorkerThreadCount() const; - - void Update(); - - template - void ParallelFor(int32 start, int32 end, Func&& func); - - void RunOnMainThread(Func&& func); -}; - -} // namespace Threading -} // namespace XCEngine -``` - -### 1.5 日志与调试系统 - -```cpp -namespace XCEngine { -namespace Core { - -template -class Event { -public: - using Callback = std::function; - using Listener = std::pair; - using Iterator = typename std::vector::iterator; - - uint64_t Subscribe(Callback callback) { - std::lock_guard lock(m_mutex); - uint64_t id = ++m_nextId; - m_listeners.emplace_back(id, std::move(callback)); - return id; - } - - void Unsubscribe(uint64_t id) { - std::lock_guard lock(m_mutex); - m_pendingUnsubscribes.push_back(id); - } - - void ProcessUnsubscribes() { - std::lock_guard lock(m_mutex); - for (uint64_t id : m_pendingUnsubscribes) { - m_listeners.erase( - std::remove_if(m_listeners.begin(), m_listeners.end(), - [id](const auto& pair) { return pair.first == id; }), - m_listeners.end() - ); - } - m_pendingUnsubscribes.clear(); - } - - void Invoke(Args... args) const { - if (!m_pendingUnsubscribes.empty()) { - std::vector listenersCopy; - { - std::lock_guard lock(m_mutex); - listenersCopy = m_listeners; - m_pendingUnsubscribes.clear(); - } - for (const auto& [id, callback] : listenersCopy) { - callback(args...); - } - } else { - for (const auto& [id, callback] : m_listeners) { - callback(args...); - } - } - } - - void Clear() { - std::lock_guard lock(m_mutex); - m_listeners.clear(); - } - - Iterator begin() { return m_listeners.begin(); } - Iterator end() { return m_listeners.end(); } - -private: - mutable std::mutex m_mutex; - std::vector m_listeners; - std::vector m_pendingUnsubscribes; - uint64_t m_nextId = 0; -}; - -using int8 = int8_t; -using int16 = int16_t; -using int32 = int32_t; -using int64 = int64_t; -using uint8 = uint8_t; -using uint16 = uint16_t; -using uint32 = uint32_t; -using uint64 = uint64_t; -using byte = uint8_t; - -class RefCounted { -public: - RefCounted() : m_refCount(1) {} - virtual ~RefCounted() = default; - - void AddRef() { ++m_refCount; } - void Release() { - if (--m_refCount == 0) { - delete this; - } - } - - uint32_t GetRefCount() const { return m_refCount.load(); } - -protected: - std::atomic m_refCount; -}; - -template -using Ref = std::shared_ptr; - -template -using UniqueRef = std::unique_ptr; - -} // namespace Core -} // namespace XCEngine - -namespace XCEngine { -namespace Debug { - -enum class LogLevel : uint8_t { - Verbose = 0, - Debug = 1, - Info = 2, - Warning = 3, - Error = 4, - Fatal = 5 -}; - -enum class LogCategory { - General, - Rendering, - Physics, - Audio, - Scripting, - Network, - Memory, - Threading, - FileSystem, - Custom -}; - -struct LogEntry { - LogLevel level; - LogCategory category; - String message; - String file; - int32 line; - String function; - uint64 timestamp; - uint32 threadId; -}; - -class ILogSink { -public: - virtual ~ILogSink() = default; - virtual void Log(const LogEntry& entry) = 0; - virtual void Flush() = 0; -}; - -class ConsoleLogSink : public ILogSink { -public: - void Log(const LogEntry& entry) override; - void Flush() override; - void SetColorOutput(bool enable); - void SetMinimumLevel(LogLevel level); -}; - -class FileLogSink : public ILogSink { -public: - explicit FileLogSink(const String& filePath); - ~FileLogSink(); - void Log(const LogEntry& entry) override; - void Flush() override; -private: - String m_filePath; - FileWriter m_writer; -}; - -class Logger { -public: - static Logger& Get(); - - void Initialize(); - void Shutdown(); - - void AddSink(std::unique_ptr sink); - void RemoveSink(ILogSink* sink); - - void Log(LogLevel level, LogCategory category, - const String& message, const char* file = nullptr, - int32 line = 0, const char* function = nullptr); - - void Verbose(LogCategory category, const String& message); - void Debug(LogCategory category, const String& message); - void Info(LogCategory category, const String& message); - void Warning(LogCategory category, const String& message); - void Error(LogCategory category, const String& message); - void Fatal(LogCategory category, const String& message); - - void SetMinimumLevel(LogLevel level); - void SetCategoryEnabled(LogCategory category, bool enabled); -}; - -#define XE_LOG(category, level, message) \ - XCEngine::Debug::Logger::Get().Log(level, category, message, __FILE__, __LINE__, __FUNCTION__) - -#define XE_ASSERT(condition, message) \ - if (!(condition)) { \ - XCEngine::Debug::Logger::Get().Fatal(XCEngine::Debug::LogCategory::General, message); \ - __debugbreak(); \ - } - -class Profiler { -public: - static Profiler& Get(); - - void Initialize(); - void Shutdown(); - - void BeginProfile(const char* name); - void EndProfile(); - - void BeginFrame(); - void EndFrame(); - - void MarkEvent(const char* name, uint64_t timestamp, uint32_t threadId); - void SetMarker(const char* name, uint32_t color); - - void ExportChromeTracing(const String& filePath); -}; - -#define XE_PROFILE_BEGIN(name) XCEngine::Debug::Profiler::Get().BeginProfile(name) -#define XE_PROFILE_END() XCEngine::Debug::Profiler::Get().EndProfile() -#define XE_PROFILE_FUNCTION() XE_PROFILE_BEGIN(__FUNCTION__) - -} // namespace Debug -} // namespace XCEngine -``` - ---- - -## 第二章 组件系统 - -### 2.1 组件基类 - -```cpp -namespace XCEngine { - -class Scene; -class GameObject; -class TransformComponent; - -// 组件类型注册(用于运行时类型识别) -class ComponentTypeRegistry { -public: - static ComponentTypeRegistry& Get(); - - template - static uint32_t GetTypeId() { - static uint32_t id = Get().Register(typeid(T).name(), static_cast(-1)); - return id; - } - - template - static uint32_t GetTypeId(const char* typeName) { - static uint32_t id = Get().Register(typeName, static_cast(-1)); - return id; - } - - static uint32_t GetTypeIdFromName(const char* typeName) { - return Get().GetIdByName(typeName); - } - - static const char* GetTypeName(uint32_t typeId) { - return Get().GetNameById(typeId); - } - -private: - uint32_t Register(const char* typeName, uint32_t suggestedId); - uint32_t GetIdByName(const char* typeName) const; - const char* GetNameById(uint32_t typeId) const; - - std::atomic m_nextTypeId{0}; - std::unordered_map m_idToName; - std::unordered_map m_nameToId; - Mutex m_mutex; -}; - -// 组件基类(类Unity MonoBehaviour) -class Component { -public: - Component(); - virtual ~Component(); - - GameObject* gameObject() const { return m_gameObject; } - TransformComponent& transform() const { return m_gameObject->GetTransform(); } - - bool IsEnabled() const { return m_enabled; } - void SetEnabled(bool enabled) { m_enabled = enabled; } - - Scene* GetScene() const; - - template - T* GetComponent() const { return m_gameObject->GetComponent(); } - - template - std::vector GetComponents() const { return m_gameObject->GetComponents(); } - - virtual void Awake() {} - virtual void Start() {} - virtual void Update(float deltaTime) {} - virtual void FixedUpdate() {} - virtual void LateUpdate(float deltaTime) {} - virtual void OnDestroy() {} - virtual void OnEnable() {} - virtual void OnDisable() {} - -protected: - GameObject* m_gameObject = nullptr; - bool m_enabled = true; - - friend class GameObject; -}; - -} // namespace XCEngine -``` - -### 2.2 Transform组件 - -```cpp -namespace XCEngine { - -class TransformComponent : public Component { -public: - Vector3 GetLocalPosition() const { return m_localPosition; } - void SetLocalPosition(const Vector3& position) { m_localPosition = position; SetDirty(); } - - Quaternion GetLocalRotation() const { return m_localRotation; } - void SetLocalRotation(const Quaternion& rotation) { m_localRotation = rotation; SetDirty(); } - - Vector3 GetLocalScale() const { return m_localScale; } - void SetLocalScale(const Vector3& scale) { m_localScale = scale; SetDirty(); } - - Vector3 GetPosition() const; - void SetPosition(const Vector3& position); - - Quaternion GetRotation() const; - void SetRotation(const Quaternion& rotation); - - Vector3 GetScale() const; - void SetScale(const Vector3& scale); - - Vector3 GetForward() const { return GetRotation() * Vector3::Forward(); } - Vector3 GetRight() const { return GetRotation() * Vector3::Right(); } - Vector3 GetUp() const { return GetRotation() * Vector3::Up(); } - - const Matrix4x4& GetLocalToWorldMatrix() const; - Matrix4x4 GetWorldToLocalMatrix() const; - - TransformComponent* GetParent() const { return m_parent; } - void SetParent(TransformComponent* parent, bool worldPositionStays = true); - - int GetChildCount() const { return static_cast(m_children.size()); } - TransformComponent* GetChild(int index) const; - TransformComponent* Find(const String& name) const; - - void DetachChildren(); - void SetAsFirstSibling(); - void SetAsLastSibling(); - void SetSiblingIndex(int index); - int GetSiblingIndex() const; - - void LookAt(const Vector3& target); - void LookAt(const Vector3& target, const Vector3& up); - void Rotate(const Vector3& eulers); - void Rotate(const Vector3& axis, float angle); - void Translate(const Vector3& translation); - void Translate(const Vector3& translation, Math::Space relativeTo); - - Vector3 TransformPoint(const Vector3& point) const; - Vector3 InverseTransformPoint(const Vector3& point) const; - Vector3 TransformDirection(const Vector3& direction) const; - Vector3 InverseTransformDirection(const Vector3& direction) const; - - void SetDirty() { m_dirty = true; } - -private: - Vector3 m_localPosition = Vector3::Zero(); - Quaternion m_localRotation = Quaternion::Identity(); - Vector3 m_localScale = Vector3::One(); - - TransformComponent* m_parent = nullptr; - std::vector m_children; - - mutable Matrix4x4 m_localToWorldMatrix; - mutable Matrix4x4 m_worldToLocalMatrix; - mutable Vector3 m_worldPosition; - mutable Quaternion m_worldRotation; - mutable Vector3 m_worldScale; - mutable bool m_dirty = true; - - void UpdateWorldTransform() const; - - friend class GameObject; -}; - -} // namespace XCEngine -``` - -### 2.3 GameObject - -```cpp -namespace XCEngine { - -class Scene; - -class GameObject { -public: - GameObject(); - ~GameObject(); - - struct ConstructParams { - String name = "GameObject"; - GameObject* parent = nullptr; - bool active = true; - }; - - static GameObject* Create(const ConstructParams& params = {}); - static void Destroy(GameObject* obj); - - String name; - bool active = true; - - TransformComponent& GetTransform() { return m_transform; } - const TransformComponent& GetTransform() const { return m_transform; } - - Scene* GetScene() const { return m_scene; } - - template - T* AddComponent(); - - template - void RemoveComponent(); - - template - T* GetComponent() const; - - template - std::vector GetComponents() const; - - template - T* GetComponentInChildren() const; - - template - std::vector GetComponentsInChildren() const; - - template - T* GetComponentInParent() const; - - GameObject* GetParent() const; - void SetParent(GameObject* parent); - void SetParent(GameObject* parent, bool worldPositionStays); - const std::vector& GetChildren() const; - GameObject* GetChild(int index) const; - int GetChildCount() const; - int GetSiblingIndex() const; - void SetSiblingIndex(int index); - - void SetActive(bool active); - bool IsActive() const; - bool IsActiveInHierarchy() const; - - static GameObject* Find(const String& name); - static std::vector FindObjectsOfType(); - static std::vector FindGameObjectsWithTag(const String& tag); - - void Destroy(); - -private: - void AddComponentInternal(Component* component); - void RemoveComponentInternal(Component* component); - - std::vector> m_components; - std::unordered_map m_componentTypeIndex; - - std::vector m_children; - GameObject* m_parent = nullptr; - Scene* m_scene = nullptr; - - TransformComponent m_transform; - - void Initialize(const ConstructParams& params); - - friend class Scene; - friend class TransformComponent; -}; - -inline GameObject::GameObject() { -} - -inline GameObject::~GameObject() { - for (auto* child : m_children) { - delete child; - } -} - -inline GameObject* GameObject::Create(const ConstructParams& params) { - GameObject* obj = new GameObject(); - obj->Initialize(params); - return obj; -} - -inline void GameObject::Destroy(GameObject* obj) { - delete obj; -} - -inline void GameObject::Initialize(const ConstructParams& params) { - name = params.name; - active = params.active; - - m_transform.m_gameObject = this; - - if (params.parent) { - params.parent->m_children.push_back(this); - m_parent = params.parent; - } -} - -inline void GameObject::Destroy() { - if (m_scene) { - m_scene->DestroyGameObject(this); - } else { - delete this; - } -} - -inline GameObject* GameObject::Find(const String& name) { - return SceneManager::Get().GetActiveScene()->Find(name); -} - -inline std::vector GameObject::FindObjectsOfType() { - return SceneManager::Get().GetActiveScene()->FindObjectsOfType(); -} - -inline std::vector GameObject::FindGameObjectsWithTag(const String& tag) { - return SceneManager::Get().GetActiveScene()->FindGameObjectsWithTag(tag); -} - -} // namespace XCEngine -``` - ---- - -## 第三章 场景系统 - -### 3.1 Scene - -```cpp -namespace XCEngine { - -class Scene { -public: - Scene(); - ~Scene(); - - const String& GetName() const { return m_name; } - void SetName(const String& name) { m_name = name; } - - GameObject* CreateGameObject(const String& name = "GameObject") { - GameObject::ConstructParams params; - params.name = name; - params.parent = nullptr; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - GameObject* CreateGameObject(const String& name, GameObject* parent) { - GameObject::ConstructParams params; - params.name = name; - params.parent = parent; - params.active = true; - - GameObject* obj = GameObject::Create(params); - obj->m_scene = this; - AddGameObject(obj); - return obj; - } - - void DestroyGameObject(GameObject* obj); - - std::vector GetRootGameObjects() const { - std::vector roots; - roots.reserve(m_gameObjects.size() / 2); - for (auto& obj : m_gameObjects) { - if (obj->GetParent() == nullptr) { - roots.push_back(obj.get()); - } - } - return roots; - } - - GameObject* Find(const String& name) const; - GameObject* FindGameObjectWithTag(const String& tag) const; - std::vector FindGameObjectsWithTag(const String& tag) const; - template - std::vector FindObjectsOfType() const; - T* FindObjectOfType() const; - - bool IsActive() const { return m_isActive; } - void SetActive(bool active); - - void Load(const String& filePath); - void LoadAsync(const String& filePath, std::function callback); - void Save(const String& filePath); - - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - void DebugDraw(); - int GetObjectCount() const { return m_gameObjects.size(); } - -private: - void AddGameObject(GameObject* obj); - void RemoveGameObject(GameObject* obj); - - String m_name; - std::vector> m_gameObjects; - bool m_isActive = true; - - friend class GameObject; - friend class SceneManager; -}; - -class SceneManager { -public: - static SceneManager& Get(); - - void Initialize(); - void Shutdown(); - - Scene* CreateScene(const String& name); - void LoadScene(const String& filePath); - void LoadSceneAsync(const String& filePath, std::function callback); - void UnloadScene(Scene* scene); - void UnloadScene(const String& sceneName); - - void SetActiveScene(Scene* scene); - void SetActiveScene(const String& sceneName); - Scene* GetActiveScene() const; - - Scene* GetScene(const String& name) const; - std::vector GetAllScenes() const; - - void Update(float deltaTime); - void FixedUpdate(float fixedDeltaTime); - void LateUpdate(float deltaTime); - - Event OnSceneLoaded; - Event OnSceneUnloaded; - Event OnActiveSceneChanged; - -private: - SceneManager() = default; - - std::vector> m_scenes; - Scene* m_activeScene = nullptr; - Scene* m_loadingScene = nullptr; - - std::unordered_map m_sceneNameMap; - std::function m_loadCallback; - bool m_loading = false; -}; - -// GameObject创建辅助类 -class GameObjectBuilder { -public: - explicit GameObjectBuilder(const String& name = "GameObject"); - ~GameObjectBuilder() = default; - - template - GameObjectBuilder& AddComponent(Args&&... args); - - GameObject* Build(); - -private: - String m_name; - std::vector> m_components; -}; - -} // namespace XCEngine -``` - ---- - -## 第四章 渲染系统 - -### 4.1 渲染抽象层 - -```cpp -namespace XCEngine { -namespace Rendering { - -enum class ResourceState { - Undefined, - RenderTarget, - DepthStencil, - ShaderResource, - UnorderedAccess, - CopySrc, - CopyDst, - Present -}; - -struct ResourceDesc { - ResourceType type; - uint32_t width = 1; - uint32_t height = 1; - uint32_t depth = 1; - uint32_t mipLevels = 1; - uint32_t arraySize = 1; - Format format = Format::Unknown; - SampleCount sampleCount = SampleCount::Count1; - ResourceState initialState = ResourceState::Undefined; - bool cpuAccessible = false; - bool randomAccess = false; - - MemoryUsage memoryUsage = MemoryUsage::Default; - - String name; -}; - -class IRenderDevice { -public: - virtual ~IRenderDevice() = default; - - virtual const char* GetApiName() const = 0; - virtual const char* GetDriverVersion() const = 0; - virtual uint64_t GetDeviceId() const = 0; - virtual uint64_t GetVendorId() const = 0; - - virtual bool SupportsRaytracing() const = 0; - virtual bool SupportsMeshShaders() const = 0; - virtual bool SupportsVariableRateShading() const = 0; - virtual bool SupportsSamplerFeedback() const = 0; - virtual uint32_t GetMaxTextureSize() const = 0; - virtual uint32_t GetMaxUBOSize() const = 0; - virtual uint32_t GetMaxMSAA() const = 0; - - virtual std::unique_ptr CreateTexture(const ResourceDesc& desc) = 0; - virtual std::unique_ptr CreateBuffer(const ResourceDesc& desc, const void* initialData = nullptr) = 0; - virtual std::unique_ptr CreateShader(const ShaderDesc& desc) = 0; - virtual std::unique_ptr CreatePipeline(const PipelineDesc& desc) = 0; - virtual std::unique_ptr CreateRenderPass(const RenderPassDesc& desc) = 0; - - virtual ICommandQueue* GetGraphicsQueue() = 0; - virtual ICommandQueue* GetComputeQueue() = 0; - virtual ICommandQueue* GetCopyQueue() = 0; - - virtual std::unique_ptr CreateFence() = 0; - virtual IDescriptorHeap* CreateDescriptorHeap(const DescriptorHeapDesc& desc) = 0; - virtual std::unique_ptr CreateQueryHeap(const QueryHeapDesc& desc) = 0; -}; - -class ICommandList { -public: - virtual ~ICommandList() = default; - - virtual void TransitionBarrier(const IResource* resource, ResourceState newState) = 0; - virtual void UAVBarrier(const IResource* resource) = 0; - virtual void FlushBarriers() = 0; - - virtual void SetPipeline(const IPipeline* pipeline) = 0; - virtual void SetVertexBuffer(uint32_t slot, const IBuffer* buffer, uint64_t offset = 0) = 0; - virtual void SetIndexBuffer(const IBuffer* buffer, IndexType indexType, uint64_t offset = 0) = 0; - virtual void SetConstantBuffer(uint32_t slot, const IBuffer* buffer, uint64_t offset = 0) = 0; - virtual void SetShaderResource(uint32_t slot, const IResource* resource) = 0; - virtual void SetSampler(uint32_t slot, const ISampler* sampler) = 0; - - virtual void Draw(uint32_t vertexCount, uint32_t firstVertex = 0) = 0; - virtual void DrawIndexed(uint32_t indexCount, uint32_t firstIndex = 0, int32_t baseVertex = 0) = 0; - virtual void DrawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, - uint32_t firstVertex = 0, uint32_t firstInstance = 0) = 0; - virtual void DrawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, - uint32_t firstIndex = 0, int32_t baseVertex = 0, uint32_t firstInstance = 0) = 0; - - virtual void Dispatch(uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) = 0; - - virtual void BuildAccelerationStructure(const RaytracingBuildDesc& desc) = 0; - virtual void SetRaytracingPipeline(const IPipeline* pipeline) = 0; - virtual void DispatchRays(const DispatchRaysDesc& desc) = 0; - - virtual void BeginRenderPass(const RenderPassBeginDesc& desc) = 0; - virtual void EndRenderPass() = 0; - - virtual void CopyResource(IResource* dst, const IResource* src) = 0; - virtual void CopyBuffer(IBuffer* dst, uint64_t dstOffset, const IBuffer* src, uint64_t srcOffset, uint64_t size) = 0; - - virtual void BeginQuery(IQueryHeap* heap, uint32_t index) = 0; - virtual void EndQuery(IQueryHeap* heap, uint32_t index) = 0; - - virtual void SetMarker(const char* name, uint32_t color) = 0; - virtual void BeginEvent(const char* name, uint32_t color) = 0; - virtual void EndEvent() = 0; -}; - -class RenderContext { -public: - class GBufferPass; - class LightingPass; - class ShadowPass; - class PostProcessPass; - - static RenderContext* Create(const RenderContextDesc& desc); - static void Destroy(RenderContext* context); - - static RenderContext* GetMain(); - - void Initialize(const RenderContextDesc& desc); - void Shutdown(); - - IRenderDevice* GetDevice() { return m_device.get(); } - - void BeginFrame(); - void EndFrame(); - - ICommandList* GetMainCommandList() { return m_mainCommandList.get(); } - - void SetRenderTarget(const RenderTarget& target); - void SetViewport(const Viewport& viewport); - void SetScissorRect(const Rect& rect); - - void RenderScene(const SceneRenderDesc& desc); - void ApplyPostProcessing(const PostProcessDesc& desc); - - void CaptureFrame(const String& filePath); - void ToggleDebugView(); - -private: - std::unique_ptr m_device; - std::unique_ptr m_mainCommandList; - std::unique_ptr m_swapChain; - - std::unique_ptr m_gBufferPass; - std::unique_ptr m_lightingPass; - std::unique_ptr m_shadowPass; - std::unique_ptr m_postProcessPass; - - uint32_t m_frameIndex = 0; - uint64_t m_frameCount = 0; - - static RenderContext* s_mainContext; -}; - -// 资源类型 -enum class ResourceType { - Unknown, - Mesh, - Texture, - Material, - Shader, - Audio, - Animation, - Skeleton, - Font, - Prefab, - Scene, - Script, - PhysicsMaterial, - NavMesh, - Video, - Custom -}; - -// 资源GUID -struct ResourceGUID { - static constexpr uint64_t INVALID = 0; - - uint64_t value = INVALID; - - bool IsValid() const { return value != INVALID; } - bool operator==(const ResourceGUID& other) const { return value == other.value; } - bool operator!=(const ResourceGUID& other) const { return value != other.value; } - - String ToString() const; - static ResourceGUID FromString(const String& str); - - struct Hash { - size_t operator()(const ResourceGUID& guid) const { - return std::hash{}(guid.value); - } - }; -}; - -// 资源基类 -class IResource { -public: - virtual ~IResource() = default; - - virtual ResourceType GetType() const = 0; - virtual const String& GetName() const = 0; - virtual ResourceGUID GetGUID() const = 0; - - virtual bool IsLoaded() const = 0; - virtual bool IsLoading() const = 0; - virtual float GetLoadingProgress() const = 0; - - virtual void AddRef() = 0; - virtual void Release() = 0; - virtual uint32_t GetRefCount() const = 0; - - virtual void Unload() = 0; -}; - -class RenderTexture : public IResource { -public: - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - uint32_t GetMipLevels() const { return m_mipLevels; } - Format GetFormat() const { return m_format; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - IShaderResourceView* GetSRV() { return m_srv.get(); } - IRenderTargetView* GetRTV() { return m_rtv.get(); } - IDepthStencilView* GetDSV() { return m_dsv.get(); } - - void* Map(); - void Unmap(); - -private: - uint32_t m_width, m_height; - uint32_t m_mipLevels; - Format m_format; - void* m_nativeHandle = nullptr; - - std::unique_ptr m_srv; - std::unique_ptr m_rtv; - std::unique_ptr m_dsv; -}; - -class RenderBuffer : public IResource { -public: - uint64_t GetSize() const { return m_size; } - BufferUsage GetUsage() const { return m_usage; } - - void* GetNativeHandle() const { return m_nativeHandle; } - - void SetData(const void* data, uint64_t size, uint64_t offset = 0); - void* Map(); - void Unmap(); - -private: - uint64_t m_size; - BufferUsage m_usage; - void* m_nativeHandle = nullptr; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - -### 4.2 渲染图 (Render Graph) - -```cpp -namespace XCEngine { -namespace Rendering { - -class RenderGraphResource { -public: - RenderGraphResource() = default; - RenderGraphResource(const String& name); - - const String& GetName() const { return m_name; } - uint32_t GetIndex() const { return m_index; } - - bool IsValid() const { return m_index != UINT32_MAX; } - operator bool() const { return IsValid(); } - -private: - String m_name; - uint32_t m_index = UINT32_MAX; - friend class RenderGraph; -}; - -class RenderGraphPass { -public: - using ExecuteFn = std::function; - - RenderGraphPass(const String& name, PassType type); - - RenderGraphResource Read(const String& resourceName); - RenderGraphResource Write(const String& resourceName); - RenderGraphResource ReadWrite(const String& resourceName, ResourceState initialState, ResourceState finalState); - - void Execute(ExecuteFn&& fn); - void Execute() const; - - void SetDebugColor(const Color& color); - - const String& GetName() const { return m_name; } - PassType GetType() const { return m_type; } - const std::vector& GetReads() const { return m_reads; } - const std::vector& GetWrites() const { return m_writes; } - -private: - String m_name; - PassType m_type; - std::vector m_reads; - std::vector m_writes; - ExecuteFn m_executeFn; - Color m_debugColor; - - friend class RenderGraph; -}; - -class RenderGraph { -public: - RenderGraph(); - ~RenderGraph(); - - RenderGraphResource CreateTexture(const String& name, const TextureDesc& desc); - RenderGraphResource CreateBuffer(const String& name, const BufferDesc& desc); - - RenderGraphPass& AddPass(const String& name, PassType type); - - void Build(); - void Execute(ICommandList* commandList); - - void SetName(const String& name); - void Print() const; - void ExportToDot(const String& filePath) const; - - void Clear(); - -private: - void Compile(); - void Validate(); - void SortPasses(); - void AllocateResources(); - - struct ResourceInfo { - String name; - ResourceDesc desc; - ResourceState currentState; - uint32_t firstPassWrite = UINT32_MAX; - uint32_t lastPassRead = UINT32_MAX; - bool imported = false; - }; - - struct PassInfo { - String name; - PassType type; - std::vector reads; - std::vector writes; - std::function executeFn; - }; - - std::vector m_resources; - std::vector m_passes; - std::unordered_map m_resourceIndex; - - bool m_built = false; -}; - -} // namespace Rendering -} // namespace XCEngine -``` - -### 4.3 相机组件 - -```cpp -namespace XCEngine { - -class CameraComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - enum class ProjectionType : uint8_t { - Perspective, - Orthographic - }; - - void SetProjectionType(ProjectionType type) { m_projectionType = type; } - ProjectionType GetProjectionType() const { return m_projectionType; } - - void SetFieldOfView(float fov) { m_fieldOfView = fov; } - float GetFieldOfView() const { return m_fieldOfView; } - - void SetOrthographicSize(float size) { m_orthographicSize = size; } - float GetOrthographicSize() const { return m_orthographicSize; } - - void SetNearClipPlane(float near) { m_nearPlane = near; } - float GetNearClipPlane() const { return m_nearPlane; } - - void SetFarClipPlane(float far) { m_farPlane = far; } - float GetFarClipPlane() const { return m_farPlane; } - - void SetViewport(const Rect& rect) { m_viewportRect = rect; } - const Rect& GetViewport() const { return m_viewportRect; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - - void SetDepth(float depth) { m_depth = depth; } - float GetDepth() const { return m_depth; } - - Matrix4x4 GetViewMatrix() const; - Matrix4x4 GetProjectionMatrix() const; - Matrix4x4 GetViewProjectionMatrix() const; - - Vector3 ScreenToWorldPoint(const Vector3& screenPoint) const; - Vector3 WorldToScreenPoint(const Vector3& worldPoint) const; - Ray ScreenPointToRay(const Vector2& screenPoint) const; - - Frustum& GetFrustum() { return m_frustum; } - -private: - ProjectionType m_projectionType = ProjectionType::Perspective; - float m_fieldOfView = 60.0f; - float m_orthographicSize = 5.0f; - float m_nearPlane = 0.1f; - float m_farPlane = 1000.0f; - float m_aspectRatio = 16.0f / 9.0f; - Rect m_viewportRect = Rect(0, 0, 1, 1); - float m_depth = 0.0f; - int32_t m_cullingMask = -1; - Frustum m_frustum; -}; - -} // namespace XCEngine -``` - -### 4.4 光照组件 - -```cpp -namespace XCEngine { - -class RenderLightComponent : public Component { -public: - void Awake() override; - void Update(float deltaTime) override; - - enum class LightType : uint8_t { - Directional, - Point, - Spot, - Area - }; - - void SetLightType(LightType type) { m_type = type; } - LightType GetLightType() const { return m_type; } - - void SetColor(const Vector3& color) { m_color = color; } - Vector3 GetColor() const { return m_color; } - - void SetIntensity(float intensity) { m_intensity = intensity; } - float GetIntensity() const { return m_intensity; } - - void SetRange(float range) { m_range = range; } - float GetRange() const { return m_range; } - - void SetSpotAngle(float angle) { m_spotAngle = angle; } - float GetSpotAngle() const { return m_spotAngle; } - - void SetCastShadows(bool cast) { m_castShadows = cast; } - bool GetCastShadows() const { return m_castShadows; } - - void SetShadowResolution(uint32_t resolution) { m_shadowResolution = resolution; } - uint32_t GetShadowResolution() const { return m_shadowResolution; } - - void SetCullingMask(int32_t mask) { m_cullingMask = mask; } - int32_t GetCullingMask() const { return m_cullingMask; } - -private: - LightType m_type = LightType::Point; - Vector3 m_color = Vector3::One(); - float m_intensity = 1.0f; - float m_range = 10.0f; - float m_spotAngle = 30.0f; - float m_penumbraAngle = 5.0f; - bool m_castShadows = true; - uint32_t m_shadowResolution = 1024; - float m_shadowBias = 0.005f; - float m_normalOffsetBias = 0.001f; - float m_nearPlane = 0.1f; - int32_t m_cullingMask = -1; - float m_intensityVariation = 0.0f; - ResourceGUID m_cookieTextureGuid = ResourceGUID::Invalid; - float m_cookieSize = 5.0f; -}; - -} // namespace XCEngine -``` - -### 4.5 渲染网格组件 - -```cpp -namespace XCEngine { - -class RenderMeshComponent : public Component { -public: - void Awake() override; - void Start() override; - void Update(float deltaTime) override; - - ResourceGUID GetMesh() const { return m_meshGuid; } - void SetMesh(const ResourceGUID& guid); - - ResourceGUID GetMaterial() const { return m_materialGuid; } - void SetMaterial(const ResourceGUID& guid); - - bool GetCastShadows() const { return m_castShadows; } - void SetCastShadows(bool cast) { m_castShadows = cast; } - - bool GetReceiveShadows() const { return m_receiveShadows; } - void SetReceiveShadows(bool receive) { m_receiveShadows = receive; } - -private: - ResourceGUID m_meshGuid; - ResourceGUID m_materialGuid; - bool m_castShadows = true; - bool m_receiveShadows = true; -}; - -} // namespace XCEngine -``` - ---- - -## 第五章 目录结构 - -``` -XCVolumeRenderer/ -├── engine/ # 引擎核心库(静态库) -│ ├── CMakeLists.txt -│ ├── include/ -│ │ └── XCGameEngine/ -│ │ ├── Core/ # 核心基础 -│ │ │ ├── Assert.h -│ │ │ ├── Event.h -│ │ │ ├── TypeTraits.h -│ │ │ └── UniquePtr.h -│ │ ├── Math/ # 数学库 -│ │ │ ├── Vector2.h -│ │ │ ├── Vector3.h -│ │ │ ├── Vector4.h -│ │ │ ├── Matrix3.h -│ │ │ ├── Matrix4.h -│ │ │ ├── Quaternion.h -│ │ │ ├── Transform.h -│ │ │ ├── Color.h -│ │ │ ├── Ray.h -│ │ │ ├── Plane.h -│ │ │ ├── Sphere.h -│ │ │ ├── Box.h -│ │ │ └── Frustum.h -│ │ ├── Containers/ # 容器 -│ │ │ ├── Array.h -│ │ │ ├── String.h -│ │ │ └── HashMap.h -│ │ ├── Memory/ # 内存管理 -│ │ │ ├── Allocator.h -│ │ │ ├── LinearAllocator.h -│ │ │ ├── PoolAllocator.h -│ │ │ └── ProxyAllocator.h -│ │ ├── Threading/ # 线程 -│ │ │ ├── Thread.h -│ │ │ ├── Mutex.h -│ │ │ ├── TaskSystem.h -│ │ │ └── Atomic.h -│ │ ├── Debug/ # 调试 -│ │ │ ├── Logger.h -│ │ │ └── Profiler.h -│ │ ├── Components/ # 组件系统 -│ │ │ ├── GameObject.h -│ │ │ ├── Component.h -│ │ │ ├── TransformComponent.h -│ │ │ ├── RenderMeshComponent.h -│ │ │ ├── RenderLightComponent.h -│ │ │ └── CameraComponent.h -│ │ ├── Scene/ # 场景系统 -│ │ │ ├── Scene.h -│ │ │ └── SceneManager.h -│ │ ├── Renderer/ # 渲染系统 -│ │ │ ├── Device.h -│ │ │ ├── Context.h -│ │ │ ├── SwapChain.h -│ │ │ ├── Buffer.h -│ │ │ ├── Texture.h -│ │ │ ├── Shader.h -│ │ │ ├── Pipeline.h -│ │ │ ├── RenderPass.h -│ │ │ ├── CommandList.h -│ │ │ ├── RenderGraph.h -│ │ │ └── RenderModule.h -│ │ └── XCGameEngine.h # 主头文件 -│ ├── src/ -│ │ ├── Core/ -│ │ ├── Math/ -│ │ ├── Components/ -│ │ ├── Scene/ -│ │ ├── Renderer/ -│ │ └── ... -│ └── third_party/ -│ ├── stb/ -│ └── json/ -│ -├── runtime/ # 游戏运行时(独立可执行文件) -│ ├── CMakeLists.txt -│ ├── src/ -│ │ ├── GameMain.cpp -│ │ └── EntryPoint.cpp -│ └── resources/ -│ -├── tools/ # 工具链 -│ ├── ShaderCompiler/ # 着色器编译器 -│ └── BuildTool/ # 构建打包工具 -│ -├── content/ # 资源内容 -│ ├── Assets/ -│ │ ├── Scenes/ -│ │ ├── Materials/ -│ │ ├── Meshes/ -│ │ ├── Textures/ -│ │ └── Shaders/ -│ └── Library/ -│ -└── docs/ # 文档 -``` - ---- - -## 附录:待扩展功能 - -以下功能将在后续迭代中添加: - -1. **物理系统** - RigidBodyComponent, ColliderComponent, PhysicsWorld -2. **音频系统** - AudioSourceComponent, AudioListenerComponent, AudioEngine -3. **动画系统** - AnimatorComponent, AnimationClip, Skeleton -4. **粒子系统** - ParticleSystemComponent, GPUParticles -5. **UI系统** - CanvasComponent, ImageComponent, TextComponent, ButtonComponent -6. **网络系统** - NetworkIdentityComponent, NetworkTransformComponent -7. **完整编辑器** - HierarchyPanel, InspectorPanel, SceneViewPanel diff --git a/docs/plan/第一阶段计划.md b/docs/plan/第一阶段计划.md new file mode 100644 index 00000000..0ec0fafb --- /dev/null +++ b/docs/plan/第一阶段计划.md @@ -0,0 +1,151 @@ +# XCEngine 渲染引擎 - 第一阶段计划 + +> **目标**: 构建核心基础层,为上层渲染系统提供底层依赖 +> **版本**: 1.0 +> **日期**: 2026-03-13 + +--- + +## 阶段目标 + +第一阶段聚焦于引擎底层基础设施的建设,确保后续渲染系统开发有稳定的基础。 + +--- + +## 模块规划 + +### 1.1 数学库 (Math Library) + +| 项目 | 内容 | +|------|------| +| **优先级** | P0 | +| **预计工作量** | 5天 | +| **包含类型** | `Vector2`, `Vector3`, `Vector4`, `Matrix3x3`, `Matrix4x4`, `Quaternion`, `Transform`, `Color`, `Rect`, `RectInt`, `Viewport`, `Ray`, `Sphere`, `Box`, `Plane`, `Frustum`, `Bounds`, `AABB`, `OBB` | +| **功能要求** | 向量运算、矩阵变换、四元数、欧拉角转换、视锥体剔除基础 | + +### 1.2 Core 基础类型 + +| 项目 | 内容 | +|------|------| +| **优先级** | P0 | +| **预计工作量** | 2天 | +| **包含类型** | 基础类型别名 (`int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `byte`)、`RefCounted`、`Ref`、`UniqueRef`、`Event` | +| **功能要求** | 基础类型别名、引用计数、智能指针、事件系统 | +| **依赖** | 无 | + +### 1.3 线程系统 (Threading) + +| 项目 | 内容 | +|------|------| +| **优先级** | P0 | +| **预计工作量** | 4天 | +| **包含类型** | `ITask`, `LambdaTask`, `TaskGroup`, `TaskSystem`, `TaskSystemConfig`, `Mutex`, `SpinLock`, `ReadWriteLock`, `Thread` | +| **功能要求** | 任务调度、依赖管理、并行计算、同步原语 | +| **依赖** | Core基础类型 | + +### 1.4 内存管理 (Memory Management) + +| 项目 | 内容 | +|------|------| +| **优先级** | P0 | +| **预计工作量** | 3天 | +| **包含类型** | `IAllocator`, `LinearAllocator`, `PoolAllocator`, `ProxyAllocator`, `MemoryManager` | +| **功能要求** | 内存分配、追踪、泄漏检测、线性/池化分配策略 | +| **依赖** | 线程系统(ProxyAllocator需要Mutex) | + +### 1.5 容器库 (Containers) + +| 项目 | 内容 | +|------|------| +| **优先级** | P0 | +| **预计工作量** | 3天 | +| **包含类型** | `Array`, `String`, `String::npos`, `HashMap` | +| **功能要求** | 动态数组、字符串操作(含npos常量)、哈希映射 | +| **依赖** | Core基础类型, 内存管理 | + +### 1.6 日志与调试系统 + +| 项目 | 内容 | +|------|------| +| **优先级** | P1 | +| **预计工作量** | 2天 | +| **包含类型** | `Logger`, `ConsoleLogSink`, `FileLogSink`, `FileWriter`, `Profiler`, `Assert` | +| **功能要求** | 分级日志、分类输出、文件写入、性能分析、断言 | +| **依赖** | Core基础类型, 容器库(String) | + +--- + +## 时间安排 + +| 周次 | 内容 | +|------|------| +| 第1周 | 数学库 + Core基础类型 | +| 第2周 | 线程系统 + 内存管理 | +| 第3周 | 容器库 + 日志系统 | + +> 注:内存管理依赖线程系统完成(ProxyAllocator需要Mutex),因此调整顺序 + +--- + +## 测试方案 + +### 测试框架 +- **推荐**: Google Test (gtest) 或 Doctest + +### 测试用例设计 + +| 模块 | 测试类别 | 测试用例示例 | +|------|---------|-------------| +| **Math** | 向量运算 | `Vector3::Dot`, `Cross`, `Normalize`, `Lerp` 精度测试 | +| **Math** | 矩阵运算 | `Matrix4x4::TRS`, `LookAt`, `Perspective` 结果正确性 | +| **Math** | 四元数 | `FromEulerAngles`, `Slerp`, `ToMatrix4x4` 精度验证 | +| **Core** | 引用计数 | `RefCounted` 多线程安全释放 | +| **Core** | 事件系统 | 订阅/取消订阅、线程安全调用 | +| **Threading** | 任务系统 | 依赖链、优先级、并行For、TaskSystemConfig | +| **Threading** | 同步原语 | 锁竞争、死锁检测 | +| **Memory** | 分配器 | 边界检查、碎片率、线性分配器回滚测试 | +| **Memory** | 泄漏检测 | 分配/释放计数、峰值追踪 | +| **Containers** | Array | 边界访问、迭代器、内存增长策略 | +| **Containers** | String | 子串、查找、大小写转换 | +| **Containers** | HashMap | 冲突处理、负载因子重分布 | +| **Logger** | 日志级别 | 过滤、分类、格式化 | + +### 执行命令 + +```bash +# 编译并运行所有单元测试 +cmake --build build --target xcengine_tests +./build/tests/xcengine_tests.exe + +# 性能基准测试 +./build/tests/xcengine_tests.exe --benchmark +``` + +--- + +## 验收标准 + +- [ ] 数学库通过全部运算正确性测试 +- [ ] Core基础类型(引用计数、智能指针)工作正常 +- [ ] 线程系统在高并发下稳定运行 +- [ ] 内存分配器无内存泄漏 +- [ ] 容器操作边界安全 +- [ ] 日志系统输出格式正确 + +--- + +## 依赖关系 + +``` +Math Library (无依赖) + │ + ├──▶ Core 基础类型 (无依赖) + │ │ + │ ├──▶ Threading (依赖 Core) + │ │ + │ ├──▶ Memory Management (依赖 Threading) + │ │ │ + │ │ └──▶ Containers (依赖 Memory, Core) + │ │ + │ └──▶ Logging & Debug (依赖 Core, Containers) +``` diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt new file mode 100644 index 00000000..cc88aa10 --- /dev/null +++ b/engine/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.15) +project(XCEngine) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(XCEngine STATIC + include/XCEngine/Math/Math.h + include/XCEngine/Math/Vector2.h + include/XCEngine/Math/Vector3.h + include/XCEngine/Math/Vector4.h + include/XCEngine/Math/Matrix3.h + include/XCEngine/Math/Matrix4.h + include/XCEngine/Math/Quaternion.h + include/XCEngine/Math/Transform.h + include/XCEngine/Math/Color.h + include/XCEngine/Math/Ray.h + include/XCEngine/Math/Plane.h + include/XCEngine/Math/Sphere.h + include/XCEngine/Math/Box.h + include/XCEngine/Math/Bounds.h + include/XCEngine/Math/Frustum.h + include/XCEngine/Math/Rect.h + include/XCEngine/Math/AABB.h + src/Math/Matrix.cpp + src/Math/Quaternion.cpp + src/Math/Transform.cpp + src/Math/Color.cpp + src/Math/Geometry.cpp + src/Math/FrustumBounds.cpp +) + +target_include_directories(XCEngine PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +if(MSVC) + target_compile_options(XCEngine PRIVATE /W3) +else() + target_compile_options(XCEngine PRIVATE -Wall) +endif() diff --git a/engine/include/XCEngine/Core/Core.h b/engine/include/XCEngine/Core/Core.h new file mode 100644 index 00000000..92b6b8ae --- /dev/null +++ b/engine/include/XCEngine/Core/Core.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Types.h" +#include "RefCounted.h" +#include "SmartPtr.h" +#include "Event.h" + +namespace XCEngine { +namespace Core { + +} // namespace Core +} // namespace XCEngine diff --git a/engine/include/XCEngine/Core/Event.h b/engine/include/XCEngine/Core/Event.h new file mode 100644 index 00000000..2d5b6b61 --- /dev/null +++ b/engine/include/XCEngine/Core/Event.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include + +namespace XCEngine { +namespace Core { + +template +class Event { +public: + using Callback = std::function; + using Listener = std::pair; + using Iterator = typename std::vector::iterator; + + uint64_t Subscribe(Callback callback) { + std::lock_guard lock(m_mutex); + uint64_t id = ++m_nextId; + m_listeners.emplace_back(id, std::move(callback)); + return id; + } + + void Unsubscribe(uint64_t id) { + std::lock_guard lock(m_mutex); + m_pendingUnsubscribes.push_back(id); + } + + void ProcessUnsubscribes() { + std::lock_guard lock(m_mutex); + for (uint64_t id : m_pendingUnsubscribes) { + m_listeners.erase( + std::remove_if(m_listeners.begin(), m_listeners.end(), + [id](const auto& pair) { return pair.first == id; }), + m_listeners.end() + ); + } + m_pendingUnsubscribes.clear(); + } + + void Invoke(Args... args) const { + std::vector listenersCopy; + { + std::lock_guard lock(m_mutex); + if (!m_pendingUnsubscribes.empty()) { + for (uint64_t id : m_pendingUnsubscribes) { + m_listeners.erase( + std::remove_if(m_listeners.begin(), m_listeners.end(), + [id](const auto& pair) { return pair.first == id; }), + m_listeners.end() + ); + } + m_pendingUnsubscribes.clear(); + } + listenersCopy = m_listeners; + } + for (const auto& [id, callback] : listenersCopy) { + callback(args...); + } + } + + void Clear() { + std::lock_guard lock(m_mutex); + m_listeners.clear(); + } + + Iterator begin() { return m_listeners.begin(); } + Iterator end() { return m_listeners.end(); } + +private: + mutable std::mutex m_mutex; + std::vector m_listeners; + std::vector m_pendingUnsubscribes; + uint64_t m_nextId = 0; +}; + +} // namespace Core +} // namespace XCEngine diff --git a/engine/include/XCEngine/Core/RefCounted.h b/engine/include/XCEngine/Core/RefCounted.h new file mode 100644 index 00000000..29644594 --- /dev/null +++ b/engine/include/XCEngine/Core/RefCounted.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Core { + +class RefCounted { +public: + RefCounted() : m_refCount(1) {} + virtual ~RefCounted() = default; + + void AddRef() { ++m_refCount; } + void Release() { + if (--m_refCount == 0) { + delete this; + } + } + + uint32_t GetRefCount() const { return m_refCount.load(); } + +protected: + std::atomic m_refCount; +}; + +} // namespace Core +} // namespace XCEngine diff --git a/engine/include/XCEngine/Core/SmartPtr.h b/engine/include/XCEngine/Core/SmartPtr.h new file mode 100644 index 00000000..596aeed7 --- /dev/null +++ b/engine/include/XCEngine/Core/SmartPtr.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Core { + +template +using Ref = std::shared_ptr; + +template +using UniqueRef = std::unique_ptr; + +template +Ref MakeRef(Args&&... args) { + return std::make_shared(std::forward(args)...); +} + +template +UniqueRef MakeUnique(Args&&... args) { + return std::make_unique(std::forward(args)...); +} + +} // namespace Core +} // namespace XCEngine diff --git a/engine/include/XCEngine/Core/Types.h b/engine/include/XCEngine/Core/Types.h new file mode 100644 index 00000000..24813d2d --- /dev/null +++ b/engine/include/XCEngine/Core/Types.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Core { + +using int8 = int8_t; +using int16 = int16_t; +using int32 = int32_t; +using int64 = int64_t; +using uint8 = uint8_t; +using uint16 = uint16_t; +using uint32 = uint32_t; +using uint64 = uint64_t; +using byte = uint8_t; + +} // namespace Core +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/AABB.h b/engine/include/XCEngine/Math/AABB.h new file mode 100644 index 00000000..b339dd2e --- /dev/null +++ b/engine/include/XCEngine/Math/AABB.h @@ -0,0 +1,123 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" +#include "Bounds.h" +#include "Matrix4.h" + +namespace XCEngine { +namespace Math { + +struct Sphere; + +struct OBB { + Vector3 center; + Vector3 extents; + Matrix4 transform; + + OBB() : center(Vector3::Zero()), extents(Vector3::One()), transform(Matrix4::Identity()) {} + OBB(const Vector3& center, const Vector3& extents) + : center(center), extents(extents), transform(Matrix4::Identity()) {} + + Vector3 GetAxis(int index) const { + return Vector3(transform.m[0][index], transform.m[1][index], transform.m[2][index]); + } + + Vector3 GetMin() const { + return center - extents; + } + + Vector3 GetMax() const { + return center + extents; + } + + bool Contains(const Vector3& point) const { + Vector3 localPoint = transform.Inverse().MultiplyPoint(point); + return std::abs(localPoint.x) <= extents.x && + std::abs(localPoint.y) <= extents.y && + std::abs(localPoint.z) <= extents.z; + } + + bool Intersects(const OBB& other) const { + OBB a = *this; + OBB b = other; + + Vector3 ea = a.extents; + Vector3 eb = b.extents; + + Vector3 pa = a.center; + Vector3 pb = b.center; + + Vector3 na = a.GetAxis(0); + Vector3 nb = a.GetAxis(1); + Vector3 nc = a.GetAxis(2); + Vector3 nd = b.GetAxis(0); + Vector3 ne = b.GetAxis(1); + Vector3 nf = b.GetAxis(2); + + Vector3 d = pb - pa; + + float ra, rb; + + ra = ea.x; + rb = std::abs(Vector3::Dot(d, na)) + + eb.x * std::abs(Vector3::Dot(nd, na)) + + eb.y * std::abs(Vector3::Dot(ne, na)) + + eb.z * std::abs(Vector3::Dot(nf, na)); + if (ra < rb) return false; + + ra = ea.y; + rb = std::abs(Vector3::Dot(d, nb)) + + eb.x * std::abs(Vector3::Dot(nd, nb)) + + eb.y * std::abs(Vector3::Dot(ne, nb)) + + eb.z * std::abs(Vector3::Dot(nf, nb)); + if (ra < rb) return false; + + ra = ea.z; + rb = std::abs(Vector3::Dot(d, nc)) + + eb.x * std::abs(Vector3::Dot(nd, nc)) + + eb.y * std::abs(Vector3::Dot(ne, nc)) + + eb.z * std::abs(Vector3::Dot(nf, nc)); + if (ra < rb) return false; + + ra = ea.x * std::abs(Vector3::Dot(na, nd)) + + ea.y * std::abs(Vector3::Dot(nb, nd)) + + ea.z * std::abs(Vector3::Dot(nc, nd)); + rb = eb.x + + eb.y * std::abs(Vector3::Dot(ne, nd)) + + eb.z * std::abs(Vector3::Dot(nf, nd)); + if (ra < rb) return false; + + ra = ea.x * std::abs(Vector3::Dot(na, ne)) + + ea.y * std::abs(Vector3::Dot(nb, ne)) + + ea.z * std::abs(Vector3::Dot(nc, ne)); + rb = eb.y + + eb.x * std::abs(Vector3::Dot(nd, ne)) + + eb.z * std::abs(Vector3::Dot(nf, ne)); + if (ra < rb) return false; + + ra = ea.x * std::abs(Vector3::Dot(na, nf)) + + ea.y * std::abs(Vector3::Dot(nb, nf)) + + ea.z * std::abs(Vector3::Dot(nc, nf)); + rb = eb.z + + eb.x * std::abs(Vector3::Dot(nd, nf)) + + eb.y * std::abs(Vector3::Dot(ne, nf)); + if (ra < rb) return false; + + return true; + } + + bool Intersects(const Sphere& sphere) const { + Vector3 localCenter = transform.Inverse().MultiplyPoint(sphere.center); + Vector3 closestPoint; + closestPoint.x = std::clamp(localCenter.x, -extents.x, extents.x); + closestPoint.y = std::clamp(localCenter.y, -extents.y, extents.y); + closestPoint.z = std::clamp(localCenter.z, -extents.z, extents.z); + + float sqrDist = Vector3::SqrMagnitude(localCenter - closestPoint); + return sqrDist <= sphere.radius * sphere.radius; + } +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Bounds.h b/engine/include/XCEngine/Math/Bounds.h new file mode 100644 index 00000000..1951ed2e --- /dev/null +++ b/engine/include/XCEngine/Math/Bounds.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Bounds { + Vector3 center = Vector3::Zero(); + Vector3 extents = Vector3::Zero(); + + Bounds() = default; + Bounds(const Vector3& center, const Vector3& size); + + Vector3 GetMin() const; + Vector3 GetMax() const; + void SetMinMax(const Vector3& min, const Vector3& max); + + bool Intersects(const Bounds& other) const; + bool Contains(const Vector3& point) const; + void Encapsulate(const Vector3& point); + void Encapsulate(const Bounds& bounds); + void Expand(float amount); + void Expand(const Vector3& amount); + Vector3 GetClosestPoint(const Vector3& point) const; + float GetVolume() const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Box.h b/engine/include/XCEngine/Math/Box.h new file mode 100644 index 00000000..a064172a --- /dev/null +++ b/engine/include/XCEngine/Math/Box.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" +#include "Matrix4.h" + +namespace XCEngine { +namespace Math { + +struct Sphere; +struct Ray; + +struct Box { + Vector3 center = Vector3::Zero(); + Vector3 extents = Vector3::Zero(); + Matrix4x4 transform = Matrix4x4::Identity(); + + Box() = default; + Box(const Vector3& center, const Vector3& extents); + + Vector3 GetMin() const; + Vector3 GetMax() const; + bool Contains(const Vector3& point) const; + bool Intersects(const Sphere& sphere) const; + bool Intersects(const Box& other) const; + bool Intersects(const Ray& ray, float& t) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Color.h b/engine/include/XCEngine/Math/Color.h new file mode 100644 index 00000000..cc5b1e4a --- /dev/null +++ b/engine/include/XCEngine/Math/Color.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Math.h" +#include "Vector2.h" +#include "Vector3.h" +#include "Vector4.h" + +namespace XCEngine { +namespace Math { + +struct Color { + float r = 1.0f; + float g = 1.0f; + float b = 1.0f; + float a = 1.0f; + + Color() = default; + constexpr Color(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) {} + + static Color White() { return Color(1, 1, 1, 1); } + static Color Black() { return Color(0, 0, 0, 1); } + static Color Red() { return Color(1, 0, 0, 1); } + static Color Green() { return Color(0, 1, 0, 1); } + static Color Blue() { return Color(0, 0, 1, 1); } + static Color Yellow() { return Color(1, 1, 0, 1); } + static Color Cyan() { return Color(0, 1, 1, 1); } + static Color Magenta() { return Color(1, 0, 1, 1); } + static Color Clear() { return Color(0, 0, 0, 0); } + + static Color Lerp(const Color& a, const Color& b, float t); + + uint32_t ToRGBA() const; + Vector3 ToVector3() const; + Vector4 ToVector4() const; + + Color operator+(const Color& other) const; + Color operator-(const Color& other) const; + Color operator*(float scalar) const; + Color operator/(float scalar) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Frustum.h b/engine/include/XCEngine/Math/Frustum.h new file mode 100644 index 00000000..60c605cf --- /dev/null +++ b/engine/include/XCEngine/Math/Frustum.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Math.h" +#include "Plane.h" +#include "Sphere.h" +#include "Bounds.h" + +namespace XCEngine { +namespace Math { + +class Frustum { +public: + Plane planes[6]; + + enum class PlaneIndex { + Left = 0, + Right = 1, + Bottom = 2, + Top = 3, + Near = 4, + Far = 5 + }; + + bool Contains(const Vector3& point) const; + bool Contains(const Sphere& sphere) const; + bool Contains(const Bounds& bounds) const; + bool Intersects(const Bounds& bounds) const; + bool Intersects(const Sphere& sphere) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Math.h b/engine/include/XCEngine/Math/Math.h new file mode 100644 index 00000000..d24c502d --- /dev/null +++ b/engine/include/XCEngine/Math/Math.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#include +#endif + +namespace XCEngine { +namespace Math { + +constexpr float PI = 3.14159265358979323846f; +constexpr float TWO_PI = 6.28318530717958647692f; +constexpr float HALF_PI = 1.57079632679489661923f; +constexpr float DEG_TO_RAD = PI / 180.0f; +constexpr float RAD_TO_DEG = 180.0f / PI; +constexpr float EPSILON = 1e-6f; +constexpr float FLOAT_MAX = 3.402823466e+38f; + +inline float Radians(float degrees) { return degrees * DEG_TO_RAD; } +inline float Degrees(float radians) { return radians * RAD_TO_DEG; } + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Matrix3.h b/engine/include/XCEngine/Math/Matrix3.h new file mode 100644 index 00000000..cebb4ace --- /dev/null +++ b/engine/include/XCEngine/Math/Matrix3.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Quaternion; + +struct Matrix3x3 { + float m[3][3]; + + Matrix3x3() { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + m[i][j] = 0.0f; + } + + static Matrix3x3 Identity() { + Matrix3x3 result; + result.m[0][0] = 1.0f; + result.m[1][1] = 1.0f; + result.m[2][2] = 1.0f; + return result; + } + + static Matrix3x3 Zero() { + return Matrix3x3(); + } + + static Matrix3x3 RotationX(float radians); + static Matrix3x3 RotationY(float radians); + static Matrix3x3 RotationZ(float radians); + static Matrix3x3 Scale(const Vector3& scale); + + Matrix3x3 operator*(const Matrix3x3& other) const; + Vector3 operator*(const Vector3& v) const; + Matrix3x3 Transpose() const; + Matrix3x3 Inverse() const; + float Determinant() const; + + float* operator[](int row) { return m[row]; } + const float* operator[](int row) const { return m[row]; } +}; + +using Matrix3 = Matrix3x3; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Matrix4.h b/engine/include/XCEngine/Math/Matrix4.h new file mode 100644 index 00000000..cffc0ead --- /dev/null +++ b/engine/include/XCEngine/Math/Matrix4.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" +#include "Vector4.h" + +namespace XCEngine { +namespace Math { + +struct Quaternion; + +struct Matrix4x4 { +public: + float m[4][4]; + + Matrix4x4() { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + m[i][j] = 0.0f; + } + + static Matrix4x4 Identity() { + Matrix4x4 result; + result.m[0][0] = 1.0f; + result.m[1][1] = 1.0f; + result.m[2][2] = 1.0f; + result.m[3][3] = 1.0f; + return result; + } + + static Matrix4x4 Zero() { + return Matrix4x4(); + } + + static Matrix4x4 Translation(const Vector3& v); + static Matrix4x4 Rotation(const Quaternion& q); + static Matrix4x4 Scale(const Vector3& v); + static Matrix4x4 TRS(const Vector3& translation, const Quaternion& rotation, const Vector3& scale); + static Matrix4x4 LookAt(const Vector3& eye, const Vector3& target, const Vector3& up); + static Matrix4x4 Perspective(float fov, float aspect, float near, float far); + static Matrix4x4 Orthographic(float left, float right, float bottom, float top, float near, float far); + static Matrix4x4 RotationX(float radians); + static Matrix4x4 RotationY(float radians); + static Matrix4x4 RotationZ(float radians); + + Matrix4x4 operator*(const Matrix4x4& other) const; + Vector4 operator*(const Vector4& v) const; + Vector3 MultiplyPoint(const Vector3& v) const; + Vector3 MultiplyVector(const Vector3& v) const; + Matrix4x4 Transpose() const; + Matrix4x4 Inverse() const; + float Determinant() const; + + Vector3 GetTranslation() const; + Quaternion GetRotation() const; + Vector3 GetScale() const; + void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const; + + float* operator[](int row) { return m[row]; } + const float* operator[](int row) const { return m[row]; } +}; + +using Matrix4 = Matrix4x4; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Plane.h b/engine/include/XCEngine/Math/Plane.h new file mode 100644 index 00000000..a0b59aa4 --- /dev/null +++ b/engine/include/XCEngine/Math/Plane.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Sphere; +struct Box; +struct Ray; + +struct Plane { + Vector3 normal = Vector3::Up(); + float distance = 0.0f; + + Plane() = default; + Plane(const Vector3& normal, float distance); + + static Plane FromPoints(const Vector3& a, const Vector3& b, const Vector3& c); + + float GetDistanceToPoint(const Vector3& point) const; + Vector3 GetClosestPoint(const Vector3& point) const; + bool GetSide(const Vector3& point) const; + bool Intersects(const Sphere& sphere) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Quaternion.h b/engine/include/XCEngine/Math/Quaternion.h new file mode 100644 index 00000000..6e6ae285 --- /dev/null +++ b/engine/include/XCEngine/Math/Quaternion.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Matrix4x4; + +struct Quaternion { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; + + Quaternion() = default; + constexpr Quaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + + static Quaternion Identity() { return Quaternion(0, 0, 0, 1); } + + static Quaternion FromAxisAngle(const Vector3& axis, float radians); + static Quaternion FromEulerAngles(float pitch, float yaw, float roll); + static Quaternion FromEulerAngles(const Vector3& euler); + static Quaternion FromRotationMatrix(const Matrix4x4& matrix); + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float t); + static Quaternion LookRotation(const Vector3& forward, const Vector3& up = Vector3::Up()); + + Vector3 ToEulerAngles() const; + Matrix4x4 ToMatrix4x4() const; + Quaternion operator*(const Quaternion& other) const; + Quaternion Inverse() const; + float Dot(const Quaternion& other) const; + float Magnitude() const; + Quaternion Normalized() const; + static Quaternion Normalize(const Quaternion& q); +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Ray.h b/engine/include/XCEngine/Math/Ray.h new file mode 100644 index 00000000..82725a68 --- /dev/null +++ b/engine/include/XCEngine/Math/Ray.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Sphere; +struct Box; +struct Plane; + +struct Ray { + Vector3 origin; + Vector3 direction; + + Ray() = default; + Ray(const Vector3& origin, const Vector3& direction); + + Vector3 GetPoint(float t) const; + bool Intersects(const Sphere& sphere, float& t) const; + bool Intersects(const Box& box, float& t) const; + bool Intersects(const Plane& plane, float& t) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Rect.h b/engine/include/XCEngine/Math/Rect.h new file mode 100644 index 00000000..ed804aea --- /dev/null +++ b/engine/include/XCEngine/Math/Rect.h @@ -0,0 +1,120 @@ +#pragma once + +#include "Math.h" +#include "Vector2.h" + +namespace XCEngine { +namespace Math { + +struct Rect { + float x = 0.0f; + float y = 0.0f; + float width = 0.0f; + float height = 0.0f; + + Rect() = default; + Rect(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} + + float GetLeft() const { return x; } + float GetRight() const { return x + width; } + float GetTop() const { return y; } + float GetBottom() const { return y + height; } + + Vector2 GetPosition() const { return Vector2(x, y); } + Vector2 GetSize() const { return Vector2(width, height); } + Vector2 GetCenter() const { return Vector2(x + width * 0.5f, y + height * 0.5f); } + + bool Contains(float px, float py) const { + return px >= x && px < x + width && py >= y && py < y + height; + } + + bool Contains(const Vector2& point) const { + return Contains(point.x, point.y); + } + + bool Intersects(const Rect& other) const { + return !(GetRight() < other.GetLeft() || GetLeft() > other.GetRight() || + GetBottom() < other.GetTop() || GetTop() > other.GetBottom()); + } + + static Rect Intersect(const Rect& a, const Rect& b) { + float left = std::max(a.GetLeft(), b.GetLeft()); + float top = std::max(a.GetTop(), b.GetTop()); + float right = std::min(a.GetRight(), b.GetRight()); + float bottom = std::min(a.GetBottom(), b.GetBottom()); + return Rect(left, top, std::max(0.0f, right - left), std::max(0.0f, bottom - top)); + } + + static Rect Union(const Rect& a, const Rect& b) { + float left = std::min(a.GetLeft(), b.GetLeft()); + float top = std::min(a.GetTop(), b.GetTop()); + float right = std::max(a.GetRight(), b.GetRight()); + float bottom = std::max(a.GetBottom(), b.GetBottom()); + return Rect(left, top, right - left, bottom - top); + } + + void Set(float newX, float newY, float newWidth, float newHeight) { + x = newX; y = newY; width = newWidth; height = newHeight; + } + + void SetPosition(float newX, float newY) { + x = newX; y = newY; + } + + void SetPosition(const Vector2& position) { + x = position.x; y = position.y; + } +}; + +struct RectInt { + int32_t x = 0; + int32_t y = 0; + int32_t width = 0; + int32_t height = 0; + + RectInt() = default; + RectInt(int32_t x, int32_t y, int32_t w, int32_t h) : x(x), y(y), width(w), height(h) {} + + int32_t GetLeft() const { return x; } + int32_t GetRight() const { return x + width; } + int32_t GetTop() const { return y; } + int32_t GetBottom() const { return y + height; } + + Vector2 GetPosition() const { return Vector2(static_cast(x), static_cast(y)); } + Vector2 GetSize() const { return Vector2(static_cast(width), static_cast(height)); } + Vector2 GetCenter() const { return Vector2(static_cast(x + width / 2), static_cast(y + height / 2)); } + + bool Contains(int32_t px, int32_t py) const { + return px >= x && px < x + width && py >= y && py < y + height; + } + + bool Intersects(const RectInt& other) const { + return !(GetRight() < other.GetLeft() || GetLeft() > other.GetRight() || + GetBottom() < other.GetTop() || GetTop() > other.GetBottom()); + } +}; + +struct Viewport { + float x = 0.0f; + float y = 0.0f; + float width = 0.0f; + float height = 0.0f; + float minDepth = 0.0f; + float maxDepth = 1.0f; + + Viewport() = default; + Viewport(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {} + Viewport(float x, float y, float w, float h, float minD, float maxD) + : x(x), y(y), width(w), height(h), minDepth(minD), maxDepth(maxD) {} + + float GetAspectRatio() const { + return height > 0.0f ? width / height : 0.0f; + } + + Rect GetRect() const { + return Rect(x, y, width, height); + } +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Sphere.h b/engine/include/XCEngine/Math/Sphere.h new file mode 100644 index 00000000..1bb15b29 --- /dev/null +++ b/engine/include/XCEngine/Math/Sphere.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" + +namespace XCEngine { +namespace Math { + +struct Sphere { + Vector3 center = Vector3::Zero(); + float radius = 0.0f; + + Sphere() = default; + Sphere(const Vector3& center, float radius); + + bool Contains(const Vector3& point) const; + bool Intersects(const Sphere& other) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Transform.h b/engine/include/XCEngine/Math/Transform.h new file mode 100644 index 00000000..6db2ff96 --- /dev/null +++ b/engine/include/XCEngine/Math/Transform.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Math.h" +#include "Vector3.h" +#include "Quaternion.h" +#include "Matrix4.h" + +namespace XCEngine { +namespace Math { + +enum class Space { + Self, + World +}; + +struct Transform { + Vector3 position = Vector3::Zero(); + Quaternion rotation = Quaternion::Identity(); + Vector3 scale = Vector3::One(); + + Matrix4 ToMatrix() const; + Transform Inverse() const; + Transform operator*(const Transform& other) const; + Vector3 TransformPoint(const Vector3& point) const; + Vector3 TransformDirection(const Vector3& direction) const; + Vector3 InverseTransformPoint(const Vector3& point) const; + Vector3 InverseTransformDirection(const Vector3& direction) const; +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Vector2.h b/engine/include/XCEngine/Math/Vector2.h new file mode 100644 index 00000000..03cf1080 --- /dev/null +++ b/engine/include/XCEngine/Math/Vector2.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Math.h" + +namespace XCEngine { +namespace Math { + +struct Vector2 { + float x = 0.0f; + float y = 0.0f; + + Vector2() = default; + constexpr Vector2(float x, float y) : x(x), y(y) {} + + static Vector2 Zero() { return Vector2(0, 0); } + static Vector2 One() { return Vector2(1, 1); } + static Vector2 Up() { return Vector2(0, 1); } + static Vector2 Down() { return Vector2(0, -1); } + static Vector2 Right() { return Vector2(1, 0); } + static Vector2 Left() { return Vector2(-1, 0); } + + static float Dot(const Vector2& a, const Vector2& b) { return a.x * b.x + a.y * b.y; } + static float Cross(const Vector2& a, const Vector2& b) { return a.x * b.y - a.y * b.x; } + + static Vector2 Normalize(const Vector2& v) { + float mag = Magnitude(v); + return mag > EPSILON ? Vector2(v.x / mag, v.y / mag) : Zero(); + } + + static float Magnitude(const Vector2& v) { return std::sqrt(v.x * v.x + v.y * v.y); } + static float SqrMagnitude(const Vector2& v) { return v.x * v.x + v.y * v.y; } + + static Vector2 Lerp(const Vector2& a, const Vector2& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + return Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); + } + + static Vector2 MoveTowards(const Vector2& current, const Vector2& target, float maxDistance) { + Vector2 diff = target - current; + float dist = Magnitude(diff); + if (dist <= maxDistance || dist < EPSILON) return target; + return current + Normalize(diff) * maxDistance; + } + + float Magnitude() const { return Magnitude(*this); } + float SqrMagnitude() const { return SqrMagnitude(*this); } + Vector2 Normalized() const { return Normalize(*this); } + + Vector2 operator+(const Vector2& other) const { return Vector2(x + other.x, y + other.y); } + Vector2 operator-(const Vector2& other) const { return Vector2(x - other.x, y - other.y); } + Vector2 operator*(float scalar) const { return Vector2(x * scalar, y * scalar); } + Vector2 operator/(float scalar) const { return Vector2(x / scalar, y / scalar); } + + Vector2& operator+=(const Vector2& other) { x += other.x; y += other.y; return *this; } + Vector2& operator-=(const Vector2& other) { x -= other.x; y -= other.y; return *this; } + Vector2& operator*=(float scalar) { x *= scalar; y *= scalar; return *this; } + Vector2& operator/=(float scalar) { x /= scalar; y /= scalar; return *this; } + + bool operator==(const Vector2& other) const { + return std::abs(x - other.x) < EPSILON && std::abs(y - other.y) < EPSILON; + } + bool operator!=(const Vector2& other) const { return !(*this == other); } +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Vector3.h b/engine/include/XCEngine/Math/Vector3.h new file mode 100644 index 00000000..d2e52a3f --- /dev/null +++ b/engine/include/XCEngine/Math/Vector3.h @@ -0,0 +1,117 @@ +#pragma once + +#include "Math.h" + +namespace XCEngine { +namespace Math { + +struct Quaternion; + +struct Vector3 { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + Vector3() = default; + constexpr Vector3(float x, float y, float z) : x(x), y(y), z(z) {} + + static Vector3 Zero() { return Vector3(0, 0, 0); } + static Vector3 One() { return Vector3(1, 1, 1); } + static Vector3 Forward() { return Vector3(0, 0, 1); } + static Vector3 Back() { return Vector3(0, 0, -1); } + static Vector3 Up() { return Vector3(0, 1, 0); } + static Vector3 Down() { return Vector3(0, -1, 0); } + static Vector3 Right() { return Vector3(1, 0, 0); } + static Vector3 Left() { return Vector3(-1, 0, 0); } + + static float Dot(const Vector3& a, const Vector3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } + static Vector3 Cross(const Vector3& a, const Vector3& b) { + return Vector3( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ); + } + + static Vector3 Normalize(const Vector3& v) { + float mag = Magnitude(v); + return mag > EPSILON ? Vector3(v.x / mag, v.y / mag, v.z / mag) : Zero(); + } + + static float Magnitude(const Vector3& v) { return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } + static float SqrMagnitude(const Vector3& v) { return v.x * v.x + v.y * v.y + v.z * v.z; } + + static Vector3 Lerp(const Vector3& a, const Vector3& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + return Vector3( + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t + ); + } + + static Vector3 MoveTowards(const Vector3& current, const Vector3& target, float maxDistance) { + Vector3 diff = target - current; + float dist = Magnitude(diff); + if (dist <= maxDistance || dist < EPSILON) return target; + return current + Normalize(diff) * maxDistance; + } + + static Vector3 Project(const Vector3& vector, const Vector3& onNormal) { + float sqrMag = Dot(onNormal, onNormal); + if (sqrMag < EPSILON) return Zero(); + float dot = Dot(vector, onNormal); + return onNormal * (dot / sqrMag); + } + + static Vector3 ProjectOnPlane(const Vector3& vector, const Vector3& planeNormal) { + float sqrMag = Dot(planeNormal, planeNormal); + if (sqrMag < EPSILON) return vector; + float dot = Dot(vector, planeNormal); + return vector - planeNormal * (dot / sqrMag); + } + + static float Angle(const Vector3& from, const Vector3& to) { + float denominator = std::sqrt(SqrMagnitude(from) * SqrMagnitude(to)); + if (denominator < EPSILON) return 0.0f; + float dot = std::clamp(Dot(from, to) / denominator, -1.0f, 1.0f); + return std::acos(dot) * RAD_TO_DEG; + } + + static Vector3 Reflect(const Vector3& inDirection, const Vector3& inNormal) { + float dot = Dot(inDirection, inNormal); + return inDirection - inNormal * (2.0f * dot); + } + + float Magnitude() const { return Magnitude(*this); } + float SqrMagnitude() const { return SqrMagnitude(*this); } + Vector3 Normalized() const { return Normalize(*this); } + + Vector3 operator+(const Vector3& other) const { return Vector3(x + other.x, y + other.y, z + other.z); } + Vector3 operator-(const Vector3& other) const { return Vector3(x - other.x, y - other.y, z - other.z); } + Vector3 operator*(float scalar) const { return Vector3(x * scalar, y * scalar, z * scalar); } + Vector3 operator/(float scalar) const { return Vector3(x / scalar, y / scalar, z / scalar); } + + Vector3 operator*(const Vector3& other) const { return Vector3(x * other.x, y * other.y, z * other.z); } + Vector3 operator/(const Vector3& other) const { return Vector3(x / other.x, y / other.y, z / other.z); } + + Vector3& operator+=(const Vector3& other) { x += other.x; y += other.y; z += other.z; return *this; } + Vector3& operator-=(const Vector3& other) { x -= other.x; y -= other.y; z -= other.z; return *this; } + Vector3& operator*=(float scalar) { x *= scalar; y *= scalar; z *= scalar; return *this; } + Vector3& operator/=(float scalar) { x /= scalar; y /= scalar; z /= scalar; return *this; } + + float operator[](int index) const { return (&x)[index]; } + float& operator[](int index) { return (&x)[index]; } + + bool operator==(const Vector3& other) const { + return std::abs(x - other.x) < EPSILON && + std::abs(y - other.y) < EPSILON && + std::abs(z - other.z) < EPSILON; + } + bool operator!=(const Vector3& other) const { return !(*this == other); } +}; + +Vector3 operator*(const Quaternion& q, const Vector3& v); + +} // namespace Math +} // namespace XCEngine diff --git a/engine/include/XCEngine/Math/Vector4.h b/engine/include/XCEngine/Math/Vector4.h new file mode 100644 index 00000000..0cd8caea --- /dev/null +++ b/engine/include/XCEngine/Math/Vector4.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Math.h" + +namespace XCEngine { +namespace Math { + +struct Vector4 { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 0.0f; + + Vector4() = default; + constexpr Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + explicit Vector4(const Vector3& v, float w = 0.0f) : x(v.x), y(v.y), z(v.z), w(w) {} + + static Vector4 Zero() { return Vector4(0, 0, 0, 0); } + static Vector4 One() { return Vector4(1, 1, 1, 1); } + + static float Dot(const Vector4& a, const Vector4& b) { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + static Vector4 Project(const Vector4& vector, const Vector4& onNormal) { + float sqrMag = Dot(onNormal, onNormal); + if (sqrMag < EPSILON) return Zero(); + float dot = Dot(vector, onNormal); + return onNormal * (dot / sqrMag); + } + + Vector3 ToVector3() const { return Vector3(x, y, z); } + + float operator[](int index) const { return (&x)[index]; } + float& operator[](int index) { return (&x)[index]; } + + Vector4 operator+(const Vector4& other) const { + return Vector4(x + other.x, y + other.y, z + other.z, w + other.w); + } + Vector4 operator-(const Vector4& other) const { + return Vector4(x - other.x, y - other.y, z - other.z, w - other.w); + } + Vector4 operator*(float scalar) const { + return Vector4(x * scalar, y * scalar, z * scalar, w * scalar); + } + + bool operator==(const Vector4& other) const { + return std::abs(x - other.x) < EPSILON && + std::abs(y - other.y) < EPSILON && + std::abs(z - other.z) < EPSILON && + std::abs(w - other.w) < EPSILON; + } + bool operator!=(const Vector4& other) const { return !(*this == other); } +}; + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/Color.cpp b/engine/src/Math/Color.cpp new file mode 100644 index 00000000..d81e956d --- /dev/null +++ b/engine/src/Math/Color.cpp @@ -0,0 +1,49 @@ +#include "Math/Color.h" + +namespace XCEngine { +namespace Math { + +Color Color::Lerp(const Color& a, const Color& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + return Color( + a.r + (b.r - a.r) * t, + a.g + (b.g - a.g) * t, + a.b + (b.b - a.b) * t, + a.a + (b.a - a.a) * t + ); +} + +uint32_t Color::ToRGBA() const { + uint32_t r = static_cast(std::clamp(r * 255.0f, 0.0f, 255.0f)); + uint32_t g = static_cast(std::clamp(g * 255.0f, 0.0f, 255.0f)); + uint32_t b = static_cast(std::clamp(b * 255.0f, 0.0f, 255.0f)); + uint32_t a = static_cast(std::clamp(a * 255.0f, 0.0f, 255.0f)); + return (r << 24) | (g << 16) | (b << 8) | a; +} + +Vector3 Color::ToVector3() const { + return Vector3(r, g, b); +} + +Vector4 Color::ToVector4() const { + return Vector4(r, g, b, a); +} + +Color Color::operator+(const Color& other) const { + return Color(r + other.r, g + other.g, b + other.b, a + other.a); +} + +Color Color::operator-(const Color& other) const { + return Color(r - other.r, g - other.g, b - other.b, a - other.a); +} + +Color Color::operator*(float scalar) const { + return Color(r * scalar, g * scalar, b * scalar, a * scalar); +} + +Color Color::operator/(float scalar) const { + return Color(r / scalar, g / scalar, b / scalar, a / scalar); +} + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/FrustumBounds.cpp b/engine/src/Math/FrustumBounds.cpp new file mode 100644 index 00000000..49e0c065 --- /dev/null +++ b/engine/src/Math/FrustumBounds.cpp @@ -0,0 +1,159 @@ +#include "Math/Frustum.h" +#include "Math/Bounds.h" + +namespace XCEngine { +namespace Math { + +bool Frustum::Contains(const Vector3& point) const { + for (int i = 0; i < 6; i++) { + if (planes[i].GetDistanceToPoint(point) < 0.0f) { + return false; + } + } + return true; +} + +bool Frustum::Contains(const Sphere& sphere) const { + for (int i = 0; i < 6; i++) { + float distance = planes[i].GetDistanceToPoint(sphere.center); + if (distance < -sphere.radius) { + return false; + } + } + return true; +} + +bool Frustum::Contains(const Bounds& bounds) const { + Vector3 corners[8] = { + bounds.GetMin(), + Vector3(bounds.GetMax().x, bounds.GetMin().y, bounds.GetMin().z), + Vector3(bounds.GetMin().x, bounds.GetMax().y, bounds.GetMin().z), + Vector3(bounds.GetMin().x, bounds.GetMin().y, bounds.GetMax().z), + bounds.GetMax(), + Vector3(bounds.GetMin().x, bounds.GetMax().y, bounds.GetMax().z), + Vector3(bounds.GetMax().x, bounds.GetMin().y, bounds.GetMax().z), + Vector3(bounds.GetMax().x, bounds.GetMax().y, bounds.GetMin().z) + }; + + for (int i = 0; i < 6; i++) { + bool allOutside = true; + for (int j = 0; j < 8; j++) { + if (planes[i].GetDistanceToPoint(corners[j]) >= 0.0f) { + allOutside = false; + break; + } + } + if (allOutside) { + return false; + } + } + return true; +} + +bool Frustum::Intersects(const Bounds& bounds) const { + Vector3 corners[8] = { + bounds.GetMin(), + Vector3(bounds.GetMax().x, bounds.GetMin().y, bounds.GetMin().z), + Vector3(bounds.GetMin().x, bounds.GetMax().y, bounds.GetMin().z), + Vector3(bounds.GetMin().x, bounds.GetMin().y, bounds.GetMax().z), + bounds.GetMax(), + Vector3(bounds.GetMin().x, bounds.GetMax().y, bounds.GetMax().z), + Vector3(bounds.GetMax().x, bounds.GetMin().y, bounds.GetMax().z), + Vector3(bounds.GetMax().x, bounds.GetMax().y, bounds.GetMin().z) + }; + + for (int i = 0; i < 6; i++) { + bool allPositive = true; + bool allNegative = true; + for (int j = 0; j < 8; j++) { + float dist = planes[i].GetDistanceToPoint(corners[j]); + if (dist >= 0.0f) allNegative = false; + else allPositive = false; + } + if (allPositive || allNegative) { + return false; + } + } + return true; +} + +bool Frustum::Intersects(const Sphere& sphere) const { + for (int i = 0; i < 6; i++) { + if (planes[i].GetDistanceToPoint(sphere.center) < -sphere.radius) { + return false; + } + } + return true; +} + +Bounds::Bounds(const Vector3& center, const Vector3& size) + : center(center), extents(size * 0.5f) {} + +Vector3 Bounds::GetMin() const { + return center - extents; +} + +Vector3 Bounds::GetMax() const { + return center + extents; +} + +void Bounds::SetMinMax(const Vector3& min, const Vector3& max) { + center = (min + max) * 0.5f; + extents = (max - min) * 0.5f; +} + +bool Bounds::Intersects(const Bounds& other) const { + if (std::abs(center.x - other.center.x) > (extents.x + other.extents.x)) return false; + if (std::abs(center.y - other.center.y) > (extents.y + other.extents.y)) return false; + if (std::abs(center.z - other.center.z) > (extents.z + other.extents.z)) return false; + return true; +} + +bool Bounds::Contains(const Vector3& point) const { + return point.x >= GetMin().x && point.x <= GetMax().x && + point.y >= GetMin().y && point.y <= GetMax().y && + point.z >= GetMin().z && point.z <= GetMax().z; +} + +void Bounds::Encapsulate(const Vector3& point) { + Vector3 min = GetMin(); + Vector3 max = GetMax(); + min = Vector3(std::min(min.x, point.x), std::min(min.y, point.y), std::min(min.z, point.z)); + max = Vector3(std::max(max.x, point.x), std::max(max.y, point.y), std::max(max.z, point.z)); + SetMinMax(min, max); +} + +void Bounds::Encapsulate(const Bounds& bounds) { + Vector3 min = GetMin(); + Vector3 max = GetMax(); + Vector3 boundsMin = bounds.GetMin(); + Vector3 boundsMax = bounds.GetMax(); + min = Vector3(std::min(min.x, boundsMin.x), std::min(min.y, boundsMin.y), std::min(min.z, boundsMin.z)); + max = Vector3(std::max(max.x, boundsMax.x), std::max(max.y, boundsMax.y), std::max(max.z, boundsMax.z)); + SetMinMax(min, max); +} + +void Bounds::Expand(float amount) { + extents += Vector3(amount, amount, amount) * 0.5f; +} + +void Bounds::Expand(const Vector3& amount) { + extents += amount * 0.5f; +} + +Vector3 Bounds::GetClosestPoint(const Vector3& point) const { + Vector3 result = point; + Vector3 min = GetMin(); + Vector3 max = GetMax(); + result.x = std::clamp(result.x, min.x, max.x); + result.y = std::clamp(result.y, min.y, max.y); + result.z = std::clamp(result.z, min.z, max.z); + return result; +} + +float Bounds::GetVolume() const { + return (extents.x * 2.0f) * (extents.y * 2.0f) * (extents.z * 2.0f); +} + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/Geometry.cpp b/engine/src/Math/Geometry.cpp new file mode 100644 index 00000000..cc5acca5 --- /dev/null +++ b/engine/src/Math/Geometry.cpp @@ -0,0 +1,161 @@ +#include "Math/Sphere.h" +#include "Math/Plane.h" +#include "Math/Box.h" +#include "Math/Ray.h" + +namespace XCEngine { +namespace Math { + +Ray::Ray(const Vector3& origin, const Vector3& direction) + : origin(origin), direction(Vector3::Normalize(direction)) {} + +Vector3 Ray::GetPoint(float t) const { + return origin + direction * t; +} + +bool Ray::Intersects(const Sphere& sphere, float& t) const { + Vector3 oc = origin - sphere.center; + float a = Vector3::Dot(direction, direction); + float b = 2.0f * Vector3::Dot(oc, direction); + float c = Vector3::Dot(oc, oc) - sphere.radius * sphere.radius; + float discriminant = b * b - 4 * a * c; + + if (discriminant < 0) { + return false; + } + + float sqrtDiscriminant = std::sqrt(discriminant); + float t0 = (-b - sqrtDiscriminant) / (2.0f * a); + float t1 = (-b + sqrtDiscriminant) / (2.0f * a); + + if (t0 > 0) { + t = t0; + return true; + } else if (t1 > 0) { + t = t1; + return true; + } + + return false; +} + +bool Ray::Intersects(const Box& box, float& t) const { + float tmin = 0.0f; + float tmax = FLOAT_MAX; + + Vector3 invDir = Vector3(1.0f / direction.x, 1.0f / direction.y, 1.0f / direction.z); + int sign[3] = { invDir.x < 0, invDir.y < 0, invDir.z < 0 }; + + Vector3 bounds[2] = { box.GetMin(), box.GetMax() }; + + for (int i = 0; i < 3; i++) { + float t0 = (bounds[sign[i]][i] - origin[i]) * invDir[i]; + float t1 = (bounds[1 - sign[i]][i] - origin[i]) * invDir[i]; + + tmin = std::max(tmin, std::min(t0, t1)); + tmax = std::min(tmax, std::max(t0, t1)); + + if (tmax < tmin) { + return false; + } + } + + if (tmin > 0) { + t = tmin; + } else { + t = tmax; + } + + return tmax >= 0; +} + +bool Ray::Intersects(const Plane& plane, float& t) const { + float denom = Vector3::Dot(plane.normal, direction); + if (std::abs(denom) < EPSILON) { + return false; + } + t = -(Vector3::Dot(plane.normal, origin) + plane.distance) / denom; + return t >= 0; +} + +Sphere::Sphere(const Vector3& center, float radius) : center(center), radius(radius) {} + +bool Sphere::Contains(const Vector3& point) const { + return Vector3::SqrMagnitude(point - center) <= radius * radius; +} + +bool Sphere::Intersects(const Sphere& other) const { + float sqrDist = Vector3::SqrMagnitude(center - other.center); + float sumRadii = radius + other.radius; + return sqrDist <= sumRadii * sumRadii; +} + +Plane::Plane(const Vector3& normal, float distance) : normal(Vector3::Normalize(normal)), distance(distance) {} + +Plane Plane::FromPoints(const Vector3& a, const Vector3& b, const Vector3& c) { + Vector3 normal = Vector3::Normalize(Vector3::Cross(b - a, c - a)); + float distance = Vector3::Dot(normal, a); + return Plane(normal, distance); +} + +float Plane::GetDistanceToPoint(const Vector3& point) const { + return Vector3::Dot(normal, point) + distance; +} + +Vector3 Plane::GetClosestPoint(const Vector3& point) const { + float distance = GetDistanceToPoint(point); + return point - normal * distance; +} + +bool Plane::GetSide(const Vector3& point) const { + return GetDistanceToPoint(point) > 0.0f; +} + +bool Plane::Intersects(const Sphere& sphere) const { + return std::abs(GetDistanceToPoint(sphere.center)) <= sphere.radius; +} + +Box::Box(const Vector3& center, const Vector3& extents) : center(center), extents(extents) {} + +Vector3 Box::GetMin() const { + return center - extents; +} + +Vector3 Box::GetMax() const { + return center + extents; +} + +bool Box::Contains(const Vector3& point) const { + Vector3 localPoint = transform.Inverse().MultiplyPoint(point); + return std::abs(localPoint.x) <= extents.x && + std::abs(localPoint.y) <= extents.y && + std::abs(localPoint.z) <= extents.z; +} + +bool Box::Intersects(const Sphere& sphere) const { + Vector3 localCenter = transform.Inverse().MultiplyPoint(sphere.center); + Vector3 closestPoint; + closestPoint.x = std::clamp(localCenter.x, -extents.x, extents.x); + closestPoint.y = std::clamp(localCenter.y, -extents.y, extents.y); + closestPoint.z = std::clamp(localCenter.z, -extents.z, extents.z); + float sqrDist = Vector3::SqrMagnitude(localCenter - closestPoint); + return sqrDist <= sphere.radius * sphere.radius; +} + +bool Box::Intersects(const Box& other) const { + Vector3 aMin = GetMin(); + Vector3 aMax = GetMax(); + Vector3 bMin = other.GetMin(); + Vector3 bMax = other.GetMax(); + + return (aMin.x <= bMax.x && aMax.x >= bMin.x) && + (aMin.y <= bMax.y && aMax.y >= bMin.y) && + (aMin.z <= bMax.z && aMax.z >= bMin.z); +} + +bool Box::Intersects(const Ray& ray, float& t) const { + return ray.Intersects(*this, t); +} + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/Matrix.cpp b/engine/src/Math/Matrix.cpp new file mode 100644 index 00000000..c906d0c6 --- /dev/null +++ b/engine/src/Math/Matrix.cpp @@ -0,0 +1,329 @@ +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Math/Quaternion.h" +#include "Math/Vector3.h" + +namespace XCEngine { +namespace Math { + +Matrix3 Matrix3::RotationX(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix3 result; + result.m[1][1] = c; result.m[1][2] = -s; + result.m[2][1] = s; result.m[2][2] = c; + return result; +} + +Matrix3 Matrix3::RotationY(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix3 result; + result.m[0][0] = c; result.m[0][2] = s; + result.m[2][0] = -s; result.m[2][2] = c; + return result; +} + +Matrix3 Matrix3::RotationZ(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix3 result; + result.m[0][0] = c; result.m[0][1] = -s; + result.m[1][0] = s; result.m[1][1] = c; + return result; +} + +Matrix3 Matrix3::Scale(const Vector3& scale) { + Matrix3 result; + result.m[0][0] = scale.x; + result.m[1][1] = scale.y; + result.m[2][2] = scale.z; + return result; +} + +Matrix3 Matrix3::operator*(const Matrix3& other) const { + Matrix3 result; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + result.m[i][j] = m[i][0] * other.m[0][j] + + m[i][1] * other.m[1][j] + + m[i][2] * other.m[2][j]; + } + } + return result; +} + +Vector3 Matrix3::operator*(const Vector3& v) const { + return Vector3( + m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z, + m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z, + m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z + ); +} + +Matrix3 Matrix3::Transpose() const { + Matrix3 result; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + result.m[i][j] = m[j][i]; + return result; +} + +float Matrix3::Determinant() const { + return m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - + m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); +} + +Matrix3 Matrix3::Inverse() const { + float det = Determinant(); + if (std::abs(det) < EPSILON) { + return Identity(); + } + + float invDet = 1.0f / det; + Matrix3 result; + + result.m[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * invDet; + result.m[0][1] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invDet; + result.m[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invDet; + result.m[1][0] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invDet; + result.m[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invDet; + result.m[1][2] = (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invDet; + result.m[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * invDet; + result.m[2][1] = (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invDet; + result.m[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invDet; + + return result; +} + +Matrix4 Matrix4::Translation(const Vector3& v) { + Matrix4 result = Identity(); + result.m[0][3] = v.x; + result.m[1][3] = v.y; + result.m[2][3] = v.z; + return result; +} + +Matrix4 Matrix4::Rotation(const Quaternion& q) { + return q.ToMatrix4x4(); +} + +Matrix4 Matrix4::Scale(const Vector3& v) { + Matrix4 result; + result.m[0][0] = v.x; + result.m[1][1] = v.y; + result.m[2][2] = v.z; + result.m[3][3] = 1.0f; + return result; +} + +Matrix4 Matrix4::TRS(const Vector3& translation, const Quaternion& rotation, const Vector3& scale) { + return Translation(translation) * Rotation(rotation) * Scale(scale); +} + +Matrix4 Matrix4::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up) { + Vector3 z = Vector3::Normalize(eye - target); + if (Vector3::SqrMagnitude(z) < EPSILON) { + z = Vector3::Forward(); + } + Vector3 x = Vector3::Normalize(Vector3::Cross(up, z)); + if (Vector3::SqrMagnitude(x) < EPSILON) { + x = Vector3::Right(); + } + Vector3 y = Vector3::Cross(z, x); + + Matrix4 result; + result.m[0][0] = x.x; result.m[0][1] = x.y; result.m[0][2] = x.z; result.m[0][3] = -Vector3::Dot(x, eye); + result.m[1][0] = y.x; result.m[1][1] = y.y; result.m[1][2] = y.z; result.m[1][3] = -Vector3::Dot(y, eye); + result.m[2][0] = z.x; result.m[2][1] = z.y; result.m[2][2] = z.z; result.m[2][3] = -Vector3::Dot(z, eye); + result.m[3][0] = 0.0f; result.m[3][1] = 0.0f; result.m[3][2] = 0.0f; result.m[3][3] = 1.0f; + return result; +} + +Matrix4 Matrix4::Perspective(float fov, float aspect, float near, float far) { + float tanHalfFov = std::tan(fov * 0.5f); + Matrix4 result; + result.m[0][0] = 1.0f / (aspect * tanHalfFov); + result.m[1][1] = 1.0f / tanHalfFov; + result.m[2][2] = -(far + near) / (far - near); + result.m[2][3] = -(2.0f * far * near) / (far - near); + result.m[3][2] = -1.0f; + return result; +} + +Matrix4 Matrix4::Orthographic(float left, float right, float bottom, float top, float near, float far) { + Matrix4 result = Identity(); + result.m[0][0] = 2.0f / (right - left); + result.m[1][1] = 2.0f / (top - bottom); + result.m[2][2] = -2.0f / (far - near); + result.m[0][3] = -(right + left) / (right - left); + result.m[1][3] = -(top + bottom) / (top - bottom); + result.m[2][3] = -(far + near) / (far - near); + return result; +} + +Matrix4 Matrix4::RotationX(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix4 result = Identity(); + result.m[1][1] = c; result.m[1][2] = -s; + result.m[2][1] = s; result.m[2][2] = c; + return result; +} + +Matrix4 Matrix4::RotationY(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix4 result = Identity(); + result.m[0][0] = c; result.m[0][2] = s; + result.m[2][0] = -s; result.m[2][2] = c; + return result; +} + +Matrix4 Matrix4::RotationZ(float radians) { + float c = std::cos(radians); + float s = std::sin(radians); + Matrix4 result = Identity(); + result.m[0][0] = c; result.m[0][1] = -s; + result.m[1][0] = s; result.m[1][1] = c; + return result; +} + +Matrix4 Matrix4::operator*(const Matrix4& other) const { + Matrix4 result; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + result.m[i][j] = m[i][0] * other.m[0][j] + + m[i][1] * other.m[1][j] + + m[i][2] * other.m[2][j] + + m[i][3] * other.m[3][j]; + } + } + return result; +} + +Vector4 Matrix4::operator*(const Vector4& v) const { + return Vector4( + m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z + m[0][3] * v.w, + m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z + m[1][3] * v.w, + m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z + m[2][3] * v.w, + m[3][0] * v.x + m[3][1] * v.y + m[3][2] * v.z + m[3][3] * v.w + ); +} + +Vector3 Matrix4::MultiplyPoint(const Vector3& v) const { + Vector4 v4(v.x, v.y, v.z, 1.0f); + Vector4 result = (*this) * v4; + return Vector3(result.x, result.y, result.z); +} + +Vector3 Matrix4::MultiplyVector(const Vector3& v) const { + Vector4 v4(v.x, v.y, v.z, 0.0f); + Vector4 result = (*this) * v4; + return Vector3(result.x, result.y, result.z); +} + +Matrix4 Matrix4::Transpose() const { + Matrix4 result; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + result.m[i][j] = m[j][i]; + } + } + return result; +} + +float Matrix4::Determinant() const { + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3]; + float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3]; + + float b00 = a00 * a11 - a01 * a10; + float b01 = a00 * a12 - a02 * a10; + float b02 = a00 * a13 - a03 * a10; + float b03 = a01 * a12 - a02 * a11; + float b04 = a01 * a13 - a03 * a11; + float b05 = a02 * a13 - a03 * a12; + float b06 = a20 * a31 - a21 * a30; + float b07 = a20 * a32 - a22 * a30; + float b08 = a20 * a33 - a23 * a30; + float b09 = a21 * a32 - a22 * a31; + float b10 = a21 * a33 - a23 * a31; + float b11 = a22 * a33 - a23 * a32; + + return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; +} + +Matrix4 Matrix4::Inverse() const { + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3]; + float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3]; + + float b00 = a00 * a11 - a01 * a10; + float b01 = a00 * a12 - a02 * a10; + float b02 = a00 * a13 - a03 * a10; + float b03 = a01 * a12 - a02 * a11; + float b04 = a01 * a13 - a03 * a11; + float b05 = a02 * a13 - a03 * a12; + float b06 = a20 * a31 - a21 * a30; + float b07 = a20 * a32 - a22 * a30; + float b08 = a20 * a33 - a23 * a30; + float b09 = a21 * a32 - a22 * a31; + float b10 = a21 * a33 - a23 * a31; + float b11 = a22 * a33 - a23 * a32; + + float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + if (std::abs(det) < EPSILON) { + return Identity(); + } + + float invDet = 1.0f / det; + + Matrix4 result; + result.m[0][0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet; + result.m[0][1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet; + result.m[0][2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet; + result.m[0][3] = (a22 * b04 - a21 * b05 - a23 * b03) * invDet; + result.m[1][0] = (a12 * b08 - a10 * b11 - a13 * b07) * invDet; + result.m[1][1] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet; + result.m[1][2] = (a32 * b02 - a30 * b05 - a33 * b01) * invDet; + result.m[1][3] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet; + result.m[2][0] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet; + result.m[2][1] = (a01 * b08 - a00 * b10 - a03 * b06) * invDet; + result.m[2][2] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet; + result.m[2][3] = (a21 * b02 - a20 * b04 - a23 * b00) * invDet; + result.m[3][0] = (a11 * b07 - a10 * b09 - a12 * b06) * invDet; + result.m[3][1] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet; + result.m[3][2] = (a31 * b01 - a30 * b03 - a32 * b00) * invDet; + result.m[3][3] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet; + + return result; +} + +Vector3 Matrix4::GetTranslation() const { + return Vector3(m[0][3], m[1][3], m[2][3]); +} + +Quaternion Matrix4::GetRotation() const { + return Quaternion::FromRotationMatrix(*this); +} + +Vector3 Matrix4::GetScale() const { + return Vector3( + std::sqrt(m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]), + std::sqrt(m[0][1] * m[0][1] + m[1][1] * m[1][1] + m[2][1] * m[2][1]), + std::sqrt(m[0][2] * m[0][2] + m[1][2] * m[1][2] + m[2][2] * m[2][2]) + ); +} + +void Matrix4::Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const { + translation = GetTranslation(); + scale = GetScale(); + rotation = GetRotation(); +} + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/Quaternion.cpp b/engine/src/Math/Quaternion.cpp new file mode 100644 index 00000000..a3ecabc8 --- /dev/null +++ b/engine/src/Math/Quaternion.cpp @@ -0,0 +1,223 @@ +#include "Math/Quaternion.h" +#include "Math/Matrix4.h" +#include "Math/Vector3.h" + +namespace XCEngine { +namespace Math { + +Quaternion Quaternion::FromAxisAngle(const Vector3& axis, float radians) { + float halfAngle = radians * 0.5f; + float s = std::sin(halfAngle); + return Quaternion( + axis.x * s, + axis.y * s, + axis.z * s, + std::cos(halfAngle) + ); +} + +Quaternion Quaternion::FromEulerAngles(float pitch, float yaw, float roll) { + float halfPitch = pitch * 0.5f; + float halfYaw = yaw * 0.5f; + float halfRoll = roll * 0.5f; + + float sinPitch = std::sin(halfPitch); + float cosPitch = std::cos(halfPitch); + float sinYaw = std::sin(halfYaw); + float cosYaw = std::cos(halfYaw); + float sinRoll = std::sin(halfRoll); + float cosRoll = std::cos(halfRoll); + + return Quaternion( + cosYaw * sinPitch * cosRoll + sinYaw * cosPitch * sinRoll, + sinYaw * cosPitch * cosRoll - cosYaw * sinPitch * sinRoll, + cosYaw * cosPitch * sinRoll - sinYaw * sinPitch * cosRoll, + cosYaw * cosPitch * cosRoll + sinYaw * sinPitch * sinRoll + ); +} + +Quaternion Quaternion::FromEulerAngles(const Vector3& euler) { + return FromEulerAngles(euler.x, euler.y, euler.z); +} + +Quaternion Quaternion::FromRotationMatrix(const Matrix4& matrix) { + float trace = matrix.m[0][0] + matrix.m[1][1] + matrix.m[2][2]; + + Quaternion q; + + if (trace > 0.0f) { + float s = std::sqrt(trace + 1.0f) * 2.0f; + q.w = 0.25f * s; + q.x = (matrix.m[2][1] - matrix.m[1][2]) / s; + q.y = (matrix.m[0][2] - matrix.m[2][0]) / s; + q.z = (matrix.m[1][0] - matrix.m[0][1]) / s; + } else if (matrix.m[0][0] > matrix.m[1][1] && matrix.m[0][0] > matrix.m[2][2]) { + float s = std::sqrt(1.0f + matrix.m[0][0] - matrix.m[1][1] - matrix.m[2][2]) * 2.0f; + q.w = (matrix.m[2][1] - matrix.m[1][2]) / s; + q.x = 0.25f * s; + q.y = (matrix.m[0][1] + matrix.m[1][0]) / s; + q.z = (matrix.m[0][2] + matrix.m[2][0]) / s; + } else if (matrix.m[1][1] > matrix.m[2][2]) { + float s = std::sqrt(1.0f + matrix.m[1][1] - matrix.m[0][0] - matrix.m[2][2]) * 2.0f; + q.w = (matrix.m[0][2] - matrix.m[2][0]) / s; + q.x = (matrix.m[0][1] + matrix.m[1][0]) / s; + q.y = 0.25f * s; + q.z = (matrix.m[1][2] + matrix.m[2][1]) / s; + } else { + float s = std::sqrt(1.0f + matrix.m[2][2] - matrix.m[0][0] - matrix.m[1][1]) * 2.0f; + q.w = (matrix.m[1][0] - matrix.m[0][1]) / s; + q.x = (matrix.m[0][2] + matrix.m[2][0]) / s; + q.y = (matrix.m[1][2] + matrix.m[2][1]) / s; + q.z = 0.25f * s; + } + + return q.Normalized(); +} + +Quaternion Quaternion::Slerp(const Quaternion& a, const Quaternion& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + float cosHalfTheta = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z; + + if (cosHalfTheta < 0.0f) { + return Slerp(Quaternion(-b.x, -b.y, -b.z, -b.w), Quaternion(a.x, a.y, a.z, a.w), 1.0f - t); + } + + if (std::abs(cosHalfTheta) >= 1.0f) { + return a; + } + + float halfTheta = std::acos(cosHalfTheta); + float sinHalfTheta = std::sqrt(1.0f - cosHalfTheta * cosHalfTheta); + + if (std::abs(sinHalfTheta) < EPSILON) { + return Quaternion( + a.w * 0.5f + b.w * 0.5f, + a.x * 0.5f + b.x * 0.5f, + a.y * 0.5f + b.y * 0.5f, + a.z * 0.5f + b.z * 0.5f + ); + } + + float ratioA = std::sin((1.0f - t) * halfTheta) / sinHalfTheta; + float ratioB = std::sin(t * halfTheta) / sinHalfTheta; + + return Quaternion( + a.x * ratioA + b.x * ratioB, + a.y * ratioA + b.y * ratioB, + a.z * ratioA + b.z * ratioB, + a.w * ratioA + b.w * ratioB + ); +} + +Quaternion Quaternion::LookRotation(const Vector3& forward, const Vector3& up) { + Vector3 f = Vector3::Normalize(forward); + Vector3 r = Vector3::Normalize(Vector3::Cross(up, f)); + Vector3 u = Vector3::Cross(f, r); + + Matrix4 m; + m.m[0][0] = r.x; m.m[0][1] = r.y; m.m[0][2] = r.z; + m.m[1][0] = u.x; m.m[1][1] = u.y; m.m[1][2] = u.z; + m.m[2][0] = f.x; m.m[2][1] = f.y; m.m[2][2] = f.z; + m.m[0][3] = m[1][3] = m[2][3] = 0.0f; + m.m[3][0] = m[3][1] = m[3][2] = 0.0f; + m.m[3][3] = 1.0f; + + return FromRotationMatrix(m); +} + +Vector3 Quaternion::ToEulerAngles() const { + float sinrCosp = 2.0f * (w * x + y * z); + float cosrCosp = 1.0f - 2.0f * (x * x + y * y); + float roll = std::atan2(sinrCosp, cosrCosp); + + float sinp = 2.0f * (w * y - z * x); + float pitch; + if (std::abs(sinp) >= 1.0f) { + pitch = std::copysign(PI * 0.5f, sinp); + } else { + pitch = std::asin(sinp); + } + + float sinyCosp = 2.0f * (w * z + x * y); + float cosyCosp = 1.0f - 2.0f * (y * y + z * z); + float yaw = std::atan2(sinyCosp, cosyCosp); + + return Vector3(pitch, yaw, roll); +} + +Matrix4 Quaternion::ToMatrix4x4() const { + float xx = x * x; + float yy = y * y; + float zz = z * z; + float xy = x * y; + float xz = x * z; + float yz = y * z; + float wx = w * x; + float wy = w * y; + float wz = w * z; + + Matrix4 result; + result.m[0][0] = 1.0f - 2.0f * (yy + zz); + result.m[0][1] = 2.0f * (xy - wz); + result.m[0][2] = 2.0f * (xz + wy); + + result.m[1][0] = 2.0f * (xy + wz); + result.m[1][1] = 1.0f - 2.0f * (xx + zz); + result.m[1][2] = 2.0f * (yz - wx); + + result.m[2][0] = 2.0f * (xz - wy); + result.m[2][1] = 2.0f * (yz + wx); + result.m[2][2] = 1.0f - 2.0f * (xx + yy); + + result.m[3][3] = 1.0f; + + return result; +} + +Quaternion Quaternion::operator*(const Quaternion& other) const { + return Quaternion( + w * other.x + x * other.w + y * other.z - z * other.y, + w * other.y - x * other.z + y * other.w + z * other.x, + w * other.z + x * other.y - y * other.x + z * other.w, + w * other.w - x * other.x - y * other.y - z * other.z + ); +} + +Quaternion Quaternion::Inverse() const { + float norm = x * x + y * y + z * z + w * w; + if (norm > 0.0f) { + float invNorm = 1.0f / norm; + return Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w * invNorm); + } + return Identity(); +} + +float Quaternion::Dot(const Quaternion& other) const { + return x * other.x + y * other.y + z * other.z + w * other.w; +} + +float Quaternion::Magnitude() const { + return std::sqrt(x * x + y * y + z * z + w * w); +} + +Quaternion Quaternion::Normalized() const { + float mag = Magnitude(); + if (mag > EPSILON) { + return Quaternion(x / mag, y / mag, z / mag, w / mag); + } + return Identity(); +} + +Quaternion Quaternion::Normalize(const Quaternion& q) { + return q.Normalized(); +} + +Vector3 operator*(const Quaternion& q, const Vector3& v) { + Vector3 qv(q.x, q.y, q.z); + Vector3 uv = Vector3::Cross(qv, v); + Vector3 uuv = Vector3::Cross(qv, uv); + return v + (uv * q.w + uuv) * 2.0f; +} + +} // namespace Math +} // namespace XCEngine diff --git a/engine/src/Math/Transform.cpp b/engine/src/Math/Transform.cpp new file mode 100644 index 00000000..6d562b40 --- /dev/null +++ b/engine/src/Math/Transform.cpp @@ -0,0 +1,44 @@ +#include "Math/Transform.h" +#include "Math/Matrix4.h" + +namespace XCEngine { +namespace Math { + +Matrix4 Transform::ToMatrix() const { + return Matrix4x4::TRS(position, rotation, scale); +} + +Transform Transform::Inverse() const { + Transform result; + result.rotation = rotation.Inverse(); + result.scale = Vector3::One() / scale; + result.position = result.rotation * (Vector3::Zero() - position) * result.scale; + return result; +} + +Transform Transform::operator*(const Transform& other) const { + Transform result; + result.position = position + rotation * (scale * other.position); + result.rotation = rotation * other.rotation; + result.scale = scale * other.scale; + return result; +} + +Vector3 Transform::TransformPoint(const Vector3& point) const { + return position + rotation * (scale * point); +} + +Vector3 Transform::TransformDirection(const Vector3& direction) const { + return rotation * (scale * direction); +} + +Vector3 Transform::InverseTransformPoint(const Vector3& point) const { + return (rotation.Inverse() * (point - position)) / scale; +} + +Vector3 Transform::InverseTransformDirection(const Vector3& direction) const { + return rotation.Inverse() * direction / scale; +} + +} // namespace Math +} // namespace XCEngine diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..0c5528b0 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.15) +project(XCEngineTests) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# ============================================================ +# Test Configuration +# ============================================================ + +option(ENABLE_COVERAGE "Enable code coverage" OFF) +option(ENABLE_BENCHMARK "Enable benchmark tests" OFF) + +# ============================================================ +# Dependencies +# ============================================================ + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://gitee.com/mirrors/googletest.git + GIT_TAG v1.14.0 +) + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +enable_testing() + +# ============================================================ +# Engine Library +# ============================================================ + +add_subdirectory(../engine engine) + +# ============================================================ +# Test Subdirectories +# ============================================================ + +add_subdirectory(math) + +# ============================================================ +# Test Summary +# ============================================================ + +add_custom_target(print_tests + COMMAND ${CMAKE_COMMAND} -E echo "===== XCEngine Test Suite =====" + COMMAND ${CMAKE_CTEST_COMMAND} -N + COMMENT "Available tests:" +) diff --git a/tests/fixtures/MathFixtures.h b/tests/fixtures/MathFixtures.h new file mode 100644 index 00000000..caf5af5c --- /dev/null +++ b/tests/fixtures/MathFixtures.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Math { +namespace Test { + +constexpr float TEST_EPSILON = 1e-5f; +constexpr double TEST_EPSILON_D = 1e-8; + +class MathTest : public ::testing::Test { +protected: + void SetUp() override { + v1 = Vector3(1.0f, 0.0f, 0.0f); + v2 = Vector3(0.0f, 1.0f, 0.0f); + v3 = Vector3(0.0f, 0.0f, 1.0f); + v4 = Vector3(1.0f, 1.0f, 1.0f); + vZero = Vector3::Zero(); + vOne = Vector3::One(); + + mIdentity = Matrix4x4::Identity(); + mTranslation = Matrix4x4::Translation(Vector3(1.0f, 2.0f, 3.0f)); + mScale = Matrix4x4::Scale(Vector3(2.0f, 2.0f, 2.0f)); + + qIdentity = Quaternion::Identity(); + qRotationX = Quaternion::FromAxisAngle(Vector3::Right(), Math::PI * 0.5f); + qRotationY = Quaternion::FromAxisAngle(Vector3::Up(), Math::PI * 0.5f); + } + + Vector3 v1, v2, v3, v4; + Vector3 vZero, vOne; + Matrix4x4 mIdentity, mTranslation, mScale; + Quaternion qIdentity, qRotationX, qRotationY; +}; + +} // namespace Test +} // namespace Math +} // namespace XCEngine diff --git a/tests/math/CMakeLists.txt b/tests/math/CMakeLists.txt new file mode 100644 index 00000000..2b824d51 --- /dev/null +++ b/tests/math/CMakeLists.txt @@ -0,0 +1,33 @@ +# ============================================================ +# Math Library Tests +# ============================================================ + +set(MATH_TEST_SOURCES + test_vector.cpp + test_matrix.cpp + test_quaternion.cpp + test_geometry.cpp +) + +add_executable(xcengine_math_tests ${MATH_TEST_SOURCES}) + +# Exclude all static runtime libraries to avoid conflicts +if(MSVC) + set_target_properties(xcengine_math_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_math_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_math_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures +) + +add_test(NAME MathTests COMMAND xcengine_math_tests) diff --git a/tests/math/test_geometry.cpp b/tests/math/test_geometry.cpp new file mode 100644 index 00000000..0ce15687 --- /dev/null +++ b/tests/math/test_geometry.cpp @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace XCEngine::Math; + +namespace { + +// ============================================================ +// Ray Tests +// ============================================================ + +TEST(Math_Ray, DefaultConstructor) { + Ray ray; + EXPECT_FLOAT_EQ(ray.origin.x, 0.0f); + EXPECT_FLOAT_EQ(ray.origin.y, 0.0f); + EXPECT_FLOAT_EQ(ray.origin.z, 0.0f); + EXPECT_FLOAT_EQ(ray.direction.x, 0.0f); + EXPECT_FLOAT_EQ(ray.direction.y, 0.0f); + EXPECT_FLOAT_EQ(ray.direction.z, 0.0f); +} + +TEST(Math_Ray, ParameterConstructor) { + Vector3 origin(1, 2, 3); + Vector3 direction(0, 0, 1); + Ray ray(origin, direction); + + EXPECT_FLOAT_EQ(ray.origin.x, 1.0f); + EXPECT_FLOAT_EQ(ray.origin.y, 2.0f); + EXPECT_FLOAT_EQ(ray.origin.z, 3.0f); +} + +TEST(Math_Ray, GetPoint) { + Ray ray(Vector3::Zero(), Vector3::Right()); + Vector3 point = ray.GetPoint(5.0f); + + EXPECT_FLOAT_EQ(point.x, 5.0f); + EXPECT_FLOAT_EQ(point.y, 0.0f); + EXPECT_FLOAT_EQ(point.z, 0.0f); +} + +TEST(Math_Ray, IntersectsSphere_Hit) { + Ray ray(Vector3(-5, 0, 0), Vector3::Right()); + Sphere sphere(Vector3::Zero(), 1.0f); + + float t; + bool hit = ray.Intersects(sphere, t); + + EXPECT_TRUE(hit); + EXPECT_GE(t, 0.0f); +} + +TEST(Math_Ray, IntersectsSphere_Miss) { + Ray ray(Vector3(0, 5, 0), Vector3::Right()); + Sphere sphere(Vector3::Zero(), 1.0f); + + float t; + bool hit = ray.Intersects(sphere, t); + + EXPECT_FALSE(hit); +} + +TEST(Math_Ray, IntersectsBox_Hit) { + Ray ray(Vector3(-5, 0, 0), Vector3::Right()); + Box box(Vector3::Zero(), Vector3(1, 1, 1)); + + float t; + bool hit = ray.Intersects(box, t); + + EXPECT_TRUE(hit); + EXPECT_GE(t, 0.0f); +} + +TEST(Math_Ray, IntersectsBox_Miss) { + Ray ray(Vector3(0, 5, 0), Vector3::Right()); + Box box(Vector3(10, 0, 0), Vector3(1, 1, 1)); + + float t; + bool hit = ray.Intersects(box, t); + + EXPECT_FALSE(hit); +} + +TEST(Math_Ray, IntersectsPlane_Hit) { + Ray ray(Vector3(0, 0, -5), Vector3::Forward()); + Plane plane(Vector3::Forward(), 0.0f); + + float t; + bool hit = ray.Intersects(plane, t); + + EXPECT_TRUE(hit); + EXPECT_NEAR(t, 5.0f, 1e-3f); +} + +TEST(Math_Ray, IntersectsPlane_Miss) { + Ray ray(Vector3(0, 0, -5), Vector3::Back()); + Plane plane(Vector3::Forward(), 0.0f); + + float t; + bool hit = ray.Intersects(plane, t); + + EXPECT_FALSE(hit); +} + +// ============================================================ +// Sphere Tests +// ============================================================ + +TEST(Math_Sphere, DefaultConstructor) { + Sphere sphere; + EXPECT_FLOAT_EQ(sphere.center.x, 0.0f); + EXPECT_FLOAT_EQ(sphere.radius, 0.0f); +} + +TEST(Math_Sphere, ParameterConstructor) { + Sphere sphere(Vector3(1, 2, 3), 5.0f); + + EXPECT_FLOAT_EQ(sphere.center.x, 1.0f); + EXPECT_FLOAT_EQ(sphere.center.y, 2.0f); + EXPECT_FLOAT_EQ(sphere.center.z, 3.0f); + EXPECT_FLOAT_EQ(sphere.radius, 5.0f); +} + +TEST(Math_Sphere, Contains_Inside) { + Sphere sphere(Vector3::Zero(), 2.0f); + EXPECT_TRUE(sphere.Contains(Vector3(1, 0, 0))); +} + +TEST(Math_Sphere, Contains_Outside) { + Sphere sphere(Vector3::Zero(), 1.0f); + EXPECT_FALSE(sphere.Contains(Vector3(5, 0, 0))); +} + +TEST(Math_Sphere, Contains_OnSurface) { + Sphere sphere(Vector3::Zero(), 1.0f); + EXPECT_TRUE(sphere.Contains(Vector3(1, 0, 0))); +} + +TEST(Math_Sphere, Intersects_Overlap) { + Sphere a(Vector3::Zero(), 1.0f); + Sphere b(Vector3(1.5f, 0, 0), 1.0f); + + EXPECT_TRUE(a.Intersects(b)); +} + +TEST(Math_Sphere, Intersects_Separate) { + Sphere a(Vector3::Zero(), 1.0f); + Sphere b(Vector3(5, 0, 0), 1.0f); + + EXPECT_FALSE(a.Intersects(b)); +} + +// ============================================================ +// Plane Tests +// ============================================================ + +TEST(Math_Plane, DefaultConstructor) { + Plane plane; + EXPECT_FLOAT_EQ(plane.normal.x, 0.0f); + EXPECT_FLOAT_EQ(plane.normal.y, 1.0f); + EXPECT_FLOAT_EQ(plane.normal.z, 0.0f); + EXPECT_FLOAT_EQ(plane.distance, 0.0f); +} + +TEST(Math_Plane, ParameterConstructor) { + Plane plane(Vector3::Forward(), 5.0f); + + EXPECT_FLOAT_EQ(plane.normal.z, 1.0f); + EXPECT_FLOAT_EQ(plane.distance, 5.0f); +} + +TEST(Math_Plane, FromPoints) { + Plane plane = Plane::FromPoints( + Vector3(0, 0, 0), + Vector3(1, 0, 0), + Vector3(0, 1, 0) + ); + + EXPECT_FLOAT_EQ(plane.normal.z, -1.0f); + EXPECT_FLOAT_EQ(plane.distance, 0.0f); +} + +TEST(Math_Plane, GetDistanceToPoint) { + Plane plane(Vector3::Forward(), 0.0f); + float dist = plane.GetDistanceToPoint(Vector3(0, 0, 5)); + + EXPECT_FLOAT_EQ(dist, 5.0f); +} + +TEST(Math_Plane, GetClosestPoint) { + Plane plane(Vector3::Forward(), 0.0f); + Vector3 point = plane.GetClosestPoint(Vector3(3, 4, 5)); + + EXPECT_FLOAT_EQ(point.x, 3.0f); + EXPECT_FLOAT_EQ(point.y, 4.0f); + EXPECT_FLOAT_EQ(point.z, 0.0f); +} + +TEST(Math_Plane, GetSide_Positive) { + Plane plane(Vector3::Forward(), 0.0f); + EXPECT_TRUE(plane.GetSide(Vector3(0, 0, 1))); +} + +TEST(Math_Plane, GetSide_Negative) { + Plane plane(Vector3::Forward(), 0.0f); + EXPECT_FALSE(plane.GetSide(Vector3(0, 0, -1))); +} + +TEST(Math_Plane, IntersectsSphere_Inside) { + Plane plane(Vector3::Forward(), 0.0f); + Sphere sphere(Vector3(0, 0, 0), 1.0f); + + EXPECT_TRUE(plane.Intersects(sphere)); +} + +TEST(Math_Plane, IntersectsSphere_Outside) { + Plane plane(Vector3::Forward(), 5.0f); + Sphere sphere(Vector3(0, 0, 0), 1.0f); + + EXPECT_FALSE(plane.Intersects(sphere)); +} + +// ============================================================ +// Bounds Tests +// ============================================================ + +TEST(Math_Bounds, DefaultConstructor) { + Bounds bounds; + EXPECT_FLOAT_EQ(bounds.center.x, 0.0f); + EXPECT_FLOAT_EQ(bounds.extents.x, 0.0f); +} + +TEST(Math_Bounds, ParameterConstructor) { + Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10)); + + EXPECT_FLOAT_EQ(bounds.center.x, 5.0f); + EXPECT_FLOAT_EQ(bounds.extents.x, 5.0f); +} + +TEST(Math_Bounds, GetMin) { + Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10)); + Vector3 min = bounds.GetMin(); + + EXPECT_FLOAT_EQ(min.x, 0.0f); + EXPECT_FLOAT_EQ(min.y, 0.0f); + EXPECT_FLOAT_EQ(min.z, 0.0f); +} + +TEST(Math_Bounds, GetMax) { + Bounds bounds(Vector3(5, 5, 5), Vector3(10, 10, 10)); + Vector3 max = bounds.GetMax(); + + EXPECT_FLOAT_EQ(max.x, 10.0f); + EXPECT_FLOAT_EQ(max.y, 10.0f); + EXPECT_FLOAT_EQ(max.z, 10.0f); +} + +TEST(Math_Bounds, SetMinMax) { + Bounds bounds; + bounds.SetMinMax(Vector3(0, 0, 0), Vector3(10, 10, 10)); + + EXPECT_FLOAT_EQ(bounds.center.x, 5.0f); + EXPECT_FLOAT_EQ(bounds.extents.x, 5.0f); +} + +TEST(Math_Bounds, Contains_Inside) { + Bounds bounds(Vector3::Zero(), Vector3(10, 10, 10)); + EXPECT_TRUE(bounds.Contains(Vector3::Zero())); +} + +TEST(Math_Bounds, Contains_Outside) { + Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2)); + EXPECT_FALSE(bounds.Contains(Vector3(5, 5, 5))); +} + +TEST(Math_Bounds, Contains_OnSurface) { + Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2)); + EXPECT_TRUE(bounds.Contains(Vector3(1, 1, 1))); +} + +TEST(Math_Bounds, Intersects_Overlap) { + Bounds a(Vector3::Zero(), Vector3(2, 2, 2)); + Bounds b(Vector3(1, 1, 1), Vector3(2, 2, 2)); + + EXPECT_TRUE(a.Intersects(b)); +} + +TEST(Math_Bounds, Intersects_Separate) { + Bounds a(Vector3::Zero(), Vector3(2, 2, 2)); + Bounds b(Vector3(10, 10, 10), Vector3(2, 2, 2)); + + EXPECT_FALSE(a.Intersects(b)); +} + +TEST(Math_Bounds, Encapsulate_Point) { + Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2)); + bounds.Encapsulate(Vector3(5, 5, 5)); + + EXPECT_GT(bounds.GetMax().x, 5.0f); +} + +TEST(Math_Bounds, Encapsulate_Bounds) { + Bounds a(Vector3::Zero(), Vector3(2, 2, 2)); + Bounds b(Vector3(5, 5, 5), Vector3(2, 2, 2)); + a.Encapsulate(b); + + EXPECT_GT(a.GetMax().x, 5.0f); +} + +TEST(Math_Bounds, Expand) { + Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2)); + bounds.Expand(2.0f); + + EXPECT_GT(bounds.GetMax().x, 3.0f); +} + +TEST(Math_Bounds, GetVolume) { + Bounds bounds(Vector3::Zero(), Vector3(2, 4, 6)); + float volume = bounds.GetVolume(); + + EXPECT_FLOAT_EQ(volume, 48.0f); +} + +TEST(Math_Bounds, GetClosestPoint) { + Bounds bounds(Vector3::Zero(), Vector3(2, 2, 2)); + Vector3 point = bounds.GetClosestPoint(Vector3(10, 10, 10)); + + EXPECT_FLOAT_EQ(point.x, 1.0f); + EXPECT_FLOAT_EQ(point.y, 1.0f); + EXPECT_FLOAT_EQ(point.z, 1.0f); +} + +// ============================================================ +// Frustum Tests +// ============================================================ + +TEST(Math_Frustum, Contains_Point) { + Frustum frustum; + Plane nearPlane; + nearPlane.normal = Vector3::Forward(); + nearPlane.distance = 1.0f; + frustum.planes[4] = nearPlane; + + EXPECT_TRUE(frustum.Contains(Vector3(0, 0, 2))); +} + +TEST(Math_Frustum, Contains_Sphere) { + Frustum frustum; + Plane nearPlane; + nearPlane.normal = Vector3::Forward(); + nearPlane.distance = 1.0f; + frustum.planes[4] = nearPlane; + + Sphere sphere(Vector3(0, 0, 5), 1.0f); + EXPECT_TRUE(frustum.Contains(sphere)); +} + +TEST(Math_Frustum, Intersects_Sphere) { + Frustum frustum; + Plane nearPlane; + nearPlane.normal = Vector3::Forward(); + nearPlane.distance = 1.0f; + frustum.planes[4] = nearPlane; + + Sphere sphere(Vector3(0, 0, 2), 0.5f); + EXPECT_TRUE(frustum.Intersects(sphere)); +} + +TEST(Math_Frustum, Intersects_Bounds) { + Frustum frustum; + Plane nearPlane; + nearPlane.normal = Vector3::Forward(); + nearPlane.distance = 1.0f; + frustum.planes[4] = nearPlane; + + Bounds bounds(Vector3(0, 0, 2), Vector3(1, 1, 1)); + EXPECT_TRUE(frustum.Intersects(bounds)); +} + +} // namespace diff --git a/tests/math/test_matrix.cpp b/tests/math/test_matrix.cpp new file mode 100644 index 00000000..c1e97433 --- /dev/null +++ b/tests/math/test_matrix.cpp @@ -0,0 +1,312 @@ +#include +#include +#include +#include + +using namespace XCEngine::Math; + +namespace { + +TEST(Math_Matrix4, DefaultConstructor_InitializesToZero) { + Matrix4x4 m; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + EXPECT_FLOAT_EQ(m.m[i][j], 0.0f); + } + } +} + +TEST(Math_Matrix4, Identity_IsCorrect) { + Matrix4x4 id = Matrix4x4::Identity(); + + EXPECT_FLOAT_EQ(id.m[0][0], 1.0f); + EXPECT_FLOAT_EQ(id.m[1][1], 1.0f); + EXPECT_FLOAT_EQ(id.m[2][2], 1.0f); + EXPECT_FLOAT_EQ(id.m[3][3], 1.0f); + + EXPECT_FLOAT_EQ(id.m[0][1], 0.0f); + EXPECT_FLOAT_EQ(id.m[1][2], 0.0f); +} + +TEST(Math_Matrix4, Zero_IsCorrect) { + Matrix4x4 zero = Matrix4x4::Zero(); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + EXPECT_FLOAT_EQ(zero.m[i][j], 0.0f); + } + } +} + +TEST(Math_Matrix4, Translation) { + Vector3 translation(1.0f, 2.0f, 3.0f); + Matrix4x4 m = Matrix4x4::Translation(translation); + + EXPECT_FLOAT_EQ(m.m[0][3], 1.0f); + EXPECT_FLOAT_EQ(m.m[1][3], 2.0f); + EXPECT_FLOAT_EQ(m.m[2][3], 3.0f); + EXPECT_FLOAT_EQ(m.m[3][3], 1.0f); +} + +TEST(Math_Matrix4, Translation_TransformsPoint) { + Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3)); + Vector3 point(0, 0, 0); + + Vector3 result = m.MultiplyPoint(point); + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 2.0f); + EXPECT_FLOAT_EQ(result.z, 3.0f); +} + +TEST(Math_Matrix4, Scale) { + Vector3 scale(2.0f, 3.0f, 4.0f); + Matrix4x4 m = Matrix4x4::Scale(scale); + + EXPECT_FLOAT_EQ(m.m[0][0], 2.0f); + EXPECT_FLOAT_EQ(m.m[1][1], 3.0f); + EXPECT_FLOAT_EQ(m.m[2][2], 4.0f); + EXPECT_FLOAT_EQ(m.m[3][3], 1.0f); +} + +TEST(Math_Matrix4, Scale_TransformsPoint) { + Matrix4x4 m = Matrix4x4::Scale(Vector3(2, 3, 4)); + Vector3 point(1, 1, 1); + + Vector3 result = m.MultiplyPoint(point); + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 3.0f); + EXPECT_FLOAT_EQ(result.z, 4.0f); +} + +TEST(Math_Matrix4, RotationX) { + Matrix4x4 m = Matrix4x4::RotationX(PI * 0.5f); + + EXPECT_NEAR(m.m[1][1], 0.0f, 1e-5f); + EXPECT_NEAR(m.m[1][2], -1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][1], 1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][2], 0.0f, 1e-5f); +} + +TEST(Math_Matrix4, RotationY) { + Matrix4x4 m = Matrix4x4::RotationY(PI * 0.5f); + + EXPECT_NEAR(m.m[0][0], 0.0f, 1e-5f); + EXPECT_NEAR(m.m[0][2], 1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][0], -1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][2], 0.0f, 1e-5f); +} + +TEST(Math_Matrix4, RotationZ) { + Matrix4x4 m = Matrix4x4::RotationZ(PI * 0.5f); + + EXPECT_NEAR(m.m[0][0], 0.0f, 1e-5f); + EXPECT_NEAR(m.m[0][1], -1.0f, 1e-5f); + EXPECT_NEAR(m.m[1][0], 1.0f, 1e-5f); + EXPECT_NEAR(m.m[1][1], 0.0f, 1e-5f); +} + +TEST(Math_Matrix4, TRS) { + Vector3 translation(1, 2, 3); + Quaternion rotation = Quaternion::Identity(); + Vector3 scale(2, 2, 2); + + Matrix4x4 m = Matrix4x4::TRS(translation, rotation, scale); + + EXPECT_FLOAT_EQ(m.m[0][3], 1.0f); + EXPECT_FLOAT_EQ(m.m[1][3], 2.0f); + EXPECT_FLOAT_EQ(m.m[2][3], 3.0f); +} + +TEST(Math_Matrix4, LookAt) { + Vector3 eye(0, 0, 5); + Vector3 target(0, 0, 0); + Vector3 up(0, 1, 0); + + Matrix4x4 m = Matrix4x4::LookAt(eye, target, up); + + EXPECT_FLOAT_EQ(m.m[0][3], 0.0f); + EXPECT_FLOAT_EQ(m.m[1][3], 0.0f); + EXPECT_FLOAT_EQ(m.m[2][3], -5.0f); +} + +TEST(Math_Matrix4, Perspective) { + float fov = PI * 0.25f; + float aspect = 16.0f / 9.0f; + float nearPlane = 0.1f; + float farPlane = 100.0f; + + Matrix4x4 m = Matrix4x4::Perspective(fov, aspect, nearPlane, farPlane); + + EXPECT_FLOAT_EQ(m.m[0][0], 1.0f / (aspect * std::tan(fov * 0.5f))); + EXPECT_FLOAT_EQ(m.m[1][1], 1.0f / std::tan(fov * 0.5f)); + EXPECT_FLOAT_EQ(m.m[2][2], -(farPlane + nearPlane) / (farPlane - nearPlane)); + EXPECT_FLOAT_EQ(m.m[2][3], -(2.0f * farPlane * nearPlane) / (farPlane - nearPlane)); + EXPECT_FLOAT_EQ(m.m[3][2], -1.0f); +} + +TEST(Math_Matrix4, Orthographic) { + float left = -10.0f, right = 10.0f; + float bottom = -10.0f, top = 10.0f; + float nearPlane = 0.1f, farPlane = 100.0f; + + Matrix4x4 m = Matrix4x4::Orthographic(left, right, bottom, top, nearPlane, farPlane); + + EXPECT_FLOAT_EQ(m.m[0][0], 0.1f); + EXPECT_FLOAT_EQ(m.m[1][1], 0.1f); + EXPECT_FLOAT_EQ(m.m[2][2], -2.0f / (farPlane - nearPlane)); +} + +TEST(Math_Matrix4, Multiply_MatrixWithMatrix) { + Matrix4x4 a = Matrix4x4::Identity(); + Matrix4x4 b = Matrix4x4::Translation(Vector3(1, 2, 3)); + + Matrix4x4 result = a * b; + + EXPECT_FLOAT_EQ(result.m[0][3], 1.0f); + EXPECT_FLOAT_EQ(result.m[1][3], 2.0f); + EXPECT_FLOAT_EQ(result.m[2][3], 3.0f); +} + +TEST(Math_Matrix4, Multiply_MatrixWithVector4) { + Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3)); + Vector4 v(1, 1, 1, 1); + + Vector4 result = m * v; + + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 3.0f); + EXPECT_FLOAT_EQ(result.z, 4.0f); +} + +TEST(Math_Matrix4, MultiplyPoint) { + Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3)); + Vector3 p(0, 0, 0); + + Vector3 result = m.MultiplyPoint(p); + + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 2.0f); + EXPECT_FLOAT_EQ(result.z, 3.0f); +} + +TEST(Math_Matrix4, MultiplyVector) { + Matrix4x4 m = Matrix4x4::Scale(Vector3(2, 2, 2)); + Vector3 v(1, 1, 1); + + Vector3 result = m.MultiplyVector(v); + + EXPECT_FLOAT_EQ(result.x, 2.0f); + EXPECT_FLOAT_EQ(result.y, 2.0f); + EXPECT_FLOAT_EQ(result.z, 2.0f); +} + +TEST(Math_Matrix4, Transpose) { + Matrix4x4 m; + m.m[0][1] = 1.0f; + m.m[1][2] = 2.0f; + m.m[2][3] = 3.0f; + + Matrix4x4 t = m.Transpose(); + + EXPECT_FLOAT_EQ(t.m[1][0], 1.0f); + EXPECT_FLOAT_EQ(t.m[2][1], 2.0f); + EXPECT_FLOAT_EQ(t.m[3][2], 3.0f); +} + +TEST(Math_Matrix4, Inverse_Identity_ReturnsIdentity) { + Matrix4x4 id = Matrix4x4::Identity(); + Matrix4x4 inv = id.Inverse(); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + EXPECT_NEAR(inv.m[i][j], id.m[i][j], 1e-5f); + } + } +} + +TEST(Math_Matrix4, Inverse_Translation) { + Matrix4x4 m = Matrix4x4::Translation(Vector3(1, 2, 3)); + Matrix4x4 inv = m.Inverse(); + + Vector3 p(0, 0, 0); + Vector3 transformed = m.MultiplyPoint(p); + Vector3 recovered = inv.MultiplyPoint(transformed); + + EXPECT_NEAR(recovered.x, 0.0f, 1e-5f); + EXPECT_NEAR(recovered.y, 0.0f, 1e-5f); + EXPECT_NEAR(recovered.z, 0.0f, 1e-5f); +} + +TEST(Math_Matrix4, Inverse_OfInverse_EqualsOriginal) { + Matrix4x4 original = Matrix4x4::TRS( + Vector3(1, 2, 3), + Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f), + Vector3(2, 2, 2) + ); + + Matrix4x4 inverted = original.Inverse(); + Matrix4x4 recovered = inverted.Inverse(); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + EXPECT_NEAR(original.m[i][j], recovered.m[i][j], 1e-4f); + } + } +} + +TEST(Math_Matrix4, Determinant_Identity_EqualsOne) { + Matrix4x4 id = Matrix4x4::Identity(); + EXPECT_FLOAT_EQ(id.Determinant(), 1.0f); +} + +TEST(Math_Matrix4, GetTranslation) { + Vector3 translation(1, 2, 3); + Matrix4x4 m = Matrix4x4::Translation(translation); + + Vector3 result = m.GetTranslation(); + + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 2.0f); + EXPECT_FLOAT_EQ(result.z, 3.0f); +} + +TEST(Math_Matrix4, GetScale) { + Vector3 scale(2, 3, 4); + Matrix4x4 m = Matrix4x4::Scale(scale); + + Vector3 result = m.GetScale(); + + EXPECT_NEAR(result.x, 2.0f, 1e-5f); + EXPECT_NEAR(result.y, 3.0f, 1e-5f); + EXPECT_NEAR(result.z, 4.0f, 1e-5f); +} + +TEST(Math_Matrix4, Decompose_TRS) { + Vector3 originalPos(1, 2, 3); + Quaternion originalRot = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + Vector3 originalScale(2, 2, 2); + + Matrix4x4 m = Matrix4x4::TRS(originalPos, originalRot, originalScale); + + Vector3 pos, scale; + Quaternion rot; + m.Decompose(pos, rot, scale); + + EXPECT_NEAR(pos.x, originalPos.x, 1e-4f); + EXPECT_NEAR(pos.y, originalPos.y, 1e-4f); + EXPECT_NEAR(pos.z, originalPos.z, 1e-4f); + + EXPECT_NEAR(scale.x, originalScale.x, 1e-4f); + EXPECT_NEAR(scale.y, originalScale.y, 1e-4f); + EXPECT_NEAR(scale.z, originalScale.z, 1e-4f); +} + +TEST(Math_Matrix4, IndexOperator) { + Matrix4x4 m; + m[0][0] = 1.0f; + + EXPECT_FLOAT_EQ(m[0][0], 1.0f); + EXPECT_FLOAT_EQ(m.m[0][0], 1.0f); +} + +} // namespace diff --git a/tests/math/test_quaternion.cpp b/tests/math/test_quaternion.cpp new file mode 100644 index 00000000..6fe9639c --- /dev/null +++ b/tests/math/test_quaternion.cpp @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include + +using namespace XCEngine::Math; + +namespace { + +TEST(Math_Quaternion, DefaultConstructor) { + Quaternion q; + EXPECT_FLOAT_EQ(q.x, 0.0f); + EXPECT_FLOAT_EQ(q.y, 0.0f); + EXPECT_FLOAT_EQ(q.z, 0.0f); + EXPECT_FLOAT_EQ(q.w, 1.0f); +} + +TEST(Math_Quaternion, ParameterConstructor) { + Quaternion q(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(q.x, 1.0f); + EXPECT_FLOAT_EQ(q.y, 2.0f); + EXPECT_FLOAT_EQ(q.z, 3.0f); + EXPECT_FLOAT_EQ(q.w, 4.0f); +} + +TEST(Math_Quaternion, Identity) { + Quaternion q = Quaternion::Identity(); + EXPECT_FLOAT_EQ(q.x, 0.0f); + EXPECT_FLOAT_EQ(q.y, 0.0f); + EXPECT_FLOAT_EQ(q.z, 0.0f); + EXPECT_FLOAT_EQ(q.w, 1.0f); +} + +TEST(Math_Quaternion, FromAxisAngle) { + Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + + float sinHalfAngle = std::sin(PI * 0.25f); + float cosHalfAngle = std::cos(PI * 0.25f); + + EXPECT_NEAR(q.x, 0.0f, 1e-5f); + EXPECT_NEAR(q.y, sinHalfAngle, 1e-5f); + EXPECT_NEAR(q.z, 0.0f, 1e-5f); + EXPECT_NEAR(q.w, cosHalfAngle, 1e-5f); +} + +TEST(Math_Quaternion, FromAxisAngle_IdentityRotation) { + Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), 0.0f); + EXPECT_FLOAT_EQ(q.x, 0.0f); + EXPECT_FLOAT_EQ(q.y, 0.0f); + EXPECT_FLOAT_EQ(q.z, 0.0f); + EXPECT_FLOAT_EQ(q.w, 1.0f); +} + +TEST(Math_Quaternion, FromEulerAngles) { + Quaternion q = Quaternion::FromEulerAngles(PI * 0.5f, 0.0f, 0.0f); + + float sinHalfPitch = std::sin(PI * 0.25f); + float cosHalfPitch = std::cos(PI * 0.25f); + + EXPECT_NEAR(q.x, sinHalfPitch, 1e-5f); + EXPECT_NEAR(q.w, cosHalfPitch, 1e-5f); +} + +TEST(Math_Quaternion, ToEulerAngles_FromEulerAngles) { + Vector3 euler(PI * 0.5f, PI * 0.25f, PI * 0.125f); + Quaternion q = Quaternion::FromEulerAngles(euler.x, euler.y, euler.z); + Vector3 result = q.ToEulerAngles(); + + EXPECT_NEAR(result.x, euler.x, 1e-4f); + EXPECT_NEAR(result.y, euler.y, 1e-4f); + EXPECT_NEAR(result.z, euler.z, 1e-4f); +} + +TEST(Math_Quaternion, ToMatrix4x4_Identity) { + Quaternion q = Quaternion::Identity(); + Matrix4x4 m = q.ToMatrix4x4(); + + EXPECT_FLOAT_EQ(m.m[0][0], 1.0f); + EXPECT_FLOAT_EQ(m.m[1][1], 1.0f); + EXPECT_FLOAT_EQ(m.m[2][2], 1.0f); + EXPECT_FLOAT_EQ(m.m[3][3], 1.0f); +} + +TEST(Math_Quaternion, ToMatrix4x4_RotationY) { + Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + Matrix4x4 m = q.ToMatrix4x4(); + + EXPECT_NEAR(m.m[0][0], 0.0f, 1e-5f); + EXPECT_NEAR(m.m[0][2], 1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][0], -1.0f, 1e-5f); + EXPECT_NEAR(m.m[2][2], 0.0f, 1e-5f); +} + +TEST(Math_Quaternion, FromRotationMatrix) { + Matrix4x4 m = Matrix4x4::RotationY(PI * 0.5f); + Quaternion q = Quaternion::FromRotationMatrix(m); + + EXPECT_NEAR(std::abs(q.y), 0.707f, 1e-3f); +} + +TEST(Math_Quaternion, Slerp_IdentityToIdentity) { + Quaternion a = Quaternion::Identity(); + Quaternion b = Quaternion::Identity(); + + Quaternion result = Quaternion::Slerp(a, b, 0.5f); + + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); + EXPECT_FLOAT_EQ(result.w, 1.0f); +} + +TEST(Math_Quaternion, Slerp_QuarterRotation) { + Quaternion a = Quaternion::Identity(); + Quaternion b = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + + Quaternion result = Quaternion::Slerp(a, b, 0.5f); + + float expectedAngle = PI * 0.25f; + Quaternion expected = Quaternion::FromAxisAngle(Vector3::Up(), expectedAngle); + + EXPECT_NEAR(result.x, expected.x, 1e-4f); + EXPECT_NEAR(result.y, expected.y, 1e-4f); + EXPECT_NEAR(result.z, expected.z, 1e-4f); + EXPECT_NEAR(result.w, expected.w, 1e-4f); +} + +TEST(Math_Quaternion, Slerp_Start_ReturnsFirst) { + Quaternion a(0.1f, 0.2f, 0.3f, 0.9f); + Quaternion b(0.9f, 0.1f, 0.1f, 0.1f); + + Quaternion result = Quaternion::Slerp(a, b, 0.0f); + + EXPECT_NEAR(result.x, a.x, 1e-5f); + EXPECT_NEAR(result.y, a.y, 1e-5f); + EXPECT_NEAR(result.z, a.z, 1e-5f); + EXPECT_NEAR(result.w, a.w, 1e-5f); +} + +TEST(Math_Quaternion, Slerp_End_ReturnsSecond) { + Quaternion a(0.1f, 0.2f, 0.3f, 0.9f); + Quaternion b(0.9f, 0.1f, 0.1f, 0.1f); + + Quaternion result = Quaternion::Slerp(a, b, 1.0f); + + EXPECT_NEAR(result.x, b.x, 1e-4f); + EXPECT_NEAR(result.y, b.y, 1e-4f); + EXPECT_NEAR(result.z, b.z, 1e-4f); + EXPECT_NEAR(result.w, b.w, 1e-4f); +} + +TEST(Math_Quaternion, Slerp_ClampsT) { + Quaternion a = Quaternion::Identity(); + Quaternion b = Quaternion::FromAxisAngle(Vector3::Up(), PI); + + Quaternion result1 = Quaternion::Slerp(a, b, -0.5f); + Quaternion result2 = Quaternion::Slerp(a, b, 1.5f); + + EXPECT_NEAR(result1.x, a.x, 1e-5f); + EXPECT_NEAR(result2.x, b.x, 1e-4f); +} + +TEST(Math_Quaternion, LookRotation_Forward) { + Quaternion q = Quaternion::LookRotation(Vector3::Forward()); + + EXPECT_NEAR(q.x, 0.0f, 1e-5f); + EXPECT_NEAR(q.y, 0.0f, 1e-5f); + EXPECT_NEAR(q.z, 0.0f, 1e-5f); + EXPECT_NEAR(q.w, 1.0f, 1e-5f); +} + +TEST(Math_Quaternion, LookRotation_Up) { + Quaternion q = Quaternion::LookRotation(Vector3::Up()); + + EXPECT_NEAR(q.x, 0.707f, 1e-3f); + EXPECT_NEAR(q.w, 0.707f, 1e-3f); +} + +TEST(Math_Quaternion, LookRotation_Right) { + Quaternion q = Quaternion::LookRotation(Vector3::Right()); + + EXPECT_NEAR(q.y, -0.707f, 1e-3f); + EXPECT_NEAR(q.w, 0.707f, 1e-3f); +} + +TEST(Math_Quaternion, Multiply_ProducesRotation) { + Quaternion q1 = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + Quaternion q2 = Quaternion::FromAxisAngle(Vector3::Right(), PI * 0.5f); + + Quaternion result = q1 * q2; + + EXPECT_TRUE(result.Magnitude() > 0.0f); +} + +TEST(Math_Quaternion, Multiply_Identity) { + Quaternion q(0.1f, 0.2f, 0.3f, 0.9f); + Quaternion identity = Quaternion::Identity(); + + Quaternion result = q * identity; + + EXPECT_NEAR(result.x, q.x, 1e-5f); + EXPECT_NEAR(result.y, q.y, 1e-5f); + EXPECT_NEAR(result.z, q.z, 1e-5f); + EXPECT_NEAR(result.w, q.w, 1e-5f); +} + +TEST(Math_Quaternion, Multiply_Vector3) { + Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + Vector3 v = Vector3::Right(); + + Vector3 result = q * v; + + EXPECT_NEAR(result.x, 0.0f, 1e-5f); + EXPECT_NEAR(result.z, 1.0f, 1e-5f); +} + +TEST(Math_Quaternion, Inverse_Identity) { + Quaternion q = Quaternion::Identity(); + Quaternion inv = q.Inverse(); + + EXPECT_FLOAT_EQ(inv.x, 0.0f); + EXPECT_FLOAT_EQ(inv.y, 0.0f); + EXPECT_FLOAT_EQ(inv.z, 0.0f); + EXPECT_FLOAT_EQ(inv.w, 1.0f); +} + +TEST(Math_Quaternion, Inverse_Rotation) { + Quaternion q = Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.5f); + Quaternion inv = q.Inverse(); + + Quaternion product = q * inv; + + EXPECT_NEAR(product.x, 0.0f, 1e-5f); + EXPECT_NEAR(product.y, 0.0f, 1e-5f); + EXPECT_NEAR(product.z, 0.0f, 1e-5f); + EXPECT_NEAR(product.w, 1.0f, 1e-5f); +} + +TEST(Math_Quaternion, Dot) { + Quaternion a(1, 0, 0, 0); + Quaternion b(1, 0, 0, 0); + + float result = a.Dot(b); + + EXPECT_FLOAT_EQ(result, 1.0f); +} + +TEST(Math_Quaternion, Dot_Orthogonal) { + Quaternion a(1, 0, 0, 0); + Quaternion b(0, 1, 0, 0); + + float result = a.Dot(b); + + EXPECT_FLOAT_EQ(result, 0.0f); +} + +TEST(Math_Quaternion, Magnitude) { + Quaternion q(1, 0, 0, 0); + EXPECT_FLOAT_EQ(q.Magnitude(), 1.0f); +} + +TEST(Math_Quaternion, Normalized) { + Quaternion q(1, 2, 3, 4); + Quaternion normalized = q.Normalized(); + + EXPECT_NEAR(normalized.Magnitude(), 1.0f, 1e-5f); +} + +TEST(Math_Quaternion, Normalize) { + Quaternion q(1, 2, 3, 4); + Quaternion result = Quaternion::Normalize(q); + + EXPECT_NEAR(result.Magnitude(), 1.0f, 1e-5f); +} + +} // namespace diff --git a/tests/math/test_vector.cpp b/tests/math/test_vector.cpp new file mode 100644 index 00000000..ba4311a9 --- /dev/null +++ b/tests/math/test_vector.cpp @@ -0,0 +1,346 @@ +#include +#include +#include +#include +#include + +using namespace XCEngine::Math; + +namespace { + +// ============================================================ +// Vector2 Tests +// ============================================================ + +TEST(Math_Vector2, DefaultConstructor_InitializesToZero) { + Vector2 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); +} + +TEST(Math_Vector2, ParameterConstructor) { + Vector2 v(3.0f, 4.0f); + EXPECT_FLOAT_EQ(v.x, 3.0f); + EXPECT_FLOAT_EQ(v.y, 4.0f); +} + +TEST(Math_Vector2, StaticFactories) { + EXPECT_EQ(Vector2::Zero(), Vector2(0, 0)); + EXPECT_EQ(Vector2::One(), Vector2(1, 1)); + EXPECT_EQ(Vector2::Up(), Vector2(0, 1)); + EXPECT_EQ(Vector2::Down(), Vector2(0, -1)); + EXPECT_EQ(Vector2::Right(), Vector2(1, 0)); + EXPECT_EQ(Vector2::Left(), Vector2(-1, 0)); +} + +TEST(Math_Vector2, Dot_Orthogonal_ReturnsZero) { + EXPECT_FLOAT_EQ(Vector2::Dot(Vector2::Right(), Vector2::Up()), 0.0f); +} + +TEST(Math_Vector2, Dot_SameDirection_ReturnsMagnitudeProduct) { + EXPECT_FLOAT_EQ(Vector2::Dot(Vector2(1, 0), Vector2(2, 0)), 2.0f); +} + +TEST(Math_Vector2, Cross_ReturnsScalar) { + EXPECT_FLOAT_EQ(Vector2::Cross(Vector2::Right(), Vector2::Up()), 1.0f); +} + +TEST(Math_Vector2, Magnitude) { + EXPECT_FLOAT_EQ(Vector2(3, 4).Magnitude(), 5.0f); +} + +TEST(Math_Vector2, SqrMagnitude) { + EXPECT_FLOAT_EQ(Vector2(3, 4).SqrMagnitude(), 25.0f); +} + +TEST(Math_Vector2, Normalize_NonZero_ReturnsUnitVector) { + Vector2 v = Vector2(3, 4).Normalized(); + EXPECT_NEAR(v.Magnitude(), 1.0f, 1e-6f); +} + +TEST(Math_Vector2, Normalize_ZeroVector_ReturnsZeroVector) { + Vector2 v = Vector2::Zero(); + Vector2 normalized = Vector2::Normalize(v); + EXPECT_FLOAT_EQ(normalized.x, 0.0f); + EXPECT_FLOAT_EQ(normalized.y, 0.0f); +} + +TEST(Math_Vector2, Lerp) { + Vector2 a(0, 0); + Vector2 b(10, 10); + + Vector2 result = Vector2::Lerp(a, b, 0.5f); + EXPECT_FLOAT_EQ(result.x, 5.0f); + EXPECT_FLOAT_EQ(result.y, 5.0f); +} + +TEST(Math_Vector2, Lerp_ClampsToZero) { + Vector2 a(0, 0); + Vector2 b(10, 10); + + Vector2 result = Vector2::Lerp(a, b, -0.5f); + EXPECT_FLOAT_EQ(result.x, 0.0f); +} + +TEST(Math_Vector2, Lerp_ClampsToOne) { + Vector2 a(0, 0); + Vector2 b(10, 10); + + Vector2 result = Vector2::Lerp(a, b, 1.5f); + EXPECT_FLOAT_EQ(result.x, 10.0f); +} + +TEST(Math_Vector2, MoveTowards) { + Vector2 current(0, 0); + Vector2 target(10, 0); + + Vector2 result = Vector2::MoveTowards(current, target, 3.0f); + EXPECT_FLOAT_EQ(result.x, 3.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); +} + +TEST(Math_Vector2, MoveTowards_AlreadyAtTarget) { + Vector2 current(10, 10); + Vector2 target(10, 10); + + Vector2 result = Vector2::MoveTowards(current, target, 5.0f); + EXPECT_EQ(result, target); +} + +TEST(Math_Vector2, Operators) { + Vector2 a(1, 2); + Vector2 b(3, 4); + + Vector2 sum = a + b; + EXPECT_FLOAT_EQ(sum.x, 4.0f); + EXPECT_FLOAT_EQ(sum.y, 6.0f); + + Vector2 diff = b - a; + EXPECT_FLOAT_EQ(diff.x, 2.0f); + EXPECT_FLOAT_EQ(diff.y, 2.0f); + + Vector2 scaled = a * 2.0f; + EXPECT_FLOAT_EQ(scaled.x, 2.0f); + EXPECT_FLOAT_EQ(scaled.y, 4.0f); + + Vector2 divided = a / 2.0f; + EXPECT_FLOAT_EQ(divided.x, 0.5f); + EXPECT_FLOAT_EQ(divided.y, 1.0f); +} + +TEST(Math_Vector2, Equality) { + Vector2 a(1.0f, 2.0f); + Vector2 b(1.0f, 2.0f); + Vector2 c(1.0f, 3.0f); + + EXPECT_TRUE(a == b); + EXPECT_FALSE(a == c); +} + +// ============================================================ +// Vector3 Tests +// ============================================================ + +TEST(Math_Vector3, DefaultConstructor_InitializesToZero) { + Vector3 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); + EXPECT_FLOAT_EQ(v.z, 0.0f); +} + +TEST(Math_Vector3, ParameterConstructor) { + Vector3 v(1.0f, 2.0f, 3.0f); + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); + EXPECT_FLOAT_EQ(v.z, 3.0f); +} + +TEST(Math_Vector3, StaticFactories) { + EXPECT_EQ(Vector3::Zero(), Vector3(0, 0, 0)); + EXPECT_EQ(Vector3::One(), Vector3(1, 1, 1)); + EXPECT_EQ(Vector3::Forward(), Vector3(0, 0, 1)); + EXPECT_EQ(Vector3::Back(), Vector3(0, 0, -1)); + EXPECT_EQ(Vector3::Up(), Vector3(0, 1, 0)); + EXPECT_EQ(Vector3::Down(), Vector3(0, -1, 0)); + EXPECT_EQ(Vector3::Right(), Vector3(1, 0, 0)); + EXPECT_EQ(Vector3::Left(), Vector3(-1, 0, 0)); +} + +TEST(Math_Vector3, Dot_Orthogonal_ReturnsZero) { + EXPECT_FLOAT_EQ(Vector3::Dot(Vector3::Right(), Vector3::Up()), 0.0f); +} + +TEST(Math_Vector3, Cross_Orthogonal_ReturnsPerpendicular) { + Vector3 result = Vector3::Cross(Vector3::Right(), Vector3::Up()); + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 1.0f); +} + +TEST(Math_Vector3, Cross_SameVector_ReturnsZero) { + Vector3 result = Vector3::Cross(Vector3::Right(), Vector3::Right()); + EXPECT_FLOAT_EQ(result.x, 0.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST(Math_Vector3, Magnitude) { + EXPECT_FLOAT_EQ(Vector3(1, 2, 2).Magnitude(), 3.0f); +} + +TEST(Math_Vector3, SqrMagnitude) { + EXPECT_FLOAT_EQ(Vector3(1, 2, 2).SqrMagnitude(), 9.0f); +} + +TEST(Math_Vector3, Normalize) { + Vector3 v = Vector3(3, 4, 0).Normalized(); + EXPECT_NEAR(v.Magnitude(), 1.0f, 1e-6f); +} + +TEST(Math_Vector3, Lerp) { + Vector3 a(0, 0, 0); + Vector3 b(10, 10, 10); + + Vector3 result = Vector3::Lerp(a, b, 0.5f); + EXPECT_FLOAT_EQ(result.x, 5.0f); + EXPECT_FLOAT_EQ(result.y, 5.0f); + EXPECT_FLOAT_EQ(result.z, 5.0f); +} + +TEST(Math_Vector3, Project_OntoNormal) { + Vector3 v(1, 1, 0); + Vector3 normal(1, 0, 0); + + Vector3 result = Vector3::Project(v, normal); + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 0.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST(Math_Vector3, ProjectOnPlane) { + Vector3 v(1, 1, 1); + Vector3 planeNormal(0, 0, 1); + + Vector3 result = Vector3::ProjectOnPlane(v, planeNormal); + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 1.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST(Math_Vector3, Angle) { + float angle = Vector3::Angle(Vector3::Right(), Vector3::Up()); + EXPECT_NEAR(angle, 90.0f, 1e-5f); +} + +TEST(Math_Vector3, Reflect) { + Vector3 direction(1, -1, 0); + Vector3 normal(0, 1, 0); + + Vector3 result = Vector3::Reflect(direction, normal); + EXPECT_FLOAT_EQ(result.x, 1.0f); + EXPECT_FLOAT_EQ(result.y, 1.0f); + EXPECT_FLOAT_EQ(result.z, 0.0f); +} + +TEST(Math_Vector3, MoveTowards) { + Vector3 current(0, 0, 0); + Vector3 target(10, 0, 0); + + Vector3 result = Vector3::MoveTowards(current, target, 4.0f); + EXPECT_FLOAT_EQ(result.x, 4.0f); +} + +TEST(Math_Vector3, Operators) { + Vector3 a(1, 2, 3); + Vector3 b(4, 5, 6); + + Vector3 sum = a + b; + EXPECT_FLOAT_EQ(sum.x, 5.0f); + EXPECT_FLOAT_EQ(sum.y, 7.0f); + EXPECT_FLOAT_EQ(sum.z, 9.0f); + + Vector3 diff = b - a; + EXPECT_FLOAT_EQ(diff.x, 3.0f); + EXPECT_FLOAT_EQ(diff.y, 3.0f); + EXPECT_FLOAT_EQ(diff.z, 3.0f); + + Vector3 scaled = a * 2.0f; + EXPECT_FLOAT_EQ(scaled.x, 2.0f); + EXPECT_FLOAT_EQ(scaled.y, 4.0f); + EXPECT_FLOAT_EQ(scaled.z, 6.0f); +} + +TEST(Math_Vector3, IndexOperator) { + Vector3 v(1, 2, 3); + EXPECT_FLOAT_EQ(v[0], 1.0f); + EXPECT_FLOAT_EQ(v[1], 2.0f); + EXPECT_FLOAT_EQ(v[2], 3.0f); +} + +// ============================================================ +// Vector4 Tests +// ============================================================ + +TEST(Math_Vector4, DefaultConstructor_InitializesToZero) { + Vector4 v; + EXPECT_FLOAT_EQ(v.x, 0.0f); + EXPECT_FLOAT_EQ(v.y, 0.0f); + EXPECT_FLOAT_EQ(v.z, 0.0f); + EXPECT_FLOAT_EQ(v.w, 0.0f); +} + +TEST(Math_Vector4, ParameterConstructor) { + Vector4 v(1.0f, 2.0f, 3.0f, 4.0f); + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); + EXPECT_FLOAT_EQ(v.z, 3.0f); + EXPECT_FLOAT_EQ(v.w, 4.0f); +} + +TEST(Math_Vector4, ConstructFromVector3) { + Vector3 v3(1, 2, 3); + Vector4 v(v3, 4.0f); + + EXPECT_FLOAT_EQ(v.x, 1.0f); + EXPECT_FLOAT_EQ(v.y, 2.0f); + EXPECT_FLOAT_EQ(v.z, 3.0f); + EXPECT_FLOAT_EQ(v.w, 4.0f); +} + +TEST(Math_Vector4, StaticFactories) { + EXPECT_EQ(Vector4::Zero(), Vector4(0, 0, 0, 0)); + EXPECT_EQ(Vector4::One(), Vector4(1, 1, 1, 1)); +} + +TEST(Math_Vector4, Dot) { + Vector4 a(1, 2, 3, 4); + Vector4 b(2, 3, 4, 5); + + float result = Vector4::Dot(a, b); + EXPECT_FLOAT_EQ(result, 40.0f); +} + +TEST(Math_Vector4, ToVector3) { + Vector4 v(1, 2, 3, 4); + Vector3 v3 = v.ToVector3(); + + EXPECT_FLOAT_EQ(v3.x, 1.0f); + EXPECT_FLOAT_EQ(v3.y, 2.0f); + EXPECT_FLOAT_EQ(v3.z, 3.0f); +} + +TEST(Math_Vector4, Operators) { + Vector4 a(1, 2, 3, 4); + Vector4 b(5, 6, 7, 8); + + Vector4 sum = a + b; + EXPECT_FLOAT_EQ(sum.x, 6.0f); + EXPECT_FLOAT_EQ(sum.y, 8.0f); + EXPECT_FLOAT_EQ(sum.z, 10.0f); + EXPECT_FLOAT_EQ(sum.w, 12.0f); + + Vector4 scaled = a * 2.0f; + EXPECT_FLOAT_EQ(scaled.x, 2.0f); +} + +} // namespace