From cfa5aa6a61d0936f2b7f97980b44f87c40defdda Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 14 Jan 2022 19:08:04 +0100 Subject: [PATCH] Add math unit tests for c++ --- Source/Engine/Core/Math/Packed.cpp | 71 +++++---- Source/Engine/Core/Math/Packed.h | 29 ++-- Source/Engine/Core/Math/Quaternion.cs | 96 +++--------- Source/Engine/Core/RandomStream.h | 12 +- Source/Engine/Tests/TestMath.cpp | 201 ++++++++++++++++++++++++++ 5 files changed, 291 insertions(+), 118 deletions(-) create mode 100644 Source/Engine/Tests/TestMath.cpp diff --git a/Source/Engine/Core/Math/Packed.cpp b/Source/Engine/Core/Math/Packed.cpp index fa6ac2b96..1ef7decbf 100644 --- a/Source/Engine/Core/Math/Packed.cpp +++ b/Source/Engine/Core/Math/Packed.cpp @@ -6,35 +6,65 @@ #include "Vector4.h" #include "Color.h" -Float1010102::Float1010102(float x, float y, float z, float w) +FloatR10G10B10A2::FloatR10G10B10A2(uint32 packed) + : Value(packed) { - x = Math::Clamp(x, 0.0f, 1.0f); - y = Math::Clamp(y, 0.0f, 1.0f); - z = Math::Clamp(z, 0.0f, 1.0f); - w = Math::Clamp(w, 0.0f, 1.0f); +} + +FloatR10G10B10A2::FloatR10G10B10A2(float x, float y, float z, float w) +{ + x = Math::Saturate(x); + y = Math::Saturate(y); + z = Math::Saturate(z); + w = Math::Saturate(w); x = Math::Round(x * 1023.0f); y = Math::Round(y * 1023.0f); z = Math::Round(z * 1023.0f); w = Math::Round(w * 3.0f); - Value = ((uint32)w << 30) | - (((uint32)z & 0x3FF) << 20) | - (((uint32)y & 0x3FF) << 10) | - (((uint32)x & 0x3FF)); + Value = ((uint32)w << 30) | (((uint32)z & 0x3FF) << 20) | (((uint32)y & 0x3FF) << 10) | (((uint32)x & 0x3FF)); } -Float1010102::Float1010102(const Vector3& v, float alpha) - : Float1010102(v.X, v.Y, v.Z, alpha) +FloatR10G10B10A2::FloatR10G10B10A2(const Vector3& v, float alpha) + : FloatR10G10B10A2(v.X, v.Y, v.Z, alpha) { } -Float1010102::Float1010102(const float* values) - : Float1010102(values[0], values[1], values[2], values[3]) +FloatR10G10B10A2::FloatR10G10B10A2(const Vector4& v) + : FloatR10G10B10A2(v.X, v.Y, v.Z, v.W) { } -Float1010102::operator Vector4() const +FloatR10G10B10A2::FloatR10G10B10A2(const float* values) + : FloatR10G10B10A2(values[0], values[1], values[2], values[3]) +{ +} + +FloatR10G10B10A2::operator Vector3() const +{ + return ToVector3(); +} + +FloatR10G10B10A2::operator Vector4() const +{ + return ToVector4(); +} + +Vector3 FloatR10G10B10A2::ToVector3() const +{ + Vector3 vectorOut; + uint32 tmp; + tmp = Value & 0x3FF; + vectorOut.X = (float)tmp / 1023.f; + tmp = (Value >> 10) & 0x3FF; + vectorOut.Y = (float)tmp / 1023.f; + tmp = (Value >> 20) & 0x3FF; + vectorOut.Z = (float)tmp / 1023.f; + return vectorOut; +} + +Vector4 FloatR10G10B10A2::ToVector4() const { Vector4 vectorOut; uint32 tmp; @@ -48,19 +78,6 @@ Float1010102::operator Vector4() const return vectorOut; } -Vector3 Float1010102::ToVector3() const -{ - Vector3 vectorOut; - uint32 tmp; - tmp = Value & 0x3FF; - vectorOut.X = (float)tmp / 1023.f; - tmp = (Value >> 10) & 0x3FF; - vectorOut.Y = (float)tmp / 1023.f; - tmp = (Value >> 20) & 0x3FF; - vectorOut.Z = (float)tmp / 1023.f; - return vectorOut; -} - FloatR11G11B10::FloatR11G11B10(float x, float y, float z) { // Reference: https://github.com/microsoft/DirectXMath diff --git a/Source/Engine/Core/Math/Packed.h b/Source/Engine/Core/Math/Packed.h index 2337b86ba..eabaae619 100644 --- a/Source/Engine/Core/Math/Packed.h +++ b/Source/Engine/Core/Math/Packed.h @@ -13,7 +13,7 @@ typedef Half Float16; /// /// Packed vector, layout: R:10 bytes, G:10 bytes, B:10 bytes, A:2 bytes, all values are stored as floats in range [0;1]. /// -struct FLAXENGINE_API Float1010102 +struct FLAXENGINE_API FloatR10G10B10A2 { union { @@ -28,41 +28,41 @@ struct FLAXENGINE_API Float1010102 uint32 Value; }; - Float1010102() = default; + FloatR10G10B10A2() = default; - explicit Float1010102(uint32 packed) - : Value(packed) - { - } - - Float1010102(float x, float y, float z, float w); - Float1010102(const Vector3& v, float alpha = 0); - explicit Float1010102(const float* values); + explicit FloatR10G10B10A2(uint32 packed); + FloatR10G10B10A2(float x, float y, float z, float w); + FloatR10G10B10A2(const Vector3& v, float alpha = 0); + FloatR10G10B10A2(const Vector4& v); + explicit FloatR10G10B10A2(const float* values); operator uint32() const { return Value; } + operator Vector3() const; operator Vector4() const; - Float1010102& operator=(const Float1010102& other) + FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other) { Value = other.Value; return *this; } - Float1010102& operator=(uint32 packed) + FloatR10G10B10A2& operator=(uint32 packed) { Value = packed; return *this; } -public: - Vector3 ToVector3() const; + Vector4 ToVector4() const; }; +// [Deprecated on 14.01.2022, expires on 14.01.2024] +typedef FloatR10G10B10A2 Float1010102; + // The 3D vector is packed into 32 bits with 11/11/10 bits per floating-point component. struct FLAXENGINE_API FloatR11G11B10 { @@ -114,7 +114,6 @@ struct FLAXENGINE_API FloatR11G11B10 } public: - Vector3 ToVector3() const; }; diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index bef9d1d83..75e0b892c 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -152,15 +152,9 @@ namespace FlaxEngine /// /// Initializes a new instance of the struct. /// - /// - /// The values to assign to the X, Y, Z, and W components of the quaternion. This must be an array - /// with four elements. - /// + /// The values to assign to the X, Y, Z, and W components of the quaternion. This must be an array with four elements. /// Thrown when is null. - /// - /// Thrown when contains more or less than four - /// elements. - /// + /// Thrown when contains more or less than four elements. public Quaternion(float[] values) { if (values == null) @@ -177,9 +171,6 @@ namespace FlaxEngine /// /// Gets a value indicating whether this instance is equivalent to the identity quaternion. /// - /// - /// true if this instance is an identity quaternion; otherwise, false. - /// public bool IsIdentity => Equals(Identity); /// @@ -238,7 +229,6 @@ namespace FlaxEngine /// /// Gets the angle of the quaternion. /// - /// The angle. public float Angle { get @@ -253,7 +243,6 @@ namespace FlaxEngine /// /// Gets the axis components of the quaternion. /// - /// The axis components of the quaternion. public Vector3 Axis { get @@ -270,14 +259,9 @@ namespace FlaxEngine /// Gets or sets the component at the specified index. /// /// The value of the X, Y, Z, or W component, depending on the index. - /// - /// The index of the component to access. Use 0 for the X component, 1 for the Y component, 2 for the Z - /// component, and 3 for the W component. - /// + /// The index of the component to access. Use 0 for the X component, 1 for the Y component, 2 for the Z component, and 3 for the W component. /// The value of the component at the specified index. - /// - /// Thrown when the is out of the range [0, 3]. - /// + /// Thrown when the is out of the range [0, 3]. public float this[int index] { get @@ -289,10 +273,8 @@ namespace FlaxEngine case 2: return Z; case 3: return W; } - throw new ArgumentOutOfRangeException(nameof(index), "Indices for Quaternion run from 0 to 3, inclusive."); } - set { switch (index) @@ -353,20 +335,14 @@ namespace FlaxEngine /// Calculates the length of the quaternion. /// /// The length of the quaternion. - /// - /// may be preferred when only the relative length is needed - /// and speed is of the essence. - /// + /// may be preferred when only the relative length is needed and speed is of the essence. public float Length => (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); /// /// Calculates the squared length of the quaternion. /// /// The squared length of the quaternion. - /// - /// This method may be preferred to when only a relative length is needed - /// and speed is of the essence. - /// + /// This method may be preferred to when only a relative length is needed and speed is of the essence. public float LengthSquared => X * X + Y * Y + Z * Z + W * W; /// @@ -1238,8 +1214,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a pitch, yaw and roll values. - /// Angles are in degrees. + /// Creates a quaternion given a pitch, yaw and roll values. Angles are in degrees. /// /// The pitch, yaw and roll angles of rotation. /// When the method completes, contains the newly created quaternion. @@ -1254,8 +1229,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a pitch, yaw and roll values. - /// Angles are in degrees. + /// Creates a quaternion given a pitch, yaw and roll values. Angles are in degrees. /// /// The pitch, yaw and roll angles of rotation. /// When the method completes, contains the newly created quaternion. @@ -1269,8 +1243,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a pitch, yaw and roll values. - /// Angles are in degrees. + /// Creates a quaternion given a pitch, yaw and roll values. Angles are in degrees. /// /// The pitch of rotation (in degrees). /// The yaw of rotation (in degrees). @@ -1287,8 +1260,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a pitch, yaw and roll values. - /// Angles are in degrees. + /// Creates a quaternion given a pitch, yaw and roll values. Angles are in degrees. /// /// The pitch of rotation (in degrees). /// The yaw of rotation (in degrees). @@ -1304,8 +1276,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a yaw, pitch, and roll value. - /// Angles are in radians. Use to convert degrees to radians. + /// Creates a quaternion given a yaw, pitch, and roll value. Angles are in radians. Use to convert degrees to radians. /// /// The yaw of rotation (in radians). /// The pitch of rotation (in radians). @@ -1331,8 +1302,7 @@ namespace FlaxEngine } /// - /// Creates a quaternion given a yaw, pitch, and roll value. - /// Angles are in radians. + /// Creates a quaternion given a yaw, pitch, and roll value. Angles are in radians. /// /// The yaw of rotation (in radians). /// The pitch of rotation (in radians). @@ -1523,10 +1493,7 @@ namespace FlaxEngine /// /// The first value to compare. /// The second value to compare. - /// - /// true if has the same value as ; otherwise, - /// false. - /// + /// true if has the same value as ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Quaternion left, Quaternion right) { @@ -1538,10 +1505,7 @@ namespace FlaxEngine /// /// The first value to compare. /// The second value to compare. - /// - /// true if has a different value than ; otherwise, - /// false. - /// + /// true if has a different value than ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Quaternion left, Quaternion right) { @@ -1551,9 +1515,7 @@ namespace FlaxEngine /// /// Returns a that represents this instance. /// - /// - /// A that represents this instance. - /// + /// A that represents this instance. public override string ToString() { return string.Format(CultureInfo.CurrentCulture, _formatString, X, Y, Z, W); @@ -1563,9 +1525,7 @@ namespace FlaxEngine /// Returns a that represents this instance. /// /// The format. - /// - /// A that represents this instance. - /// + /// A that represents this instance. public string ToString(string format) { if (format == null) @@ -1579,9 +1539,7 @@ namespace FlaxEngine /// Returns a that represents this instance. /// /// The format provider. - /// - /// A that represents this instance. - /// + /// A that represents this instance. public string ToString(IFormatProvider formatProvider) { return string.Format(formatProvider, _formatString, X, Y, Z, W); @@ -1592,9 +1550,7 @@ namespace FlaxEngine /// /// The format. /// The format provider. - /// - /// A that represents this instance. - /// + /// A that represents this instance. public string ToString(string format, IFormatProvider formatProvider) { if (format == null) @@ -1607,9 +1563,7 @@ namespace FlaxEngine /// /// Returns a hash code for this instance. /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() { unchecked @@ -1651,9 +1605,7 @@ namespace FlaxEngine /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// + /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Quaternion other) { @@ -1665,9 +1617,7 @@ namespace FlaxEngine /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// + /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Quaternion other) { @@ -1678,9 +1628,7 @@ namespace FlaxEngine /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// + /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { if (!(value is Quaternion)) diff --git a/Source/Engine/Core/RandomStream.h b/Source/Engine/Core/RandomStream.h index 729b89539..a510356ea 100644 --- a/Source/Engine/Core/RandomStream.h +++ b/Source/Engine/Core/RandomStream.h @@ -10,7 +10,7 @@ /// /// Very basic pseudo numbers generator. /// -class RandomStream +class FLAXENGINE_API RandomStream { private: @@ -102,7 +102,6 @@ public: uint32 GetUnsignedInt() const { MutateSeed(); - return *(uint32*)&_seed; } @@ -145,6 +144,15 @@ public: } while (l > 1.0f || l < ZeroTolerance); return Vector3::Normalize(result); } + + /// + /// Gets a random with components in a range between [0;1]. + /// + /// A random . + Vector3 GetVector3() const + { + return Vector3(GetFraction(), GetFraction(), GetFraction()); + } /// /// Helper function for rand implementations. diff --git a/Source/Engine/Tests/TestMath.cpp b/Source/Engine/Tests/TestMath.cpp new file mode 100644 index 000000000..0f1082bb4 --- /dev/null +++ b/Source/Engine/Tests/TestMath.cpp @@ -0,0 +1,201 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Core/Math/Packed.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Core/RandomStream.h" +#include + +static Quaternion RotationX(float angle) +{ + const float halfAngle = angle * 0.5f; + return Quaternion(Math::Sin(halfAngle), 0.0f, 0.0f, Math::Cos(halfAngle)); +} + +TEST_CASE("FloatR10G10B10A2") +{ + SECTION("Test Conversion") + { + CHECK(Vector4::NearEqual(Vector4::Zero, FloatR10G10B10A2(Vector4::Zero).ToVector4())); + CHECK(Vector4::NearEqual(Vector4::One, FloatR10G10B10A2(Vector4::One).ToVector4())); + CHECK(Vector4::NearEqual(Vector4(0.5004888f, 0.5004888f, 0.5004888f, 0.666667f), FloatR10G10B10A2(Vector4(0.5f)).ToVector4())); + CHECK(Vector4::NearEqual(Vector4(1, 0, 0, 0), FloatR10G10B10A2(Vector4(1, 0, 0, 0)).ToVector4())); + CHECK(Vector4::NearEqual(Vector4(0, 1, 0, 0), FloatR10G10B10A2(Vector4(0, 1, 0, 0)).ToVector4())); + CHECK(Vector4::NearEqual(Vector4(0, 0, 1, 0), FloatR10G10B10A2(Vector4(0, 0, 1, 0)).ToVector4())); + CHECK(Vector4::NearEqual(Vector4(0, 0, 0, 1), FloatR10G10B10A2(Vector4(0, 0, 0, 1)).ToVector4())); + } +} + +TEST_CASE("FloatR11G11B10") +{ + SECTION("Test Conversion") + { + CHECK(Vector3::NearEqual(Vector3::Zero, FloatR11G11B10(Vector3::Zero).ToVector3())); + CHECK(Vector3::NearEqual(Vector3::One, FloatR11G11B10(Vector3::One).ToVector3())); + CHECK(Vector3::NearEqual(Vector3(0.5f, 0.5f, 0.5f), FloatR11G11B10(Vector3(0.5f)).ToVector3())); + CHECK(Vector3::NearEqual(Vector3(1, 0, 0), FloatR11G11B10(Vector3(1, 0, 0)).ToVector3())); + CHECK(Vector3::NearEqual(Vector3(0, 1, 0), FloatR11G11B10(Vector3(0, 1, 0)).ToVector3())); + CHECK(Vector3::NearEqual(Vector3(0, 0, 1), FloatR11G11B10(Vector3(0, 0, 1)).ToVector3())); + CHECK(Vector3::NearEqual(Vector3(10, 11, 12), FloatR11G11B10(Vector3(10, 11, 12)).ToVector3())); + } +} + +TEST_CASE("Quaternion") +{ + SECTION("Test Euler") + { + CHECK(Quaternion::NearEqual(Quaternion::Euler(90, 0, 0), Quaternion(0.7071068f, 0, 0, 0.7071068f))); + CHECK(Quaternion::NearEqual(Quaternion::Euler(25, 0, 10), Quaternion(0.215616f, -0.018864f, 0.0850898f, 0.9725809f))); + CHECK(Vector3::NearEqual(Vector3(25, 0, 10), Quaternion::Euler(25, 0, 10).GetEuler())); + CHECK(Vector3::NearEqual(Vector3(25, -5, 10), Quaternion::Euler(25, -5, 10).GetEuler())); + } + SECTION("Test Multiply") + { + auto q = Quaternion::Identity; + auto delta = Quaternion::Euler(0, 10, 0); + for (int i = 0; i < 9; i++) + q *= delta; + CHECK(Quaternion::NearEqual(Quaternion::Euler(0, 90, 0), q, 0.00001f)); + } +} + +TEST_CASE("Transform") +{ + SECTION("Test World Matrix") + { + Transform t1(Vector3(10, 1, 10), Quaternion::Euler(45, 0, -15), Vector3(1.5f, 0.5f, 0.1f)); + + Matrix a1 = t1.GetWorld(); + + Matrix a2; + { + Matrix m1, m2; + Matrix::Scaling(t1.Scale, a2); + Matrix::RotationQuaternion(t1.Orientation, m2); + Matrix::Multiply(a2, m2, m1); + Matrix::Translation(t1.Translation, m2); + Matrix::Multiply(m1, m2, a2); + } + + Matrix a3; + Matrix::Transformation(t1.Scale, t1.Orientation, t1.Translation, a3); + + CHECK(a1 == a2); + CHECK(a1 == a3); + } + SECTION("Test Local To World") + { + Transform t1(Vector3(10, 1, 10), Quaternion::Euler(45, 0, -15), Vector3(1.5f, 0.5f, 0.1f)); + Transform t2(Vector3(0, 20, 0), Quaternion::Euler(0, 0, 15), Vector3(1.0f, 2.0f, 1.0f)); + + Transform a1 = t1.LocalToWorld(t2); + Vector3 a2 = t1.LocalToWorld(t2.Translation); + + Vector3 a3; + { + Vector3 result; + Matrix scale, rotation, scaleRotation; + Matrix::Scaling(t1.Scale, scale); + Matrix::RotationQuaternion(t1.Orientation, rotation); + Matrix::Multiply(scale, rotation, scaleRotation); + Vector3::Transform(t2.Translation, scaleRotation, result); + a3 = result + t1.Translation; + } + + Vector3 a4T[1]; + Vector3 a4Ta[1] = { t2.Translation }; + t1.LocalToWorld(a4Ta, ARRAY_COUNT(a4Ta), a4T); + Vector3 a4 = a4T[0]; + + CHECK(Vector3::NearEqual(a1.Translation, a2)); + CHECK(Vector3::NearEqual(a2, a3)); + CHECK(Vector3::NearEqual(a2, a4)); + } + SECTION("Test World To Local") + { + Transform t1 = Transform(Vector3(10, 1, 10), Quaternion::Euler(45, 0, -15), Vector3(1.5f, 0.5f, 0.1f)); + Transform t2 = Transform(Vector3(0, 20, 0), Quaternion::Euler(0, 0, 15), Vector3(1.0f, 2.0f, 1.0f)); + + Transform a1 = t1.WorldToLocal(t2); + Vector3 a2 = t1.WorldToLocal(t2.Translation); + + Vector3 a3; + { + Matrix scale, rotation, scaleRotation; + Matrix::Scaling(t1.Scale, scale); + Matrix::RotationQuaternion(t1.Orientation, rotation); + Matrix::Multiply(scale, rotation, scaleRotation); + Matrix::Invert(scaleRotation, scale); + a3 = t2.Translation - t1.Translation; + Vector3::Transform(a3, scale, a3); + } + + Vector3 a4T[1]; + Vector3 a4Ta[1] = { t2.Translation }; + t1.WorldToLocal(a4Ta, ARRAY_COUNT(a4Ta), a4T); + Vector3 a4 = a4T[0]; + + CHECK(Vector3::NearEqual(a1.Translation, a2)); + CHECK(Vector3::NearEqual(a2, a3, 0.0001f)); + CHECK(Vector3::NearEqual(a2, a4)); + } + SECTION("Test World Local Space") + { + Transform trans = Transform(Vector3(1, 2, 3)); + + CHECK(Vector3::NearEqual(Vector3(1, 2, 3), trans.LocalToWorld(Vector3(0, 0, 0)))); + CHECK(Vector3::NearEqual(Vector3(4, 4, 4), trans.LocalToWorld(Vector3(3, 2, 1)))); + CHECK(Vector3::NearEqual(Vector3(-1, -2, -3), trans.WorldToLocal(Vector3(0, 0, 0)))); + CHECK(Vector3::NearEqual(Vector3(0, 0, 0), trans.WorldToLocal(Vector3(1, 2, 3)))); + + trans = Transform(Vector3::Zero, Quaternion::Euler(0, 90, 0)); + CHECK(Vector3::NearEqual(Vector3(0, 2, -1), trans.LocalToWorld(Vector3(1, 2, 0)))); + + trans.Translation = Vector3(1, 0, 0); + trans.Orientation = RotationX(PI * 0.5f); + trans.Scale = Vector3(2, 2, 2); + CHECK(Vector3::NearEqual(Vector3(1, 0, 2), trans.LocalToWorld(Vector3(0, 1, 0)))); + + Transform t1 = trans.LocalToWorld(Transform::Identity); + CHECK(Vector3::NearEqual(Vector3(1.0f, 0, 0), t1.Translation)); + CHECK(Quaternion::NearEqual(RotationX(PI * 0.5f), t1.Orientation)); + CHECK(Vector3::NearEqual(Vector3(2.0f, 2.0f, 2.0f), t1.Scale)); + + Transform t2 = trans.WorldToLocal(Transform::Identity); + CHECK(Vector3::NearEqual(Vector3(-0.5f, 0, 0), t2.Translation)); + CHECK(Quaternion::NearEqual(RotationX(PI * -0.5f), t2.Orientation)); + CHECK(Vector3::NearEqual(Vector3(0.5f, 0.5f, 0.5f), t2.Scale)); + + RandomStream rand(10); + for (int32 i = 0; i < 10; i++) + { + Transform a = Transform(rand.GetVector3(), Quaternion::Euler((float)i * 10, 0, (float)i), rand.GetVector3() * 10.0f); + Transform b = Transform(rand.GetVector3(), Quaternion::Euler((float)i, 1, 22), rand.GetVector3() * 0.3f); + + Transform ab = a.LocalToWorld(b); + Transform ba = a.WorldToLocal(ab); + + CHECK(Transform::NearEqual(b, ba, 0.00001f)); + } + } + SECTION("Test Add Subtract") + { + RandomStream rand(10); + for (int32 i = 0; i < 10; i++) + { + Transform a = Transform(rand.GetVector3(), Quaternion::Euler((float)i * 10, 0, (float)i), rand.GetVector3() * 10.0f); + Transform b = Transform(rand.GetVector3(), Quaternion::Euler((float)i, 1, 22), rand.GetVector3() * 0.3f); + + Transform ab = a + b; + Transform newA = ab - b; + CHECK(Transform::NearEqual(a, newA, 0.00001f)); + + Transform ba = b + a; + Transform newB = ba - a; + CHECK(Transform::NearEqual(b, newB, 0.00001f)); + } + } +}