Browse Source

Vector and Quaternion moved from gaming core, more convenient string
testing

Kajetan Johannes Hammerle 1 year ago
parent
commit
0877c487b4

+ 41 - 0
math/Quaternion.cpp

@@ -0,0 +1,41 @@
+#include "math/Quaternion.h"
+
+Core::Quaternion::Quaternion() : w(1.0f) {
+}
+
+Core::Quaternion::Quaternion(const Vector3& axis, float angle) : xyz(axis) {
+    xyz.normalize();
+    float factor = 0.0f;
+    sincosf(angle * (static_cast<float>(M_PI) / 360.0f), &factor, &w);
+    xyz *= factor;
+}
+
+Core::Quaternion Core::Quaternion::lerp(float f,
+                                        const Quaternion& other) const {
+    Quaternion q;
+    q.xyz = xyz * (1.0f - f) + other.xyz * f;
+    q.w = w * (1.0f - f) + other.w * f;
+    float iLength = 1.0f / sqrtf(q.xyz.squareLength() + q.w * q.w);
+    q.xyz *= iLength;
+    q.w *= iLength;
+    return q;
+}
+
+Core::Quaternion& Core::Quaternion::operator*=(const Quaternion& other) {
+    float dot = xyz.dot(other.xyz);
+    xyz = other.xyz * w + xyz * other.w + xyz.cross(other.xyz);
+    w = w * other.w - dot;
+    return *this;
+}
+
+Core::Quaternion Core::Quaternion::operator*(const Quaternion& other) const {
+    Quaternion q(*this);
+    q *= other;
+    return q;
+}
+
+Core::Vector3 Core::Quaternion::operator*(const Vector3& v) const {
+    Vector3 qv = v * w + xyz.cross(v);
+    Vector3 qvq = xyz * xyz.dot(v) + qv * w - qv.cross(xyz);
+    return qvq;
+}

+ 31 - 0
math/Quaternion.h

@@ -0,0 +1,31 @@
+#ifndef CORE_QUATERNION_H
+#define CORE_QUATERNION_H
+
+#include "math/Vector.h"
+#include "utils/ArrayString.h"
+
+namespace Core {
+    class Quaternion {
+        Vector3 xyz;
+        float w;
+
+    public:
+        Quaternion();
+        Quaternion(const Vector3& axis, float angle);
+
+        Quaternion lerp(float f, const Quaternion& other) const;
+        Quaternion& operator*=(const Quaternion& other);
+        Quaternion operator*(const Quaternion& other) const;
+        Vector3 operator*(const Vector3& v) const;
+
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            return s.append("(") || s.append(xyz[0]) || s.append(" i + ") ||
+                   s.append(xyz[1]) || s.append(" j + ") || s.append(xyz[2]) ||
+                   s.append(" k + ") || s.append(w) || s.append(')');
+        }
+    };
+}
+
+#endif

+ 22 - 0
math/Vector.cpp

@@ -0,0 +1,22 @@
+#include "math/Vector.h"
+
+template<>
+Core::Vector3& Core::Vector3::setAngles(float lengthAngle, float widthAngle) {
+    float sWidth = 0.0f;
+    float cWidth = 0.0f;
+    sincosf(widthAngle * (static_cast<float>(M_PI) / 180.0f), &sWidth, &cWidth);
+
+    float sLength = 0.0f;
+    float cLength = 0.0f;
+    sincosf(lengthAngle * (static_cast<float>(M_PI) / 180.0f), &sLength,
+            &cLength);
+
+    return *this = Vector3(cWidth * cLength, sWidth, -sLength * cWidth);
+}
+
+template<>
+Core::Vector3 Core::Vector3::cross(const Vector3& other) const {
+    return Vector3(values[1] * other.values[2] - values[2] * other.values[1],
+                   values[2] * other.values[0] - values[0] * other.values[2],
+                   values[0] * other.values[1] - values[1] * other.values[0]);
+}

+ 210 - 0
math/Vector.h

@@ -0,0 +1,210 @@
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#include "math/Math.h"
+#include "utils/ArrayString.h"
+
+namespace Core {
+    template<int N, typename T>
+    class Vector final {
+        static_assert(N > 0, "Vector size must be positive");
+        T values[static_cast<unsigned int>(N)];
+
+    public:
+        Vector() {
+            for(int i = 0; i < N; i++) {
+                values[i] = static_cast<T>(0);
+            }
+        }
+
+        template<typename OT, typename... Args>
+        Vector(OT a, Args&&... args) {
+            init<0>(a, args...);
+        }
+
+    private:
+        template<int I>
+        void init() {
+            static_assert(I == N, "vector parameters do not match its size");
+        }
+
+        template<int I, typename OT, typename... Args>
+        void init(OT a, Args&&... args) {
+            values[I] = static_cast<T>(a);
+            init<I + 1>(args...);
+        }
+
+    public:
+        Vector& setAngles(float, float) = delete;
+        Vector cross(const Vector&) const = delete;
+
+        Vector& operator+=(const Vector& other) {
+            for(int i = 0; i < N; i++) {
+                values[i] += other.values[i];
+            }
+            return *this;
+        }
+
+        Vector operator+(const Vector& other) const {
+            Vector v = *this;
+            v += other;
+            return v;
+        }
+
+        Vector& operator-=(const Vector& other) {
+            for(int i = 0; i < N; i++) {
+                values[i] -= other.values[i];
+            }
+            return *this;
+        }
+
+        Vector operator-() const {
+            Vector v = *this;
+            for(int i = 0; i < N; i++) {
+                v.values[i] = -v.values[i];
+            }
+            return v;
+        }
+
+        Vector operator-(const Vector& other) const {
+            Vector v = *this;
+            v -= other;
+            return v;
+        }
+
+        Vector& operator*=(T factor) {
+            for(int i = 0; i < N; i++) {
+                values[i] *= factor;
+            }
+            return *this;
+        }
+
+        Vector& operator*=(const Vector& other) {
+            for(int i = 0; i < N; i++) {
+                values[i] *= other.values[i];
+            }
+            return *this;
+        }
+
+        Vector operator*(T factor) const {
+            Vector v = *this;
+            v *= factor;
+            return v;
+        }
+
+        Vector operator*(const Vector& other) const {
+            Vector v = *this;
+            v *= other;
+            return v;
+        }
+
+        Vector& operator/=(T factor) {
+            for(int i = 0; i < N; i++) {
+                values[i] /= factor;
+            }
+            return *this;
+        }
+
+        Vector& operator/=(const Vector& other) {
+            for(int i = 0; i < N; i++) {
+                values[i] /= other.values[i];
+            }
+            return *this;
+        }
+
+        Vector operator/(T factor) const {
+            Vector v = *this;
+            v /= factor;
+            return v;
+        }
+
+        Vector operator/(const Vector& other) const {
+            Vector v = *this;
+            v /= other;
+            return v;
+        }
+
+        T dot(const Vector& v) const {
+            T length = 0.0f;
+            for(int i = 0; i < N; i++) {
+                length += values[i] * v.values[i];
+            }
+            return length;
+        }
+
+        T squareLength() const {
+            return dot(*this);
+        }
+
+        float length() const {
+            return sqrtf(squareLength());
+        }
+
+        Vector& normalize() {
+            *this *= 1.0f / length();
+            return *this;
+        }
+
+        T& operator[](int index) {
+            return values[index];
+        }
+
+        const T& operator[](int index) const {
+            return values[index];
+        }
+
+        Vector<N, int> toInt() const {
+            Vector<N, int> cast;
+            for(int i = 0; i < N; i++) {
+                cast[i] = static_cast<int>(values[i]);
+            }
+            return cast;
+        }
+
+        Vector<N, float> toFloat() const {
+            Vector<N, float> cast;
+            for(int i = 0; i < N; i++) {
+                cast[i] = static_cast<float>(values[i]);
+            }
+            return cast;
+        }
+
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
+            for(int i = 0; i < N - 1; i++) {
+                if(s.append(values[i]) || s.append(", ")) {
+                    return true;
+                }
+            }
+            if(N > 0 && s.append(values[N - 1])) {
+                return true;
+            }
+            return s.append("]");
+        }
+    };
+
+    using Vector4 = Vector<4, float>;
+    using Vector3 = Vector<3, float>;
+    using Vector2 = Vector<2, float>;
+
+    using IntVector4 = Vector<4, int>;
+    using IntVector3 = Vector<3, int>;
+    using IntVector2 = Vector<2, int>;
+
+    template<>
+    Vector3& Vector3::setAngles(float lengthAngle, float widthAngle);
+
+    template<>
+    Vector3 Vector3::cross(const Vector3& other) const;
+}
+
+template<int N, typename T>
+Core::Vector<N, T> operator*(T factor, const Core::Vector<N, T>& v) {
+    return v * factor;
+}
+
+#endif

+ 5 - 1
meson.build

@@ -5,6 +5,8 @@ src = [
     'utils/Utility.cpp',
     'utils/HashCode.cpp',
     'data/BitArray.cpp',
+    'math/Vector.cpp',
+    'math/Quaternion.cpp',
 ]
 
 src_tests = [
@@ -23,6 +25,8 @@ src_tests = [
     'tests/StackTests.cpp',
     'tests/RingBufferTests.cpp',
     'tests/ComponentsTests.cpp',
+    'tests/VectorTests.cpp',
+    'tests/QuaternionTests.cpp',
 ]
 
 compiler = meson.get_compiler('cpp')
@@ -46,7 +50,7 @@ compile_args = compiler.get_supported_arguments([
     '-nostdinc++', '-fno-exceptions', '-fno-rtti', '-fno-threadsafe-statics'
 ])
 link_args = compiler.get_supported_arguments([
-    '-nodefaultlibs', '-lc'
+    '-nodefaultlibs', '-lc', '-lm'
 ])
 
 core_include = [include_directories('.')]

+ 4 - 0
test/Main.cpp

@@ -10,10 +10,12 @@
 #include "tests/LinkedListTests.h"
 #include "tests/ListTests.h"
 #include "tests/MathTests.h"
+#include "tests/QuaternionTests.h"
 #include "tests/RingBufferTests.h"
 #include "tests/StackTests.h"
 #include "tests/UniquePointerTests.h"
 #include "tests/UtilityTests.h"
+#include "tests/VectorTests.h"
 #include "utils/Utility.h"
 
 static void onExit(int code, void* data) {
@@ -31,10 +33,12 @@ int main() {
     Core::LinkedListTests::test();
     Core::ListTests::test();
     Core::MathTests::test();
+    Core::QuaternionTests::test();
     Core::RingBufferTests::test();
     Core::StackTests::test();
     Core::UniquePointerTests::test();
     Core::UtilityTests::test();
+    Core::VectorTests::test();
 
     Core::Test::finalize();
 

+ 18 - 0
test/Test.h

@@ -38,6 +38,22 @@ namespace Core::Test {
             }
         }
 
+        template<typename A, typename B>
+        void checkString(const char* file, int line, const A& wanted,
+                         const B& actual) {
+            ArrayString<2048> a;
+            if(a.append(wanted)) {
+                CORE_LOG_WARNING("too small buffer");
+                return;
+            }
+            ArrayString<2048> b;
+            if(b.append(actual)) {
+                CORE_LOG_WARNING("too small buffer");
+                return;
+            }
+            return checkEqual(file, line, a, b);
+        }
+
         void checkFloat(const char* file, int line, float wanted, float actual,
                         float error);
     }
@@ -47,6 +63,8 @@ namespace Core::Test {
 #define CORE_TEST_EQUAL(wanted, actual)                                        \
     Core::Test::Internal::checkEqual<Core::RemoveReference<decltype(actual)>>( \
         __FILE__, __LINE__, wanted, actual);
+#define CORE_TEST_STRING(wanted, actual)                                       \
+    Core::Test::Internal::checkString(__FILE__, __LINE__, wanted, actual);
 #define CORE_TEST_FALSE(actual) CORE_TEST_EQUAL(false, actual);
 #define CORE_TEST_TRUE(actual) CORE_TEST_EQUAL(true, actual);
 #define CORE_TEST_NULL(actual) CORE_TEST_EQUAL(true, actual == nullptr);

+ 3 - 11
tests/ArrayListTests.cpp

@@ -4,14 +4,6 @@
 #include "test/Test.h"
 
 using IntList = Core::ArrayList<int, 20>;
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
 
 static void testAdd() {
     IntList list;
@@ -121,18 +113,18 @@ static void testToString1() {
     CORE_TEST_NOT_NULL(list.add(1));
     CORE_TEST_NOT_NULL(list.add(243));
     CORE_TEST_NOT_NULL(list.add(-423));
-    CORE_TEST_EQUAL(build("[1, 243, -423]"), build(list));
+    CORE_TEST_STRING("[1, 243, -423]", list);
 }
 
 static void testToString2() {
     IntList list;
     CORE_TEST_NOT_NULL(list.add(1));
-    CORE_TEST_EQUAL(build("[1]"), build(list));
+    CORE_TEST_STRING("[1]", list);
 }
 
 static void testToString3() {
     IntList list;
-    CORE_TEST_EQUAL(build("[]"), build(list));
+    CORE_TEST_STRING("[]", list);
 }
 
 static void testRemove() {

+ 2 - 11
tests/ArrayTests.cpp

@@ -3,27 +3,18 @@
 #include "data/Array.h"
 #include "test/Test.h"
 
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
-
 static void testToString1() {
     Core::Array<int, 3> a;
     a[0] = 1;
     a[1] = 243;
     a[2] = -423;
-    CORE_TEST_EQUAL(build("[1, 243, -423]"), build(a));
+    CORE_TEST_STRING("[1, 243, -423]", a);
 }
 
 static void testToString2() {
     Core::Array<int, 1> a;
     a[0] = 1;
-    CORE_TEST_EQUAL(build("[1]"), build(a));
+    CORE_TEST_STRING("[1]", a);
 }
 
 void Core::ArrayTests::test() {

+ 3 - 12
tests/BitArrayTests.cpp

@@ -3,15 +3,6 @@
 #include "data/BitArray.h"
 #include "test/Test.h"
 
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
-
 static void testSetRead() {
     Core::BitArray bits;
     CORE_TEST_FALSE(bits.resize(4, 3));
@@ -101,19 +92,19 @@ static void testToString1() {
     Core::BitArray bits;
     CORE_TEST_FALSE(bits.resize(4, 3));
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
-    CORE_TEST_EQUAL(build("[1, 2, 3, 4]"), build(bits));
+    CORE_TEST_STRING("[1, 2, 3, 4]", bits);
 }
 
 static void testToString2() {
     Core::BitArray bits;
     CORE_TEST_FALSE(bits.resize(1, 3));
     bits.set(0, 1);
-    CORE_TEST_EQUAL(build("[1]"), build(bits));
+    CORE_TEST_STRING("[1]", bits);
 }
 
 static void testToString3() {
     Core::BitArray bits;
-    CORE_TEST_EQUAL(build("[]"), build(bits));
+    CORE_TEST_STRING("[]", bits);
 }
 
 void Core::BitArrayTests::test() {

+ 3 - 11
tests/HashMapTests.cpp

@@ -4,14 +4,6 @@
 #include "test/Test.h"
 
 using IntMap = Core::HashMap<int, int>;
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
 
 static void testAdd() {
     IntMap map;
@@ -133,18 +125,18 @@ static void testToString1() {
     CORE_TEST_NOT_NULL(map.add(1, 3));
     CORE_TEST_NOT_NULL(map.add(2, 4));
     CORE_TEST_NOT_NULL(map.add(3, 5));
-    CORE_TEST_EQUAL(build("[1 = 3, 2 = 4, 3 = 5]"), build(map));
+    CORE_TEST_STRING("[1 = 3, 2 = 4, 3 = 5]", map);
 }
 
 static void testToString2() {
     IntMap map;
     CORE_TEST_NOT_NULL(map.add(1, 3));
-    CORE_TEST_EQUAL(build("[1 = 3]"), build(map));
+    CORE_TEST_STRING("[1 = 3]", map);
 }
 
 static void testToString3() {
     IntMap map;
-    CORE_TEST_EQUAL(build("[]"), build(map));
+    CORE_TEST_STRING("[]", map);
 }
 
 static void testCopy() {

+ 3 - 11
tests/LinkedListTests.cpp

@@ -14,14 +14,6 @@ struct LinkedListTester {
 };
 
 using IntList = Core::LinkedList<int>;
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
 
 static void testWithoutCopyOrMove() {
     Core::LinkedList<LinkedListTester> list;
@@ -124,18 +116,18 @@ static void testToString1() {
     CORE_TEST_NOT_NULL(list.add(1));
     CORE_TEST_NOT_NULL(list.add(243));
     CORE_TEST_NOT_NULL(list.add(-423));
-    CORE_TEST_EQUAL(build("[1, 243, -423]"), build(list));
+    CORE_TEST_STRING("[1, 243, -423]", list);
 }
 
 static void testToString2() {
     IntList list;
     CORE_TEST_NOT_NULL(list.add(1));
-    CORE_TEST_EQUAL(build("[1]"), build(list));
+    CORE_TEST_STRING("[1]", list);
 }
 
 static void testToString3() {
     IntList list;
-    CORE_TEST_EQUAL(build("[]"), build(list));
+    CORE_TEST_STRING("[]", list);
 }
 
 static void testRemove() {

+ 3 - 11
tests/ListTests.cpp

@@ -4,14 +4,6 @@
 #include "test/Test.h"
 
 using IntList = Core::List<int>;
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
 
 static void testAdd() {
     IntList list;
@@ -119,18 +111,18 @@ static void testToString1() {
     CORE_TEST_NOT_NULL(list.add(1));
     CORE_TEST_NOT_NULL(list.add(243));
     CORE_TEST_NOT_NULL(list.add(-423));
-    CORE_TEST_EQUAL(build("[1, 243, -423]"), build(list));
+    CORE_TEST_STRING("[1, 243, -423]", list);
 }
 
 static void testToString2() {
     IntList list;
     CORE_TEST_NOT_NULL(list.add(1));
-    CORE_TEST_EQUAL(build("[1]"), build(list));
+    CORE_TEST_STRING("[1]", list);
 }
 
 static void testToString3() {
     IntList list;
-    CORE_TEST_EQUAL(build("[]"), build(list));
+    CORE_TEST_STRING("[]", list);
 }
 
 static void testRemoveBySwap() {

+ 81 - 0
tests/QuaternionTests.cpp

@@ -0,0 +1,81 @@
+#include "tests/QuaternionTests.h"
+
+#include "math/Quaternion.h"
+#include "test/Test.h"
+
+const float eps = 0.0001f;
+
+template<int N, typename T>
+static void compareVectors(const Core::Vector<N, T>& wanted,
+                           const Core::Vector<N, T>& actual) {
+    for(int i = 0; i < N; i++) {
+        CORE_TEST_FLOAT(wanted[i], actual[i], eps);
+    }
+}
+
+static void testInit() {
+    Core::Quaternion q;
+    CORE_TEST_STRING("(0.00 i + 0.00 j + 0.00 k + 1.00)", q);
+}
+
+static void testAxisAndDegreesInit() {
+    Core::Quaternion q(Core::Vector3(1.0f, 2.0f, 3.0f), 142.0f);
+    CORE_TEST_STRING("(0.25 i + 0.51 j + 0.76 k + 0.33)", q);
+}
+
+static void testLerp() {
+    Core::Quaternion q1(Core::Vector3(2.0f, 5.0f, 7.0f), 130.0f);
+    Core::Quaternion q2(Core::Vector3(1.0f, 2.0f, 4.0f), 260.0f);
+    Core::Quaternion q3 = q1.lerp(0.3f, q2);
+    CORE_TEST_STRING("(0.22 i + 0.53 j + 0.81 k + 0.12)", q3);
+}
+
+static void testMulSet() {
+    Core::Quaternion q1(Core::Vector3(2.0f, 5.0f, 7.0f), 50.0f);
+    Core::Quaternion q2(Core::Vector3(2.0f, 5.0f, 7.0f), 60.0f);
+    Core::Quaternion q3;
+    q3 *= q1;
+    CORE_TEST_STRING(q1, q3);
+    q3 *= q2;
+    CORE_TEST_STRING(Core::Quaternion(Core::Vector3(2.0f, 5.0f, 7.0f), 110.0f),
+                     q3);
+}
+
+static void testMul() {
+    Core::Quaternion q1(Core::Vector3(2.0f, 5.0f, 7.0f), 50.0f);
+    Core::Quaternion q2(Core::Vector3(2.0f, 5.0f, 7.0f), 60.0f);
+    Core::Quaternion q3 = q1 * q2;
+    CORE_TEST_STRING(Core::Quaternion(Core::Vector3(2.0f, 5.0f, 7.0f), 110.0f),
+                     q3);
+}
+
+static void testMulVector() {
+    Core::Quaternion q1(Core::Vector3(1.0f, 0.0f, 0.0f), 90.0f);
+    Core::Quaternion q2(Core::Vector3(0.0f, 1.0f, 0.0f), 90.0f);
+    Core::Quaternion q3(Core::Vector3(0.0f, 0.0f, 1.0f), 90.0f);
+
+    Core::Vector3 v1(1.0f, 0.0f, 0.0f);
+    Core::Vector3 v2(0.0f, 1.0f, 0.0f);
+    Core::Vector3 v3(0.0f, 0.0f, 1.0f);
+
+    compareVectors(Core::Vector3(1.0f, 0.0f, 0.0f), q1 * v1);
+    compareVectors(Core::Vector3(0.0f, 0.0f, 1.0f), q1 * v2);
+    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f), q1 * v3);
+
+    compareVectors(Core::Vector3(0.0f, 0.0f, -1.0f), q2 * v1);
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f), q2 * v2);
+    compareVectors(Core::Vector3(1.0f, 0.0f, 0.0f), q2 * v3);
+
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f), q3 * v1);
+    compareVectors(Core::Vector3(-1.0f, 0.0f, 0.0f), q3 * v2);
+    compareVectors(Core::Vector3(0.0f, 0.0f, 1.0f), q3 * v3);
+}
+
+void Core::QuaternionTests::test() {
+    testInit();
+    testAxisAndDegreesInit();
+    testLerp();
+    testMulSet();
+    testMul();
+    testMulVector();
+}

+ 8 - 0
tests/QuaternionTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_QUATERNION_TESTS_H
+#define CORE_QUATERNION_TESTS_H
+
+namespace Core::QuaternionTests {
+    void test();
+}
+
+#endif

+ 7 - 16
tests/RingBufferTests.cpp

@@ -3,15 +3,6 @@
 #include "data/RingBuffer.h"
 #include "test/Test.h"
 
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
-
 struct Tester final {
     static int ids;
     static int sum;
@@ -233,37 +224,37 @@ static void testOverall() {
     CORE_TEST_NOT_NULL(buffer.add());
     CORE_TEST_NOT_NULL(buffer.add());
     CORE_TEST_NOT_NULL(buffer.add());
-    CORE_TEST_EQUAL(build("[1, 2, 3]"), build(buffer));
+    CORE_TEST_STRING("[1, 2, 3]", buffer);
     CORE_TEST_EQUAL(3, buffer.getLength());
     CORE_TEST_EQUAL(6, Tester::sum);
 
     CORE_TEST_FALSE(buffer.remove());
-    CORE_TEST_EQUAL(build("[2, 3]"), build(buffer));
+    CORE_TEST_STRING("[2, 3]", buffer);
     CORE_TEST_EQUAL(2, buffer.getLength());
     CORE_TEST_EQUAL(5, Tester::sum);
 
     CORE_TEST_NOT_NULL(buffer.add());
-    CORE_TEST_EQUAL(build("[2, 3, 4]"), build(buffer));
+    CORE_TEST_STRING("[2, 3, 4]", buffer);
     CORE_TEST_EQUAL(3, buffer.getLength());
     CORE_TEST_EQUAL(9, Tester::sum);
 
     CORE_TEST_FALSE(buffer.remove());
-    CORE_TEST_EQUAL(build("[3, 4]"), build(buffer));
+    CORE_TEST_STRING("[3, 4]", buffer);
     CORE_TEST_EQUAL(2, buffer.getLength());
     CORE_TEST_EQUAL(7, Tester::sum);
 
     CORE_TEST_NOT_NULL(buffer.add());
-    CORE_TEST_EQUAL(build("[3, 4, 5]"), build(buffer));
+    CORE_TEST_STRING("[3, 4, 5]", buffer);
     CORE_TEST_EQUAL(3, buffer.getLength());
     CORE_TEST_EQUAL(12, Tester::sum);
 
     CORE_TEST_FALSE(buffer.remove());
-    CORE_TEST_EQUAL(build("[4, 5]"), build(buffer));
+    CORE_TEST_STRING("[4, 5]", buffer);
     CORE_TEST_EQUAL(2, buffer.getLength());
     CORE_TEST_EQUAL(9, Tester::sum);
 
     buffer.clear();
-    CORE_TEST_EQUAL(build("[]"), build(buffer));
+    CORE_TEST_STRING("[]", buffer);
     CORE_TEST_EQUAL(0, buffer.getLength());
     CORE_TEST_EQUAL(0, Tester::sum);
 }

+ 3 - 12
tests/StackTests.cpp

@@ -3,15 +3,6 @@
 #include "data/Stack.h"
 #include "test/Test.h"
 
-using String = Core::ArrayString<128>;
-
-template<typename T>
-static String build(const T& t) {
-    String s;
-    CORE_TEST_FALSE(s.append(t));
-    return s;
-}
-
 template<typename T>
 static void testPushPopPeek() {
     T stack;
@@ -45,20 +36,20 @@ static void testToString1() {
     CORE_TEST_NOT_NULL(stack.push(1));
     CORE_TEST_NOT_NULL(stack.push(243));
     CORE_TEST_NOT_NULL(stack.push(-423));
-    CORE_TEST_EQUAL(build("[1, 243, -423]"), build(stack));
+    CORE_TEST_STRING("[1, 243, -423]", stack);
 }
 
 template<typename T>
 static void testToString2() {
     T stack;
     CORE_TEST_NOT_NULL(stack.push(1));
-    CORE_TEST_EQUAL(build("[1]"), build(stack));
+    CORE_TEST_STRING("[1]", stack);
 }
 
 template<typename T>
 static void testToString3() {
     T stack;
-    CORE_TEST_EQUAL(build("[]"), build(stack));
+    CORE_TEST_STRING("[]", stack);
 }
 
 template<typename T>

+ 271 - 0
tests/VectorTests.cpp

@@ -0,0 +1,271 @@
+#include "tests/VectorTests.h"
+
+#include "math/Vector.h"
+#include "test/Test.h"
+
+const float eps = 0.0001f;
+
+template<int N, typename T>
+static void compareVectors(const Core::Vector<N, T>& wanted,
+                           const Core::Vector<N, T>& actual) {
+    for(int i = 0; i < N; i++) {
+        CORE_TEST_FLOAT(static_cast<float>(wanted[i]),
+                        static_cast<float>(actual[i]), eps);
+    }
+}
+
+static void testInitAndRead() {
+    Core::Vector3 v1;
+    Core::Vector2 v2(1.0f, 2.0f);
+    Core::Vector3 v3(3.0f, 4.0f, 5.0f);
+    Core::Vector4 v4(6.0f, 7.0f, 8.0f, 9.0f);
+    // CORE_TEST_EQUAL(0.0f, v1[0]);
+    // CORE_TEST_EQUAL(0.0f, v1[0]);
+    // CORE_TEST_EQUAL(0.0f, v1[0]);
+    // CORE_TEST_EQUAL(1.0f, v2[0]);
+    // CORE_TEST_EQUAL(2.0f, v2[1]);
+    // CORE_TEST_EQUAL(3.0f, v3[0]);
+    // CORE_TEST_EQUAL(4.0f, v3[1]);
+    // CORE_TEST_EQUAL(5.0f, v3[2]);
+    // CORE_TEST_EQUAL(6.0f, v4[0]);
+    // CORE_TEST_EQUAL(7.0f, v4[1]);
+    // CORE_TEST_EQUAL(8.0f, v4[2]);
+    // CORE_TEST_EQUAL(9.0f, v4[3]);
+}
+
+static void testSetAngles() {
+    float root = sqrtf(2.0f) * 0.5f;
+    compareVectors(Core::Vector3(1.0f, 0.0f, 0.0f),
+                   Core::Vector3().setAngles(0.0f, 0.0f));
+    compareVectors(Core::Vector3(root, 0.0f, -root),
+                   Core::Vector3().setAngles(45.0f, 0.0f));
+    compareVectors(Core::Vector3(0.0f, 0.0f, -1.0f),
+                   Core::Vector3().setAngles(90.0f, 0.0f));
+    compareVectors(Core::Vector3(-root, 0.0f, -root),
+                   Core::Vector3().setAngles(135.0f, 0.0f));
+    compareVectors(Core::Vector3(-1.0f, 0.0f, 0.0f),
+                   Core::Vector3().setAngles(180.0f, 0.0f));
+    compareVectors(Core::Vector3(-root, 0.0f, root),
+                   Core::Vector3().setAngles(225.0f, 0.0f));
+    compareVectors(Core::Vector3(0.0f, 0.0f, 1.0f),
+                   Core::Vector3().setAngles(270.0f, 0.0f));
+    compareVectors(Core::Vector3(root, 0.0f, root),
+                   Core::Vector3().setAngles(315.0f, 0.0f));
+
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
+                   Core::Vector3().setAngles(0.0f, 90.0f));
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
+                   Core::Vector3().setAngles(90.0f, 90.0f));
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
+                   Core::Vector3().setAngles(180.0f, 90.0f));
+    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
+                   Core::Vector3().setAngles(270.0f, 90.0f));
+
+    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f),
+                   Core::Vector3().setAngles(0.0f, -90.0f));
+    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f),
+                   Core::Vector3().setAngles(90.0f, -90.0f));
+    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f),
+                   Core::Vector3().setAngles(180.0f, -90.0f));
+    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f),
+                   Core::Vector3().setAngles(270.0f, -90.0f));
+
+    compareVectors(Core::Vector3(root, root, 0.0f),
+                   Core::Vector3().setAngles(0.0f, 45.0f));
+    compareVectors(Core::Vector3(0.0f, root, -root),
+                   Core::Vector3().setAngles(90.0f, 45.0f));
+    compareVectors(Core::Vector3(-root, root, 0.0f),
+                   Core::Vector3().setAngles(180.0f, 45.0f));
+    compareVectors(Core::Vector3(0.0f, root, root),
+                   Core::Vector3().setAngles(270.0f, 45.0f));
+
+    compareVectors(Core::Vector3(root, -root, 0.0f),
+                   Core::Vector3().setAngles(0.0f, -45.0f));
+    compareVectors(Core::Vector3(0.0f, -root, -root),
+                   Core::Vector3().setAngles(90.0f, -45.0f));
+    compareVectors(Core::Vector3(-root, -root, 0.0f),
+                   Core::Vector3().setAngles(180.0f, -45.0f));
+    compareVectors(Core::Vector3(0.0f, -root, root),
+                   Core::Vector3().setAngles(270.0f, -45.0f));
+
+    compareVectors(Core::Vector3(0.5f, root, -0.5f),
+                   Core::Vector3().setAngles(45.0f, 45.0f));
+}
+
+static void testCross() {
+    compareVectors(
+        Core::Vector3(0.0f, 0.0f, 1.0f),
+        Core::Vector3(1.0f, 0.0f, 0.0f).cross(Core::Vector3(0.0f, 1.0f, 0.0f)));
+    compareVectors(
+        Core::Vector3(0.0f, -1.0f, 0.0f),
+        Core::Vector3(1.0f, 0.0f, 0.0f).cross(Core::Vector3(0.0f, 0.0f, 1.0f)));
+    compareVectors(
+        Core::Vector3(0.0f, 0.0f, -1.0f),
+        Core::Vector3(0.0f, 1.0f, 0.0f).cross(Core::Vector3(1.0f, 0.0f, 0.0f)));
+    compareVectors(
+        Core::Vector3(1.0f, 0.0f, 0.0f),
+        Core::Vector3(0.0f, 1.0f, 0.0f).cross(Core::Vector3(0.0f, 0.0f, 1.0f)));
+    compareVectors(
+        Core::Vector3(0.0f, 1.0f, 0.0f),
+        Core::Vector3(0.0f, 0.0f, 1.0f).cross(Core::Vector3(1.0f, 0.0f, 0.0f)));
+    compareVectors(
+        Core::Vector3(-1.0f, 0.0f, 0.0f),
+        Core::Vector3(0.0f, 0.0f, 1.0f).cross(Core::Vector3(0.0f, 1.0f, 0.0f)));
+}
+
+static void testSetAdd() {
+    Core::Vector3 v;
+    v += Core::Vector3(1.0f, 2.0f, 3.0f);
+    compareVectors(Core::Vector3(1.0f, 2.0f, 3.0f), v);
+    v += Core::Vector3(2.0f, 3.0f, 4.0f);
+    compareVectors(Core::Vector3(3.0f, 5.0f, 7.0f), v);
+}
+
+static void testAdd() {
+    compareVectors(Core::Vector3(1.0f, 2.0f, 3.0f),
+                   Core::Vector3() + Core::Vector3(1.0f, 2.0f, 3.0f));
+    compareVectors(Core::Vector3(3.0f, 5.0f, 7.0f),
+                   Core::Vector3(1.0f, 2.0f, 3.0f) +
+                       Core::Vector3(2.0f, 3.0f, 4.0f));
+}
+
+static void testSetSub() {
+    Core::Vector3 v;
+    v -= Core::Vector3(1.0f, 2.0f, 3.0f);
+    compareVectors(Core::Vector3(-1.0f, -2.0f, -3.0f), v);
+    v -= Core::Vector3(2.0f, 3.0f, 4.0f);
+    compareVectors(Core::Vector3(-3.0f, -5.0f, -7.0f), v);
+}
+
+static void testSub() {
+    compareVectors(Core::Vector3(1.0f, 2.0f, 3.0f),
+                   Core::Vector3() - Core::Vector3(-1.0f, -2.0f, -3.0f));
+    compareVectors(Core::Vector3(-1.0f, -1.0f, -1.0f),
+                   Core::Vector3(1.0f, 2.0f, 3.0f) -
+                       Core::Vector3(2.0f, 3.0f, 4.0f));
+}
+
+static void testInvert() {
+    compareVectors(Core::Vector3(-1.0f, 2.0f, 3.0f),
+                   -Core::Vector3(1.0f, -2.0f, -3.0f));
+}
+
+static void testSetMul() {
+    Core::Vector3 v(1.0f, 2.0f, 3.0f);
+    v *= 3.0f;
+    compareVectors(Core::Vector3(3.0f, 6.0f, 9.0f), v);
+    v *= -2.0f;
+    compareVectors(Core::Vector3(-6.0f, -12.0f, -18.0f), v);
+}
+
+static void testMul() {
+    compareVectors(Core::Vector3(-3.0f, -6.0f, -9.0f),
+                   3.0f * Core::Vector3(-1.0f, -2.0f, -3.0f));
+    compareVectors(Core::Vector3(3.0f, 6.0f, 9.0f),
+                   Core::Vector3(1.0f, 2.0f, 3.0f) * 3.0);
+}
+
+static void testSetMulVector() {
+    Core::Vector3 v(1.0f, 2.0f, 3.0f);
+    v *= Core::Vector3(2.0f, 1.0f, 3.0f);
+    compareVectors(Core::Vector3(2.0f, 2.0f, 9.0f), v);
+    v *= Core::Vector3(-3.0f, 4.0f, -2.0f);
+    compareVectors(Core::Vector3(-6.0f, 8.0f, -18.0f), v);
+}
+
+static void testMulVector() {
+    compareVectors(Core::Vector3(-2.0f, -2.0f, -9.0f),
+                   Core::Vector3(2.0f, 1.0f, 3.0f) *
+                       Core::Vector3(-1.0f, -2.0f, -3.0f));
+    compareVectors(Core::Vector3(2.0f, 2.0f, 9.0f),
+                   Core::Vector3(1.0f, 2.0f, 3.0f) *
+                       Core::Vector3(2.0f, 1.0f, 3.0f));
+}
+
+static void testSetDiv() {
+    Core::Vector3 v(12.0f, 24.0f, 9.0f);
+    v /= 3.0f;
+    compareVectors(Core::Vector3(4.0f, 8.0f, 3.0f), v);
+    v /= -2.0f;
+    compareVectors(Core::Vector3(-2.0f, -4.0f, -1.5f), v);
+}
+
+static void testDiv() {
+    compareVectors(Core::Vector3(-1.0f, -2.0f, -3.0f),
+                   Core::Vector3(-3.0f, -6.0f, -9.0f) / 3.0f);
+}
+
+static void testDot() {
+    CORE_TEST_FLOAT(9.0f,
+                    Core::Vector3(-4.0f, 2.0f, -3.0f)
+                        .dot(Core::Vector3(-1.0f, -2.0f, -3.0f)),
+                    eps);
+    CORE_TEST_FLOAT(
+        -22.0f,
+        Core::Vector3(2.0f, 2.0f, -4.0f).dot(Core::Vector3(1.0f, -2.0f, 5.0f)),
+        eps);
+}
+
+static void testSquareLength() {
+    CORE_TEST_FLOAT(29.0f, Core::Vector3(-4.0f, 2.0f, -3.0f).squareLength(),
+                    eps);
+    CORE_TEST_FLOAT(24.0f, Core::Vector3(2.0f, 2.0f, -4.0f).squareLength(),
+                    eps);
+}
+
+static void testLength() {
+    CORE_TEST_FLOAT(3.0f, Core::Vector3(-2.0f, 2.0f, -1.0f).length(), eps);
+    CORE_TEST_FLOAT(7.0f, Core::Vector3(6.0f, 2.0f, -3.0f).length(), eps);
+}
+
+static void testNormalize() {
+    Core::Vector3 v1(-2.0f, 2.0f, -1.0f);
+    Core::Vector3 v2 = v1 * (1.0f / 3.0f);
+    v1.normalize();
+    compareVectors(v2, v1);
+
+    Core::Vector3 v3(6.0f, 2.0f, -3.0f);
+    Core::Vector3 v4 = v3 * (1.0f / 7.0f);
+    v3.normalize();
+    compareVectors(v4, v3);
+}
+
+static void testCast() {
+    compareVectors(Core::Vector3(-2.5f, 2.6f, 9.0f).toInt(),
+                   Core::IntVector3(-2, 2, 9));
+    compareVectors(Core::IntVector3(-2.5f, 2.6f, 9.0f).toFloat(),
+                   Core::Vector3(-2.0f, 2.0f, 9.0f));
+}
+
+static void testToString() {
+    Core::ArrayString<200> s;
+    CORE_TEST_FALSE(s.append(Core::Vector<1, float>()));
+    CORE_TEST_FALSE(s.append(Core::Vector2(2.0f, 3.0f)));
+    CORE_TEST_FALSE(s.append(Core::Vector3(4.0f, 5.0f, 6.0f)));
+    Core::ArrayString<200> s2;
+    CORE_TEST_FALSE(s2.append("[0.00][2.00, 3.00][4.00, 5.00, 6.00]"));
+    CORE_TEST_EQUAL(s2, s);
+}
+
+void Core::VectorTests::test() {
+    testInitAndRead();
+    testSetAngles();
+    testCross();
+    testSetAdd();
+    testAdd();
+    testSetSub();
+    testSub();
+    testInvert();
+    testSetMul();
+    testMul();
+    testSetMulVector();
+    testMulVector();
+    testSetDiv();
+    testDiv();
+    testDot();
+    testSquareLength();
+    testLength();
+    testNormalize();
+    testCast();
+    testToString();
+}

+ 8 - 0
tests/VectorTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_VECTOR_TESTS_H
+#define CORE_VECTOR_TESTS_H
+
+namespace Core::VectorTests {
+    void test();
+}
+
+#endif

+ 14 - 9
utils/Logger.h

@@ -16,12 +16,17 @@ namespace Core::Logger {
         }
         Core::ArrayString<2048> s;
         if(s.append(format)) {
-            printf(
-                "\33[1;31m%s:%d contains an invalid format string\33[39;49m\n",
-                file, line);
+            if(printf("\33[1;31m%s:%d contains an invalid format "
+                      "string\33[39;49m\n",
+                      file, line) <= 0) {
+                Core::exitWithHandler(1);
+            }
         } else if(s.format(Core::forward<Args>(args)...)) {
-            printf("\33[1;31m%s:%d formatting failed\33[39;49m\n", file, line);
-        } else if(fputs(start, stdout) < 0 || s.print() ||
+            if(printf("\33[1;31m%s:%d formatting failed\33[39;49m\n", file,
+                      line) <= 0) {
+                Core::exitWithHandler(1);
+            }
+        } else if(printf("%s%s:%d | ", start, file, line) <= 0 || s.print() ||
                   printf("\33[39;49m\n") <= 0) {
             Core::exitWithHandler(1);
         }
@@ -31,7 +36,7 @@ namespace Core::Logger {
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 1
 #define CORE_LOG_ERROR(format, ...)                                            \
     log(Core::Logger::Level::ERROR, __FILE__, __LINE__, "\33[1;31m[ERROR] ",   \
-        format, __VA_ARGS__);
+        format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_ERROR(format, ...)
 #endif
@@ -39,7 +44,7 @@ namespace Core::Logger {
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 2
 #define CORE_LOG_WARNING(format, ...)                                          \
     log(Core::Logger::Level::WARNING, __FILE__, __LINE__,                      \
-        "\33[1;33m[WARNING] ", format, __VA_ARGS__);
+        "\33[1;33m[WARNING] ", format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_WARNING(format, ...)
 #endif
@@ -47,7 +52,7 @@ namespace Core::Logger {
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 3
 #define CORE_LOG_INFO(format, ...)                                             \
     log(Core::Logger::Level::INFO, __FILE__, __LINE__, "\33[1;37m[INFO] ",     \
-        format, __VA_ARGS__);
+        format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_INFO(format, ...)
 #endif
@@ -55,7 +60,7 @@ namespace Core::Logger {
 #if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 4
 #define CORE_LOG_DEBUG(format, ...)                                            \
     log(Core::Logger::Level::DEBUG, __FILE__, __LINE__, "\33[1;32m[DEBUG] ",   \
-        format, __VA_ARGS__);
+        format __VA_OPT__(, ) __VA_ARGS__);
 #else
 #define CORE_LOG_DEBUG(format, ...)
 #endif