Browse Source

row aligned matrix class with tests, vector4 specialization

Kajetan Johannes Hammerle 3 years ago
parent
commit
a22b853d90
9 changed files with 363 additions and 2 deletions
  1. 2 0
      Main.cpp
  2. 108 0
      math/Matrix.cpp
  3. 47 0
      math/Matrix.h
  4. 14 0
      math/Vector.cpp
  5. 9 0
      math/Vector.h
  6. 1 1
      meson.build
  7. 168 0
      tests/MatrixTests.cpp
  8. 8 0
      tests/MatrixTests.h
  9. 6 1
      tests/VectorTests.cpp

+ 2 - 0
Main.cpp

@@ -7,6 +7,7 @@
 #include "tests/RingBufferTests.h"
 #include "tests/SplitStringTests.h"
 #include "tests/VectorTests.h"
+#include "tests/MatrixTests.h"
 
 int main() {
     ArrayTests::test();
@@ -18,5 +19,6 @@ int main() {
     RingBufferTests::test();
     SplitStringTests::test();
     VectorTests::test();
+    MatrixTests::test();
     return 0;
 }

+ 108 - 0
math/Matrix.cpp

@@ -0,0 +1,108 @@
+#include <cmath>
+
+#include "math/Matrix.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);
+}
+
+Matrix& Matrix::set(int index, const Vector4& v) {
+    data[index] = v;
+    return *this;
+}
+
+Matrix Matrix::transpose() {
+    Matrix m;
+    for(int x = 0; x < 4; x++) {
+        for(int y = 0; y < 4; y++) {
+            m.data[x][y] = data[y][x];
+        }
+    }
+    return m;
+}
+
+const float* Matrix::getValues() const {
+    return &(data[0][0]);
+}
+
+Matrix& Matrix::operator*=(const Matrix& m) {
+    data[0] = data[0][0] * m.data[0] + data[0][1] * m.data[1] + data[0][2] * m.data[2] + data[0][3] * m.data[3];
+    data[1] = data[1][0] * m.data[0] + data[1][1] * m.data[1] + data[1][2] * m.data[2] + data[1][3] * m.data[3];
+    data[2] = data[2][0] * m.data[0] + data[2][1] * m.data[1] + data[2][2] * m.data[2] + data[2][3] * m.data[3];
+    data[3] = data[3][0] * m.data[0] + data[3][1] * m.data[1] + data[3][2] * m.data[2] + data[3][3] * m.data[3];
+    return *this;
+}
+
+Matrix Matrix::operator*(const Matrix& other) const {
+    Matrix m = *this;
+    m *= other;
+    return m;
+}
+
+Vector3 Matrix::operator*(const Vector3& v) const {
+    Vector4 v4(v[0], v[1], v[2], 1.0f);
+    return Vector3(data[0].dot(v4), data[1].dot(v4), data[2].dot(v4)) * (1.0f / data[3].dot(v4));
+}
+
+Matrix& Matrix::scale(const Vector3& v) {
+    data[0] *= v[0];
+    data[1] *= v[1];
+    data[2] *= v[2];
+    return *this;
+}
+
+Matrix& Matrix::scale(float s) {
+    return scale(Vector3(s, s, s));
+}
+
+Matrix& Matrix::translate(const Vector3& v) {
+    return translateX(v[0]).translateY(v[1]).translateZ(v[2]);
+}
+
+Matrix& Matrix::translateX(float tx) {
+    data[0] += data[3] * tx;
+    return *this;
+}
+
+Matrix& Matrix::translateY(float ty) {
+    data[1] += data[3] * ty;
+    return *this;
+}
+
+Matrix& Matrix::translateZ(float tz) {
+    data[2] += data[3] * tz;
+    return *this;
+}
+
+Matrix& Matrix::translateTo(const Vector3& v) {
+    data[0] = Vector4(1.0f, 0.0f, 0.0f, v[0]);
+    data[1] = Vector4(0.0f, 1.0f, 0.0f, v[1]);
+    data[2] = Vector4(0.0f, 0.0f, 1.0f, v[2]);
+    data[3] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
+    return *this;
+}
+
+Matrix& Matrix::rotate(float degrees, int a, int b) {
+    float sin;
+    float cos;
+    sincosf(degrees * (M_PI / 180.0f), &sin, &cos);
+    Vector4 v = data[a];
+    data[a] = cos * data[a] - sin * data[b];
+    data[b] = sin * v + cos * data[b];
+    return *this;
+}
+
+Matrix& Matrix::rotateX(float degrees) {
+    return rotate(degrees, 1, 2);
+}
+
+Matrix& Matrix::rotateY(float degrees) {
+    return rotate(-degrees, 0, 2);
+}
+
+Matrix& Matrix::rotateZ(float degrees) {
+    return rotate(degrees, 0, 1);
+}

+ 47 - 0
math/Matrix.h

@@ -0,0 +1,47 @@
+#ifndef MATRIX_H
+#define MATRIX_H
+
+#include "math/Vector.h"
+#include "utils/StringBuffer.h"
+
+class Matrix final {
+    Vector4 data[4];
+
+    Matrix& rotate(float degrees, int a, int b);
+
+public:
+    Matrix();
+
+    Matrix& set(int index, const Vector4& v);
+
+    Matrix transpose();
+
+    const float* getValues() const;
+
+    Matrix& operator*=(const Matrix& other);
+    Matrix operator*(const Matrix& other) const;
+    Vector3 operator*(const Vector3& v) const;
+
+    Matrix& scale(const Vector3& v);
+    Matrix& scale(float f);
+
+    Matrix& translate(const Vector3& v);
+    Matrix& translateX(float tx);
+    Matrix& translateY(float ty);
+    Matrix& translateZ(float tz);
+    Matrix& translateTo(const Vector3& v);
+
+    Matrix& rotateX(float degrees);
+    Matrix& rotateY(float degrees);
+    Matrix& rotateZ(float degrees);
+
+    template<int L>
+    void toString(StringBuffer<L>& s) const {
+        s.append('[').append(data[0]).append(", ");
+        s.append(data[1]).append(", ");
+        s.append(data[2]).append(", ");
+        s.append(data[3]).append("]");
+    }
+};
+
+#endif

+ 14 - 0
math/Vector.cpp

@@ -2,6 +2,20 @@
 
 #include "math/Vector.h"
 
+template<>
+Vector<4>& Vector<4>::set(float x, float y, float z, float w) {
+    values[0] = x;
+    values[1] = y;
+    values[2] = z;
+    values[3] = w;
+    return *this;
+}
+
+template<>
+Vector<4>::Vector(float x, float y, float z, float w) {
+    set(x, y, z, w);
+}
+
 template<>
 Vector<3>& Vector<3>::set(float x, float y, float z) {
     values[0] = x;

+ 9 - 0
math/Vector.h

@@ -17,9 +17,11 @@ public:
 
     Vector(float, float) = delete;
     Vector(float, float, float) = delete;
+    Vector(float, float, float, float) = delete;
 
     Vector& set(float, float) = delete;
     Vector& set(float, float, float) = delete;
+    Vector& set(float, float, float, float) = delete;
 
     Vector& setAngles(float, float) = delete;
     Vector cross(const Vector&) const = delete;
@@ -92,6 +94,10 @@ public:
         return *this;
     }
 
+    float& operator[](int index) {
+        return values[index];
+    }
+
     const float& operator[](int index) const {
         return values[index];
     }
@@ -115,15 +121,18 @@ Vector<N> operator*(float factor, const Vector<N>& v) {
     return v * factor;
 }
 
+template<> Vector<4>::Vector(float x, float y, float z, float w);
 template<> Vector<3>::Vector(float x, float y, float z);
 template<> Vector<2>::Vector(float x, float y);
 
+template<> Vector<4>& Vector<4>::set(float x, float y, float z, float w);
 template<> Vector<3>& Vector<3>::set(float x, float y, float z);
 template<> Vector<2>& Vector<2>::set(float x, float y);
 
 template<> Vector<3>& Vector<3>::setAngles(float lengthAngle, float widthAngle);
 template<> Vector<3> Vector<3>::cross(const Vector<3>& other) const;
 
+typedef Vector<4> Vector4;
 typedef Vector<3> Vector3;
 typedef Vector<2> Vector2;
 

+ 1 - 1
meson.build

@@ -1,6 +1,6 @@
 project('gaming core tests', 'cpp')
 
-sources = ['Main.cpp', 'tests/Test.cpp', 'tests/ArrayTests.cpp', 'tests/HashMapTests.cpp', 'tests/ListTests.cpp', 'tests/BitArrayTests.cpp', 'tests/StringBufferTests.cpp', 'tests/RandomTests.cpp', 'utils/Random.cpp', 'tests/RingBufferTests.cpp', 'tests/SplitStringTests.cpp', 'tests/VectorTests.cpp', 'math/Vector.cpp']
+sources = ['Main.cpp', 'tests/Test.cpp', 'tests/ArrayTests.cpp', 'tests/HashMapTests.cpp', 'tests/ListTests.cpp', 'tests/BitArrayTests.cpp', 'tests/StringBufferTests.cpp', 'tests/RandomTests.cpp', 'utils/Random.cpp', 'tests/RingBufferTests.cpp', 'tests/SplitStringTests.cpp', 'tests/VectorTests.cpp', 'math/Vector.cpp', 'math/Matrix.cpp', 'tests/MatrixTests.cpp']
 
 executable('tests', 
     sources: sources,

+ 168 - 0
tests/MatrixTests.cpp

@@ -0,0 +1,168 @@
+#include <cmath>
+
+#include "tests/MatrixTests.h"
+#include "tests/Test.h"
+#include "utils/StringBuffer.h"
+#include "math/Matrix.h"
+
+const float eps = 0.0001f;
+
+template<int N>
+static void compareVectors(Test& test, const Vector<N>& wanted, const Vector<N>& actual, const char* text) {
+    for(int i = 0; i < N; i++) {
+        test.checkFloat(wanted[i], actual[i], eps, StringBuffer<50>(text).append(" (").append(i).append(")"));
+    }
+}
+
+static void testInit(Test& test) {
+    Matrix m;
+    const float* data = m.getValues();
+    for(int i = 0; i < 16; i++) {
+        int x = i % 4;
+        int y = i / 4;
+        test.checkEqual(static_cast<float> (x == y), data[i], StringBuffer<50>("init ").append(i));
+    }
+}
+
+static void testTranspose(Test& test) {
+    Matrix m;
+    m.set(0, Vector4(1.0f, 2.0f, 3.0f, 4.0f));
+    m.set(1, Vector4(5.0f, 6.0f, 7.0f, 8.0f));
+    m.set(2, Vector4(9.0f, 10.0f, 11.0f, 12.0f));
+    m.set(3, Vector4(13.0f, 14.0f, 15.0f, 16.0f));
+    Matrix t = m.transpose();
+    Matrix m2 = t.transpose();
+
+    const float* mp = m.getValues();
+    const float* tp = t.getValues();
+    for(int x = 0; x < 4; x++) {
+        for(int y = 0; y < 4; y++) {
+            StringBuffer<50> s("transpose ");
+            s.append(x).append(" | ").append(y);
+            test.checkEqual(mp[y * 4 + x], tp[x * 4 + y], s);
+        }
+    }
+    const float* mp2 = m2.getValues();
+    for(int i = 0; i < 16; i++) {
+        test.checkEqual(mp[i], mp2[i], StringBuffer<50>("transpose ").append(i));
+    }
+}
+
+static void testScale(Test& test) {
+    Matrix m;
+    m.scale(Vector3(2.0f, 3.0f, 4.0f));
+    compareVectors(test, Vector3(-8.0f, 18.0f, 28.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "scale");
+}
+
+static void testUniformScale(Test& test) {
+    Matrix m;
+    m.scale(2.0f);
+    compareVectors(test, Vector3(-8.0f, 12.0f, 14.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "uniform scale");
+}
+
+static void testTranslateX(Test& test) {
+    Matrix m;
+    m.translateX(5.0f);
+    compareVectors(test, Vector3(1.0f, 6.0f, 7.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "translate x");
+}
+
+static void testTranslateY(Test& test) {
+    Matrix m;
+    m.translateY(6.0f);
+    compareVectors(test, Vector3(-4.0f, 12.0f, 7.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "translate y");
+}
+
+static void testTranslateZ(Test& test) {
+    Matrix m;
+    m.translateZ(7.0f);
+    compareVectors(test, Vector3(-4.0f, 6.0f, 14.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "translate z");
+}
+
+static void testTranslate(Test& test) {
+    Matrix m;
+    m.translate(Vector3(1.0f, 2.0f, 3.0f));
+    compareVectors(test, Vector3(-3.0f, 8.0f, 10.0f), m * Vector3(-4.0f, 6.0f, 7.0f), "translate");
+}
+
+static void testCombination(Test& test) {
+    Matrix m;
+    m.scale(2.0f);
+    m.translateX(1.0f);
+    m.translateY(2.0f);
+    m.translateZ(3.0f);
+    m.translate(Vector3(-4.0f, 2.0, 3.0f));
+    m.scale(Vector3(2.0f, 3.0, 4.0f));
+    m.scale(0.5f);
+    compareVectors(test, Vector3(-1.0f, 9.0f, 16.0f), m * Vector3(1.0f, 1.0f, 1.0f), "combination");
+}
+
+static void testMatrixCombination(Test& test) {
+    Matrix a;
+    a.scale(2.0f);
+    a.translate(Vector3(1.0f, 2.0, 3.0f));
+
+    Matrix b;
+    b.scale(3.0f);
+    b.translate(Vector3(1.0f, 1.0, 1.0f));
+
+    Matrix c;
+    c.translate(Vector3(-1.0f, -2.0f, -3.0f));
+    c *= b * a;
+
+    compareVectors(test, Vector3(9.0f, 11.0f, 13.0f), c * Vector3(1.0f, 1.0f, 1.0f), "combination");
+}
+
+static void testRotateX(Test& test) {
+    Matrix m;
+    m.rotateX(90);
+    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f), m * Vector3(1.0f, 0.0f, 0.0f), "rotate x 1");
+    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f), m * Vector3(0.0f, 1.0f, 0.0f), "rotate x 2");
+    compareVectors(test, Vector3(0.0f, -1.0f, 0.0f), m * Vector3(0.0f, 0.0f, 1.0f), "rotate x 3");
+}
+
+static void testRotateY(Test& test) {
+    Matrix m;
+    m.rotateY(90);
+    compareVectors(test, Vector3(0.0f, 0.0f, -1.0f), m * Vector3(1.0f, 0.0f, 0.0f), "rotate y 1");
+    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f), m * Vector3(0.0f, 1.0f, 0.0f), "rotate y 2");
+    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f), m * Vector3(0.0f, 0.0f, 1.0f), "rotate y 3");
+}
+
+static void testRotateZ(Test& test) {
+    Matrix m;
+    m.rotateZ(90);
+    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f), m * Vector3(1.0f, 0.0f, 0.0f), "rotate z 1");
+    compareVectors(test, Vector3(-1.0f, 0.0f, 0.0f), m * Vector3(0.0f, 1.0f, 0.0f), "rotate z 2");
+    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f), m * Vector3(0.0f, 0.0f, 1.0f), "rotate z 3");
+}
+
+static void testToString(Test& test) {
+    StringBuffer<200> s;
+    Matrix m;
+    m.set(0, Vector4(1.0f, 2.0f, 3.0f, 4.0f));
+    m.set(1, Vector4(5.0f, 6.0f, 7.0f, 8.0f));
+    m.set(2, Vector4(9.0f, 10.0f, 11.0f, 12.0f));
+    m.set(3, Vector4(13.0f, 14.0f, 15.0f, 16.0f));
+    s.append(m);
+    test.checkEqual(StringBuffer<200>("[[1.00, 2.00, 3.00, 4.00], [5.00, 6.00, 7.00, 8.00], "
+            "[9.00, 10.00, 11.00, 12.00], [13.00, 14.00, 15.00, 16.00]]"), s, "to string");
+}
+
+void MatrixTests::test() {
+    Test test("Matrix");
+    testInit(test);
+    testScale(test);
+    testUniformScale(test);
+    testTranspose(test);
+    testTranslateX(test);
+    testTranslateY(test);
+    testTranslateZ(test);
+    testTranslate(test);
+    testCombination(test);
+    testMatrixCombination(test);
+    testRotateX(test);
+    testRotateY(test);
+    testRotateZ(test);
+    testToString(test);
+    test.finalize();
+}

+ 8 - 0
tests/MatrixTests.h

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

+ 6 - 1
tests/VectorTests.cpp

@@ -1,4 +1,4 @@
-#include <math.h>
+#include <cmath>
 
 #include "tests/VectorTests.h"
 #include "tests/Test.h"
@@ -18,6 +18,7 @@ static void testInitAndRead(Test& test) {
     Vector3 v1;
     Vector2 v2(1.0f, 2.0f);
     Vector3 v3(3.0f, 4.0f, 5.0f);
+    Vector4 v4(6.0f, 7.0f, 8.0f, 9.0f);
     test.checkEqual(0.0f, v1[0], "init 1");
     test.checkEqual(0.0f, v1[0], "init 2");
     test.checkEqual(0.0f, v1[0], "init 3");
@@ -26,6 +27,10 @@ static void testInitAndRead(Test& test) {
     test.checkEqual(3.0f, v3[0], "init 6");
     test.checkEqual(4.0f, v3[1], "init 7");
     test.checkEqual(5.0f, v3[2], "init 8");
+    test.checkEqual(6.0f, v4[0], "init 9");
+    test.checkEqual(7.0f, v4[1], "init 10");
+    test.checkEqual(8.0f, v4[2], "init 11");
+    test.checkEqual(9.0f, v4[3], "init 12");
 }
 
 static void testSetAngles(Test& test) {