dlib/math.d
2025-08-16 10:39:28 +10:00

1087 lines
18 KiB
D

import aliases;
import includes;
import util;
import std.math;
import std.math.algebraic;
import core.stdc.math : tanf, cosf, sinf, sqrtf;
import std.traits;
import inteli;
import std.meta;
import std.format;
import std.stdio;
T AlignPow2(T)(T v, T a)
{
return (v + a - 1) & ~(a - 1);
}
f32 Radians(f32 deg)
{
return deg * (PI / 180.0);
}
struct Vector(T, int N)
{
static assert(N > 1 && N <= 4);
enum _N = N;
alias T _T;
union
{
T[N] v;
struct
{
T x;
alias x r;
T y;
alias y g;
static if (N > 2)
{
T z;
alias z b;
}
static if (N > 3)
{
T w;
alias w a;
}
};
}
this(Vec3, f32)(Vec3 v3, f32 f) if (N == 4 && is(T: f32))
{
x = v3.x;
y = v3.y;
z = v3.z;
w = f;
}
this(Arr)(Arr arr) if (is(Arr: typeof(v)))
{
this.v = arr;
}
this(Args...)(Args args)
{
static if (args.length == 1)
{
opAssign!(Args[0])(args[0]);
}
else static if (args.length == N)
{
mixin(GenerateLoop!("v[@] = args[@];", N)());
}
else static if (args.length == 2 && N == 4)
{
v[0] = args[0];
v[1] = args[0];
v[2] = args[0];
v[3] = args[1];
}
else
{
static assert(false, "Invalid Vector constructor");
}
}
ref Vector opAssign(U)(U x) if (is(U: T))
{
mixin(GenerateLoop!("v[@] = x;", N)());
return this;
}
ref Vector opAssign(U)(U u) if (is(U : Vector))
{
v[] = u.v[];
return this;
}
inout(T)* ptr() inout @property
{
return v.ptr;
}
bool opEquals(U)(U other) if (is(U: Vector!(T, N)))
{
bool result = true;
foreach(i; 0 .. N)
{
if (fabs(v[i] - other.v[i]) > 0.0000009)
{
result = false;
break;
}
}
return result;
}
int opDollar()
{
return N;
}
T[] opSlice()
{
return v[];
}
Vector opUnary(string op)() if (op == "+" || op == "-" || op == "~" || op == "!")
{
Vector result;
mixin(GenerateLoop!("res.v[@] = " ~ op ~ " v[@];", N)());
return res;
}
ref Vector opOpAssign(string op, U)(U value) if (is(U: Vector))
{
mixin(GenerateLoop!("v[@] " ~ op ~ "= value.v[@];", N)());
return this;
}
ref Vector opOpAssign(string op, U)(U value) if (IsConvertible!(U))
{
Vector conv = value;
return opOpAssign!(op)(conv);
}
@property auto opDispatch(string op, U = void)() if (ValidSwizzle!(op) && op.length <= 4)
{
Vector!(T, op.length) result;
enum index_tuple = SwizzleTuple!(op);
static foreach(i, index; index_tuple)
{
result.v[i] = v[index];
}
return result;
}
@property void opDispatch(string op, U)(U x) if ((op.length > 1) && ValidUniqueSwizzle!(op) && is(typeof(Vector!(T, op.length)(x))))
{
Vector!(T, op.length) conv = x;
enum index_tuple = SwizzleTuple!(op);
static foreach(i, index; index_tuple)
{
v[index] = conv[i];
}
}
static if (N == 4)
{
Vector opBinary(string op, U)(U operand) if ((is(U: Vector!(f32, 4)) && is(T: f32)) && (op == "*" || op == "+" || op == "-" || op == "/"))
{
Vector result;
f32* l = &x;
f32* r = &operand.x;
f32* res = &result.x;
asm
{
mov R8, l;
mov R9, r;
mov R10, res;
movups XMM0, x.offsetof[R8];
movups XMM1, operand.x.offsetof[R9];
}
static if (op == "*") asm { mulps XMM0, XMM1; }
else static if (op == "-") asm { subps XMM0, XMM1; }
else static if (op == "+") asm { addps XMM0, XMM1; }
else static if (op == "/") asm { divps XMM0, XMM1; }
asm
{
movups result.x.offsetof[R10], XMM0;
}
return result;
}
Vector opBinary(string op, U)(U operand) if (IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/"))
{
Vector result;
Vector other = operand;
f32* l = &x;
f32* r = &other.x;
f32* res = &result.x;
asm
{
mov R8, l;
mov R9, r;
mov R10, res;
movups XMM0, x.offsetof[R8];
movups XMM1, other.x.offsetof[R9];
}
static if (op == "*") asm { mulps XMM0, XMM1; }
else static if (op == "-") asm { subps XMM0, XMM1; }
else static if (op == "+") asm { addps XMM0, XMM1; }
else static if (op == "/") asm { divps XMM0, XMM1; }
asm
{
movups result.x.offsetof[R8], XMM0;
}
return result;
}
}
else
{
Vector opBinary(string op, U)(U operand) if (is(U: Vector) && U._N == N && (op == "*" || op == "+" || op == "-" || op == "/"))
{
Vector res;
mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " operand.v[@];", N)());
return res;
}
Vector opBinary(string op, U)(U operand) if (IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/"))
{
Vector res;
Vector other = operand;
mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " other.v[@];", N)());
return res;
}
}
ref T opIndex(size_t i)
{
return v[i];
}
T opIndexAssign(U : T)(U x, size_t i)
{
return v[i] = x;
}
U opCast(U)() if (IsVector!(U) && (U._N == _N))
{
U result;
mixin(GenerateLoop!("res.v[@] = cast(U._T)v[@];", N)());
return result;
}
template IsConvertible(T)
{
enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }()));
}
template SwizzleIndex(char c)
{
static if ((c == 'x' || c == 'r') && N > 0)
enum SwizzleIndex = 0;
else static if ((c == 'y' || c == 'g') && N > 1)
enum SwizzleIndex = 1;
else static if ((c == 'z' || c == 'b') && N > 2)
enum SwizzleIndex = 2;
else static if ((c == 'w' || c == 'a') && N > 3)
enum SwizzleIndex = 3;
else
enum SwizzleIndex = -1;
}
template SwizzleSet(char c)
{
static if (c == 'x' || c == 'y' || c == 'z' || c == 'w')
enum SwizzleSet = 0;
else static if (c == 'r' || c == 'g' || c == 'b' || c == 'a')
enum SwizzleSet = 1;
else
enum SwizzleSet = -1;
}
template SwizzleTuple(string op)
{
enum op_length = op.length;
static if (op.length == 0)
enum SwizzleTuple = [];
else
enum SwizzleTuple = [ SwizzleIndex!(op[0])] ~ SwizzleTuple!(op[1 .. op.length]);
}
template SearchString(char c, string s)
{
static if (s.length == 0)
{
enum bool result = false;
}
else
{
enum string tail = s[1 .. s.length];
enum bool result = (s[0] == c) || SearchString!(c, tail).result;
}
}
template UniqueChars(string s)
{
static if (s.length == 1)
{
enum bool result = true;
}
else
{
enum tail = s[1 .. s.length];
enum bool result = !(SearchString!(s[0], tail).result) && UniqueChars!(tail).result;
}
}
template ValidSwizzle(string op, int last_swizzle = -1)
{
static if (op.length == 0)
{
enum bool ValidSwizzle = true;
}
else
{
enum length = op.length;
enum int swizzle_set = SwizzleSet!(op[0]);
enum bool valid_swizzle_set = (last_swizzle == -1 || (swizzle_set == last_swizzle));
enum bool ValidSwizzle = (SwizzleIndex!(op[0]) != -1) && valid_swizzle_set && ValidSwizzle!(op[1 .. length], swizzle_set);
}
}
template ValidUniqueSwizzle(string op)
{
static if (ValidSwizzle!(op))
{
enum ValidUniqueSwizzle = UniqueChars!(op).result;
}
else
{
enum ValidUniqueSwizzle = false;
}
}
}
// TODO: fix alignment for non mat4s
align(16) struct Matrix(T, int D)
{
static assert(D > 0 && D <= 4);
alias Vector!(T, D) MatrixVec;
alias T _T;
alias T[4] Row;
enum N = D*D;
enum _D = D;
union
{
T[N] v;
Row[D] rows;
MatrixVec[D] vec;
static if (D == 4) mat4 glm_mat;
static if (D == 3) mat3 glm_mat;
static if (D == 2) mat2 glm_mat;
}
// TODO: setup
this(U...)(U values)
{
static if ((U.length == N) && allSatisfy!(IsTypeAssignable, U))
{
static foreach(i, x; values)
{
v[i] = x;
}
}
else static if ((U.length == 1) && (isAssignable!(U[0])) && (!is(U[0] : Matrix)))
{
v[] = values[0];
}
else static assert(false, "Cannot construct matrix with provided parameters");
}
this(U)(T x)
{
static foreach(i; 0 .. N)
{
v[i] = x;
}
}
@property inout(T)* ptr() inout
{
return v.ptr;
}
ref Matrix opAssign(U : T)(U x)
{
static foreach(i; 0 .. N)
{
v[i] = x;
}
return this;
}
ref Matrix opAssign(U : Matrix)(U x)
{
static foreach(i; 0 .. N)
{
v[i] = x.v[i];
}
return this;
}
ref Matrix opAssign(U)(U x) if (IsMatrixInstantiation!(U) && is(U._T : _T) && (!is(U: Matrix) && (U.N != N)))
{
static foreach(i; 0 .. N)
{
v[i] = x.v[i];
}
return this;
}
ref T opIndex(size_t i, size_t j)
{
return v[(i * D) + j];
}
T opIndexAssign(U: T)(U x, size_t i, size_t j)
{
return v[(i * D) + j] = x;
}
bool opEquals(U)(U other) if (is(U: Matrix!(T, D)))
{
bool result = true;
static foreach(i; 0 .. N)
{
if (fabs(this.v[i] - other.v[i]) > 0.0000009)
{
result = false;
}
}
return result;
}
Matrix opBinary(string op)(T scalar) if (op == "*")
{
Matrix result;
static foreach(i; 0 .. N)
{
result.v[i] = v[i] * scalar;
}
return result;
}
static if (D == 4)
{
Vec4 opBinary(string op, U)(U x) if (is(U: Vec4) && is(T: f32) && (op == "*"))
{
Vec4 result = 0.0;
glm_mat4_mulv(glm_mat.ptr, x.v.ptr, result.v.ptr);
return result;
}
Matrix opBinary(string op, U)(U x) if (is(U: Matrix!(T, D)) && is(T: f32) && D == 4 && (op == "*"))
{
Matrix result;
MatZero(&result);
glm_mat4_mul(glm_mat.ptr, x.glm_mat.ptr, result.glm_mat.ptr);
return result;
}
}
template IsTypeAssignable(U)
{
enum bool IsTypeAssignable = std.traits.isAssignable!(T, U);
}
template IsMatrixInstantiation(U)
{
static void IsMatrix(T, int D)(Matrix!(T, D) x) {}
enum bool IsMatrixInstantiation = is(typeof(IsMatrix(U.init)));
}
}
struct Quat
{
union
{
f32[4] v;
Vec4 vec;
struct
{
f32 x;
f32 y;
f32 z;
f32 w;
};
};
this(f32 w, f32 x, f32 y, f32 z)
{
vec.x = x;
vec.y = y;
vec.z = z;
vec.w = w;
}
U opCast(U)() if (is(U: Mat4))
{
Mat4 result;
glm_quat_mat4(vec.ptr, result.glm_mat.ptr);
return result;
}
Quat opBinary(string op, U)(U r) if (op == "*" && is(U: Quat))
{
Quat q;
q.x = this.w * r.x + this.x * r.w + this.y * r.z - this.z * r.y;
q.y = this.w * r.y - this.x * r.z + this.y * r.w + this.z * r.x;
q.z = this.w * r.z + this.x * r.y - this.y * r.x + this.z * r.w;
q.w = this.w * r.w - this.x * r.x - this.y * r.y - this.z * r.z;
return q;
}
}
Mat4
Mat4MulASM(Mat4 l, Mat4 r)
{
Mat4 result;
auto lp = &l;
auto rp = &r;
auto res = &result;
// TODO: fix this
asm @trusted
{
mov R8, lp;
mov R9, rp;
mov R10, res;
movups XMM0, [R8];
movups XMM1, [R9+00];
movups XMM2, [R9+16];
movups XMM3, [R9+32];
movups XMM4, [R9+48];
movups XMM6, XMM1;
shufps XMM6, XMM6, 0; // XMM5 = vec.xxxx;
mulps XMM6, XMM0; // XMM6 = col1;
movups XMM7, XMM2;
shufps XMM7, XMM7, 0;
mulps XMM7, XMM0; // XMM7 = col2;
movups XMM8, XMM3;
shufps XMM8, XMM8, 0;
mulps XMM8, XMM0; // XMM8 = col3;
movups XMM9, XMM3;
shufps XMM9, XMM9, 0;
mulps XMM9, XMM0; // XMM9 = col4;
movups XMM0, [R8+16];
movups XMM5, XMM1;
shufps XMM5, XMM5, 85; // XMM5 = vec.yyyy;
mulps XMM5, XMM0;
addps XMM6, XMM5;
movups XMM5, XMM2;
shufps XMM5, XMM5, 85;
mulps XMM5, XMM0;
addps XMM7, XMM5;
movups XMM5, XMM3;
shufps XMM5, XMM5, 85;
mulps XMM5, XMM0;
addps XMM8, XMM5;
movups XMM5, XMM4;
shufps XMM5, XMM5, 85;
mulps XMM5, XMM0;
addps XMM9, XMM5;
movups XMM0, [R8+32];
movups XMM5, XMM1;
shufps XMM5, XMM5, 170; // XMM5 = vec.zzzz;
mulps XMM5, XMM0;
addps XMM6, XMM5;
movups XMM5, XMM2;
shufps XMM5, XMM5, 170;
mulps XMM5, XMM0;
addps XMM7, XMM5;
movups XMM5, XMM3;
shufps XMM5, XMM5, 170;
mulps XMM5, XMM0;
addps XMM8, XMM5;
movups XMM5, XMM4;
shufps XMM5, XMM5, 170;
mulps XMM5, XMM0;
addps XMM9, XMM5;
movups XMM0, [R8+48];
movups XMM5, XMM1;
shufps XMM5, XMM5, 255; // XMM5 = vec.wwww;
mulps XMM5, XMM0;
addps XMM6, XMM5;
movups XMM5, XMM2;
shufps XMM5, XMM5, 255;
mulps XMM5, XMM0;
addps XMM7, XMM5;
movups XMM5, XMM3;
shufps XMM5, XMM5, 255;
mulps XMM5, XMM0;
addps XMM8, XMM5;
movups XMM5, XMM4;
shufps XMM5, XMM5, 255;
mulps XMM5, XMM0;
addps XMM9, XMM5;
movups [R10+00], XMM6;
movups [R10+16], XMM7;
movups [R10+32], XMM8;
movups [R10+48], XMM9;
}
return result;
}
pragma(inline): Mat4
Mat4Identity()
{
return Mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
pragma(inline): void
Mat4Identity(Mat4* mat)
{
MatZero(mat);
(*mat)[0, 0] = 1.0;
(*mat)[1, 1] = 1.0;
(*mat)[2, 2] = 1.0;
(*mat)[3, 3] = 1.0;
}
pragma(inline): Mat3
Mat3Identity()
{
return Mat3(
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0
);
}
pragma(inline): Mat2
Mat2Identity()
{
return Mat2(
1.0, 0.0,
0.0, 1.0
);
}
pragma(inline): Quat
QuatFromAxis(f32 angle, Vec3 axis)
{
Quat q;
glm_quatv(q.vec.ptr, angle, axis.v.ptr);
return q;
}
pragma(inline): f32
Dot(Vec2* l, Vec2* r)
{
return l.x * r.x + l.y * r.y;
}
pragma(inline): f32
Dot(Vec3* l, Vec3* r)
{
return l.x * r.x + l.y * r.y + l.z * r.z;
}
pragma(inline): f32
Dot(Vec4* l, Vec4* r)
{
// TODO: SIMD this
return l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
}
pragma(inline): f32
Norm(Vec3* v)
{
return sqrtf(Dot(v, v));
}
pragma(inline): f32
Norm(Vec4* v)
{
// TODO: SIMD this
return sqrtf(Dot(v, v));
}
pragma(inline): void
Normalize(T)(T* vec) if (is(T: Vec2) || is(T: Vec3) || is(T: Vec4))
{
f32 length = Norm(vec);
if (length < f32.epsilon)
{
mixin(GenerateLoop!("vec.v[@] = 0.0;", vec._N)());
}
else
{
mixin(GenerateLoop!("vec.v[@] *= (1.0 / length);", vec._N)());
}
}
pragma(inline): T
Normalize(T)(T vec) if (is(T: Vec2) || is(T: Vec3) || is(T: Vec4))
{
Normalize(&vec);
return vec;
}
pragma(inline): Quat
Normalize(Quat q)
{
f32 dot = Norm(&q.vec);
if (dot <= 0.0)
{
q = Quat(1.0, 0.0, 0.0, 0.0);
}
q.vec *= 1.0 / sqrtf(dot);
return q;
}
pragma(inline): Mat4
Perspective(f32 fov, f32 aspect, f32 near, f32 far)
{
Mat4 res;
MatZero(&res);
glm_perspective(fov, aspect, near, far, res.glm_mat.ptr);
res[1, 1] *= -1.0;
return res;
}
pragma(inline): Vec3
Rotate(Quat q, Vec3 vec)
{
Quat p = Normalize(q);
Vec3 i = p.vec.xyz;
f32 r = p.vec.w;
Vec3 v1 = i * (2.0 * Dot(&i, &vec));
Vec3 v2 = i * (r * r - Dot(&i, &i));
v1 += v2;
v2 = i * vec;
v2 = v2 * (2.0 * r);
return v1 + v2;
}
pragma(inline): Mat4
LookAt(Vec3 eye, Vec3 center, Vec3 up)
{
Mat4 result;
MatZero(&result);
glm_lookat(eye.v.ptr, center.v.ptr, up.v.ptr, result.glm_mat.ptr);
return result;
}
pragma(inline): Mat4
Look(Vec3 eye, Vec3 dir, Vec3 up)
{
return LookAt(eye, eye + dir, up);
}
pragma(inline): Vec3
Cross(Vec3 a, Vec3 b)
{
Vec3 c;
Cross(a, b, &c);
return c;
}
pragma(inline): Vec3
CrossN(Vec3 a, Vec3 b)
{
Vec3 c;
Cross(a, b, &c);
Normalize(&c);
return c;
}
pragma(inline): void
CrossN(Vec3 a, Vec3 b, Vec3* dst)
{
Cross(a, b, dst);
glm_vec3_normalize(dst.v.ptr);
}
pragma(inline): void
Cross(Vec3 a, Vec3 b, Vec3* dst)
{
glm_vec3_cross(a.v.ptr, b.v.ptr, dst.v.ptr);
}
pragma(inline): void
MatZero(Mat4* mat)
{
auto v = &mat.vec;
asm
{
mov R8, v;
xorps XMM0, XMM0;
movups mat.vec.offsetof[R8]+00, XMM0;
movups mat.vec.offsetof[R8]+16, XMM0;
movups mat.vec.offsetof[R8]+32, XMM0;
movups mat.vec.offsetof[R8]+48, XMM0;
}
}
pragma(inline): void
Translate(Mat4* mat, Vec3 vec)
{
glm_translate(mat.glm_mat.ptr, vec.v.ptr);
}
pragma(inline): Mat4
Inverse(Mat4 mat)
{
Mat4 res;
MatZero(&res);
glm_mat4_inv(mat.glm_mat.ptr, res.glm_mat.ptr);
return res;
}
pragma(inline): f32
Mix(f32 x, f32 y, f32 a)
{
return x * (1 - a) + y * a;
}
pragma(inline): f32
InverseLerp(f32 v, f32 min, f32 max)
{
return (v - min) / (max - min);
}
pragma(inline): f32
Remap(f32 v, f32 in_min, f32 in_max, f32 out_min, f32 out_max)
{
f32 t = InverseLerp(v, in_min, in_max);
return Mix(out_min, out_max, t);
}
unittest
{
enum FLOAT_MAX = f32.max;
enum FLOAT_MIN = -f32.max;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.time;
import std.range : take;
import std.algorithm.iteration : sum;
void PrintMatrix(Mat4 mat)
{
foreach(i; 0 .. mat.N)
{
if (i % 4 == 0)
{
printf("\n");
}
printf("%.08f ", mat.v[i]);
}
printf("\n");
}
srand(cast(u32)time(null));
f32 RandomFloat()
{
return cast(f32)(rand())/cast(f32)(RAND_MAX + 1.0);
}
{ // Vec2 arithmetic
Vec2 v1 = Vec2(5.0, 10.0);
Vec2 v2 = Vec2(2.5, 5.0);
Vec2 result = v1 * v2;
assert(result == Vec2(12.5, 50.0), "Vec2 mul failure");
result = v1 + v2;
assert(result == Vec2(7.5, 15.0), "Vec2 add failure");
result = v1 - v2;
assert(result == Vec2(2.5, 5.0), "Vec2 sub failure");
result = v1 / v2;
assert(result == Vec2(2.0), "Vec2 div failure");
}
{ // Vec3 Arithmetic
Vec3 v1 = Vec3(5.0, 10.0, 15.0);
Vec3 v2 = Vec3(2.5, 5.0, 7.5);
Vec3 result = v1 * v2;
assert(result == Vec3(12.5, 50.0, 112.5), "Vec3 mul failure");
result = v1 + v2;
assert(result == Vec3(7.5, 15.0, 22.5), "Vec3 add failure");
result = v1 - v2;
assert(result == Vec3(2.5, 5.0, 7.5), "Vec3 sub failure");
result = v1 / v2;
assert(result == Vec3(2.0), "Vec3 div failure");
}
{ // Vec3 Arithmetic
Vec4 v1 = Vec4(5.0, 10.0, 15.0, 20.0);
Vec4 v2 = Vec4(2.5, 5.0, 7.5, 10.0);
Vec4 result = v1 * v2;
assert(result == Vec4(12.5, 50.0, 112.5, 200.0), "Vec4 mul failure");
result = v1 + v2;
assert(result == Vec4(7.5, 15.0, 22.5, 30.0), "Vec4 add failure");
result = v1 - v2;
assert(result == Vec4(2.5, 5.0, 7.5, 10.0), "Vec4 sub failure");
result = v1 / v2;
assert(result == Vec4(2.0), "Vec4 div failure");
}
{ // Mat4 Arithmetic
Mat4 m1 = RandomMat4();
Mat4 m2 = RandomMat4();
Mat4 m3 = m1 * m2;
Mat4 m4;
MatZero(&m4);
for(u32 i = 0; i < 4; i += 1)
{
for(u32 j = 0; j < 4; j += 1)
{
for(u32 k = 0; k < 4; k += 1)
{
m4.rows[i][j] += m1.rows[k][j] * m2.rows[i][k];
}
}
}
assert(m3 == m4, "Mat4 mul failure");
}
{ // Translate
Mat4 mat = Mat4Identity();
Vec4 vec = Vec4(1.0, 2.0, 3.0, 1.0);
Translate(&mat, Vec3(13.0, 11.0, 7.0));
Vec4 result = mat * vec;
assert(result == Vec4(14.0, 13.0, 10.0, 1.0));
mat = Mat4Identity();
Translate(&mat, Vec3(1.0, -1.0, -5.0));
result = mat * result;
assert(result == Vec4(15.0, 12.0, 5.0, 1.0));
}
{ // Identity
Mat4 identity = Mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
Mat4 mat = Mat4Identity();
assert(identity == mat);
}
{ // Inverse
foreach(i; 0 .. 1000)
{
Mat4 m1 = RandomMat4();
Mat4 m1_inv = Inverse(m1);
Mat4 m1_reinv = Inverse(m1_inv);
assert(m1 == m1_reinv, "Inverse test failed");
}
}
{ // Cross
Vec3 v1 = Vec3(2.0, -3.0, 4.0);
Vec3 v2 = Vec3(12.0, -31.0, 43.0);
Vec3 v3 = Cross(v1, v2);
Vec3 v4 = Vec3(
v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x
);
assert(v3 == v4, "Vec3 Cross failure");
v3 = CrossN(v1, v2);
Normalize(&v4);
assert(v3 == v4, "Vec3 CrossN failure");
}
}