1 module polyplex.math.vector; 2 import std.math, std.traits, std..string; 3 4 private bool ProperVectorBinaryOperation(string op) { 5 return op == "/" || op == "*" || op == "+" || op == "-"; 6 } 7 8 private bool ProperVectorUnaryOperation(string op) { 9 return op == "-" || op == "+"; 10 } 11 12 private auto RSwizzleIndex(char c) { 13 switch ( toLower(c) ) { 14 default: assert(false, "Trying to swizzle invalid component '%s'".format(c)); 15 case 'r': case 'x': return 0; 16 case 'g': case 'y': return 1; 17 case 'b': case 'z': return 2; 18 case 'a': case 'w': return 3; 19 } 20 } 21 unittest { 22 assert(RSwizzleIndex('r') == 0); 23 assert(RSwizzleIndex('G') == 1); 24 assert(RSwizzleIndex('z') == 2); 25 assert(RSwizzleIndex('W') == 3); 26 } 27 28 // Returns a list of indices for the swizzle, intended to be enumerated over 29 private auto GenerateSwizzleList(string swizzle_list)() { 30 assert(swizzle_list.length <= 4, "Can't swizzle with more than 4 elements"); 31 int[] buffer; 32 static foreach ( swizzle; swizzle_list ) { 33 buffer ~= RSwizzleIndex(swizzle); 34 } 35 return buffer; 36 } 37 38 /** 39 Applies generic vector operations such as addition, subtraction etc. 40 **/ 41 private template GenericVectorOperatorFunctionsMixin(T, int Dim) { 42 private alias GVec = typeof(this); 43 44 /// return a vector by this vector `op` a scalar T 45 public GVec opBinary(string op)(T rhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) { 46 GVec temp_vector; 47 // Apply operator to each element in the vector. temp[i] = vec[i] op rhs 48 static foreach ( i; 0 .. Dim ) 49 mixin(q{ temp_vector.data[i] = this.data[i] %s rhs; }.format(op)); 50 return temp_vector; 51 } 52 53 /// return a vector by a scalar T `op` this vector 54 public GVec opBinaryRight(string op)(T lhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) { 55 GVec temp_vector; 56 // Apply operator to each element in the vector. temp[i] = lhs op vec[i] 57 static foreach ( i; 0 .. Dim ) 58 mixin(q{ temp_vector.data[i] = lhs %s this.data[i] ; }.format(op)); 59 return temp_vector; 60 } 61 62 /// return a vector operated on by this `op` another vector (of same type) 63 public GVec opBinary(string op)(GVec rhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) { 64 GVec temp_vector; 65 // Apply operator to each element in the vector. temp[i] = vec[i] op rhs[i] 66 static foreach ( i; 0 .. Dim ) 67 mixin(q{ temp_vector.data[i] = this.data[i] %s rhs.data[i]; }.format(op)); 68 return temp_vector; 69 } 70 71 /// Operate on vector with a unary operator 72 public GVec opUnary(string op)() pure const nothrow if ( ProperVectorUnaryOperation(op) ) { 73 GVec temp_vector; 74 // Apply unary operator to vector. temp[i] = op vec[i] 75 static foreach ( i; 0 .. Dim ) 76 mixin(q{ temp_vector.data[i] = %s this.data[i]; }.format(op)); 77 return temp_vector; 78 } 79 80 /// Equality comparison (only on ints) 81 static if ( is(Type == int) ) 82 public bool opEquals(GVec rhs) pure const nothrow { 83 bool equality = true; 84 // propagate equality checks throughout each element comparison of the vectors 85 static foreach ( i; 0 .. Dim ) 86 equality &= this.data[i] == rhs.data[i]; 87 return equality; 88 } 89 90 /// Casts this vector to another vector type U 91 public U opCast(U)() pure const nothrow if ( IsVector!U && U.Dim == Dim ) { 92 U temp_vector; 93 // Apply cast to each element of vector 94 static foreach ( i; 0 .. Dim ) 95 temp_vector.data[i] = cast(U.Type)(this.data[i]); 96 return temp_vector; 97 } 98 99 /// Operator assignment on a scalar or vector 100 public void opOpAssign(string op, U)(U rhs) pure nothrow if ( __traits(isArithmetic, U) || IsVector!U ) { 101 mixin(q{ this = this %s rhs; }.format(op)); 102 } 103 } 104 105 // Unittest on operations 106 unittest { 107 assert(int3(5) == int3(5)); 108 assert(int3(3) != int3(5)); 109 assert((int3(6)/int3(2) - int3(1)) + int3(3)*int3(5) == int3(17)); 110 assert(int3(6) - 1 == int3(5)); 111 assert(1 - int3(6) == int3(-5)); 112 assert(-int3(6) == +int3(-6)); 113 assert(!__traits(compiles, int3(5)%int3(3))); 114 assert(__traits(compiles, cast(float3)int3(3))); 115 } 116 117 /** 118 Applies default functions that should be given to vectors, such as Zero and distance 119 **/ 120 private template GenericVectorDefaultFunctionsMixin(T, int Dim) { 121 private alias GVec = typeof(this); 122 123 /// Returns a vector of zero elements 124 public static GVec Zero ( ) pure nothrow { return GVec(cast(T)0); } 125 126 /// Returns a vector of one elements 127 public static GVec One ( ) pure nothrow { return GVec(cast(T)1); } 128 129 /// Returns a vector of NaN elements (floating points only) 130 static if (__traits(isFloating, T)) 131 public static GVec NaN ( ) pure nothrow { 132 return GVec(T.nan); 133 } 134 135 /// Dot product operator 136 public T Dot(GVec rhs) pure const nothrow { 137 T temp = 0; 138 // multiply each element together and add to temp 139 static foreach ( i; 0 .. Dim ) 140 temp += this.data[i]*rhs.data[i]; 141 return temp; 142 } 143 144 /// Magnitude/length of a vector (distance from origin) 145 public alias Magnitude = Length; 146 public T Length() pure const nothrow { 147 T accumulator = 0; 148 // Accumulate the squared of each element in the vector 149 static foreach ( i; 0 .. Dim ) 150 accumulator += this.data[i]*this.data[i]; 151 // Sqrt the float equivalent and then cast to proper type 152 return cast(T)sqrt(cast(float)accumulator); 153 } 154 155 /// Distance between two vectors 156 public T Distance(GVec rhs) pure const nothrow { 157 return (this - rhs).Length; 158 } 159 160 /// Returns a normalized vector 161 GVec Normalize() pure const nothrow { 162 return this.opBinary!"/"(this.Length()); 163 } 164 } 165 166 // unittest on generic functions 167 unittest { 168 assert(int3(1, 2, 3).Dot(cast(int3)float3(4, 5, 6)) == 32); 169 assert(int3(1, 2, 3).Length == 3); 170 assert(int3(1, 2, 3).Distance(int3(4, 1, 3)) == 3); 171 assert(int3.One.x == 1); 172 assert(!__traits(compiles, int3.Nan)); 173 import std.math : abs; 174 assert(abs(float3(0.2f, 0.6f, 0.5f).Normalize.x-0.248069f) <= 0.02f); 175 } 176 177 /** 178 Applies assertions, immutables, introspections, component swizzling and other 179 generic functions that can be applied to a vector 180 **/ 181 private template GenericVectorMixin(T, int _Dim) { 182 // -- assertions about type and length 183 static assert(_Dim >= 2 && _Dim <= 4, "The length of a vector must be 2, 3 or 4."); 184 static assert(__traits(isArithmetic, T), "Type must be arithmetic (float, int, etc)"); 185 // -- setup immutables 186 public immutable static size_t Dim = _Dim; 187 public static alias Type = T; 188 189 // -- introspection functions 190 /// Checks if vector U is compatible with this vector type 191 public enum IsCompatible(U) = (U.Dim == Dim && is(U.Type == T)); 192 193 // -- mixins 194 mixin GenericVectorOperatorFunctionsMixin!(Type, Dim); 195 mixin GenericVectorDefaultFunctionsMixin!(Type, Dim); 196 197 /// Swizzles on one single component returning a single value (ei .x, returning a T) 198 private auto SwizzleOnOneComponent(string swizzle_list)() pure const nothrow { 199 // Since GenerateSwizzleList returns an array, index the first element 200 return this.data[(GenerateSwizzleList!swizzle_list)[0]]; 201 } 202 203 /// Swizzles on multiple components returning a vector (ei .xy, returning Vector2T!T) 204 private auto SwizzleOnMultipleComponents(string swizzle_list)() pure const nothrow { 205 // create a temporary vector 206 Vector!(T, swizzle_list.length) ret_vec; 207 // iterate the swizzle list and fill vector 208 static foreach ( iter, index; GenerateSwizzleList!swizzle_list ) 209 ret_vec.data[iter] = this.data[index]; 210 return ret_vec; 211 } 212 213 /// opDispatch for vector swizzling 214 public @property auto opDispatch(string swizzle_list, U = void)() pure const nothrow if ( swizzle_list.length <= 4 ) { 215 // Check if returning a single element, or a vector 216 static if ( swizzle_list.length == 1 ) 217 return SwizzleOnOneComponent!swizzle_list; 218 else 219 return SwizzleOnMultipleComponents!swizzle_list; 220 } 221 222 /// opDispatch for vector swizzling assignment 223 public @property auto opDispatch(string swizzle_list, U)(U x) pure nothrow { 224 // Check if setting a single element or a list of elements 225 static if ( swizzle_list.length == 1 ) { 226 // GenerateSwizzleList returns an array, so get first element and set it to x 227 this.data[(GenerateSwizzleList!swizzle_list)[0]] = x; 228 return x; // return this scalar ( v.x = v.y = .. ) 229 } else { 230 // iterate swizzle list and set values of this vector 231 Vector!(T, swizzle_list.length) tvec = x; // convert scalar to vector 232 static foreach ( iter, index; GenerateSwizzleList!swizzle_list ) 233 this.data[index] = tvec.data[iter]; 234 return tvec; // return this vector ( v.xy = v.zw = .. ) 235 } 236 } 237 238 239 /// Returns a pointer to the data container of this vector 240 public inout(T)* ptr() pure inout nothrow { return data.ptr; } 241 } 242 243 // unittest on swizzling 244 unittest { 245 int4 x = int4(1, 2, 3, 4); 246 assert(x.xyzw == int4(1, 2, 3, 4)); 247 assert(x.xxxx == int4(1)); 248 assert(x.wzyx == int4(4, 3, 2, 1)); 249 assert(x.xz == int2(1, 3)); 250 assert(x.x == 1); 251 int2 y = int2(1, 2); 252 assert(y.xyxy == int4(1, 2, 1, 2)); 253 assert(y.yyy == int3(2, 2, 2)); 254 } 255 256 // --- Vector declaration and utility introspection functions 257 struct Vector(T, int Dim); 258 259 // `is` has an interesting property in that it can generate templates in-place 260 // using its second parameter. Thus you can use an argument-list to check if 261 // T is of any type Vector(U[0], U[1]) 262 /// Returns if type is a vector 263 enum IsVector(T) = is(T : Vector!U, U...); 264 265 unittest { 266 assert(!IsVector!float); 267 assert(IsVector!float2); 268 } 269 270 // --- Vector2T struct 271 alias Vector2T(T) = Vector!(T, 2); 272 alias vec2 = Vector2T!float; 273 alias vec2i = Vector2T!int; 274 alias float2 = Vector2T!float; 275 alias int2 = Vector2T!int; 276 alias Vector2 = Vector2T!float; 277 alias Vector2i = Vector2T!int; 278 279 struct Vector(T, int _Dim:2) { 280 public T[2] data = [0, 0]; 281 282 mixin GenericVectorMixin!(T, 2); 283 284 /// Constructor for scalars 285 public this(U)(U x) { data[] = cast(T)x; } 286 287 /// Constructor for parameter lists 288 public this(U)(U x, U y) { data[] = [x, y]; } 289 290 /// Constructor for vectors of same type 291 public this(Vector2T!T vec) { data[] = vec.data; } 292 293 /// Constructor for explicit lists 294 public this(T[] list) { 295 assert(list.length == 2, "List length mismatch"); 296 data[] = list; 297 } 298 299 public string toString() { return `<%s, %s>`.format(data[0], data[1]); } 300 public alias ToString = toString; 301 } 302 303 // --- Vector3T struct 304 alias Vector3T(T) = Vector!(T, 3); 305 alias vec3 = Vector3T!float; 306 alias vec3i = Vector3T!int; 307 alias float3 = Vector3T!float; 308 alias int3 = Vector3T!int; 309 alias Vector3 = Vector3T!float; 310 alias Vector3i = Vector3T!int; 311 312 struct Vector(T, int _Dim:3) { 313 public T[3] data = [0, 0, 0]; 314 315 mixin GenericVectorMixin!(T, 3); 316 317 /// Constructor for scalars 318 public this(U)(U x) { data[] = cast(T)x; } 319 320 /// Constructor for parameter lists 321 public this(U)(U x, U y, U z) { data[] = [x, y, z]; } 322 323 /// Constructor for vectors of same type 324 public this(Vector3T!T vec) { data[] = vec.data; } 325 326 /// Constructor for explicit lists 327 public this(T[] list) { 328 assert(list.length == 3, "List length mismatch"); 329 data[] = list; 330 } 331 332 public string toString() { return `<%s, %s, %s>`.format(data[0], data[1], data[2]); } 333 public alias ToString = toString; 334 335 public Vector3T!T Cross(Vector3T!T a, Vector3T!T b) pure const nothrow { 336 return Vector3T!T( 337 a.y*b.z - a.z*b.y, 338 a.z*b.x - a.x * b.z, 339 a.x*b.y - a.y * b.x); 340 } 341 } 342 343 // --- Vector4T struct 344 alias Vector4T(T) = Vector!(T, 4); 345 alias vec4 = Vector4T!float; 346 alias vec4i = Vector4T!int; 347 alias float4 = Vector4T!float; 348 alias int4 = Vector4T!int; 349 alias Vector4 = Vector4T!float; 350 alias Vector4i = Vector4T!int; 351 352 struct Vector(T, int _Dim:4) { 353 public T[4] data = [0, 0, 0, 0]; 354 355 mixin GenericVectorMixin!(T, 4); 356 357 /// Constructor for scalars 358 public this(U)(U x) { data[] = cast(T)x; } 359 360 /// Constructor for parameter lists 361 public this(U)(U x, U y, U z, U w) { data[] = [x, y, z, w]; } 362 363 /// Constructor for vectors of same type 364 public this(Vector4T!T vec) { data[] = vec.data; } 365 366 /// Constructor for explicit lists 367 public this(T[] list) { 368 assert(list.length == 4, "List length mismatch"); 369 data[] = list; 370 } 371 372 string toString() { return `<%s, %s, %s, %s>`.format(data[0], data[1], data[2], data[3]); } 373 alias ToString = toString; 374 }