1 module dath.vector; 2 3 version(unittest) import fluent.asserts; 4 5 import std.traits; 6 7 public alias Vec2f = Vec!(float, 2); 8 public alias Vec3f = Vec!(float, 3); 9 public alias Vec4f = Vec!(float, 4); 10 11 public alias Vec2 = Vec2f; 12 public alias Vec3 = Vec3f; 13 public alias Vec4 = Vec4f; 14 15 public alias Vec2d = Vec!(double, 2); 16 public alias Vec3d = Vec!(double, 3); 17 public alias Vec4d = Vec!(double, 4); 18 19 public alias Vec2i = Vec!(int, 2); 20 public alias Vec3i = Vec!(int, 3); 21 public alias Vec4i = Vec!(int, 4); 22 23 public alias Vec2u = Vec!(uint, 2); 24 public alias Vec3u = Vec!(uint, 3); 25 public alias Vec4u = Vec!(uint, 4); 26 27 /++ 28 + Numeric Vector type with an optional amount of components. 29 +/ 30 public struct Vec(T, ulong n) if (n >= 1 && isNumeric!T) 31 { 32 union { 33 /++ 34 + Internal data. 35 +/ 36 T[n] v; 37 38 struct 39 { 40 // Predefined component names. 41 42 static if (n >= 1) 43 { 44 T x; 45 } 46 47 static if (n >= 2) 48 { 49 T y; 50 } 51 52 static if (n >= 3) 53 { 54 T z; 55 } 56 57 static if (n >= 4) 58 { 59 T w; 60 } 61 } 62 } 63 64 private alias _t = T; 65 private enum _n = n; 66 67 public this(U)(U[] elements) @nogc pure nothrow 68 { 69 assert(isNumeric!U, "All components must be numeric."); 70 71 assert(elements.length > 0, "No components provided."); 72 73 assert(elements.length == 1 || elements.length == n, 74 "Number of components must be either 1 or the number of components the vector holds."); 75 76 v = elements; 77 } 78 79 public this(U...)(U args) @nogc pure nothrow 80 { 81 static foreach (arg; args) 82 { 83 static assert(isNumeric!(typeof(arg)), "All components must be numeric"); 84 } 85 86 static assert(args.length > 0, "No components provided."); 87 88 static assert(args.length == 1 || args.length == n, 89 "Number of components must be either 1 or the number of components the vector holds."); 90 91 static if (args.length == 1) 92 { 93 v[] = args[0]; 94 } 95 else 96 { 97 v = [args]; 98 } 99 } 100 101 /++ 102 + Internal data as a pointer, use for sending data to shaders. 103 +/ 104 public auto ptr() @nogc pure nothrow const 105 { 106 return v.ptr; 107 } 108 109 /++ 110 + Vector magnitude. 111 +/ 112 public real magnitude() @nogc pure nothrow const 113 { 114 import std.math : sqrt; 115 116 real sum = 0; 117 for (int i = 0; i < n; i++) 118 { 119 sum += v[i] * v[i]; 120 } 121 122 return sqrt(sum); 123 } 124 125 /++ 126 + Normalizes the vectors. Changes the current struct! 127 +/ 128 public void normalize() @nogc pure nothrow 129 { 130 this = normalized(); 131 } 132 133 /++ 134 + Returns the normalized vector. Doesn't change the current struct! 135 +/ 136 public Vec!(T, n) normalized() @nogc pure nothrow const 137 { 138 real mag = magnitude(); 139 140 if (mag == 0) 141 { 142 return Vec!(T, n)(0); 143 } 144 145 return this / mag; 146 } 147 148 /++ 149 + Returns the negated vector. 150 +/ 151 public Vec!(T, n) opUnary(string s)() @nogc pure nothrow const if (s == "-") 152 { 153 Vec!(T, n) res; 154 res.v = -v[]; 155 return res; 156 } 157 158 /++ 159 + Returns the mul of this * scalar. 160 +/ 161 public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "*") 162 { 163 Vec!(T, n) res; 164 res.v = v[] * scalar; 165 return res; 166 } 167 168 /++ 169 + Returns the mul of this * scalar. 170 +/ 171 public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "*") 172 { 173 v[] *= scalar; 174 } 175 176 /++ 177 + Returns the sum of this + scalar. 178 +/ 179 public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "+") 180 { 181 Vec!(T, n) res; 182 res.v = v[] + scalar; 183 return res; 184 } 185 186 /++ 187 + Returns the sum of this + scalar. 188 +/ 189 public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "+") 190 { 191 v[] += scalar; 192 } 193 194 /++ 195 + Returns the sub of this - scalar. 196 +/ 197 public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "-") 198 { 199 Vec!(T, n) res; 200 res.v = v[] - scalar; 201 return res; 202 } 203 204 /++ 205 + Returns the sub of this - scalar. 206 +/ 207 public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "-") 208 { 209 v[] -= scalar; 210 } 211 212 /++ 213 + Returns the div of this / scalar. 214 +/ 215 public Vec!(T, n) opBinary(string s) (in float scalar) @nogc pure nothrow const if (s == "/") 216 { 217 Vec!(T, n) res; 218 res.v = v[] / cast(T) scalar; 219 return res; 220 } 221 222 /++ 223 + Returns the div of this / scalar. 224 +/ 225 public void opOpAssign(string s) (in float scalar) @nogc pure nothrow if (s == "/") 226 { 227 v[] /= scalar; 228 } 229 230 /++ 231 + Returns the sum of 2 vectors. 232 +/ 233 public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "+") 234 { 235 Vec!(T, n) res; 236 res.v = v[] + other.v[]; 237 return res; 238 } 239 240 /++ 241 + Returns the sum of 2 vectors. 242 +/ 243 public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "+") 244 { 245 v[] += other.v[]; 246 } 247 248 /++ 249 + Returns the sub of 2 vectors. 250 +/ 251 public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "-") 252 { 253 Vec!(T, n) res; 254 res.v = v[] - other.v[]; 255 return res; 256 } 257 258 /++ 259 + Returns the sub of 2 vectors. 260 +/ 261 public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "-") 262 { 263 v[] -= other.v[]; 264 } 265 266 /++ 267 + Returns the mul of 2 vectors. 268 +/ 269 public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "*") 270 { 271 Vec!(T, n) res; 272 res.v = v[] * other.v[]; 273 return res; 274 } 275 276 /++ 277 + Returns the mul of 2 vectors. 278 +/ 279 public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "*") 280 { 281 v[] *= other.v[]; 282 } 283 284 /++ 285 + Returns the div of 2 vectors. 286 +/ 287 public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "/") 288 { 289 Vec!(T, n) res; 290 res.v = v[] / other.v[]; 291 return res; 292 } 293 294 /++ 295 + Returns the div of 2 vectors. 296 +/ 297 public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "/") 298 { 299 v[] /= other.v[]; 300 } 301 302 /++ 303 + Get the N-th component. 304 +/ 305 public T opIndex(int n) @nogc pure const nothrow 306 { 307 return v[n]; 308 } 309 310 /++ 311 + Get the whole internal array. 312 +/ 313 public T[n] opIndex() @nogc pure const nothrow 314 { 315 return v[]; 316 } 317 318 /++ 319 + Set the nth component 320 +/ 321 public T opIndexAssign(T value, int n) @nogc pure nothrow 322 { 323 return v[n] = value; 324 } 325 326 /++ 327 + Cast to a vector of a different type. 328 +/ 329 public U opCast(U)() pure nothrow const if (is(U : Vec!R, R...) && (U._n == n)) 330 { 331 U res; 332 foreach (i, el; v) 333 { 334 res.v[i] = cast(U._t) v[i]; 335 } 336 return res; 337 } 338 339 /++ 340 + Swizzling. 341 +/ 342 public Vec!(T, swizzle.length) opDispatch(const string swizzle)() @nogc const pure nothrow 343 { 344 T[swizzle.length] arr; 345 346 static foreach (i, c; swizzle) 347 { 348 static assert(coordToIdx!(c) <= n-1, 349 "Trying to swizzle the " ~ c ~ " component, but this vector is too small."); 350 351 arr[i] = v[coordToIdx!(c)]; 352 } 353 354 Vec!(T, swizzle.length) res; 355 res.v = arr; 356 return res; 357 } 358 359 private template coordToIdx(char c) 360 { 361 static if (c == 'x') enum coordToIdx = 0; 362 else static if (c == 'y') enum coordToIdx = 1; 363 else static if (c == 'z') enum coordToIdx = 2; 364 else static if (c == 'w') enum coordToIdx = 3; 365 else static assert(false, "Unknown vector component " ~ c); 366 } 367 } 368 369 /++ 370 + Returns the dot product of 2 vectors. 371 +/ 372 public real dot(T, ulong n)(Vec!(T, n) a, Vec!(T, n) b) @nogc pure nothrow 373 { 374 real res = 0f; 375 static foreach (i; 0..n) 376 { 377 res += a.v[i] * b.v[i]; 378 } 379 return res; 380 } 381 382 /++ 383 + Returns the cross product of 2 Vec3. The result is always a float Vec3. 384 +/ 385 public Vec!(float, 3) cross(T)(Vec!(T, 3) a, Vec!(T, 3) b) @nogc pure nothrow 386 { 387 return Vec!(float, 3)(a.y * b.z - a.z * b.y, 388 a.z * b.x - a.x * b.z, 389 a.x * b.y - a.y * b.x); 390 } 391 392 @("Creating vectors") 393 unittest 394 { 395 const t1 = Vec2(2f, 3f); 396 397 t1.x.should.equal(2f); 398 t1.y.should.equal(3f); 399 400 const t2 = Vec2(2f); 401 402 t2.x.should.equal(2f); 403 t2.y.should.equal(2f); 404 405 const t3 = Vec4d(1f, 2f, 3f, 4f); 406 407 t3.x.should.equal(1f); 408 t3.y.should.equal(2f); 409 t3.z.should.equal(3f); 410 t3.w.should.equal(4f); 411 412 const t4 = Vec2i(5, 6); 413 414 t4.x.should.equal(5); 415 t4.y.should.equal(6); 416 417 const float[] arr = [1f, 2f]; 418 const t5 = Vec2(arr); 419 420 t5.x.should.equal(1); 421 t5.y.should.equal(2); 422 } 423 424 @("Vector magnitude") 425 unittest 426 { 427 const t1 = Vec3(5, 6, 8); 428 429 t1.magnitude().should.be.approximately(11.180, 0.01); 430 431 const t2 = Vec2d(65, 76); 432 433 t2.magnitude().should.be.approximately(100, 0.01); 434 } 435 436 @("Vector normalization") 437 unittest 438 { 439 const t1 = Vec3(75, 64, 23); 440 const t2 = t1.normalized(); 441 442 t2.x.should.be.approximately(0.74, 0.01); 443 t2.y.should.be.approximately(0.63, 0.01); 444 t2.z.should.be.approximately(0.22, 0.01); 445 } 446 447 @("Vector scalar operations") 448 unittest 449 { 450 auto t1 = Vec3(63, 75, 38); 451 452 // negation 453 (-t1).should.equal(Vec3(-63, -75, -38)); 454 455 // multiplication with a scalar 456 (t1 * 2).should.equal(Vec3(126, 150, 76)); 457 t1 *= 3; 458 t1.should.equal(Vec3(189, 225, 114)); 459 460 // sum with a scalar 461 (t1 + 2).should.equal(Vec3(191, 227, 116)); 462 t1 += 3; 463 t1.should.equal(Vec3(192, 228, 117)); 464 465 // sub with a scalar 466 (t1 - 2).should.equal(Vec3(190, 226, 115)); 467 t1 -= 3; 468 t1.should.equal(Vec3(189, 225, 114)); 469 470 // division with a scalar 471 (t1 / 2).should.equal(Vec3(94.5, 112.5, 57)); 472 t1 /= 3; 473 t1.should.equal(Vec3(63, 75, 38)); 474 } 475 476 @("Vector operations with other vectors") 477 unittest 478 { 479 auto t1 = Vec3(63, 75, 38); 480 auto t2 = Vec3(37, 98, 100); 481 482 // sum of 2 vectors 483 (t1 + t2).should.equal(Vec3(100, 173, 138)); 484 t1 += t2; 485 t1.should.equal(Vec3(100, 173, 138)); 486 487 // sub of 2 vectors 488 (t1 - t2).should.equal(Vec3(63, 75, 38)); 489 t1 -= t2; 490 t1.should.equal(Vec3(63, 75, 38)); 491 492 // multiplication of 2 vectors 493 (t1 * t2).should.equal(Vec3(2331, 7350, 3800)); 494 t1 *= t2; 495 t1.should.equal(Vec3(2331, 7350, 3800)); 496 497 // division of 2 vectors 498 (t1 / t2).should.equal(Vec3(63, 75, 38)); 499 t1 /= t2; 500 t1.should.equal(Vec3(63, 75, 38)); 501 } 502 503 @("Vector components and casting") 504 unittest 505 { 506 auto t1 = Vec3(63, 75, 38); 507 508 t1[0].should.equal(63); 509 510 t1[1] = 100; 511 t1[1].should.equal(100); 512 t1.y.should.equal(100); 513 514 auto t2 = cast(Vec3d) t1; 515 t2.should.equal(Vec3d(63, 100, 38)); 516 517 auto t3 = cast(Vec3i) t2; 518 t3.should.equal(Vec3i(63, 100, 38)); 519 } 520 521 @("Vector dot and cross product") 522 unittest 523 { 524 const t1 = Vec3(63, 75, 38); 525 const t2 = Vec3(37, 98, 100); 526 527 t1.dot(t2).should.equal(13_481); 528 529 t1.cross(t2).should.equal(Vec3(3776, -4894, 3399)); 530 } 531 532 @("Vector swizzling") 533 unittest 534 { 535 const t1 = Vec3(4, 5, 6); 536 const t2 = t1.xz; 537 538 t2.should.equal(Vec2(4, 6)); 539 540 const t3 = Vec4(1f, 2f, 3f, 4f); 541 const t4 = t3.xxyyww; 542 543 t4.should.equal(Vec!(float, 6)(1f, 1f, 2f, 2f, 4f, 4f)); 544 }