Browse Source

more stuff moved from gaming core, additional error information with
callback

Kajetan Johannes Hammerle 1 year ago
parent
commit
4cf031a4ce

+ 1 - 1
data/Array.h

@@ -50,7 +50,7 @@ namespace Core {
             return N;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 10 - 4
data/ArrayList.h

@@ -65,10 +65,11 @@ namespace Core {
             return begin() + length;
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return T* add(Args&&... args) {
             if(length >= N) {
+                CORE_ERROR(Error::CAPACITY_REACHED);
                 return nullptr;
             }
             return new(begin() + length++) T(Core::forward<Args>(args)...);
@@ -93,10 +94,10 @@ namespace Core {
             length = 0;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool removeBySwap(int index) {
             if(index < 0 || index >= length) {
-                return true;
+                return CORE_ERROR(Error::INVALID_REMOVE_INDEX);
             }
             length--;
             if(index != length) {
@@ -106,7 +107,12 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
+        check_return bool removeLast() {
+            return removeBySwap(length - 1);
+        }
+
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 2 - 2
data/BitArray.cpp

@@ -133,12 +133,12 @@ void Core::BitArray::fill(int value) {
 
 check_return bool Core::BitArray::resize(int newLength, int newBits) {
     if(newLength <= 0 || newBits <= 0) {
-        return true;
+        return CORE_ERROR(Error::NEGATIVE_ARGUMENT);
     }
     int arrayLength = getArrayLength(newLength, newBits);
     int* newData = new int[arrayLength];
     if(newData == nullptr) {
-        return true;
+        return CORE_ERROR(Error::OUT_OF_MEMORY);
     }
     Core::memorySet(newData, 0, arrayLength * CORE_SIZE(int));
 

+ 3 - 3
data/BitArray.h

@@ -17,7 +17,7 @@ namespace Core {
         BitArray& operator=(const BitArray& other) = delete;
         BitArray& operator=(BitArray&& other);
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool copyFrom(const BitArray& other);
 
         BitArray& set(int index, int value);
@@ -29,12 +29,12 @@ namespace Core {
 
         int select(int index) const;
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool resize(int newLength, int newBits);
 
         void fill(int value);
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 2 - 2
data/Components.h

@@ -58,7 +58,7 @@ namespace Core {
             }
         };
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return T* add(Entity e, Args&&... args) {
             int index = components.getLength();
@@ -76,7 +76,7 @@ namespace Core {
             return c;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool remove(Entity e) {
             int* indexP = entityToIndex.search(e);
             if(indexP == nullptr) {

+ 6 - 6
data/HashMap.h

@@ -115,7 +115,7 @@ namespace Core {
         List<NodePointerList> nodePointers;
 
     public:
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool copyFrom(const HashMap& other) {
             HashMap copy;
             for(const auto& e : other) {
@@ -128,7 +128,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool rehash(int minCapacity) {
             if(minCapacity <= nodePointers.getLength()) {
                 return false;
@@ -150,7 +150,7 @@ namespace Core {
             return false;
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return V* tryEmplace(const K& key, Args&&... args) {
             if(rehash(nodes.getLength() + 1)) {
@@ -171,7 +171,7 @@ namespace Core {
             return &(np->data.value);
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename VA>
         check_return V* add(const K& key, VA&& value) {
             if(rehash(nodes.getLength() + 1)) {
@@ -193,7 +193,7 @@ namespace Core {
             return &(np->data.value);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool remove(const K& key) {
             NodePointerList& list = nodePointers[hashIndex(key)];
             for(int i = 0; i < list.getLength(); i++) {
@@ -263,7 +263,7 @@ namespace Core {
             return ConstEntryIterator(nodes.end());
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 4 - 3
data/LinkedList.h

@@ -70,7 +70,7 @@ namespace Core {
             return *this;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool copyFrom(const LinkedList& other) {
             LinkedList copy;
             for(const T& t : other) {
@@ -82,11 +82,12 @@ namespace Core {
             return false;
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return Node* add(Args&&... args) {
             Node* n = new Node(Core::forward<Args>(args)...);
             if(n == nullptr) {
+                CORE_ERROR(Error::OUT_OF_MEMORY);
                 return nullptr;
             }
             length++;
@@ -133,7 +134,7 @@ namespace Core {
             last = nullptr;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 17 - 14
data/List.h

@@ -32,7 +32,7 @@ namespace Core {
             return *this;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool copyFrom(const List& other) {
             List copy;
             copy.data = allocate(other.capacity);
@@ -63,7 +63,7 @@ namespace Core {
             return data + length;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool reserve(int n) {
             if(n <= capacity) {
                 return false;
@@ -81,7 +81,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool shrink() {
             if(length == capacity) {
                 return false;
@@ -99,7 +99,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool resize(int n, const T& t) {
             if(length < n) {
                 if(reserve(n)) {
@@ -117,7 +117,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool resize(int n) {
             if(length < n) {
                 if(reserve(n)) {
@@ -135,7 +135,7 @@ namespace Core {
             return false;
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return T* add(Args&&... args) {
             if(ensureCapacity()) {
@@ -167,10 +167,10 @@ namespace Core {
             length = 0;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool removeBySwap(int index) {
             if(index < 0 || index >= length) {
-                return true;
+                return CORE_ERROR(Error::INVALID_REMOVE_INDEX);
             }
             length--;
             if(index != length) {
@@ -180,10 +180,10 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool remove(int index) {
             if(index < 0 || index >= length) {
-                return true;
+                return CORE_ERROR(Error::INVALID_REMOVE_INDEX);
             }
             length--;
             for(int i = index; i < length; i++) {
@@ -193,12 +193,12 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool removeLast() {
             return removeBySwap(length - 1);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {
@@ -222,9 +222,12 @@ namespace Core {
 
         static T* allocate(int n) {
             if(n <= 0) {
+                CORE_ERROR(Error::NEGATIVE_ARGUMENT);
                 return nullptr;
             }
-            return reinterpret_cast<T*>(new Aligned[static_cast<size_t>(n)]);
+            T* t = reinterpret_cast<T*>(new Aligned[static_cast<size_t>(n)]);
+            CORE_ERROR(Error::OUT_OF_MEMORY, t == nullptr);
+            return t;
         }
 
         void swap(List& other) {
@@ -233,7 +236,7 @@ namespace Core {
             Core::swap(data, other.data);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool ensureCapacity() {
             if(length >= capacity) {
                 return reserve(capacity + Core::Math::max(4, capacity / 4));

+ 5 - 4
data/RingBuffer.h

@@ -51,10 +51,11 @@ namespace Core {
             return *this;
         }
 
-        // returns a nullptr on error
+        // returns a nullptr on error and calls the error callback
         template<typename... Args>
         check_return T* add(Args&&... args) {
             if(getLength() >= N) {
+                CORE_ERROR(Error::CAPACITY_REACHED);
                 return nullptr;
             }
             T* t = new(data + writeIndex) T(Core::forward<Args>(args)...);
@@ -88,10 +89,10 @@ namespace Core {
             values = 0;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool remove() {
             if(!canRemove()) {
-                return true;
+                return CORE_ERROR(Error::NOT_FOUND);
             }
             values--;
             (*this)[0].~T();
@@ -99,7 +100,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 3 - 3
data/Stack.h

@@ -11,7 +11,7 @@ namespace Core {
             S data;
 
         public:
-            // returns a nullptr on error
+            // returns a nullptr on error and calls the error callback
             template<typename... Args>
             check_return T* push(Args&&... args) {
                 return data.add(Core::forward<Args>(args)...);
@@ -21,7 +21,7 @@ namespace Core {
                 data.clear();
             }
 
-            // returns true on error
+            // returns true on error and calls the error callback
             check_return bool pop() {
                 // removeBySwap is checked
                 return data.removeBySwap(data.getLength() - 1);
@@ -39,7 +39,7 @@ namespace Core {
                 return data[data.getLength() - 1];
             }
 
-            // returns true on error
+            // returns true on error and calls the error callback
             template<int L>
             check_return bool toString(ArrayString<L>& s) const {
                 return s.append(data);

+ 1 - 1
math/Box.h

@@ -22,7 +22,7 @@ namespace Core {
         const Vector3& getMin() const;
         const Vector3& getMax() const;
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             return s.append("Box(") || s.append(min) || s.append(", ") ||

+ 96 - 0
math/BufferedValue.h

@@ -0,0 +1,96 @@
+#ifndef CORE_BUFFERED_VALUE_H
+#define CORE_BUFFERED_VALUE_H
+
+#include "math/Math.h"
+
+namespace Core {
+    template<typename T>
+    class BufferedValue {
+        T last;
+        T current;
+
+    public:
+        BufferedValue(const T& t) : last(t), current(t) {
+        }
+
+        void update() {
+            last = current;
+        }
+
+        T get(float lag) const {
+            return Math::interpolate(last, current, lag);
+        }
+
+        template<typename O>
+        BufferedValue& operator=(const O& o) {
+            current = o;
+            return *this;
+        }
+
+        template<typename O>
+        BufferedValue& operator+=(const O& o) {
+            current += o;
+            return *this;
+        }
+
+        template<typename O>
+        BufferedValue& operator-=(const O& o) {
+            current -= o;
+            return *this;
+        }
+
+        template<typename O>
+        BufferedValue& operator*=(const O& o) {
+            current *= o;
+            return *this;
+        }
+
+        template<typename O>
+        BufferedValue& operator/=(const O& o) {
+            current /= o;
+            return *this;
+        }
+
+        template<typename O>
+        auto operator+(const O& o) const {
+            return current + o;
+        }
+
+        template<typename O>
+        auto operator-(const O& o) const {
+            return current - o;
+        }
+
+        template<typename O>
+        auto operator*(const O& o) const {
+            return current * o;
+        }
+
+        template<typename O>
+        auto operator/(const O& o) const {
+            return current / o;
+        }
+
+        auto operator-() const {
+            return -current;
+        }
+
+        auto& operator[](int index) {
+            return current[index];
+        }
+
+        const auto& operator[](int index) const {
+            return current[index];
+        }
+
+        operator T&() {
+            return current;
+        }
+
+        operator const T&() const {
+            return current;
+        }
+    };
+}
+
+#endif

+ 67 - 0
math/Frustum.cpp

@@ -0,0 +1,67 @@
+#include "math/Frustum.h"
+
+#include <math.h>
+
+Core::Frustum::Frustum(float fieldOfView, float nearClip_, float farClip_)
+    : tan(tanf(Core::Math::degreeToRadian(fieldOfView) * 0.5f)),
+      nearClip(nearClip_), farClip(farClip_) {
+    float diff = 1.0f / (nearClip - farClip);
+    projection.set(1, Vector4(0.0f, 1.0f / tan, 0.0f, 0.0f));
+    projection.set(2, Vector4(0.0f, 0.0f, (nearClip + farClip) * diff,
+                              (2.0f * nearClip * farClip) * diff));
+    projection.set(3, Vector4(0.0f, 0.0f, -1.0f, 0.0f));
+}
+
+const Core::Matrix& Core::Frustum::updateProjection(const IntVector2& size) {
+    projection.set(0, Vector4(static_cast<float>(size[1]) /
+                                  (tan * static_cast<float>(size[0])),
+                              0.0f, 0.0f, 0.0f));
+    return projection;
+}
+
+void Core::Frustum::updatePlanes(const Vector3& pos, const Vector3& right,
+                                 const Vector3& up, const Vector3& front,
+                                 const IntVector2& size) {
+    float aspect = static_cast<float>(size[0]) / static_cast<float>(size[1]);
+
+    float hNearHeight = tan * nearClip;
+    float hNearWidth = hNearHeight * aspect;
+
+    float hFarHeight = tan * farClip;
+    float hFarWidth = hFarHeight * aspect;
+
+    Vector3 fCenter = pos + front * farClip;
+    Vector3 fTopLeft = fCenter + (up * hFarHeight) - (right * hFarWidth);
+    Vector3 fTopRight = fCenter + (up * hFarHeight) + (right * hFarWidth);
+    Vector3 fBottomRight = fCenter - (up * hFarHeight) + (right * hFarWidth);
+
+    Vector3 nCenter = pos + front * nearClip;
+    Vector3 nTopLeft = nCenter + (up * hNearHeight) - (right * hNearWidth);
+    Vector3 nBottomLeft = nCenter - (up * hNearHeight) - (right * hNearWidth);
+    Vector3 nBottomRight = nCenter - (up * hNearHeight) + (right * hNearWidth);
+
+    planes[0] = Plane(nBottomRight, nTopLeft, nBottomLeft);     // n plane
+    planes[1] = Plane(fTopRight, fBottomRight, fTopLeft);       // f plane
+    planes[2] = Plane(nBottomRight, nBottomLeft, fBottomRight); // bottom plane
+    planes[3] = Plane(fTopLeft, nTopLeft, fTopRight);           // top plane
+    planes[4] = Plane(nBottomLeft, nTopLeft, fTopLeft);         // left plane
+    planes[5] = Plane(fBottomRight, fTopRight, nBottomRight);   // right plane
+}
+
+bool Core::Frustum::isInside(const Vector3& pos) const {
+    for(const Plane& p : planes) {
+        if(p.getSignedDistance(pos) < 0.0f) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool Core::Frustum::isInside(const Vector3& pos, float radius) const {
+    for(const Plane& p : planes) {
+        if(p.getSignedDistance(pos) < -radius) {
+            return false;
+        }
+    }
+    return true;
+}

+ 38 - 0
math/Frustum.h

@@ -0,0 +1,38 @@
+#ifndef CORE_FRUSTUM_H
+#define CORE_FRUSTUM_H
+
+#include "data/Array.h"
+#include "math/Matrix.h"
+#include "math/Plane.h"
+
+namespace Core {
+    class Frustum final {
+        Matrix projection;
+        Array<Plane, 6> planes;
+
+    public:
+        float tan;
+        float nearClip;
+        float farClip;
+
+        Frustum(float fieldOfView, float nearClip, float farClip);
+        const Matrix& updateProjection(const IntVector2& size);
+        void updatePlanes(const Vector3& pos, const Vector3& right,
+                          const Vector3& up, const Vector3& front,
+                          const IntVector2& size);
+
+        bool isInside(const Vector3& pos) const;
+        bool isInside(const Vector3& pos, float radius) const;
+
+        // returns true on error and calls the error callback
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            return s.append("(tan = ") || s.append(tan) ||
+                   s.append(", nearClip = ") || s.append(nearClip) ||
+                   s.append(", farClip = ") || s.append(farClip) ||
+                   s.append(')');
+        }
+    };
+}
+
+#endif

+ 1 - 1
math/Matrix.h

@@ -37,7 +37,7 @@ namespace Core {
         Matrix& rotateZ(float degrees);
         Matrix& rotate(const Quaternion& q);
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             return s.append('[') || s.append(data[0]) || s.append(", ") ||

+ 51 - 0
math/MatrixStack.h

@@ -0,0 +1,51 @@
+#ifndef CORE_MATRIX_STACK_H
+#define CORE_MATRIX_STACK_H
+
+#include "data/ArrayList.h"
+#include "math/Matrix.h"
+
+namespace Core {
+    template<int N>
+    class MatrixStack final {
+        ArrayList<Matrix, N> stack;
+
+    public:
+        MatrixStack() {
+            (void)stack.add(Matrix());
+        }
+
+        // returns true on error and calls the error callback
+        check_return bool pop() {
+            if(stack.getLength() <= 1) {
+                return CORE_ERROR(Error::NOT_FOUND);
+            }
+            return stack.removeLast();
+        }
+
+        // returns true on error and calls the error callback
+        check_return bool push() {
+            return stack.add(peek()) == nullptr;
+        }
+
+        Matrix& peek() {
+            return stack[stack.getLength() - 1];
+        }
+
+        const Matrix& peek() const {
+            return stack[stack.getLength() - 1];
+        }
+
+        void clear() {
+            stack.clear();
+            (void)stack.add(Matrix());
+        }
+
+        // returns true on error and calls the error callback
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            return s.append(stack);
+        }
+    };
+}
+
+#endif

+ 12 - 0
math/Plane.cpp

@@ -0,0 +1,12 @@
+#include "math/Plane.h"
+
+Core::Plane::Plane() : d(0) {
+}
+
+Core::Plane::Plane(const Vector3& a, const Vector3& b, const Vector3& c)
+    : abc((b - a).cross(c - a).normalize()), d(-abc.dot(b)) {
+}
+
+float Core::Plane::getSignedDistance(const Vector3& v) const {
+    return abc.dot(v) + d;
+}

+ 27 - 0
math/Plane.h

@@ -0,0 +1,27 @@
+#ifndef CORE_PLANE_H
+#define CORE_PLANE_H
+
+#include "math/Vector.h"
+#include "utils/ArrayString.h"
+
+namespace Core {
+    class Plane final {
+        Vector3 abc;
+        float d;
+
+    public:
+        Plane();
+        Plane(const Vector3& a, const Vector3& b, const Vector3& c);
+        float getSignedDistance(const Vector3& v) const;
+
+        // returns true on error and calls the error callback
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            return s.append("(") || s.append(abc[0]) || s.append(" x + ") ||
+                   s.append(abc[1]) || s.append(" y + ") || s.append(abc[2]) ||
+                   s.append(" z + ") || s.append(d) || s.append(')');
+        }
+    };
+}
+
+#endif

+ 1 - 1
math/Quaternion.h

@@ -18,7 +18,7 @@ namespace Core {
         Quaternion operator*(const Quaternion& other) const;
         Vector3 operator*(const Vector3& v) const;
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             return s.append("(") || s.append(xyz[0]) || s.append(" i + ") ||

+ 1 - 1
math/Vector.h

@@ -169,7 +169,7 @@ namespace Core {
             return cast;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             if(s.append("[")) {

+ 49 - 0
math/View.cpp

@@ -0,0 +1,49 @@
+#include "math/View.h"
+
+void Core::View::updateDirections(float lengthAngle, float widthAngle) {
+    back.setAngles(lengthAngle, widthAngle);
+    right = back.cross(Vector3(0.0f, 1.0f, 0.0f));
+    right.normalize();
+    up = right.cross(back);
+    up.normalize();
+    back = -back;
+}
+
+void Core::View::updateDirections(const Quaternion& q) {
+    up = q * Vector3(0.0f, 1.0f, 0.0f);
+    back = q * Vector3(-1.0f, 0.0f, 0.0f);
+    right = up.cross(back);
+    right.normalize();
+}
+
+const Core::Matrix& Core::View::updateMatrix(const Vector3& pos) {
+    view.set(0, Vector4(right[0], right[1], right[2], right.dot(-pos)));
+    view.set(1, Vector4(up[0], up[1], up[2], up.dot(-pos)));
+    view.set(2, Vector4(back[0], back[1], back[2], back.dot(-pos)));
+    view.set(3, Vector4(0.0f, 0.0f, 0.0f, 1.0f));
+    return view;
+}
+
+Core::Vector3 Core::View::getUp() const {
+    return up;
+}
+
+Core::Vector3 Core::View::getDown() const {
+    return -up;
+}
+
+Core::Vector3 Core::View::getLeft() const {
+    return -right;
+}
+
+Core::Vector3 Core::View::getRight() const {
+    return right;
+}
+
+Core::Vector3 Core::View::getFront() const {
+    return -back;
+}
+
+Core::Vector3 Core::View::getBack() const {
+    return back;
+}

+ 27 - 0
math/View.h

@@ -0,0 +1,27 @@
+#ifndef CORE_VIEW_H
+#define CORE_VIEW_H
+
+#include "math/Matrix.h"
+
+namespace Core {
+    class View final {
+        Matrix view;
+        Vector3 right;
+        Vector3 up;
+        Vector3 back;
+
+    public:
+        void updateDirections(float lengthAngle, float widthAngle);
+        void updateDirections(const Quaternion& q);
+        const Matrix& updateMatrix(const Vector3& pos);
+
+        Vector3 getUp() const;
+        Vector3 getDown() const;
+        Vector3 getLeft() const;
+        Vector3 getRight() const;
+        Vector3 getFront() const;
+        Vector3 getBack() const;
+    };
+}
+
+#endif

+ 8 - 0
meson.build

@@ -10,6 +10,9 @@ src = [
     'math/Quaternion.cpp',
     'math/Matrix.cpp',
     'math/Box.cpp',
+    'math/Plane.cpp',
+    'math/Frustum.cpp',
+    'math/View.cpp',
 ]
 
 src_tests = [
@@ -32,6 +35,11 @@ src_tests = [
     'tests/QuaternionTests.cpp',
     'tests/MatrixTests.cpp',
     'tests/BoxTests.cpp',
+    'tests/BufferedValueTests.cpp',
+    'tests/PlaneTests.cpp',
+    'tests/FrustumTests.cpp',
+    'tests/ViewTests.cpp',
+    'tests/MatrixStackTests.cpp',
 ]
 
 compiler = meson.get_compiler('cpp')

+ 21 - 0
test/Main.cpp

@@ -6,18 +6,23 @@
 #include "tests/ArrayTests.h"
 #include "tests/BitArrayTests.h"
 #include "tests/BoxTests.h"
+#include "tests/BufferedValueTests.h"
 #include "tests/ComponentsTests.h"
+#include "tests/FrustumTests.h"
 #include "tests/HashMapTests.h"
 #include "tests/LinkedListTests.h"
 #include "tests/ListTests.h"
 #include "tests/MathTests.h"
+#include "tests/MatrixStackTests.h"
 #include "tests/MatrixTests.h"
+#include "tests/PlaneTests.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 "tests/ViewTests.h"
 #include "utils/Utility.h"
 
 static void onExit(int code, void* data) {
@@ -25,24 +30,40 @@ static void onExit(int code, void* data) {
     printf("Hello from exit %d: 0x%x\n", code, i);
 }
 
+static void onError(const char* file, int line, Core::Error e, void* data) {
+    if(e == Core::Error::CAPACITY_REACHED ||
+       e == Core::Error::INVALID_REMOVE_INDEX) {
+        return;
+    }
+    (void)data;
+    CORE_LOG_ERROR("Error in #:# - #", file, line, static_cast<int>(e));
+}
+
 int main() {
+    Core::setErrorHandler(onError, nullptr);
+
     Core::ArrayListTests::test();
     Core::ArrayStringTests::test();
     Core::ArrayTests::test();
     Core::BitArrayTests::test();
     Core::BoxTests::test();
+    Core::BufferedValueTests::test();
     Core::ComponentsTests::test();
+    Core::FrustumTests::test();
     Core::HashMapTests::test();
     Core::LinkedListTests::test();
     Core::ListTests::test();
     Core::MathTests::test();
+    Core::MatrixStackTests::test();
     Core::MatrixTests::test();
+    Core::PlaneTests::test();
     Core::QuaternionTests::test();
     Core::RingBufferTests::test();
     Core::StackTests::test();
     Core::UniquePointerTests::test();
     Core::UtilityTests::test();
     Core::VectorTests::test();
+    Core::ViewTests::test();
 
     Core::Test::finalize();
 

+ 14 - 0
test/Test.h

@@ -2,6 +2,7 @@
 #define CORE_TEST_H
 
 #include "data/HashMap.h"
+#include "math/Vector.h"
 #include "utils/Logger.h"
 
 namespace Core::Test {
@@ -56,6 +57,16 @@ namespace Core::Test {
 
         void checkFloat(const char* file, int line, float wanted, float actual,
                         float error);
+
+        template<int N, typename T>
+        void checkVector(const char* file, int line,
+                         const Core::Vector<N, T>& wanted,
+                         const Core::Vector<N, T>& actual, float error) {
+            for(int i = 0; i < N; i++) {
+                checkFloat(file, line, static_cast<float>(wanted[i]),
+                           static_cast<float>(actual[i]), error);
+            }
+        }
     }
     void finalize();
 }
@@ -71,5 +82,8 @@ namespace Core::Test {
 #define CORE_TEST_NOT_NULL(actual) CORE_TEST_EQUAL(true, actual != nullptr);
 #define CORE_TEST_FLOAT(wanted, actual, error)                                 \
     Core::Test::Internal::checkFloat(__FILE__, __LINE__, wanted, actual, error);
+#define CORE_TEST_VECTOR(wanted, actual)                                       \
+    Core::Test::Internal::checkVector(__FILE__, __LINE__, wanted, actual,      \
+                                      0.0001f);
 
 #endif

+ 90 - 0
tests/BufferedValueTests.cpp

@@ -0,0 +1,90 @@
+#include "tests/BufferedValueTests.h"
+
+#include "math/BufferedValue.h"
+#include "math/Vector.h"
+#include "test/Test.h"
+
+const float eps = 0.0001f;
+
+static void testInit() {
+    Core::BufferedValue<float> b = 5.0f;
+    CORE_TEST_FLOAT(5.0f, b.get(0.0f), eps);
+    CORE_TEST_FLOAT(5.0f, b.get(0.5f), eps);
+    CORE_TEST_FLOAT(5.0f, b.get(1.0f), eps);
+    CORE_TEST_FLOAT(5.0f, b, eps);
+}
+
+static void testInterpolate() {
+    Core::BufferedValue<float> b = 5.0f;
+    b = 7.0f;
+    CORE_TEST_FLOAT(5.0f, b.get(0.0f), eps);
+    CORE_TEST_FLOAT(6.0f, b.get(0.5f), eps);
+    CORE_TEST_FLOAT(7.0f, b.get(1.0f), eps);
+    CORE_TEST_FLOAT(7.0f, b, eps);
+}
+
+static void testUpdate() {
+    Core::BufferedValue<float> b = 5.0f;
+    b = 7.0f;
+    b.update();
+    CORE_TEST_FLOAT(7.0f, b.get(0.0f), eps);
+    CORE_TEST_FLOAT(7.0f, b.get(0.5f), eps);
+    CORE_TEST_FLOAT(7.0f, b.get(1.0f), eps);
+    CORE_TEST_FLOAT(7.0f, b, eps);
+}
+
+static void testCalculate() {
+    Core::BufferedValue<float> b = 5.0f;
+    b = 7.0f;
+    b += 3.0f;
+    CORE_TEST_FLOAT(5.0f, b.get(0.0f), eps);
+    CORE_TEST_FLOAT(7.5f, b.get(0.5f), eps);
+    CORE_TEST_FLOAT(10.0f, b.get(1.0f), eps);
+    CORE_TEST_FLOAT(10.0f, b, eps);
+    CORE_TEST_FLOAT(12.0f, b + 2.0f, eps);
+}
+
+static void testVector() {
+    Core::Vector2 base(5.0f, 6.0f);
+    Core::BufferedValue<Core::Vector2> b = base;
+
+    CORE_TEST_VECTOR(base, b.get(1.0f));
+    b = Core::Vector2(7.0f, 5.0);
+    CORE_TEST_VECTOR(Core::Vector2(7.0f, 5.0f), b.get(1.0f));
+    b += Core::Vector2(1.0f, 1.0f);
+    CORE_TEST_VECTOR(Core::Vector2(8.0f, 6.0f), b.get(1.0f));
+    b -= Core::Vector2(1.0f, 1.0f);
+    CORE_TEST_VECTOR(Core::Vector2(7.0f, 5.0f), b.get(1.0f));
+    b *= Core::Vector2(2.0f, 2.0f);
+    CORE_TEST_VECTOR(Core::Vector2(14.0f, 10.0f), b.get(1.0f));
+    b /= Core::Vector2(0.5f, 0.5f);
+    CORE_TEST_VECTOR(Core::Vector2(28.0f, 20.0f), b.get(1.0f));
+    b = b + Core::Vector2(1.0f, 1.0f);
+    CORE_TEST_VECTOR(Core::Vector2(29.0f, 21.0f), b.get(1.0f));
+    b = b - Core::Vector2(1.0f, 1.0f);
+    CORE_TEST_VECTOR(Core::Vector2(28.0f, 20.0f), b.get(1.0f));
+    b = b * Core::Vector2(2.0f, 2.0f);
+    CORE_TEST_VECTOR(Core::Vector2(56.0f, 40.0f), b.get(1.0f));
+    b = b / Core::Vector2(0.5f, 0.5f);
+    CORE_TEST_VECTOR(Core::Vector2(112.0f, 80.0f), b.get(1.0f));
+    b = Core::Vector2(1.0f, 1.0f) + b;
+    CORE_TEST_VECTOR(Core::Vector2(113.0f, 81.0f), b.get(1.0f));
+    b = Core::Vector2(1.0f, 1.0f) - b;
+    CORE_TEST_VECTOR(Core::Vector2(-112.0f, -80.0f), b.get(1.0f));
+    b = Core::Vector2(2.0f, 2.0f) * b;
+    CORE_TEST_VECTOR(Core::Vector2(-224.0f, -160.0f), b.get(1.0f));
+    b = Core::Vector2(224.0f, 160.0f) / b;
+    CORE_TEST_VECTOR(Core::Vector2(-1.0f, -1.0f), b.get(1.0f));
+    b = -b;
+    CORE_TEST_VECTOR(Core::Vector2(1.0f, 1.0f), b.get(1.0f));
+    b[0] += 3;
+    CORE_TEST_VECTOR(Core::Vector2(4.0f, 1.0f), b.get(1.0f));
+}
+
+void Core::BufferedValueTests::test() {
+    testInit();
+    testInterpolate();
+    testUpdate();
+    testCalculate();
+    testVector();
+}

+ 8 - 0
tests/BufferedValueTests.h

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

+ 55 - 0
tests/FrustumTests.cpp

@@ -0,0 +1,55 @@
+#include "tests/FrustumTests.h"
+
+#include "math/Frustum.h"
+#include "test/Test.h"
+
+using V3 = Core::Vector3;
+
+static void testToString() {
+    Core::Frustum f(60.0f, 0.1f, 1000.0f);
+    CORE_TEST_STRING("(tan = 0.58, nearClip = 0.10, farClip = 1000.00)", f);
+}
+
+static void testPointIsInside() {
+    Core::IntVector2 size(200, 100);
+    Core::Frustum f(60.0f, 0.1f, 1000.0f);
+    f.updatePlanes(V3(0.0f, 0.0f, 0.0f), V3(1.0f, 0.0f, 0.0f),
+                   V3(0.0f, 1.0f, 0.0f), V3(0.0f, 0.0f, 1.0f), size);
+
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 0.0f, 5.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 0.0f, 1004.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 0.0f, -5.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 50.0f, 5.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, -50.0f, 5.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(50.0f, 0.0f, 5.0f)));
+    CORE_TEST_FALSE(f.isInside(V3(-50.0f, 0.0f, 5.0f)));
+}
+
+static void testSphereIsInside() {
+    Core::IntVector2 size(200, 100);
+    Core::Frustum f(60.0f, 0.1f, 1000.0f);
+    f.updatePlanes(V3(0.0f, 0.0f, 0.0f), V3(1.0f, 0.0f, 0.0f),
+                   V3(0.0f, 1.0f, 0.0f), V3(0.0f, 0.0f, 1.0f), size);
+
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 0.0f, 5.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 0.0f, 1004.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 0.0f, -5.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, 50.0f, 5.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(0.0f, -50.0f, 5.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(50.0f, 0.0f, 5.0f), 3.0f));
+    CORE_TEST_FALSE(f.isInside(V3(-50.0f, 0.0f, 5.0f), 3.0f));
+
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 0.0f, 5.0f), 3.0f));
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 0.0f, 1004.0f), 50.0f));
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 0.0f, -5.0f), 50.0f));
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, 50.0f, 5.0f), 50.0f));
+    CORE_TEST_TRUE(f.isInside(V3(0.0f, -50.0f, 5.0f), 50.0f));
+    CORE_TEST_TRUE(f.isInside(V3(50.0f, 0.0f, 5.0f), 50.0f));
+    CORE_TEST_TRUE(f.isInside(V3(-50.0f, 0.0f, 5.0f), 50.0f));
+}
+
+void Core::FrustumTests::test() {
+    testToString();
+    testPointIsInside();
+    testSphereIsInside();
+}

+ 8 - 0
tests/FrustumTests.h

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

+ 110 - 0
tests/MatrixStackTests.cpp

@@ -0,0 +1,110 @@
+#include "tests/MatrixStackTests.h"
+
+#include "math/MatrixStack.h"
+#include "test/Test.h"
+
+using Matrices = Core::MatrixStack<5>;
+
+static void testInit() {
+    Core::Matrix m;
+    Matrices stack;
+    for(int i = 0; i < 16; i++) {
+        CORE_TEST_FLOAT(m.getValues()[i], stack.peek().getValues()[i], 0.0f);
+    }
+}
+
+static void testPop() {
+    Matrices stack;
+    CORE_TEST_FALSE(stack.push());
+    stack.peek().scale(2.0f);
+    CORE_TEST_FALSE(stack.pop());
+    Core::Matrix m;
+    for(int i = 0; i < 16; i++) {
+        CORE_TEST_FLOAT(m.getValues()[i], stack.peek().getValues()[i], 0.0f);
+    }
+}
+
+static void testPush() {
+    Matrices stack;
+    for(int i = 0; i < 4; i++) {
+        CORE_TEST_FALSE(stack.push());
+    }
+    for(int i = 0; i < 1000000; i++) {
+        CORE_TEST_TRUE(stack.push());
+    }
+}
+
+static void testClear() {
+    Matrices stack;
+    stack.peek().scale(2.0f);
+    CORE_TEST_FALSE(stack.push());
+    stack.peek().scale(2.0f);
+    stack.clear();
+    Core::Matrix m;
+    for(int i = 0; i < 16; i++) {
+        CORE_TEST_FLOAT(m.getValues()[i], stack.peek().getValues()[i], 0.0f);
+    }
+}
+
+static void testToString1() {
+    Matrices stack;
+    stack.peek().scale(2.0f);
+    CORE_TEST_FALSE(stack.push());
+    stack.peek().scale(3.0f);
+    CORE_TEST_FALSE(stack.push());
+    stack.peek().scale(4.0f);
+    CORE_TEST_STRING("["
+                     "[[2.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 2.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 2.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]], "
+                     "[[6.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 6.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 6.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]], "
+                     "[[24.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 24.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 24.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]]"
+                     "]",
+                     stack);
+}
+
+static void testToString2() {
+    Matrices stack;
+    stack.peek().scale(2.0f);
+    CORE_TEST_FALSE(stack.push());
+    stack.peek().scale(3.0f);
+    CORE_TEST_STRING("["
+                     "[[2.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 2.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 2.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]], "
+                     "[[6.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 6.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 6.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]]"
+                     "]",
+                     stack);
+}
+
+static void testToString3() {
+    Matrices stack;
+    CORE_TEST_STRING("["
+                     "[[1.00, 0.00, 0.00, 0.00], "
+                     "[0.00, 1.00, 0.00, 0.00], "
+                     "[0.00, 0.00, 1.00, 0.00], "
+                     "[0.00, 0.00, 0.00, 1.00]]"
+                     "]",
+                     stack);
+}
+
+void Core::MatrixStackTests::test() {
+    testInit();
+    testPop();
+    testPush();
+    testClear();
+    testToString1();
+    testToString2();
+    testToString3();
+}

+ 8 - 0
tests/MatrixStackTests.h

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

+ 33 - 59
tests/MatrixTests.cpp

@@ -3,16 +3,7 @@
 #include "math/Matrix.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);
-    }
-}
+using V3 = Core::Vector3;
 
 static void testInit() {
     Core::Matrix m;
@@ -48,44 +39,38 @@ static void testTranspose() {
 
 static void testScale() {
     Core::Matrix m;
-    m.scale(Core::Vector3(2.0f, 3.0f, 4.0f));
-    compareVectors(Core::Vector3(-8.0f, 18.0f, 28.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    m.scale(V3(2.0f, 3.0f, 4.0f));
+    CORE_TEST_VECTOR(V3(-8.0f, 18.0f, 28.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testUniformScale() {
     Core::Matrix m;
     m.scale(2.0f);
-    compareVectors(Core::Vector3(-8.0f, 12.0f, 14.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    CORE_TEST_VECTOR(V3(-8.0f, 12.0f, 14.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testTranslateX() {
     Core::Matrix m;
     m.translateX(5.0f);
-    compareVectors(Core::Vector3(1.0f, 6.0f, 7.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    CORE_TEST_VECTOR(V3(1.0f, 6.0f, 7.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testTranslateY() {
     Core::Matrix m;
     m.translateY(6.0f);
-    compareVectors(Core::Vector3(-4.0f, 12.0f, 7.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    CORE_TEST_VECTOR(V3(-4.0f, 12.0f, 7.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testTranslateZ() {
     Core::Matrix m;
     m.translateZ(7.0f);
-    compareVectors(Core::Vector3(-4.0f, 6.0f, 14.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    CORE_TEST_VECTOR(V3(-4.0f, 6.0f, 14.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testTranslate() {
     Core::Matrix m;
-    m.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
-    compareVectors(Core::Vector3(-3.0f, 8.0f, 10.0f),
-                   m * Core::Vector3(-4.0f, 6.0f, 7.0f));
+    m.translate(V3(1.0f, 2.0f, 3.0f));
+    CORE_TEST_VECTOR(V3(-3.0f, 8.0f, 10.0f), m * V3(-4.0f, 6.0f, 7.0f));
 }
 
 static void testCombination() {
@@ -94,61 +79,50 @@ static void testCombination() {
     m.translateX(1.0f);
     m.translateY(2.0f);
     m.translateZ(3.0f);
-    m.translate(Core::Vector3(-4.0f, 2.0f, 3.0f));
-    m.scale(Core::Vector3(2.0f, 3.0f, 4.0f));
+    m.translate(V3(-4.0f, 2.0f, 3.0f));
+    m.scale(V3(2.0f, 3.0f, 4.0f));
     m.scale(0.5f);
-    compareVectors(Core::Vector3(-1.0f, 9.0f, 16.0f),
-                   m * Core::Vector3(1.0f, 1.0f, 1.0f));
+    CORE_TEST_VECTOR(V3(-1.0f, 9.0f, 16.0f), m * V3(1.0f, 1.0f, 1.0f));
 }
 
 static void testMatrixCombination() {
     Core::Matrix a;
     a.scale(2.0f);
-    a.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
+    a.translate(V3(1.0f, 2.0f, 3.0f));
 
     Core::Matrix b;
     b.scale(3.0f);
-    b.translate(Core::Vector3(1.0f, 1.0f, 1.0f));
+    b.translate(V3(1.0f, 1.0f, 1.0f));
 
     Core::Matrix c;
-    c.translate(Core::Vector3(-1.0f, -2.0f, -3.0f));
+    c.translate(V3(-1.0f, -2.0f, -3.0f));
     c *= b * a;
 
-    compareVectors(Core::Vector3(9.0f, 11.0f, 13.0f),
-                   c * Core::Vector3(1.0f, 1.0f, 1.0f));
+    CORE_TEST_VECTOR(V3(9.0f, 11.0f, 13.0f), c * V3(1.0f, 1.0f, 1.0f));
 }
 
 static void testRotateX() {
     Core::Matrix m;
     m.rotateX(90);
-    compareVectors(Core::Vector3(1.0f, 0.0f, 0.0f),
-                   m * Core::Vector3(1.0f, 0.0f, 0.0f));
-    compareVectors(Core::Vector3(0.0f, 0.0f, 1.0f),
-                   m * Core::Vector3(0.0f, 1.0f, 0.0f));
-    compareVectors(Core::Vector3(0.0f, -1.0f, 0.0f),
-                   m * Core::Vector3(0.0f, 0.0f, 1.0f));
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), m * V3(1.0f, 0.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), m * V3(0.0f, 1.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), m * V3(0.0f, 0.0f, 1.0f));
 }
 
 static void testRotateY() {
     Core::Matrix m;
     m.rotateY(90);
-    compareVectors(Core::Vector3(0.0f, 0.0f, -1.0f),
-                   m * Core::Vector3(1.0f, 0.0f, 0.0f));
-    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
-                   m * Core::Vector3(0.0f, 1.0f, 0.0f));
-    compareVectors(Core::Vector3(1.0f, 0.0f, 0.0f),
-                   m * Core::Vector3(0.0f, 0.0f, 1.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f), m * V3(1.0f, 0.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), m * V3(0.0f, 1.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), m * V3(0.0f, 0.0f, 1.0f));
 }
 
 static void testRotateZ() {
     Core::Matrix m;
     m.rotateZ(90);
-    compareVectors(Core::Vector3(0.0f, 1.0f, 0.0f),
-                   m * Core::Vector3(1.0f, 0.0f, 0.0f));
-    compareVectors(Core::Vector3(-1.0f, 0.0f, 0.0f),
-                   m * Core::Vector3(0.0f, 1.0f, 0.0f));
-    compareVectors(Core::Vector3(0.0f, 0.0f, 1.0f),
-                   m * Core::Vector3(0.0f, 0.0f, 1.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), m * V3(1.0f, 0.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f), m * V3(0.0f, 1.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), m * V3(0.0f, 0.0f, 1.0f));
 }
 
 static void testToString() {
@@ -166,22 +140,22 @@ static void testToString() {
 }
 
 static void testQuaternionMatrix() {
-    Core::Quaternion q1(Core::Vector3(1.0f, 0.0f, 0.0f), 48.0f);
-    Core::Quaternion q2(Core::Vector3(0.0f, 1.0f, 0.0f), 52.0f);
-    Core::Quaternion q3(Core::Vector3(0.0f, 0.0f, 1.0f), 60.0f);
+    Core::Quaternion q1(V3(1.0f, 0.0f, 0.0f), 48.0f);
+    Core::Quaternion q2(V3(0.0f, 1.0f, 0.0f), 52.0f);
+    Core::Quaternion q3(V3(0.0f, 0.0f, 1.0f), 60.0f);
 
     Core::Matrix m;
-    m.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
+    m.translate(V3(1.0f, 2.0f, 3.0f));
     m.rotate(q1).rotate(q2).rotate(q3);
-    m.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
+    m.translate(V3(1.0f, 2.0f, 3.0f));
 
     Core::Matrix check;
-    check.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
+    check.translate(V3(1.0f, 2.0f, 3.0f));
     check.rotateX(48.0f).rotateY(52.0f).rotateZ(60.0f);
-    check.translate(Core::Vector3(1.0f, 2.0f, 3.0f));
+    check.translate(V3(1.0f, 2.0f, 3.0f));
 
     for(int i = 0; i < 16; i++) {
-        CORE_TEST_FLOAT(check.getValues()[i], m.getValues()[i], eps);
+        CORE_TEST_FLOAT(check.getValues()[i], m.getValues()[i], 0.0001f);
     }
 }
 

+ 39 - 0
tests/PlaneTests.cpp

@@ -0,0 +1,39 @@
+#include "tests/PlaneTests.h"
+
+#include "math/Plane.h"
+#include "test/Test.h"
+
+const float eps = 0.0001f;
+
+using V3 = Core::Vector3;
+
+static void testToString1() {
+    Core::Plane p;
+    CORE_TEST_STRING("(0.00 x + 0.00 y + 0.00 z + 0.00)", p);
+}
+
+static void testToString2() {
+    Core::Plane p(V3(3.0f, 6.0f, 8.0f), V3(7.0f, 6.0f, 2.0f),
+                  V3(4.0f, 4.0f, 4.0f));
+    CORE_TEST_STRING("(-0.68 x + 0.57 y + -0.46 z + 2.28)", p);
+}
+
+static void testSignedDistance() {
+    V3 a(3.0f, 6.0f, 8.0f);
+    V3 b(7.0f, 6.0f, 2.0f);
+    V3 c(4.0f, 4.0f, 4.0f);
+    Core::Plane p(a, b, c);
+    CORE_TEST_FLOAT(0.0f, p.getSignedDistance(a), eps);
+    CORE_TEST_FLOAT(0.0f, p.getSignedDistance(b), eps);
+    CORE_TEST_FLOAT(0.0f, p.getSignedDistance(c), eps);
+    CORE_TEST_FLOAT(-1.13960576f, p.getSignedDistance(V3(5.0f, 8.0f, 10.0f)),
+                    eps);
+    CORE_TEST_FLOAT(0.911684612f, p.getSignedDistance(V3(3.0f, 2.0f, 1.0f)),
+                    eps);
+}
+
+void Core::PlaneTests::test() {
+    testToString1();
+    testToString2();
+    testSignedDistance();
+}

+ 8 - 0
tests/PlaneTests.h

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

+ 25 - 35
tests/QuaternionTests.cpp

@@ -3,15 +3,7 @@
 #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);
-    }
-}
+using V3 = Core::Vector3;
 
 static void testInit() {
     Core::Quaternion q;
@@ -19,56 +11,54 @@ static void testInit() {
 }
 
 static void testAxisAndDegreesInit() {
-    Core::Quaternion q(Core::Vector3(1.0f, 2.0f, 3.0f), 142.0f);
+    Core::Quaternion q(V3(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 q1(V3(2.0f, 5.0f, 7.0f), 130.0f);
+    Core::Quaternion q2(V3(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 q1(V3(2.0f, 5.0f, 7.0f), 50.0f);
+    Core::Quaternion q2(V3(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);
+    CORE_TEST_STRING(Core::Quaternion(V3(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 q1(V3(2.0f, 5.0f, 7.0f), 50.0f);
+    Core::Quaternion q2(V3(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);
+    CORE_TEST_STRING(Core::Quaternion(V3(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::Quaternion q1(V3(1.0f, 0.0f, 0.0f), 90.0f);
+    Core::Quaternion q2(V3(0.0f, 1.0f, 0.0f), 90.0f);
+    Core::Quaternion q3(V3(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);
+    V3 v1(1.0f, 0.0f, 0.0f);
+    V3 v2(0.0f, 1.0f, 0.0f);
+    V3 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);
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), q1 * v1);
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), q1 * v2);
+    CORE_TEST_VECTOR(V3(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);
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f), q2 * v1);
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), q2 * v2);
+    CORE_TEST_VECTOR(V3(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);
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), q3 * v1);
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f), q3 * v2);
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), q3 * v3);
 }
 
 void Core::QuaternionTests::test() {

+ 97 - 152
tests/VectorTests.cpp

@@ -5,19 +5,13 @@
 
 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);
-    }
-}
+using V3 = Core::Vector3;
+using I3 = Core::IntVector3;
 
 static void testInitAndRead() {
-    Core::Vector3 v1;
+    V3 v1;
     Core::Vector2 v2(1.0f, 2.0f);
-    Core::Vector3 v3(3.0f, 4.0f, 5.0f);
+    V3 v3(3.0f, 4.0f, 5.0f);
     Core::Vector4 v4(6.0f, 7.0f, 8.0f, 9.0f);
     CORE_TEST_FLOAT(0.0f, v1[0], 0.0f);
     CORE_TEST_FLOAT(0.0f, v1[0], 0.0f);
@@ -35,213 +29,164 @@ static void testInitAndRead() {
 
 static void testSetAngles() {
     float root = Core::Math::squareRoot(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));
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), V3().setAngles(0.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(root, 0.0f, -root), V3().setAngles(45.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f), V3().setAngles(90.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(-root, 0.0f, -root), V3().setAngles(135.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f), V3().setAngles(180.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(-root, 0.0f, root), V3().setAngles(225.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), V3().setAngles(270.0f, 0.0f));
+    CORE_TEST_VECTOR(V3(root, 0.0f, root), V3().setAngles(315.0f, 0.0f));
+
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), V3().setAngles(0.0f, 90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), V3().setAngles(90.0f, 90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), V3().setAngles(180.0f, 90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), V3().setAngles(270.0f, 90.0f));
+
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), V3().setAngles(0.0f, -90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), V3().setAngles(90.0f, -90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), V3().setAngles(180.0f, -90.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), V3().setAngles(270.0f, -90.0f));
+
+    CORE_TEST_VECTOR(V3(root, root, 0.0f), V3().setAngles(0.0f, 45.0f));
+    CORE_TEST_VECTOR(V3(0.0f, root, -root), V3().setAngles(90.0f, 45.0f));
+    CORE_TEST_VECTOR(V3(-root, root, 0.0f), V3().setAngles(180.0f, 45.0f));
+    CORE_TEST_VECTOR(V3(0.0f, root, root), V3().setAngles(270.0f, 45.0f));
+
+    CORE_TEST_VECTOR(V3(root, -root, 0.0f), V3().setAngles(0.0f, -45.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -root, -root), V3().setAngles(90.0f, -45.0f));
+    CORE_TEST_VECTOR(V3(-root, -root, 0.0f), V3().setAngles(180.0f, -45.0f));
+    CORE_TEST_VECTOR(V3(0.0f, -root, root), V3().setAngles(270.0f, -45.0f));
+
+    CORE_TEST_VECTOR(V3(0.5f, root, -0.5f), V3().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)));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f),
+                     V3(1.0f, 0.0f, 0.0f).cross(V3(0.0f, 1.0f, 0.0f)));
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f),
+                     V3(1.0f, 0.0f, 0.0f).cross(V3(0.0f, 0.0f, 1.0f)));
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f),
+                     V3(0.0f, 1.0f, 0.0f).cross(V3(1.0f, 0.0f, 0.0f)));
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f),
+                     V3(0.0f, 1.0f, 0.0f).cross(V3(0.0f, 0.0f, 1.0f)));
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f),
+                     V3(0.0f, 0.0f, 1.0f).cross(V3(1.0f, 0.0f, 0.0f)));
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f),
+                     V3(0.0f, 0.0f, 1.0f).cross(V3(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);
+    V3 v;
+    v += V3(1.0f, 2.0f, 3.0f);
+    CORE_TEST_VECTOR(V3(1.0f, 2.0f, 3.0f), v);
+    v += V3(2.0f, 3.0f, 4.0f);
+    CORE_TEST_VECTOR(V3(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));
+    CORE_TEST_VECTOR(V3(1.0f, 2.0f, 3.0f), V3() + V3(1.0f, 2.0f, 3.0f));
+    CORE_TEST_VECTOR(V3(3.0f, 5.0f, 7.0f),
+                     V3(1.0f, 2.0f, 3.0f) + V3(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);
+    V3 v;
+    v -= V3(1.0f, 2.0f, 3.0f);
+    CORE_TEST_VECTOR(V3(-1.0f, -2.0f, -3.0f), v);
+    v -= V3(2.0f, 3.0f, 4.0f);
+    CORE_TEST_VECTOR(V3(-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));
+    CORE_TEST_VECTOR(V3(1.0f, 2.0f, 3.0f), V3() - V3(-1.0f, -2.0f, -3.0f));
+    CORE_TEST_VECTOR(V3(-1.0f, -1.0f, -1.0f),
+                     V3(1.0f, 2.0f, 3.0f) - V3(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));
+    CORE_TEST_VECTOR(V3(-1.0f, 2.0f, 3.0f), -V3(1.0f, -2.0f, -3.0f));
 }
 
 static void testSetMul() {
-    Core::Vector3 v(1.0f, 2.0f, 3.0f);
+    V3 v(1.0f, 2.0f, 3.0f);
     v *= 3.0f;
-    compareVectors(Core::Vector3(3.0f, 6.0f, 9.0f), v);
+    CORE_TEST_VECTOR(V3(3.0f, 6.0f, 9.0f), v);
     v *= -2.0f;
-    compareVectors(Core::Vector3(-6.0f, -12.0f, -18.0f), v);
+    CORE_TEST_VECTOR(V3(-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);
+    CORE_TEST_VECTOR(V3(-3.0f, -6.0f, -9.0f), 3.0f * V3(-1.0f, -2.0f, -3.0f));
+    CORE_TEST_VECTOR(V3(3.0f, 6.0f, 9.0f), V3(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);
+    V3 v(1.0f, 2.0f, 3.0f);
+    v *= V3(2.0f, 1.0f, 3.0f);
+    CORE_TEST_VECTOR(V3(2.0f, 2.0f, 9.0f), v);
+    v *= V3(-3.0f, 4.0f, -2.0f);
+    CORE_TEST_VECTOR(V3(-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));
+    CORE_TEST_VECTOR(V3(-2.0f, -2.0f, -9.0f),
+                     V3(2.0f, 1.0f, 3.0f) * V3(-1.0f, -2.0f, -3.0f));
+    CORE_TEST_VECTOR(V3(2.0f, 2.0f, 9.0f),
+                     V3(1.0f, 2.0f, 3.0f) * V3(2.0f, 1.0f, 3.0f));
 }
 
 static void testSetDiv() {
-    Core::Vector3 v(12.0f, 24.0f, 9.0f);
+    V3 v(12.0f, 24.0f, 9.0f);
     v /= 3.0f;
-    compareVectors(Core::Vector3(4.0f, 8.0f, 3.0f), v);
+    CORE_TEST_VECTOR(V3(4.0f, 8.0f, 3.0f), v);
     v /= -2.0f;
-    compareVectors(Core::Vector3(-2.0f, -4.0f, -1.5f), v);
+    CORE_TEST_VECTOR(V3(-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);
+    CORE_TEST_VECTOR(V3(-1.0f, -2.0f, -3.0f), V3(-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)),
+    CORE_TEST_FLOAT(9.0f, V3(-4.0f, 2.0f, -3.0f).dot(V3(-1.0f, -2.0f, -3.0f)),
+                    eps);
+    CORE_TEST_FLOAT(-22.0f, V3(2.0f, 2.0f, -4.0f).dot(V3(1.0f, -2.0f, 5.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);
+    CORE_TEST_FLOAT(29.0f, V3(-4.0f, 2.0f, -3.0f).squareLength(), eps);
+    CORE_TEST_FLOAT(24.0f, V3(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);
+    CORE_TEST_FLOAT(3.0f, V3(-2.0f, 2.0f, -1.0f).length(), eps);
+    CORE_TEST_FLOAT(7.0f, V3(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);
+    V3 v1(-2.0f, 2.0f, -1.0f);
+    V3 v2 = v1 * (1.0f / 3.0f);
     v1.normalize();
-    compareVectors(v2, v1);
+    CORE_TEST_VECTOR(v2, v1);
 
-    Core::Vector3 v3(6.0f, 2.0f, -3.0f);
-    Core::Vector3 v4 = v3 * (1.0f / 7.0f);
+    V3 v3(6.0f, 2.0f, -3.0f);
+    V3 v4 = v3 * (1.0f / 7.0f);
     v3.normalize();
-    compareVectors(v4, v3);
+    CORE_TEST_VECTOR(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));
+    CORE_TEST_VECTOR(V3(-2.5f, 2.6f, 9.0f).toInt(), I3(-2, 2, 9));
+    CORE_TEST_VECTOR(I3(-2.5f, 2.6f, 9.0f).toFloat(), V3(-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_TEST_FALSE(s.append(V3(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);

+ 33 - 0
tests/ViewTests.cpp

@@ -0,0 +1,33 @@
+#include "tests/ViewTests.h"
+
+#include "math/View.h"
+#include "test/Test.h"
+
+using V3 = Core::Vector3;
+
+static void testFromAngles() {
+    Core::View v;
+    v.updateDirections(0.0f, 0.0f);
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), v.getUp());
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), v.getDown());
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f), v.getLeft());
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), v.getRight());
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), v.getFront());
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f), v.getBack());
+}
+
+static void testFromQuaternion() {
+    Core::View v;
+    v.updateDirections(Core::Quaternion());
+    CORE_TEST_VECTOR(V3(0.0f, 1.0f, 0.0f), v.getUp());
+    CORE_TEST_VECTOR(V3(0.0f, -1.0f, 0.0f), v.getDown());
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, -1.0f), v.getLeft());
+    CORE_TEST_VECTOR(V3(0.0f, 0.0f, 1.0f), v.getRight());
+    CORE_TEST_VECTOR(V3(1.0f, 0.0f, 0.0f), v.getFront());
+    CORE_TEST_VECTOR(V3(-1.0f, 0.0f, 0.0f), v.getBack());
+}
+
+void Core::ViewTests::test() {
+    testFromAngles();
+    testFromQuaternion();
+}

+ 8 - 0
tests/ViewTests.h

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

+ 37 - 36
utils/ArrayString.h

@@ -66,28 +66,28 @@ namespace Core {
             return DATA_LENGTH;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(char c) {
             if(c < 0) {
-                return true;
+                return CORE_ERROR(Error::NEGATIVE_ARGUMENT);
             }
             return appendUnicode(static_cast<u32>(c));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(signed char c) {
             if(c < 0) {
-                return true;
+                return CORE_ERROR(Error::NEGATIVE_ARGUMENT);
             }
             return appendUnicode(static_cast<u32>(c));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(unsigned char c) {
             return appendUnicode(c);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(const char* s) {
             while(true) {
                 u32 u = 0;
@@ -102,93 +102,93 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(const signed char* s) {
             return append(reinterpret_cast<const char*>(s));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(const unsigned char* s) {
             return append(reinterpret_cast<const char*>(s));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(signed short s) {
             return convertAppend(s);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(unsigned short s) {
             return convertAppend(s);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(signed int i) {
             return convertAppend(i);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(unsigned int i) {
             return convertAppend(i);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(signed long l) {
             return convertAppend(l);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(unsigned long l) {
             return convertAppend(l);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(signed long long ll) {
             return convertAppend(ll);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(unsigned long long ll) {
             return convertAppend(ll);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(float f) {
             return convertAppend(static_cast<double>(f));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(double d) {
             return convertAppend(d);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(long double ld) {
             return convertAppend(ld);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool append(bool b) {
             return b ? append("true") : append("false");
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool appendUnicode(u32 c) {
             if(length >= DATA_LENGTH) {
-                return true;
+                return CORE_ERROR(Error::CAPACITY_REACHED);
             }
             data[length++] = c;
             addToHash(c);
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<typename T>
         check_return bool append(const T& t) {
             return t.toString(*this);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<int L>
         check_return bool toString(ArrayString<L>& s) const {
             int l = length; // length changes if &s == this
@@ -210,7 +210,7 @@ namespace Core {
             return hash;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool print() const {
             for(int i = 0; i < length; i++) {
                 u32 c = data[i];
@@ -241,12 +241,12 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool printLine() const {
             return print() || Core::putChar('\n');
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<typename... Args>
         check_return bool format(Args&&... args) {
             ArrayString s;
@@ -258,7 +258,8 @@ namespace Core {
         }
 
     private:
-        static bool printChar(u32 u, u32 shift, u32 a, u32 o) {
+        // returns true on error and calls the error callback
+        check_return static bool printChar(u32 u, u32 shift, u32 a, u32 o) {
             return Core::putChar(static_cast<int>(((u >> shift) & a) | o));
         }
 
@@ -269,7 +270,7 @@ namespace Core {
             return static_cast<u32>(*(s++));
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         static bool readUnicode(u32& u, const char*& s) {
             u = read(s);
             if((u & 0x80) == 0) {
@@ -278,7 +279,7 @@ namespace Core {
             if((u & 0xE0) == 0xC0) {
                 u32 u2 = read(s);
                 if(u2 == 0) {
-                    return true;
+                    return CORE_ERROR(Error::INVALID_CHAR);
                 }
                 u = ((u & 0x1F) << 6) | (u2 & 0x3F);
                 return false;
@@ -286,7 +287,7 @@ namespace Core {
                 u32 u2 = read(s);
                 u32 u3 = read(s);
                 if(u2 == 0 || u3 == 0) {
-                    return true;
+                    return CORE_ERROR(Error::INVALID_CHAR);
                 }
                 u = ((u & 0xF) << 12) | ((u2 & 0x3F) << 6) | (u3 & 0x3F);
                 return false;
@@ -295,20 +296,20 @@ namespace Core {
                 u32 u3 = read(s);
                 u32 u4 = read(s);
                 if(u2 == 0 || u3 == 0 || u4 == 0) {
-                    return true;
+                    return CORE_ERROR(Error::INVALID_CHAR);
                 }
                 u = ((u & 0x07) << 18) | ((u2 & 0x3F) << 12) |
                     ((u3 & 0x3F) << 6) | (u4 & 0x3F);
                 return false;
             }
-            return true;
+            return CORE_ERROR(Error::INVALID_CHAR);
         }
 
         void addToHash(u32 u) {
             hash = static_cast<u32>(2120251889) * hash + static_cast<u32>(u);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<typename T, typename... Args>
         check_return bool formatBuffer(ArrayString& s, int index, const T& t,
                                        Args&&... args) {
@@ -331,7 +332,7 @@ namespace Core {
             return formatBuffer(s, index, Core::forward<Args>(args)...);
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         check_return bool formatBuffer(ArrayString& s, int index) {
             while(index < length) {
                 if(s.appendUnicode(data[index++])) {
@@ -341,7 +342,7 @@ namespace Core {
             return false;
         }
 
-        // returns true on error
+        // returns true on error and calls the error callback
         template<typename T>
         check_return bool convertAppend(T t) {
             constexpr int BUFFER_SIZE = 64;

+ 1 - 0
utils/Logger.h

@@ -14,6 +14,7 @@ namespace Core::Logger {
         if(Core::Logger::level < l) {
             return;
         }
+        file = Core::getFileName(file);
         Core::ArrayString<2048> s;
         if(s.append(start) || s.append("#:# | ") || s.format(file, line)) {
             CORE_EXIT(1);

+ 30 - 4
utils/Utility.cpp

@@ -4,9 +4,23 @@
 #include <stdlib.h>
 #include <string.h>
 
+static Core::ErrorHandler errorHandler = nullptr;
+static void* errorData = nullptr;
 static Core::ExitHandler exitHandler = nullptr;
 static void* exitData = nullptr;
 
+bool Core::handleError(const char* file, int line, Error e, bool check) {
+    if(check && errorHandler != nullptr) {
+        errorHandler(file, line, e, errorData);
+    }
+    return check;
+}
+
+void Core::setErrorHandler(ErrorHandler eh, void* data) {
+    errorHandler = eh;
+    errorData = data;
+}
+
 void* operator new(size_t bytes) noexcept {
     return malloc(bytes);
 }
@@ -59,10 +73,11 @@ void Core::setExitHandler(ExitHandler eh, void* data) {
 #define CORE_TO_STRING(type, cast, format)                                     \
     check_return bool Core::toString(type t, char* buffer, int size) {         \
         if(size < 0) {                                                         \
-            return true;                                                       \
+            return CORE_ERROR(Error::NEGATIVE_ARGUMENT);                       \
         }                                                                      \
-        return snprintf(buffer, static_cast<unsigned int>(size), format,       \
-                        static_cast<cast>(t)) >= size;                         \
+        return CORE_ERROR(Error::CAPACITY_REACHED,                             \
+                          snprintf(buffer, static_cast<unsigned int>(size),    \
+                                   format, static_cast<cast>(t)) >= size);     \
     }
 
 CORE_TO_STRING(signed short, signed short, "%hd")
@@ -78,7 +93,7 @@ CORE_TO_STRING(double, double, "%.2lf")
 CORE_TO_STRING(long double, long double, "%.2Lf")
 
 bool Core::putChar(int c) {
-    return putchar(c) == EOF;
+    return CORE_ERROR(Error::BLOCKED_STDOUT, putchar(c) == EOF);
 }
 
 void Core::memorySet(void* p, int c, int n) {
@@ -93,4 +108,15 @@ void Core::memoryCopy(void* dest, const void* src, int n) {
         return;
     }
     memcpy(dest, src, static_cast<unsigned int>(n));
+}
+
+const char* Core::getFileName(const char* path) {
+    int end = 0;
+    while(path[end] != '\0') {
+        end++;
+    }
+    while(end > 0 && path[end - 1] != '/') {
+        end--;
+    }
+    return path + end;
 }

+ 33 - 15
utils/Utility.h

@@ -6,6 +6,24 @@
 #define CORE_SIZE(t) static_cast<int>(sizeof(t))
 
 namespace Core {
+    enum class Error {
+        NONE,
+        NEGATIVE_ARGUMENT,
+        CAPACITY_REACHED,
+        BLOCKED_STDOUT,
+        OUT_OF_MEMORY,
+        INVALID_CHAR,
+        NOT_FOUND,
+        INVALID_INDEX,
+        INVALID_REMOVE_INDEX
+    };
+
+    using ErrorHandler = void (*)(const char*, int, Error, void*);
+    bool handleError(const char* file, int line, Error e, bool check = true);
+    void setErrorHandler(ErrorHandler eh, void* data);
+#define CORE_ERROR(error, ...)                                                 \
+    Core::handleError(__FILE__, __LINE__, error __VA_OPT__(, ) __VA_ARGS__)
+
     namespace Internal {
         template<typename T>
         struct BaseRemoveReference {
@@ -105,41 +123,41 @@ namespace Core {
     }
 
     using ExitHandler = void (*)(int, void*);
-
     void exitWithHandler(const char* file, int line, int value);
-    void setExitHandler(ExitHandler eh, void*);
-
+    void setExitHandler(ExitHandler eh, void* data);
 #define CORE_EXIT(exitValue)                                                   \
     Core::exitWithHandler(__FILE__, __LINE__, exitValue)
 
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(signed short s, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(unsigned short s, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(signed int i, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(unsigned int i, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(signed long l, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(unsigned long l, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(signed long long ll, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(unsigned long long ll, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(float f, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(double d, char* buffer, int size);
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool toString(long double ld, char* buffer, int size);
 
-    // returns true on error
+    // returns true on error and calls the error callback
     check_return bool putChar(int c);
 
     void memorySet(void* p, int c, int n);
     void memoryCopy(void* dest, const void* src, int n);
+
+    const char* getFileName(const char* path);
 }
 
 using i64 = Core::Internal::SelectSigned<8>;