1 module gfm.math.vector; 2 3 import std.traits, 4 std.math, 5 std.conv, 6 std.array, 7 std..string; 8 9 import gfm.math.funcs; 10 11 static if( __VERSION__ < 2066 ) private enum nogc = 1; 12 13 /** 14 * Generic 1D small vector. 15 * Params: 16 * N = number of elements 17 * T = type of elements 18 */ 19 align(1) struct Vector(T, int N) 20 { 21 align(1): 22 nothrow: 23 public 24 { 25 static assert(N >= 1); 26 27 // fields definition 28 union 29 { 30 T[N] v; 31 struct 32 { 33 static if (N >= 1) 34 { 35 T x; 36 alias x r; 37 } 38 static if (N >= 2) 39 { 40 T y; 41 alias y g; 42 } 43 static if (N >= 3) 44 { 45 T z; 46 alias z b; 47 } 48 static if (N >= 4) 49 { 50 T w; 51 alias w a; 52 } 53 } 54 } 55 56 static if (N == 2) 57 { 58 /// Creates a vector of 2 elements. 59 @nogc this(X : T, Y : T)(X x_, Y y_) pure nothrow 60 { 61 x = x_; 62 y = y_; 63 } 64 } 65 else static if (N == 3) 66 { 67 /// Creates a vector of 3 elements. 68 @nogc this(X : T, Y : T, Z : T)(X x_, Y y_, Z z_) pure nothrow 69 { 70 x = x_; 71 y = y_; 72 z = z_; 73 } 74 75 /// Creates a vector of 3 elements. 76 @nogc this(X : T, Y : T)(Vector!(X, 2) xy_, Y z_) pure nothrow 77 { 78 x = xy_.x; 79 y = xy_.y; 80 z = z_; 81 } 82 83 /// Creates a vector of 3 elements. 84 @nogc this(X : T, Y : T)(X x_, Vector!(Y, 2) yz_) pure nothrow 85 { 86 x = x_; 87 y = yz_.x; 88 z = yz_.y; 89 } 90 } 91 else static if (N == 4) 92 { 93 /// Creates a vector of 4 elements. 94 @nogc this(X : T, Y : T, Z : T, W : T)(X x_, Y y_, Z z_, W w_) pure nothrow 95 { 96 x = x_; 97 y = y_; 98 z = z_; 99 w = w_; 100 } 101 102 /// Creates a vector of 4 elements. 103 @nogc this(X : T, Y : T)(Vector!(X, 2) xy_, Vector!(Y, 2)zw_) pure nothrow 104 { 105 x = xy_.x; 106 y = xy_.y; 107 z = zw_.x; 108 w = zw_.y; 109 } 110 111 /// Creates a vector of 4 elements. 112 @nogc this(X : T, Y : T, Z : T)(Vector!(X, 2) xy_, Y z_, Z w_) pure nothrow 113 { 114 x = xy_.x; 115 y = xy_.y; 116 z = z_; 117 w = w_; 118 } 119 120 /// Creates a vector of 4 elements. 121 @nogc this(X : T, Y : T)(Vector!(X, 3) xyz_, Y w_) pure nothrow 122 { 123 x = xyz_.x; 124 y = xyz_.y; 125 z = xyz_.z; 126 w = w_; 127 } 128 129 /// Creates a vector of 4 elements. 130 @nogc this(X : T, Y : T)(X x_, Vector!(X, 3) yzw_) pure nothrow 131 { 132 x = x_; 133 y = yzw_.x; 134 z = yzw_.y; 135 w = yzw_.z; 136 } 137 } 138 139 /// Construct a Vector from a value. 140 @nogc this(U)(U x) pure nothrow 141 { 142 opAssign!U(x); 143 } 144 145 /// Assign a Vector from a compatible type. 146 @nogc ref Vector opAssign(U)(U x) pure nothrow if (is(U: T)) 147 { 148 v[] = x; // copy to each component 149 return this; 150 } 151 152 /// Assign a Vector with a static array type. 153 @nogc ref Vector opAssign(U)(U arr) pure nothrow if ((isStaticArray!(U) && is(typeof(arr[0]) : T) && (arr.length == N))) 154 { 155 v[] = arr[]; 156 return this; 157 } 158 159 /// Assign with a dynamic array. 160 /// Size is checked in debug-mode. 161 @nogc ref Vector opAssign(U)(U arr) pure nothrow if (isDynamicArray!(U) && is(typeof(arr[0]) : T)) 162 { 163 assert(arr.length == N); 164 mixin(generateLoopCode!("v[@] = arr[@];", N)()); 165 return this; 166 } 167 168 /// Assign from a samey Vector. 169 @nogc ref Vector opAssign(U)(U u) pure nothrow if (is(U : Vector)) 170 { 171 v[] = u.v[]; 172 return this; 173 } 174 175 /// Assign from other vectors types (same size, compatible type). 176 @nogc ref Vector opAssign(U)(U x) pure nothrow if (is(typeof(U._isVector)) 177 && is(U._T : T) 178 && (!is(U: Vector)) 179 && (U._N == _N)) 180 { 181 mixin(generateLoopCode!("v[@] = x.v[@];", N)()); 182 return this; 183 } 184 185 /// Returns: a pointer to content. 186 @nogc inout(T)* ptr() pure inout nothrow @property 187 { 188 return v.ptr; 189 } 190 191 /// Converts to a pretty string. 192 string toString() const nothrow 193 { 194 try 195 return format("%s", v); 196 catch (Exception e) 197 assert(false); // should not happen since format is right 198 } 199 200 @nogc bool opEquals(U)(U other) pure const nothrow 201 if (is(U : Vector)) 202 { 203 for (int i = 0; i < N; ++i) 204 { 205 if (v[i] != other.v[i]) 206 { 207 return false; 208 } 209 } 210 return true; 211 } 212 213 @nogc bool opEquals(U)(U other) pure const nothrow 214 if (isConvertible!U) 215 { 216 Vector conv = other; 217 return opEquals(conv); 218 } 219 220 @nogc Vector opUnary(string op)() pure const nothrow 221 if (op == "+" || op == "-" || op == "~" || op == "!") 222 { 223 Vector res = void; 224 mixin(generateLoopCode!("res.v[@] = " ~ op ~ " v[@];", N)()); 225 return res; 226 } 227 228 @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow 229 if (is(U : Vector)) 230 { 231 mixin(generateLoopCode!("v[@] " ~ op ~ "= operand.v[@];", N)()); 232 return this; 233 } 234 235 @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow if (isConvertible!U) 236 { 237 Vector conv = operand; 238 return opOpAssign!op(conv); 239 } 240 241 @nogc Vector opBinary(string op, U)(U operand) pure const nothrow 242 if (is(U: Vector) || (isConvertible!U)) 243 { 244 Vector result = void; 245 static if (is(U: T)) 246 mixin(generateLoopCode!("result.v[@] = cast(T)(v[@] " ~ op ~ " operand);", N)()); 247 else 248 { 249 Vector other = operand; 250 mixin(generateLoopCode!("result.v[@] = cast(T)(v[@] " ~ op ~ " other.v[@]);", N)()); 251 } 252 return result; 253 } 254 255 @nogc Vector opBinaryRight(string op, U)(U operand) pure const nothrow if (isConvertible!U) 256 { 257 Vector result = void; 258 static if (is(U: T)) 259 mixin(generateLoopCode!("result.v[@] = cast(T)(operand " ~ op ~ " v[@]);", N)()); 260 else 261 { 262 Vector other = operand; 263 mixin(generateLoopCode!("result.v[@] = cast(T)(other.v[@] " ~ op ~ " v[@]);", N)()); 264 } 265 return result; 266 } 267 268 @nogc ref T opIndex(size_t i) pure nothrow 269 { 270 return v[i]; 271 } 272 273 @nogc ref const(T) opIndex(size_t i) pure const nothrow 274 { 275 return v[i]; 276 } 277 278 @nogc T opIndexAssign(U : T)(U x, size_t i) pure nothrow 279 { 280 return v[i] = x; 281 } 282 283 284 /// Implements swizzling. 285 /// 286 /// Example: 287 /// --- 288 /// vec4i vi = [4, 1, 83, 10]; 289 /// assert(vi.zxxyw == [83, 4, 4, 1, 10]); 290 /// --- 291 @nogc @property auto opDispatch(string op, U = void)() pure const nothrow if (isValidSwizzle!(op)) 292 { 293 alias Vector!(T, op.length) returnType; 294 returnType res = void; 295 enum indexTuple = swizzleTuple!op; 296 foreach(i, index; indexTuple) 297 res.v[i] = v[index]; 298 return res; 299 } 300 301 /// Support swizzling assignment like in shader languages. 302 /// 303 /// Example: 304 /// --- 305 /// vec3f v = [0, 1, 2]; 306 /// v.yz = v.zx; 307 /// assert(v == [0, 2, 0]); 308 /// --- 309 @nogc @property void opDispatch(string op, U)(U x) pure 310 if ((op.length >= 2) 311 && (isValidSwizzleUnique!op) // v.xyy will be rejected 312 && is(typeof(Vector!(T, op.length)(x)))) // can be converted to a small vector of the right size 313 { 314 Vector!(T, op.length) conv = x; 315 enum indexTuple = swizzleTuple!op; 316 foreach(i, index; indexTuple) 317 v[index] = conv[i]; 318 } 319 320 /// Casting to small vectors of the same size. 321 /// Example: 322 /// --- 323 /// vec4f vf; 324 /// vec4d vd = cast!(vec4d)vf; 325 /// --- 326 @nogc U opCast(U)() pure const nothrow if (is(typeof(U._isVector)) && (U._N == _N)) 327 { 328 U res = void; 329 mixin(generateLoopCode!("res.v[@] = cast(U._T)v[@];", N)()); 330 return res; 331 } 332 333 /// Implement slices operator overloading. 334 /// Allows to go back to slice world. 335 /// Returns: length. 336 @nogc int opDollar() pure const nothrow 337 { 338 return N; 339 } 340 341 /// Returns: a slice which covers the whole Vector. 342 @nogc T[] opSlice() pure nothrow 343 { 344 return v[]; 345 } 346 347 // vec[a..b] 348 @nogc T[] opSlice(int a, int b) pure nothrow 349 { 350 return v[a..b]; 351 } 352 353 /// Returns: squared length. 354 @nogc T squaredLength() pure const nothrow 355 { 356 T sumSquares = 0; 357 mixin(generateLoopCode!("sumSquares += v[@] * v[@];", N)()); 358 return sumSquares; 359 } 360 361 // Returns: squared Euclidean distance. 362 @nogc T squaredDistanceTo(Vector v) pure const nothrow 363 { 364 return (v - this).squaredLength(); 365 } 366 367 static if (isFloatingPoint!T) 368 { 369 /// Returns: Euclidean length 370 @nogc T length() pure const nothrow 371 { 372 return sqrt(squaredLength()); 373 } 374 375 /// Returns: Inverse of Euclidean length. 376 @nogc T inverseLength() pure const nothrow 377 { 378 return 1 / sqrt(squaredLength()); 379 } 380 381 /// Faster but less accurate inverse of Euclidean length. 382 /// Returns: Inverse of Euclidean length. 383 @nogc T fastInverseLength() pure const nothrow 384 { 385 return inverseSqrt(squaredLength()); 386 } 387 388 /// Returns: Euclidean distance between this and other. 389 @nogc T distanceTo(Vector other) pure const nothrow 390 { 391 return (other - this).length(); 392 } 393 394 /// In-place normalization. 395 @nogc void normalize() pure nothrow 396 { 397 auto invLength = inverseLength(); 398 mixin(generateLoopCode!("v[@] *= invLength;", N)()); 399 } 400 401 /// Returns: Normalized vector. 402 @nogc Vector normalized() pure const nothrow 403 { 404 Vector res = this; 405 res.normalize(); 406 return res; 407 } 408 409 /// Faster but less accurate in-place normalization. 410 @nogc void fastNormalize() pure nothrow 411 { 412 auto invLength = fastInverseLength(); 413 mixin(generateLoopCode!("v[@] *= invLength;", N)()); 414 } 415 416 /// Faster but less accurate vector normalization. 417 /// Returns: Normalized vector. 418 @nogc Vector fastNormalized() pure const nothrow 419 { 420 Vector res = this; 421 res.fastNormalize(); 422 return res; 423 } 424 425 static if (N == 3) 426 { 427 /// Gets an orthogonal vector from a 3-dimensional vector. 428 /// Doesn’t normalise the output. 429 /// Authors: Sam Hocevar 430 /// See_also: Source at $(WEB lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts). 431 @nogc Vector getOrthogonalVector() pure const nothrow 432 { 433 return abs(x) > abs(z) ? Vector(-y, x, 0.0) : Vector(0.0, -z, y); 434 } 435 } 436 } 437 } 438 439 private 440 { 441 enum _isVector = true; // do we really need this? I don't know. 442 443 enum _N = N; 444 alias T _T; 445 446 // define types that can be converted to this, but are not the same type 447 template isConvertible(T) 448 { 449 enum bool isConvertible = (!is(T : Vector)) 450 && is(typeof( 451 { 452 T x; 453 Vector v = x; 454 }())); 455 } 456 457 // define types that can't be converted to this 458 template isForeign(T) 459 { 460 enum bool isForeign = (!isConvertible!T) && (!is(T: Vector)); 461 } 462 463 template isValidSwizzle(string op, int lastSwizzleClass = -1) 464 { 465 static if (op.length == 0) 466 enum bool isValidSwizzle = true; 467 else 468 { 469 enum len = op.length; 470 enum int swizzleClass = swizzleClassify!(op[0]); 471 enum bool swizzleClassValid = (lastSwizzleClass == -1 || (swizzleClass == lastSwizzleClass)); 472 enum bool isValidSwizzle = (swizzleIndex!(op[0]) != -1) 473 && swizzleClassValid 474 && isValidSwizzle!(op[1..len], swizzleClass); 475 } 476 } 477 478 template searchElement(char c, string s) 479 { 480 static if (s.length == 0) 481 { 482 enum bool result = false; 483 } 484 else 485 { 486 enum string tail = s[1..s.length]; 487 enum bool result = (s[0] == c) || searchElement!(c, tail).result; 488 } 489 } 490 491 template hasNoDuplicates(string s) 492 { 493 static if (s.length == 1) 494 { 495 enum bool result = true; 496 } 497 else 498 { 499 enum tail = s[1..s.length]; 500 enum bool result = !(searchElement!(s[0], tail).result) && hasNoDuplicates!(tail).result; 501 } 502 } 503 504 // true if the swizzle has at the maximum one time each letter 505 template isValidSwizzleUnique(string op) 506 { 507 static if (isValidSwizzle!op) 508 enum isValidSwizzleUnique = hasNoDuplicates!op.result; 509 else 510 enum bool isValidSwizzleUnique = false; 511 } 512 513 template swizzleIndex(char c) 514 { 515 static if((c == 'x' || c == 'r') && N >= 1) 516 enum swizzleIndex = 0; 517 else static if((c == 'y' || c == 'g') && N >= 2) 518 enum swizzleIndex = 1; 519 else static if((c == 'z' || c == 'b') && N >= 3) 520 enum swizzleIndex = 2; 521 else static if ((c == 'w' || c == 'a') && N >= 4) 522 enum swizzleIndex = 3; 523 else 524 enum swizzleIndex = -1; 525 } 526 527 template swizzleClassify(char c) 528 { 529 static if(c == 'x' || c == 'y' || c == 'z' || c == 'w') 530 enum swizzleClassify = 0; 531 else static if(c == 'r' || c == 'g' || c == 'b' || c == 'a') 532 enum swizzleClassify = 1; 533 else 534 enum swizzleClassify = -1; 535 } 536 537 template swizzleTuple(string op) 538 { 539 enum opLength = op.length; 540 static if (op.length == 0) 541 enum swizzleTuple = []; 542 else 543 enum swizzleTuple = [ swizzleIndex!(op[0]) ] ~ swizzleTuple!(op[1..op.length]); 544 } 545 } 546 } 547 548 private string definePostfixAliases(string type) 549 { 550 return "alias " ~ type ~ "!byte " ~ type ~ "b;\n" 551 ~ "alias " ~ type ~ "!ubyte " ~ type ~ "ub;\n" 552 ~ "alias " ~ type ~ "!short " ~ type ~ "s;\n" 553 ~ "alias " ~ type ~ "!ushort " ~ type ~ "us;\n" 554 ~ "alias " ~ type ~ "!int " ~ type ~ "i;\n" 555 ~ "alias " ~ type ~ "!uint " ~ type ~ "ui;\n" 556 ~ "alias " ~ type ~ "!long " ~ type ~ "l;\n" 557 ~ "alias " ~ type ~ "!ulong " ~ type ~ "ul;\n" 558 ~ "alias " ~ type ~ "!float " ~ type ~ "f;\n" 559 ~ "alias " ~ type ~ "!double " ~ type ~ "d;\n"; 560 } 561 562 template vec2(T) { alias Vector!(T, 2) vec2; } 563 template vec3(T) { alias Vector!(T, 3) vec3; } 564 template vec4(T) { alias Vector!(T, 4) vec4; } 565 566 mixin(definePostfixAliases("vec2")); 567 mixin(definePostfixAliases("vec3")); 568 mixin(definePostfixAliases("vec4")); 569 570 571 private 572 { 573 static string generateLoopCode(string formatString, int N)() pure nothrow 574 { 575 string result; 576 for (int i = 0; i < N; ++i) 577 { 578 string index = ctIntToString(i); 579 // replace all @ by indices 580 result ~= formatString.replace("@", index); 581 } 582 return result; 583 } 584 585 // Speed-up CTFE conversions 586 static string ctIntToString(int n) pure nothrow 587 { 588 static immutable string[16] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; 589 if (n < 10) 590 return table[n]; 591 else 592 return to!string(n); 593 } 594 } 595 596 597 /// Element-wise minimum. 598 @nogc Vector!(T, N) min(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 599 { 600 import std.algorithm: min; 601 Vector!(T, N) res = void; 602 mixin(generateLoopCode!("res.v[@] = min(a.v[@], b.v[@]);", N)()); 603 return res; 604 } 605 606 /// Element-wise maximum. 607 @nogc Vector!(T, N) max(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 608 { 609 import std.algorithm: max; 610 Vector!(T, N) res = void; 611 mixin(generateLoopCode!("res.v[@] = max(a.v[@], b.v[@]);", N)()); 612 return res; 613 } 614 615 /// Returns: Dot product. 616 @nogc T dot(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 617 { 618 T sum = 0; 619 mixin(generateLoopCode!("sum += a.v[@] * b.v[@];", N)()); 620 return sum; 621 } 622 623 /// Returns: 3D cross product. 624 /// Thanks to vuaru for corrections. 625 @nogc Vector!(T, 3) cross(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow 626 { 627 return Vector!(T, 3)(a.y * b.z - a.z * b.y, 628 a.z * b.x - a.x * b.z, 629 a.x * b.y - a.y * b.x); 630 } 631 632 /// 3D reflect, like the GLSL function. 633 /// Returns: a reflected by normal b. 634 @nogc Vector!(T, 3) reflect(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow 635 { 636 return a - (2 * dot(b, a)) * b; 637 } 638 639 640 /// Returns: angle between vectors. 641 /// See_also: "The Right Way to Calculate Stuff" at $(WEB www.plunk.org/~hatch/rightway.php) 642 @nogc T angleBetween(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 643 { 644 auto aN = a.normalized(); 645 auto bN = b.normalized(); 646 auto dp = dot(aN, bN); 647 648 if (dp < 0) 649 return PI - 2 * asin((-bN-aN).length / 2); 650 else 651 return 2 * asin((bN-aN).length / 2); 652 } 653 654 655 unittest 656 { 657 static assert(vec2i.isValidSwizzle!"xyx"); 658 static assert(!vec2i.isValidSwizzle!"xyz"); 659 static assert(vec4i.isValidSwizzle!"brra"); 660 static assert(!vec4i.isValidSwizzle!"rgyz"); 661 static assert(vec2i.isValidSwizzleUnique!"xy"); 662 static assert(vec2i.isValidSwizzleUnique!"yx"); 663 static assert(!vec2i.isValidSwizzleUnique!"xx"); 664 665 assert(vec2l(0, 1) == vec2i(0, 1)); 666 667 int[2] arr = [0, 1]; 668 int[] arr2 = new int[2]; 669 arr2[] = arr[]; 670 vec2i a = vec2i([0, 1]); 671 vec2i a2 = vec2i(0, 1); 672 immutable vec2i b = vec2i(0); 673 assert(b[0] == 0 && b[1] == 0); 674 vec2i c = arr; 675 vec2l d = arr2; 676 assert(a == a2); 677 assert(a == c); 678 assert(vec2l(a) == vec2l(a)); 679 assert(vec2l(a) == d); 680 681 vec4i x = [4, 5, 6, 7]; 682 assert(x == x); 683 --x[0]; 684 assert(x[0] == 3); 685 ++x[0]; 686 assert(x[0] == 4); 687 x[1] &= 1; 688 x[2] = 77 + x[2]; 689 x[3] += 3; 690 assert(x == [4, 1, 83, 10]); 691 assert(x.xxywz == [4, 4, 1, 10, 83]); 692 assert(x.xxxxxxx == [4, 4, 4, 4, 4, 4, 4]); 693 assert(x.abgr == [10, 83, 1, 4]); 694 assert(a != b); 695 696 vec2l e = a; 697 vec2l f = a + b; 698 assert(f == vec2l(a)); 699 700 vec3ui g = vec3i(78,9,4); 701 g ^= vec3i(78,9,4); 702 assert(g == vec3ui(0)); 703 //g[0..2] = 1u; 704 //assert(g == [2, 1, 0]); 705 706 assert(vec2i(4, 5) + 1 == vec2i(5,6)); 707 assert(vec2i(4, 5) - 1 == vec2i(3,4)); 708 assert(1 + vec2i(4, 5) == vec2i(5,6)); 709 assert(vec3f(1,1,1) * 0 == 0); 710 assert(1.0 * vec3d(4,5,6) == vec3f(4,5.0f,6.0)); 711 712 auto dx = vec2i(1,2); 713 auto dy = vec2i(4,5); 714 auto dp = dot(dx, dy); 715 assert(dp == 14 ); 716 717 vec3i h = cast(vec3i)(vec3d(0.5, 1.1, -2.2)); 718 assert(h == [0, 1, -2]); 719 assert(h[] == [0, 1, -2]); 720 assert(h[1..3] == [1, -2]); 721 assert(h.zyx == [-2, 1, 0]); 722 723 h.yx = vec2i(5, 2); // swizzle assignment 724 725 assert(h.xy == [2, 5]); 726 assert(-h[1] == -5); 727 assert(++h[0] == 3); 728 729 //assert(h == [-2, 1, 0]); 730 assert(!__traits(compiles, h.xx = h.yy)); 731 vec4ub j; 732 733 assert(lerp(vec2f(-10, -1), vec2f(10, 1), 0.5) == vec2f(0, 0)); 734 } 735