Compare commits

...

5 Commits

Author SHA1 Message Date
13a68eea45 right-handed DirectionVector
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m49s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 30s
2024-11-27 19:46:58 -05:00
79e617b780 RoundTrip angle conversion.
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m23s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 31s
2024-11-19 15:39:06 -05:00
aaea5ff53e AxisAngle FromQuaternion
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 5m52s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 24s
2024-11-19 09:13:37 -05:00
2caa4c8412 Quaternion from EulerAngleXYZ
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m14s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 21s
2024-11-18 21:51:40 -05:00
bb1b1b5a13 Remove EulerAngle & Add EulerAngleXYZ.
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m38s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 26s
2024-11-18 20:39:08 -05:00
20 changed files with 529 additions and 661 deletions

View File

@@ -84,6 +84,9 @@ namespace J3ML::Math::Constants {
constexpr float RecipSqrt2Pi = 0.3989422804014326779399460599343818684758586311649346576659258296706579258993018385012523339073069364;
/// pi - https://www.mathsisfun.com/numbers/pi.html
constexpr float Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679;
/// pi * 0.5.
constexpr float HalfPi = 1.5707963267948966192313216916397514420985846996875529104874722961539082031431044993140174126710585;
/// e - https://www.mathsisfun.com/numbers/e-eulers-number.html
constexpr float EulersNumber = 2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274;
/// 2pi - The ratio of a circle's circumferecne to its radius, and the number of radians in one turn.

View File

@@ -2,28 +2,23 @@
#include <J3ML/LinearAlgebra/EulerAngle.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/LinearAlgebra/AxisAngle.hpp>
#include <J3ML/LinearAlgebra/Vector3.hpp>
namespace J3ML::LinearAlgebra
{
namespace J3ML::LinearAlgebra {
class AxisAngle;
}
/// Transitional datatype, not useful for internal representation of rotation
/// But has uses for conversion and manipulation.
class AxisAngle {
public:
Vector3 axis;
float angle;
public:
AxisAngle();
explicit AxisAngle(const Quaternion& q);
explicit AxisAngle(const EulerAngle& e);
/// Transitional datatype, not useful for internal representation of rotation
/// But has uses for conversion and manipulation.
class J3ML::LinearAlgebra::AxisAngle {
public:
Vector3 axis;
// Radians.
float angle;
public:
AxisAngle();
explicit AxisAngle(const Quaternion& q);
explicit AxisAngle(const EulerAngleXYZ& e);
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

@@ -0,0 +1,20 @@
#pragma once
#include <J3ML/LinearAlgebra/Vector3.hpp>
namespace J3ML::LinearAlgebra {
class DirectionVectorRH;
}
/// Direction vector of a given Matrix3x3 RotationMatrix in a Right-handed coordinate space.
class J3ML::LinearAlgebra::DirectionVectorRH : public Vector3 {
private:
// This is purposefully not exposed because these types aren't usually convertable.
explicit DirectionVectorRH(const Vector3& rhs);
public:
static DirectionVectorRH Forward(const Matrix3x3& rhs);
static DirectionVectorRH Backward(const Matrix3x3& rhs);
static DirectionVectorRH Left(const Matrix3x3& rhs);
static DirectionVectorRH Right(const Matrix3x3& rhs);
static DirectionVectorRH Up(const Matrix3x3& rhs);
static DirectionVectorRH Down(const Matrix3x3& rhs);
};

View File

@@ -5,48 +5,19 @@
#include <J3ML/LinearAlgebra/AxisAngle.hpp>
namespace J3ML::LinearAlgebra {
class EulerAngleXYZ;
}
class AxisAngle;
// Essential Reading:
// http://www.essentialmath.com/GDC2012/GDC2012_JMV_Rotations.pdf
class EulerAngle {
class J3ML::LinearAlgebra::EulerAngleXYZ {
public:
EulerAngle();
EulerAngle(float pitch, float yaw, float roll);
EulerAngle(const Vector3& vec) : pitch(vec.x), yaw(vec.y), roll(vec.z) {}
AxisAngle ToAxisAngle() const;
[[nodiscard]] Quaternion ToQuaternion() const;
explicit EulerAngle(const Quaternion& rhs);
explicit EulerAngle(const AxisAngle& rhs);
/// TODO: Implement separate upper and lower bounds
/// Preserves internal value of euler angles, normalizes and clamps the output.
/// This does not solve gimbal lock!!!
float GetPitch(float pitch_limit) const;
float GetYaw(float yaw_limit) const;
float GetRoll(float roll_limit) const;
bool operator==(const EulerAngle& a) const;
void clamp();
// TODO: Euler Angles do not represent a vector, length doesn't apply, nor is this information meaningful for this data type.
// If you need a meaningful representation of length in 3d space, use a vector!!
[[nodiscard]] float length() const {
return 0;
}
// TODO: Implement
Vector3 unitVector() const;
EulerAngle movementAngle() const;
public:
float pitch;
float yaw;
float roll;
float roll = 0; // X
float pitch = 0; // Y
float yaw = 0; // Z
public:
EulerAngleXYZ(float roll, float pitch, float yaw);
public:
explicit EulerAngleXYZ(const Quaternion& rhs);
explicit EulerAngleXYZ(const AxisAngle& rhs);
explicit EulerAngleXYZ(const Matrix3x3& rhs);
};
}

View File

@@ -7,7 +7,7 @@ namespace J3ML::LinearAlgebra
class Vector3; // A type representing a position in a 3-dimensional coordinate space.
class Vector4; // A type representing a position in a 4-dimensional coordinate space.
class Angle2D; // Uses x,y components to represent a 2D rotation.
class EulerAngle; // Uses pitch,yaw,roll components to represent a 3D orientation.
class EulerAngleXYZ; // Uses pitch,yaw,roll components to represent a 3D orientation.
class AxisAngle; //
class CoordinateFrame; //
class Matrix2x2;
@@ -15,6 +15,7 @@ namespace J3ML::LinearAlgebra
class Matrix4x4;
class Transform2D;
class Transform3D;
class DirectionVectorRH; // A type representing a direction in 3D space.
class Quaternion;

View File

@@ -63,7 +63,10 @@ namespace J3ML::LinearAlgebra {
/// Constructs this matrix3x3 from the given quaternion.
explicit Matrix3x3(const Quaternion& orientation);
/// Constructs this matrix3x3 from the given euler angle.
explicit Matrix3x3(const EulerAngle& orientation);
explicit Matrix3x3(const EulerAngleXYZ& orientation);
explicit Matrix3x3(const AxisAngle& orientation);
/// Constructs this Matrix3x3 from a pointer to an array of floats.
explicit Matrix3x3(const float *data);
@@ -153,6 +156,7 @@ namespace J3ML::LinearAlgebra {
/// Sets this matrix to perform a rotation about the given axis and angle.
void SetRotatePart(const Vector3& a, float angle);
void SetRotatePart(const AxisAngle& axisAngle);
/// Sets this matrix to perform the rotation expressed by the given quaternion.
void SetRotatePart(const Quaternion& quat);
@@ -239,17 +243,10 @@ namespace J3ML::LinearAlgebra {
inline float* ptr() { return &elems[0][0];}
[[nodiscard]] inline const float* ptr() const {return &elems[0][0];}
/// Convers this rotation matrix to a quaternion.
/// This function assumes that the matrix is orthonormal (no shear or scaling) and does not perform any mirroring (determinant > 0)
[[nodiscard]] Quaternion ToQuat() const;
/// Attempts to convert this matrix to a quaternion. Returns false if the conversion cannot succeed (this matrix was not a rotation
/// matrix, and there is scaling ,shearing, or mirroring in this matrix)
bool TryConvertToQuat(Quaternion& q) const;
/// Converts this rotation matrix to an Euler Angle.
[[nodiscard]] EulerAngle ToEulerAngle() const;
/// Returns the main diagonal.
/// The main diagonal consists of the elements at m[0][0], m[1][1], m[2][2]
[[nodiscard]] Vector3 Diagonal() const;

View File

@@ -71,8 +71,7 @@ namespace J3ML::LinearAlgebra {
/// Constructs this Matrix4x4 from the given quaternion.
explicit Matrix4x4(const Quaternion& orientation);
/// Constructs this Matrix4x4 from the given Euler Angle.
explicit Matrix4x4(const EulerAngle& orientation);
/// Constructs this float4x4 from the given quaternion and translation.
/// Logically, the translation occurs after the rotation has been performed.
@@ -567,8 +566,6 @@ namespace J3ML::LinearAlgebra {
[[nodiscard]] Quaternion ToQuat() const;
[[nodiscard]] EulerAngle ToEulerAngle() const;
/// Returns true if this Matrix4x4 is equal to the given Matrix4x4, up to given per-element epsilon.
bool Equals(const Matrix4x4& other, float epsilon = 1e-3f) const;

View File

@@ -4,258 +4,237 @@
#include <J3ML/Algorithm/RNG.hpp>
#include <cmath>
namespace J3ML::LinearAlgebra
{
class Quaternion {
public:
/// The identity quaternion performs no rotation when applied to a vector.
static const Quaternion Identity;
/// A compile-time constant Quaternion with the value (NAN, NAN, NAN, NAN).
/// For this constant, each element has the value of quiet NAN, or Not-A-Number.
/// @note Never compare a Quaternion to this value! Due to how IEEE floats work, "nan == nan" returns false!
/// That is, nothing is equal to NaN, not even NaN itself!
static const Quaternion NaN;
public:
/// The default constructor does not initialize any member values.
Quaternion();
/// Copy constructor
Quaternion(const Quaternion &rhs) = default;
/// Constructs a quaternion from the given data buffer.
/// @param data An array of four floats to use for the quaternion, in the order 'x,y,z,w.' (== 'i,j,k,w')
explicit Quaternion(const float *data);
namespace J3ML::LinearAlgebra {
class Quaternion;
}
explicit Quaternion(const Matrix3x3 &rotationMtrx);
explicit Quaternion(const Matrix4x4 &rotationMtrx);
class J3ML::LinearAlgebra::Quaternion {
public:
float x;
float y;
float z;
float w;
public:
/// The identity quaternion performs no rotation when applied to a vector.
static const Quaternion Identity;
/// A compile-time constant Quaternion with the value (NAN, NAN, NAN, NAN).
/// For this constant, each element has the value of quiet NAN, or Not-A-Number.
/// @note Never compare a Quaternion to this value! Due to how IEEE floats work, "nan == nan" returns false!
/// That is, nothing is equal to NaN, not even NaN itself!
static const Quaternion NaN;
public:
/// The default constructor does not initialize any member values.
Quaternion() = default;
/// Copy constructor
Quaternion(const Quaternion &rhs);
/// Quaternion from Matrix3x3
explicit Quaternion(const Matrix3x3& ro_mat);
/// Quaternion from Matrix4x4 RotatePart.
explicit Quaternion(const Matrix4x4& ro_mat);
/// Quaternion from EulerAngleXYZ.
explicit Quaternion(const EulerAngleXYZ& rhs);
/// Quaternion from AxisAngle.
explicit Quaternion(const AxisAngle& angle);
/// Quaternion from Vector4 (no conversion).
explicit Quaternion(const Vector4& vector4);
/// @param x The factor of i.
/// @param y The factor of j.
/// @param z The factor of k.
/// @param w The scalar factor (or 'w').
/// @note The input data is not normalized after construction, this has to be done manually.
Quaternion(float X, float Y, float Z, float W);
/// @param x The factor of i.
/// @param y The factor of j.
/// @param z The factor of k.
/// @param w The scalar factor (or 'w').
/// @note The input data is not normalized after construction, this has to be done manually.
Quaternion(float X, float Y, float Z, float W);
/// Constructs this quaternion by specifying a rotation axis and the amount of rotation to be performed about that axis
/// @param rotationAxis The normalized rotation axis to rotate about. If using Vector4 version of the constructor, the w component of this vector must be 0.
/// @param rotationAngleRadians The angle to rotate by, in radians. For example, Pi/4.f equals to 45 degrees, Pi/2.f is 90 degrees, etc.
/// @see DegToRad()
Quaternion(const Vector3 &rotationAxis, float rotationAngleRadians);
Quaternion(const Vector4 &rotationAxis, float rotationAngleRadians);
/// Creates a LookAt quaternion.
/** A LookAt quaternion is a quaternion that orients an object to face towards a specified target direction.
@param localForward Specifies the forward direction in the local space of the object. This is the direction
the model is facing at in its own local/object space, often +X(1,0,0), +Y(0,1,0), or +Z(0,0,1).
The vector to pass in here depends on the conventions you or your modeling software is using, and it is best
to pick one convention for all your objects, and be consistent.
This input parameter must be a normalized vector.
@param targetDirection Specifies the desired world space direction the object should look at. This function
will compute a quaternion which will rotate the localForward vector to orient towards this targetDirection
vector. This input parameter must be a normalized vector.
@param localUp Specifies the up direction in the local space of the object. This is the up direction the model
was authored in, often +Y (0,1,0) or +Z (0,0,1). The vector to pass in here depends on the conventions you
or your modeling software is using, and it is best to pick one convention for all your objects, and be
consistent. This input parameter must be a normalized vector. This vector must be perpendicular to the
vector localForward, i.e. localForward.Dot(localUp) == 0.
@param worldUp Specifies the global up direction of the scene in world space. Simply rotating one vector to
coincide with another (localForward->targetDirection) would cause the up direction of the resulting
orientation to drift (e.g. the model could be looking at its target its head slanted sideways). To keep
the up direction straight, this function orients the localUp direction of the model to point towards
the specified worldUp direction (as closely as possible). The worldUp and targetDirection vectors cannot be
collinear, but they do not need to be perpendicular either.
@return A quaternion that maps the given local space forward direction vector to point towards the given target
direction, and the given local up direction towards the given target world up direction. For the returned
quaternion Q it holds that M * localForward = targetDirection, and M * localUp lies in the plane spanned
by the vectors targetDirection and worldUp.
@see RotateFromTo() */
static Quaternion LookAt(const Vector3& localForward, const Vector3& targetDirection, const Vector3& localUp, const Vector3& worldUp);
explicit Quaternion(const Vector4& vector4);
explicit Quaternion(const EulerAngle& angle);
explicit Quaternion(const AxisAngle& angle);
/// Creates a new quaternion that rotates about the positive X axis by the given rotation.
static Quaternion RotateX(float rad);
/// Creates a new quaternion that rotates about the positive Y axis by the given rotation.
static Quaternion RotateY(float rad);
/// Creates a new quaternion that rotates about the positive Z axis by the given rotation.
static Quaternion RotateZ(float rad);
/// Creates a LookAt quaternion.
/** A LookAt quaternion is a quaternion that orients an object to face towards a specified target direction.
@param localForward Specifies the forward direction in the local space of the object. This is the direction
the model is facing at in its own local/object space, often +X(1,0,0), +Y(0,1,0), or +Z(0,0,1).
The vector to pass in here depends on the conventions you or your modeling software is using, and it is best
to pick one convention for all your objects, and be consistent.
This input parameter must be a normalized vector.
@param targetDirection Specifies the desired world space direction the object should look at. This function
will compute a quaternion which will rotate the localForward vector to orient towards this targetDirection
vector. This input parameter must be a normalized vector.
@param localUp Specifies the up direction in the local space of the object. This is the up direction the model
was authored in, often +Y (0,1,0) or +Z (0,0,1). The vector to pass in here depends on the conventions you
or your modeling software is using, and it is best to pick one convention for all your objects, and be
consistent. This input parameter must be a normalized vector. This vector must be perpendicular to the
vector localForward, i.e. localForward.Dot(localUp) == 0.
@param worldUp Specifies the global up direction of the scene in world space. Simply rotating one vector to
coincide with another (localForward->targetDirection) would cause the up direction of the resulting
orientation to drift (e.g. the model could be looking at its target its head slanted sideways). To keep
the up direction straight, this function orients the localUp direction of the model to point towards
the specified worldUp direction (as closely as possible). The worldUp and targetDirection vectors cannot be
collinear, but they do not need to be perpendicular either.
@return A quaternion that maps the given local space forward direction vector to point towards the given target
direction, and the given local up direction towards the given target world up direction. For the returned
quaternion Q it holds that M * localForward = targetDirection, and M * localUp lies in the plane spanned
by the vectors targetDirection and worldUp.
@see RotateFromTo() */
static Quaternion LookAt(const Vector3& localForward, const Vector3& targetDirection, const Vector3& localUp, const Vector3& worldUp);
/// Creates a new quaternion that rotates sourceDirection vector (in world space) to coincide with the
/// targetDirection vector (in world space).
/// Rotation is performed about the origin.
/// The vectors sourceDirection and targetDirection are assumed to be normalized.
/// @note There are multiple such rotations - this function returns the rotation that has the shortest angle
/// (when decomposed to axis-angle notation).
static Quaternion RotateFromTo(const Vector3& sourceDirection, const Vector3& targetDirection);
static Quaternion RotateFromTo(const Vector4& sourceDirection, const Vector4& targetDirection);
/// Creates a new Quaternion that rotates about the given axis by the given angle.
static Quaternion RotateAxisAngle(const AxisAngle& axisAngle);
/// Creates a new quaternion that rotates about the positive X axis by the given rotation.
static Quaternion RotateX(float angleRadians);
/// Creates a new quaternion that rotates about the positive Y axis by the given rotation.
static Quaternion RotateY(float angleRadians);
/// Creates a new quaternion that rotates about the positive Z axis by the given rotation.
static Quaternion RotateZ(float angleRadians);
/// Creates a new quaternion that rotates sourceDirection vector (in world space) to coincide with the
/// targetDirection vector (in world space).
/// Rotation is performed about the origin.
/// The vectors sourceDirection and targetDirection are assumed to be normalized.
/// @note There are multiple such rotations - this function returns the rotation that has the shortest angle
/// (when decomposed to axis-angle notation).
static Quaternion RotateFromTo(const Vector3& sourceDirection, const Vector3& targetDirection);
static Quaternion RotateFromTo(const Vector4& sourceDirection, const Vector4& targetDirection);
/// Creates a new quaternion that
/// 1. rotates sourceDirection vector to coincide with the targetDirection vector, and then
/// 2. rotates sourceDirection2 (which was transformed by 1.) to targetDirection2, but keeping the constraint that
/// sourceDirection must look at targetDirection
static Quaternion RotateFromTo(const Vector3& sourceDirection, const Vector3& targetDirection, const Vector3& sourceDirection2, const Vector3& targetDirection2);
/// Creates a new quaternion that
/// 1. rotates sourceDirection vector to coincide with the targetDirection vector, and then
/// 2. rotates sourceDirection2 (which was transformed by 1.) to targetDirection2, but keeping the constraint that
/// sourceDirection must look at targetDirection
static Quaternion RotateFromTo(const Vector3& sourceDirection, const Vector3& targetDirection, const Vector3& sourceDirection2, const Vector3& targetDirection2);
/// Returns a uniformly random unitary quaternion.
static Quaternion RandomRotation(RNG &rng);
public:
void SetFromAxisAngle(const Vector3 &vector3, float between);
/// Returns a uniformly random unitary quaternion.
static Quaternion RandomRotation(RNG &rng);
public:
/// Inverses this quaternion in-place.
/// @note For optimization purposes, this function assumes that the quaternion is unitary, in which
/// case the inverse of the quaternion is simply just the same as its conjugate.
/// This function does not detect whether the operation succeeded or failed.
void Inverse();
void SetFromAxisAngle(const Vector4 &vector4, float between);
void SetFrom(const AxisAngle& angle);
/// Returns an inverted copy of this quaternion.
[[nodiscard]] Quaternion Inverted() const;
/// Computes the conjugate of this quaternion in-place.
void Conjugate();
/// Returns a conjugated copy of this quaternion.
[[nodiscard]] Quaternion Conjugated() const;
/// Inverses this quaternion in-place.
/// @note For optimization purposes, this function assumes that the quaternion is unitary, in which
/// case the inverse of the quaternion is simply just the same as its conjugate.
/// This function does not detect whether the operation succeeded or failed.
void Inverse();
/// Inverses this quaternion in-place.
/// Call this function when the quaternion is not known beforehand to be normalized.
/// This function computes the inverse proper, and normalizes the result.
/// @note Because of the normalization, it does not necessarily hold that q * q.InverseAndNormalize() == id.
/// @return Returns the old length of this quaternion (not the old length of the inverse quaternion).
float InverseAndNormalize();
/// Returns an inverted copy of this quaternion.
[[nodiscard]] Quaternion Inverted() const;
/// Computes the conjugate of this quaternion in-place.
void Conjugate();
/// Returns a conjugated copy of this quaternion.
[[nodiscard]] Quaternion Conjugated() const;
/// Returns the local +X axis in the post-transformed coordinate space. This is the same as transforming the vector (1,0,0) by this quaternion.
[[nodiscard]] Vector3 WorldX() const;
/// Returns the local +Y axis in the post-transformed coordinate space. This is the same as transforming the vector (0,1,0) by this quaternion.
[[nodiscard]] Vector3 WorldY() const;
/// Returns the local +Z axis in the post-transformed coordinate space. This is the same as transforming the vector (0,0,1) by this quaternion.
[[nodiscard]] Vector3 WorldZ() const;
/// Returns the axis of rotation for this quaternion.
[[nodiscard]] Vector3 Axis() const;
/// Inverses this quaternion in-place.
/// Call this function when the quaternion is not known beforehand to be normalized.
/// This function computes the inverse proper, and normalizes the result.
/// @note Because of the normalization, it does not necessarily hold that q * q.InverseAndNormalize() == id.
/// @return Returns the old length of this quaternion (not the old length of the inverse quaternion).
float InverseAndNormalize();
/// Returns the angle of rotation for this quaternion, in radians.
[[nodiscard]] float Angle() const;
/// Returns the local +X axis in the post-transformed coordinate space. This is the same as transforming the vector (1,0,0) by this quaternion.
[[nodiscard]] Vector3 WorldX() const;
/// Returns the local +Y axis in the post-transformed coordinate space. This is the same as transforming the vector (0,1,0) by this quaternion.
[[nodiscard]] Vector3 WorldY() const;
/// Returns the local +Z axis in the post-transformed coordinate space. This is the same as transforming the vector (0,0,1) by this quaternion.
[[nodiscard]] Vector3 WorldZ() const;
[[nodiscard]] float LengthSquared() const;
[[nodiscard]] float Length() const;
/// Returns the axis of rotation for this quaternion.
[[nodiscard]] Vector3 Axis() const;
[[nodiscard]] Matrix3x3 ToMatrix3x3() const;
[[nodiscard]] Matrix4x4 ToMatrix4x4() const;
/// Returns the angle of rotation for this quaternion, in radians.
[[nodiscard]] float Angle() const;
[[nodiscard]] Matrix4x4 ToMatrix4x4(const Vector3 &translation) const;
[[nodiscard]] float LengthSquared() const;
[[nodiscard]] float Length() const;
[[nodiscard]] Vector3 Transform(const Vector3& vec) const;
[[nodiscard]] Vector3 Transform(float X, float Y, float Z) const;
// Note: We only transform the x,y,z components of 4D vectors, w is left untouched
[[nodiscard]] Vector4 Transform(const Vector4& vec) const;
[[nodiscard]] Vector4 Transform(float X, float Y, float Z, float W) const;
[[nodiscard]] EulerAngle ToEulerAngle() const;
[[nodiscard]] Quaternion Lerp(const Quaternion& b, float t) const;
static Quaternion Lerp(const Quaternion &source, const Quaternion& target, float t);
[[nodiscard]] Quaternion Slerp(const Quaternion& q2, float t) const;
static Quaternion Slerp(const Quaternion &source, const Quaternion& target, float t);
/// Returns the 'from' vector rotated towards the 'to' vector by the given normalized time parameter.
/** This function slerps the given 'form' vector toward the 'to' vector.
@param from A normalized direction vector specifying the direction of rotation at t=0
@param to A normalized direction vector specifying the direction of rotation at t=1
@param t The interpolation time parameter, in the range [0, 1]. Input values outside this range are
silently clamped to the [0, 1] interval.
@return A spherical linear interpolation of the vector 'from' towards the vector 'to'. */
static Vector3 SlerpVector(const Vector3& from, const Vector3& to, float t);
/// Returns the 'from' vector rotated towards the 'to' vector by the given absolute angle, in radians.
/** This function slerps the given 'from' vector towards the 'to' vector.
@param from A normalized direction vector specifying the direction of rotation at angleRadians=0.
@param to A normalized direction vector specifying the target direction to rotate towards.
@param angleRadians The maximum angle to rotate the 'from' vector by, in the range [0, pi]. If the
angle between 'from' and 'to' is smaller than this angle, then the vector 'to' is returned.
Input values outside this range are silently clamped to the [0, pi] interval.
@return A spherical linear interpolation of the vector 'from' towards the vector 'to'. */
static Vector3 SlerpVectorAbs(const Vector3 &from, const Vector3& to, float angleRadians);
/// Normalizes this quaternion in-place.
/// @returns false if failure, true if success.
[[nodiscard]] bool Normalize();
/// Returns a normalized copy of this quaternion.
[[nodiscard]] Quaternion Normalized() const;
/// Returns true if the length of this quaternion is one.
[[nodiscard]] bool IsNormalized(float epsilon = 1e-5f) const;
[[nodiscard]] bool IsInvertible(float epsilon = 1e-3f) const;
/// Returns true if the entries of this quaternion are all finite.
[[nodiscard]] bool IsFinite() const;
/// Returns true if this quaternion equals rhs, up to the given epsilon.
[[nodiscard]] bool Equals(const Quaternion& rhs, float epsilon = 1e-3f) const;
/// Compares whether this Quaternion and the given Quaternion are identical bit-by-bit in the underlying representation.
/// @note Prefer using this over e.g. memcmp, since there can be SSE-related padding in the structures.
bool BitEquals(const Quaternion& rhs) const;
/// @return A pointer to the first element (x). The data is contiguous in memory.
/// ptr[0] gives x, ptr[1] gives y, ptr[2] gives z, ptr[3] gives w.
inline float *ptr() { return &x; }
[[nodiscard]] inline const float *ptr() const { return &x; }
[[nodiscard]] Matrix3x3 ToMatrix3x3() const;
[[nodiscard]] Matrix4x4 ToMatrix4x4() const;
// Multiplies two quaternions together.
// The product q1 * q2 returns a quaternion that concatenates the two orientation rotations.
// The rotation q2 is applied first before q1.
Quaternion operator * (const Quaternion& rhs) const;
[[nodiscard]] Matrix4x4 ToMatrix4x4(const Vector3 &translation) const;
// Unsafe
Quaternion operator * (float scalar) const;
[[nodiscard]] Vector3 Transform(const Vector3& vec) const;
[[nodiscard]] Vector3 Transform(float X, float Y, float Z) const;
// Note: We only transform the x,y,z components of 4D vectors, w is left untouched
[[nodiscard]] Vector4 Transform(const Vector4& vec) const;
[[nodiscard]] Vector4 Transform(float X, float Y, float Z, float W) const;
// Unsafe
Quaternion operator / (float scalar) const;
[[nodiscard]] Quaternion Lerp(const Quaternion& b, float t) const;
static Quaternion Lerp(const Quaternion &source, const Quaternion& target, float t);
[[nodiscard]] Quaternion Slerp(const Quaternion& q2, float t) const;
static Quaternion Slerp(const Quaternion &source, const Quaternion& target, float t);
// Transforms the given vector by this Quaternion.
Vector3 operator * (const Vector3& rhs) const;
/// Returns the 'from' vector rotated towards the 'to' vector by the given normalized time parameter.
/** This function slerps the given 'form' vector toward the 'to' vector.
@param from A normalized direction vector specifying the direction of rotation at t=0
@param to A normalized direction vector specifying the direction of rotation at t=1
@param t The interpolation time parameter, in the range [0, 1]. Input values outside this range are
silently clamped to the [0, 1] interval.
@return A spherical linear interpolation of the vector 'from' towards the vector 'to'. */
static Vector3 SlerpVector(const Vector3& from, const Vector3& to, float t);
Vector4 operator * (const Vector4& rhs) const;
/// Returns the 'from' vector rotated towards the 'to' vector by the given absolute angle, in radians.
/** This function slerps the given 'from' vector towards the 'to' vector.
@param from A normalized direction vector specifying the direction of rotation at angleRadians=0.
@param to A normalized direction vector specifying the target direction to rotate towards.
@param angleRadians The maximum angle to rotate the 'from' vector by, in the range [0, pi]. If the
angle between 'from' and 'to' is smaller than this angle, then the vector 'to' is returned.
Input values outside this range are silently clamped to the [0, pi] interval.
@return A spherical linear interpolation of the vector 'from' towards the vector 'to'. */
static Vector3 SlerpVectorAbs(const Vector3 &from, const Vector3& to, float angleRadians);
/// Normalizes this quaternion in-place.
/// Returns the old length of this quaternion, or 0 if normalization failed.
float Normalize();
/// Returns a normalized copy of this quaternion.
[[nodiscard]] Quaternion Normalized() const;
/// Returns true if the length of this quaternion is one.
[[nodiscard]] bool IsNormalized(float epsilon = 1e-5f) const;
[[nodiscard]] bool IsInvertible(float epsilon = 1e-3f) const;
/// Returns true if the entries of this quaternion are all finite.
[[nodiscard]] bool IsFinite() const;
/// Returns true if this quaternion equals rhs, up to the given epsilon.
[[nodiscard]] bool Equals(const Quaternion& rhs, float epsilon = 1e-3f) const;
/// Compares whether this Quaternion and the given Quaternion are identical bit-by-bit in the underlying representation.
/// @note Prefer using this over e.g. memcmp, since there can be SSE-related padding in the structures.
bool BitEquals(const Quaternion& rhs) const;
/// @return A pointer to the first element (x). The data is contiguous in memory.
/// ptr[0] gives x, ptr[1] gives y, ptr[2] gives z, ptr[3] gives w.
inline float *ptr() { return &x; }
[[nodiscard]] inline const float *ptr() const { return &x; }
// Multiplies two quaternions together.
// The product q1 * q2 returns a quaternion that concatenates the two orientation rotations.
// The rotation q2 is applied first before q1.
Quaternion operator * (const Quaternion& rhs) const;
// Unsafe
Quaternion operator * (float scalar) const;
// Unsafe
Quaternion operator / (float scalar) const;
// Transforms the given vector by this Quaternion.
Vector3 operator * (const Vector3& rhs) const;
Vector4 operator * (const Vector4& rhs) const;
// Divides a quaternion by another. Divison "a / b" results in a quaternion that rotates the orientation b to coincide with orientation of
Quaternion operator / (const Quaternion& rhs) const;
Quaternion operator + (const Quaternion& rhs) const;
// Divides a quaternion by another. Divison "a / b" results in a quaternion that rotates the orientation b to coincide with orientation of
Quaternion operator / (const Quaternion& rhs) const;
Quaternion operator + (const Quaternion& rhs) const;
Quaternion operator + () const;
Quaternion operator - () const;
Quaternion operator + () const;
Quaternion operator - () const;
/// Computes the dot product of this and the given quaternion.
/// Dot product is commutative.
[[nodiscard]] float Dot(const Quaternion &quaternion) const;
/// Computes the dot product of this and the given quaternion.
/// Dot product is commutative.
[[nodiscard]] float Dot(const Quaternion &quaternion) const;
/// Returns the angle between this and the target orientation (the shortest route) in radians.
[[nodiscard]] float AngleBetween(const Quaternion& target) const;
/// Returns the axis of rotation to get from this orientation to target orientation (the shortest route).
[[nodiscard]] Vector3 AxisFromTo(const Quaternion& target) const;
/// Returns the angle between this and the target orientation (the shortest route) in radians.
[[nodiscard]] float AngleBetween(const Quaternion& target) const;
/// Returns the axis of rotation to get from this orientation to target orientation (the shortest route).
[[nodiscard]] Vector3 AxisFromTo(const Quaternion& target) const;
[[nodiscard]] AxisAngle ToAxisAngle() const;
void SetFromAxisAngle(const AxisAngle& axisAngle);
/// Sets this quaternion to represent the same rotation as the given matrix.
void Set(const Matrix3x3& matrix);
void Set(const Matrix4x4& matrix);
void Set(float x, float y, float z, float w);
void Set(const Quaternion& q);
void Set(const Vector4& v);
/// Sets this quaternion to represent the same rotation as the given matrix.
void Set(const Matrix3x3& matrix);
void Set(const Matrix4x4& matrix);
void Set(float x, float y, float z, float w);
void Set(const Quaternion& q);
void Set(const Vector4& v);
public:
float x;
float y;
float z;
float w;
};
}
};

View File

@@ -2,60 +2,22 @@
#include <J3ML/LinearAlgebra/Quaternion.hpp>
namespace J3ML::LinearAlgebra {
AxisAngle::AxisAngle() : axis(Vector3::Zero) {}
AxisAngle::AxisAngle() : axis(Vector3::Zero), angle(0) {}
AxisAngle::AxisAngle(const Vector3 &axis, float angle) : axis(axis), angle(angle) {}
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)
};
AxisAngle::AxisAngle(const Quaternion& rhs) {
float halfAngle = std::acos(rhs.w);
angle = halfAngle * 2.f;
float reciprocalSinAngle = 1.f / std::sqrt(1.f - rhs.w*rhs.w);
axis = { rhs.x*reciprocalSinAngle, rhs.y*reciprocalSinAngle, rhs.z*reciprocalSinAngle };
}
AxisAngle::AxisAngle(const Quaternion &q) {
auto theta = std::acos(q.w) * 2.f;
auto ax = q.x / std::sin(std::acos(theta));
auto ay = q.y / std::sin(std::acos(theta));
auto az = q.z / std::sin(std::acos(theta));
}
AxisAngle::AxisAngle(const EulerAngle &e) {
// Assuming the angles are in radians
float heading = e.pitch;
float attitude = e.yaw;
float bank = e.roll;
float c1 = std::cos(heading / 2.f);
float s1 = std::sin(heading / 2.f);
float c2 = std::cos(attitude / 2.f);
float s2 = std::sin(attitude / 2.f);
float c3 = std::cos(bank / 2.f);
float s3 = std::sin(bank / 2.f);
float w = c1*c2*c3 - s1*s2*s3;
float x = c1*c2*c3 + s1*s2*s3;
float y = s1*c2*c3 + c1*s2*s3;
float z = c1*s2*c3 - s1*c2*s3;
angle = 2.f * std::acos(w);
double norm = x*x + y*y + z*z;
if (norm < 0.001) { // when all euler angles are zero angle=0, so
// we can set axis to anything to avoid divide by zero
x = 1;
y = z = 0;
} else {
norm = std::sqrt(norm);
x /= norm;
y /= norm;
z /= norm;
}
axis = {x, y, z};
AxisAngle::AxisAngle(const EulerAngleXYZ& e) {
auto a = AxisAngle(Quaternion(e));
axis = a.axis;
angle = a.angle;
}
}

View File

@@ -0,0 +1,38 @@
#include <J3ML/LinearAlgebra/DirectionVector.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
DirectionVectorRH::DirectionVectorRH(const Vector3& rhs) {
x = rhs.x;
y = rhs.y;
z = rhs.z;
}
DirectionVectorRH DirectionVectorRH::Forward(const Matrix3x3& rhs) {
return DirectionVectorRH(rhs.Col(2));
}
DirectionVectorRH DirectionVectorRH::Backward(const Matrix3x3& rhs) {
return DirectionVectorRH(-rhs.Col(2));
}
DirectionVectorRH DirectionVectorRH::Left(const Matrix3x3& rhs) {
return DirectionVectorRH(-rhs.Col(0));
}
DirectionVectorRH DirectionVectorRH::Right(const Matrix3x3& rhs) {
return DirectionVectorRH(rhs.Col(0));
}
DirectionVectorRH DirectionVectorRH::Up(const Matrix3x3 &rhs) {
return DirectionVectorRH(rhs.Col(1));
}
DirectionVectorRH DirectionVectorRH::Down(const Matrix3x3& rhs) {
return DirectionVectorRH(-rhs.Col(1));
}

View File

@@ -1,147 +1,45 @@
#include <J3ML/LinearAlgebra/EulerAngle.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <cmath>
#include <algorithm>
namespace J3ML::LinearAlgebra {
EulerAngle::EulerAngle(float pitch, float yaw, float roll): pitch(pitch), yaw(yaw), roll(roll)
{}
float EulerAngle::GetPitch(float pitch_limit) const
{ return std::clamp( std::remainderf(pitch,360.f), -pitch_limit, pitch_limit); }
float EulerAngle::GetYaw(float yaw_limit) const
{ return std::clamp(std::remainderf(yaw, 360.f), -yaw_limit, yaw_limit); }
float EulerAngle::GetRoll(float pitch_limit) const
{ return std::clamp( std::remainderf(pitch,360.f), -pitch_limit, pitch_limit); }
bool EulerAngle::operator==(const EulerAngle& a) const
{
return (pitch == a.pitch) && (yaw == a.yaw) && (roll == a.roll);
EulerAngleXYZ::EulerAngleXYZ(float roll, float pitch, float yaw) {
this->roll = roll;
this->pitch = pitch;
this->yaw = yaw;
}
void EulerAngle::clamp()
{
if (this->pitch > 89.0f)
this->pitch = 89.0f;
if (this->pitch <= -89.0f)
this->pitch = -89.0f;
//TODO: Make this entirely seamless by getting the amount they rotated passed -180 and +180 by.
if (this->yaw <= -180.0f)
this->yaw = 180.0f;
if (this->yaw >= 180.01f)
this->yaw = -179.9f;
if (this->roll >= 360.0f)
this->roll = 0.0;
if (this->roll <= -360.0f)
this->roll = 0.0;
EulerAngleXYZ::EulerAngleXYZ(const AxisAngle& rhs) {
*this = EulerAngleXYZ(Quaternion(rhs));
}
EulerAngle EulerAngle::movementAngle() const
{
EulerAngle a;
a.pitch = (cos(Math::Radians(yaw)) * cos(Math::Radians(pitch)));
a.yaw = -sin(Math::Radians(pitch));
a.roll = (sin(Math::Radians(yaw)) * cos(Math::Radians(pitch)));
return a;
EulerAngleXYZ::EulerAngleXYZ(const Quaternion& q) {
float sy = 2 * q.x * q.z + 2 * q.y * q.w;
bool gimbal_lock = std::abs(sy) > 0.99999f;
if (!gimbal_lock)
roll = Math::Degrees(std::atan2(-(2 * q.y * q.z - 2 * q.x * q.w),2 * q.w * q.w + 2 * q.z * q.z - 1));
else
roll = Math::Degrees(std::atan2(2 * q.y * q.z + 2 * q.x * q.w,2 * q.w * q.w + 2 * q.y * q.y - 1));
pitch = Math::Degrees(std::asin(sy));
if (!gimbal_lock)
yaw = Math::Degrees(std::atan2(-(2 * q.x * q.y - 2 * q.z * q.w),2 * q.w * q.w + 2 * q.x * q.x - 1));
else
yaw = 0;
}
EulerAngle::EulerAngle() : pitch(0), yaw(0), roll(0) {}
EulerAngleXYZ::EulerAngleXYZ(const Matrix3x3& rhs) {
auto m = rhs.Transposed();
auto sy = m.At(0, 2);
auto unlocked = std::abs(sy) < 0.99999f;
EulerAngle::EulerAngle(const AxisAngle &rhs) {
float x = rhs.axis.x;
float y = rhs.axis.y;
float z = rhs.axis.z;
float angle = rhs.angle;
double s = std::sin(rhs.angle);
double c = std::cos(rhs.angle);
double t = 1-c;
// if axis is not already normalized then uncomment this
// double magnitude = std::sqrt(x*x + y*y + z*z);
// if (magnitude == 0) throw error;
// x /= magnitude;
// y /= magnitude;
// z /= magnitude;
if ((x*y*t + z*s) > 0.998) { // North pole singularity detected
pitch = 2 * std::atan2(x * std::sin(angle/2.f), std::cos(angle/2.f));
yaw = Math::Pi / 2.f;
roll = 0;
return;
}
if ((x*y*t + z*s) < -0.998) { // South pole singularity detected
pitch = -2 * std::atan2(x * std::sin(angle/2.f), std::cos(angle/2.f));
yaw = -Math::Pi / 2.f;
roll = 0;
return;
}
pitch = std::atan2(y * s-x * z * t, 1 - (y*y + z*z) * t);
yaw = std::asin(x * y * t + z * s);
roll = std::atan2(x * s - y * z * t, 1 - (x*x + z*z) * t);
}
AxisAngle EulerAngle::ToAxisAngle() const {
auto c1 = std::cos(yaw / 2);
auto c2 = std::cos(pitch / 2);
auto c3 = std::cos(roll / 2);
auto s1 = std::sin(yaw / 2);
auto s2 = std::sin(pitch / 2);
auto s3 = std::sin(roll / 2);
auto angle = 2 * std::acos(c1*c2*c3 - s1*s2*s3);
auto x = s1*s2*c3 + c1*c2*s3;
auto y = s1*c2*c3 + c1*s2*s3;
auto z = c1*s2*c3 - s1*c2*s3;
// todo: normalize?
// sqrt(x^2 + y^2 + z^2) = sqrt((s1 s2 c3 +c1 c2 s3)^2+(s1 c2 c3 + c1 s2 s3)^2+(c1 s2 c3 - s1 c2 s3)^2)
return {{x,y,z}, angle};
}
Quaternion EulerAngle::ToQuaternion() const {
auto c1 = std::cos(yaw / 2);
auto c2 = std::cos(pitch / 2);
auto c3 = std::cos(roll / 2);
auto s1 = std::sin(yaw / 2);
auto s2 = std::sin(pitch / 2);
auto s3 = std::sin(roll / 2);
auto w = c1*c2*c3 - s1*s2*s3;
auto x = s1*s2*c3 + c1*c2*s3;
auto y = s1*c2*c3 + c1*s2*s3;
auto z = c1*s2*c3 - s1*c2*s3;
return {w,x,y,z};
}
EulerAngle::EulerAngle(const Quaternion &rhs) {
double test = rhs.x * rhs.y + rhs.z * rhs.w;
if (test > 0.499) { // Singularity at north pole
pitch = 2 * std::atan2(rhs.x, rhs.w);
yaw = Math::Pi / 2.f;
roll = 0;
return;
}
if (test < -0.499) { // Singularity at south pole
pitch = -2 * std::atan2(rhs.x, rhs.y);
yaw = - Math::Pi / 2.f;
roll = 0;
return;
}
float sqx = rhs.x * rhs.x;
float sqy = rhs.y * rhs.y;
float sqz = rhs.z * rhs.z;
roll = Math::Degrees(unlocked ? std::atan2(-m.At(1, 2), m.At(2, 2)) : std::atan2(m.At(2, 1), m.At(1, 1)));
pitch = Math::Degrees(std::asin(sy));
yaw = Math::Degrees(unlocked ? std::atan2(-m.At(0, 1), m.At(0, 0)) : 0);
}
}

View File

@@ -109,27 +109,8 @@ namespace J3ML::LinearAlgebra {
//this->elems[2][2] = r3.z;
}
Matrix3x3::Matrix3x3(const Quaternion &orientation) {
SetRotatePart(orientation);
}
Matrix3x3::Matrix3x3(const EulerAngle &orientation) {
auto sa = std::sin(orientation.pitch);
auto ca = std::cos(orientation.pitch);
auto sb = std::sin(orientation.roll);
auto cb = std::cos(orientation.roll);
auto sh = std::sin(orientation.yaw);
auto ch = std::cos(orientation.yaw);
At(0, 0) = ch*ca;
At(0, 1) = -ch*sa*cb + sh*sh;
At(0, 2) = ch*sa*sb + sh*cb;
At(1, 0) = sa;
At(1, 1) = ca*cb;
At(1, 2) = -ca*cb;
At(2, 0) = -sh*ca;
At(2, 1) = sh*sa*cb + ch*sb;
At(2, 2) = -sh*sa*sb + ch*cb;
Matrix3x3::Matrix3x3(const Quaternion& orientation) {
*this = Matrix3x3(EulerAngleXYZ(orientation));
}
float Matrix3x3::Determinant() const {
@@ -207,28 +188,7 @@ namespace J3ML::LinearAlgebra {
};
}
Quaternion Matrix3x3::ToQuat() const {
auto m00 = At(0,0);
auto m01 = At(0, 1);
auto m02 = At(0, 2);
auto m10 = At(1,0);
auto m11 = At(1, 1);
auto m12 = At(1, 2);
auto m20 = At(2,0);
auto m21 = At(2, 1);
auto m22 = At(2, 2);
auto w = std::sqrt(1.f + m00 + m11 + m22) / 2.f;
float w4 = (4.f * w);
return {
(m21 - m12) / w4,
(m02 - m20) / w4,
(m10 - m01) / w4,
w
};
}
void Matrix3x3::SetRotatePart(const Vector3 &a, float angle) {
void Matrix3x3::SetRotatePart(const Vector3& a, float angle) {
float s = std::sin(angle);
float c = std::cos(angle);
@@ -371,9 +331,9 @@ namespace J3ML::LinearAlgebra {
return m;
}
Matrix3x3 Matrix3x3::FromScale(float sx, float sy, float sz) {
Matrix3x3 Matrix3x3::FromScale(float sin_roll, float sy, float sz) {
Matrix3x3 m;
m.At(0,0) = sx;
m.At(0,0) = sin_roll;
m.At(1,1) = sy;
m.At(2,2) = sz;
return m;
@@ -992,25 +952,12 @@ namespace J3ML::LinearAlgebra {
bool Matrix3x3::TryConvertToQuat(Quaternion &q) const {
if (IsColOrthogonal() && HasUnitaryScale() && !HasNegativeScale()) {
q = ToQuat();
q = Quaternion(*this);
return true;
}
return false;
}
EulerAngle Matrix3x3::ToEulerAngle() const {
auto heading = std::atan2(-At(2, 0), At(0, 0));
auto attitude = std::asin(At(1, 0));
auto bank = std::atan2(-At(1,2), At(1,1));
if (At(1, 0) == 1 || At(1, 0) == -1) // North Pole || South Pole
{
heading = std::atan2(At(0, 2), At(2,2));
bank = 0;
}
return {attitude, heading, bank};
}
void Matrix3x3::BatchTransform(Vector3 *pointArray, int numPoints, int stride) const {
assert(pointArray || numPoints == 0);
assert(stride >= (int)sizeof(Vector3));
@@ -1119,6 +1066,25 @@ namespace J3ML::LinearAlgebra {
return m;
}
Matrix3x3::Matrix3x3(const EulerAngleXYZ& e) {
float cos_roll = std::cos(Math::Radians(e.roll));
float sin_roll = std::sin(Math::Radians(e.roll));
float cos_pitch = std::cos(Math::Radians(e.pitch));
float sin_pitch = std::sin(Math::Radians(e.pitch));
float cos_yaw = std::cos(Math::Radians(e.yaw));
float sin_yaw = std::sin(Math::Radians(e.yaw));
Matrix3x3 m;
m.SetRow(0, Vector3(cos_pitch * cos_yaw, sin_roll * sin_pitch *cos_yaw + cos_roll * sin_yaw, -cos_roll * sin_pitch *cos_yaw + sin_roll * sin_yaw));
m.SetRow(1, Vector3(-cos_pitch * sin_yaw, -sin_roll * sin_pitch * sin_yaw + cos_roll *cos_yaw, cos_roll * sin_pitch * sin_yaw + sin_roll *cos_yaw));
m.SetRow(2, Vector3(sin_pitch, -sin_roll * cos_pitch, cos_roll * cos_pitch));
*this = m;
}
Matrix3x3::Matrix3x3(const AxisAngle& orientation) {
*this = Matrix3x3(Quaternion(orientation));
}
}

View File

@@ -86,10 +86,6 @@ namespace J3ML::LinearAlgebra {
Set3x3Part(Matrix3x3(orientation));
}
Matrix4x4::Matrix4x4(const EulerAngle &orientation) {
Set3x3Part(Matrix3x3(orientation));
}
void Matrix4x4::SetTranslatePart(float translateX, float translateY, float translateZ) {
elems[0][3] = translateX;
elems[1][3] = translateY;
@@ -777,19 +773,6 @@ namespace J3ML::LinearAlgebra {
};
}
EulerAngle Matrix4x4::ToEulerAngle() const {
auto heading = std::atan2(-At(2, 0), At(0, 0));
auto attitude = std::asin(At(1, 0));
auto bank = std::atan2(-At(1,2), At(1,1));
if (At(1, 0) == 1 || At(1, 0) == -1) // North Pole || South Pole
{
heading = std::atan2(At(0, 2), At(2,2));
bank = 0;
}
return {attitude, heading, bank};
}
bool Matrix4x4::InverseOrthogonalUniformScale() {
assert(!ContainsProjection());
assert(IsColOrthogonal(1e-3f));

View File

@@ -1,23 +1,46 @@
#include <J3ML/LinearAlgebra/Vector3.hpp>
#include <J3ML/LinearAlgebra/Vector4.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <J3ML/LinearAlgebra/Matrix4x4.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/LinearAlgebra/AxisAngle.hpp>
#include <J3ML/LinearAlgebra/EulerAngle.hpp>
#include <J3ML/LinearAlgebra/Vector4.hpp>
#include <J3ML/LinearAlgebra/Vector3.hpp>
namespace J3ML::LinearAlgebra {
const Quaternion Quaternion::Identity = Quaternion(0.f, 0.f, 0.f, 1.f);
const Quaternion Quaternion::NaN = Quaternion(NAN, NAN, NAN, NAN);
Quaternion Quaternion::operator-() const
{
Quaternion Quaternion::operator-() const {
return {-x, -y, -z, -w};
}
Quaternion::Quaternion(const Matrix3x3 &rotationMtrx) {}
Quaternion::Quaternion(const Matrix3x3& ro_mat) {
auto m = ro_mat.Transposed();
auto m00 = m.At(0,0);
auto m01 = m.At(0, 1);
auto m02 = m.At(0, 2);
auto m10 = m.At(1,0);
auto m11 = m.At(1, 1);
auto m12 = m.At(1, 2);
auto m20 = m.At(2,0);
auto m21 = m.At(2, 1);
auto m22 = m.At(2, 2);
Quaternion::Quaternion(const Matrix4x4 &rotationMtrx) {}
auto field_w = std::sqrt(1.f + m00 + m11 + m22) / 2.f;
float w4 = (4.f * field_w);
x = (m21 - m12) / w4;
y = (m02 - m20) / w4;
z = (m10 - m01) / w4;
w = field_w;
Normalize();
}
Quaternion::Quaternion(const Matrix4x4& ro_mat) {
auto q = Quaternion(ro_mat.GetRotatePart());
x = q.x; y = q.y; z = q.z; w = q.w;
}
Vector3 Quaternion::WorldX() const { return Transform(1.f, 0.f, 0.f); }
@@ -50,22 +73,6 @@ namespace J3ML::LinearAlgebra {
return (*this * (t - 1.f) + b * t).Normalized();
}
void Quaternion::SetFromAxisAngle(const Vector3 &axis, float angle) {
float sinz, cosz;
sinz = std::sin(angle*0.5f);
cosz = std::cos(angle*0.5f);
x = axis.x * sinz;
y = axis.y * sinz;
z = axis.z * sinz;
w = cosz;
}
void Quaternion::SetFromAxisAngle(const Vector4 &axis, float angle)
{
SetFromAxisAngle(Vector3(axis.x, axis.y, axis.z), angle);
}
Quaternion Quaternion::operator*(float scalar) const {
return Quaternion(x * scalar, y * scalar, z * scalar, w * scalar);
}
@@ -82,8 +89,6 @@ namespace J3ML::LinearAlgebra {
Quaternion Quaternion::operator+() const { return *this; }
Quaternion::Quaternion() {}
Quaternion::Quaternion(float X, float Y, float Z, float W) : x(X), y(Y), z(Z), w(W) {}
// TODO: implement
@@ -148,20 +153,6 @@ namespace J3ML::LinearAlgebra {
return (*this * (a * sign) + q2 * b).Normalized();
}
AxisAngle Quaternion::ToAxisAngle() const {
float halfAngle = std::acos(w);
float angle = halfAngle * 2.f;
// TODO: Can Implement Fast Inverted Sqrt Here
float reciprocalSinAngle = 1.f / std::sqrt(1.f - w*w);
Vector3 axis = {
x*reciprocalSinAngle,
y*reciprocalSinAngle,
z*reciprocalSinAngle
};
return AxisAngle(axis, angle);
}
float Quaternion::AngleBetween(const Quaternion &target) const {
Quaternion delta = target / *this;
return delta.Normalized().Angle();
@@ -212,47 +203,13 @@ namespace J3ML::LinearAlgebra {
return Vector3(x, y, z) * rcpSinAngle;
}
Quaternion::Quaternion(const Vector3 &rotationAxis, float rotationAngleRadians) {
SetFromAxisAngle(rotationAxis, rotationAngleRadians);
}
Quaternion::Quaternion(const Vector4 &rotationAxis, float rotationAngleRadians) {
SetFromAxisAngle(rotationAxis, rotationAngleRadians);
}
Quaternion::Quaternion(const AxisAngle &angle) {
Quaternion::Quaternion(const AxisAngle& angle) {
double s = std::sin(angle.angle / 2);
x = angle.axis.x * s;
y = angle.axis.y * s;
z = angle.axis.z * s;
w = std::cos(angle.angle / 2);
}
Quaternion::Quaternion(const EulerAngle &angle) {
// Abbreviations for the various angular functions
double cr = std::cos(angle.roll * 0.5);
double sr = std::sin(angle.roll * 0.5);
double cp = std::cos(angle.pitch * 0.5);
double sp = std::sin(angle.pitch * 0.5);
double cy = std::cos(angle.yaw * 0.5);
double sy = std::sin(angle.yaw * 0.5);
w = cr * cp * cy + sr * sp * sy;
x = sr * cp * cy - cr * sp * sy;
y = cr * sp * cy + sr * cp * sy;
z = cr * cp * sy - sr * sp * cy;
}
void Quaternion::SetFrom(const AxisAngle &angle) {
double s = std::sin(angle.angle / 2);
x = angle.axis.x * s;
y = angle.axis.y * s;
z = angle.axis.z * s;
w = std::cos(angle.angle / 2);
}
EulerAngle Quaternion::ToEulerAngle() const {
return EulerAngle(*this);
Normalize();
}
Quaternion Quaternion::RandomRotation(RNG &rng) {
@@ -271,16 +228,16 @@ namespace J3ML::LinearAlgebra {
return Quaternion::Identity;
}
float Quaternion::Normalize() {
bool Quaternion::Normalize() {
float length = Length();
if (length < 1e-4f)
return 0.f;
return false;
float rcpLength = 1.f / length;
x *= rcpLength;
y *= rcpLength;
z *= rcpLength;
w *= rcpLength;
return length;
return true;
}
bool Quaternion::IsNormalized(float epsilon) const {
@@ -325,9 +282,10 @@ namespace J3ML::LinearAlgebra {
Quaternion Quaternion::LookAt(const Vector3 &localForward, const Vector3 &targetDirection, const Vector3 &localUp,
const Vector3 &worldUp) {
return Matrix3x3::LookAt(localForward, targetDirection, localUp, worldUp).ToQuat();
return Quaternion(Matrix3x3::LookAt(localForward, targetDirection, localUp, worldUp));
}
/*
Quaternion Quaternion::RotateX(float angleRadians) {
return {{1,0,0}, angleRadians};
}
@@ -343,6 +301,7 @@ namespace J3ML::LinearAlgebra {
Quaternion Quaternion::RotateAxisAngle(const AxisAngle &axisAngle) {
return {axisAngle.axis, axisAngle.angle};
}
*/
Quaternion Quaternion::RotateFromTo(const Vector3 &sourceDirection, const Vector3 &targetDirection) {
assert(sourceDirection.IsNormalized());
@@ -369,14 +328,6 @@ namespace J3ML::LinearAlgebra {
return Quaternion::RotateFromTo(sourceDirection.XYZ(), targetDirection.XYZ());
}
Quaternion::Quaternion(const float *data) {
assert(data);
x = data[0];
y = data[1];
z = data[2];
w = data[3];
}
Quaternion Quaternion::Lerp(const Quaternion &source, const Quaternion &target, float t) { return source.Lerp(target, t);}
Quaternion Quaternion::Slerp(const Quaternion &source, const Quaternion &target, float t) { return source.Slerp(target, t);}
@@ -401,4 +352,28 @@ namespace J3ML::LinearAlgebra {
float Quaternion::LengthSquared() const { return x*x + y*y + z*z + w*w;}
float Quaternion::Length() const { return std::sqrt(LengthSquared()); }
Quaternion::Quaternion(const EulerAngleXYZ& rhs) {
float cos_roll = Math::Cos(0.5f * Math::Radians(rhs.roll));
float sin_roll = Math::Sin(0.5f * Math::Radians(rhs.roll));
float cos_pitch = Math::Cos(0.5f * Math::Radians(rhs.pitch));
float sin_pitch = Math::Sin(0.5f * Math::Radians(rhs.pitch));
float cos_yaw = Math::Cos(0.5f * Math::Radians(rhs.yaw));
float sin_yaw = Math::Sin(0.5f * Math::Radians(rhs.yaw));
x = cos_roll * sin_pitch * sin_yaw + sin_roll * cos_pitch * cos_yaw;
y = -sin_roll * cos_pitch * sin_yaw + cos_roll * sin_pitch * cos_yaw;
z = cos_roll * cos_pitch * sin_yaw + sin_roll * sin_pitch * cos_yaw;
w = -sin_roll * sin_pitch * sin_yaw + cos_roll * cos_pitch * cos_yaw;
Normalize();
}
Quaternion::Quaternion(const Quaternion& rhs) {
x = rhs.x;
y = rhs.y;
z = rhs.z;
w = rhs.w;
}
}

View File

@@ -0,0 +1,25 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
jtest::Unit AxisAngleUnit {"AxisAngle"};
namespace AxisAngleTests {
inline void Define() {
using namespace jtest;
AxisAngleUnit += Test("From_Quaternion", [] {
AxisAngle expected_result({0.3860166, 0.4380138, 0.8118714}, 0.6742209);
Quaternion q(0.1276794, 0.1448781, 0.2685358, 0.9437144);
AxisAngle from_quaternion(q);
jtest::check(Math::EqualAbs(expected_result.axis.x, from_quaternion.axis.x, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.axis.y, from_quaternion.axis.y, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.axis.z, from_quaternion.axis.z, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.angle, from_quaternion.angle, 1e-6f));
});
}
inline void Run() {
AxisAngleUnit.RunAll();
}
}

View File

@@ -5,14 +5,21 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
jtest::Unit EulerAngleUnit {"EulerAngle"};
jtest::Unit EulerAngleUnit {"EulerAngle_XYZ"};
namespace EulerAngleTests {
inline void Define() {
using namespace jtest;
EulerAngleUnit += Test("Not Implemented", [] {
throw("Not Implemented");
EulerAngleUnit += Test("From_Quaternion", [] {
EulerAngleXYZ expected_result(-170, 88, -160);
Quaternion q(0.1840604, 0.6952024, 0.1819093, 0.6706149);
EulerAngleXYZ from_quaternion(q);
jtest::check(Math::EqualAbs(Math::Radians(expected_result.roll), Math::Radians(from_quaternion.roll), 1e-5f));
jtest::check(Math::EqualAbs(Math::Radians(expected_result.pitch), Math::Radians(from_quaternion.pitch), 1e-5f));
jtest::check(Math::EqualAbs(Math::Radians(expected_result.yaw), Math::Radians(from_quaternion.yaw), 1e-5f));
});
}
inline void Run() {

View File

@@ -11,6 +11,42 @@ namespace Matrix3x3Tests
using namespace jtest;
using namespace J3ML::LinearAlgebra;
Matrix3x3Unit += Test("AngleTypeRound-TripConversion", [] {
EulerAngleXYZ expected_result(8, 60, -27);
Matrix3x3 m(expected_result);
AxisAngle a(expected_result);
Quaternion q(a);
Matrix3x3 m2(q);
Quaternion q2(m2);
AxisAngle a2(q2);
EulerAngleXYZ round_trip(a2);
jtest::check(Math::EqualAbs(Math::Radians(expected_result.roll), Math::Radians(round_trip.roll), 1e-6f));
jtest::check(Math::EqualAbs(Math::Radians(expected_result.pitch), Math::Radians(round_trip.pitch), 1e-6f));
jtest::check(Math::EqualAbs(Math::Radians(expected_result.yaw), Math::Radians(round_trip.yaw), 1e-6f));
});
Matrix3x3Unit += Test("From_EulerAngleXYZ", []{
Matrix3x3 expected_result(Vector3(0.4455033, 0.2269952, 0.8660254),
Vector3(-0.3421816, 0.9370536, -0.0695866),
Vector3(-0.8273081, -0.2653369, 0.4951340)
);
EulerAngleXYZ e(8, 60, -27);
Matrix3x3 from_euler(e);
jtest::check(Math::EqualAbs(expected_result.At(0, 0), from_euler.At(0, 0), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(0, 1), from_euler.At(0, 1), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(0, 2), from_euler.At(0, 2), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(1, 0), from_euler.At(1, 0), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(1, 1), from_euler.At(1, 1), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(1, 2), from_euler.At(1, 2), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(2, 0), from_euler.At(2, 0), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(2, 1), from_euler.At(2, 1), 1e-6f));
jtest::check(Math::EqualAbs(expected_result.At(2, 2), from_euler.At(2, 2), 1e-6f));
});
Matrix3x3Unit += Test("Add_Unary", []
{
Matrix3x3 m(1,2,3, 4,5,6, 7,8,9);

View File

@@ -111,20 +111,6 @@ namespace Matrix4x4Tests {
Matrix4x4Unit += Test("InverseOrthonormal", [] {});
Matrix4x4Unit += Test("DeterminantCorrectness", [] { });
Matrix4x4Unit += Test("MulMat3x3", [] {});
Matrix4x4Unit += Test("AngleTypeRoundtripConversion", [] {
Matrix4x4 matrix;
EulerAngle a(Math::Radians(45), Math::Radians(45), Math::Radians(45));
Quaternion q(a);
matrix.SetRotatePart(q);
//matrix.SetRotatePartX(a.pitch);
//matrix.SetRotatePartY(a.yaw);
//matrix.SetRotatePartZ(a.roll);
EulerAngle fromMatrix = matrix.GetRotatePart().ToQuat().ToEulerAngle();
jtest::check(a == fromMatrix);
});
}
inline void Run() {

View File

@@ -4,10 +4,14 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/LinearAlgebra/EulerAngle.hpp>
#include <J3ML/Algorithm/RNG.hpp>
jtest::Unit QuaternionUnit {"Quaternion"};
namespace QuaternionTests {
// This is here to check the accuracy of the Slerp inside the Quaternion class.
// Although you don't jtest::check anything :shrug: - Redacted.
Quaternion PreciseSlerp(const Quaternion &a, const Quaternion& b, float t)
{
double angle = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
@@ -69,6 +73,28 @@ namespace QuaternionTests {
Quaternion lerp = q.Lerp(q2, t);
}
});
QuaternionUnit += Test("From_EulerAngleXYZ", [] {
Quaternion expected_result(0.1819093, 0.6706149, 0.1840604, 0.6952024);
EulerAngleXYZ e(10, 88, 20);
Quaternion from_euler(e);
jtest::check(Math::EqualAbs(expected_result.x, from_euler.x, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.y, from_euler.y, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.z, from_euler.z, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.w, from_euler.w, 1e-6f));
});
QuaternionUnit += Test("From_AxisAngle", [] {
Quaternion expected_result(0.0579133, 0.0782044, 0.1765667, 0.9794664);
AxisAngle a({0.2872573, 0.3879036, 0.8757934}, 0.4059981);
Quaternion from_axis(a);
jtest::check(Math::EqualAbs(expected_result.x, from_axis.x, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.y, from_axis.y, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.z, from_axis.z, 1e-6f));
jtest::check(Math::EqualAbs(expected_result.w, from_axis.w, 1e-6f));
});
QuaternionUnit += Test("Mat4x4Conversion", [] { throw("Not Implemented"); });
QuaternionUnit += Test("MulOpQuat", [] { throw("Not Implemented"); });
QuaternionUnit += Test("DivOpQuat", [] { throw("Not Implemented"); });

View File

@@ -16,6 +16,7 @@
#include "Geometry/FrustumTests.hpp"
#include "LinearAlgebra/EulerAngleTests.hpp"
#include "LinearAlgebra/AxisAngleTests.hpp"
#include "LinearAlgebra/Matrix2x2Tests.hpp"
#include "LinearAlgebra/Matrix3x3Tests.hpp"
#include "LinearAlgebra/Matrix4x4Tests.hpp"
@@ -64,10 +65,11 @@ namespace LinearAlgebraTests
{
void Define()
{
EulerAngleTests::Define();
Vector2Tests::Define();
Vector3Tests::Define();
Vector4Tests::Define();
AxisAngleTests::Define();
EulerAngleTests::Define();
QuaternionTests::Define();
Matrix2x2Tests::Define();
Matrix3x3Tests::Define();
@@ -76,10 +78,11 @@ namespace LinearAlgebraTests
}
void Run()
{
EulerAngleTests::Run();
Vector2Tests::Run();
Vector3Tests::Run();
Vector4Tests::Run();
AxisAngleTests::Run();
EulerAngleTests::Run();
QuaternionTests::Run();
Matrix2x2Tests::Run();
Matrix3x3Tests::Run();