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