1 module polyplex.math.linear.vectors; 2 import std.math, std.traits, std.string, std.range.primitives, std.format; 3 import polyplex.math; 4 5 private: 6 7 /// Shared constructors between vectors 8 enum SharedVectorCtor = q{ 9 10 /// Special constructor that fills the vector with specified value 11 this(Y)(Y value) if (isNumeric!Y) { 12 static foreach(i; 0..Dimensions) { 13 values[i] = cast(T)value; 14 } 15 } 16 17 /// Construct from vector 18 this(Y)(Y other) if (IsVector!Y) { 19 static foreach(i; 0..Dimensions) { 20 // If we're outside the bounds of the other vector then skip the axies 21 static if (i < other.Dimensions) mixin(q{ values[i] = cast(T)other.values[i]; }); 22 } 23 } 24 25 /// Construct from static array of coordinates 26 this(T[Dimensions] values) { 27 this = values[0..Dimensions]; 28 } 29 30 /// Construct from dynamic array of coordinates 31 this(T[] values) { 32 this = values[0..Dimensions]; 33 } 34 }; 35 36 /// Vector operations are implemented via this. 37 mixin template SharedVectorOp(T, GVector, int Dimensions) { 38 39 static assert(Dimensions > 1 && Dimensions < 5, "The dimensions of a vector can minimum be 2, maximum be 4"); 40 41 /** 42 OpenGL helper function to get the pointer to the start of the vector data. 43 */ 44 T* ptr() { return values.ptr; } 45 46 /// Allow casting this vector to an other type of vector 47 Y opCast(Y)() if (IsVector!Y) { 48 return Y(this); 49 } 50 51 /// Allow doing binary operations between 2 vectors 52 GVector opBinary(string op, T2)(T2 other) if (IsVector!T2) { 53 GVector vec; 54 static foreach(i; 0..Dimensions) { 55 // If we're outside the bounds of the other vector then skip the axies 56 static if (i < other.Dimensions) mixin(q{ vec.values[i] = this.values[i] %s other.values[i]; }.format(op)); 57 } 58 return vec; 59 } 60 61 /// Allow doing binary operations between a vector and a numeric type 62 GVector opBinary(string op, T2)(T2 other) if (isNumeric!(T2)) { 63 GVector vec; 64 static foreach(i; 0..Dimensions) { 65 mixin(q{ vec.values[i] = this.values[i] %s other; }.format(op)); 66 } 67 return vec; 68 } 69 70 /// Binary operations between this and a vector or a numeric type. 71 void opOpAssign(string op, T2)(T2 other) if (isNumeric!T2 || IsVector!T2) { 72 mixin(q{this = this %s other;}.format(op)); 73 } 74 75 /// Binary operations between this and a numeric type. 76 void opAssign(T2)(T2 other) if (isNumeric!T2) { 77 this = [other]; 78 } 79 80 /// Binary operations between this and a vector. 81 void opAssign(T2)(T2 other) if (!is(T2 : GVector) && IsVector!T2) { 82 this = cast(GVector)other; 83 } 84 85 /// Binary operations between this and arrays. 86 void opAssign(T2)(T2 rhs) if (IsNumericArray!T2) { 87 static foreach(i; 0..Dimensions) { 88 values[i] = cast(T)rhs[i]; 89 } 90 } 91 92 /** 93 Get the difference between 2 vectors 94 Any difference outside the bounds of the vector will be ignored. 95 */ 96 GVector Difference(T2)(T2 other) if (IsVector!T2) { 97 return other-this; 98 } 99 100 /** 101 Get the distance between 2 vectors 102 Any difference outside the bounds of the vector will be ignored. 103 */ 104 T Distance(T2)(T2 other) if (IsVector!T2) { 105 return (other-this).Length; 106 } 107 108 /** 109 Get the length/magnitude of this vector 110 */ 111 T Length() { 112 T len; 113 static foreach(i; 0..Dimensions) { 114 len += (values[i]*values[i]); 115 } 116 return cast(T)Mathf.Sqrt(cast(float)len); 117 } 118 119 /** 120 Get the length/magnitude of this vector 121 */ 122 alias Magnitude = Length; 123 124 /** 125 Get the normalized vector 126 */ 127 GVector Normalize() { 128 // Normalize by dividing the values from the current values array by the magnitude/length 129 GVector vec; 130 immutable(T) len = Length(); 131 static foreach(i; 0..Dimensions) { 132 vec.values[i] = values[i]/len; 133 } 134 return vec; 135 } 136 137 /** 138 Get the dot product of 2 vectors 139 Any difference outside the bounds of the vector will be ignored. 140 */ 141 T Dot(T2)(T2 other) if (IsVector!T2) { 142 T dot; 143 static foreach(i; 0..Dimensions) { 144 static if (i < other.Dimensions) dot += (val*other.values[i]); 145 } 146 return cast(T)dot; 147 } 148 149 /** 150 Returns a one vector (vector of all ones) 151 */ 152 static GVector One() { 153 return GVector(1); 154 } 155 156 /** 157 Returns a zero vector (vector of all zeroes) 158 */ 159 static GVector Zero() { 160 return GVector(0); 161 } 162 163 /** 164 Returns a zero vector (vector of all NaN) 165 */ 166 static GVector NaN()() if (is(T : float) || is(T : double)) { 167 return GVector(T.nan); 168 } 169 170 /** 171 Returns a zero vector (vector of all infinity) 172 */ 173 static GVector Infinity()() if (is(T : float) || is(T : double)) { 174 return GVector(T.infinity); 175 } 176 177 /** 178 Cross product of 2D vector 179 */ 180 GVector Cross()(GVector other) if (Dimensions == 2) { 181 return GVector( 182 (this.Y*other.Z)-(this.Z*other.Y), 183 (this.X*other.Y)-(this.Y*other.X)); 184 } 185 186 /** 187 Cross product of 2D vector 188 */ 189 GVector Cross()(GVector other) if (Dimensions == 3) { 190 return GVector( 191 (this.Y*other.Z)-(this.Z*other.Y), 192 (this.Z*other.X)-(this.X*other.Z), 193 (this.X*other.Y)-(this.Y*other.X)); 194 } 195 196 static if (Dimensions >= 2) { 197 198 /** 199 Returns a vector pointing to the global up 200 */ 201 static GVector Up() { 202 GVector v; 203 v.Y = -1; 204 return v; 205 } 206 207 /** 208 Returns a vector pointing to the global down 209 */ 210 static GVector Down() { 211 GVector v; 212 v.Y = 1; 213 return v; 214 } 215 216 /** 217 Returns a vector pointing to the global left 218 */ 219 static GVector Left() { 220 GVector v; 221 v.X = -1; 222 return v; 223 } 224 225 /** 226 Returns a vector pointing to the global right 227 */ 228 static GVector Right() { 229 GVector v; 230 v.X = 1; 231 return v; 232 } 233 } 234 235 static if (Dimensions >= 3) { 236 237 /// TODO: If the Z axis is flipped then flip the logic here! 238 239 /** 240 Returns a vector pointing to the global forward 241 This is only usable on Vectors with a Z axis! 242 */ 243 static GVector Forward() { 244 GVector v; 245 v.Z = 1; 246 return v; 247 } 248 249 /** 250 Returns a vector pointing to the global backward 251 This is only usable on Vectors with a Z axis! 252 */ 253 static GVector Backward() { 254 GVector v; 255 v.Z = -1; 256 return v; 257 } 258 259 } 260 261 string toString() { 262 import std.conv : text; 263 return values.text; 264 } 265 } 266 267 268 public: 269 /** 270 A 2D vector 271 */ 272 @trusted 273 nothrow 274 struct Vector2T(T) if (isNumeric!T) { 275 private: 276 alias GVector = typeof(this); 277 278 public: 279 /// The count of dimensions the vector consists of 280 enum Dimensions = 2; 281 282 alias Type = T; 283 284 union { 285 struct { 286 /// The X component 287 T X = 0; 288 289 /// The Y component 290 T Y = 0; 291 } 292 293 /// An array of the values in this vector 294 T[Dimensions] values; 295 } 296 297 /// Construct from X and Y coordinates 298 this(Y)(Y x, Y y) if (isNumeric!Y) { 299 this.X = cast(T)x; 300 this.Y = cast(T)y; 301 } 302 303 mixin(SharedVectorCtor); 304 mixin SharedVectorOp!(T, GVector, Dimensions); 305 } 306 307 /** 308 A 3D vector 309 */ 310 @trusted 311 nothrow 312 struct Vector3T(T) if (isNumeric!T) { 313 private: 314 alias GVector = typeof(this); 315 316 public: 317 /// The count of dimensions the vector consists of 318 enum Dimensions = 3; 319 320 alias Type = T; 321 322 union { 323 struct { 324 /// The X component 325 T X = 0; 326 327 /// The Y component 328 T Y = 0; 329 330 /// The Z component 331 T Z = 0; 332 } 333 334 /// An array of the values in this vector 335 T[Dimensions] values; 336 } 337 338 /// Construct from X and Y coordinates 339 this(Y)(Y x, Y y) if (isNumeric!Y) { 340 this.X = cast(T)x; 341 this.Y = cast(T)y; 342 } 343 344 /// Construct from X and Y coordinates 345 this(Y)(Y x, Y y, Y z) if (isNumeric!Y) { 346 this.X = cast(T)x; 347 this.Y = cast(T)y; 348 this.Z = cast(T)z; 349 } 350 351 mixin(SharedVectorCtor); 352 mixin SharedVectorOp!(T, GVector, Dimensions); 353 } 354 355 /** 356 A 4D vector 357 */ 358 @trusted 359 nothrow 360 struct Vector4T(T) if (isNumeric!T) { 361 private: 362 alias GVector = typeof(this); 363 364 public: 365 /// The count of dimensions the vector consists of 366 enum Dimensions = 4; 367 368 alias Type = T; 369 370 union { 371 struct { 372 /// The X component 373 T X = 0; 374 375 /// The Y component 376 T Y = 0; 377 378 /// The Z component 379 T Z = 0; 380 381 /// The Z component 382 T W = 0; 383 } 384 385 /// An array of the values in this vector 386 T[Dimensions] values; 387 } 388 389 /// Construct from X and Y coordinates 390 this(Y)(Y x, Y y) if (isNumeric!Y) { 391 this.X = cast(T)x; 392 this.Y = cast(T)y; 393 } 394 395 /// Construct from X and Y coordinates 396 this(Y)(Y x, Y y, Y z) if (isNumeric!Y) { 397 this.X = cast(T)x; 398 this.Y = cast(T)y; 399 this.Z = cast(T)z; 400 } 401 402 /// Construct from X and Y coordinates 403 this(Y)(Y x, Y y, Y z, Y w) if (isNumeric!Y) { 404 this.X = cast(T)x; 405 this.Y = cast(T)y; 406 this.Z = cast(T)z; 407 this.W = cast(T)w; 408 } 409 410 // Implement shared vector operations & constructors 411 mixin(SharedVectorCtor); 412 mixin SharedVectorOp!(T, GVector, Dimensions); 413 } 414 415 // Copied from the GLMath implementation, checks if T is a Vector2T 416 enum IsVector2T(T) = is(T : Vector2T!U, U...); 417 418 // Copied from the GLMath implementation, checks if T is a Vector3T 419 enum IsVector3T(T) = is(T : Vector3T!U, U...); 420 421 // Copied from the GLMath implementation, checks if T is a Vector4T 422 enum IsVector4T(T) = is(T : Vector4T!U, U...); 423 424 enum IsNumericArray(T) = isArray!T && isNumeric!(ElementType!T); 425 426 //Combination of all IsVector 427 enum IsVector(T) = (IsVector2T!T || IsVector3T!T || IsVector4T!T); 428 429 alias Vector2 = Vector2T!float; 430 alias Vector2i = Vector2T!int; 431 alias Vector3 = Vector3T!float; 432 alias Vector3i = Vector3T!int; 433 alias Vector4 = Vector4T!float; 434 alias Vector4i = Vector4T!int;