diff --git a/CMakeLists.txt b/CMakeLists.txt index c3af1a6..7781f69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,10 @@ PROJECT(J3ML LANGUAGES CXX ) - if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) message(FATAL_ERROR "In-source builds are not allowed") endif() - - set(CMAKE_CXX_STANDARD 20) #set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (WIN32) diff --git a/include/J3ML/Geometry.h b/include/J3ML/Geometry.h index 8400e29..9ca5021 100644 --- a/include/J3ML/Geometry.h +++ b/include/J3ML/Geometry.h @@ -1,3 +1,4 @@ +#include #include #pragma once @@ -19,19 +20,12 @@ namespace Geometry { class Triangle2D; class Polygon2D; - struct IntersectionResult2D { - - }; + struct IntersectionResult2D {}; bool Intersects2D(LineSegment2D seg, Rectangle rect); IntersectionResult2D GetIntersection2D(LineSegment2D seg, Rectangle rect); - - - - - // A 3D axis-aligned bounding box // This data structure can be used to represent coarse bounds of objects, in situations where detailed triangle-level // computations can be avoided. In physics systems, bounding boxes are used as an efficient early-out test for geometry diff --git a/include/J3ML/LinearAlgebra/AxisAngle.h b/include/J3ML/LinearAlgebra/AxisAngle.h index 89580f7..12cc047 100644 --- a/include/J3ML/LinearAlgebra/AxisAngle.h +++ b/include/J3ML/LinearAlgebra/AxisAngle.h @@ -13,6 +13,12 @@ namespace LinearAlgebra float angle; public: AxisAngle(); - AxisAngle(const Vector3& axis, float angle); + + AxisAngle(const Vector3 &axis, float angle); + + EulerAngle ToEulerAngleXYZ() const; + + Quaternion ToQuaternion() const; + static AxisAngle FromEulerAngleXYZ(const EulerAngle&); }; } \ No newline at end of file diff --git a/include/J3ML/LinearAlgebra/EulerAngle.h b/include/J3ML/LinearAlgebra/EulerAngle.h index 3116e6d..ccd3838 100644 --- a/include/J3ML/LinearAlgebra/EulerAngle.h +++ b/include/J3ML/LinearAlgebra/EulerAngle.h @@ -11,8 +11,13 @@ public: EulerAngle(); EulerAngle(float pitch, float yaw, float roll); EulerAngle(const Vector3& vec) : pitch(vec.x), yaw(vec.y), roll(vec.z) {} - static EulerAngle FromRadians(float radians); - static EulerAngle FromDegrees(float degrees); + + AxisAngle ToAxisAngle() const; + + + explicit EulerAngle(const Quaternion& orientation); + explicit EulerAngle(const AxisAngle& orientation); + /// TODO: Implement separate upper and lower bounds /// Preserves internal value of euler angles, normalizes and clamps the output. /// This does not solve gimbal lock!!! diff --git a/include/J3ML/LinearAlgebra/Matrix3x3.h b/include/J3ML/LinearAlgebra/Matrix3x3.h index 6bec31e..3d1a16b 100644 --- a/include/J3ML/LinearAlgebra/Matrix3x3.h +++ b/include/J3ML/LinearAlgebra/Matrix3x3.h @@ -45,14 +45,36 @@ namespace LinearAlgebra { Vector3 GetColumn(int index) const; float At(int x, int y) const; - /// Creates a new M3x3 that rotates about the given axis by the given angle - static Matrix3x3 RotateAxisAngle(const Vector3& rhs); + void SetRotatePart(const Vector3& a, float angle); - static Matrix3x3 RotateFromTo(const Vector3& source, const Vector3& direction); + /// Creates a new M3x3 that rotates about the given axis by the given angle + static Matrix3x3 RotateAxisAngle(const Vector3& axis, float angleRadians); + + static Matrix3x3 RotateFromTo(const Vector3& source, const Vector3& direction) + { + + } + + void SetRow(int i, const Vector3 &vector3); + void SetColumn(int i, const Vector3& vector); + + void Orthonormalize(int c0, int c1, int c2) + { + Vector3 v0 = GetColumn(c0); + Vector3 v1 = GetColumn(c1); + Vector3 v2 = GetColumn(c2); + Vector3::Orthonormalize(v0, v1, v2); + SetColumn(c0, v0); + SetColumn(c1, v1); + SetColumn(c2, v2); + } static Matrix3x3 LookAt(const Vector3& forward, const Vector3& target, const Vector3& localUp, const Vector3& worldUp); - static Matrix3x3 FromQuat(const Quaternion& orientation); + static Matrix3x3 FromQuat(const Quaternion& orientation) + { + return Matrix3x3(orientation); + } Quaternion ToQuat() const; diff --git a/include/J3ML/LinearAlgebra/Matrix4x4.h b/include/J3ML/LinearAlgebra/Matrix4x4.h index 8d38b47..c97542b 100644 --- a/include/J3ML/LinearAlgebra/Matrix4x4.h +++ b/include/J3ML/LinearAlgebra/Matrix4x4.h @@ -1,8 +1,27 @@ #pragma once #include +#include namespace LinearAlgebra { + + + template + void SetMatrixRotatePart(Matrix &m, const Quaternion& q) + { + // See e.g. http://www.geometrictools.com/Documentation/LinearAlgebraicQuaternions.pdf . + const float x = q.x; + const float y = q.y; + const float z = q.z; + const float w = q.w; + m[0][0] = 1 - 2*(y*y + z*z); m[0][1] = 2*(x*y - z*w); m[0][2] = 2*(x*z + y*w); + m[1][0] = 2*(x*y + z*w); m[1][1] = 1 - 2*(x*x + z*z); m[1][2] = 2*(y*z - x*w); + m[2][0] = 2*(x*z - y*w); m[2][1] = 2*(y*z + x*w); m[2][2] = 1 - 2*(x*x + y*y); + } + + + + /// A 4-by-4 matrix for affine transformations and perspective projections of 3D geometry. /* This matrix can represent the most generic form of transformations for 3D objects, * including perspective projections, which a 4-by-3 cannot store, @@ -21,22 +40,50 @@ namespace LinearAlgebra { enum { Rows = 4 }; enum { Cols = 4 }; + // A constant matrix that has zeroes in all its entries static const Matrix4x4 Zero; + // A constant matrix that is the identity. static const Matrix4x4 Identity; + + // A compile-time constant float4x4 which has NaN in each element. + // For this constant, each element has the value of quet NaN, or Not-A-Number. + // Never compare a matrix to this value. Due to how IEEE floats work, "nan == nan" returns false! static const Matrix4x4 NaN; Matrix4x4() {} Matrix4x4(float val); + Matrix4x4(const Matrix3x3&); + Matrix4x4(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33); Matrix4x4(const Vector4& r1, const Vector4& r2, const Vector4& r3, const Vector4& r4); + + explicit Matrix4x4(const Quaternion& orientation); + + void SetTranslatePart(float translateX, float translateY, float translateZ); + + void SetTranslatePart(const Vector3& offset); + + void SetRotatePart(const Quaternion& q); + + + void SetRow(int row, const Vector3& rowVector, float m_r3); + void SetRow(int row, const Vector4& rowVector); + void SetRow(int row, float m_r0, float m_r1, float m_r2, float m_r3); + + + Matrix4x4(const Quaternion& orientation, const Vector3& translation); + Vector4 GetRow(int index) const; Vector4 GetColumn(int index) const; - float At(int x, int y) const; + float At(int x, int y) const + { + return elems[x][y]; + } Vector4 Diagonal() const; Vector4 WorldX() const; @@ -47,13 +94,20 @@ namespace LinearAlgebra { // If the determinant is nonzero, this matrix is invertible. float Determinant() const; - Matrix4x4 Inverse() const; + Matrix4x4 Inverse() const + { + + } Matrix4x4 Transpose() const; Vector2 Transform(const Vector2& rhs) const; Vector3 Transform(const Vector3& rhs) const; Vector4 Transform(const Vector4& rhs) const; + Matrix4x4 D3DOrthoProjLH(float n, float f, float h, float v) + { + + } Vector3 GetTranslationComponent() const; Matrix3x3 GetRotationComponent() const; diff --git a/src/J3ML/LinearAlgebra/AxisAngle.cpp b/src/J3ML/LinearAlgebra/AxisAngle.cpp index b0b3492..3a85944 100644 --- a/src/J3ML/LinearAlgebra/AxisAngle.cpp +++ b/src/J3ML/LinearAlgebra/AxisAngle.cpp @@ -1,8 +1,17 @@ #include - +#include namespace LinearAlgebra { AxisAngle::AxisAngle() : axis(Vector3::Zero) {} AxisAngle::AxisAngle(const Vector3 &axis, float angle) : axis(axis), angle(angle) {} + + Quaternion AxisAngle::ToQuaternion() const { + return { + axis.x * std::sin(angle/2), + axis.y * std::sin(angle/2), + axis.z * std::sin(angle/2), + std::cos(angle/2) + }; + } } \ No newline at end of file diff --git a/src/J3ML/LinearAlgebra/Matrix3x3.cpp b/src/J3ML/LinearAlgebra/Matrix3x3.cpp index eb1a733..97107f0 100644 --- a/src/J3ML/LinearAlgebra/Matrix3x3.cpp +++ b/src/J3ML/LinearAlgebra/Matrix3x3.cpp @@ -196,5 +196,87 @@ namespace LinearAlgebra { }; } + void Matrix3x3::SetRotatePart(const Vector3 &a, float angle) { + float s = std::sin(angle); + float c = std::cos(angle); + + const float c1 = 1.f - c; + + elems[0][0] = c+c1*a.x*a.x; + elems[1][0] = c1*a.x*a.y+s*a.z; + elems[2][0] = c1*a.x*a.z-s*a.y; + + elems[0][1] = c1*a.x*a.y-s*a.z; + elems[1][1] = c+c1*a.y*a.y; + elems[2][1] = c1*a.y*a.z+s*a.x; + + elems[0][2] = c1*a.x*a.z+s*a.y; + elems[1][2] = c1*a.y*a.z-s*a.x; + elems[2][2] = c+c1*a.z*a.z; + + } + + Matrix3x3 Matrix3x3::RotateAxisAngle(const Vector3 &axis, float angleRadians) { + Matrix3x3 r; + r.SetRotatePart(axis, angleRadians); + return r; + } + + void Matrix3x3::SetRow(int i, const Vector3 &vec) { + elems[i][0] = vec.x; + elems[i][1] = vec.y; + elems[i][2] = vec.z; + } + + Matrix3x3 + Matrix3x3::LookAt(const Vector3 &forward, const Vector3 &target, const Vector3 &localUp, const Vector3 &worldUp) { + // User must input proper normalized input direction vectors + // In the local space, the forward and up directions must be perpendicular to be well-formed. + // In the world space, the target direction and world up cannot be degenerate (co-linear) + // Generate the third basis vector in the local space; + Vector3 localRight = localUp.Cross(forward).Normalize(); + // A. Now we have an orthonormal linear basis {localRight, localUp, forward} for the object local space. + // Generate the third basis vector for the world space + Vector3 worldRight = worldUp.Cross(target).Normalize(); + // Since the input worldUp vector is not necessarily perpendicular to the target direction vector + // We need to compute the real world space up vector that the "head" of the object will point + // towards when the model is looking towards the desired target direction + Vector3 perpWorldUp = target.Cross(worldRight).Normalize(); + // B. Now we have an orthonormal linear basis {worldRight, perpWorldUp, targetDirection } for the desired target orientation. + // We want to build a matrix M that performs the following mapping: + // 1. localRight must be mapped to worldRight. (M * localRight = worldRight) + // 2. localUp must be mapped to perpWorldUp. (M * localUp = perpWorldUp) + // 3. localForward must be mapped to targetDirection. (M * localForward = targetDirection) + // i.e. we want to map the basis A to basis B. + + // This matrix M exists, and it is an orthonormal rotation matrix with a determinant of +1, because + // the bases A and B are orthonormal with the same handedness. + + // Below, use the notation that (a,b,c) is a 3x3 matrix with a as its first column, b second, and c third. + + // By algebraic manipulation, we can rewrite conditions 1, 2 and 3 in a matrix form: + // M * (localRight, localUp, localForward) = (worldRight, perpWorldUp, targetDirection) + // or M = (worldRight, perpWorldUp, targetDirection) * (localRight, localUp, localForward)^{-1}. + // or M = m1 * m2, where + + // m1 equals (worldRight, perpWorldUp, target): + Matrix3x3 m1(worldRight, perpWorldUp, target); + + // and m2 equals (localRight, localUp, localForward)^{-1}: + Matrix3x3 m2; + m2.SetRow(0, localRight); + m2.SetRow(1, localUp); + m2.SetRow(2, forward); + // Above we used the shortcut that for an orthonormal matrix M, M^{-1} = M^T. So set the rows + // and not the columns to directly produce the transpose, i.e. the inverse of (localRight, localUp, localForward). + + // Compute final M. + m2 = m1 * m2; + + // And fix any numeric stability issues by re-orthonormalizing the result. + m2.Orthonormalize(0, 1, 2); + return m2; + } + } diff --git a/src/J3ML/LinearAlgebra/Matrix4x4.cpp b/src/J3ML/LinearAlgebra/Matrix4x4.cpp index 6088c72..4d1e014 100644 --- a/src/J3ML/LinearAlgebra/Matrix4x4.cpp +++ b/src/J3ML/LinearAlgebra/Matrix4x4.cpp @@ -77,4 +77,41 @@ namespace LinearAlgebra { Matrix4x4::Matrix4x4(const Quaternion &orientation) { } + + void Matrix4x4::SetTranslatePart(float translateX, float translateY, float translateZ) { + elems[0][3] = translateX; + elems[1][3] = translateY; + elems[2][3] = translateZ; + } + + void Matrix4x4::SetTranslatePart(const Vector3 &offset) { + elems[0][3] = offset.x; + elems[1][3] = offset.y; + elems[2][3] = offset.z; + } + + void Matrix4x4::SetRotatePart(const Quaternion &q) { + SetMatrixRotatePart(*this, q); + } + + void Matrix4x4::SetRow(int row, const Vector3 &rowVector, float m_r3) { + SetRow(row, rowVector.x, rowVector.y, rowVector.z, m_r3); + } + + void Matrix4x4::SetRow(int row, const Vector4 &rowVector) { + SetRow(row, rowVector.x, rowVector.y, rowVector.z, rowVector.w); + } + + void Matrix4x4::SetRow(int row, float m_r0, float m_r1, float m_r2, float m_r3) { + elems[row][0] = m_r0; + elems[row][1] = m_r1; + elems[row][2] = m_r2; + elems[row][3] = m_r3; + } + + Matrix4x4::Matrix4x4(const Quaternion &orientation, const Vector3 &translation) { + SetRotatePart(orientation); + SetTranslatePart(translation); + SetRow(3, 0, 0, 0, 1); + } } \ No newline at end of file