SFML/bindings/d/import/dsfml/system/vector.d
2011-01-30 18:38:20 +00:00

470 lines
13 KiB
D

/*
* DSFML - SFML Library wrapper for the D programming language.
* Copyright (C) 2010 Andreas Hollandt
*
* This software is provided 'as-is', without any express or
* implied warranty. In no event will the authors be held
* liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute
* it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented;
* you must not claim that you wrote the original software.
* If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but
* is not required.
*
* 2. Altered source versions must be plainly marked as such,
* and must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any
* source distribution.
*/
module dsfml.system.vector;
import std.conv;
import std.math;
import std.traits : isFloatingPoint;
import std.typetuple;
/**
* generic fixed-size Vector struct
*
* Params:
* T = element type
* dim = vector dimension
*/
struct Vector(T, uint dim)
{
static assert (dim >= 2 && dim <= 4);
// vectors of 3 floats are extended to 4 to make it possible to use SSE optimizations
private const realdim = (is(T == float) && dim == 3 && sseAvailable) ? 4 : dim;
// vectors of (3)4 floats or 2 doubles will use SSE
private const bool useSSE = (is(T == float) && realdim == 4 /* || is(T == double) && dim == 2 */ ) && sseAvailable;
private alias LengthReturnType!(T) LengthType; // the type returned by length
union
{
/// normal struct element access
struct
{
static if (dim >= 1) T x;
static if (dim >= 2) T y;
static if (dim >= 3) T z;
static if (dim >= 4) T w;
}
struct
{
static if (dim >= 1) T r;
static if (dim >= 2) T g;
static if (dim >= 3) T b;
static if (dim >= 4) T a;
}
// only the array has the hidden 4th value in case of vec3f
// this is to be able to foreach over tuple without computing w unnecessarily
T[realdim] cell; /// array access
Repeat!(T, dim) tuple; /// for tuple access
}
// zero vectors
static if (2 == dim) const static Vector zero = Vector(0, 0);
static if (3 == dim) const static Vector zero = Vector(0, 0, 0);
static if (4 == dim) const static Vector zero = Vector(0, 0, 0, 0);
static if (2 == dim) const static Vector one = Vector(1, 1);
static if (3 == dim) const static Vector one = Vector(1, 1, 1);
static if (4 == dim) const static Vector one = Vector(1, 1, 1, 1);
static if (2 == dim) const static Vector unitX = Vector(1, 0);
static if (3 == dim) const static Vector unitX = Vector(1, 0, 0);
static if (4 == dim) const static Vector unitX = Vector(1, 0, 0, 0);
static if (2 == dim) const static Vector unitY = Vector(0, 1);
static if (3 == dim) const static Vector unitY = Vector(0, 1, 0);
static if (4 == dim) const static Vector unitY = Vector(0, 1, 0, 0);
static if (3 == dim) const static Vector unitZ = Vector(0, 0, 1);
static if (4 == dim) const static Vector unitZ = Vector(0, 0, 1, 0);
static if (4 == dim) const static Vector unitW = Vector(0, 0, 0, 1);
/// ensure that no component is a NaN
invariant()
{
assert(isValid());
}
// checks if the elements aren't NaNs
private bool isValid() const
{
static if (dim >= 1) if (isNaN(x)) return false;
static if (dim >= 2) if (isNaN(y)) return false;
static if (dim >= 3) if (isNaN(z)) return false;
static if (dim >= 4) if (isNaN(w)) return false;
return true;
}
/************************************************************************************
* Operator overloading
***********************************************************************************/
/// negate the vector
Vector opUnary(string op : "-")() const
{
static if (dim == 2) return Vector(-x, -y);
else static if (dim == 3) return Vector(-x, -y, -z);
else static if (dim == 4) return Vector(-x, -y, -z, -w);
}
/// dot product
T opBinary(string op : "*")(typeof(this) v) const
if (is(typeof(T+T)) && is(typeof(T*T)))
{
static if (dim == 2) return x*v.x + y*v.y;
else static if (dim == 3) return x*v.x + y*v.y + z*v.z;
else static if (dim == 4) return x*v.x + y*v.y + z*v.z + w*v.w;
}
/// element-wise operations, +, -,
Vector opBinary(string op, U:typeof(this))(U v) const
// check if the operation is supported on the type T
if (op != "*" && (op == "+" && is(typeof(T+T)) || op == "-" && is(typeof(T-T)) || op == "*" && is(typeof(T*T))
|| op == "/" && is(typeof(T/T)) || op == "%" && is(typeof(T%T))))
{
Vector res = void;
foreach (i, x; tuple)
mixin("res.tuple[i] = tuple[i] " ~ op ~ " v.tuple[i];");
return res;
}
/// operations with a scalar
Vector opBinary(string op, U)(U s) const
{
Vector res = void;
foreach(i, x; tuple)
mixin("res.tuple[i] = tuple[i] " ~ op ~ " s;");
return res;
}
/// element-wise assign operations, +=, -=, ...
Vector opOpAssign(string op, U:Vector)(U v)
{
foreach (i, _; tuple)
mixin("tuple[i] " ~ op ~ "= v.tuple[i];");
return this;
}
/// (*=) overload
Vector opOpAssign(string op, U)(U s)
if (!is(U:typeof(this))) // TODO: there's some dmd bug about this
{
foreach (i, _; tuple)
mixin("tuple[i] " ~ op ~ "= s;");
return this;
}
/// return length*length
@property LengthType sqLength()
{
static if (2 == dim) return (x * x + y * y);
else static if (3 == dim) return (x * x + y * y + z * z);
else static if (4 == dim) return (x * x + y * y + z * z + w * w);
else static assert (false);
}
/// return the vector length
@property LengthType length()
{
static if (useSSE)
{
static if (is(t == float) && dim == 3) // make sure that w is 0
assert(w == 0);
float res;
auto p = cell.ptr;
asm
{
// movups XMM0, &cell;
mov EAX, p;
movups XMM0, [EAX];
mulps XMM0, XMM0; // v0 = vec(x*x, y*y, z*z, w*w)
movaps XMM1, XMM0; // v1 = v0
shufps XMM0, XMM1, 0x4e; // v0 = vec(z*z, w*w, x*x, y*y)
addps XMM0, XMM1; // v0 = vec(x*x + z*z, y*y + w*w, z*z + x*x, w*w + y*y)
movaps XMM1, XMM0; // v1 = v0
shufps XMM1, XMM1, 0x11; // v1 = vec(w*w + y*y, z*z + x*x, w*w + y*y, z*z + x*x)
addps XMM0, XMM1; // v0 = |vec|^2 at all 4 positions
rsqrtss XMM0, XMM0; // v0 = 1/sqrt(v0)
rcpss XMM0, XMM0; // v= = 1/v0
movss res, XMM0;
}
return res;
}
else
{
// compute squared length
auto ret = sqLength();
// compute sqrt
version(useFastSqrt)
{
static if (is(T == float))
return fastSqrt(ret);
}
return sqrt(ret);
}
}
void normalize()
{
static if (useSSE)
{
static if (is(t == float) && dim == 3) // make sure that w is 0
assert (w == 0, "vector component w isn't 0!");
auto p = cell.ptr;
asm
{
mov EAX, p;
movups XMM0, [EAX];
movaps XMM2, XMM0; // save it for later
mulps XMM0, XMM0; // v0 = vec(x*x, y*y, z*z, w*w)
movaps XMM1, XMM0; // v1 = v0
shufps XMM0, XMM1, 0x4e; // v0 = vec(z*z, w*w, x*x, y*y)
addps XMM0, XMM1; // v0 = vec(x*x + z*z, y*y + w*w, z*z + x*x, w*w + y*y)
movaps XMM1, XMM0; // v1 = v0
shufps XMM1, XMM1, 0x11; // v1 = vec(w*w + y*y, z*z + x*x, w*w + y*y, z*z + x*x)
addps XMM0, XMM1; // v0 = |vec|^2 at all 4 positions
rsqrtps XMM0, XMM0; // v0 = 1/sqrt(v0)
mulps XMM2, XMM0; // v2 = vec * v0
movups [EAX], XMM0;
}
}
else
{
auto len = length();
foreach(i, _; tuple) // bug 2411 workaround, foreach ref on tuples doesn't work
tuple[i] /= len;
}
}
/// return normalized version of this vector
Vector normalized()
{
Vector res = this;
res.normalize();
return res;
}
///
string toString()
{
string res = "[";
res ~= to!(string)(x);
static if (dim >= 2) res ~= ", " ~ to!(string)(y);
static if (dim >= 3) res ~= ", " ~ to!(string)(z);
static if (dim >= 4) res ~= ", " ~ to!(string)(w);
return res ~ "]";
}
static if (is(T == float))
{
/// do a quick normalize using fast approximate inverse sqrt
void quickNormalize()
{
T inv = invSqrt(sqLength);
this *= inv;
}
/// return a normalized version of this vector
Vector quickNormalized()
{
auto res = this;
res.quickNormalize();
return res;
}
}
/// return a pointer to the vector data
@property T* ptr()
{
return &x;
}
/// calculate distance to other vector
LengthType distance(Vector other)
{
assert (isValid);
assert (other.isValid);
other -= this; // doable cause other is a struct not ref
return other.length;
}
///
bool opEquals(ref const Vector v) const
{
assert (isValid);
assert (v.isValid);
static if (dim >= 1) if (x != v.x) return false;
static if (dim >= 2) if (y != v.y) return false;
static if (dim >= 3) if (z != v.z) return false;
static if (dim >= 4) if (w != v.w) return false;
return true;
}
/// swizzling
@property Vector!(T,n.length) opDispatch(string n)() const
if (allCharsValid(n,"xyzw"[0..dim]))
{
static if (n.length == 2) return
Vector!(T,n.length)(cell[n[0]-'x'], cell[n[1]-'x']);
static if (n.length == 3) return
Vector!(T,n.length)(cell[n[0]-'x'], cell[n[1]-'x'], cell[n[2]-'x']);
static if (n.length == 4) return
Vector!(T,n.length)(cell[n[0]-'x'], cell[n[1]-'x'], cell[n[2]-'x'], cell[n[3]-'x']);
}
// helper function
static private bool allCharsValid( string s, string valid )
{
foreach ( e1; s )
{
bool b = false;
foreach (e2; valid)
b |= e1 == e2;
if (!b)
return false;
}
return true;
}
///
bool isUnit()
{
real sql = cast(real)sqLength();
return abs(sql - 1.0) < 0.001;
}
}
/******* useful alias declarations *******/
alias Vector!(float, 2) Vector2f; ///
alias Vector!(float, 3) Vector3f; ///
alias Vector!(float, 4) Vector4f; ///
alias Vector!(double, 2) Vector2d; ///
alias Vector!(double, 3) Vector3d; ///
alias Vector!(double, 4) Vector4d; ///
alias Vector!(int, 2) Vector2i; ///
alias Vector!(int, 3) Vector3i; ///
alias Vector!(int, 4) Vector4i; ///
alias Vector!(uint, 2) Vector2ui; ///
alias Vector!(uint, 3) Vector3ui; ///
alias Vector!(uint, 4) Vector4ui; ///
alias Vector!(ushort, 2) Vector2us; ///
alias Vector!(ushort, 3) Vector3us; ///
alias Vector!(ushort, 4) Vector4us; ///
alias Vector!(ubyte, 2) Vector2ub; ///
alias Vector!(ubyte, 3) Vector3ub; ///
alias Vector!(ubyte, 4) Vector4ub; ///
// TODO: do all kinds of unittesting
import std.stdio;
unittest
{
writeln("unittests running");
Vector3f v = {1.5f, 1.f, 0.5f};
Vector3f w = {-1.f, 2.f, -0.5f};
writefln("v: %f w: %f", v.length - sqrt(3.5f), w.length - sqrt(5.25f));
// strangely calculating w.length is much less accurate
assert(v.length - sqrt(3.5f) < 0.001, sseAvailable ? "SSE length calculation failed" : "normal length calculation failed");
assert(w.length - sqrt(5.25f) < 0.001, sseAvailable ? "SSE length calculation failed" : "normal length calculation failed");
assert(v+w == Vector3f(0.5f, 3.f, 0.f));
assert(v-w == Vector3f(2.5f, -1.f, 1.f));
auto r = v.xy;
writeln(r);
}
/**
* compute 1/sqrt(x)
* assumes x > 0
*
* Copyright (C) 2002-2006 Chris Lomont
* explanation on www.lomont.org
*/
float invSqrt(float x)
{
assert(x > 0);
float xhalf = 0.5f * x;
int i = *cast(int*)&x; // get bits for floating value
i = 0x5f375a86 - (i >> 1); // gives initial guess y0 with magic number
x = *cast(float*)&i; // convert bits back to float
x = x*(1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
return x;
}
/**
* compute sqrt(x)
* assumes x >= 0
*/
float fastSqrt(float x)
{
assert(x >= 0);
int i = *cast(int*) &x;
if (0 == ((i >> 23)&255))
return 0; // close
return x * invSqrt(x);
}
// get the correct return type for the length function
private template LengthReturnType(T)
{
static if (is(T == float) || is(T == double) || is(T == real))
alias T LengthReturnType;
else
alias float LengthReturnType;
}
/// repeat a type count times
template Repeat(T, int count)
{
static if (!count)
alias TypeTuple!() Repeat;
else
alias TypeTuple!(T, Repeat!(T, count-1)) Repeat;
}
// determine SSE usability
// TODO: make more sophisticated
version(X86)
version(D_InlineAsm_X86)
const bool sseAvailable = is(typeof({void* foo; asm { mov EAX, foo; movups XMM0, [EAX]; } }));
version(X86_64)
version(D_InlineAsm_X86_64)
const bool sseAvailable = false; // TODO: add this