module dlib.math;

import dlib.aliases;
import dlib.util;

import std.traits;
import std.math.traits;
import core.internal.traits : allSatisfy;

public import std.math.rounding : round;
public import core.math : sqrt, cos, sin;

enum PI = 3.14159L;

const u16[128] _RSQRT_TABLE = [
	0xb451, 0xb2f0, 0xb196, 0xb044, 0xaef9, 0xadb6, 0xac79, 0xab43,
	0xaa14, 0xa8eb, 0xa7c8, 0xa6aa, 0xa592, 0xa480, 0xa373, 0xa26b,
	0xa168, 0xa06a, 0x9f70, 0x9e7b, 0x9d8a, 0x9c9d, 0x9bb5, 0x9ad1,
	0x99f0, 0x9913, 0x983a, 0x9765, 0x9693, 0x95c4, 0x94f8, 0x9430,
	0x936b, 0x92a9, 0x91ea, 0x912e, 0x9075, 0x8fbe, 0x8f0a, 0x8e59,
	0x8daa, 0x8cfe, 0x8c54, 0x8bac, 0x8b07, 0x8a64, 0x89c4, 0x8925,
	0x8889, 0x87ee, 0x8756, 0x86c0, 0x862b, 0x8599, 0x8508, 0x8479,
	0x83ec, 0x8361, 0x82d8, 0x8250, 0x81c9, 0x8145, 0x80c2, 0x8040,
	0xff02, 0xfd0e, 0xfb25, 0xf947, 0xf773, 0xf5aa, 0xf3ea, 0xf234,
	0xf087, 0xeee3, 0xed47, 0xebb3, 0xea27, 0xe8a3, 0xe727, 0xe5b2,
	0xe443, 0xe2dc, 0xe17a, 0xe020, 0xdecb, 0xdd7d, 0xdc34, 0xdaf1,
	0xd9b3, 0xd87b, 0xd748, 0xd61a, 0xd4f1, 0xd3cd, 0xd2ad, 0xd192,
	0xd07b, 0xcf69, 0xce5b, 0xcd51, 0xcc4a, 0xcb48, 0xca4a, 0xc94f,
	0xc858, 0xc764, 0xc674, 0xc587, 0xc49d, 0xc3b7, 0xc2d4, 0xc1f4,
	0xc116, 0xc03c, 0xbf65, 0xbe90, 0xbdbe, 0xbcef, 0xbc23, 0xbb59,
	0xba91, 0xb9cc, 0xb90a, 0xb84a, 0xb78c, 0xb6d0, 0xb617, 0xb560,
];

union Union64
{
	f64 f;
	u64 u;
}

union Union32
{
	f32 f;
	u32 u;
}

auto
AsUint(T)(T v) if(isFloatingPoint!(T))
{
	static if(is(T == f32))
	{
		Union32 u = { f: v };
	}
	else
	{
		Union64 u = { f: v };
	}

	return u.u;
}

auto
AsFloat(T)(T v) if(isIntegral!(T))
{
	static if(T.sizeof == 4)
	{
		Union32 u = { u: v };
	}
	else
	{
		Union64 u = { u: v };
	}

	return u.f;
}

T
_MathInvalid(T)(T v) if(isFloatingPoint!(T))
{
	return (v - v) / (v - v);
}

u32
Mul32(T)(T a, T b) if(isIntegral!(T))
{
	return cast(u32)(cast(u64)(a*b) >> 32);
}

u64
Mul64(u64 a, u64 b)
{
	u64 ahi = a>>32;
	u64 alo = a&0xffffffff;
	u64 bhi = b>>32;
	u64 blo = b&0xffffffff;
	return ahi*bhi + (ahi*blo >> 32) + (alo*bhi >> 32);
}

/*
T
Sqrt(T)(T x) if(isFloatingPoint!(T))
{
	u64 ix, top, m;

	ix  = AsUint(x);
	top = ix >> 52;
	if (top - 0x001 >= 0x7ff - 0x001) {
		if (ix * 2 == 0)
		{
			return x;
		}
		if (ix == 0x7ff0000000000000)
		{
			return x;
		}
		if (ix > 0x7ff0000000000000)
		{
			return _MathInvalid(x);
		}
		ix   = AsUint(x * 0x1p52);
		top  = ix >> 52;
		top -= 52;
	}

	i32 even = top & 1;
	m = (ix << 11) | 0x8000000000000000;
	if (even)
	{
		m >>= 1;
	} 

	top = (top + 0x3ff) >> 1;

	static const u64 three = 0xc0000000;
	u64 r, s, d, u, i;

	i = (ix >> 46) % 128;
	r = cast(u32)(_RSQRT_TABLE[cast(usize)i] << 16);
	s = Mul32(m>>32, r);
	d = Mul32(s, r);
	u = three - d;
	r = Mul32(r, u) << 1;
	s = Mul32(s, u) << 1;
	d = Mul32(s, r);
	u = three - d;
	r = Mul32(r, u) << 1;
	r = r << 32;
	s = Mul64(m, r);
	d = Mul64(s, r);
	u = (three<<32) - d;
	s = Mul64(s, u);  
	s = (s - 2) >> 9; 

	u64 d0, d1, d2;
	f64 y, t;
	d0 = (m << 42) - s*s;
	d1 = s - d0;
	d2 = d1 + s + 1;
	s += d1 >> 63;
	s &= 0x000fffffffffffff;
	s |= top << 52;
	y = AsFloat(s);
	
	static if(true) 
	{
		u64 tiny = d2==0 ? 0 : 0x0010000000000000;
		tiny |= (d1^d2) & 0x8000000000000000;
		t = AsFloat(tiny);
		y = cast(f64)(y + t);
	}

	return y;
}
*/

T
Abs(T)(T x) if(isSigned!(T))
{
	static if(isFloatingPoint!(T))
	{
		static if(is(T == f32)) 
		{
			Union32 f = { f: x };
		}
		else
		{
			Union64 f = { f: x };
		}
		
		f.u &= cast(typeof(f.u))-1 / 2;

		x = f.f;
	}
	else
	{
		x = x >= 0 ? x : -x;
	}

	return x;
}

T
Ceil(T)(T x) if(is(T == f32) || is(T == f64))
{
	if(!isNaN(x) && !isInfinity(x) && x != 0.0)
	{
		x = _Floor(x+1.0);
	}

	return x;
}

T
Floor(T)(T x) if(is(T == f32) || is(T == f64))
{
	if(!isNaN(x) && !isInfinity(x) && x != 0.0)
	{
		x = _Floor(x);
	}

	return x;
}

T 
_Floor(T)(const T x) @trusted pure nothrow @nogc
{
	alias F = floatTraits!(T);
	// Take care not to trigger library calls from the compiler,
	// while ensuring that we don't get defeated by some optimizers.
	union floatBits
	{
		T rv;
		ushort[T.sizeof/2] vu;

		// Other kinds of extractors for real formats.
		static if (F.realFormat == RealFormat.ieeeSingle)
			uint vi;
		else static if (F.realFormat == RealFormat.ieeeDouble)
			ulong vi;
	}

	floatBits y = void;
	y.rv = x;

	// Find the exponent (power of 2)
	// Do this by shifting the raw value so that the exponent lies in the low bits,
	// then mask out the sign bit, and subtract the bias.
	static if (F.realFormat == RealFormat.ieeeSingle)
	{
		int exp = ((y.vi >> (T.mant_dig - 1)) & 0xff) - 0x7f;
		enum mantissa_mask = F.MANTISSAMASK_INT;
		enum sign_shift = 31;
	}
	else static if (F.realFormat == RealFormat.ieeeDouble)
	{
		long exp = ((y.vi >> (T.mant_dig - 1)) & 0x7ff) - 0x3ff;
		enum mantissa_mask = F.MANTISSAMASK_LONG;
		enum sign_shift = 63;
	}
	else static if (F.realFormat == RealFormat.ieeeExtended ||
									F.realFormat == RealFormat.ieeeExtended53)
	{
		int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;

		version (LittleEndian)
			int pos = 0;
		else
			int pos = 4;
	}
	else static if (F.realFormat == RealFormat.ieeeQuadruple)
	{
		int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff;

		version (LittleEndian)
			int pos = 0;
		else
			int pos = 7;
	}
	else
			static assert(false, "Not implemented for this architecture");

	if (exp < 0)
	{
		if (x < 0.0)
			return -1.0;
		else
			return 0.0;
	}

	static if (F.realFormat == RealFormat.ieeeSingle ||
						 F.realFormat == RealFormat.ieeeDouble)
	{
		if (exp < (T.mant_dig - 1))
		{
			// Clear all bits representing the fraction part.
			// Note: the fraction mask represents the floating point number 0.999999...
			// i.e: `2.0 ^^ (exp - T.mant_dig + 1) * (fraction_mask + 1) == 1.0`
			const fraction_mask = mantissa_mask >> exp;

			if ((y.vi & fraction_mask) != 0)
			{
				// If 'x' is negative, then first substract (1.0 - T.epsilon) from the value.
				if (y.vi >> sign_shift)
					y.vi += fraction_mask;
				y.vi &= ~fraction_mask;
			}
		}
	}
	else
	{
		static if (F.realFormat == RealFormat.ieeeExtended53)
			exp = (T.mant_dig + 11 - 1) - exp; // mant_dig is really 64
		else
			exp = (T.mant_dig - 1) - exp;

		// Zero 16 bits at a time.
		while (exp >= 16)
		{
			version (LittleEndian)
				y.vu[pos++] = 0;
			else
				y.vu[pos--] = 0;
			exp -= 16;
		}

		// Clear the remaining bits.
		if (exp > 0)
			y.vu[pos] &= 0xffff ^ ((1 << exp) - 1);

		if ((x < 0.0) && (x != y.rv))
			y.rv -= 1.0;
	}

	return y.rv;
}

T
_MinOrMaxInner(T, bool max, Args...)(T first, Args args)
{
	T value = first;
	static foreach(i; 0 .. args.length)
	{
		static if(max)  if(cast(T)(args[i]) > value) value = cast(T)(args[i]);
		static if(!max) if(cast(T)(args[i]) < value) value = cast(T)(args[i]);
	}

	return value;
}

T
Max(T, Args...)(T first, Args args)
{
	return _MinOrMaxInner!(T, true, Args)(first, args);
}

T
Min(T, Args...)(T first, Args args)
{
	return _MinOrMaxInner!(T, false, Args)(first, args);
}

T
RoundUp(T)(T a, T b)
{
	a += b - 1;
	a -= a%b;
	return a;
}

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 = 0;
		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)
		{
			enum bool is_array          = isArray!(Args[0]);
			enum bool is_assignable     = isAssignable!(T, Args[0]);
			enum bool types_are_numeric = TypesAreNumeric!(T, Args[0]);

			static if(is_array || is_assignable || types_are_numeric)
			{
				opAssign!(Args[0])(args[0]);
			}
			else static assert(false, "Invalid Vector constructor");
		}
		else static if(args.length == N)
		{
			static foreach(i; 0 .. N)
			{
				v[i] = cast(T)args[i];
			}
		}
		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) || isAssignable!(T, U) || TypesAreNumeric!(T, U)) 
	{
		v[] = cast(T)x;
		return this;
	}

	ref Vector opAssign(U)(U u) if(isArray!(U) && U.length == N)
	{
		static foreach(i; 0 .. N)
		{
			v[i] = cast(T)u[i];
		}
		return this;
	}

	ref Vector opAssign(U)(U u) if(isInstanceOf!(Vector, U))
	{
		static foreach(i; 0 .. N)
		{
			v[i] = cast(T)u.v[i];
		}
		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(Abs(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 res;
		static foreach(i; 0 .. N)
		{
			mixin("res.v[", i, "] = " ~ op ~ "v[", i, "];\n");
		}
		return res;
	}

	ref Vector opOpAssign(string op, U)(U value) if(is(U: Vector))
	{
		static foreach(i; 0 .. N)
		{
			mixin("v[", i, "] " ~op~ "= value.v[", i, "];");
		}
		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];
		}
	}

	Vector
	opBinaryRight(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/"))
	{
		Vector res;
		static foreach(i; 0 .. N)
		{
			mixin("res.v[", i, "] = v[", i, "] " ~op~ " operand;");
		}
		return res;
	}

	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;
	
			version(X86_64)
			{
				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;
				}
			}
			else
			{
				static foreach(i; 0 .. N)
				{
					result.v[i] = mixin("this.v[", i, "] " ~op~ " operand.v[", i, "]");
				}
			}

			return result;
		}

		Vector opBinary(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/")) 
		{
			Vector result;

			version(X86_64)
			{
				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[R10], XMM0;
				}
			}
			else
			{
				static foreach(i; 0 .. N)
				{
					mixin("result.v[", i, "] = this.v[", i, "] " ~op~ " operand;");
				}
			}

			return result;
		}
	}
	else
	{
		Vector opBinary(string op, U)(U operand) if(is(U: Vector) && U._N == N && (op == "*" || op == "+" || op == "-" || op == "/"))
		{
			Vector res;
			
			static foreach(i; 0 .. N)
			{
				mixin("res.v[", i, "] = v[", i, "] " ~ op ~ " operand.v[", i, "];");
			}

			return res;
		}

		Vector opBinary(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/"))
		{
			Vector res;
			Vector other = operand;

			static foreach(i; 0 .. N)
			{
				mixin("res.v[", i, "] = v[", i, "] " ~ op ~ " other.v[", i, "];");
			}

			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;

		static foreach(i; 0 .. N)
		{
			rev.v[i] = cast(U._T)v[i];
		}

		return result;
	}

	template IsConvertible(U)
	{
		enum bool IsConvertible = !is(U == Vector) && __traits(compiles, { U u; T t = cast(T)u; } );
	}

	template TypesAreNumeric(U, V)
	{
		enum bool TypesAreNumeric = isNumeric!(U) && isNumeric!(V);
	}

	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 = 0;
		Row[D]       rows;
		MatrixVec[D] vec;
	}

	// TODO: setup 

	this(U...)(U values)
	{
		static if((U.length == N) && (allSatisfy!(IsTypeAssignable, U) || allSatisfy!(isFloatingPoint, 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;
		}
	}

	this(U)(U x) if(is(U: typeof(this.v)))
	{
		this.v = 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(Abs(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)(Vec4 x) if(is(T: f32) && (op == "*"))
		{
			Vec4 result = 0.0;
			
			// TODO: optimize
			Mat4MulVec4(&this, &x, &result);

			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);
			Mat4Mul(&this, &x, &result);

			return result;
		}
	}

	template IsTypeAssignable(U)
	{
		enum bool IsTypeAssignable = std.traits.isAssignable!(T, U);
	}

	template IsMatrixInstantiation(U)
	{
		enum bool IsMatrixInstantiation = isInstanceOf!(Matrix, U);
	}
}

struct Quat
{
	union
	{
		f32[4] v   = 0;
		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;

		// TODO: optimize
		Mat4FromQuat(&this, &result);

		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;
	}
}

void
Mat4Mul(Mat4* l, Mat4* r, Mat4* result)
{
	version(X86_64)
	{
		asm @trusted 
		{
			mov    R8,   l;
			mov    R9,   r;
			mov    R10,  result;

			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, XMM4;
			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;
		}
	}
}

void
Vec4MulAddScale(Vec4* a, f32 s, Vec4* dst) // glm_vec4_muladds
{
	(*dst) += (*a)*s;
}

void
Mat4Inv(Mat4* mat, Mat4* dst) // glm_mat4_inv
{
  f32 a   = mat.rows[0][0], b   = mat.rows[0][1], c   = mat.rows[0][2], d   = mat.rows[0][3],
      e   = mat.rows[1][0], f   = mat.rows[1][1], g   = mat.rows[1][2], h   = mat.rows[1][3],
      i   = mat.rows[2][0], j   = mat.rows[2][1], k   = mat.rows[2][2], l   = mat.rows[2][3],
      m   = mat.rows[3][0], n   = mat.rows[3][1], o   = mat.rows[3][2], p   = mat.rows[3][3],

      c1  = k * p - l * o,  c2  = c * h - d * g,  c3  = i * p - l * m,
      c4  = a * h - d * e,  c5  = j * p - l * n,  c6  = b * h - d * f, 
      c7  = i * n - j * m,  c8  = a * f - b * e,  c9  = j * o - k * n,
      c10 = b * g - c * f,  c11 = i * o - k * m,  c12 = a * g - c * e,

      idt = 1.0f/(c8*c1+c4*c9+c10*c3+c2*c7-c12*c5-c6*c11), ndt = -idt;

  dst.rows[0][0] = (f * c1  - g * c5  + h * c9)  * idt;
  dst.rows[0][1] = (b * c1  - c * c5  + d * c9)  * ndt;
  dst.rows[0][2] = (n * c2  - o * c6  + p * c10) * idt;
  dst.rows[0][3] = (j * c2  - k * c6  + l * c10) * ndt;

  dst.rows[1][0] = (e * c1  - g * c3  + h * c11) * ndt;
  dst.rows[1][1] = (a * c1  - c * c3  + d * c11) * idt;
  dst.rows[1][2] = (m * c2  - o * c4  + p * c12) * ndt;
  dst.rows[1][3] = (i * c2  - k * c4  + l * c12) * idt;

  dst.rows[2][0] = (e * c5  - f * c3  + h * c7)  * idt;
  dst.rows[2][1] = (a * c5  - b * c3  + d * c7)  * ndt;
  dst.rows[2][2] = (m * c6  - n * c4  + p * c8)  * idt;
  dst.rows[2][3] = (i * c6  - j * c4  + l * c8)  * ndt;

  dst.rows[3][0] = (e * c9  - f * c11 + g * c7)  * ndt;
  dst.rows[3][1] = (a * c9  - b * c11 + c * c7)  * idt;
  dst.rows[3][2] = (m * c10 - n * c12 + o * c8)  * ndt;
  dst.rows[3][3] = (i * c10 - j * c12 + k * c8)  * idt;
}

void
Mat4MulVec4(Mat4* m, Vec4* v, Vec4* dst) // glm_mat4_mulv
{
  dst.v[0] = m.rows[0][0] * v.v[0] + m.rows[1][0] * v.v[1] + m.rows[2][0] * v.v[2] + m.rows[3][0] * v.v[3];
  dst.v[1] = m.rows[0][1] * v.v[0] + m.rows[1][1] * v.v[1] + m.rows[2][1] * v.v[2] + m.rows[3][1] * v.v[3];
  dst.v[2] = m.rows[0][2] * v.v[0] + m.rows[1][2] * v.v[1] + m.rows[2][2] * v.v[2] + m.rows[3][2] * v.v[3];
  dst.v[3] = m.rows[0][3] * v.v[0] + m.rows[1][3] * v.v[1] + m.rows[2][3] * v.v[2] + m.rows[3][3] * v.v[3];
}

void
Mat4FromQuat(Quat* q, Mat4* dst) // glm_quat_mat4
{
	f32 w,  x,  y,  z,
			xx, yy, zz,
			xy, yz, xz, 
			wx, wy, wz, norm, s;

	norm = Norm(q);
	s    = norm > 0.0f ? 2.0f/norm : 0.0f;

  x = q.v[0];
  y = q.v[1];
  z = q.v[2];
  w = q.v[3];

  xx = s * x * x;  xy = s * x * y;  wx = s * w * x;
  yy = s * y * y;  yz = s * y * z;  wy = s * w * y;
  zz = s * z * z;  xz = s * x * z;  wz = s * w * z;

  dst.rows[0][0] = 1.0f - yy - zz;
  dst.rows[1][1] = 1.0f - xx - zz;
  dst.rows[2][2] = 1.0f - xx - yy;

  dst.rows[0][1] = xy + wz;
  dst.rows[1][2] = yz + wx;
  dst.rows[2][0] = xz + wy;

  dst.rows[1][0] = xy - wz;
  dst.rows[2][1] = yz - wx;
  dst.rows[0][2] = xz - wy;

  dst.rows[0][3] = 0.0f;
  dst.rows[1][3] = 0.0f;
  dst.rows[2][3] = 0.0f;
  dst.rows[3][0] = 0.0f;
  dst.rows[3][1] = 0.0f;
  dst.rows[3][2] = 0.0f;
  dst.rows[3][3] = 1.0f;
}

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) // glm_quatv
{
	Quat q;

  f32 a = angle * 0.5f;
  f32 c = cos(a);
  f32 s = sin(a);

	Vec3 k = Normalize(axis);

  q.x = s * k.x;
  q.y = s * k.y;
  q.z = s * k.z;
  q.w = c;

	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(Quat* q)
{
	return Norm(&q.vec);
}

pragma(inline) f32
Norm(Vec3* v)
{
	return sqrt(Dot(v, v));
}

pragma(inline) f32
Norm(Vec4* v)
{
	// TODO: SIMD this
	return sqrt(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)
	{
		static foreach(i; 0 .. vec._N)
		{
			vec.v[i] = 0.0;
		}
	}
	else
	{
		static foreach(i; 0 .. vec._N)
		{
			vec.v[i] *= (1.0 / length);
		}
	}
}

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 / sqrt(dot);

	return q;
}

pragma(inline) Mat4
Perspective(bool flip_y = true)(f32 fov, f32 aspect, f32 near, f32 far) // glm_perspective_lh_zo
{
	Mat4 mat;

  f32 f  = 1.0f / tanf(fovy * 0.5f);
  f32 fn = 1.0f / (nearZ - farZ);

  mat.rows[0][0] = f / aspect;
  mat.rows[1][1] = f;
  mat.rows[2][2] = -far * fn;
  mat.rows[2][3] = 1.0f;
  mat.rows[3][2] = near * far * fn;

	static if(flip_y)
	{
		mat[1, 1] *= -1.0;
	}

	return res;
}

void
Ortho(Mat4* mat, f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far) // glm_ortho_lh_zo
{
	MatZero(mat);

  f32 rl = 1.0f / (right  - left);
  f32 tb = 1.0f / (top    - bottom);
  f32 fn =-1.0f / (far    - near);

  mat.rows[0][0] = 2.0f * rl;
  mat.rows[1][1] = 2.0f * tb;
  mat.rows[2][2] =-fn;
  mat.rows[3][0] =-(right  + left)    * rl;
  mat.rows[3][1] =-(top    + bottom)  * tb;
  mat.rows[3][2] = near * fn;
  mat.rows[3][3] = 1.0f;
}

Mat4
Ortho(f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far)
{
	Mat4 mat;
	Ortho(&mat, left, bottom, right, top, near, far);
	return mat;
}

T
Mix(T, U)(T a, T b, U t) if((IsVector!(T) || is(T == f32) || is(T == f64)) && (is(U == f32) || is(U == f64)))
{
	return a + t * (b - a);
}

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) // glm_lookat_lh
{
	Mat4 result;

	Vec3 f = Normalize(center - eye);

	Vec3 s = CrossN(up, f);
	Vec3 u = Cross(f, s);

  result.rows[0][0] = s[0];
  result.rows[0][1] = u[0];
  result.rows[0][2] = f[0];
  result.rows[1][0] = s[1];
  result.rows[1][1] = u[1];
  result.rows[1][2] = f[1];
  result.rows[2][0] = s[2];
  result.rows[2][1] = u[2];
  result.rows[2][2] = f[2];
  result.rows[3][0] =-Dot(&s, &eye);
  result.rows[3][1] =-Dot(&u, &eye);
  result.rows[3][2] =-Dot(&f, &eye);
  result.rows[0][3] = result.rows[1][3] = result.rows[2][3] = 0.0f;
  result.rows[3][3] = 1.0f;

	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);
	Normalize(dst);
}

pragma(inline) void
Cross(Vec3 a, Vec3 b, Vec3* dst)
{
  dst.x = a.y * b.z - a.z * b.y;
  dst.y = a.z * b.x - a.x * b.z;
  dst.z = a.x * b.y - a.y * b.x;
}

pragma(inline) void
MatZero(Mat4* mat)
{
	mat.v[] = 0.0;
}

pragma(inline) void
Translate(Mat4* mat, Vec3 vec)
{
	Vec4MulAddScale(&mat.vec[0], vec.x, &mat.vec[3]);
	Vec4MulAddScale(&mat.vec[1], vec.y, &mat.vec[3]);
	Vec4MulAddScale(&mat.vec[2], vec.z, &mat.vec[3]);
}

pragma(inline) Mat4
Inverse(Mat4 mat)
{
	Mat4 res;
	MatZero(&res);

	// TODO: optimize
	Mat4Inv(&mat, &res);

	return res;
}

void
Transform(Vec3* vec, Mat4* mat, f32 last = 1.0)
{
	Vec4 tvec = Vec4(*vec, last);
	tvec = *mat * tvec;
	*vec = tvec.xyz;
}

pragma(inline) T
Lerp(T)(T x, T y, T a)
{
	return x * (1 - a) + y * a;
}

pragma(inline) T
InverseLerp(T)(T v, T min, T max)
{
	return (v - min) / (max - min);
}

pragma(inline) T
Remap(T)(T v, T in_min, T in_max, T out_min, T out_max)
{
	T t = InverseLerp(v, in_min, in_max);
	return Lerp(out_min, out_max, t);
}

template 
IsVector(T)
{
//	import std.traits : isInstanceOf;
	enum bool IsVector = isInstanceOf!(Vector, T);
}

T
Clamp(T)(T value, T min, T max) if(isNumeric!(T))
{
	assert(min <= max, "Clamp assertion failed: min is higher than max");
	T x = value < min ? min : value;
	return x > max ? max : x;
}

T
Clamp(T)(T value, T min, T max) if(IsVector!(T))
{
	static foreach(i; 0 .. value._N)
	{
		value.v[i] = Clamp(value.v[i], min.v[i], max.v[i]);
	}

	return value;
}

version(DLIB_TEST)
{
	void DLibTestMath()
	{
		enum FLOAT_MAX = f32.max;
		enum FLOAT_MIN = -f32.max;

		/*

		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");
		}
		*/

		{ // Initializers
			u32[4] arr = [1, 2, 3, 4];
			Vec4 vec = Vec4(arr);
			assert(vec == Vec4(1.0, 2.0, 3.0, 4.0));

			Mat2 mat = Mat2(1.0, 0.0, 0.0, 1.0);

			f32[16] floats = 22.0;
			Mat4 mat4 = Mat4(floats);
			assert(mat4.v == floats);

			Quat quat = Quat(1.0, 1.0, 1.0, 1.0);

			Quat quat2;
		}

		{
			Vec3 vec = Vec3(1.0, 2.0, 3.0);
			Mat4 mat = Mat4(
				1.0, 1.0, 1.0, 1.0,
				2.0, 2.0, 2.0, 2.0,
				3.0, 3.0, 3.0, 3.0,
				4.0, 4.0, 4.0, 4.0
			);

			Transform(&vec, &mat, 1.0);

			assert(vec == Vec3(18.0));

			Vec4 low  = Vec4(-5.0);
			Vec4 high = Vec4(+5.0);
			Vec4 test = Vec4(-500.0);

			test = Clamp(test, low, high);

			assert(test == low);

			test = Vec4(+500.0);
			test = Clamp(test, low, high);

			assert(test == high);

		}

		{
			Vec4 m0 = Vec4(0.0);
			Vec4 m1 = Vec4(10.0);

			assert(Mix(m0, m1, 0.0)  == m0);
			assert(Mix(m0, m1, 1.0)  == m1);
			assert(Mix(m0, m1, 0.5)  == Vec4(5.0));
			assert(Mix(m0, m1, 0.75) == Vec4(7.5));

			void TestModify(Vec4 v)
			{
				v.r = 55;
			}
		}

		{
			u32 max = Max(50, 33, 123.0);
			assert(max == 123);
		}

		{
			Vec2 v0 = 50U;

			assert(Abs(v0.x-50.0) < 0.0009 && Abs(v0.y-50.0) < 0.0009);

			U8Vec2 v1 = U8Vec2(55U);

			assert(v1.x == 55 && v1.y == 55);
		}

		{
			assert(Ceil(10.5) == 11.0);
			assert(Floor(100.33) == 100.0);
			assert(Abs(-500) == 500);
			assert(Abs(-500.0) == 500.0);

			assert(Clamp(55, 10, 40)  == 40);
			assert(Clamp(-20, -5, 55) == -5);
		}
	}

	unittest
	{
		DLibTestMath();
	}
}
