3 Commits bf06ed795d ... 3a6c1c50d5

Author SHA1 Message Date
  Kajetan Johannes Hammerle 3a6c1c50d5 Quaternions and tests 3 weeks ago
  Kajetan Johannes Hammerle 1ebddec0de Matrix and tests 3 weeks ago
  Kajetan Johannes Hammerle d419395439 IntVector implementation and tests 3 weeks ago
14 changed files with 889 additions and 218 deletions
  1. 1 1
      .clangd
  2. 6 9
      CMakeLists.txt
  3. 34 0
      include/core/Matrix.h
  4. 24 0
      include/core/Quaternion.h
  5. 22 17
      include/core/Vector.h
  6. 172 0
      src/Matrix.c
  7. 63 0
      src/Quaternion.c
  8. 78 47
      src/Vector.c
  9. 2 2
      test/Main.c
  10. 20 0
      test/Test.c
  11. 18 0
      test/Test.h
  12. 225 0
      test/modules/MatrixTests.c
  13. 109 0
      test/modules/QuaternionTests.c
  14. 115 142
      test/modules/VectorTests.c

+ 1 - 1
.clangd

@@ -1,3 +1,3 @@
 CompileFlags:
-  Add: [-ferror-limit=0, -std=c2x, -DERROR_SIMULATOR=true]
+  Add: [-std=c2x, -DERROR_SIMULATOR=true]
   CompilationDatabase: ./build_debug/

+ 6 - 9
CMakeLists.txt

@@ -6,18 +6,16 @@ set(CMAKE_C_STANDARD 23)
 set(SRC
     "src/Buffer.c"
     "src/Logger.c"
+    "src/Matrix.c"
+    "src/Quaternion.c"
     "src/Random.c"
     "src/Utility.c"
     "src/Vector.c"
     #"src/BitArray.cpp"
     #"src/Box.cpp"
     #"src/Frustum.cpp"
-    #"src/Matrix.cpp"
-    #"src/Mutex.cpp"
     #"src/Plane.cpp"
-    #"src/Quaternion.cpp"
     #"src/SpinLock.cpp"
-    #"src/Thread.cpp"
     #"src/View.cpp"
 )
 
@@ -25,6 +23,8 @@ set(SRC_TESTS
     "test/Main.c"
     "test/Test.c"
     "test/modules/BufferTests.c"
+    "test/modules/MatrixTests.c"
+    "test/modules/QuaternionTests.c"
     "test/modules/RandomTests.c"
     "test/modules/UtilityTests.c"
     "test/modules/VectorTests.c"
@@ -36,9 +36,7 @@ set(SRC_TESTS
     #"test/modules/LinkedListTests.cpp"
     #"test/modules/ListTests.cpp"
     #"test/modules/MatrixStackTests.cpp"
-    #"test/modules/MatrixTests.cpp"
     #"test/modules/PlaneTests.cpp"
-    #"test/modules/QuaternionTests.cpp"
     #"test/modules/RingBufferTests.cpp"
     #"test/modules/StackTests.cpp"
     #"test/modules/ThreadTests.cpp"
@@ -159,6 +157,8 @@ target_sources(core PUBLIC
         ./include/core/Buffer.h
         ./include/core/Check.h
         ./include/core/Logger.h
+        ./include/core/Matrix.h
+        ./include/core/Quaternion.h
         ./include/core/Random.h
         ./include/core/Types.h
         ./include/core/Utility.h
@@ -169,14 +169,11 @@ target_sources(core PUBLIC
 #        ./include/core/HashMap.hpp
 #        ./include/core/LinkedList.hpp
 #        ./include/core/List.hpp
-#        ./include/core/Matrix.hpp
 #        ./include/core/MatrixStack.hpp
 #        ./include/core/Plane.hpp
 #        ./include/core/ProbingHashMap.hpp
-#        ./include/core/Quaternion.hpp
 #        ./include/core/RingBuffer.hpp
 #        ./include/core/Stack.hpp
-#        ./include/core/Thread.hpp
 #        ./include/core/Vector.hpp
 #        ./include/core/View.hpp
 )

+ 34 - 0
include/core/Matrix.h

@@ -0,0 +1,34 @@
+#ifndef CORE_MATRIX_H
+#define CORE_MATRIX_H
+
+#include "core/Quaternion.h"
+#include "core/Vector.h"
+
+typedef struct {
+    CoreVector4 data[4];
+} CoreMatrix;
+
+#define CORE_ZERO_MATRIX ((CoreMatrix){0})
+#define CORE_UNIT_MATRIX                                                       \
+    ((CoreMatrix){{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}})
+
+CoreMatrix* coreTransposeMatrix(CoreMatrix* m);
+CoreMatrix* coreMulSetMatrix(CoreMatrix* m, const CoreMatrix* a);
+CoreMatrix* coreMulMatrix(CoreMatrix* m, const CoreMatrix* a,
+                          const CoreMatrix* b);
+CoreVector3* coreMulMatrixV3(CoreVector3* v, const CoreMatrix* m,
+                             const CoreVector3* a);
+CoreMatrix* coreScaleMatrix(CoreMatrix* m, const CoreVector3* v);
+CoreMatrix* coreScaleMatrixF(CoreMatrix* m, float f);
+CoreMatrix* coreTranslateMatrix(CoreMatrix* m, const CoreVector3* v);
+CoreMatrix* coreTranslateMatrixX(CoreMatrix* m, float tx);
+CoreMatrix* coreTranslateMatrixY(CoreMatrix* m, float ty);
+CoreMatrix* coreTranslateMatrixZ(CoreMatrix* m, float tz);
+CoreMatrix* coreTranslateMatrixTo(CoreMatrix* m, const CoreVector3* v);
+CoreMatrix* coreRotateMatrixX(CoreMatrix* m, float degrees);
+CoreMatrix* coreRotateMatrixY(CoreMatrix* m, float degrees);
+CoreMatrix* coreRotateMatrixZ(CoreMatrix* m, float degrees);
+CoreMatrix* coreRotateMatrix(CoreMatrix* m, const CoreQuaternion* q);
+size_t coreToStringMatrix(const CoreMatrix* m, char* buffer, size_t n);
+
+#endif

+ 24 - 0
include/core/Quaternion.h

@@ -0,0 +1,24 @@
+#ifndef CORE_QUATERNION_H
+#define CORE_QUATERNION_H
+
+#include "core/Vector.h"
+
+typedef struct {
+    CoreVector3 xyz;
+    float w;
+} CoreQuaternion;
+
+#define CORE_UNIT_QUATERNION ((CoreQuaternion){{0.0f, 0.0f, 0.0f}, 1.0f})
+
+CoreQuaternion* coreAxisAngleQ(CoreQuaternion* q, const CoreVector3* axis,
+                               float angle);
+CoreQuaternion* coreLerpQ(CoreQuaternion* q, const CoreQuaternion* a, float f,
+                          const CoreQuaternion* b);
+CoreQuaternion* coreMulSetQ(CoreQuaternion* q, const CoreQuaternion* other);
+CoreQuaternion* coreMulQ(CoreQuaternion* q, const CoreQuaternion* a,
+                         const CoreQuaternion* b);
+CoreVector3* coreMulQV3(CoreVector3* r, const CoreQuaternion* q,
+                        const CoreVector3* v);
+size_t coreToStringQ(const CoreQuaternion* q, char* buffer, size_t n);
+
+#endif

+ 22 - 17
include/core/Vector.h

@@ -6,7 +6,7 @@
 #define CORE_VECTOR_OP2(name) name *r, const name *a
 #define CORE_VECTOR_OP3(name) CORE_VECTOR_OP2(name), const name* b
 
-#define CORE_DEFINE_VECTOR(N, name, sname, type, factor)                       \
+#define CORE_DEFINE_VECTOR(N, name, sname, type)                               \
     typedef struct {                                                           \
         type data[N];                                                          \
     } name;                                                                    \
@@ -18,24 +18,29 @@
     name* coreMul##sname(CORE_VECTOR_OP3(name));                               \
     name* coreDivSet##sname(CORE_VECTOR_OP2(name));                            \
     name* coreDiv##sname(CORE_VECTOR_OP3(name));                               \
-    name* coreMulSet##sname##factor(name* r, type f);                          \
-    name* coreMul##sname##factor(name* r, const name* a, type f);              \
-    name* coreDivSet##sname##factor(name* r, type f);                          \
-    name* coreDiv##sname##factor(name* r, const name* a, type f);              \
+    name* coreMulSet##sname##F(name* r, type f);                               \
+    name* coreMul##sname##F(name* r, const name* a, type f);                   \
+    name* coreDivSet##sname##F(name* r, type f);                               \
+    name* coreDiv##sname##F(name* r, const name* a, type f);                   \
     name* coreInvertSet##sname(name* r);                                       \
     name* coreInvert##sname(name* r, const name* a);                           \
-    type coreDot##sname(const name* a, const name* b);                         \
-    type coreSquareLength##sname(const name* a);                               \
-    type coreLength##sname(const name* a);                                     \
-    name* coreNormalize##sname(name* r);                                       \
-    void coreToString##sname(const name* a, char* buffer, size_t n);
-
-CORE_DEFINE_VECTOR(2, CoreVector2, V2, float, F)
-CORE_DEFINE_VECTOR(3, CoreVector3, V3, float, F)
-CORE_DEFINE_VECTOR(4, CoreVector4, V4, float, F)
-CORE_DEFINE_VECTOR(2, CoreIntVector2, IV2, int, I)
-CORE_DEFINE_VECTOR(3, CoreIntVector3, IV3, int, I)
-CORE_DEFINE_VECTOR(4, CoreIntVector4, IV4, int, I)
+    size_t coreToString##sname(const name* a, char* buffer, size_t n);
+
+#define CORE_DEFINE_FVECTOR(N, name)                                           \
+    float coreDotV##N(const name* a, const name* b);                           \
+    float coreSquareLengthV##N(const name* a);                                 \
+    float coreLengthV##N(const name* a);                                       \
+    name* coreNormalizeV##N(name* r);
+
+CORE_DEFINE_VECTOR(2, CoreVector2, V2, float)
+CORE_DEFINE_VECTOR(3, CoreVector3, V3, float)
+CORE_DEFINE_VECTOR(4, CoreVector4, V4, float)
+CORE_DEFINE_FVECTOR(2, CoreVector2)
+CORE_DEFINE_FVECTOR(3, CoreVector3)
+CORE_DEFINE_FVECTOR(4, CoreVector4)
+CORE_DEFINE_VECTOR(2, CoreIntVector2, IV2, int)
+CORE_DEFINE_VECTOR(3, CoreIntVector3, IV3, int)
+CORE_DEFINE_VECTOR(4, CoreIntVector4, IV4, int)
 
 CoreVector3* coreAngles(CoreVector3* r, float lengthAngle, float widthAngle);
 CoreVector3* coreCross(CORE_VECTOR_OP3(CoreVector3));

+ 172 - 0
src/Matrix.c

@@ -0,0 +1,172 @@
+#include "core/Matrix.h"
+
+#include <math.h>
+#include <stdio.h>
+
+#include "core/Utility.h"
+
+CoreMatrix* coreTransposeMatrix(CoreMatrix* m) {
+    CoreMatrix c;
+    for(size_t x = 0; x < 4; x++) {
+        for(size_t y = 0; y < 4; y++) {
+            c.data[x].data[y] = m->data[y].data[x];
+        }
+    }
+    *m = c;
+    return m;
+}
+
+#define CV40 (&(CoreVector4){0})
+
+CoreMatrix* coreMulSetMatrix(CoreMatrix* m, const CoreMatrix* a) {
+    for(int i = 0; i < 4; i++) {
+        CoreVector4 d = {0};
+        coreAddSetV4(&d, coreMulV4F(CV40, a->data + 0, m->data[i].data[0]));
+        coreAddSetV4(&d, coreMulV4F(CV40, a->data + 1, m->data[i].data[1]));
+        coreAddSetV4(&d, coreMulV4F(CV40, a->data + 2, m->data[i].data[2]));
+        coreAddSetV4(&d, coreMulV4F(CV40, a->data + 3, m->data[i].data[3]));
+        m->data[i] = d;
+    }
+    return m;
+}
+
+CoreMatrix* coreMulMatrix(CoreMatrix* m, const CoreMatrix* a,
+                          const CoreMatrix* b) {
+    *m = *a;
+    coreMulSetMatrix(m, b);
+    return m;
+}
+
+CoreVector3* coreMulMatrixV3(CoreVector3* v, const CoreMatrix* m,
+                             const CoreVector3* a) {
+    CoreVector4 v4 = {a->data[0], a->data[1], a->data[2], 1.0f};
+    v->data[0] = coreDotV4(m->data + 0, &v4);
+    v->data[1] = coreDotV4(m->data + 1, &v4);
+    v->data[2] = coreDotV4(m->data + 2, &v4);
+    coreMulSetV3F(v, 1.0f / coreDotV4(m->data + 3, &v4));
+    return v;
+}
+
+CoreMatrix* coreScaleMatrix(CoreMatrix* m, const CoreVector3* v) {
+    coreMulSetV4F(m->data + 0, v->data[0]);
+    coreMulSetV4F(m->data + 1, v->data[1]);
+    coreMulSetV4F(m->data + 2, v->data[2]);
+    return m;
+}
+
+CoreMatrix* coreScaleMatrixF(CoreMatrix* m, float f) {
+    return coreScaleMatrix(m, &(CoreVector3){f, f, f});
+}
+
+CoreMatrix* coreTranslateMatrix(CoreMatrix* m, const CoreVector3* v) {
+    coreTranslateMatrixX(m, v->data[0]);
+    coreTranslateMatrixY(m, v->data[1]);
+    coreTranslateMatrixZ(m, v->data[2]);
+    return m;
+}
+
+CoreMatrix* coreTranslateMatrixX(CoreMatrix* m, float tx) {
+    coreAddSetV4(m->data + 0, coreMulV4F(CV40, m->data + 3, tx));
+    return m;
+}
+
+CoreMatrix* coreTranslateMatrixY(CoreMatrix* m, float ty) {
+    coreAddSetV4(m->data + 1, coreMulV4F(CV40, m->data + 3, ty));
+    return m;
+}
+
+CoreMatrix* coreTranslateMatrixZ(CoreMatrix* m, float tz) {
+    coreAddSetV4(m->data + 2, coreMulV4F(CV40, m->data + 3, tz));
+    return m;
+}
+
+CoreMatrix* coreTranslateMatrixTo(CoreMatrix* m, const CoreVector3* v) {
+    m->data[0] = (CoreVector4){1.0f, 0.0f, 0.0f, v->data[0]};
+    m->data[1] = (CoreVector4){0.0f, 1.0f, 0.0f, v->data[1]};
+    m->data[2] = (CoreVector4){0.0f, 0.0f, 1.0f, v->data[2]};
+    m->data[3] = (CoreVector4){0.0f, 0.0f, 0.0f, 1.0f};
+    return m;
+}
+
+static CoreMatrix* rotate(CoreMatrix* m, float degrees, int a, int b) {
+    degrees = coreDegreeToRadian(degrees);
+    float sin = sinf(degrees);
+    float cos = cosf(degrees);
+    CoreVector4 v = m->data[a];
+    coreSubV4(m->data + a, coreMulV4F(CV40, &v, cos),
+              coreMulV4F(CV40, m->data + b, sin));
+    coreAddV4(m->data + b, coreMulV4F(CV40, &v, sin),
+              coreMulV4F(CV40, m->data + b, cos));
+    return m;
+}
+
+CoreMatrix* coreRotateMatrixX(CoreMatrix* m, float degrees) {
+    return rotate(m, degrees, 1, 2);
+}
+
+CoreMatrix* coreRotateMatrixY(CoreMatrix* m, float degrees) {
+    return rotate(m, -degrees, 0, 2);
+}
+
+CoreMatrix* coreRotateMatrixZ(CoreMatrix* m, float degrees) {
+    return rotate(m, degrees, 0, 1);
+}
+
+CoreMatrix* coreRotateMatrix(CoreMatrix* m, const CoreQuaternion* q) {
+    CoreVector3 a;
+    CoreVector3 b;
+    CoreVector3 c;
+    CoreVector3 d;
+    coreMulQV3(&a, q,
+               &(CoreVector3){m->data[0].data[0], m->data[1].data[0],
+                              m->data[2].data[0]});
+    coreMulQV3(&b, q,
+               &(CoreVector3){m->data[0].data[1], m->data[1].data[1],
+                              m->data[2].data[1]});
+    coreMulQV3(&c, q,
+               &(CoreVector3){m->data[0].data[2], m->data[1].data[2],
+                              m->data[2].data[2]});
+    coreMulQV3(&d, q,
+               &(CoreVector3){m->data[0].data[3], m->data[1].data[3],
+                              m->data[2].data[3]});
+    m->data[0] = (CoreVector4){a.data[0], b.data[0], c.data[0], d.data[0]};
+    m->data[1] = (CoreVector4){a.data[1], b.data[1], c.data[1], d.data[1]};
+    m->data[2] = (CoreVector4){a.data[2], b.data[2], c.data[2], d.data[2]};
+
+    //    Vector3 a = q * Vector3(data[0][0], data[1][0], data[2][0]);
+    //    Vector3 b = q * Vector3(data[0][1], data[1][1], data[2][1]);
+    //    Vector3 c = q * Vector3(data[0][2], data[1][2], data[2][2]);
+    //    Vector3 d = q * Vector3(data[0][3], data[1][3], data[2][3]);
+    //    set(0, Vector4(a[0], b[0], c[0], d[0]));
+    //    set(1, Vector4(a[1], b[1], c[1], d[1]));
+    //    set(2, Vector4(a[2], b[2], c[2], d[2]));
+    return m;
+}
+
+static void add(size_t* w, char** buffer, size_t* n, size_t shift) {
+    *w += shift;
+    if(*n > shift) {
+        *buffer += shift;
+        *n -= shift;
+    } else {
+        *n = 0;
+    }
+}
+
+static void addI(size_t* w, char** buffer, size_t* n, int shift) {
+    add(w, buffer, n, shift < 0 ? 0 : (size_t)shift);
+}
+
+size_t coreToStringMatrix(const CoreMatrix* m, char* buffer, size_t n) {
+    size_t w = 0;
+    addI(&w, &buffer, &n, snprintf(buffer, n, "["));
+    add(&w, &buffer, &n, coreToStringV4(m->data + 0, buffer, n));
+    addI(&w, &buffer, &n, snprintf(buffer, n, ", "));
+    add(&w, &buffer, &n, coreToStringV4(m->data + 1, buffer, n));
+    addI(&w, &buffer, &n, snprintf(buffer, n, ", "));
+    add(&w, &buffer, &n, coreToStringV4(m->data + 2, buffer, n));
+    addI(&w, &buffer, &n, snprintf(buffer, n, ", "));
+    add(&w, &buffer, &n, coreToStringV4(m->data + 3, buffer, n));
+    addI(&w, &buffer, &n, snprintf(buffer, n, "]"));
+    return w;
+}

+ 63 - 0
src/Quaternion.c

@@ -0,0 +1,63 @@
+#include "core/Quaternion.h"
+
+#include <math.h>
+#include <stdio.h>
+
+#include "core/Utility.h"
+
+#define CV30 (&(CoreVector3){0})
+
+CoreQuaternion* coreAxisAngleQ(CoreQuaternion* q, const CoreVector3* axis,
+                               float angle) {
+    q->xyz = *axis;
+    coreNormalizeV3(&q->xyz);
+    angle = coreDegreeToRadian(angle) * 0.5f;
+    q->w = cosf(angle);
+    coreMulSetV3F(&q->xyz, sinf(angle));
+    return q;
+}
+
+CoreQuaternion* coreLerpQ(CoreQuaternion* q, const CoreQuaternion* a, float f,
+                          const CoreQuaternion* b) {
+    coreAddV3(&q->xyz, coreMulV3F(CV30, &a->xyz, 1.0f - f),
+              coreMulV3F(CV30, &b->xyz, f));
+    q->w = a->w * (1.0f - f) + b->w * f;
+    float iLength = 1.0f / sqrtf(coreSquareLengthV3(&q->xyz) + q->w * q->w);
+    coreMulSetV3F(&q->xyz, iLength);
+    q->w *= iLength;
+    return q;
+}
+
+CoreQuaternion* coreMulSetQ(CoreQuaternion* q, const CoreQuaternion* other) {
+    float dot = coreDotV3(&q->xyz, &other->xyz);
+    coreAddV3(&q->xyz, coreMulV3F(CV30, &other->xyz, q->w),
+              coreAddV3(CV30, coreMulV3F(CV30, &q->xyz, other->w),
+                        coreCross(CV30, &q->xyz, &other->xyz)));
+    q->w = q->w * other->w - dot;
+    return q;
+}
+
+CoreQuaternion* coreMulQ(CoreQuaternion* q, const CoreQuaternion* a,
+                         const CoreQuaternion* b) {
+    *q = *a;
+    coreMulSetQ(q, b);
+    return q;
+}
+
+CoreVector3* coreMulQV3(CoreVector3* r, const CoreQuaternion* q,
+                        const CoreVector3* v) {
+    CoreVector3 qv;
+    coreAddV3(&qv, coreMulV3F(CV30, v, q->w), coreCross(CV30, &q->xyz, v));
+
+    coreAddV3(r, coreMulV3F(CV30, &q->xyz, coreDotV3(&q->xyz, v)),
+              coreSubV3(CV30, coreMulV3F(CV30, &qv, q->w),
+                        coreCross(CV30, &qv, &q->xyz)));
+    return r;
+}
+
+size_t coreToStringQ(const CoreQuaternion* q, char* buffer, size_t n) {
+    int w = snprintf(buffer, n, "(%.3f i + %.3f j + %.3f k + %.3f)",
+                     (double)q->xyz.data[0], (double)q->xyz.data[1],
+                     (double)q->xyz.data[2], (double)q->w);
+    return w < 0 ? 0 : (size_t)w;
+}

+ 78 - 47
src/Vector.c

@@ -5,9 +5,12 @@
 
 #include "core/Utility.h"
 
-#define V2 CoreVector2
-#define V3 CoreVector3
-#define V4 CoreVector4
+typedef CoreVector2 V2;
+typedef CoreVector3 V3;
+typedef CoreVector4 V4;
+typedef CoreIntVector2 IV2;
+typedef CoreIntVector3 IV3;
+typedef CoreIntVector4 IV4;
 
 V3* coreAngles(V3* r, float lengthAngle, float widthAngle) {
     lengthAngle = coreDegreeToRadian(lengthAngle);
@@ -31,85 +34,86 @@ V3* coreCross(V3* r, const V3* a, const V3* b) {
     return r;
 }
 
-#define VECTOR_IMPL(T, N, SN, F, CN)                                           \
-    T* coreAddSet##SN(T* r, const T* a) {                                      \
-        return coreAdd##SN(r, r, a);                                           \
+#define VECTOR_IMPL(T, N, FT)                                                  \
+    T* coreAddSet##T(T* r, const T* a) {                                       \
+        return coreAdd##T(r, r, a);                                            \
     }                                                                          \
                                                                                \
-    T* coreAdd##SN(T* r, const T* a, const T* b) {                             \
+    T* coreAdd##T(T* r, const T* a, const T* b) {                              \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] + b->data[i];                              \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreSubSet##SN(T* r, const T* a) {                                      \
-        return coreSub##SN(r, r, a);                                           \
+    T* coreSubSet##T(T* r, const T* a) {                                       \
+        return coreSub##T(r, r, a);                                            \
     }                                                                          \
                                                                                \
-    T* coreSub##SN(T* r, const T* a, const T* b) {                             \
+    T* coreSub##T(T* r, const T* a, const T* b) {                              \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] - b->data[i];                              \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreMulSet##SN(T* r, const T* a) {                                      \
-        return coreMul##SN(r, r, a);                                           \
+    T* coreMulSet##T(T* r, const T* a) {                                       \
+        return coreMul##T(r, r, a);                                            \
     }                                                                          \
                                                                                \
-    T* coreMul##SN(T* r, const T* a, const T* b) {                             \
+    T* coreMul##T(T* r, const T* a, const T* b) {                              \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] * b->data[i];                              \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreDivSet##SN(T* r, const T* a) {                                      \
-        return coreDiv##SN(r, r, a);                                           \
+    T* coreDivSet##T(T* r, const T* a) {                                       \
+        return coreDiv##T(r, r, a);                                            \
     }                                                                          \
                                                                                \
-    T* coreDiv##SN(T* r, const T* a, const T* b) {                             \
+    T* coreDiv##T(T* r, const T* a, const T* b) {                              \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] / b->data[i];                              \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreMulSet##SN##F(T* r, float f) {                                      \
-        return coreMul##SN##F(r, r, f);                                        \
+    T* coreMulSet##T##F(T* r, FT f) {                                          \
+        return coreMul##T##F(r, r, f);                                         \
     }                                                                          \
                                                                                \
-    T* coreMul##SN##F(T* r, const T* a, float f) {                             \
+    T* coreMul##T##F(T* r, const T* a, FT f) {                                 \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] * f;                                       \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreDivSet##SN##F(T* r, float f) {                                      \
-        return coreDiv##SN##F(r, r, f);                                        \
+    T* coreDivSet##T##F(T* r, FT f) {                                          \
+        return coreDiv##T##F(r, r, f);                                         \
     }                                                                          \
                                                                                \
-    T* coreDiv##SN##F(T* r, const T* a, float f) {                             \
+    T* coreDiv##T##F(T* r, const T* a, FT f) {                                 \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = a->data[i] / f;                                       \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    T* coreInvertSet##SN(T* r) {                                               \
-        return coreInvert##SN(r, r);                                           \
+    T* coreInvertSet##T(T* r) {                                                \
+        return coreInvert##T(r, r);                                            \
     }                                                                          \
                                                                                \
-    T* coreInvert##SN(T* r, const T* a) {                                      \
+    T* coreInvert##T(T* r, const T* a) {                                       \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = -a->data[i];                                          \
         }                                                                      \
         return r;                                                              \
-    }                                                                          \
-                                                                               \
-    float coreDot##SN(const T* a, const T* b) {                                \
+    }
+
+#define VECTOR_IMPL_FLOAT(T, N, CN)                                            \
+    float coreDot##T(const T* a, const T* b) {                                 \
         float length = 0;                                                      \
         for(int i = 0; i < N; i++) {                                           \
             length += a->data[i] * b->data[i];                                 \
@@ -117,46 +121,73 @@ V3* coreCross(V3* r, const V3* a, const V3* b) {
         return length;                                                         \
     }                                                                          \
                                                                                \
-    float coreSquareLength##SN(const T* a) {                                   \
-        return coreDot##SN(a, a);                                              \
+    float coreSquareLength##T(const T* a) {                                    \
+        return coreDot##T(a, a);                                               \
     }                                                                          \
                                                                                \
-    float coreLength##SN(const T* a) {                                         \
-        return sqrtf(coreSquareLength##SN(a));                                 \
+    float coreLength##T(const T* a) {                                          \
+        return sqrtf(coreSquareLength##T(a));                                  \
     }                                                                          \
                                                                                \
-    T* coreNormalize##SN(T* r) {                                               \
-        return coreMulSet##SN##F(r, 1.0f / coreLength##SN(r));                 \
+    T* coreNormalize##T(T* r) {                                                \
+        return coreMulSet##T##F(r, 1.0f / coreLength##T(r));                   \
     }                                                                          \
                                                                                \
-    T* coreConvertI##SN(T* r, const CN* a) {                                   \
+    T* coreConvertI##T(T* r, const CN* a) {                                    \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = (float)a->data[i];                                    \
         }                                                                      \
         return r;                                                              \
     }                                                                          \
                                                                                \
-    CN* coreConvert##SN(CN* r, const T* a) {                                   \
+    CN* coreConvert##T(CN* r, const T* a) {                                    \
         for(int i = 0; i < N; i++) {                                           \
             r->data[i] = (int)a->data[i];                                      \
         }                                                                      \
         return r;                                                              \
     }
 
-VECTOR_IMPL(CoreVector2, 2, V2, F, CoreIntVector2)
-VECTOR_IMPL(CoreVector3, 3, V3, F, CoreIntVector3)
-VECTOR_IMPL(CoreVector4, 4, V4, F, CoreIntVector4)
+VECTOR_IMPL(V2, 2, float)
+VECTOR_IMPL(V3, 3, float)
+VECTOR_IMPL(V4, 4, float)
+VECTOR_IMPL(IV2, 2, int)
+VECTOR_IMPL(IV3, 3, int)
+VECTOR_IMPL(IV4, 4, int)
+VECTOR_IMPL_FLOAT(V2, 2, IV2)
+VECTOR_IMPL_FLOAT(V3, 3, IV3)
+VECTOR_IMPL_FLOAT(V4, 4, IV4)
+
+static size_t safeCast(int s) {
+    return s < 0 ? 0 : (size_t)s;
+}
+
+size_t coreToStringV2(const V2* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%.3f, %.3f]", (double)a->data[0],
+                             (double)a->data[1]));
+}
+
+size_t coreToStringV3(const V3* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%.3f, %.3f, %.3f]",
+                             (double)a->data[0], (double)a->data[1],
+                             (double)a->data[2]));
+}
+
+size_t coreToStringV4(const V4* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%.3f, %.3f, %.3f, %.3f]",
+                             (double)a->data[0], (double)a->data[1],
+                             (double)a->data[2], (double)a->data[3]));
+}
 
-void coreToStringV2(const V2* a, char* buffer, size_t n) {
-    snprintf(buffer, n, "[%.3f, %.3f]", (double)a->data[0], (double)a->data[1]);
+size_t coreToStringIV2(const IV2* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%d, %d]", a->data[0], a->data[1]));
 }
 
-void coreToStringV3(const V3* a, char* buffer, size_t n) {
-    snprintf(buffer, n, "[%.3f, %.3f, %.3f]", (double)a->data[0],
-             (double)a->data[1], (double)a->data[2]);
+size_t coreToStringIV3(const IV3* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%d, %d, %d]", a->data[0], a->data[1],
+                             a->data[2]));
 }
 
-void coreToStringV4(const V4* a, char* buffer, size_t n) {
-    snprintf(buffer, n, "[%.3f, %.3f, %.3f, %.3f]", (double)a->data[0],
-             (double)a->data[1], (double)a->data[2], (double)a->data[3]);
+size_t coreToStringIV4(const IV4* a, char* buffer, size_t n) {
+    return safeCast(snprintf(buffer, n, "[%d, %d, %d, %d]", a->data[0],
+                             a->data[1], a->data[2], a->data[3]));
 }

+ 2 - 2
test/Main.c

@@ -40,9 +40,9 @@ int main(int argAmount, const char** args) {
     // coreTestLinkedList(light);
     // coreTestList(light);
     // coreTestMatrixStack(light);
-    // coreTestMatrix();
+    coreTestMatrix();
     // coreTestPlane();
-    // coreTestQuaternion();
+    coreTestQuaternion();
     coreTestRandom(light);
     // coreTestRingBuffer();
     // coreTestStack(light);

+ 20 - 0
test/Test.c

@@ -121,3 +121,23 @@ bool coreTestNotNull(CORE_TEST_ARGS, const void* actual) {
     puts(CORE_TERMINAL_RESET);
     return false;
 }
+
+bool coreTestVectorN(CORE_TEST_ARGS, const float* wanted, const float* actual,
+                     size_t n) {
+    for(size_t i = 0; i < n; i++) {
+        if(!coreTestFloat(file, line, wanted[i], actual[i], 0.01f)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool coreTestIntVectorN(CORE_TEST_ARGS, const int* wanted, const int* actual,
+                        size_t n) {
+    for(size_t i = 0; i < n; i++) {
+        if(!coreTestInt(file, line, wanted[i], actual[i])) {
+            return false;
+        }
+    }
+    return true;
+}

+ 18 - 0
test/Test.h

@@ -38,4 +38,22 @@ bool coreTestNotNull(CORE_TEST_ARGS, const void* p);
 #define CORE_TEST_NULL(actual) coreTestNull(__FILE__, __LINE__, actual)
 #define CORE_TEST_NOT_NULL(actual) coreTestNotNull(__FILE__, __LINE__, actual)
 
+bool coreTestVectorN(CORE_TEST_ARGS, const float* wanted, const float* actual,
+                     size_t n);
+#define CORE_TEST_V2(wanted, actual)                                           \
+    coreTestVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 2)
+#define CORE_TEST_V3(wanted, actual)                                           \
+    coreTestVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 3)
+#define CORE_TEST_V4(wanted, actual)                                           \
+    coreTestVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 4)
+
+bool coreTestIntVectorN(CORE_TEST_ARGS, const int* wanted, const int* actual,
+                        size_t n);
+#define CORE_TEST_IV2(wanted, actual)                                          \
+    coreTestIntVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 2)
+#define CORE_TEST_IV3(wanted, actual)                                          \
+    coreTestIntVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 3)
+#define CORE_TEST_IV4(wanted, actual)                                          \
+    coreTestIntVectorN(__FILE__, __LINE__, (wanted)->data, (actual)->data, 4)
+
 #endif

+ 225 - 0
test/modules/MatrixTests.c

@@ -0,0 +1,225 @@
+#include "../Tests.h"
+#include "core/Matrix.h"
+
+typedef CoreMatrix Matrix;
+typedef CoreVector3 V3;
+#define CV3(a, b, c) (&(V3){a, b, c})
+#define CV30 CV3(0.0f, 0.0f, 0.0f)
+
+static void testInit() {
+    Matrix m = CORE_UNIT_MATRIX;
+    const float* data = (float*)&m;
+    for(int i = 0; i < 16; i++) {
+        int x = i % 4;
+        int y = i / 4;
+        CORE_TEST_FLOAT(x == y, data[i], 0.0f);
+    }
+}
+
+static void testTranspose() {
+    Matrix m;
+    float* data = (float*)&m;
+    for(int i = 0; i < 16; i++) {
+        data[i] = (float)(i + 1);
+    }
+    Matrix t = m;
+    coreTransposeMatrix(&t);
+    Matrix m2 = t;
+    coreTransposeMatrix(&m2);
+
+    const float* mp = (float*)&m;
+    const float* tp = (float*)&t;
+    for(int x = 0; x < 4; x++) {
+        for(int y = 0; y < 4; y++) {
+            CORE_TEST_FLOAT(mp[y * 4 + x], tp[x * 4 + y], 0.0f);
+        }
+    }
+    const float* mp2 = (float*)&m2;
+    for(int i = 0; i < 16; i++) {
+        CORE_TEST_FLOAT(mp[i], mp2[i], 0.0f);
+    }
+}
+
+static void testScale() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreScaleMatrix(&m, CV3(2.0f, 3.0f, 4.0f));
+    CORE_TEST_V3(CV3(-8.0f, 18.0f, 28.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testUniformScale() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreScaleMatrixF(&m, 2.0f);
+    CORE_TEST_V3(CV3(-8.0f, 12.0f, 14.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testTranslateX() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreTranslateMatrixX(&m, 5.0f);
+    CORE_TEST_V3(CV3(1.0f, 6.0f, 7.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testTranslateY() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreTranslateMatrixY(&m, 6.0f);
+    CORE_TEST_V3(CV3(-4.0f, 12.0f, 7.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testTranslateZ() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreTranslateMatrixZ(&m, 7.0f);
+    CORE_TEST_V3(CV3(-4.0f, 6.0f, 14.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testTranslate() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreTranslateMatrix(&m, CV3(1.0f, 2.0f, 3.0f));
+    CORE_TEST_V3(CV3(-3.0f, 8.0f, 10.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(-4.0f, 6.0f, 7.0f)));
+}
+
+static void testTranslateTo() {
+    char buffer[1024];
+    Matrix m;
+    for(int i = 0; i < 16; i++) {
+        ((float*)&m)[i] = (float)i + 1.0f;
+    }
+    coreTranslateMatrixTo(&m, CV3(6.0f, 8.0f, 9.0f));
+    coreToStringMatrix(&m, buffer, sizeof(buffer));
+    CORE_TEST_STRING(
+        "[[1.000, 0.000, 0.000, 6.000], [0.000, 1.000, 0.000, 8.000], "
+        "[0.000, 0.000, 1.000, 9.000], [0.000, 0.000, 0.000, 1.000]]",
+        buffer);
+}
+
+static void testCombination() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreScaleMatrixF(&m, 2.0f);
+    coreTranslateMatrixX(&m, 1.0f);
+    coreTranslateMatrixY(&m, 2.0f);
+    coreTranslateMatrixZ(&m, 3.0f);
+    coreTranslateMatrix(&m, CV3(-4.0f, 2.0f, 3.0f));
+    coreScaleMatrix(&m, CV3(2.0f, 3.0f, 4.0f));
+    coreScaleMatrixF(&m, 0.5f);
+    CORE_TEST_V3(CV3(-1.0f, 9.0f, 16.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(1.0f, 1.0f, 1.0f)));
+}
+
+static void testMatrixCombination() {
+    Matrix a = CORE_UNIT_MATRIX;
+    coreScaleMatrixF(&a, 2.0f);
+    coreTranslateMatrix(&a, CV3(1.0f, 2.0f, 3.0f));
+
+    Matrix b = CORE_UNIT_MATRIX;
+    coreScaleMatrixF(&b, 3.0f);
+    coreTranslateMatrix(&b, CV3(1.0f, 1.0f, 1.0f));
+
+    Matrix c = CORE_UNIT_MATRIX;
+    coreTranslateMatrix(&c, CV3(-1.0f, -2.0f, -3.0f));
+
+    coreMulSetMatrix(&c, coreMulMatrix(&CORE_ZERO_MATRIX, &b, &a));
+
+    CORE_TEST_V3(CV3(9.0f, 11.0f, 13.0f),
+                 coreMulMatrixV3(CV30, &c, CV3(1.0f, 1.0f, 1.0f)));
+}
+
+static void testRotateX() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreRotateMatrixX(&m, 90.0f);
+    CORE_TEST_V3(CV3(1.0f, 0.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(1.0f, 0.0f, 0.0f)));
+    CORE_TEST_V3(CV3(0.0f, 0.0f, 1.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 1.0f, 0.0f)));
+    CORE_TEST_V3(CV3(0.0f, -1.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 0.0f, 1.0f)));
+}
+
+static void testRotateY() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreRotateMatrixY(&m, 90.0f);
+    CORE_TEST_V3(CV3(0.0f, 0.0f, -1.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(1.0f, 0.0f, 0.0f)));
+    CORE_TEST_V3(CV3(0.0f, 1.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 1.0f, 0.0f)));
+    CORE_TEST_V3(CV3(1.0f, 0.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 0.0f, 1.0f)));
+}
+
+static void testRotateZ() {
+    Matrix m = CORE_UNIT_MATRIX;
+    coreRotateMatrixZ(&m, 90.0f);
+    CORE_TEST_V3(CV3(0.0f, 1.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(1.0f, 0.0f, 0.0f)));
+    CORE_TEST_V3(CV3(-1.0f, 0.0f, 0.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 1.0f, 0.0f)));
+    CORE_TEST_V3(CV3(0.0f, 0.0f, 1.0f),
+                 coreMulMatrixV3(CV30, &m, CV3(0.0f, 0.0f, 1.0f)));
+}
+
+static void testQuaternionMatrix() {
+    CoreQuaternion q1 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q1, CV3(1.0f, 0.0f, 0.0f), 48.0f);
+    CoreQuaternion q2 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q2, CV3(0.0f, 1.0f, 0.0f), 52.0f);
+    CoreQuaternion q3 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q3, CV3(0.0f, 0.0f, 1.0f), 60.0f);
+
+    Matrix m = CORE_UNIT_MATRIX;
+    coreTranslateMatrix(&m, CV3(1.0f, 2.0f, 3.0f));
+    coreRotateMatrix(&m, &q1);
+    coreRotateMatrix(&m, &q2);
+    coreRotateMatrix(&m, &q3);
+    coreTranslateMatrix(&m, CV3(1.0f, 2.0f, 3.0f));
+
+    Matrix check = CORE_UNIT_MATRIX;
+    coreTranslateMatrix(&check, CV3(1.0f, 2.0f, 3.0f));
+    coreRotateMatrixX(&check, 48.0f);
+    coreRotateMatrixY(&check, 52.0f);
+    coreRotateMatrixZ(&check, 60.0f);
+    coreTranslateMatrix(&check, CV3(1.0f, 2.0f, 3.0f));
+
+    for(int i = 0; i < 16; i++) {
+        CORE_TEST_FLOAT(((float*)&check)[i], ((float*)&m)[i], 0.0001f);
+    }
+}
+
+static void testToString() {
+    Matrix m;
+    for(int i = 0; i < 16; i++) {
+        ((float*)&m)[i] = (float)i + 1.0f;
+    }
+    char buffer[1024];
+    size_t n = coreToStringMatrix(&m, buffer, sizeof(buffer));
+    CORE_TEST_SIZE(127, n);
+    CORE_TEST_STRING(
+        "[[1.000, 2.000, 3.000, 4.000], [5.000, 6.000, 7.000, 8.000], "
+        "[9.000, 10.000, 11.000, 12.000], [13.000, 14.000, 15.000, 16.000]]",
+        buffer);
+
+    n = coreToStringMatrix(&m, buffer, 20);
+    CORE_TEST_SIZE(127, n);
+    CORE_TEST_STRING("[[1.000, 2.000, 3.0", buffer);
+}
+
+void coreTestMatrix() {
+    testInit();
+    testTranspose();
+    testScale();
+    testUniformScale();
+    testTranslateX();
+    testTranslateY();
+    testTranslateZ();
+    testTranslate();
+    testTranslateTo();
+    testCombination();
+    testMatrixCombination();
+    testRotateX();
+    testRotateY();
+    testRotateZ();
+    testQuaternionMatrix();
+    testToString();
+}

+ 109 - 0
test/modules/QuaternionTests.c

@@ -0,0 +1,109 @@
+#include "../Tests.h"
+#include "core/Quaternion.h"
+
+typedef CoreQuaternion Q;
+#define Q(a, b, c, d) (&(Q){{a, b, c}, d})
+#define CV3(a, b, c) (&(CoreVector3){a, b, c})
+#define CV30 CV3(0.0f, 0.0f, 0.0f)
+
+static void testInit() {
+    Q q = CORE_UNIT_QUATERNION;
+    char buffer[128];
+    coreToStringQ(&q, buffer, sizeof(buffer));
+    CORE_TEST_STRING("(0.000 i + 0.000 j + 0.000 k + 1.000)", buffer);
+}
+
+static void testAxisAndDegreesInit() {
+    Q q = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q, CV3(1.0f, 2.0f, 3.0f), 142.0f);
+    char buffer[128];
+    coreToStringQ(&q, buffer, sizeof(buffer));
+    CORE_TEST_STRING("(0.253 i + 0.505 j + 0.758 k + 0.326)", buffer);
+}
+
+static void testLerp() {
+    Q q1 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q1, CV3(2.0f, 5.0f, 7.0f), 130.0f);
+    Q q2 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q2, CV3(1.0f, 2.0f, 4.0f), 260.0f);
+    Q q3;
+    coreLerpQ(&q3, &q1, 0.3f, &q2);
+
+    char buffer[128];
+    coreToStringQ(&q3, buffer, sizeof(buffer));
+    CORE_TEST_STRING("(0.223 i + 0.529 j + 0.810 k + 0.119)", buffer);
+}
+
+static void testMulSet() {
+    Q q1 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q1, CV3(2.0f, 5.0f, 7.0f), 50.0f);
+    Q q2 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q2, CV3(2.0f, 5.0f, 7.0f), 60.0f);
+    Q q3 = CORE_UNIT_QUATERNION;
+    coreMulSetQ(&q3, &q1);
+
+    char bufferQ1[128];
+    coreToStringQ(&q1, bufferQ1, sizeof(bufferQ1));
+    char bufferQ3[128];
+    coreToStringQ(&q3, bufferQ3, sizeof(bufferQ3));
+    CORE_TEST_STRING(bufferQ1, bufferQ3);
+
+    coreMulSetQ(&q3, &q2);
+    coreToStringQ(&q3, bufferQ3, sizeof(bufferQ3));
+
+    coreAxisAngleQ(&q1, CV3(2.0f, 5.0f, 7.0f), 110.0f);
+    coreToStringQ(&q1, bufferQ1, sizeof(bufferQ1));
+
+    CORE_TEST_STRING(bufferQ1, bufferQ3);
+}
+
+static void testMul() {
+    Q q1 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q1, CV3(2.0f, 5.0f, 7.0f), 50.0f);
+    Q q2 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q2, CV3(2.0f, 5.0f, 7.0f), 60.0f);
+    Q q3 = CORE_UNIT_QUATERNION;
+    coreMulQ(&q3, &q1, &q2);
+
+    char bufferQ3[128];
+    coreToStringQ(&q3, bufferQ3, sizeof(bufferQ3));
+
+    Q q = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q, CV3(2.0f, 5.0f, 7.0f), 110.0f);
+    char bufferQ[128];
+    coreToStringQ(&q, bufferQ, sizeof(bufferQ));
+
+    CORE_TEST_STRING(bufferQ, bufferQ3);
+}
+
+static void testMulVector() {
+    Q q1 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q1, CV3(1.0f, 0.0f, 0.0f), 90.0f);
+    Q q2 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q2, CV3(0.0f, 1.0f, 0.0f), 90.0f);
+    Q q3 = CORE_UNIT_QUATERNION;
+    coreAxisAngleQ(&q3, CV3(0.0f, 0.0f, 1.0f), 90.0f);
+
+    CoreVector3 v1 = {1.0f, 0.0f, 0.0f};
+    CoreVector3 v2 = {0.0f, 1.0f, 0.0f};
+    CoreVector3 v3 = {0.0f, 0.0f, 1.0f};
+
+    CORE_TEST_V3(CV3(1.0f, 0.0f, 0.0f), coreMulQV3(CV30, &q1, &v1));
+    CORE_TEST_V3(CV3(0.0f, 0.0f, 1.0f), coreMulQV3(CV30, &q1, &v2));
+    CORE_TEST_V3(CV3(0.0f, -1.0f, 0.0f), coreMulQV3(CV30, &q1, &v3));
+    CORE_TEST_V3(CV3(0.0f, 0.0f, -1.0f), coreMulQV3(CV30, &q2, &v1));
+    CORE_TEST_V3(CV3(0.0f, 1.0f, 0.0f), coreMulQV3(CV30, &q2, &v2));
+    CORE_TEST_V3(CV3(1.0f, 0.0f, 0.0f), coreMulQV3(CV30, &q2, &v3));
+    CORE_TEST_V3(CV3(0.0f, 1.0f, 0.0f), coreMulQV3(CV30, &q3, &v1));
+    CORE_TEST_V3(CV3(-1.0f, 0.0f, 0.0f), coreMulQV3(CV30, &q3, &v2));
+    CORE_TEST_V3(CV3(0.0f, 0.0f, 1.0f), coreMulQV3(CV30, &q3, &v3));
+}
+
+void coreTestQuaternion() {
+    testInit();
+    testAxisAndDegreesInit();
+    testLerp();
+    testMulSet();
+    testMul();
+    testMulVector();
+}

+ 115 - 142
test/modules/VectorTests.c

@@ -33,203 +33,170 @@ const float eps = 0.0001f;
 #define CIV4(a, b, c, d) (&(IV4){a, b, c, d})
 #define CIV40 CIV4(0, 0, 0, 0)
 
-static void testV(const char* file, int line, const float* wanted,
-                  const float* actual, size_t n) {
-    for(size_t i = 0; i < n; i++) {
-        coreTestFloat(file, line, wanted[i], actual[i], eps);
-    }
-}
-
-#define testV2(wanted, actual)                                                 \
-    testV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 2)
-#define testV3(wanted, actual)                                                 \
-    testV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 3)
-#define testV4(wanted, actual)                                                 \
-    testV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 4)
-
-static void testIV(const char* file, int line, const int* wanted,
-                   const int* actual, size_t n) {
-    for(size_t i = 0; i < n; i++) {
-        coreTestInt(file, line, wanted[i], actual[i]);
-    }
-}
-
-#define testIV2(wanted, actual)                                                \
-    testIV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 2)
-#define testIV3(wanted, actual)                                                \
-    testIV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 3)
-#define testIV4(wanted, actual)                                                \
-    testIV(__FILE__, __LINE__, (wanted)->data, (actual)->data, 4)
+#define TESTS X(V2) X(V3) X(V4) X(IV2) X(IV3) X(IV4)
 
 static void testSetAngles() {
     float root = sqrtf(2) * 0.5f;
-    testV3(FV3(1, 0, 0), coreAngles(CV30, 0, 0));
-    testV3(FV3(root, 0, -root), coreAngles(CV30, 45, 0));
-    testV3(FV3(0, 0, -1), coreAngles(CV30, 90, 0));
-    testV3(FV3(-root, 0, -root), coreAngles(CV30, 135, 0));
-    testV3(FV3(-1, 0, 0), coreAngles(CV30, 180, 0));
-    testV3(FV3(-root, 0, root), coreAngles(CV30, 225, 0));
-    testV3(FV3(0, 0, 1), coreAngles(CV30, 270, 0));
-    testV3(FV3(root, 0, root), coreAngles(CV30, 315, 0));
-
-    testV3(FV3(0, 1, 0), coreAngles(CV30, 0, 90));
-    testV3(FV3(0, 1, 0), coreAngles(CV30, 90, 90));
-    testV3(FV3(0, 1, 0), coreAngles(CV30, 180, 90));
-    testV3(FV3(0, 1, 0), coreAngles(CV30, 270, 90));
-
-    testV3(FV3(0, -1, 0), coreAngles(CV30, 0, -90));
-    testV3(FV3(0, -1, 0), coreAngles(CV30, 90, -90));
-    testV3(FV3(0, -1, 0), coreAngles(CV30, 180, -90));
-    testV3(FV3(0, -1, 0), coreAngles(CV30, 270, -90));
-
-    testV3(FV3(root, root, 0), coreAngles(CV30, 0, 45));
-    testV3(FV3(0, root, -root), coreAngles(CV30, 90, 45));
-    testV3(FV3(-root, root, 0), coreAngles(CV30, 180, 45));
-    testV3(FV3(0, root, root), coreAngles(CV30, 270, 45));
-
-    testV3(FV3(root, -root, 0), coreAngles(CV30, 0, -45));
-    testV3(FV3(0, -root, -root), coreAngles(CV30, 90, -45));
-    testV3(FV3(-root, -root, 0), coreAngles(CV30, 180, -45));
-    testV3(FV3(0, -root, root), coreAngles(CV30, 270, -45));
-
-    testV3(FV3(0.5f, root, -0.5f), coreAngles(CV30, 45, 45));
+    CORE_TEST_V3(FV3(1, 0, 0), coreAngles(CV30, 0, 0));
+    CORE_TEST_V3(FV3(root, 0, -root), coreAngles(CV30, 45, 0));
+    CORE_TEST_V3(FV3(0, 0, -1), coreAngles(CV30, 90, 0));
+    CORE_TEST_V3(FV3(-root, 0, -root), coreAngles(CV30, 135, 0));
+    CORE_TEST_V3(FV3(-1, 0, 0), coreAngles(CV30, 180, 0));
+    CORE_TEST_V3(FV3(-root, 0, root), coreAngles(CV30, 225, 0));
+    CORE_TEST_V3(FV3(0, 0, 1), coreAngles(CV30, 270, 0));
+    CORE_TEST_V3(FV3(root, 0, root), coreAngles(CV30, 315, 0));
+
+    CORE_TEST_V3(FV3(0, 1, 0), coreAngles(CV30, 0, 90));
+    CORE_TEST_V3(FV3(0, 1, 0), coreAngles(CV30, 90, 90));
+    CORE_TEST_V3(FV3(0, 1, 0), coreAngles(CV30, 180, 90));
+    CORE_TEST_V3(FV3(0, 1, 0), coreAngles(CV30, 270, 90));
+
+    CORE_TEST_V3(FV3(0, -1, 0), coreAngles(CV30, 0, -90));
+    CORE_TEST_V3(FV3(0, -1, 0), coreAngles(CV30, 90, -90));
+    CORE_TEST_V3(FV3(0, -1, 0), coreAngles(CV30, 180, -90));
+    CORE_TEST_V3(FV3(0, -1, 0), coreAngles(CV30, 270, -90));
+
+    CORE_TEST_V3(FV3(root, root, 0), coreAngles(CV30, 0, 45));
+    CORE_TEST_V3(FV3(0, root, -root), coreAngles(CV30, 90, 45));
+    CORE_TEST_V3(FV3(-root, root, 0), coreAngles(CV30, 180, 45));
+    CORE_TEST_V3(FV3(0, root, root), coreAngles(CV30, 270, 45));
+
+    CORE_TEST_V3(FV3(root, -root, 0), coreAngles(CV30, 0, -45));
+    CORE_TEST_V3(FV3(0, -root, -root), coreAngles(CV30, 90, -45));
+    CORE_TEST_V3(FV3(-root, -root, 0), coreAngles(CV30, 180, -45));
+    CORE_TEST_V3(FV3(0, -root, root), coreAngles(CV30, 270, -45));
+
+    CORE_TEST_V3(FV3(0.5f, root, -0.5f), coreAngles(CV30, 45, 45));
 }
 
 static void testCross() {
-    testV3(FV3(0, 0, 1), coreCross(CV30, FV3(1, 0, 0), FV3(0, 1, 0)));
-    testV3(FV3(0, -1, 0), coreCross(CV30, FV3(1, 0, 0), FV3(0, 0, 1)));
-    testV3(FV3(0, 0, -1), coreCross(CV30, FV3(0, 1, 0), FV3(1, 0, 0)));
-    testV3(FV3(1, 0, 0), coreCross(CV30, FV3(0, 1, 0), FV3(0, 0, 1)));
-    testV3(FV3(0, 1, 0), coreCross(CV30, FV3(0, 0, 1), FV3(1, 0, 0)));
-    testV3(FV3(-1, 0, 0), coreCross(CV30, FV3(0, 0, 1), FV3(0, 1, 0)));
+    CORE_TEST_V3(FV3(0, 0, 1), coreCross(CV30, FV3(1, 0, 0), FV3(0, 1, 0)));
+    CORE_TEST_V3(FV3(0, -1, 0), coreCross(CV30, FV3(1, 0, 0), FV3(0, 0, 1)));
+    CORE_TEST_V3(FV3(0, 0, -1), coreCross(CV30, FV3(0, 1, 0), FV3(1, 0, 0)));
+    CORE_TEST_V3(FV3(1, 0, 0), coreCross(CV30, FV3(0, 1, 0), FV3(0, 0, 1)));
+    CORE_TEST_V3(FV3(0, 1, 0), coreCross(CV30, FV3(0, 0, 1), FV3(1, 0, 0)));
+    CORE_TEST_V3(FV3(-1, 0, 0), coreCross(CV30, FV3(0, 0, 1), FV3(0, 1, 0)));
 }
 
-#define TEST_SET_ADD(T)                                                        \
+static void testSetAdd() {
+#define X(T)                                                                   \
     {                                                                          \
         T v = {0};                                                             \
         coreAddSet##T(&v, C##T(1, 2, 3, 4));                                   \
-        test##T(C##T(1, 2, 3, 4), &v);                                         \
+        CORE_TEST_##T(C##T(1, 2, 3, 4), &v);                                   \
         coreAddSet##T(&v, C##T(2, 3, 4, 5));                                   \
-        test##T(C##T(3, 5, 7, 9), &v);                                         \
+        CORE_TEST_##T(C##T(3, 5, 7, 9), &v);                                   \
     }
-
-static void testSetAdd() {
-    TEST_SET_ADD(V2)
-    TEST_SET_ADD(V3)
-    TEST_SET_ADD(V4)
+    TESTS
+#undef X
 }
 
 static void testAdd() {
-    testV3(FV3(1, 2, 3), coreAddV3(CV30, CV30, FV3(1, 2, 3)));
-    testV3(FV3(3, 5, 7), coreAddV3(CV30, FV3(1, 2, 3), FV3(2, 3, 4)));
+    CORE_TEST_V3(FV3(1, 2, 3), coreAddV3(CV30, CV30, FV3(1, 2, 3)));
+    CORE_TEST_V3(FV3(3, 5, 7), coreAddV3(CV30, FV3(1, 2, 3), FV3(2, 3, 4)));
 }
 
-#define TEST_SET_SUB(T)                                                        \
+static void testSetSub() {
+#define X(T)                                                                   \
     {                                                                          \
         T v = {0};                                                             \
         coreSubSet##T(&v, C##T(1, 2, 3, 4));                                   \
-        test##T(C##T(-1, -2, -3, -4), &v);                                     \
+        CORE_TEST_##T(C##T(-1, -2, -3, -4), &v);                               \
         coreSubSet##T(&v, C##T(2, 3, 4, 5));                                   \
-        test##T(C##T(-3, -5, -7, -9), &v);                                     \
+        CORE_TEST_##T(C##T(-3, -5, -7, -9), &v);                               \
     }
-
-static void testSetSub() {
-    TEST_SET_SUB(V2)
-    TEST_SET_SUB(V3)
-    TEST_SET_SUB(V4)
+    TESTS
+#undef X
 }
 
 static void testSub() {
-    testV3(FV3(1, 2, 3), coreSubV3(CV30, CV30, FV3(-1, -2, -3)));
-    testV3(FV3(-1, -1, -1), coreSubV3(CV30, FV3(1, 2, 3), FV3(2, 3, 4)));
+    CORE_TEST_V3(FV3(1, 2, 3), coreSubV3(CV30, CV30, FV3(-1, -2, -3)));
+    CORE_TEST_V3(FV3(-1, -1, -1), coreSubV3(CV30, FV3(1, 2, 3), FV3(2, 3, 4)));
 }
 
-#define TEST_SET_MUL(T)                                                        \
+static void testSetMul() {
+#define X(T)                                                                   \
     {                                                                          \
         T v = *C##T(1, 2, 3, 4);                                               \
         coreMulSet##T##F(&v, 3);                                               \
-        test##T(C##T(3, 6, 9, 12), &v);                                        \
+        CORE_TEST_##T(C##T(3, 6, 9, 12), &v);                                  \
         coreMulSet##T##F(&v, -2);                                              \
-        test##T(C##T(-6, -12, -18, -24), &v);                                  \
+        CORE_TEST_##T(C##T(-6, -12, -18, -24), &v);                            \
     }
-
-static void testSetMul() {
-    TEST_SET_MUL(V2)
-    TEST_SET_MUL(V3)
-    TEST_SET_MUL(V4)
+    TESTS
+#undef X
 }
 
 static void testMul() {
-    testV3(FV3(3, 6, 9), coreMulV3F(CV30, FV3(1, 2, 3), 3));
+    CORE_TEST_V3(FV3(3, 6, 9), coreMulV3F(CV30, FV3(1, 2, 3), 3));
 }
 
-#define TEST_SET_MUL_VECTOR(T)                                                 \
+static void testSetMulVector() {
+#define X(T)                                                                   \
     {                                                                          \
         T v = *C##T(1, 2, 3, 4);                                               \
         coreMulSet##T(&v, C##T(2, 1, 3, 4));                                   \
-        test##T(C##T(2, 2, 9, 16), &v);                                        \
+        CORE_TEST_##T(C##T(2, 2, 9, 16), &v);                                  \
         coreMulSet##T(&v, C##T(-3, 4, -2, -2));                                \
-        test##T(C##T(-6, 8, -18, -32), &v);                                    \
+        CORE_TEST_##T(C##T(-6, 8, -18, -32), &v);                              \
     }
-
-static void testSetMulVector() {
-    TEST_SET_MUL_VECTOR(V2)
-    TEST_SET_MUL_VECTOR(V3)
-    TEST_SET_MUL_VECTOR(V4)
+    TESTS
+#undef X
 }
 
 static void testMulVector() {
-    testV3(FV3(-2, -2, -9), coreMulV3(CV30, FV3(2, 1, 3), FV3(-1, -2, -3)));
-    testV3(FV3(2, 2, 9), coreMulV3(CV30, FV3(1, 2, 3), FV3(2, 1, 3)));
+    CORE_TEST_V3(FV3(-2, -2, -9),
+                 coreMulV3(CV30, FV3(2, 1, 3), FV3(-1, -2, -3)));
+    CORE_TEST_V3(FV3(2, 2, 9), coreMulV3(CV30, FV3(1, 2, 3), FV3(2, 1, 3)));
 }
 
-#define TEST_SET_DIV(T)                                                        \
+static void testSetDiv() {
+#define X(T)                                                                   \
     {                                                                          \
-        T v = *C##T(12, 24, 9, 27);                                            \
+        T v = *C##T(18, 36, 9, 27);                                            \
         coreDivSet##T##F(&v, 3);                                               \
-        test##T(C##T(4, 8, 3, 9), &v);                                         \
-        coreDivSet##T##F(&v, -2);                                              \
-        test##T(C##T(-2, -4, -1.5f, -4.5f), &v);                               \
+        CORE_TEST_##T(C##T(6, 12, 3, 9), &v);                                  \
+        coreDivSet##T##F(&v, -3);                                              \
+        CORE_TEST_##T(C##T(-2, -4, -1, -3), &v);                               \
     }
-
-static void testSetDiv() {
-    TEST_SET_DIV(V2);
-    TEST_SET_DIV(V3);
-    TEST_SET_DIV(V4);
+    TESTS
+#undef X
 }
 
 static void testDiv() {
-    testV3(FV3(-1, -2, -3), coreDivV3F(CV30, FV3(-3, -6, -9), 3));
+    CORE_TEST_V3(FV3(-1, -2, -3), coreDivV3F(CV30, FV3(-3, -6, -9), 3));
 }
 
-#define TEST_SET_DIV_VECTOR(T)                                                 \
+static void testSetDivVector() {
+#define X(T)                                                                   \
     {                                                                          \
-        T v = *C##T(3, 4, 6, 8);                                               \
+        T v = *C##T(12, 4, 6, 8);                                              \
         coreDivSet##T(&v, C##T(2, 1, 3, 4));                                   \
-        test##T(C##T(1.5f, 4, 2, 2), &v);                                      \
+        CORE_TEST_##T(C##T(6, 4, 2, 2), &v);                                   \
         coreDivSet##T(&v, C##T(-3, 4, -2, -1));                                \
-        test##T(C##T(-0.5f, 1, -1, -2), &v);                                   \
+        CORE_TEST_##T(C##T(-2, 1, -1, -2), &v);                                \
     }
-
-static void testSetDivVector() {
-    TEST_SET_DIV_VECTOR(V2)
-    TEST_SET_DIV_VECTOR(V3)
-    TEST_SET_DIV_VECTOR(V4)
+    TESTS
+#undef X
 }
 
 static void testDivVector() {
-    testV3(FV3(-2, -0.5f, -1), coreDivV3(CV30, FV3(2, 1, 3), FV3(-1, -2, -3)));
-    testV3(FV3(0.5f, 2, 1), coreDivV3(CV30, FV3(1, 2, 3), FV3(2, 1, 3)));
+    CORE_TEST_V3(FV3(-2, -0.5f, -1),
+                 coreDivV3(CV30, FV3(2, 1, 3), FV3(-1, -2, -3)));
+    CORE_TEST_V3(FV3(0.5f, 2, 1), coreDivV3(CV30, FV3(1, 2, 3), FV3(2, 1, 3)));
 }
 
 static void testSetInvert() {
-    testV2(FV2(-1, 2), coreInvertSetV2(FV2(1, -2)));
-    testV3(FV3(-1, 2, 3), coreInvertSetV3(FV3(1, -2, -3)));
-    testV4(CV4(-1, 2, 3, 4), coreInvertSetV4(CV4(1, -2, -3, -4)));
+    CORE_TEST_V2(FV2(-1, 2), coreInvertSetV2(FV2(1, -2)));
+    CORE_TEST_V3(FV3(-1, 2, 3), coreInvertSetV3(FV3(1, -2, -3)));
+    CORE_TEST_V4(CV4(-1, 2, 3, 4), coreInvertSetV4(CV4(1, -2, -3, -4)));
+    CORE_TEST_IV2(FIV2(-1, 2), coreInvertSetIV2(FIV2(1, -2)));
+    CORE_TEST_IV3(FIV3(-1, 2, 3), coreInvertSetIV3(FIV3(1, -2, -3)));
+    CORE_TEST_IV4(CIV4(-1, 2, 3, 4), coreInvertSetIV4(CIV4(1, -2, -3, -4)));
 }
 
 static void testInvert() {
-    testV3(FV3(-1, 2, 3), coreInvertV3(CV30, FV3(1, -2, -3)));
+    CORE_TEST_V3(FV3(-1, 2, 3), coreInvertV3(CV30, FV3(1, -2, -3)));
 }
 
 static void testDot() {
@@ -259,51 +226,51 @@ static void testNormalize() {
         V2 v2;
         coreMulV2F(&v2, &v1, 1.0f / 25.0f);
         coreNormalizeV2(&v1);
-        testV2(&v2, &v1);
+        CORE_TEST_V2(&v2, &v1);
 
         V2 v3 = {15, 36};
         V2 v4;
         coreMulV2F(&v4, &v3, 1.0f / 39.0f);
         coreNormalizeV2(&v3);
-        testV2(&v4, &v3);
+        CORE_TEST_V2(&v4, &v3);
     }
     {
         V3 v1 = {-2, 2, -1};
         V3 v2;
         coreMulV3F(&v2, &v1, 1.0f / 3.0f);
         coreNormalizeV3(&v1);
-        testV3(&v2, &v1);
+        CORE_TEST_V3(&v2, &v1);
 
         V3 v3 = {6, 2, -3};
         V3 v4;
         coreMulV3F(&v4, &v3, 1.0f / 7.0f);
         coreNormalizeV3(&v3);
-        testV3(&v4, &v3);
+        CORE_TEST_V3(&v4, &v3);
     }
     {
         V4 v1 = {-2, 2, 0, -1};
         V4 v2;
         coreMulV4F(&v2, &v1, 1.0f / 3.0f);
         coreNormalizeV4(&v1);
-        testV4(&v2, &v1);
+        CORE_TEST_V4(&v2, &v1);
 
         V4 v3 = {6, 0, -6, 3};
         V4 v4;
         coreMulV4F(&v4, &v3, 1.0f / 9.0f);
         coreNormalizeV4(&v3);
-        testV4(&v4, &v3);
+        CORE_TEST_V4(&v4, &v3);
     }
 }
 
 static void testCast() {
-    testV2(FV2(-2.0f, 2.0f), coreConvertIV2(CV20, FIV2(-2, 2)));
-    testIV2(FIV2(-2, 2), coreConvertV2(CIV20, FV2(-2.5f, 2.6f)));
-    testV3(FV3(-2.0f, 2.0f, 9.0f), coreConvertIV3(CV30, FIV3(-2, 2, 9)));
-    testIV3(FIV3(-2, 2, 9), coreConvertV3(CIV30, FV3(-2.5f, 2.6f, 9.0f)));
-    testV4(CV4(-2.0f, 2.0f, 9.0f, 6.0f),
-           coreConvertIV4(CV40, CIV4(-2, 2, 9, 6)));
-    testIV4(CIV4(-2, 2, 9, 3),
-            coreConvertV4(CIV40, CV4(-2.5f, 2.6f, 9.0f, 3.2f)));
+    CORE_TEST_V2(FV2(-2.0f, 2.0f), coreConvertIV2(CV20, FIV2(-2, 2)));
+    CORE_TEST_IV2(FIV2(-2, 2), coreConvertV2(CIV20, FV2(-2.5f, 2.6f)));
+    CORE_TEST_V3(FV3(-2.0f, 2.0f, 9.0f), coreConvertIV3(CV30, FIV3(-2, 2, 9)));
+    CORE_TEST_IV3(FIV3(-2, 2, 9), coreConvertV3(CIV30, FV3(-2.5f, 2.6f, 9.0f)));
+    CORE_TEST_V4(CV4(-2.0f, 2.0f, 9.0f, 6.0f),
+                 coreConvertIV4(CV40, CIV4(-2, 2, 9, 6)));
+    CORE_TEST_IV4(CIV4(-2, 2, 9, 3),
+                  coreConvertV4(CIV40, CV4(-2.5f, 2.6f, 9.0f, 3.2f)));
 }
 
 static void testToString() {
@@ -314,6 +281,12 @@ static void testToString() {
     CORE_TEST_STRING("[4.000, 5.000, 6.000]", buffer);
     coreToStringV4(CV4(4, 5, 6, 7), buffer, sizeof(buffer));
     CORE_TEST_STRING("[4.000, 5.000, 6.000, 7.000]", buffer);
+    coreToStringIV2(FIV2(4, 5), buffer, sizeof(buffer));
+    CORE_TEST_STRING("[4, 5]", buffer);
+    coreToStringIV3(FIV3(4, 5, 6), buffer, sizeof(buffer));
+    CORE_TEST_STRING("[4, 5, 6]", buffer);
+    coreToStringIV4(CIV4(4, 5, 6, 7), buffer, sizeof(buffer));
+    CORE_TEST_STRING("[4, 5, 6, 7]", buffer);
 }
 
 void coreTestVector() {