Compare commits

...

13 Commits

Author SHA1 Message Date
85aac1c192 Update CMakeLists.txt
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 6m44s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 24s
Get rid of event cmake warning.
2024-10-10 12:14:21 -04:00
29db4f0792 Implement rest of LineSegment2D members.
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m24s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 27s
2024-10-07 15:36:48 -04:00
143b7e7279 Implementing LineSegment2D class
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m30s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 28s
2024-10-06 22:25:01 -04:00
3861741ff2 Remove inlining of Capsule::AnyPointFast() (seems to prevent compilation on -O3 optimization level)
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m16s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 28s
2024-10-04 12:36:43 -04:00
262603a496 Merge remote-tracking branch 'origin/main'
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 5m25s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 23s
2024-10-01 10:38:56 -04:00
5e0e4b8349 Implementing LineSegment2D class 2024-10-01 10:38:49 -04:00
Redacted
756316169e Update README.md
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 5m15s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 20s
2024-09-17 14:46:53 -04:00
237c318bf1 fix https://git.redacted.cc/Redacted/ReWindow.git using rebitch
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m24s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 21s
2024-08-26 19:49:12 -04:00
2b5978f11b Matrix2x2 ptr()
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m25s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 20s
2024-08-24 10:32:05 -04:00
155296ac88 Merge remote-tracking branch 'origin/main'
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 1m11s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 20s
2024-08-22 18:12:39 -04:00
71474081bc Changes by dawsh 2024-08-22 18:12:07 -04:00
Redacted
53badd110a Update CMakeLists.txt
Some checks failed
Run ReCI Build Test / Explore-Gitea-Actions (push) Failing after 6m2s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 20s
2024-08-22 12:03:49 -04:00
05d664ecba migrate to new JTest API
Some checks failed
Run ReCI Build Test / Explore-Gitea-Actions (push) Failing after 6m37s
Build Docs With Doxygen / Explore-Gitea-Actions (push) Successful in 24s
2024-08-21 20:21:14 -04:00
31 changed files with 1936 additions and 1353 deletions

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.18...3.28)
cmake_minimum_required(VERSION 3.18...3.27)
PROJECT(J3ML
VERSION 1.1
LANGUAGES CXX
@@ -34,7 +34,7 @@ set_target_properties(J3ML PROPERTIES LINKER_LANGUAGE CXX)
CPMAddPackage(
NAME jtest
URL https://git.redacted.cc/josh/jtest/archive/Prerelease-5.zip
URL https://git.redacted.cc/josh/jtest/archive/Release-1.4.zip
)
target_include_directories(J3ML PUBLIC ${jtest_SOURCE_DIR}/include)
@@ -58,4 +58,4 @@ target_link_libraries(MathDemo ${PROJECT_NAME})
if(WIN32)
#target_compile_options(MathDemo PRIVATE -mwindows)
endif()
endif()

View File

@@ -4,7 +4,7 @@
Yet Another C++ Math Standard
J3ML is a "Modern C++" C++ library designed to provide comprehensive support for 3D mathematical operations commonly used in computer graphics, game development, physics simulations, and related fields. It offers a wide range of functionalities to simplify the implementation of complex mathematical operations in your projects.
J3ML is a "Modern C++" library designed to provide comprehensive support for 3D mathematical operations commonly used in computer graphics, game development, physics simulations, and related fields. It offers a wide range of functionalities to simplify the implementation of complex mathematical operations in your projects.
![Static Badge](https://img.shields.io/badge/Lit-Based-%20)
@@ -50,7 +50,7 @@ Documentation is automatically generated from latest commit and is hosted at htt
# Contributing
Contributions to J3ML are welcome! If you find a bug, have a feature request, or would like to contribute code, please submit an issue or pull request to the GitHub repository.
Contributions to J3ML are welcome! If you find a bug, have a feature request, or would like to contribute code, please submit an issue or pull request to the repository.
# License

View File

@@ -16,6 +16,15 @@
#include <J3ML/Geometry/Shape.hpp>
#include <J3ML/Geometry/Forward.hpp>
/// See Christer Ericson's Real-time Collision Detection, p. 87, or
/// James Arvo's "Transforming Axis-aligned Bounding Boxes" in Graphics Gems 1, pp. 548-550.
/// http://www.graphicsgems.org/
template <typename Matrix>
void AABB2DTransformAsAABB2D(AABB2D& aabb, Matrix& m);
namespace J3ML::Geometry
{
using LinearAlgebra::Vector2;
@@ -31,28 +40,30 @@ namespace J3ML::Geometry
minPoint(min), maxPoint(max)
{}
float Width() const;
float Height() const;
[[nodiscard]] float Width() const;
[[nodiscard]] float Height() const;
float DistanceSq(const Vector2& pt) const;
Vector2 Centroid();
[[nodiscard]] float DistanceSq(const Vector2& pt) const;
void SetNegativeInfinity();
void Enclose(const Vector2& point);
bool Intersects(const AABB2D& rhs) const;
[[nodiscard]] bool Intersects(const AABB2D& rhs) const;
bool Contains(const AABB2D& rhs) const;
[[nodiscard]] bool Contains(const AABB2D& rhs) const;
bool Contains(const Vector2& pt) const;
[[nodiscard]] bool Contains(const Vector2& pt) const;
bool Contains(int x, int y) const;
[[nodiscard]] bool Contains(int x, int y) const;
bool IsDegenerate() const;
[[nodiscard]] bool IsDegenerate() const;
bool HasNegativeVolume() const;
[[nodiscard]] bool HasNegativeVolume() const;
bool IsFinite() const;
[[nodiscard]] bool IsFinite() const;
Vector2 PosInside(const Vector2 &normalizedPos) const;
@@ -62,5 +73,30 @@ namespace J3ML::Geometry
AABB2D operator-(const Vector2& pt) const;
Vector2 CornerPoint(int cornerIndex);
void TransformAsAABB(const Matrix3x3& transform);
void TransformAsAABB(const Matrix4x4& transform);
};
template<typename Matrix>
void AABB2DTransformAsAABB2D(AABB2D &aabb, Matrix &m) {
float ax = m[0][0] * aabb.minPoint.x;
float bx = m[0][0] * aabb.maxPoint.x;
float ay = m[0][1] * aabb.minPoint.y;
float by = m[0][1] * aabb.maxPoint.y;
float ax2 = m[1][0] * aabb.minPoint.x;
float bx2 = m[1][0] * aabb.maxPoint.x;
float ay2 = m[1][1] * aabb.minPoint.y;
float by2 = m[1][1] * aabb.maxPoint.y;
aabb.minPoint.x = J3ML::Math::Min(ax, bx) + J3ML::Math::Min(ay, by) + m[0][3];
aabb.maxPoint.x = J3ML::Math::Max(ax, bx) + J3ML::Math::Max(ay, by) + m[0][3];
aabb.minPoint.y = J3ML::Math::Min(ax2, bx2) + J3ML::Math::Min(ay2, by2) + m[1][3];
aabb.maxPoint.y = J3ML::Math::Max(ax2, bx2) + J3ML::Math::Max(ay2, by2) + m[1][3];
}
}

View File

@@ -54,7 +54,7 @@ namespace J3ML::Geometry
/// Quickly returns an arbitrary point inside this Capsule. Used in GJK intersection test.
[[nodiscard]] inline Vector3 AnyPointFast() const;
[[nodiscard]] Vector3 AnyPointFast() const;
/// Generates a point that perhaps lies inside this capsule.
/** @param height A normalized value between [0,1]. This specifies the point position along the height line of this capsule.

View File

@@ -83,7 +83,7 @@ namespace J3ML::Geometry
/// Specifies whether this frustum is a perspective or an orthographic frustum.
FrustumType type;
/// Specifies whether the [-1, 1] or [0, 1] range is used for the post-projective depth range.
FrustumProjectiveSpace projectiveSpace;
FrustumProjectiveSpace projectiveSpace ;
/// Specifies the chirality of world and view spaces
FrustumHandedness handedness;
/// The eye point of this frustum

View File

@@ -0,0 +1,174 @@
#pragma once
#include <J3ML/LinearAlgebra/Vector2.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <J3ML/LinearAlgebra/Matrix4x4.hpp>
namespace J3ML::Geometry
{
/// A line segment in 2D space is a finite line with a start and end point.
class LineSegment2D {
public:
/// The starting point of this line segment.
Vector2 A;
/// The end point of this line segment.
Vector2 B;
/// The default constructor does not initialize any members of this class.
/** This means that the values of the members a and b are undefined after creating a new LineSegment2D using this
default constructor. Remember to assign to them before use.
@see A, B. */
LineSegment2D() {}
/// Constructs a line segment through the given end points.
LineSegment2D(const Vector2 &a, const Vector2 &b);
/// Returns a point on the line.
/** @param d The normalized distance along the line segment to compute. If a value in the range [0, 1] is passed, then the
returned point lies along this line segment. If some other value is specified, the returned point lies on the line
defined by this line segment, but *not* inside the interval from a to b.
@note The meaning of d here differs from Line2D::GetPoint and Ray2D::GetPoint. For the class LineSegment2D,
GetPoint(0) returns a, and getPoint(1) returns b. This means that GetPoint(1) will not generally be exactly one unit
away from the starting point of this line segment, as is the case with Line2D and Ray2D.
@return (1-d)*a + d*b;
@see a, b, Line2D::GetPoint(), Ray2D::GetPoint() */
Vector2 GetPoint(float d) const;
/// Returns the center point of this line segment.
/** This function is the same as calling GetPoint(0.5f), but provided here as convenience.
@see GetPoint(). */
Vector2 CenterPoint() const;
Vector2 Centroid() const;
/// Reverses the direction of this line segment.
/** This function swaps the start and end points of this line segment so that it runs from b to a.
This does not have an effect on the set of points represented by this line segment, but it reverses
the direction of the vector returned by Dir().
@note This function operates in-place.
@see a, b, Dir(). */
void Reverse();
/// Returns the normalized direction vector that points in the direction a->b.
/** @note The returned vector is normalized, meaning that its length is 1, not |b-a|.
@see a, b. */
Vector2 Dir() const;
/// Quickly returns an arbitrary point inside this LineSegment2D. Used in GJK intersection test.
Vector2 AnyPointFast() const;
/// Computes an extreme point of this LineSegment2D in the given direction.
/** An extreme point is a farthest point along this LineSegment2D in the given direction. Given a direction,
this point is not necessarily unique.
@param direction The direction vector of the direction to find the extreme point. This vector may
be un-normalized, but may not be null.
@return An extreme point of this LineSegment2D in the given direction. The returned point is always
either a or b.
@see a, b. **/
Vector2 ExtremePoint(const Vector2 &direction) const;
Vector2 ExtremePoint(const Vector2 &direction, float &projectionDistance) const;
/// Translates this LineSegment2D in world space.
/** @param offset The amount of displacement to apply to this LineSegment2D, in world space coordinates.
@see Transform(). */
void Translate(const Vector2& offset);
/// Applies a transformation to this line.
/** This function operates in-place.
@see Translate(), Transform(), classes Matrix3x3, Matrix4x4, Quaternion*/
void Transform(const Matrix3x3& transform);
void Transform(const Matrix4x4& transform);
void Transform(const Quaternion& transform);
/// Computes the length of this line segment.
/** @return |b - a|
@see a, b. */
float Length() const;
/// Computes the squared length of this line segment.
/** Calling this function is faster than calling Length(), since this function avoids computing a square root.
If you only need to compare lengths to each other and are not interested in the actual length values,
you can compare using LengthSq(), instead of Length(), since Sqrt() is an order-preserving,
(monotonous and non-decreasing) function. */
float LengthSq() const;
float LengthSquared() const;
/// Tests if this line segment is finite
/** A line segment is finite if its endpoints a and b do not contain floating-point NaNs for +/- infs
in them.
@return True if both a and b have finite floating-point values. */
bool IsFinite() const;
/// Tests if this line segment represents the same set of points than the the given line segment.
/** @param epsilon Specifies how much distance threshold to allow in the comparison.
@return True if a == rhs.a && b == rhs.b, or, a == rhs.b && b == rhs.a, within the given epsilon. */
bool Equals(const LineSegment2D& rhs, float epsilon = 1e-3f) const;
/// Tests if the given point or line segment is contained on this line segment.
/** @param epsilon Because a line segment is a one-dimensional object in 3D space, an epsilon value
is used as a threshold for this test. This effectively transforms this line segment to a capsule with
the radius indicated by this value.
@return True if this line segment contains the given point or line segment.
@see Intersects, ClosestPoint(), Distance(). */
bool Contains(const Vector2& point, float epsilon = 1e-3f) const;
bool Contains(const LineSegment2D& rhs, float epsilon = 1e-3f) const;
/// Computes the closest point on this line segment to the given object.
/** @param d If specified, this parameter receives the normalized distance along
this line segment which specifies the closest point on this line segment to
the specified point.
@return The closest point on this line segment to the given object.
@see Contains(), Distance(), Intersects(). */
Vector2 ClosestPoint(const Vector2& point) const;
Vector2 ClosestPoint(const Vector2& point, float& d) const;
/** @param d2 [out] If specified, this parameter receives the (normalized, in case of line segment)
distance along the other line object which specifies the closest point on that line to
this line segment. */
Vector2 ClosestPoint(const LineSegment2D& other) const;
Vector2 ClosestPoint(const LineSegment2D& other, float& d) const;
Vector2 ClosestPoint(const LineSegment2D& other, float& d, float& d2) const;
/// Computes the distance between this line segment and the given object.
/** @param d [out] If specified, this parameter receives the normalized distance along
this line segment which specifies the closest point on this line segment to
the specified point.
@return The distance between this line segment and the given object.
@see */
float Distance(const Vector2& point) const;
float Distance(const Vector2& point, float& d) const;
/** @param d2 [out] If specified, this parameter receives the (normalized, in case of line segment)
distance along the other line object which specifies the closest point on that line to
this line segment. */
float Distance(const LineSegment2D& other) const;
float Distance(const LineSegment2D& other, float& d) const;
float Distance(const LineSegment2D& other, float& d, float& d2) const;
float DistanceSq(const Vector2& point) const;
float DistanceSq(const LineSegment2D& other) const;
/** @param epsilon If testing intersection between two line segments, a distance threshold value is used to account
for floating point inaccuracies. */
bool Intersects(const LineSegment2D& lineSegment, float epsilon = 1e-3f) const;
/// Projects this LineSegment2D onto the given 1D axis direction vector.
/** This function collapses this LineSegment2D onto a 1D axis for the purposes of e.g. separate axis test computations.
This function returns a 1D range [outMin, outNax] denoting the interval of the projection.
@param direction The 1D axis to project to. This vector may be unnormalized, in which case the output
of this function gets scaled by the length of this vector.
@param outMin [out] Returns the minimum extent of this vector along the projection axis.
@param outMax [out] Returns the maximum extent of this vector along the projection axis. */
void ProjectToAxis(const Vector2& direction, float& outMin, float& outMax) const;
protected:
private:
};
LineSegment2D operator * (const Matrix3x3& transform, const LineSegment2D& line);
LineSegment2D operator * (const Matrix4x4& transform, const LineSegment2D& line);
LineSegment2D operator * (const Quaternion& transform, const LineSegment2D& line);
}

View File

@@ -19,8 +19,8 @@
namespace J3ML::LinearAlgebra {
class Matrix2x2 {
public:
enum { Rows = 3 };
enum { Cols = 3 };
enum { Rows = 2 };
enum { Cols = 2 };
static const Matrix2x2 Zero;
static const Matrix2x2 Identity;
static const Matrix2x2 NaN;
@@ -51,6 +51,9 @@ namespace J3ML::LinearAlgebra {
Vector2 operator * (const Vector2& rhs) const;
Matrix2x2 operator * (const Matrix2x2 &rhs) const;
inline float* ptr() { return &elems[0][0];}
[[nodiscard]] inline const float* ptr() const {return &elems[0][0];}
protected:
float elems[2][2];
};

View File

@@ -236,8 +236,8 @@ namespace J3ML::LinearAlgebra {
/// Accesses this structure as a float array.
/// @return A pointer to the upper-left element. The data is contiguous in memory.
/// ptr[0] gives the element [0][0], ptr[1] is [0][1]. ptr[2] is [0][2]
inline float *ptr() { return &elems[0][0];}
[[nodiscard]] inline const float *ptr() const {return &elems[0][0];}
inline float* ptr() { return &elems[0][0];}
[[nodiscard]] inline const float* ptr() const {return &elems[0][0];}
/// Convers this rotation matrix to a quaternion.

View File

@@ -36,7 +36,7 @@ namespace J3ML::Algorithm {
u32 RNG::Int()
{
assert(modulus != 0);
//assert(modulus != 0);
/// TODO: Convert to using Shrage's method for approximate factorization (Numerical Recipes in C)
// Currently we cast everything to 64-bit to avoid overflow, which is quite dumb.

View File

@@ -77,4 +77,20 @@ namespace J3ML::Geometry
a.maxPoint = maxPoint - pt;
return a;
}
Vector2 AABB2D::Centroid() {
return (minPoint + (maxPoint / 2.f));
}
Vector2 AABB2D::CornerPoint(int cornerIndex) {
assert(0 <= cornerIndex && cornerIndex <= 3);
switch(cornerIndex)
{
default:
case 0: return minPoint;
case 1: return {minPoint.x, maxPoint.y};
case 2: return {maxPoint.x, minPoint.y};
case 3: return maxPoint;
}
}
}

View File

@@ -547,7 +547,7 @@ namespace J3ML::Geometry
Matrix4x4 Frustum::ComputeWorldMatrix() const {
assert(pos.IsFinite());
assert(up.IsNormalized(1e-3f));
//assert(up.IsNormalized(1e-3f));
assert(front.IsNormalized(1e-3f));
assert(up.IsPerpendicular(front));
@@ -577,9 +577,9 @@ namespace J3ML::Geometry
if (type == FrustumType::Invalid || projectiveSpace == FrustumProjectiveSpace::Invalid)
return Matrix4x4::NaN;
assert(type == FrustumType::Perspective || type == FrustumType::Orthographic);
assert(projectiveSpace == FrustumProjectiveSpace::GL || projectiveSpace == FrustumProjectiveSpace::D3D);
assert(handedness == FrustumHandedness::Left || handedness == FrustumHandedness::Right);
//assert(type == FrustumType::Perspective || type == FrustumType::Orthographic);
//assert(projectiveSpace == FrustumProjectiveSpace::GL || projectiveSpace == FrustumProjectiveSpace::D3D);
//assert(handedness == FrustumHandedness::Left || handedness == FrustumHandedness::Right);
if (type == FrustumType::Perspective) {
if (projectiveSpace == FrustumProjectiveSpace::GL) {
@@ -608,7 +608,7 @@ namespace J3ML::Geometry
return Matrix4x4::D3DOrthoProjLH(nearPlaneDistance, farPlaneDistance, orthographicWidth, orthographicHeight);
}
}
assert(false && "Not all values of Frustum were initialized properly! Please initialize correctly before calling Frustum::ProjectionMatrix()!");
//assert(false && "Not all values of Frustum were initialized properly! Please initialize correctly before calling Frustum::ProjectionMatrix()!");
return Matrix4x4::NaN;
}

View File

@@ -0,0 +1,165 @@
#include <J3ML/Geometry/LineSegment2D.hpp>
void Line2DClosestPointLineLine(const Vector2& v0, const Vector2& v10, const Vector2& v2, const Vector2& v32, float& d, float& d2) // TODO: Move to Line2D when that exists
{
// assert(!v10.IsZero());
// assert(!v32.IsZero());
Vector2 v02 = v0 - v2;
float d0232 = v02.Dot(v32);
float d3210 = v32.Dot(v10);
float d3232 = v32.Dot(v32);
// assert(d3232 != 0.f); // Don't call with a zero direction vector.
float d0210 = v02.Dot(v10);
float d1010 = v10.Dot(v10);
float denom = d1010*d3232 - d3210*d3210;
d = (denom !=0) ? (d0232*d3210 - d0210*d3232) / denom : 0;
d2 = (d0232 + d * d3210) / d3232;
}
Geometry::LineSegment2D::LineSegment2D(const Vector2 &a, const Vector2 &b)
: A(a), B(b) {}
Vector2 Geometry::LineSegment2D::GetPoint(float d) const {
return (1.f - d) * A + d * B;
}
Vector2 Geometry::LineSegment2D::CenterPoint() const {
return (A + B) * 0.5f;
}
Vector2 Geometry::LineSegment2D::Centroid() const {
return CenterPoint();
}
void Geometry::LineSegment2D::Reverse() {
Swap(A, B);
}
Vector2 Geometry::LineSegment2D::Dir() const {
return (B - A).Normalized();
}
Vector2 Geometry::LineSegment2D::AnyPointFast() const { return A; }
Vector2 Geometry::LineSegment2D::ExtremePoint(const Vector2 &direction) const {
return Vector2::Dot(direction, B - A) >= 0.f ? B : A;
}
Vector2 Geometry::LineSegment2D::ExtremePoint(const Vector2 &direction, float &projectionDistance) const {
Vector2 extremePoint = ExtremePoint(direction);
projectionDistance = extremePoint.Dot(direction);
return extremePoint;
}
void Geometry::LineSegment2D::Translate(const Vector2 &offset) {
A += offset;
B += offset;
}
void Geometry::LineSegment2D::Transform(const Matrix3x3 &transform) {
A = transform.Mul(A);
B = transform.Mul(B);
}
void Geometry::LineSegment2D::Transform(const Matrix4x4 &transform) {
A = transform.Mul(A);
B = transform.Mul(B);
}
void Geometry::LineSegment2D::Transform(const Quaternion &transform) {
//A = transform.Mul(A);
//B = transform.Mul(B);
}
float Geometry::LineSegment2D::Length() const { return A.Distance(B); }
float Geometry::LineSegment2D::LengthSq() const { return A.DistanceSq(B); }
float Geometry::LineSegment2D::LengthSquared() const { return LengthSq(); }
bool Geometry::LineSegment2D::IsFinite() const { return A.IsFinite() && B.IsFinite(); }
bool Geometry::LineSegment2D::Equals(const Geometry::LineSegment2D &rhs, float epsilon) const {
return (A.Equals(rhs.A, epsilon) && B.Equals(rhs.B, epsilon) ||
A.Equals(rhs.B, epsilon) && B.Equals(rhs.A, epsilon));
}
bool Geometry::LineSegment2D::Contains(const Vector2 &point, float epsilon) const {
return ClosestPoint(point).DistanceSq(point) <= epsilon;
}
bool Geometry::LineSegment2D::Contains(const Geometry::LineSegment2D &rhs, float epsilon) const {
return Contains(rhs.A, epsilon) && Contains(rhs.B, epsilon);
}
Vector2 Geometry::LineSegment2D::ClosestPoint(const Vector2 &point) const {
float d;
return ClosestPoint(point, d);
}
Vector2 Geometry::LineSegment2D::ClosestPoint(const Vector2 &point, float &d) const {
Vector2 dir = B - A;
d = J3ML::Math::Clamp01(Vector2::Dot(point - A, dir) / dir.LengthSquared());
return A + d * dir;
}
Vector2 Geometry::LineSegment2D::ClosestPoint(const Geometry::LineSegment2D &other) const {
float d, d2;
return ClosestPoint(other, d, d2);
}
Vector2 Geometry::LineSegment2D::ClosestPoint(const Geometry::LineSegment2D &other, float &d) const {
float d2; return ClosestPoint(other, d, d2);
}
Vector2 Geometry::LineSegment2D::ClosestPoint(const Geometry::LineSegment2D &other, float &d, float &d2) const {
Vector2 dir = B - A;
Line2DClosestPointLineLine(A, B - A, other.A, other.B - other.A, d, d2);
return Vector2::Zero;
}
float Geometry::LineSegment2D::Distance(const Vector2 &point) const { float d; return Distance(point, d); }
float Geometry::LineSegment2D::Distance(const Vector2 &point, float &d) const {
/// See Christer Ericson's Real-Time Collision Detection, p. 130.
Vector2 closestPoint = ClosestPoint(point, d);
return closestPoint.Distance(point);
}
float Geometry::LineSegment2D::Distance(const Geometry::LineSegment2D &other) const { float d, d2; return Distance(other, d, d2);}
float Geometry::LineSegment2D::Distance(const Geometry::LineSegment2D &other, float &d) const { float d2; return Distance(other, d, d2); }
float Geometry::LineSegment2D::Distance(const Geometry::LineSegment2D &other, float &d, float &d2) const {
ClosestPoint(other, d, d2);
return GetPoint(d).Distance(other.GetPoint(d2));
}
float Geometry::LineSegment2D::DistanceSq(const Geometry::LineSegment2D &other) const {
float d, d2;
ClosestPoint(other, d, d2);
return GetPoint(d).DistanceSq(other.GetPoint(d2));
}
float Geometry::LineSegment2D::DistanceSq(const Vector2 &point) const {
float d;
/// See Christer Ericson's Real-Time Collision Detection, p.130.
Vector2 closestPoint = ClosestPoint(point, d);
return closestPoint.DistanceSq(point);
}
bool Geometry::LineSegment2D::Intersects(const Geometry::LineSegment2D &lineSegment, float epsilon) const {
return Distance(lineSegment) <= epsilon;
}
void Geometry::LineSegment2D::ProjectToAxis(const Vector2 &direction, float &outMin, float &outMax) const {
outMin = Vector2::Dot(direction, A);
outMax = Vector2::Dot(direction, B);
if (outMax < outMin)
Swap(outMin, outMax);
}

View File

@@ -211,7 +211,7 @@ namespace J3ML::Geometry
}
Vector3 Ray::GetPoint(float distance) const {
assert(Direction.IsNormalized());
//assert(Direction.IsNormalized());
return Origin + distance * Direction;
}
}

View File

@@ -676,7 +676,7 @@ namespace J3ML::LinearAlgebra {
Vector3 Matrix4x4::TransformDir(float tx, float ty, float tz) const
{
assert(!this->ContainsProjection()); // This function does not divide by w or output it, so cannot have projection.
//assert(!this->ContainsProjection()); // This function does not divide by w or output it, so cannot have projection.
return Vector3(At(0, 0) * tx + At(0, 1) * ty + At(0, 2) * tz,
At(1, 0) * tx + At(1, 1) * ty + At(1, 2) * tz,
At(2, 0) * tx + At(2, 1) * ty + At(2, 2) * tz);
@@ -685,7 +685,7 @@ namespace J3ML::LinearAlgebra {
void Matrix4x4::InverseOrthonormal()
{
assert(!ContainsProjection());
//assert(!ContainsProjection());
// a) Transposed the top-left 3x3 part in-place to produce R^t.
Swap(elems[0][1], elems[1][0]);

View File

@@ -1,142 +1,149 @@
#include <J3ML/Algorithm/RNG.hpp>
#include <jtest/jtest.hpp>
using J3ML::Algorithm::RNG;
#include <jtest/Unit.hpp>
void Float01InclTest()
jtest::Unit RNGUnit{"RNG"};
namespace RNGTests
{
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
inline void Define()
{
float f = rng.Float01Incl();
float f2 = rng.Float01Incl();
using namespace jtest;
using J3ML::Algorithm::RNG;
RNGUnit += Test("IntFast", []{
RNG rng;
u32 prev = rng.IntFast();
for (int i = 0; i < 1000; ++i)
{
u32 next = rng.IntFast();
jtest::check(next != prev);
prev = next;
}
});
RNGUnit += Test("Int", []{
RNG rng;
assert(rng.lastNumber != 0 || rng.increment != 0);
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
int prev = rng.Int();
int next = rng.Int();
jtest::check(prev != 0 || next != 0);
if (prev != next)
allEqual = false;
}
jtest::check(!allEqual);
});
RNGUnit += Test("Int_A_B", []{
RNG rng;
for (int i = 0; i < 1000; ++i)
{
int a = rng.Int();
int b = rng.Int();
if (b < a)
Swap(a, b);
int val = rng.Int(a, b);
jtest::check( a <= val);
jtest::check(val <= b);
}
});
RNGUnit += Test("Float", []{
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
float f = rng.Float();
float f2 = rng.Float();
jtest::check(f < 1.f);
jtest::check(f >= 0.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
});
RNGUnit += Test("Float01Incl", [] {
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
float f = rng.Float01Incl();
float f2 = rng.Float01Incl();
jtest::check(f <= 1.f);
jtest::check(f >= 0.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
});
RNGUnit += Test("FloatNeg1_1", []{
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
float f = rng.FloatNeg1_1();
float f2 = rng.FloatNeg1_1();
jtest::check(f < 1.f);
jtest::check(f > -1.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
});
RNGUnit += Test("Float_A_B", []{
RNG rng;
for (int i = 0; i < 1000; ++i)
{
float a = rng.Float();
float b = rng.Float();
if (a == b)
continue;
if (b < a)
Swap(a, b);
float f = rng.Float(a, b);
jtest::check(a <= f);
jtest::check(f < b);
}
});
RNGUnit += Test("Float_A_B_Incl", [] {
RNG rng;
for (int i = 0; i < 1000; ++i)
{
float a = rng.Float();
float b = rng.Float();
if (b < a)
Swap(a, b);
float f = rng.FloatIncl(a, b);
jtest::check(a <= f);
jtest::check(f <= b);
}
});
jtest::check(f <= 1.f);
jtest::check(f >= 0.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
}
void FloatABInclTest()
{
RNG rng;
for (int i = 0; i < 1000; ++i)
inline void Run()
{
float a = rng.Float();
float b = rng.Float();
if (b < a)
Swap(a, b);
float f = rng.FloatIncl(a, b);
jtest::check(a <= f);
jtest::check(f <= b);
RNGUnit.RunAll();
}
}
int RNGTests()
{
TEST("RNG::IntFast", []{
RNG rng;
u32 prev = rng.IntFast();
for (int i = 0; i < 1000; ++i)
{
u32 next = rng.IntFast();
jtest::check(next != prev);
prev = next;
}
});
TEST("RNG::Int", []{
RNG rng;
assert(rng.lastNumber != 0 || rng.increment != 0);
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
int prev = rng.Int();
int next = rng.Int();
jtest::check(prev != 0 || next != 0);
if (prev != next)
allEqual = false;
}
jtest::check(!allEqual);
});
TEST("Rng::Int_A_B", []{
RNG rng;
for (int i = 0; i < 1000; ++i)
{
int a = rng.Int();
int b = rng.Int();
if (b < a)
Swap(a, b);
int val = rng.Int(a, b);
jtest::check( a <= val);
jtest::check(val <= b);
}
});
TEST("Rng::Float", []{
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
float f = rng.Float();
float f2 = rng.Float();
jtest::check(f < 1.f);
jtest::check(f >= 0.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
});
TEST("Rng::Float01Incl", Float01InclTest);
TEST("Rng::FloatNeg1_1", []{
RNG rng;
bool allEqual = true;
for (int i = 0; i < 1000; ++i)
{
float f = rng.FloatNeg1_1();
float f2 = rng.FloatNeg1_1();
jtest::check(f < 1.f);
jtest::check(f > -1.f);
jtest::check(f != 0.f || f2 != 0.f);
if (f != f2)
allEqual = false;
}
jtest::check(!allEqual);
});
TEST("Rng, Float_A_B", []{
RNG rng;
for (int i = 0; i < 1000; ++i)
{
float a = rng.Float();
float b = rng.Float();
if (a == b)
continue;
if (b < a)
Swap(a, b);
float f = rng.Float(a, b);
jtest::check(a <= f);
jtest::check(f < b);
}
});
TEST("Rng::Float_A_B_Incl", FloatABInclTest);
return 0;
}

View File

@@ -11,83 +11,88 @@
#pragma once
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/Geometry/AABB.hpp>
#include <J3ML/Geometry/LineSegment.hpp>
#include <J3ML/Geometry/Sphere.hpp>
jtest::Unit AABBUnit {"AABB"};
namespace AABBTests {
inline void Define()
{
using namespace jtest;
using namespace J3ML::Geometry;
void AABBTests()
{
using namespace jtest;
using namespace J3ML::Geometry;
TEST("AABB::Contains", [] {
AABB a ({0,0,0}, {10,10,10});
AABBUnit += Test("Contains", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains({0,0,0}));
check(a.Contains({10,10,10}));
check(a.Contains({1,2,3}));
check(!a.Contains({-1, 2, 3}));
check(!a.Contains({1, -2, 3}));
check(!a.Contains({1, 2, -3}));
check(!a.Contains({11, 2, 3}));
});
check(a.Contains({0,0,0}));
check(a.Contains({10,10,10}));
check(a.Contains({1,2,3}));
check(!a.Contains({-1, 2, 3}));
check(!a.Contains({1, -2, 3}));
check(!a.Contains({1, 2, -3}));
check(!a.Contains({11, 2, 3}));
});
TEST("AABB::ContainsAABB", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains(a));
check(a.Contains(AABB({5, 5, 5}, {6, 6, 6})));
check(a.Contains(AABB({5, 5, 5}, {10, 6, 6})));
check(!a.Contains(AABB({5,5,5}, {15, 15, 15})));
check(!a.Contains(AABB({5,5,5}, {5, 15, 5})));
check(!a.Contains(AABB({-5,-5,-5}, {5, 5, 5})));
check(!a.Contains(AABB({-5,-5,-5}, {0, 0, 0})));
});
AABBUnit += Test("ContainsAABB", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains(a));
check(a.Contains(AABB({5, 5, 5}, {6, 6, 6})));
check(a.Contains(AABB({5, 5, 5}, {10, 6, 6})));
check(!a.Contains(AABB({5,5,5}, {15, 15, 15})));
check(!a.Contains(AABB({5,5,5}, {5, 15, 5})));
check(!a.Contains(AABB({-5,-5,-5}, {5, 5, 5})));
check(!a.Contains(AABB({-5,-5,-5}, {0, 0, 0})));
});
TEST("AABB::ContainsLineSegment", [] {
AABB a ({0,0,0}, {10,10,10});
AABBUnit += Test("ContainsLineSegment", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains(LineSegment({0,0,0}, {10,10,10})));
check(a.Contains(LineSegment({10,10,10}, {0,0,0})));
check(a.Contains(LineSegment({5,5,5}, {6,6,6})));
check(a.Contains(LineSegment({5,5,5}, {10,6,6})));
check(!a.Contains(LineSegment({5,5,5}, {15, 15, 15})));
check(!a.Contains(LineSegment({5,5,5}, {5, 15,5})));
check(!a.Contains(LineSegment({-5, -5, -5}, {5,5,5})));
check(!a.Contains(LineSegment({-5, -5, -5}, {0,0,0})));
check(!a.Contains(LineSegment({15, 15, 15}, {0,0,0})));
});
check(a.Contains(LineSegment({0,0,0}, {10,10,10})));
check(a.Contains(LineSegment({10,10,10}, {0,0,0})));
check(a.Contains(LineSegment({5,5,5}, {6,6,6})));
check(a.Contains(LineSegment({5,5,5}, {10,6,6})));
check(!a.Contains(LineSegment({5,5,5}, {15, 15, 15})));
check(!a.Contains(LineSegment({5,5,5}, {5, 15,5})));
check(!a.Contains(LineSegment({-5, -5, -5}, {5,5,5})));
check(!a.Contains(LineSegment({-5, -5, -5}, {0,0,0})));
check(!a.Contains(LineSegment({15, 15, 15}, {0,0,0})));
});
TEST("AABB::ContainsSphere", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains(Sphere({0,0,0}, 0.f)));
check(a.Contains(Sphere({5,5,5}, 1.f)));
check(!a.Contains(Sphere({5,5,5}, 15.f)));
check(!a.Contains(Sphere({9,5,5}, 2.f)));
check(!a.Contains(Sphere({1,5,5}, 2.f)));
check(!a.Contains(Sphere({-10, -10, -10}, 1000.f)));
});
AABBUnit += Test("ContainsSphere", [] {
AABB a ({0,0,0}, {10,10,10});
check(a.Contains(Sphere({0,0,0}, 0.f)));
check(a.Contains(Sphere({5,5,5}, 1.f)));
check(!a.Contains(Sphere({5,5,5}, 15.f)));
check(!a.Contains(Sphere({9,5,5}, 2.f)));
check(!a.Contains(Sphere({1,5,5}, 2.f)));
check(!a.Contains(Sphere({-10, -10, -10}, 1000.f)));
});
TEST("AABB::IntersectsAABB", [] {
AABB a({0,0,0}, {10,10,10});
AABB b({5,0,0}, {15,10,10});
AABB c({-5,-5,-5}, {0,10,0});
AABB d({20,20,20}, {30,30,30});
AABB e({1,1,1}, {9,9,9});
check(a.Intersects(a));
check(a.Intersects(b));
check(!a.Intersects(c));
check(!a.Intersects(d));
check(a.Intersects(e));
});
AABBUnit += Test("IntersectsAABB", [] {
AABB a({0,0,0}, {10,10,10});
AABB b({5,0,0}, {15,10,10});
AABB c({-5,-5,-5}, {0,10,0});
AABB d({20,20,20}, {30,30,30});
AABB e({1,1,1}, {9,9,9});
check(a.Intersects(a));
check(a.Intersects(b));
check(!a.Intersects(c));
check(!a.Intersects(d));
check(a.Intersects(e));
});
TEST("AABB::TransformAsAABB", []{ /* TODO: Implement Test Stub */ });
TEST("AABB::IsDegenerate", []{ /* TODO: Implement Test Stub */ });
TEST("AABB::Volume", []{ /* TODO: Implement Test Stub */ });
AABBUnit += Test("TransformAsAABB", []{ /* TODO: Implement Test Stub */ });
AABBUnit += Test("IsDegenerate", []{ /* TODO: Implement Test Stub */ });
AABBUnit += Test("Volume", []{ /* TODO: Implement Test Stub */ });
}
inline void Run() {
AABBUnit.RunAll();
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/Geometry/Forward.hpp>
#include <jtest/jtest.hpp>
jtest::Unit CommonGeometryUnit {"CommonGeometry"};
namespace CommonGeometryTests {
inline void Define() {
using namespace jtest;
using J3ML::Geometry::Interval;
CommonGeometryUnit += Test("Interval_Intersect", [] {
// <- a ->
// <- b ->
jtest::check(!(Interval{0, 1}.Intersects({2, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({0, 1})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 4}.Intersects({3, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 4}.Intersects({1, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({3, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{3, 5}.Intersects({2, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({2, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({2, 3})));
// . a
// . b
jtest::check(!(Interval{2, 2}.Intersects({2, 2})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 5}.Intersects({3, 4})));
// <- a ->
// <- b ->
jtest::check(!(Interval{3, 4}.Intersects({2, 5})));
});
}
inline void Run() {
CommonGeometryUnit.RunAll();
}
}

View File

@@ -12,6 +12,7 @@
#pragma once
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/Geometry/Frustum.hpp>
#include <J3ML/Geometry/PBVolume.hpp>
@@ -44,285 +45,295 @@ Frustum GenIdFrustum(FrustumType t, FrustumHandedness h, FrustumProjectiveSpace
case 7 : f = GenIdFrustum(FrustumType::Orthographic, FrustumHandedness::Right, FrustumProjectiveSpace::D3D); break; \
} \
jtest::Unit FrustumUnit {"Frustum"};
namespace FrustumTests
{
inline void Define()
{
using namespace jtest;
using namespace J3ML::Geometry;
using namespace J3ML::Math;
void FrustumTests() {
using namespace jtest;
using namespace J3ML::Geometry;
using namespace J3ML::Math;
RNG rng;
RNG rng;
TEST("Frustum::AspectRatio", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(EqualAbs(f.AspectRatio(), 1.f));
};
});
TEST("Frustum::WorldRight", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Handedness() == FrustumHandedness::Right) {
check(f.WorldRight().Equals({1, 0, 0}));
} else { // In the test func, all cameras lock down to -Z, so left-handed cameras need to point their right towards -X then.
check(f.WorldRight().Equals({-1, 0, 0}));
}
}
});
TEST("Frustum::Chirality", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(f.WorldMatrix().Determinant() > 0.f);
check(f.ViewMatrix().Determinant() > 0.f);
if (f.Handedness() == FrustumHandedness::Left)
check(f.ProjectionMatrix().Determinant4() > 0.f); // left-handed view -> projection space transform does not change handedness.
else
check(f.ProjectionMatrix().Determinant4() < 0.f); // but right-handed transform should.
}
});
TEST("Frustum::Planes", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(f.NearPlane().Normal.Equals({0,0,1}));
check(Math::EqualAbs(f.NearPlane().distance, -1.f));
check(f.FarPlane().Normal.Equals({0,0,-1}));
check(Math::EqualAbs(f.FarPlane().distance, 100.f));
for (int i = 0; i < 9; ++i) {
Vector3 pt;
if (i == 8)
pt = f.CenterPoint();
else
pt = f.CornerPoint(i);
check(f.NearPlane().SignedDistance(pt) < 1e-3f);
check(f.FarPlane().SignedDistance(pt) < 1e-3f);
check(f.LeftPlane().SignedDistance(pt) < 1e-3f);
check(f.RightPlane().SignedDistance(pt) < 1e-3f);
check(f.TopPlane().SignedDistance(pt) < 1e-3f);
check(f.BottomPlane().SignedDistance(pt) < 1e-3f);
check(f.Contains(pt));
FrustumUnit += Test("Frustum::AspectRatio", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(EqualAbs(f.AspectRatio(), 1.f));
}
}
});
});
TEST("Frustum::Corners", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Left) {
check(f.CornerPoint(0).Equals({1.f, -1.f, -1.f}));
check(f.CornerPoint(1).Equals({100.f, -100.f, -100.f}));
check(f.CornerPoint(2).Equals({1.f, 1, -1.f}));
check(f.CornerPoint(3).Equals({100.f, 100.f, 100.f}));
check(f.CornerPoint(4).Equals({-1.f, -1.f, -1.f}));
check(f.CornerPoint(5).Equals({-100.f, -100.f, -100.f}));
check(f.CornerPoint(6).Equals({-1.f, 1.f, -1.f}));
check(f.CornerPoint(7).Equals({-100.f, 100.f, -100.f}));
} else if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Right) {
check(f.CornerPoint(0).Equals({-1.f, -1.f, -1.f}));
check(f.CornerPoint(1).Equals({-100.f, -100.f, -100.f}));
check(f.CornerPoint(2).Equals({-1.f, 1.f, -1.f}));
check(f.CornerPoint(3).Equals({-100.f, 100.f, -100.f}));
check(f.CornerPoint(4).Equals({1.f, -1.f, -1.f}));
check(f.CornerPoint(5).Equals({100.f, -100.f, -100.f}));
check(f.CornerPoint(6).Equals({1.f, 1.f, -1.f}));
check(f.CornerPoint(7).Equals({100.f, 100.f, -100.f}));
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Left) {
check(f.CornerPoint(0).Equals({50.f, -50.f, -1.f}));
check(f.CornerPoint(1).Equals({50.f, -50.f, -100.f}));
check(f.CornerPoint(2).Equals({50.f, 50.f, -1.f}));
check(f.CornerPoint(3).Equals({50.f, 50.f, -100.f}));
check(f.CornerPoint(4).Equals({-50.f, -50.f, -1.f}));
check(f.CornerPoint(5).Equals({-50.f, -50.f, -100.f}));
check(f.CornerPoint(6).Equals({-50.f, 50.f, -1.f}));
check(f.CornerPoint(7).Equals({-50.f, 50.f, -100.f}));
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Right) {
check(f.CornerPoint(0).Equals({-50.f, -50.f, -1.f}));
check(f.CornerPoint(1).Equals({-50.f, -50.f, -100.f}));
check(f.CornerPoint(2).Equals({-50.f, 50.f, -1.f}));
check(f.CornerPoint(3).Equals({-50.f, 50.f, -100.f}));
check(f.CornerPoint(4).Equals({50.f, -50.f, -1.f}));
check(f.CornerPoint(5).Equals({50.f, -50.f, -100.f}));
check(f.CornerPoint(6).Equals({50.f, 50.f, -1.f}));
check(f.CornerPoint(7).Equals({50.f, 50.f, -100.f}));
}
}
});
TEST("Frustum::ProjectUnprojectSymmetry", [&rng] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
for (int i = 0; i < 10; ++i) {
// Orient and locate the Frustum randomly
Matrix3x3 rot = Matrix3x3::RandomRotation(rng);
f.Transform(rot);
f.SetPos(f.Pos() + Vector3::RandomDir(rng, rng.Float(1.f, 100.f)));
for (int j = 0; j < 100; ++j) {
Vector2 pt = Vector2::RandomBox(rng, -1.f, 1.f);
Vector3 pos = f.NearPlanePos(pt);
Vector3 pt2 = f.Project(pos);
check(pt.Equals(pt2.XY()));
pos = f.FarPlanePos(pt);
pt2 = f.Project(pos);
check(pt.Equals(pt2.XY()));
pos = f.PointInside(pt.x, pt.y, rng.Float());
pt2 = f.Project(pos);
FrustumUnit += Test("Frustum::WorldRight", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Handedness() == FrustumHandedness::Right) {
check(f.WorldRight().Equals({1, 0, 0}));
} else { // In the test func, all cameras lock down to -Z, so left-handed cameras need to point their right towards -X then.
check(f.WorldRight().Equals({-1, 0, 0}));
}
}
}
});
});
TEST("Frustum::PlaneNormalsAreCorrect", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
auto pb = f.ToPBVolume();
Vector3 corners[8];
Plane planes[6];
f.GetCornerPoints(corners);
f.GetPlanes(planes);
FrustumUnit += Test("Frustum::Chirality", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(f.WorldMatrix().Determinant() > 0.f);
check(f.ViewMatrix().Determinant() > 0.f);
if (f.Handedness() == FrustumHandedness::Left)
check(f.ProjectionMatrix().Determinant4() > 0.f); // left-handed view -> projection space transform does not change handedness.
else
check(f.ProjectionMatrix().Determinant4() < 0.f); // but right-handed transform should.
}
});
for (int i = 0; i < 8; ++i)
check(pb.Contains(corners[i]));
FrustumUnit += Test("Frustum::Planes", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
check(f.NearPlane().Normal.Equals({0,0,1}));
check(Math::EqualAbs(f.NearPlane().distance, -1.f));
for(int i = 0; i < 6; ++i)
for(int j = 0; j < 8; ++j)
check(planes[i].SignedDistance(corners[j]) <= 0.f);
}
});
check(f.FarPlane().Normal.Equals({0,0,-1}));
check(Math::EqualAbs(f.FarPlane().distance, 100.f));
TEST("Frustum::IsConvex", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
Polyhedron p = f.ToPolyhedron();
for (int i = 0; i < 9; ++i) {
Vector3 pt;
if (i == 8)
pt = f.CenterPoint();
else
pt = f.CornerPoint(i);
check(f.NearPlane().SignedDistance(pt) < 1e-3f);
check(f.FarPlane().SignedDistance(pt) < 1e-3f);
check(f.LeftPlane().SignedDistance(pt) < 1e-3f);
check(f.RightPlane().SignedDistance(pt) < 1e-3f);
check(f.TopPlane().SignedDistance(pt) < 1e-3f);
check(f.BottomPlane().SignedDistance(pt) < 1e-3f);
check(f.Contains(pt));
}
}
});
for(int i = 0; i < 6; ++i)
FrustumUnit += Test("Frustum::Corners", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Left) {
check(f.CornerPoint(0).Equals({1.f, -1.f, -1.f}));
check(f.CornerPoint(1).Equals({100.f, -100.f, -100.f}));
check(f.CornerPoint(2).Equals({1.f, 1, -1.f}));
check(f.CornerPoint(3).Equals({100.f, 100.f, 100.f}));
check(f.CornerPoint(4).Equals({-1.f, -1.f, -1.f}));
check(f.CornerPoint(5).Equals({-100.f, -100.f, -100.f}));
check(f.CornerPoint(6).Equals({-1.f, 1.f, -1.f}));
check(f.CornerPoint(7).Equals({-100.f, 100.f, -100.f}));
} else if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Right) {
check(f.CornerPoint(0).Equals({-1.f, -1.f, -1.f}));
check(f.CornerPoint(1).Equals({-100.f, -100.f, -100.f}));
check(f.CornerPoint(2).Equals({-1.f, 1.f, -1.f}));
check(f.CornerPoint(3).Equals({-100.f, 100.f, -100.f}));
check(f.CornerPoint(4).Equals({1.f, -1.f, -1.f}));
check(f.CornerPoint(5).Equals({100.f, -100.f, -100.f}));
check(f.CornerPoint(6).Equals({1.f, 1.f, -1.f}));
check(f.CornerPoint(7).Equals({100.f, 100.f, -100.f}));
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Left) {
check(f.CornerPoint(0).Equals({50.f, -50.f, -1.f}));
check(f.CornerPoint(1).Equals({50.f, -50.f, -100.f}));
check(f.CornerPoint(2).Equals({50.f, 50.f, -1.f}));
check(f.CornerPoint(3).Equals({50.f, 50.f, -100.f}));
check(f.CornerPoint(4).Equals({-50.f, -50.f, -1.f}));
check(f.CornerPoint(5).Equals({-50.f, -50.f, -100.f}));
check(f.CornerPoint(6).Equals({-50.f, 50.f, -1.f}));
check(f.CornerPoint(7).Equals({-50.f, 50.f, -100.f}));
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Right) {
check(f.CornerPoint(0).Equals({-50.f, -50.f, -1.f}));
check(f.CornerPoint(1).Equals({-50.f, -50.f, -100.f}));
check(f.CornerPoint(2).Equals({-50.f, 50.f, -1.f}));
check(f.CornerPoint(3).Equals({-50.f, 50.f, -100.f}));
check(f.CornerPoint(4).Equals({50.f, -50.f, -1.f}));
check(f.CornerPoint(5).Equals({50.f, -50.f, -100.f}));
check(f.CornerPoint(6).Equals({50.f, 50.f, -1.f}));
check(f.CornerPoint(7).Equals({50.f, 50.f, -100.f}));
}
}
});
FrustumUnit += Test("Frustum::ProjectUnprojectSymmetry", [&rng] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
for (int i = 0; i < 10; ++i) {
// Orient and locate the Frustum randomly
Matrix3x3 rot = Matrix3x3::RandomRotation(rng);
f.Transform(rot);
f.SetPos(f.Pos() + Vector3::RandomDir(rng, rng.Float(1.f, 100.f)));
for (int j = 0; j < 100; ++j) {
Vector2 pt = Vector2::RandomBox(rng, -1.f, 1.f);
Vector3 pos = f.NearPlanePos(pt);
Vector3 pt2 = f.Project(pos);
check(pt.Equals(pt2.XY()));
pos = f.FarPlanePos(pt);
pt2 = f.Project(pos);
check(pt.Equals(pt2.XY()));
pos = f.PointInside(pt.x, pt.y, rng.Float());
pt2 = f.Project(pos);
}
}
}
});
FrustumUnit += Test("Frustum::PlaneNormalsAreCorrect", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
auto pb = f.ToPBVolume();
Vector3 corners[8];
Plane planes[6];
f.GetCornerPoints(corners);
f.GetPlanes(planes);
for (int i = 0; i < 8; ++i)
check(pb.Contains(corners[i]));
for(int i = 0; i < 6; ++i)
for(int j = 0; j < 8; ++j)
check(planes[i].SignedDistance(corners[j]) <= 0.f);
}
});
FrustumUnit += Test("Frustum::IsConvex", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
Polyhedron p = f.ToPolyhedron();
for(int i = 0; i < 6; ++i)
{
Plane p1 = f.GetPlane(i);
Plane p2 = p.FacePlane(i);
check(p1.Equals(p2));
}
check(p.EulerFormulaHolds());
check(p.IsClosed());
check(p.IsConvex());
check(!p.IsNull());
}
});
FrustumUnit += Test("Plane::ProjectToNegativeHalf", [] {
Plane p(Vector3(0,1,0), 50.f);
Vector3 neg = Vector3(0,-100.f, 0);
Vector3 pos = Vector3(0, 100.f, 0);
check(neg.Equals(p.ProjectToNegativeHalf(neg)));
check(!neg.Equals(p.ProjectToPositiveHalf(neg)));
check(pos.Equals(p.ProjectToPositiveHalf(pos)));
check(!pos.Equals(p.ProjectToNegativeHalf(pos)));
});
FrustumUnit += Test("Frustum::Contains", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
for(int i = 0; i < 8; ++i)
{
Vector3 corner = f.CornerPoint(i);
Vector3 closestPoint = f.ClosestPoint(corner);
float distance = f.Distance(corner);
if (!f.Contains(corner) || distance > 1e-4f)
//LOGE("Closest point to %s: %s", corner.ToString().c_str(), closestPoint.ToString().c_str());
check(f.Contains(corner));
check(distance < 10.f);
}
check(f.Contains(f.CenterPoint()));
}
});
FrustumUnit += Test("Frustum::ContainsCorners", [&rng] {
constexpr float SCALE = 1e2f;
Vector3 pt = Vector3::RandomBox(rng, Vector3::FromScalar(-SCALE), Vector3::FromScalar(SCALE));
Frustum b;// = RandomFrustumContainingPoint(rng, pt);
for(int i = 0; i < 9; ++i)
{
Plane p1 = f.GetPlane(i);
Plane p2 = p.FacePlane(i);
check(p1.Equals(p2));
Vector3 point = (i == 8) ? b.CenterPoint() : b.CornerPoint(i);
check(b.NearPlane().SignedDistance(point) < 1e-3f);
check(b.FarPlane().SignedDistance(point) < 1e-3f);
check(b.LeftPlane().SignedDistance(point) < 1e-3f);
check(b.RightPlane().SignedDistance(point) < 1e-3f);
check(b.TopPlane().SignedDistance(point) < 1e-3f);
check(b.BottomPlane().SignedDistance(point) < 1e-3f);
check(b.Contains(point));
}
check(p.EulerFormulaHolds());
check(p.IsClosed());
check(p.IsConvex());
check(!p.IsNull());
}
});
});
TEST("Plane::ProjectToNegativeHalf", [] {
Plane p(Vector3(0,1,0), 50.f);
FrustumUnit += Test("Frustum::Matrices", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Handedness() == FrustumHandedness::Right) {
Matrix4x4 wm = f.WorldMatrix();
check(wm.IsIdentity());
Vector3 neg = Vector3(0,-100.f, 0);
Vector3 pos = Vector3(0, 100.f, 0);
check(neg.Equals(p.ProjectToNegativeHalf(neg)));
check(!neg.Equals(p.ProjectToPositiveHalf(neg)));
Matrix4x4 vm = f.ViewMatrix();
check(vm.IsIdentity());
} else {
Matrix4x4 wm = f.WorldMatrix() * Matrix4x4::RotateY(Math::Pi);
check(wm.IsIdentity());
check(pos.Equals(p.ProjectToPositiveHalf(pos)));
check(!pos.Equals(p.ProjectToNegativeHalf(pos)));
});
TEST("Frustum::Contains", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
for(int i = 0; i < 8; ++i)
{
Vector3 corner = f.CornerPoint(i);
Vector3 closestPoint = f.ClosestPoint(corner);
float distance = f.Distance(corner);
if (!f.Contains(corner) || distance > 1e-4f)
//LOGE("Closest point to %s: %s", corner.ToString().c_str(), closestPoint.ToString().c_str());
check(f.Contains(corner));
check(distance < 10.f);
Matrix4x4 vm = f.ViewMatrix() * Matrix4x4::RotateY(Math::Pi);
check(vm.IsIdentity());
}
}
check(f.Contains(f.CenterPoint()));
}
});
});
TEST("Frustum::ContainsCorners", [&rng] {
constexpr float SCALE = 1e2f;
FrustumUnit += Test("Frustum::Projection", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
const float nearD = (f.ProjectiveSpace() == FrustumProjectiveSpace::D3D) ? 0.f : -1.f;
Vector3 pt = Vector3::RandomBox(rng, Vector3::FromScalar(-SCALE), Vector3::FromScalar(SCALE));
Frustum b;// = RandomFrustumContainingPoint(rng, pt);
for(int i = 0; i < 9; ++i)
{
Vector3 point = (i == 8) ? b.CenterPoint() : b.CornerPoint(i);
check(b.NearPlane().SignedDistance(point) < 1e-3f);
check(b.FarPlane().SignedDistance(point) < 1e-3f);
check(b.LeftPlane().SignedDistance(point) < 1e-3f);
check(b.RightPlane().SignedDistance(point) < 1e-3f);
check(b.TopPlane().SignedDistance(point) < 1e-3f);
check(b.BottomPlane().SignedDistance(point) < 1e-3f);
check(b.Contains(point));
}
});
TEST("Frustum::Matrices", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Handedness() == FrustumHandedness::Right) {
Matrix4x4 wm = f.WorldMatrix();
check(wm.IsIdentity());
Matrix4x4 vm = f.ViewMatrix();
check(vm.IsIdentity());
} else {
Matrix4x4 wm = f.WorldMatrix() * Matrix4x4::RotateY(Math::Pi);
check(wm.IsIdentity());
Matrix4x4 vm = f.ViewMatrix() * Matrix4x4::RotateY(Math::Pi);
check(vm.IsIdentity());
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
check(f.Project(f.CornerPoint(0)).Equals({-1,-1, nearD}));
check(f.Project(f.CornerPoint(1)).Equals({-1,-1, 1}));
check(f.Project(f.CornerPoint(2)).Equals({-1,1, nearD}));
check(f.Project(f.CornerPoint(3)).Equals({-1,1, 1}));
check(f.Project(f.CornerPoint(4)).Equals({1,-1, nearD}));
check(f.Project(f.CornerPoint(5)).Equals({1,-1, 1}));
check(f.Project(f.CornerPoint(6)).Equals({1,1, nearD}));
check(f.Project(f.CornerPoint(7)).Equals({1,1, 1}));
}
}
});
});
TEST("Frustum::Projection", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
const float nearD = (f.ProjectiveSpace() == FrustumProjectiveSpace::D3D) ? 0.f : -1.f;
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
check(f.Project(f.CornerPoint(0)).Equals({-1,-1, nearD}));
check(f.Project(f.CornerPoint(1)).Equals({-1,-1, 1}));
check(f.Project(f.CornerPoint(2)).Equals({-1,1, nearD}));
check(f.Project(f.CornerPoint(3)).Equals({-1,1, 1}));
check(f.Project(f.CornerPoint(4)).Equals({1,-1, nearD}));
check(f.Project(f.CornerPoint(5)).Equals({1,-1, 1}));
check(f.Project(f.CornerPoint(6)).Equals({1,1, nearD}));
check(f.Project(f.CornerPoint(7)).Equals({1,1, 1}));
}
});
TEST("Frustum::UnProject", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Type() == FrustumType::Perspective) {
Ray r = f.UnProject(0, 0);
check(r.Origin.Equals(f.Pos()));
check(r.Origin.Equals({0,0,0}));
check(r.Direction.Equals({0,0,-1}));
FrustumUnit += Test("Frustum::UnProject", [] {
Frustum f;
FOR_EACH_FRUSTUM_CONVENTION(f)
if (f.Type() == FrustumType::Perspective) {
Ray r = f.UnProject(0, 0);
check(r.Origin.Equals(f.Pos()));
check(r.Origin.Equals({0,0,0}));
check(r.Direction.Equals({0,0,-1}));
r = f.UnProject(-1, -1);
check(r.Origin.Equals(f.Pos()));
check(r.Origin.Equals({0,0,0}));
r = f.UnProject(-1, -1);
check(r.Origin.Equals(f.Pos()));
check(r.Origin.Equals({0,0,0}));
}
}
});
FrustumUnit += Test("Frustum::UnProjectFromNearPlane", []{});
FrustumUnit += Test("Frustum::UnProjectFromNearPlane", []{});
}
});
inline void Run()
{
//FrustumUnit.RunAll();
}
}
TEST("Frustum::UnProjectFromNearPlane", []{});
TEST("Frustum::UnProjectFromNearPlane", []{});
}

View File

@@ -1,58 +0,0 @@
#pragma once
#include <J3ML/Geometry/Forward.hpp>
#include <jtest/jtest.hpp>
using J3ML::Geometry::Interval;
int CommonGeometryTests() {
TEST("Geometry::Interval_Intersect", [] {
// <- a ->
// <- b ->
jtest::check(!(Interval{0, 1}.Intersects({2, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({0, 1})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 4}.Intersects({3, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 4}.Intersects({1, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({3, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{3, 5}.Intersects({2, 3})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({2, 5})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 3}.Intersects({2, 3})));
// . a
// . b
jtest::check(!(Interval{2, 2}.Intersects({2, 2})));
// <- a ->
// <- b ->
jtest::check(!(Interval{2, 5}.Intersects({3, 4})));
// <- a ->
// <- b ->
jtest::check(!(Interval{3, 4}.Intersects({2, 5})));
});
return 0;
}

View File

@@ -7,4 +7,5 @@
/// @file OBBTests.hpp
/// @desc Unit tests for the Oriented Bounding Box class.
/// @edit 2024-07-23
/// @edit 2024-07-23

View File

@@ -1,94 +1,103 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <jtest/jtest.hpp>
#include <J3ML/Geometry/Triangle.hpp>
using J3ML::Geometry::Interval;
using J3ML::Geometry::Triangle;
int TriangleTests() {
TEST("Triangle::FaceNormal", [] {
Triangle t(
{-1, -1, -1},
{0, 1, 0},
{1, -1, 1}
);
jtest::check(t.FaceNormal() == Vector3{4, 0, -4});
});
TEST("Triangle::IntersectTriangle", []
jtest::Unit TriangleUnit {"Triangle"};
namespace TriangleTests {
inline void Define()
{
Triangle xyTriangle(
{0.0f, 0.0f, 0.0f},
{1.0f, 1.0f, 0.0f},
{2.0f, 0.0f, 0.0f}
);
using namespace jtest;
using J3ML::Geometry::Interval;
using J3ML::Geometry::Triangle;
// Triangle collides with itself
jtest::check(Intersects(xyTriangle, xyTriangle));
// Translate 1 towards x -- should collide
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(1.0f, 0.0f, 0.0f))));
// Translate 2 towards x -- should collide exactly on V1
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(2.0f, 0.0f, 0.0f))));
// Translate 2 towards negative x -- should collide exactly on V0
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(-2.0f, 0.0f, 0.0f))));
// Translate 3 towards x -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(3.0f, 0.0f, 0.0f))));
// Translate 3 towards negative x -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(-3.0f, 0.0f, 0.0f))));
// Translate 1 towards z -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(0.0f, 0.0f, 1.0f))));
// Triangle collides with contained smaller triangle
jtest::check(!Intersects(xyTriangle, xyTriangle.Scaled(Vector3(0.5f, 0.5f, 0.5f)).Translated(Vector3(0.25f, 0.25f, 0.0f))));
TriangleUnit += Test("FaceNormal", [] {
Triangle t(
{-1, -1, -1},
{0, 1, 0},
{1, -1, 1}
);
Triangle zxTriangle (
{0.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 2.0f}
);
jtest::check(t.FaceNormal() == Vector3{4, 0, -4});
});
// Should collide exactly on V0
jtest::check(Intersects(xyTriangle, zxTriangle));
// Should collide across xyTriangle's edge and zxTriangle's face
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -1.0))));
// Should collide exactly on V1
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -2.0))));
// xyTriangle's face should be poked by zxTriangle's V0
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, 0.0f))));
// xyTriangle's face should be cut by zxTriangle
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, -0.5f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, 1.0f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -3.0f))));
TriangleUnit += Test("IntersectTriangle", []
{
Triangle xyTriangle(
{0.0f, 0.0f, 0.0f},
{1.0f, 1.0f, 0.0f},
{2.0f, 0.0f, 0.0f}
);
Triangle yxTriangle(
{0.0f, 0.0f, 0.0f},
{1.0f, 1.0f, 0.0f},
{0.0f, 2.0f, 0.0f}
);
// Triangle collides with itself
jtest::check(Intersects(xyTriangle, xyTriangle));
// Translate 1 towards x -- should collide
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(1.0f, 0.0f, 0.0f))));
// Translate 2 towards x -- should collide exactly on V1
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(2.0f, 0.0f, 0.0f))));
// Translate 2 towards negative x -- should collide exactly on V0
jtest::check(Intersects(xyTriangle, xyTriangle.Translated(Vector3(-2.0f, 0.0f, 0.0f))));
// Translate 3 towards x -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(3.0f, 0.0f, 0.0f))));
// Translate 3 towards negative x -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(-3.0f, 0.0f, 0.0f))));
// Translate 1 towards z -- should not collide
jtest::check(!Intersects(xyTriangle, xyTriangle.Translated(Vector3(0.0f, 0.0f, 1.0f))));
// Triangle collides with contained smaller triangle
jtest::check(!Intersects(xyTriangle, xyTriangle.Scaled(Vector3(0.5f, 0.5f, 0.5f)).Translated(Vector3(0.25f, 0.25f, 0.0f))));
// Should collide on V0-V1 edge
jtest::check(Intersects(yxTriangle, yxTriangle));
// Should not collide
jtest::check(!Intersects(xyTriangle, yxTriangle.Translated(Vector3(0.0f, 1.0f, 0.0f))));
// Should not collide
jtest::check(!Intersects(yxTriangle, yxTriangle.Translated(Vector3(0.0f, 0.0f, 1.0f))));
Triangle zxTriangle (
{0.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 2.0f}
);
Triangle zyInvertedTriangle(
{0.0f, 1.0f, -1.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 1.0f}
);
// Should collide exactly on V1
jtest::check(Intersects(xyTriangle, zyInvertedTriangle));
// Should not collide
jtest::check(!Intersects(xyTriangle, zyInvertedTriangle.Translated(Vector3(0.0f, 1.0f, 0.0f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zyInvertedTriangle.Translated(Vector3(0.25f, 0.75f, 0.0f))));
});
// Should collide exactly on V0
jtest::check(Intersects(xyTriangle, zxTriangle));
// Should collide across xyTriangle's edge and zxTriangle's face
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -1.0))));
// Should collide exactly on V1
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -2.0))));
// xyTriangle's face should be poked by zxTriangle's V0
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, 0.0f))));
// xyTriangle's face should be cut by zxTriangle
jtest::check(Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, -0.5f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zxTriangle.Translated(Vector3(1.0f, 1.0f, 1.0f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zxTriangle.Translated(Vector3(0.0f, 0.0f, -3.0f))));
Triangle yxTriangle(
{0.0f, 0.0f, 0.0f},
{1.0f, 1.0f, 0.0f},
{0.0f, 2.0f, 0.0f}
);
return 0;
// Should collide on V0-V1 edge
jtest::check(Intersects(yxTriangle, yxTriangle));
// Should not collide
jtest::check(!Intersects(xyTriangle, yxTriangle.Translated(Vector3(0.0f, 1.0f, 0.0f))));
// Should not collide
jtest::check(!Intersects(yxTriangle, yxTriangle.Translated(Vector3(0.0f, 0.0f, 1.0f))));
Triangle zyInvertedTriangle(
{0.0f, 1.0f, -1.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 1.0f}
);
// Should collide exactly on V1
jtest::check(Intersects(xyTriangle, zyInvertedTriangle));
// Should not collide
jtest::check(!Intersects(xyTriangle, zyInvertedTriangle.Translated(Vector3(0.0f, 1.0f, 0.0f))));
// Should not collide
jtest::check(!Intersects(xyTriangle, zyInvertedTriangle.Translated(Vector3(0.25f, 0.75f, 0.0f))));
});
}
inline void Run()
{
TriangleUnit.RunAll();
}
}

View File

@@ -2,6 +2,20 @@
// Created by josh on 12/26/2023.
//
int EulerAngleTests() {
return 0;
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
jtest::Unit EulerAngleUnit {"EulerAngle"};
namespace EulerAngleTests {
inline void Define() {
using namespace jtest;
EulerAngleUnit += Test("Not Implemented", [] {
throw("Not Implemented");
});
}
inline void Run() {
EulerAngleUnit.RunAll();
}
}

View File

@@ -1,8 +1,19 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <J3ML/LinearAlgebra/Matrix2x2.hpp>
using namespace J3ML::LinearAlgebra;
jtest::Unit Matrix2x2Unit {"Matrix2x2"};
namespace Matrix2x2Tests {
inline void Define() {
using namespace jtest;
using namespace J3ML::LinearAlgebra;
int Matrix2x2Tests() {
return 0;
Matrix2x2Unit += Test("Not Implemented", [] {
throw("Not Implemented");
});
}
inline void Run() {
Matrix2x2Unit.RunAll();
}
}

View File

@@ -1,96 +1,103 @@
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Matrix3x3.hpp>
#include <J3ML/LinearAlgebra/Matrix4x4.hpp>
using namespace J3ML::LinearAlgebra;
// TODO: create RNG instance
int Matrix3x3Tests() {
TEST("Mat3x3::Add_Unary", []
jtest::Unit Matrix3x3Unit {"Matrix3x3"};
namespace Matrix3x3Tests
{
inline void Define()
{
Matrix3x3 m(1,2,3, 4,5,6, 7,8,9);
Matrix3x3 m2 = +m;
jtest::check(m.Equals(m2));
});
using namespace jtest;
using namespace J3ML::LinearAlgebra;
TEST("Mat3x3::Solve_Axb", []{
RNG rng;
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
Vector3 b = Vector3::RandomBox(rng, Vector3::FromScalar(-10.f), Vector3::FromScalar(10.f));
Vector3 x;
bool success = A.SolveAxb(b, x);
jtest::check(success || mayFail);
if (success)
Matrix3x3Unit += Test("Add_Unary", []
{
Vector3 b2 = A*x;
jtest::check(b2.Equals(b, 1e-1f));
}
});
Matrix3x3 m(1,2,3, 4,5,6, 7,8,9);
Matrix3x3 m2 = +m;
jtest::check(m.Equals(m2));
});
TEST("Mat3x3::Inverse_Case", []
{
Matrix3x3 m(-8.75243664f,6.71196938f,-5.95816374f,6.81996822f,-6.85106039f,2.38949537f,-0.856015682f,3.45762491f,3.311584f);
Matrix3x3Unit += Test("Solve_Axb", []{
RNG rng;
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
bool success = m.Inverse();
jtest::check(success);
});
Vector3 b = Vector3::RandomBox(rng, Vector3::FromScalar(-10.f), Vector3::FromScalar(10.f));
TEST("Mat3x3::Inverse", []
{
RNG rng;
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
Vector3 x;
bool success = A.SolveAxb(b, x);
jtest::check(success || mayFail);
if (success)
{
Vector3 b2 = A*x;
jtest::check(b2.Equals(b, 1e-1f));
}
});
Matrix3x3 A2 = A;
bool success = A2.Inverse();
jtest::check(success || mayFail);
if (success)
Matrix3x3Unit += Test("Inverse_Case", []
{
Matrix3x3 id = A * A2;
Matrix3x3 id2 = A2 * A;
jtest::check(id.Equals(Matrix3x3::Identity, 0.3f));
jtest::check(id2.Equals(Matrix3x3::Identity, 0.3f));
}
});
Matrix3x3 m(-8.75243664f,6.71196938f,-5.95816374f,6.81996822f,-6.85106039f,2.38949537f,-0.856015682f,3.45762491f,3.311584f);
bool success = m.Inverse();
jtest::check(success);
});
TEST("Mat3x3::InverseFast", []
{
// TODO: Fix implementation of InverseFast
/*
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
Matrix3x3 A2 = A;
bool success = A2.InverseFast();
assert(success || mayFail);
if (success)
Matrix3x3Unit += Test("Inverse", []
{
Matrix3x3 id = A * A2;
Matrix3x3 id2 = A2 * A;
assert(id.Equals(Matrix3x3::Identity, 0.3f));
assert(id2.Equals(Matrix3x3::Identity, 0.3f));
}
*/
});
RNG rng;
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
TEST("Mat3x3::MulMat4x4", []
Matrix3x3 A2 = A;
bool success = A2.Inverse();
jtest::check(success || mayFail);
if (success)
{
Matrix3x3 id = A * A2;
Matrix3x3 id2 = A2 * A;
jtest::check(id.Equals(Matrix3x3::Identity, 0.3f));
jtest::check(id2.Equals(Matrix3x3::Identity, 0.3f));
}
});
Matrix3x3Unit += Test("InverseFast", []
{
// TODO: Fix implementation of InverseFast
/*
Matrix3x3 A = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
Matrix3x3 A2 = A;
bool success = A2.InverseFast();
assert(success || mayFail);
if (success)
{
Matrix3x3 id = A * A2;
Matrix3x3 id2 = A2 * A;
assert(id.Equals(Matrix3x3::Identity, 0.3f));
assert(id2.Equals(Matrix3x3::Identity, 0.3f));
}
*/
});
Matrix3x3Unit += Test("MulMat4x4", []
{
RNG rng;
Matrix3x3 m = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
Matrix4x4 m_ = m;
Matrix4x4 m2 = Matrix4x4::RandomGeneral(rng, -10.f, 10.f);
Matrix4x4 test = m * m2;
Matrix4x4 correct = m_ * m2;
jtest::check(test.Equals(correct));
});
}
inline void Run()
{
RNG rng;
Matrix3x3 m = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
Matrix4x4 m_ = m;
Matrix4x4 m2 = Matrix4x4::RandomGeneral(rng, -10.f, 10.f);
Matrix4x4 test = m * m2;
Matrix4x4 correct = m_ * m2;
jtest::check(test.Equals(correct));
});
return 0;
Matrix3x3Unit.RunAll();
}
}

View File

@@ -1,129 +1,135 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Matrix4x4.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/Math.hpp>
using namespace J3ML::LinearAlgebra;
jtest::Unit Matrix4x4Unit {"Matrix4x4"};
namespace Matrix4x4Tests {
inline void Define() {
using namespace jtest;
using namespace J3ML::LinearAlgebra;
using namespace J3ML::Math;
void Matrix4x4_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);
}
int Matrix4x4Tests() {
TEST("Mat4x4::Add_Unary", []
{
Matrix4x4 m(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2 = +m;
jtest::check(m.Equals(m2));
});
TEST("Mat4x4::Inverse", [] {
RNG rng;
Matrix4x4 A = Matrix4x4::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
Matrix4x4 A2 = A;
bool success = A2.Inverse();
jtest::check(success || mayFail);
if (success)
Matrix4x4Unit += Test("Add_Unary", []
{
Matrix4x4 id = A * A2;
Matrix4x4 id2 = A2 * A;
Matrix4x4 m(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2 = +m;
jtest::check(m.Equals(m2));
});
jtest::check(id.Equals(Matrix4x4::Identity, 0.3f));
jtest::check(id2.Equals(Matrix4x4::Identity, 0.3f));
}
});
Matrix4x4Unit += Test("Inverse", [] {
RNG rng;
Matrix4x4 A = Matrix4x4::RandomGeneral(rng, -10.f, 10.f);
bool mayFail = Math::EqualAbs(A.Determinant(), 0.f, 1e-2f);
TEST("Mat4x4::Ctor", []{
RNG rng;
Matrix3x3 m = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
Matrix4x4 A2 = A;
bool success = A2.Inverse();
Matrix4x4 m2(m);
jtest::check(success || mayFail);
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x)
assert(Math::EqualAbs(m.At(y, x), m2.At(y, x)));
if (success)
{
Matrix4x4 id = A * A2;
Matrix4x4 id2 = A2 * A;
jtest::check(Math::EqualAbs(m2[0][3], 0.f));
jtest::check(Math::EqualAbs(m2[1][3], 0.f));
jtest::check(Math::EqualAbs(m2[2][3], 0.f));
jtest::check(id.Equals(Matrix4x4::Identity, 0.3f));
jtest::check(id2.Equals(Matrix4x4::Identity, 0.3f));
}
});
jtest::check(Math::EqualAbs(m2[3][0], 0.f));
jtest::check(Math::EqualAbs(m2[3][1], 0.f));
jtest::check(Math::EqualAbs(m2[3][2], 0.f));
jtest::check(Math::EqualAbs(m2[3][3], 0.f));
});
Matrix4x4Unit += Test("Ctor", []{
RNG rng;
Matrix3x3 m = Matrix3x3::RandomGeneral(rng, -10.f, 10.f);
TEST("Mat4x4::SetRow", []
{
Matrix4x4 m;
m.SetRow(0, 1,2,3,4);
m.SetRow(1, Vector4(5,6,7,8));
m.SetRow(2, 9,10,11,12);
m.SetRow(3, 13, 14, 15, 16);
Matrix4x4 m2(m);
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x)
assert(Math::EqualAbs(m.At(y, x), m2.At(y, x)));
jtest::check(Math::EqualAbs(m2[0][3], 0.f));
jtest::check(Math::EqualAbs(m2[1][3], 0.f));
jtest::check(Math::EqualAbs(m2[2][3], 0.f));
jtest::check(Math::EqualAbs(m2[3][0], 0.f));
jtest::check(Math::EqualAbs(m2[3][1], 0.f));
jtest::check(Math::EqualAbs(m2[3][2], 0.f));
jtest::check(Math::EqualAbs(m2[3][3], 0.f));
});
Matrix4x4Unit += Test("SetRow", []
{
Matrix4x4 m;
m.SetRow(0, 1,2,3,4);
m.SetRow(1, Vector4(5,6,7,8));
m.SetRow(2, 9,10,11,12);
m.SetRow(3, 13, 14, 15, 16);
Matrix4x4 m3(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2;
m2.Set(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
jtest::check(m.Equals(m2));
jtest::check(m.Equals(m3));
});
Matrix4x4 m3(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2;
m2.Set(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
jtest::check(m.Equals(m2));
jtest::check(m.Equals(m3));
});
TEST("Mat4x4::SwapRows", []
{
Matrix4x4 m(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2(13,14,15,16, 9,10,11,12, 5,6,7,8, 1,2,3,4);
m.SwapRows(0,3);
m.SwapRows(1,2);
jtest::check(m.Equals(m2));
});
Matrix4x4Unit += Test("SwapRows", []
{
Matrix4x4 m(1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16);
Matrix4x4 m2(13,14,15,16, 9,10,11,12, 5,6,7,8, 1,2,3,4);
m.SwapRows(0,3);
m.SwapRows(1,2);
jtest::check(m.Equals(m2));
});
TEST("Mat4x4::CtorCols", []
{
Matrix4x4 m(Vector4(1,2,3,4), Vector4(5,6,7,8), Vector4(9,10,11,12), Vector4(13,14,15,16));
Matrix4x4 m2(1,5,9,13, 2,6,10,14, 3,7,11,15, 4,8,12,16);
jtest::check(m.Equals(m2));
});
Matrix4x4Unit += Test("CtorCols", []
{
Matrix4x4 m(Vector4(1,2,3,4), Vector4(5,6,7,8), Vector4(9,10,11,12), Vector4(13,14,15,16));
Matrix4x4 m2(1,5,9,13, 2,6,10,14, 3,7,11,15, 4,8,12,16);
jtest::check(m.Equals(m2));
});
TEST("Mat4x4::CtorFromQuat", []
{
RNG rng;
// TODO: Multiple random passes
Quaternion q = Quaternion::RandomRotation(rng);
Matrix4x4 m(q);
Matrix4x4Unit += Test("CtorFromQuat", []
{
RNG rng;
// TODO: Multiple random passes
Quaternion q = Quaternion::RandomRotation(rng);
Matrix4x4 m(q);
Vector3 v = Vector3(-1, 5, 20.f);
Vector3 v1 = q * v;
Vector3 v2 = m.Transform(v); //m.TransformPos(v);
jtest::check(v1.Equals(v2));
});
Vector3 v = Vector3(-1, 5, 20.f);
Vector3 v1 = q * v;
Vector3 v2 = m.Transform(v); //m.TransformPos(v);
jtest::check(v1.Equals(v2));
});
TEST("Mat4x4::CtorFromQuatTrans", [] {});
TEST("Mat4x4::Translate", [] {});
TEST("Mat4x4::Scale", [] {});
TEST("Mat4x4::InverseOrthogonalUniformScale", [] {});
TEST("Mat4x4::InverseOrthonormal", [] {});
TEST("Mat4x4::DeterminantCorrectness", [] { });
TEST("Mat4x4::MulMat3x3", [] {});
Matrix4x4Unit += Test("CtorFromQuatTrans", [] {});
Matrix4x4Unit += Test("Translate", [] {});
Matrix4x4Unit += Test("Scale", [] {});
Matrix4x4Unit += Test("InverseOrthogonalUniformScale", [] {});
Matrix4x4Unit += Test("InverseOrthonormal", [] {});
Matrix4x4Unit += Test("DeterminantCorrectness", [] { });
Matrix4x4Unit += Test("MulMat3x3", [] {});
TEST("Mat4x4::AngleTypeRoundtripConversion", Matrix4x4_AngleTypeRoundtripConversion);
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);
});
return 0;
}
inline void Run() {
Matrix4x4Unit.RunAll();
}
}
@@ -143,3 +149,4 @@ int Matrix4x4Tests() {

View File

@@ -1,77 +1,87 @@
#pragma once
#include <cmath>
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Quaternion.hpp>
#include <J3ML/Algorithm/RNG.hpp>
using namespace J3ML::LinearAlgebra;
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;
double sign = 1.0;
if (angle > 0) {
angle = -angle;
sign = -1.0;
}
double A;
double B;
if (angle <= 1.0) // perform spherical linear interpolation
jtest::Unit QuaternionUnit {"Quaternion"};
namespace QuaternionTests {
Quaternion PreciseSlerp(const Quaternion &a, const Quaternion& b, float t)
{
angle = std::acos(angle); // After this, angle is in the range pi / 2 -> 0 as the original angle variable ranged from 0 -> 1.
double angle = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
double angleT = t * angle;
double sign = 1.0;
double s[3] = { std::sin(angle), std::sin(angle - angleT), std::sin(angleT)};
double c = 1.0 / s[0];
if (angle > 0) {
angle = -angle;
sign = -1.0;
}
double A;
double B;
A = s[1] * c;
B = s[2] * c;
} else { // If angle is close to taking the denominator to zero, resort to linear interpolation (and normalization).
A = 1.0 - t;
B = t;
if (angle <= 1.0) // perform spherical linear interpolation
{
angle = std::acos(angle); // After this, angle is in the range pi / 2 -> 0 as the original angle variable ranged from 0 -> 1.
double angleT = t * angle;
double s[3] = { std::sin(angle), std::sin(angle - angleT), std::sin(angleT)};
double c = 1.0 / s[0];
A = s[1] * c;
B = s[2] * c;
} else { // If angle is close to taking the denominator to zero, resort to linear interpolation (and normalization).
A = 1.0 - t;
B = t;
}
Quaternion C;
C.x = (float)(a.x*A*sign + b.x*B);
C.y = (float)(a.y*A*sign + b.y*B);
C.z = (float)(a.z*A*sign + b.z*B);
C.w = (float)(a.w*A*sign + b.w*B);
return C.Normalized();
}
Quaternion C;
C.x = (float)(a.x*A*sign + b.x*B);
C.y = (float)(a.y*A*sign + b.y*B);
C.z = (float)(a.z*A*sign + b.z*B);
C.w = (float)(a.w*A*sign + b.w*B);
return C.Normalized();
}
inline void Define()
{
using namespace jtest;
using namespace J3ML::LinearAlgebra;
using J3ML::Algorithm::RNG;
QuaternionUnit += Test("SlerpPrecision", [] {
RNG rng;
float maxError = 0;
float maxLerpError = 0;
float magnitudeError = 0;
for (int i = 0; i < 10000; ++i)
{
Quaternion q = Quaternion::RandomRotation(rng);
Quaternion q2 = Quaternion::RandomRotation(rng);
float t = rng.Float01Incl();
int QuaternionTests()
{
Quaternion correct = PreciseSlerp(q, q2, t);
Quaternion fast = q.Slerp(q2, t);
TEST("Quat::SlerpPrecision", [] {
RNG rng;
float maxError = 0;
float maxLerpError = 0;
float magnitudeError = 0;
for (int i = 0; i < 10000; ++i)
{
Quaternion q = Quaternion::RandomRotation(rng);
Quaternion q2 = Quaternion::RandomRotation(rng);
float t = rng.Float01Incl();
magnitudeError = std::max(magnitudeError, std::abs(1.f - fast.LengthSquared()));
Quaternion lerp = q.Lerp(q2, t);
}
});
QuaternionUnit += Test("Mat4x4Conversion", [] { throw("Not Implemented"); });
QuaternionUnit += Test("MulOpQuat", [] { throw("Not Implemented"); });
QuaternionUnit += Test("DivOpQuat", [] { throw("Not Implemented"); });
QuaternionUnit += Test("Lerp", [] { throw("Not Implemented"); });
QuaternionUnit += Test("RotateFromTo", [] { throw("Not Implemented"); });
QuaternionUnit += Test("Transform", [] { throw("Not Implemented"); });
Quaternion correct = PreciseSlerp(q, q2, t);
Quaternion fast = q.Slerp(q2, t);
}
magnitudeError = std::max(magnitudeError, std::abs(1.f - fast.LengthSquared()));
Quaternion lerp = q.Lerp(q2, t);
}
});
TEST("Quat::Mat4x4Conversion", [] {});
TEST("Quat::MulOpQuat", [] {});
TEST("Quat::DivOpQuat", [] {});
TEST("Quat::Lerp", [] {});
TEST("Quat::RotateFromTo", [] {});
TEST("Quat::Transform", [] {});
return 0;
inline void Run()
{
QuaternionUnit.RunAll();
}
}

View File

@@ -1,191 +1,201 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Vector2.hpp>
using J3ML::LinearAlgebra::Vector2;
int Vector2Tests()
jtest::Unit Vector2Unit {"Vector2"};
namespace Vector2Tests
{
TEST("Vector2::Ctor_Default", []
inline void Define()
{
// TODO: implement check_eq
jtest::check(Vector2() == Vector2::Zero);
});
using namespace jtest;
using J3ML::LinearAlgebra::Vector2;
TEST("Vector2::Ctor_XY", []
Vector2Unit += Test("Ctor_Default", []
{
// TODO: implement check_eq
jtest::check(Vector2() == Vector2::Zero);
});
Vector2Unit += Test("Ctor_XY", []
{
Vector2 vec (1, 0);
// TODO: implement check_eq
jtest::check(vec == Vector2::Right);
});
Vector2Unit += Test("Addition_Op", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (3, 3);
// TODO: implement check_eq
jtest::check(A+B == C);
});
Vector2Unit += Test("Addition_Method", []
{
Vector2 A (2,2);
Vector2 B (2,2);
Vector2 C (4, 4);
// TODO: implement check_eq
jtest::check(A.Add(B) == C);
});
Vector2Unit += Test("Addition_Static", []
{
Vector2 A (3, 3);
Vector2 B (2, 2);
Vector2 C (5, 5);
// TODO: implement check_eq
jtest::check(Vector2::Add(A, B) == C);
});
Vector2Unit += Test("Subtract_Op", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(A-B == C);
});
Vector2Unit += Test("Subtract_Method", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(A.Sub(B) == C);
});
Vector2Unit += Test("Subtract_Static", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(Vector2::Sub(A, B) == C);
});
Vector2Unit += Test("Scalar_Multiplication", []
{
Vector2 A (5, 1);
float B = 0.5f;
Vector2 C (2.5f, .5f);
jtest::check(A*B == C);
});
Vector2Unit += Test("Size", []
{
jtest::check(sizeof(Vector2) == 8);
});
Vector2Unit += Test("NaN", []
{
jtest::check(Vector2::Zero != Vector2::NaN);
jtest::check(Vector2::Up != Vector2::NaN);
jtest::check(Vector2::Left != Vector2::NaN);
jtest::check(Vector2::Down != Vector2::NaN);
jtest::check(Vector2::Right != Vector2::NaN);
jtest::check(Vector2::NaN != Vector2::NaN);
});
Vector2Unit += Test("MarginOfError", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check(A.IsWithinMarginOfError(B, 0.5f));
});
Vector2Unit += Test("Min", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check( Vector2::Min(A, B) == B);
});
Vector2Unit += Test("Max", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check( Vector2::Max(A, B) == A);
});
Vector2Unit += Test("Clamp", []
{
Vector2 Input (0, 20);
Vector2 Minimum ( 2, 2);
Vector2 Maximum (16, 16);
Vector2 ExpectedResult (2, 16);
jtest::check(Input.Clamp(Minimum, Maximum) == ExpectedResult);
});
Vector2Unit += Test("DotProduct", []
{
Vector2 A (2, 2);
Vector2 B (1, 1);
jtest::check(A.Dot(B) == 4.f);
});
Vector2Unit += Test("Project", []
{
Vector2 Base (4, 4);
Vector2 Projected (1, 0);
Vector2 ExpectedResult (4, 0);
jtest::check(Base.Project(Projected) == ExpectedResult);
});
Vector2Unit += Test("Normalized", []
{
Vector2 A(2, 0);
Vector2 B(1, 0);
jtest::check(A.Normalized() == B);
});
Vector2Unit += Test("Lerp", []
{
Vector2 A (2,2);
Vector2 B (10, 10);
Vector2 C (6, 6);
jtest::check(A.Lerp(B, 0.f) == A);
jtest::check(A.Lerp(B, 1.f) == B);
jtest::check(A.Lerp(B, 0.5f) == C);
});
Vector2Unit += Test("AngleBetween", []
{
Vector2 A (0.5f, 0.5);
Vector2 B (0.5f, 0.1f);
A = A.Normalized();
B = B.Normalized();
jtest::check_float_eq(A.AngleBetween(B), 0.58800244);
});
}
inline void Run()
{
Vector2 vec (1, 0);
// TODO: implement check_eq
jtest::check(vec == Vector2::Right);
});
TEST("Vector2::Addition_Op", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (3, 3);
// TODO: implement check_eq
jtest::check(A+B == C);
});
TEST("Vector2::Addition_Method", []
{
Vector2 A (2,2);
Vector2 B (2,2);
Vector2 C (4, 4);
// TODO: implement check_eq
jtest::check(A.Add(B) == C);
});
TEST("Vector2::Addition_Static", []
{
Vector2 A (3, 3);
Vector2 B (2, 2);
Vector2 C (5, 5);
// TODO: implement check_eq
jtest::check(Vector2::Add(A, B) == C);
});
TEST("Vector2::Subtract_Op", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(A-B == C);
});
TEST("Vector2::Subtract_Method", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(A.Sub(B) == C);
});
TEST("Vector2::Subtract_Static", []
{
Vector2 A (1,1);
Vector2 B (2,2);
Vector2 C (-1, -1);
jtest::check(Vector2::Sub(A, B) == C);
});
TEST("Vector2::Scalar_Multiplication", []
{
Vector2 A (5, 1);
float B = 0.5f;
Vector2 C (2.5f, .5f);
jtest::check(A*B == C);
});
TEST("Vector2::Size", []
{
jtest::check(sizeof(Vector2) == 8);
});
TEST("Vector2::NaN", []
{
jtest::check(Vector2::Zero != Vector2::NaN);
jtest::check(Vector2::Up != Vector2::NaN);
jtest::check(Vector2::Left != Vector2::NaN);
jtest::check(Vector2::Down != Vector2::NaN);
jtest::check(Vector2::Right != Vector2::NaN);
jtest::check(Vector2::NaN != Vector2::NaN);
});
TEST("Vector2::MarginOfError", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check(A.IsWithinMarginOfError(B, 0.5f));
});
TEST("Vector2::Min", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check( Vector2::Min(A, B) == B);
});
TEST("Vector2::Max", []
{
Vector2 A (2,2);
Vector2 B (1.85, 1.85);
jtest::check( Vector2::Max(A, B) == A);
});
TEST("Vector2::Clamp", []
{
Vector2 Input (0, 20);
Vector2 Minimum ( 2, 2);
Vector2 Maximum (16, 16);
Vector2 ExpectedResult (2, 16);
jtest::check(Input.Clamp(Minimum, Maximum) == ExpectedResult);
});
TEST("Vector2::DotProduct", []
{
Vector2 A (2, 2);
Vector2 B (1, 1);
jtest::check(A.Dot(B) == 4.f);
});
TEST("Vector2::Project", []
{
Vector2 Base (4, 4);
Vector2 Projected (1, 0);
Vector2 ExpectedResult (4, 0);
jtest::check(Base.Project(Projected) == ExpectedResult);
});
TEST("Vector2::Normalized", []
{
Vector2 A(2, 0);
Vector2 B(1, 0);
jtest::check(A.Normalized() == B);
});
TEST("Vector2::Lerp", []
{
Vector2 A (2,2);
Vector2 B (10, 10);
Vector2 C (6, 6);
jtest::check(A.Lerp(B, 0.f) == A);
jtest::check(A.Lerp(B, 1.f) == B);
jtest::check(A.Lerp(B, 0.5f) == C);
});
TEST("Vector2::AngleBetween", []
{
Vector2 A (0.5f, 0.5);
Vector2 B (0.5f, 0.1f);
A = A.Normalized();
B = B.Normalized();
// TODO: AngleBetween returns not a number
// TODO: implement jtest::check_float_eq
jtest::check(A.AngleBetween(B) == 0.58800244);
});
return 0;
Vector2Unit.RunAll();
}
}

View File

@@ -1,206 +1,215 @@
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
#include <J3ML/LinearAlgebra/Vector3.hpp>
using J3ML::LinearAlgebra::Vector3;
inline void EXPECT_V3_EQ(const Vector3& lhs, const Vector3& rhs)
{
jtest::check(lhs.x == rhs.x);
jtest::check(lhs.y == rhs.y);
jtest::check(lhs.z == rhs.z);
}
int Vector3Tests() {
TEST("Vector3::Ctor_Default", []
jtest::Unit Vector3Unit {"Vector3"};
namespace Vector3Tests {
inline void EXPECT_V3_EQ(const Vector3& lhs, const Vector3& rhs)
{
EXPECT_V3_EQ(Vector3(), Vector3::Zero);
});
jtest::check(lhs.x == rhs.x);
jtest::check(lhs.y == rhs.y);
jtest::check(lhs.z == rhs.z);
}
TEST("Vector3::Ctor_XYZ", []
inline void Define() {
using namespace jtest;
using J3ML::LinearAlgebra::Vector3;
Vector3Unit += Test("Ctor_Default", []
{
EXPECT_V3_EQ(Vector3(), Vector3::Zero);
});
Vector3Unit += Test("Ctor_XYZ", []
{
Vector3 Input (0, 1, 0);
EXPECT_V3_EQ(Input, Vector3::Down);
});
Vector3Unit += Test("Addition_Op", [] {
Vector3 A (1,1,1);
Vector3 B (2,2,2);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(A + B, ExpectedResult);
});
Vector3Unit += Test("Addition_Method", [] {
Vector3 A (1,1,1);
Vector3 B (2,2,2);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(A.Add(B), ExpectedResult);
});
Vector3Unit += Test("Addition_Static", [] {
Vector3 A (1,1,1);
Vector3 B (3,3,3);
Vector3 ExpectedResult (4,4,4);
EXPECT_V3_EQ(Vector3::Add(A, B), ExpectedResult);
});
Vector3Unit += Test("Subtract_Op", [] {
Vector3 A (2,2,2);
Vector3 B (.5f, .5f, .5f);
Vector3 ExpectedResult (1.5f, 1.5f, 1.5f);
EXPECT_V3_EQ(A - B, ExpectedResult);
});
Vector3Unit += Test("Subtract_Method", [] {
Vector3 A (3,3,3);
Vector3 B (1,1,1);
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(A.Sub(B), ExpectedResult);
});
Vector3Unit += Test("V3_Subtract_Static", [] {
Vector3 A (4,4,4);
Vector3 B (1,1,1);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(Vector3::Sub(A, B), ExpectedResult);
});
Vector3Unit += Test("Scalar_Mult_Op", [] {
Vector3 A ( 1,1,1);
float B = 1.5f;
Vector3 ExpectedResult (1.5f, 1.5f, 1.5f);
EXPECT_V3_EQ(A * B, ExpectedResult);
});
Vector3Unit += Test("Scalar_Mult_Method", [] {
Vector3 A (3,3,3);
float B = 1.5f;
Vector3 ExpectedResult (4.5f, 4.5f, 4.5f);
EXPECT_V3_EQ(A.Mul(B), ExpectedResult);
});
Vector3Unit += Test("Scalar_Mult_Static", [] {
Vector3 A (2,2,2);
float B = 1.5f;
Vector3 ExpectedResult (3.f, 3.f, 3.f);
EXPECT_V3_EQ(Vector3::Mul(A, B), ExpectedResult);
});
Vector3Unit += Test("Scalar_Div_Op", [] {
Vector3 A (4,4,4);
float B = 2.f;
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(A / B, ExpectedResult);
});
Vector3Unit += Test("Scalar_Div_Method", [] {
Vector3 A (6,6,6);
float B = 2.f;
Vector3 ExpectedResult ( 3,3,3);
EXPECT_V3_EQ(A.Div(B), ExpectedResult);
});
Vector3Unit += Test("Scalar_Div_Static", [] {
Vector3 A (3,3,3);
float B = 1.5f;
Vector3 ExpectedResult ( 2.f, 2.f, 2.f);
EXPECT_V3_EQ(Vector3::Div(A, B), ExpectedResult);
});
Vector3Unit += Test("Sizeof", [] {
jtest::check(sizeof(Vector3) == 12);
});
Vector3Unit += Test("NaN", [] {
jtest::check(Vector3(0, 0, 0) != Vector3::NaN);
});
Vector3Unit += Test("Min", [] {
Vector3 Input (2,2,2);
Vector3 Minimum (3,3,3);
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(Input.Min(Minimum), ExpectedResult);
});
Vector3Unit += Test("Max", [] {
Vector3 Input (2,2,2);
Vector3 Maximum (3,3,3);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(Input.Max(Maximum), ExpectedResult);
});
Vector3Unit += Test("Clamp", [] {
Vector3 Input (5,-1,8);
Vector3 Minimum (1,1,1);
Vector3 Maximum (5,5,5);
Vector3 ExpectedResult (5,1,5);
EXPECT_V3_EQ(Input.Clamp(Minimum, Maximum), ExpectedResult);
});
Vector3Unit += Test("DotProduct", [] {
Vector3 A(6,6,6);
Vector3 B(1,1,1);
float ExpectedResult = 18;
// TODO: Add check_float_eq
jtest::check(A.Dot(B) == ExpectedResult);
});
Vector3Unit += Test("CrossProduct", [] {
Vector3 A(1,1,1);
Vector3 B(2,2,2);
Vector3 ExpectedResult (0,0,0);
EXPECT_V3_EQ(A.Cross(B), ExpectedResult);
});
Vector3Unit += Test("Project", [] {
Vector3 Base;
Vector3 Projection;
Vector3 ExpectedResult;
});
Vector3Unit += Test("Normalized", [] {
Vector3 Input (2, 0, 0);
Vector3 ExpectedResult (1, 0, 0);
EXPECT_V3_EQ(Input.Normalized(), ExpectedResult);
});
Vector3Unit += Test("Lerp", []
{
Vector3 Start;
Vector3 Finish;
float Percent = 50;
Vector3 ExpectedResult;
EXPECT_V3_EQ(Start.Lerp(Finish, Percent), ExpectedResult);
});
Vector3Unit += Test("AngleBetween", [] {
using J3ML::LinearAlgebra::Angle2D;
Vector3 A ( .5f, .5f, .5f);
Vector3 B (.25f, .75f, .25f);
A = A.Normalized();
B = B.Normalized();
Angle2D ExpectedResult (-0.69791365, -2.3561945);
//std::cout << A.AngleBetween(B).x << ", " << A.AngleBetween(B).y << "";
auto angle = A.AngleBetween(B);
jtest::check(angle.x == ExpectedResult.x);
jtest::check(angle.y == ExpectedResult.y);
});
}
inline void Run()
{
Vector3 Input (0, 1, 0);
EXPECT_V3_EQ(Input, Vector3::Down);
});
TEST("Vector3::Addition_Op", [] {
Vector3 A (1,1,1);
Vector3 B (2,2,2);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(A + B, ExpectedResult);
});
TEST("Vector3::Addition_Method", [] {
Vector3 A (1,1,1);
Vector3 B (2,2,2);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(A.Add(B), ExpectedResult);
});
TEST("Vector3::Addition_Static", [] {
Vector3 A (1,1,1);
Vector3 B (3,3,3);
Vector3 ExpectedResult (4,4,4);
EXPECT_V3_EQ(Vector3::Add(A, B), ExpectedResult);
});
TEST("Vector3::Subtract_Op", [] {
Vector3 A (2,2,2);
Vector3 B (.5f, .5f, .5f);
Vector3 ExpectedResult (1.5f, 1.5f, 1.5f);
EXPECT_V3_EQ(A - B, ExpectedResult);
});
TEST("Vector3::Subtract_Method", [] {
Vector3 A (3,3,3);
Vector3 B (1,1,1);
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(A.Sub(B), ExpectedResult);
});
TEST("Vector3Test, V3_Subtract_Static", [] {
Vector3 A (4,4,4);
Vector3 B (1,1,1);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(Vector3::Sub(A, B), ExpectedResult);
});
TEST("Vector3::Scalar_Mult_Op", [] {
Vector3 A ( 1,1,1);
float B = 1.5f;
Vector3 ExpectedResult (1.5f, 1.5f, 1.5f);
EXPECT_V3_EQ(A * B, ExpectedResult);
});
TEST("Vector3::Scalar_Mult_Method", [] {
Vector3 A (3,3,3);
float B = 1.5f;
Vector3 ExpectedResult (4.5f, 4.5f, 4.5f);
EXPECT_V3_EQ(A.Mul(B), ExpectedResult);
});
TEST("Vector3::Scalar_Mult_Static", [] {
Vector3 A (2,2,2);
float B = 1.5f;
Vector3 ExpectedResult (3.f, 3.f, 3.f);
EXPECT_V3_EQ(Vector3::Mul(A, B), ExpectedResult);
});
TEST("Vector3::Scalar_Div_Op", [] {
Vector3 A (4,4,4);
float B = 2.f;
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(A / B, ExpectedResult);
});
TEST("Vector3::Scalar_Div_Method", [] {
Vector3 A (6,6,6);
float B = 2.f;
Vector3 ExpectedResult ( 3,3,3);
EXPECT_V3_EQ(A.Div(B), ExpectedResult);
});
TEST("Vector3::Scalar_Div_Static", [] {
Vector3 A (3,3,3);
float B = 1.5f;
Vector3 ExpectedResult ( 2.f, 2.f, 2.f);
EXPECT_V3_EQ(Vector3::Div(A, B), ExpectedResult);
});
TEST("Vector3::Sizeof", [] {
jtest::check(sizeof(Vector3) == 12);
});
TEST("Vector3::NaN", [] {
jtest::check(Vector3(0, 0, 0) != Vector3::NaN);
});
TEST("Vector3::Min", [] {
Vector3 Input (2,2,2);
Vector3 Minimum (3,3,3);
Vector3 ExpectedResult (2,2,2);
EXPECT_V3_EQ(Input.Min(Minimum), ExpectedResult);
});
TEST("Vector3::Max", [] {
Vector3 Input (2,2,2);
Vector3 Maximum (3,3,3);
Vector3 ExpectedResult (3,3,3);
EXPECT_V3_EQ(Input.Max(Maximum), ExpectedResult);
});
TEST("Vector3::Clamp", [] {
Vector3 Input (5,-1,8);
Vector3 Minimum (1,1,1);
Vector3 Maximum (5,5,5);
Vector3 ExpectedResult (5,1,5);
EXPECT_V3_EQ(Input.Clamp(Minimum, Maximum), ExpectedResult);
});
TEST("Vector3::DotProduct", [] {
Vector3 A(6,6,6);
Vector3 B(1,1,1);
float ExpectedResult = 18;
// TODO: Add check_float_eq
jtest::check(A.Dot(B) == ExpectedResult);
});
TEST("Vector3::CrossProduct", [] {
Vector3 A(1,1,1);
Vector3 B(2,2,2);
Vector3 ExpectedResult (0,0,0);
EXPECT_V3_EQ(A.Cross(B), ExpectedResult);
});
TEST("Vector3::Project", [] {
Vector3 Base;
Vector3 Projection;
Vector3 ExpectedResult;
});
TEST("Vector3::Normalized", [] {
Vector3 Input (2, 0, 0);
Vector3 ExpectedResult (1, 0, 0);
EXPECT_V3_EQ(Input.Normalized(), ExpectedResult);
});
TEST("Vector3::Lerp", []
{
Vector3 Start;
Vector3 Finish;
float Percent = 50;
Vector3 ExpectedResult;
EXPECT_V3_EQ(Start.Lerp(Finish, Percent), ExpectedResult);
});
TEST("Vector3::AngleBetween", [] {
using J3ML::LinearAlgebra::Angle2D;
Vector3 A ( .5f, .5f, .5f);
Vector3 B (.25f, .75f, .25f);
A = A.Normalized();
B = B.Normalized();
Angle2D ExpectedResult (-0.69791365, -2.3561945);
//std::cout << A.AngleBetween(B).x << ", " << A.AngleBetween(B).y << "";
auto angle = A.AngleBetween(B);
jtest::check(angle.x == ExpectedResult.x);
jtest::check(angle.y == ExpectedResult.y);
});
return 0;
Vector3Unit.RunAll();
}
}

View File

@@ -1,41 +1,55 @@
#include <J3ML/LinearAlgebra/Vector4.hpp>
#include <jtest/jtest.hpp>
#include <jtest/Unit.hpp>
using Vector4 = J3ML::LinearAlgebra::Vector4;
void EXPECT_V4_EQ(const Vector4& lhs, const Vector4& rhs)
namespace jtest
{
jtest::check(lhs.x == rhs.x);
jtest::check(lhs.y == rhs.y);
jtest::check(lhs.z == rhs.z);
jtest::check(lhs.w == rhs.w);
void check_v4_eq(const Vector4& lhs, const Vector4& rhs)
{
check_float_eq(lhs.x, rhs.x);
check_float_eq(lhs.y, rhs.y);
check_float_eq(lhs.z, rhs.z);
check_float_eq(lhs.w, rhs.w);
}
}
int Vector4Tests()
jtest::Unit Vector4Unit {"Vector4"};
namespace Vector4Tests
{
TEST("Vector4::Ctor_Default", []
inline void Define()
{
EXPECT_V4_EQ(Vector4(), Vector4::Zero);
});
using namespace jtest;
TEST("Vector4::Constructor_XYZW", []
Vector4Unit += Test("Vector4()", []{
jtest::check_v4_eq(Vector4(), Vector4::Zero);
});
Vector4Unit += Test("Vector4(float x, float y, float z, float) w", []{
Vector4 Input (0, 1, 0, 1);
jtest::check_float_eq(Input.x, 0);
jtest::check_float_eq(Input.y, 1);
jtest::check_float_eq(Input.z, 0);
jtest::check_float_eq(Input.w, 1);
});
Vector4Unit += Test("Vector4::Addition_Op", [] {
Vector4 A (1, 1, 1, 1);
Vector4 B (2, 2, 2, 2);
Vector4 ExpectedResult (3, 3, 3, 3);
jtest::check_v4_eq(A + B, ExpectedResult);
});
}
inline void Run()
{
Vector4 Input (0, 1, 0, 1);
// TODO: implement jtest::check_float_eq
jtest::check(Input.x == 0);
jtest::check(Input.y == 1);
jtest::check(Input.z == 0);
jtest::check(Input.w == 1);
});
TEST("Vector4::Addition_Op", [] {
Vector4 A (1, 1, 1, 1);
Vector4 B (2, 2, 2, 2);
Vector4 ExpectedResult (3, 3, 3, 3);
EXPECT_V4_EQ(A + B, ExpectedResult);
});
Vector4Unit.RunAll();
}
}
/*TEST(Vector4Test, V4_Addition_Method)
{
@@ -108,9 +122,4 @@ int Vector4Tests()
TEST(Vector4Tests, V4_DotProduct) {}
TEST(Vector4Tests, V4_CrossProduct) {}
TEST(Vector4Tests, V4_Project) {}
TEST(Vector4Test, V4_Normalize) {}*/
return 0;
}
TEST(Vector4Test, V4_Normalize) {}*/

View File

@@ -13,71 +13,80 @@
#pragma once
#include <jtest/jtest.hpp>
#include "J3ML/J3ML.hpp"
#include <jtest/Unit.hpp>
#include <J3ML/J3ML.hpp>
inline void MathFuncTests()
jtest::Unit MathUnit {"Math"};
namespace MathTests
{
using namespace jtest;
using namespace J3ML::Math;
inline void Define()
{
using namespace jtest;
using namespace J3ML::Math;
using namespace J3ML;
TEST("Math::IsFinite", [] {
check(IsFinite<u32>(420));
check(IsFinite<u64>(25));
check(IsFinite(5.f));
check(IsFinite(5.0));
check(!IsFinite(Infinity));
check(!IsFinite(NotANumber));
});
MathUnit += Test("IsFinite", [] {
check(IsFinite<u32>(420));
check(IsFinite<u64>(25));
check(IsFinite(5.f));
check(IsFinite(5.0));
check(!IsFinite(Infinity));
check(!IsFinite(NotANumber));
});
TEST("Math::IsNotANumber", [] {
check(!IsNotANumber(5.f));
check(!IsNotANumber(5.0));
check(IsNotANumber(NotANumber));
check(!IsNotANumber(Infinity));
});
MathUnit += Test("IsNotANumber", [] {
check(!IsNotANumber(5.f));
check(!IsNotANumber(5.0));
check(IsNotANumber(NotANumber));
check(!IsNotANumber(Infinity));
});
TEST("Math::IsInfinite", [] {
check(!IsInfinite(5.f));
check(!IsInfinite(5.0));
check(IsInfinite(Infinity));
});
MathUnit += Test("IsInfinite", [] {
check(!IsInfinite(5.f));
check(!IsInfinite(5.0));
check(IsInfinite(Infinity));
});
TEST("Math::ReinterpretAsU32", [] {
check(ReinterpretAs<u32>(0.f) == 0x00000000);
check(ReinterpretAs<u32>(1.f) == 0x3F800000);
check(ReinterpretAs<u32>(2.f) == 0x40000000);
check(ReinterpretAs<u32>(-1.f) == 0xBF800000);
check(ReinterpretAs<u32>(Infinity) == 0x7F800000);
});
MathUnit += Test("ReinterpretAsU32", [] {
check(ReinterpretAs<u32>(0.f) == 0x00000000);
check(ReinterpretAs<u32>(1.f) == 0x3F800000);
check(ReinterpretAs<u32>(2.f) == 0x40000000);
check(ReinterpretAs<u32>(-1.f) == 0xBF800000);
check(ReinterpretAs<u32>(Infinity) == 0x7F800000);
});
TEST("Math::ReinterpretAsFloat", [] {
check(ReinterpretAs<float, u32>(0x00000000) == 0.f);
check(ReinterpretAs<float, u32>(0x3F800000) == 1.f);
check(ReinterpretAs<float, u32>(0x40000000) == 2.f);
check(ReinterpretAs<float, u32>(0xBF800000) == -1.f);
check(ReinterpretAs<float>(0x7F800000) == Infinity);
check(IsNotANumber(ReinterpretAs<float>(0x7F800001)));
});
MathUnit += Test("ReinterpretAsFloat", [] {
check(ReinterpretAs<float, u32>(0x00000000) == 0.f);
check(ReinterpretAs<float, u32>(0x3F800000) == 1.f);
check(ReinterpretAs<float, u32>(0x40000000) == 2.f);
check(ReinterpretAs<float, u32>(0xBF800000) == -1.f);
check(ReinterpretAs<float>(0x7F800000) == Infinity);
check(IsNotANumber(ReinterpretAs<float>(0x7F800001)));
});
TEST("Math::SqrtVals", [] {
MathUnit += Test("SqrtVals", [] {
});
});
TEST("Math::SqrtPrecision", [] {
MathUnit += Test("SqrtPrecision", [] {
});
});
TEST("Math::SqrtRSqrtPrecision", [] {
MathUnit += Test("SqrtRSqrtPrecision", [] {
});
});
TEST("Math::SqrtRecipPrecision", [] {
MathUnit += Test("SqrtRecipPrecision", [] {
});
});
TEST("Math::Min", [] {});
TEST("Math::Max", [] {});
TEST("Math::IsPow2", [] {});
MathUnit += Test("Min", [] {});
MathUnit += Test("Max", [] {});
MathUnit += Test("IsPow2", [] {});
}
}
inline void Run()
{
MathUnit.RunAll();
}
}

View File

@@ -9,15 +9,12 @@
/// @desc Includes and runs all project unit tests.
/// @edit 2024-07-06
#include "Algorithm/RNGTests.hpp"
#include "Geometry/Geometry.hpp"
#include "Geometry/CommonGeometryTests.hpp"
#include "Geometry/TriangleTests.hpp"
#include "Geometry/AABBTests.hpp"
#include "Geometry/FrustumTests.hpp"
#include "LinearAlgebra/EulerAngleTests.hpp"
#include "LinearAlgebra/Matrix2x2Tests.hpp"
#include "LinearAlgebra/Matrix3x3Tests.hpp"
@@ -32,44 +29,108 @@
// TODO: Fix this once Automatic Test Discovery is implemented in jtest
// TODO: Add test category blocks as feature to jtest
void MiscTests() {
MathFuncTests();
//void GeometryTests() {
//CommonGeometryTests();
//TriangleTests();
//AABBTests();
//FrustumTests();
//}
//void LinearAlgebraTests() {
//EulerAngleTests();
//Vector2Tests();
//Vector3Tests();
//Vector4Tests();
//QuaternionTests();
//Matrix2x2Tests();
//Matrix3x3Tests();
//Matrix4x4Tests();
//}
namespace AlgorithmTests
{
void Define()
{
RNGTests::Define();
}
void Run()
{
RNGTests::Run();
}
}
void AlgorithmTests() {
RNGTests();
namespace LinearAlgebraTests
{
void Define()
{
EulerAngleTests::Define();
Vector2Tests::Define();
Vector3Tests::Define();
Vector4Tests::Define();
QuaternionTests::Define();
Matrix2x2Tests::Define();
Matrix3x3Tests::Define();
Matrix4x4Tests::Define();
}
void Run()
{
EulerAngleTests::Run();
Vector2Tests::Run();
Vector3Tests::Run();
Vector4Tests::Run();
QuaternionTests::Run();
Matrix2x2Tests::Run();
Matrix3x3Tests::Run();
Matrix4x4Tests::Run();
}
}
void GeometryTests() {
CommonGeometryTests();
TriangleTests();
AABBTests();
FrustumTests();
namespace GeometryTests
{
void Define()
{
CommonGeometryTests::Define();
TriangleTests::Define();
AABBTests::Define();
FrustumTests::Define();
}
void Run()
{
CommonGeometryTests::Run();
TriangleTests::Run();
AABBTests::Run();
FrustumTests::Run();
}
}
void LinearAlgebraTests() {
EulerAngleTests();
Vector2Tests();
Vector3Tests();
Vector4Tests();
QuaternionTests();
Matrix2x2Tests();
Matrix3x3Tests();
Matrix4x4Tests();
void DefineTests()
{
MathTests::Define();
AlgorithmTests::Define();
LinearAlgebraTests::Define();
GeometryTests::Define();
}
void RunTests()
{
MathTests::Run();
AlgorithmTests::Run();
LinearAlgebraTests::Run();
GeometryTests::Run();
}
int main(int argc, char** argv)
{
MiscTests();
AlgorithmTests();
LinearAlgebraTests();
GeometryTests();
DefineTests();
RunTests();
jtest::run_tests();
return 0;
}