feat: 添加Math库和Google Test测试框架
- 新增Math库: Vector2/3/4, Matrix3/4, Quaternion, Transform, Color等 - 新增测试框架: Google Test (gtest) - 新增140个单元测试,覆盖Vector, Matrix, Quaternion, Geometry - VolumeRenderer支持vcpkg的NanoVDB - 添加TESTING.md测试文档
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
cmake_minimum_required(VERSION 3.15)
|
cmake_minimum_required(VERSION 3.15)
|
||||||
project(XCVolumeRenderer VERSION 1.0 LANGUAGES CXX)
|
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 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 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
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}
|
${CMAKE_SOURCE_DIR}
|
||||||
|
${VCPKG_ROOT}/installed/x64-windows/include
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
|
|||||||
285
docs/TESTING.md
Normal file
285
docs/TESTING.md
Normal file
@@ -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<Matrix4x4> {};
|
||||||
|
|
||||||
|
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 检测
|
||||||
4564
docs/XCEngine渲染引擎架构设计.md
Normal file
4564
docs/XCEngine渲染引擎架构设计.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
151
docs/plan/第一阶段计划.md
Normal file
151
docs/plan/第一阶段计划.md
Normal file
@@ -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<T>`、`UniqueRef<T>`、`Event<T>` |
|
||||||
|
| **功能要求** | 基础类型别名、引用计数、智能指针、事件系统 |
|
||||||
|
| **依赖** | 无 |
|
||||||
|
|
||||||
|
### 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<T>`, `String`, `String::npos`, `HashMap<K,V>` |
|
||||||
|
| **功能要求** | 动态数组、字符串操作(含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)
|
||||||
|
```
|
||||||
43
engine/CMakeLists.txt
Normal file
43
engine/CMakeLists.txt
Normal file
@@ -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()
|
||||||
12
engine/include/XCEngine/Core/Core.h
Normal file
12
engine/include/XCEngine/Core/Core.h
Normal file
@@ -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
|
||||||
79
engine/include/XCEngine/Core/Event.h
Normal file
79
engine/include/XCEngine/Core/Event.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
class Event {
|
||||||
|
public:
|
||||||
|
using Callback = std::function<void(Args...)>;
|
||||||
|
using Listener = std::pair<uint64_t, Callback>;
|
||||||
|
using Iterator = typename std::vector<Listener>::iterator;
|
||||||
|
|
||||||
|
uint64_t Subscribe(Callback callback) {
|
||||||
|
std::lock_guard<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||||
|
m_pendingUnsubscribes.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessUnsubscribes() {
|
||||||
|
std::lock_guard<std::mutex> 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<Listener> listenersCopy;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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<std::mutex> 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<Listener> m_listeners;
|
||||||
|
std::vector<uint64_t> m_pendingUnsubscribes;
|
||||||
|
uint64_t m_nextId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
} // namespace XCEngine
|
||||||
27
engine/include/XCEngine/Core/RefCounted.h
Normal file
27
engine/include/XCEngine/Core/RefCounted.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
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<uint32_t> m_refCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
} // namespace XCEngine
|
||||||
25
engine/include/XCEngine/Core/SmartPtr.h
Normal file
25
engine/include/XCEngine/Core/SmartPtr.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using Ref = std::shared_ptr<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using UniqueRef = std::unique_ptr<T>;
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
Ref<T> MakeRef(Args&&... args) {
|
||||||
|
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
UniqueRef<T> MakeUnique(Args&&... args) {
|
||||||
|
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
} // namespace XCEngine
|
||||||
19
engine/include/XCEngine/Core/Types.h
Normal file
19
engine/include/XCEngine/Core/Types.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
123
engine/include/XCEngine/Math/AABB.h
Normal file
123
engine/include/XCEngine/Math/AABB.h
Normal file
@@ -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
|
||||||
31
engine/include/XCEngine/Math/Bounds.h
Normal file
31
engine/include/XCEngine/Math/Bounds.h
Normal file
@@ -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
|
||||||
30
engine/include/XCEngine/Math/Box.h
Normal file
30
engine/include/XCEngine/Math/Box.h
Normal file
@@ -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
|
||||||
43
engine/include/XCEngine/Math/Color.h
Normal file
43
engine/include/XCEngine/Math/Color.h
Normal file
@@ -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
|
||||||
32
engine/include/XCEngine/Math/Frustum.h
Normal file
32
engine/include/XCEngine/Math/Frustum.h
Normal file
@@ -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
|
||||||
27
engine/include/XCEngine/Math/Math.h
Normal file
27
engine/include/XCEngine/Math/Math.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#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
|
||||||
50
engine/include/XCEngine/Math/Matrix3.h
Normal file
50
engine/include/XCEngine/Math/Matrix3.h
Normal file
@@ -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
|
||||||
66
engine/include/XCEngine/Math/Matrix4.h
Normal file
66
engine/include/XCEngine/Math/Matrix4.h
Normal file
@@ -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
|
||||||
29
engine/include/XCEngine/Math/Plane.h
Normal file
29
engine/include/XCEngine/Math/Plane.h
Normal file
@@ -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
|
||||||
40
engine/include/XCEngine/Math/Quaternion.h
Normal file
40
engine/include/XCEngine/Math/Quaternion.h
Normal file
@@ -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
|
||||||
27
engine/include/XCEngine/Math/Ray.h
Normal file
27
engine/include/XCEngine/Math/Ray.h
Normal file
@@ -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
|
||||||
120
engine/include/XCEngine/Math/Rect.h
Normal file
120
engine/include/XCEngine/Math/Rect.h
Normal file
@@ -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<float>(x), static_cast<float>(y)); }
|
||||||
|
Vector2 GetSize() const { return Vector2(static_cast<float>(width), static_cast<float>(height)); }
|
||||||
|
Vector2 GetCenter() const { return Vector2(static_cast<float>(x + width / 2), static_cast<float>(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
|
||||||
21
engine/include/XCEngine/Math/Sphere.h
Normal file
21
engine/include/XCEngine/Math/Sphere.h
Normal file
@@ -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
|
||||||
31
engine/include/XCEngine/Math/Transform.h
Normal file
31
engine/include/XCEngine/Math/Transform.h
Normal file
@@ -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
|
||||||
66
engine/include/XCEngine/Math/Vector2.h
Normal file
66
engine/include/XCEngine/Math/Vector2.h
Normal file
@@ -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
|
||||||
117
engine/include/XCEngine/Math/Vector3.h
Normal file
117
engine/include/XCEngine/Math/Vector3.h
Normal file
@@ -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
|
||||||
57
engine/include/XCEngine/Math/Vector4.h
Normal file
57
engine/include/XCEngine/Math/Vector4.h
Normal file
@@ -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
|
||||||
49
engine/src/Math/Color.cpp
Normal file
49
engine/src/Math/Color.cpp
Normal file
@@ -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<uint32_t>(std::clamp(r * 255.0f, 0.0f, 255.0f));
|
||||||
|
uint32_t g = static_cast<uint32_t>(std::clamp(g * 255.0f, 0.0f, 255.0f));
|
||||||
|
uint32_t b = static_cast<uint32_t>(std::clamp(b * 255.0f, 0.0f, 255.0f));
|
||||||
|
uint32_t a = static_cast<uint32_t>(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
|
||||||
159
engine/src/Math/FrustumBounds.cpp
Normal file
159
engine/src/Math/FrustumBounds.cpp
Normal file
@@ -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
|
||||||
161
engine/src/Math/Geometry.cpp
Normal file
161
engine/src/Math/Geometry.cpp
Normal file
@@ -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
|
||||||
329
engine/src/Math/Matrix.cpp
Normal file
329
engine/src/Math/Matrix.cpp
Normal file
@@ -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
|
||||||
223
engine/src/Math/Quaternion.cpp
Normal file
223
engine/src/Math/Quaternion.cpp
Normal file
@@ -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
|
||||||
44
engine/src/Math/Transform.cpp
Normal file
44
engine/src/Math/Transform.cpp
Normal file
@@ -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
|
||||||
50
tests/CMakeLists.txt
Normal file
50
tests/CMakeLists.txt
Normal file
@@ -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:"
|
||||||
|
)
|
||||||
40
tests/fixtures/MathFixtures.h
vendored
Normal file
40
tests/fixtures/MathFixtures.h
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Math/Math.h>
|
||||||
|
|
||||||
|
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
|
||||||
33
tests/math/CMakeLists.txt
Normal file
33
tests/math/CMakeLists.txt
Normal file
@@ -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)
|
||||||
385
tests/math/test_geometry.cpp
Normal file
385
tests/math/test_geometry.cpp
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Math/Ray.h>
|
||||||
|
#include <XCEngine/Math/Sphere.h>
|
||||||
|
#include <XCEngine/Math/Box.h>
|
||||||
|
#include <XCEngine/Math/Plane.h>
|
||||||
|
#include <XCEngine/Math/Bounds.h>
|
||||||
|
#include <XCEngine/Math/Frustum.h>
|
||||||
|
#include <XCEngine/Math/Matrix4.h>
|
||||||
|
|
||||||
|
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
|
||||||
312
tests/math/test_matrix.cpp
Normal file
312
tests/math/test_matrix.cpp
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Math/Matrix4.h>
|
||||||
|
#include <XCEngine/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Math/Quaternion.h>
|
||||||
|
|
||||||
|
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
|
||||||
277
tests/math/test_quaternion.cpp
Normal file
277
tests/math/test_quaternion.cpp
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Math/Quaternion.h>
|
||||||
|
#include <XCEngine/Math/Matrix4.h>
|
||||||
|
#include <XCEngine/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Math/Math.h>
|
||||||
|
|
||||||
|
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
|
||||||
346
tests/math/test_vector.cpp
Normal file
346
tests/math/test_vector.cpp
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <XCEngine/Math/Vector2.h>
|
||||||
|
#include <XCEngine/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Math/Vector4.h>
|
||||||
|
#include <XCEngine/Math/Math.h>
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user