Browse Source

improved ring buffer, box moved to core with tests, view for easy view
matrix building, convenience methods

Kajetan Johannes Hammerle 2 years ago
parent
commit
a692cb21f1

+ 4 - 0
Main.cpp

@@ -5,6 +5,7 @@
 #include "tests/ArrayListTests.h"
 #include "tests/ArrayTests.h"
 #include "tests/BitArrayTests.h"
+#include "tests/BoxTests.h"
 #include "tests/BufferTests.h"
 #include "tests/ClockTests.h"
 #include "tests/ColorTests.h"
@@ -27,6 +28,7 @@
 #include "tests/TypedBufferTests.h"
 #include "tests/UniquePointerTests.h"
 #include "tests/VectorTests.h"
+#include "tests/ViewTests.h"
 #include "utils/Logger.h"
 
 static int ticks = 40;
@@ -72,6 +74,8 @@ int main(int argAmount, char** args) {
     UniquePointerTests::test();
     NetworkTests::test();
     ComponentsTests::test();
+    BoxTests::test();
+    ViewTests::test();
 
     Window::Options options(4, 3, {800, 480}, false, "Test");
     Error error = Window::open(options);

+ 20 - 6
data/RingBuffer.h

@@ -11,7 +11,7 @@ class RingBuffer final {
     int values = 0;
 
 public:
-    bool write(const T& t) {
+    bool add(const T& t) {
         if(values >= N) {
             return true;
         }
@@ -21,15 +21,29 @@ public:
         return false;
     }
 
-    bool canRead() const {
+    int getLength() const {
+        return values;
+    }
+
+    bool canRemove() const {
         return values > 0;
     }
 
-    T read() {
-        values -= values > 0;
-        T& t = data[readIndex];
+    bool remove() {
+        if(values <= 0) {
+            return true;
+        }
+        values--;
         readIndex = (readIndex + 1) % N;
-        return t;
+        return false;
+    }
+
+    T& operator[](int index) {
+        return data[(index + readIndex) % N];
+    }
+
+    const T& operator[](int index) const {
+        return data[(index + readIndex) % N];
     }
 
     void clear() {

+ 55 - 0
math/Box.cpp

@@ -0,0 +1,55 @@
+#include "math/Box.h"
+
+Box::Box(const Vector3& min, const Vector3& max) : min(min), max(max) {
+}
+
+Box::Box(const Vector3& size) {
+    for(int i = 0; i < 3; i++) {
+        if(size[i] < 0.0f) {
+            min[i] = size[i];
+        } else {
+            max[i] = size[i];
+        }
+    }
+}
+
+Box Box::offset(const Vector3& offset) const {
+    return Box(min + offset, max + offset);
+}
+
+bool Box::collidesWith(const Box& other) const {
+    return max[0] > other.min[0] && min[0] < other.max[0] &&
+           max[1] > other.min[1] && min[1] < other.max[1] &&
+           max[2] > other.min[2] && min[2] < other.max[2];
+}
+
+Box Box::expand(const Vector3& offset) const {
+    Vector3 add(offset[0] > 0.0f ? offset[0] : 0.0f,
+                offset[1] > 0.0f ? offset[1] : 0.0f,
+                offset[2] > 0.0f ? offset[2] : 0.0f);
+    Vector3 sub(offset[0] < 0.0f ? offset[0] : 0.0f,
+                offset[1] < 0.0f ? offset[1] : 0.0f,
+                offset[2] < 0.0f ? offset[2] : 0.0f);
+    return Box(min + sub, max + add);
+}
+
+Box Box::grow(const Vector3& growth) const {
+    Vector3 half = growth * 0.5f;
+    Vector3 nMin = min - half;
+    Vector3 nMax = max + half;
+    for(int i = 0; i < 3; i++) {
+        if(nMin[i] > nMax[i]) {
+            nMin[i] = (min[i] + max[i]) * 0.5f;
+            nMax[i] = nMin[i];
+        }
+    }
+    return Box(nMin, nMax);
+}
+
+const Vector3& Box::getMin() const {
+    return min;
+}
+
+const Vector3& Box::getMax() const {
+    return max;
+}

+ 30 - 0
math/Box.h

@@ -0,0 +1,30 @@
+#ifndef COLLISION_BOX_H
+#define COLLISION_BOX_H
+
+#include "math/Vector.h"
+#include "utils/StringBuffer.h"
+
+class Box {
+    Vector3 min;
+    Vector3 max;
+
+    Box(const Vector3& min, const Vector3& max);
+
+public:
+    Box(const Vector3& size);
+
+    Box offset(const Vector3& offset) const;
+    bool collidesWith(const Box& other) const;
+    Box expand(const Vector3& offset) const;
+    Box grow(const Vector3& growth) const;
+
+    const Vector3& getMin() const;
+    const Vector3& getMax() const;
+
+    template<int L>
+    void toString(StringBuffer<L>& s) const {
+        s.append("Box(").append(min).append(", ").append(max).append(")");
+    }
+};
+
+#endif

+ 1 - 1
math/Frustum.cpp

@@ -4,7 +4,7 @@ Frustum::Frustum(float fieldOfView, float nearClip, float farClip)
     : fieldOfView(fieldOfView), nearClip(nearClip), farClip(farClip) {
 }
 
-Matrix& Frustum::updateProjection(const IntVector2& size) {
+const Matrix& Frustum::updateProjection(const IntVector2& size) {
     float tan = tanf(fieldOfView * (0.5f * M_PI / 180.0f));
     float q = 1.0f / tan;
     float aspect = static_cast<float>(size[0]) / size[1];

+ 1 - 1
math/Frustum.h

@@ -16,7 +16,7 @@ public:
     float farClip;
 
     Frustum(float fieldOfView, float nearClip, float farClip);
-    Matrix& updateProjection(const IntVector2& size);
+    const Matrix& updateProjection(const IntVector2& size);
     void updatePlanes(const Vector3& pos, const Vector3& right,
                       const Vector3& up, const Vector3& front,
                       const IntVector2& size);

+ 2 - 2
math/Math.h

@@ -1,5 +1,5 @@
-#ifndef MATH_H
-#define MATH_H
+#ifndef COREMATH_H
+#define COREMATH_H
 
 #include <cmath>
 

+ 5 - 4
math/Matrix.cpp

@@ -2,10 +2,11 @@
 #include "math/Math.h"
 
 Matrix::Matrix() {
-    data[0] = Vector4(1.0f, 0.0f, 0.0f, 0.0f);
-    data[1] = Vector4(0.0f, 1.0f, 0.0f, 0.0f);
-    data[2] = Vector4(0.0f, 0.0f, 1.0f, 0.0f);
-    data[3] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
+    unit();
+}
+
+Matrix& Matrix::unit() {
+    return translateTo(Vector3(0.0f, 0.0f, 0.0f));
 }
 
 Matrix& Matrix::set(int index, const Vector4& v) {

+ 2 - 0
math/Matrix.h

@@ -12,6 +12,8 @@ class Matrix final {
 public:
     Matrix();
 
+    Matrix& unit();
+
     Matrix& set(int index, const Vector4& v);
 
     Matrix transpose();

+ 16 - 0
math/Vector.h

@@ -138,6 +138,22 @@ public:
         return values[index];
     }
 
+    Vector<N, int> toInt() const {
+        Vector<N, int> cast;
+        for(int i = 0; i < N; i++) {
+            cast[i] = values[i];
+        }
+        return cast;
+    }
+
+    Vector<N, float> toFloat() const {
+        Vector<N, float> cast;
+        for(int i = 0; i < N; i++) {
+            cast[i] = values[i];
+        }
+        return cast;
+    }
+
     template<int L>
     void toString(StringBuffer<L>& s) const {
         s.append("[");

+ 54 - 0
math/View.cpp

@@ -0,0 +1,54 @@
+#include "math/View.h"
+
+const Matrix& View::update(float lengthAngle, float widthAngle,
+                           const Vector3& pos) {
+    back.setAngles(lengthAngle, widthAngle);
+    right = back.cross(Vector3(0.0f, 1.0f, 0.0f));
+    right.normalize();
+    up = right.cross(back);
+    up.normalize();
+    back = -back;
+
+    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;
+}
+
+const Matrix& View::update(const Quaternion& q, const Vector3& pos) {
+    up = q * Vector3(0.0f, 1.0f, 0.0f);
+    back = q * Vector3(-1.0f, 0.0f, 0.0f);
+    right = up.cross(back);
+    right.normalize();
+
+    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;
+}
+
+Vector3 View::getUp() const {
+    return up;
+}
+
+Vector3 View::getDown() const {
+    return -up;
+}
+
+Vector3 View::getLeft() const {
+    return -right;
+}
+
+Vector3 View::getRight() const {
+    return right;
+}
+
+Vector3 View::getFront() const {
+    return -back;
+}
+
+Vector3 View::getBack() const {
+    return back;
+}

+ 28 - 0
math/View.h

@@ -0,0 +1,28 @@
+#ifndef VIEW_H
+#define VIEW_H
+
+#include "data/Array.h"
+#include "math/Matrix.h"
+#include "math/Plane.h"
+#include "math/Quaternion.h"
+
+class View final {
+    Matrix view;
+    Vector3 right;
+    Vector3 up;
+    Vector3 back;
+
+public:
+    const Matrix& update(float lengthAngle, float widthAngle,
+                         const Vector3& pos);
+    const Matrix& update(const Quaternion& q, const Vector3& pos);
+
+    Vector3 getUp() const;
+    Vector3 getDown() const;
+    Vector3 getLeft() const;
+    Vector3 getRight() const;
+    Vector3 getFront() const;
+    Vector3 getBack() const;
+};
+
+#endif

+ 4 - 0
meson.build

@@ -3,7 +3,9 @@ project('gamingcore', 'cpp', default_options : ['default_library=static'])
 src = [
     'io/ImageReader.cpp',
     'math/Math.cpp',
+    'math/Box.cpp',
     'math/Frustum.cpp',
+    'math/View.cpp',
     'math/Matrix.cpp',
     'math/Plane.cpp',
     'math/Quaternion.cpp',
@@ -32,6 +34,7 @@ src_tests = [
     'tests/ArrayTests.cpp',
     'tests/BitArrayTests.cpp',
     'tests/BufferTests.cpp',
+    'tests/BoxTests.cpp',
     'tests/ClockTests.cpp',
     'tests/ColorTests.cpp',
     'tests/FrustumTests.cpp',
@@ -53,6 +56,7 @@ src_tests = [
     'tests/UniquePointerTests.cpp',
     'tests/MathTests.cpp',
     'tests/VectorTests.cpp',
+    'tests/ViewTests.cpp',
     'tests/ComponentsTests.cpp',
 ]
 

+ 8 - 10
rendering/GL.cpp

@@ -23,6 +23,8 @@ static_assert(std::is_same<GL::VertexArray, GLuint>::value,
               "vertex array has invalid type");
 static_assert(std::is_same<GL::Buffer, GLuint>::value,
               "buffer has invalid type");
+static_assert(std::is_same<GL::BufferUsage, GLenum>::value,
+              "buffer usage has invalid type");
 
 GL::ShaderType GL::NO_SHADER = 0;
 GL::ShaderType GL::VERTEX_SHADER = GL_VERTEX_SHADER;
@@ -31,6 +33,10 @@ GL::ShaderType GL::GEOMETRY_SHADER = GL_GEOMETRY_SHADER;
 GL::ShaderType GL::TESSELATION_CONTROL_SHADER = GL_TESS_CONTROL_SHADER;
 GL::ShaderType GL::TESSELATION_EVALUATION_SHADER = GL_TESS_EVALUATION_SHADER;
 
+GL::BufferUsage GL::STATIC_DRAW = GL_STATIC_DRAW;
+GL::BufferUsage GL::STREAM_DRAW = GL_STREAM_DRAW;
+GL::BufferUsage GL::DYNAMIC_DRAW = GL_DYNAMIC_DRAW;
+
 GL::Attribute::Attribute(int count, int size, int type, bool normalized)
     : count(count), size(size), type(type), normalized(normalized) {
 }
@@ -388,16 +394,8 @@ void GL::bindBuffer(Buffer b) {
     glBindBuffer(GL_ARRAY_BUFFER, b);
 }
 
-void GL::bufferDataStatic(int size, const void* data) {
-    glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
-}
-
-void GL::bufferDataStream(int size, const void* data) {
-    glBufferData(GL_ARRAY_BUFFER, size, data, GL_STREAM_DRAW);
-}
-
-void GL::bufferDataDynamic(int size, const void* data) {
-    glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW);
+void GL::bufferData(int size, const void* data, BufferUsage usage) {
+    glBufferData(GL_ARRAY_BUFFER, size, data, usage);
 }
 
 void GL::bufferSubData(int offset, int size, const void* data) {

+ 6 - 3
rendering/GL.h

@@ -12,6 +12,7 @@ namespace GL {
     typedef unsigned int ColorAttachment;
     typedef unsigned int VertexArray;
     typedef unsigned int Buffer;
+    typedef unsigned int BufferUsage;
 
     extern ShaderType NO_SHADER;
     extern ShaderType VERTEX_SHADER;
@@ -20,6 +21,10 @@ namespace GL {
     extern ShaderType TESSELATION_CONTROL_SHADER;
     extern ShaderType TESSELATION_EVALUATION_SHADER;
 
+    extern BufferUsage STATIC_DRAW;
+    extern BufferUsage STREAM_DRAW;
+    extern BufferUsage DYNAMIC_DRAW;
+
     class Attribute final {
         int count;
         int size;
@@ -113,9 +118,7 @@ namespace GL {
     void deleteVertexArray(VertexArray va);
     void bindVertexArray(VertexArray va);
     void bindBuffer(Buffer b);
-    void bufferDataStatic(int size, const void* data);
-    void bufferDataStream(int size, const void* data);
-    void bufferDataDynamic(int size, const void* data);
+    void bufferData(int size, const void* data, BufferUsage usage);
     void bufferSubData(int offset, int size, const void* data);
     void drawTriangles(int offset, int vertices);
     void drawTriangleStrip(int offset, int vertices);

+ 4 - 0
rendering/Shader.cpp

@@ -83,6 +83,10 @@ void Shader::setMatrix(const char* name, const float* data) {
     GL::setMatrix(program, name, data);
 }
 
+void Shader::setMatrix(const char* name, const Matrix& m) {
+    setMatrix(name, m.getValues());
+}
+
 void Shader::setInt(const char* name, int data) {
     GL::setInt(program, name, data);
 }

+ 2 - 1
rendering/Shader.h

@@ -3,7 +3,7 @@
 
 #include "data/Array.h"
 #include "data/List.h"
-#include "math/Vector.h"
+#include "math/Matrix.h"
 #include "rendering/GL.h"
 #include "utils/Error.h"
 
@@ -56,6 +56,7 @@ public:
 
     void use() const;
     void setMatrix(const char* name, const float* data);
+    void setMatrix(const char* name, const Matrix& m);
     void setInt(const char* name, int data);
     void setFloat(const char* name, float data);
 

+ 4 - 12
rendering/VertexBuffer.cpp

@@ -58,22 +58,14 @@ void VertexBuffer::init(const Attributes& attributes) {
     attributes.set();
 }
 
-void VertexBuffer::setStaticData(int size, const void* data) {
+void VertexBuffer::setData(int size, const void* data, GL::BufferUsage usage) {
     VertexBuffer::size = size;
     bindBuffer();
-    GL::bufferDataStatic(size, data);
+    GL::bufferData(size, data, usage);
 }
 
-void VertexBuffer::setStreamData(int size, const void* data) {
-    VertexBuffer::size = size;
-    bindBuffer();
-    GL::bufferDataStream(size, data);
-}
-
-void VertexBuffer::setDynamicData(int size, const void* data) {
-    VertexBuffer::size = size;
-    bindBuffer();
-    GL::bufferDataDynamic(size, data);
+void VertexBuffer::setData(const Buffer& buffer, GL::BufferUsage usage) {
+    setData(buffer.getLength(), buffer, usage);
 }
 
 void VertexBuffer::updateData(int offset, int size, const void* data) {

+ 9 - 3
rendering/VertexBuffer.h

@@ -3,6 +3,8 @@
 
 #include "data/ArrayList.h"
 #include "rendering/GL.h"
+#include "utils/Buffer.h"
+#include "utils/TypedBuffer.h"
 
 class VertexBuffer final {
     GL::VertexArray vertexArray;
@@ -33,9 +35,13 @@ public:
 
     void init(const Attributes& attributes);
 
-    void setStaticData(int size, const void* data = nullptr);
-    void setStreamData(int size, const void* data = nullptr);
-    void setDynamicData(int size, const void* data = nullptr);
+    void setData(int size, const void* data, GL::BufferUsage usage);
+    void setData(const Buffer& buffer, GL::BufferUsage usage);
+    template<typename T>
+    void setData(GL::BufferUsage usage, const TypedBuffer<T>& buffer) {
+        setData(buffer.getByteLength(), buffer, usage);
+    }
+
     void updateData(int offset, int size, const void* data);
 
     void draw(int vertices, int offset = 0);

+ 72 - 0
tests/BoxTests.cpp

@@ -0,0 +1,72 @@
+#include "tests/BoxTests.h"
+#include "math/Box.h"
+#include "tests/Test.h"
+#include "utils/StringBuffer.h"
+
+typedef StringBuffer<250> String;
+
+static void testInit(Test& test) {
+    Box box(Vector3(1.0f, 2.0f, 3.0f));
+    test.checkEqual(String("Box([0.00, 0.00, 0.00], [1.00, 2.00, 3.00])"),
+                    String(box), "init 1");
+    test.checkEqual(String("[0.00, 0.00, 0.00]"), String(box.getMin()),
+                    "get min 1");
+    test.checkEqual(String("[1.00, 2.00, 3.00]"), String(box.getMax()),
+                    "get max 1");
+
+    box = Box(Vector3(-1.0f, -2.0f, -3.0f));
+    test.checkEqual(String("Box([-1.00, -2.00, -3.00], [0.00, 0.00, 0.00])"),
+                    String(box), "init 2");
+    test.checkEqual(String("[-1.00, -2.00, -3.00]"), String(box.getMin()),
+                    "get min 2");
+    test.checkEqual(String("[0.00, 0.00, 0.00]"), String(box.getMax()),
+                    "get max 2");
+}
+
+static void testOffset(Test& test) {
+    Box box(Vector3(1.0f, 2.0f, 3.0f));
+    test.checkEqual(String("Box([7.00, -4.00, 6.00], [8.00, -2.00, 9.00])"),
+                    String(box.offset(Vector3(7.0f, -4.0f, 6.0f))), "expand");
+}
+
+static void testCollidesWith(Test& test) {
+    Box boxA(Vector3(1.0f, 2.0f, 3.0f));
+    Box boxB(Vector3(-1.0f, -2.0f, -3.0f));
+    Box boxC(Vector3(2.0f, 2.0f, 2.0f));
+    boxC = boxC.offset(Vector3(-1.0f, -1.0f, -1.0f));
+
+    test.checkTrue(boxC.collidesWith(boxA), "collides with 1");
+    test.checkTrue(boxC.collidesWith(boxB), "collides with 2");
+    test.checkTrue(boxA.collidesWith(boxC), "collides with 3");
+    test.checkTrue(boxB.collidesWith(boxC), "collides with 4");
+    test.checkFalse(boxA.collidesWith(boxB), "collides with 5");
+    test.checkFalse(boxB.collidesWith(boxA), "collides with 6");
+}
+
+static void testExpand(Test& test) {
+    Box box(Vector3(1.0f, 2.0f, 3.0f));
+    test.checkEqual(String("Box([0.00, -4.00, 0.00], [8.00, 2.00, 9.00])"),
+                    String(box.expand(Vector3(7.0f, -4.0f, 6.0f))), "expand");
+    test.checkEqual(String("Box([-7.00, 0.00, -6.00], [1.00, 6.00, 3.00])"),
+                    String(box.expand(Vector3(-7.0f, 4.0f, -6.0f))), "expand");
+}
+
+static void testGrow(Test& test) {
+    Box box(Vector3(1.0f, 2.0f, 3.0f));
+    test.checkEqual(String("Box([-2.00, -1.00, -3.00], [3.00, 3.00, 6.00])"),
+                    String(box.grow(Vector3(4.0f, 2.0f, 6.0f))), "expand");
+    test.checkEqual(String("Box([0.50, 1.00, 1.50], [0.50, 1.00, 1.50])"),
+                    String(box.grow(Vector3(-4.0f, -2.0f, -6.0f))), "expand");
+    test.checkEqual(String("Box([0.05, 1.00, 0.50], [0.95, 1.00, 2.50])"),
+                    String(box.grow(Vector3(-0.1f, -4.0f, -1.0f))), "expand");
+}
+
+void BoxTests::test() {
+    Test test("Box");
+    testInit(test);
+    testOffset(test);
+    testCollidesWith(test);
+    testExpand(test);
+    testGrow(test);
+    test.finalize();
+}

+ 8 - 0
tests/BoxTests.h

@@ -0,0 +1,8 @@
+#ifndef BOXTESTS_H
+#define BOXTESTS_H
+
+namespace BoxTests {
+    void test();
+}
+
+#endif

+ 47 - 47
tests/RingBufferTests.cpp

@@ -4,66 +4,66 @@
 
 static void testReadAndWrite(Test& test) {
     RingBuffer<int, 5> buffer;
-    test.checkEqual(false, buffer.canRead(), "no read from empty buffer");
-    buffer.write(4);
-    test.checkEqual(true, buffer.canRead(), "can read from buffer with value");
-    test.checkEqual(4, buffer.read(), "can read value");
-    test.checkEqual(false, buffer.canRead(), "value is gone after read");
+    test.checkFalse(buffer.canRemove(), "no read from empty buffer");
+    buffer.add(4);
+    test.checkTrue(buffer.canRemove(), "can read from buffer with value");
+    test.checkEqual(4, buffer[0], "can read value");
+    test.checkFalse(buffer.remove(), "can remove value");
+    test.checkFalse(buffer.canRemove(), "value is gone after read");
 }
 
 static void testOverflow(Test& test) {
     RingBuffer<int, 3> buffer;
-    bool r1 = buffer.write(1);
-    bool r2 = buffer.write(2);
-    bool r3 = buffer.write(3);
-    bool r4 = buffer.write(4);
-    bool r5 = buffer.write(5);
-    test.checkEqual(false, r1, "check buffer write return value 1");
-    test.checkEqual(false, r2, "check buffer write return value 2");
-    test.checkEqual(false, r3, "check buffer write return value 3");
-    test.checkEqual(true, r4, "check buffer write return value 4");
-    test.checkEqual(true, r5, "check buffer write return value 5");
-    test.checkEqual(1, buffer.read(),
-                    "can read value from overflowed buffer 1");
-    test.checkEqual(2, buffer.read(),
-                    "can read value from overflowed buffer 2");
-    test.checkEqual(3, buffer.read(),
-                    "can read value from overflowed buffer 3");
-    test.checkEqual(false, buffer.canRead(), "values are gone after full read");
+    bool r1 = buffer.add(1);
+    bool r2 = buffer.add(2);
+    bool r3 = buffer.add(3);
+    bool r4 = buffer.add(4);
+    bool r5 = buffer.add(5);
+    test.checkFalse(r1, "check buffer write return value 1");
+    test.checkFalse(r2, "check buffer write return value 2");
+    test.checkFalse(r3, "check buffer write return value 3");
+    test.checkTrue(r4, "check buffer write return value 4");
+    test.checkTrue(r5, "check buffer write return value 5");
+    test.checkEqual(1, buffer[0], "can read value from overflowed buffer 1");
+    test.checkFalse(buffer.remove(), "can remove from overflowed buffer 1");
+    test.checkEqual(2, buffer[0], "can read value from overflowed buffer 2");
+    test.checkFalse(buffer.remove(), "can remove from overflowed buffer 2");
+    test.checkEqual(3, buffer[0], "can read value from overflowed buffer 3");
+    test.checkFalse(buffer.remove(), "can read value from overflowed buffer 3");
+    test.checkFalse(buffer.canRemove(), "values are gone after full read");
 }
 
 static void testRefill(Test& test) {
     RingBuffer<int, 3> buffer;
-    buffer.write(1);
-    buffer.write(2);
-    buffer.write(3);
-    buffer.write(4);
-    test.checkEqual(true, buffer.canRead(), "values are ready to be read 2");
-    test.checkEqual(1, buffer.read(),
-                    "can read value from overflowed buffer 1");
-    test.checkEqual(2, buffer.read(),
-                    "can read value from overflowed buffer 2");
-    test.checkEqual(3, buffer.read(),
-                    "can read value from overflowed buffer 3");
-    test.checkEqual(false, buffer.canRead(),
-                    "values are gone after full read 1");
-    buffer.write(5);
-    buffer.write(6);
-    test.checkEqual(true, buffer.canRead(), "values are ready to be read 2");
-    test.checkEqual(5, buffer.read(),
-                    "can read value from overflowed buffer 4");
-    test.checkEqual(6, buffer.read(),
-                    "can read value from overflowed buffer 5");
-    test.checkEqual(false, buffer.canRead(),
-                    "values are gone after full read 1");
+    buffer.add(1);
+    buffer.add(2);
+    buffer.add(3);
+    buffer.add(4);
+    test.checkTrue(buffer.canRemove(), "values are ready to be read 2");
+    test.checkEqual(1, buffer[0], "can read value from overflowed buffer 1");
+    test.checkFalse(buffer.remove(), "can remove from overflowed buffer 1");
+    test.checkEqual(2, buffer[0], "can read value from overflowed buffer 2");
+    test.checkFalse(buffer.remove(), "can remove from overflowed buffer 2");
+    test.checkEqual(3, buffer[0], "can read value from overflowed buffer 3");
+    test.checkFalse(buffer.remove(), "can read value from overflowed buffer 3");
+
+    test.checkFalse(buffer.canRemove(), "values are gone after full read 1");
+    buffer.add(5);
+    buffer.add(6);
+    test.checkTrue(buffer.canRemove(), "values are ready to be read 2");
+    test.checkEqual(5, buffer[0], "can read value from overflowed buffer 4");
+    test.checkFalse(buffer.remove(), "can remove from overflowed buffer 4");
+    test.checkEqual(6, buffer[0], "can read value from overflowed buffer 5");
+    test.checkFalse(buffer.remove(), "can read value from overflowed buffer 5");
+    test.checkFalse(buffer.canRemove(), "values are gone after full read 1");
 }
 
 static void testClear(Test& test) {
     RingBuffer<int, 3> buffer;
-    buffer.write(1);
-    buffer.write(2);
+    buffer.add(1);
+    buffer.add(2);
     buffer.clear();
-    test.checkEqual(false, buffer.canRead(), "no read after clear");
+    test.checkFalse(buffer.canRemove(), "no read after clear");
 }
 
 void RingBufferTests::test() {

+ 8 - 0
tests/VectorTests.cpp

@@ -230,6 +230,13 @@ static void testNormalize(Test& test) {
     compareVectors(test, v4, v3, "normalize 2");
 }
 
+static void testCast(Test& test) {
+    compareVectors(test, Vector3(-2.5f, 2.6f, 9.0f).toInt(),
+                   IntVector3(-2, 2, 9), "float to int");
+    compareVectors(test, IntVector3(-2.5f, 2.6f, 9.0f).toFloat(),
+                   Vector3(-2.0f, 2.0f, 9.0f), "float to int");
+}
+
 static void testToString(Test& test) {
     StringBuffer<50> s;
     s.append(Vector<1, float>())
@@ -259,6 +266,7 @@ void VectorTests::test() {
     testSquareLength(test);
     testLength(test);
     testNormalize(test);
+    testCast(test);
     testToString(test);
     test.finalize();
 }

+ 45 - 0
tests/ViewTests.cpp

@@ -0,0 +1,45 @@
+#include "tests/ViewTests.h"
+#include "math/View.h"
+#include "tests/Test.h"
+#include "utils/StringBuffer.h"
+
+const float eps = 0.0001f;
+
+template<int N, typename T>
+static void compareVectors(Test& test, const Vector<N, T>& wanted,
+                           const Vector<N, T>& actual, const char* text) {
+    for(int i = 0; i < N; i++) {
+        test.checkFloat(
+            wanted[i], actual[i], eps,
+            StringBuffer<100>(text).append(" (").append(i).append(")"));
+    }
+}
+
+static void testFromAngles(Test& test) {
+    View v;
+    v.update(0.0f, 0.0f, Vector3());
+    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f), v.getUp(), "up");
+    compareVectors(test, Vector3(0.0f, -1.0f, 0.0f), v.getDown(), "down");
+    compareVectors(test, Vector3(0.0f, 0.0f, -1.0f), v.getLeft(), "left");
+    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f), v.getRight(), "right");
+    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f), v.getFront(), "front");
+    compareVectors(test, Vector3(-1.0f, 0.0f, 0.0f), v.getBack(), "back");
+}
+
+static void testFromQuaternion(Test& test) {
+    View v;
+    v.update(Quaternion(), Vector3());
+    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f), v.getUp(), "up");
+    compareVectors(test, Vector3(0.0f, -1.0f, 0.0f), v.getDown(), "down");
+    compareVectors(test, Vector3(0.0f, 0.0f, -1.0f), v.getLeft(), "left");
+    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f), v.getRight(), "right");
+    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f), v.getFront(), "front");
+    compareVectors(test, Vector3(-1.0f, 0.0f, 0.0f), v.getBack(), "back");
+}
+
+void ViewTests::test() {
+    Test test("View");
+    testFromAngles(test);
+    testFromQuaternion(test);
+    test.finalize();
+}

+ 8 - 0
tests/ViewTests.h

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