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:
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
|
||||
Reference in New Issue
Block a user