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