Compare commits

...

5 Commits

Author SHA1 Message Date
fd2289cee3 Implement V3::Direction 2024-01-15 15:20:32 -05:00
03f0193df7 Implement M3x3::SetColumn 2024-01-15 15:17:39 -05:00
1eae732718 Implement D3DOrtho 2024-01-12 10:27:36 -05:00
32046d88cd Implement Diagonal 2024-01-12 05:53:12 -05:00
93759ba545 Jim 2024-01-12 05:17:01 -05:00
11 changed files with 351 additions and 33 deletions

View File

@@ -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)

View File

@@ -1,3 +1,4 @@
#include <J3ML/LinearAlgebra/Vector2.h>
#include <J3ML/LinearAlgebra/Vector3.h>
#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

View File

@@ -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&);
};
}

View File

@@ -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!!!

View File

@@ -43,16 +43,39 @@ namespace LinearAlgebra {
Vector3 GetRow(int index) const;
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;

View File

@@ -1,42 +1,79 @@
#pragma once
#include <J3ML/LinearAlgebra.h>
#include <J3ML/LinearAlgebra/Quaternion.h>
namespace LinearAlgebra {
/// 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,
* and translations, which a 3-by-3 cannot represent.
* The elements of this matrix are
* m_00, m_01, m_02, m_03
* m_10, m_11, m_12, m_13
* m_20, m_21, m_22, xm_23,
* m_30, m_31, m_32, m_33
*
* The element m_yx is the value on the row y and column x.
* You can access m_yx using the double-bracket notation m[y][x]
*/
/* This matrix can represent the most generic form of transformations for 3D objects,
* including perspective projections, which a 4-by-3 cannot store,
* and translations, which a 3-by-3 cannot represent.
* The elements of this matrix are
* m_00, m_01, m_02, m_03
* m_10, m_11, m_12, m_13
* m_20, m_21, m_22, am_23,
* m_30, m_31, m_32, m_33
*
* The element m_yx is the value on the row y and column x.
* You can access m_yx using the double-bracket notation m[y][x]
*/
class Matrix4x4 {
public:
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);
Vector3 GetTranslatePart() const;
Matrix3x3 GetRotatePart() const
{
return Matrix3x3 {
At(0, 0), At(0, 1), At(0, 2),
At(1, 0), At(1, 1), At(1, 2),
At(2, 0), At(2, 1), At(2, 2)
};
}
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,7 +84,10 @@ namespace LinearAlgebra {
// If the determinant is nonzero, this matrix is invertible.
float Determinant() const;
Matrix4x4 Inverse() const;
Matrix4x4 Inverse() const
{
}
Matrix4x4 Transpose() const;
@@ -55,12 +95,31 @@ namespace LinearAlgebra {
Vector3 Transform(const Vector3& rhs) const;
Vector4 Transform(const Vector4& rhs) const;
static Matrix4x4 D3DOrthoProjLH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 D3DOrthoProjRH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 D3DPerspProjLH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 D3DPerspProjRH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 OpenGLOrthoProjLH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 OpenGLOrthoProjRH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 OpenGLPerspProjLH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
static Matrix4x4 OpenGLPerspProjRH(float nearPlane, float farPlane, float hViewportSize, float vViewportSize);
Vector3 GetTranslationComponent() const;
Matrix3x3 GetRotationComponent() const;
Vector4 GetRow() const;
Vector4 GetColumn() const;
Vector4 operator[](int row)
{
return Vector4{elems[row][0], elems[row][1], elems[row][2], elems[row][3]};
}
protected:
float elems[4][4];
void SetMatrixRotatePart(Matrix4x4 &m, const Quaternion &q);
};
}

View File

@@ -30,6 +30,38 @@ public:
static const Vector3 Backward;
static const Vector3 NaN;
static void Orthonormalize(Vector3& a, Vector3& b)
{
a = a.Normalize();
b = b - b.ProjectToNorm(a);
b = b.Normalize();
}
//Returns the DirectionVector for a given angle.
Vector3 Direction(const Vector3 &rhs) const;
static void Orthonormalize(Vector3& a, Vector3& b, Vector3& c)
{
a = a.Normalize();
b = b - b.ProjectToNorm(a);
b = b.Normalize();
c = c - c.ProjectToNorm(a);
c = c - c.ProjectToNorm(b);
c = c.Normalize();
}
bool AreOrthonormal(const Vector3& a, const Vector3& b, float epsilon)
{
}
Vector3 ProjectToNorm(const Vector3& direction)
{
return direction * this->Dot(direction);
}
float GetX() const;
float GetY() const;
float GetZ() const;

View File

@@ -1,8 +1,17 @@
#include <J3ML/LinearAlgebra/AxisAngle.h>
#include <J3ML/LinearAlgebra/Quaternion.h>
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)
};
}
}

View File

@@ -196,5 +196,100 @@ 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;
}
void Matrix3x3::SetColumn(int i, const Vector3& vec)
{
elems[0][i] = vec.x;
elems[1][i] = vec.y;
elems[2][i] = 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;
}
Vector3 Matrix3x3::Diagonal() const {
return {elems[0][0],
elems[1][1],
elems[2][2]
};
}
}

View File

@@ -77,4 +77,95 @@ 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::SetMatrixRotatePart(Matrix4x4 &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;
elems[0][0] = 1 - 2*(y*y + z*z); elems[0][1] = 2*(x*y - z*w); elems[0][2] = 2*(x*z + y*w);
elems[1][0] = 2*(x*y + z*w); elems[1][1] = 1 - 2*(x*x + z*z); elems[1][2] = 2*(y*z - x*w);
elems[2][0] = 2*(x*z - y*w); elems[2][1] = 2*(y*z + x*w); elems[2][2] = 1 - 2*(x*x + y*y);
}
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);
}
Matrix4x4 Matrix4x4::D3DOrthoProjLH(float n, float f, float h, float v) {
float p00 = 2.f / h; float p01 = 0; float p02 = 0; float p03 = 0.f;
float p10 = 0; float p11 = 2.f / v; float p12 = 0; float p13 = 0.f;
float p20 = 0; float p21 = 0; float p22 = 1.f / (f-n); float p23 = n / (n-f);
float p30 = 0; float p31 = 0; float p32 = 0.f; float p33 = 1.f;
return {p00, p01, p02, p03, p10, p11, p12, p13, p20, p21, p22, p23, p30, p31, p32, p33};
}
/** This function generates an orthographic projection matrix that maps from
the Direct3D view space to the Direct3D normalized viewport space as follows:
In Direct3D view space, we assume that the camera is positioned at the origin (0,0,0).
The camera looks directly towards the positive Z axis (0,0,1).
The -X axis spans to the left of the screen, +X goes to the right.
-Y goes to the bottom of the screen, +Y goes to the top.
After the transformation, we're in the Direct3D normalized viewport space as follows:
(-1,-1,0) is the bottom-left corner of the viewport at the near plane.
(1,1,0) is the top-right corner of the viewport at the near plane.
(0,0,0) is the center point at the near plane.
Coordinates with z=1 are at the far plane.
Examples:
(0,0,n) maps to (0,0,0).
(0,0,f) maps to (0,0,1).
(-h/2, -v/2, n) maps to (-1, -1, 0).
(h/2, v/2, f) maps to (1, 1, 1).
*/
Matrix4x4 Matrix4x4::D3DOrthoProjRH(float n, float f, float h, float v)
{
// D3DOrthoProjLH and D3DOrthoProjRH differ from each other in that the third column is negated.
// This corresponds to LH = RH * In, where In is a diagonal matrix with elements [1 1 -1 1].
float p00 = 2.f / h; float p01 = 0; float p02 = 0; float p03 = 0.f;
float p10 = 0; float p11 = 2.f / v; float p12 = 0; float p13 = 0.f;
float p20 = 0; float p21 = 0; float p22 = 1.f / (n-f); float p23 = n / (n-f);
float p30 = 0; float p31 = 0; float p32 = 0.f; float p33 = 1.f;
}
}

View File

@@ -301,5 +301,12 @@ namespace LinearAlgebra {
return lhs.AngleBetween(rhs);
}
Vector3 Vector3::Direction(const Vector3 &rhs) const {
float x = (cos(Math::Radians(rhs.y)) * cos(Math::Radians(rhs.x)));
float y = -sin(Math::Radians(rhs.x));
float z = (sin(Math::Radians(rhs.y)) * cos(Math::Radians(rhs.x)));
return {x, y, z};
}
#pragma endregion
}