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