1 /// Vector linear algebra module 2 module gfx.math.vec; 3 4 import std.traits : isFloatingPoint, isNumeric, isStaticArray; 5 6 pure @safe nothrow @nogc: 7 8 alias Vec2(T) = Vec!(T, 2); 9 alias Vec3(T) = Vec!(T, 3); 10 alias Vec4(T) = Vec!(T, 4); 11 12 alias DVec(int N) = Vec!(double, N); 13 alias FVec(int N) = Vec!(float, N); 14 alias IVec(int N) = Vec!(int, N); 15 16 alias DVec2 = Vec!(double, 2); 17 alias DVec3 = Vec!(double, 3); 18 alias DVec4 = Vec!(double, 4); 19 20 alias FVec2 = Vec!(float, 2); 21 alias FVec3 = Vec!(float, 3); 22 alias FVec4 = Vec!(float, 4); 23 24 alias IVec2 = Vec!(int, 2); 25 alias IVec3 = Vec!(int, 3); 26 alias IVec4 = Vec!(int, 4); 27 28 29 /// Build a Vec whose component type and size is deducted from arguments. 30 auto vec(Comps...)(Comps comps) 31 if (Comps.length > 0 && !(Comps.length == 1 && isStaticArray!(Comps[0]))) 32 { 33 import std.traits : CommonType, Unqual; 34 alias FlatTup = CompTup!(Comps); 35 alias CompType = Unqual!(CommonType!(typeof(FlatTup.init.expand))); 36 alias ResVec = Vec!(CompType, FlatTup.length); 37 return ResVec (comps); 38 } 39 40 /// ditto 41 auto vec(Arr)(in Arr arr) 42 if (isStaticArray!Arr) 43 { 44 import std.traits : Unqual; 45 alias CompType = Unqual!(typeof(arr[0])); 46 alias ResVec = Vec!(CompType, Arr.length); 47 return ResVec(arr); 48 } 49 50 /// 51 unittest 52 { 53 import std.algorithm : equal; 54 import std.traits : Unqual; 55 56 immutable v1 = vec (1, 2, 4.0, 0); // CommonType!(int, double) is double 57 immutable int[4] arr1 = [1, 2, 4, 0]; 58 59 static assert( is(Unqual!(typeof(v1)) == DVec4) ); 60 61 assert(equal(v1.data, arr1[])); 62 63 immutable int[3] arr2 = [0, 1, 2]; 64 immutable v2 = vec (arr2); 65 static assert( is(Unqual!(typeof(v2)) == IVec3) ); 66 assert(equal(v2.data, arr2[])); 67 } 68 69 /// Build a Vec with specified component type T and size deducted from arguments. 70 template vec (T) if (isNumeric!T) 71 { 72 auto vec (Comps...)(Comps comps) 73 if (Comps.length > 0 && !(Comps.length == 1 && isStaticArray!(Comps[0]))) 74 { 75 alias ResVec = Vec!(T, numComps!Comps); 76 return ResVec (comps); 77 } 78 auto vec (ArrT)(in ArrT arr) 79 if (isStaticArray!ArrT) 80 { 81 alias ResVec = Vec!(T, ArrT.length); 82 return ResVec (arr); 83 } 84 } 85 86 /// ditto 87 alias dvec = vec!double; 88 /// ditto 89 alias fvec = vec!float; 90 /// ditto 91 alias ivec = vec!int; 92 93 /// 94 unittest 95 { 96 import std.algorithm : equal; 97 import std.traits : Unqual; 98 99 immutable v1 = dvec (1, 2, 4, 0); // none of the args is double 100 immutable int[4] arr1 = [1, 2, 4, 0]; 101 static assert( is(Unqual!(typeof(v1)) == DVec4) ); 102 assert(equal(v1.data, arr1[])); 103 104 immutable int[3] arr2 = [0, 1, 2]; 105 immutable v2 = fvec(arr2); 106 static assert( is(Unqual!(typeof(v2)) == FVec3) ); 107 assert(equal(v2.data, arr2[])); 108 109 immutable int[4] arr3 = [0, 1, 2, 3]; 110 immutable v3 = dvec (1, 2); 111 immutable v4 = dvec (0, v3, 3); 112 static assert( is(Unqual!(typeof(v4)) == DVec4) ); 113 assert(equal(v4.data, arr3[])); 114 } 115 116 /// Build a Vec with specified size and type deducted from arguments 117 template vec (size_t N) 118 { 119 import std.traits : isDynamicArray, Unqual; 120 121 auto vec (Arr)(in Arr arr) if (isDynamicArray!Arr) 122 in { 123 assert(arr.length == N); 124 } 125 body { 126 alias CompType = Unqual!(typeof(arr[0])); 127 return Vec!(CompType, N)(arr); 128 } 129 130 auto vec (T)(in T comp) if (isNumeric!T) 131 { 132 return Vec!(Unqual!T, N)(comp); 133 } 134 } 135 136 /// ditto 137 alias vec2 = vec!2; 138 /// ditto 139 alias vec3 = vec!3; 140 /// ditto 141 alias vec4 = vec!4; 142 143 /// 144 unittest 145 { 146 import std.algorithm : equal; 147 import std.array : staticArray; 148 import std.traits : Unqual; 149 150 const double[4] arr1 = [1, 2, 4, 0]; 151 const v1 = vec4 (arr1[]); // passing slice to erase compile-time length 152 static assert( is(Unqual!(typeof(v1)) == DVec4) ); 153 assert(equal(v1.data, arr1[])); 154 155 const int comp = 2; 156 const v2 = vec4 (comp); 157 static assert( is(Unqual!(typeof(v2)) == IVec4) ); 158 assert(equal(v2.data, staticArray([2, 2, 2, 2])[])); 159 } 160 161 162 struct Vec(T, size_t N) if(N > 0 && isNumeric!T) 163 { 164 import std.meta : Repeat; 165 import std.traits : isArray, isImplicitlyConvertible; 166 import std.typecons : Tuple; 167 168 package T[N] _rep = 0; 169 170 static if (isFloatingPoint!T) 171 { 172 /// A vector with each component set as NaN (not a number) 173 enum nan = Vec!(T, N)(T.nan); 174 } 175 176 /// The number of components in the vector 177 enum length = N; 178 179 /// The type of a vector component 180 alias Component = T; 181 182 /// Build a vector from its components 183 /// Can be called with any combination of scalar or vectors, as long as 184 /// the total number of scalar fit with length 185 this (Comps...)(in Comps comps) pure nothrow @nogc @safe 186 if (Comps.length > 1) 187 { 188 import std.conv : to; 189 190 enum nc = numComps!(Comps); 191 static assert(nc == N, 192 "type sequence "~Comps.stringof~" (size "~nc.to!string~ 193 ") do not fit the size of "~Vec!(T, N).stringof~" (size "~N.to!string~")."); 194 195 const ct = compTup(comps); 196 197 static if ( 198 is(typeof([ ct.expand ])) && 199 isImplicitlyConvertible!(typeof([ ct.expand ]), typeof(_rep)) 200 ) 201 { 202 _rep = [ ct.expand ]; 203 } 204 else { 205 static foreach (i; 0 .. ct.length) { 206 _rep[i] = cast(T)ct[i]; 207 } 208 } 209 } 210 211 /// Build a vector from an array. 212 this(Arr)(in Arr arr) 213 if (isArray!Arr) 214 { 215 static if (isStaticArray!Arr) { 216 static assert(Arr.length == length); 217 } 218 else { 219 assert(arr.length == length); 220 } 221 static if (is(typeof(arr[0]) == T)) { 222 _rep[] = arr; 223 } 224 else { 225 static assert(isImplicitlyConvertible!(typeof(arr[0]), T)); 226 static foreach (i; 0..N) 227 { 228 _rep[i] = arr[i]; 229 } 230 } 231 } 232 233 /// Build a vector with all components assigned to one value 234 this(in T comp) 235 { 236 _rep = comp; 237 } 238 239 // Tuple representation 240 241 /// Alias to a type sequence holding all components 242 alias CompSeq = Repeat!(N, T); 243 244 /// All components in a tuple 245 @property Tuple!(CompSeq) tup() const 246 { 247 return Tuple!(CompSeq)(_rep); 248 } 249 250 /// Return the data of the array 251 @property inout(T)[] data() inout 252 { 253 return _rep[]; 254 } 255 256 /// All components in a static array 257 @property T[length] array() const 258 { 259 return _rep; 260 } 261 262 // access by name and swizzling 263 264 private template compNameIndex(char c) 265 { 266 static if (c == 'x' || c == 'r' || c == 's' || c == 'u') 267 { 268 enum compNameIndex = 0; 269 } 270 else static if (c == 'y' || c == 'g' || c == 't' || c == 'v') 271 { 272 enum compNameIndex = 1; 273 } 274 else static if (c == 'z' || c == 'b' || c == 'p') 275 { 276 static assert (N >= 3, "component "~c~" is only accessible with 3 or more components vectors"); 277 enum compNameIndex = 2; 278 } 279 else static if (c == 'w' || c == 'a' || c == 'q') 280 { 281 static assert (N >= 4, "component "~c~" is only accessible with 4 or more components vectors"); 282 enum compNameIndex = 3; 283 } 284 else 285 { 286 static assert (false, "component "~c~" is not recognized"); 287 } 288 } 289 290 /// Access the component by name. 291 @property T opDispatch(string name)() const 292 if (name.length == 1) 293 { 294 return _rep[compNameIndex!(name[0])]; 295 } 296 297 /// Assign the component by name. 298 @property void opDispatch(string name)(in T val) 299 if (name.length == 1) 300 { 301 _rep[compNameIndex!(name[0])] = val; 302 } 303 304 /// Access the components by swizzling. 305 @property auto opDispatch(string name)() const 306 if (name.length > 1) 307 { 308 Vec!(T, name.length) res; 309 static foreach (i; 0 .. name.length) { 310 res[i] = _rep[compNameIndex!(name[i])]; 311 } 312 return res; 313 } 314 315 /// Assign the components by swizzling. 316 @property void opDispatch(string name, U, size_t num)(in Vec!(U, num) v) 317 if (isImplicitlyConvertible!(U, T)) 318 { 319 static assert(name.length == num, name~" number of components do not match with type "~(Vec!(U, num).stringof)); 320 static foreach (i; 0 .. name.length) 321 { 322 _rep[compNameIndex!(name[i])] = v[i]; 323 } 324 } 325 326 /// Cast the vector to another type of component 327 auto opCast(V)() const if (isVec!(length, V)) 328 { 329 V res; 330 static foreach (i; 0 .. length) { 331 res[i] = cast(V.Component)_rep[i]; 332 } 333 return res; 334 } 335 336 /// Unary "+" operation. 337 Vec!(T, N) opUnary(string op : "+")() const 338 { 339 return this; 340 } 341 /// Unary "-" operation. 342 Vec!(T, N) opUnary(string op : "-")() const 343 { 344 Vec!(T, N) res = this; 345 static foreach (i; 0 .. length) { 346 res[i] = -res[i]; 347 } 348 return res; 349 } 350 351 /// Perform a term by term assignment operation on the vector. 352 ref Vec!(T, N) opOpAssign(string op, U)(in Vec!(U, N) oth) 353 if ((op == "+" || op == "-" || op == "*" || op == "/") && isNumeric!U) 354 { 355 foreach (i, ref v; _rep) 356 { 357 mixin("v " ~ op ~ "= oth[i];"); 358 } 359 return this; 360 } 361 362 /// Perform a scalar assignment operation on the vector. 363 ref Vec!(T, N) opOpAssign(string op, U)(in U val) 364 if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%" 365 && __traits(isIntegral, U))) && isNumeric!U) 366 { 367 foreach (ref v; _rep) 368 { 369 mixin("v " ~ op ~ "= val;"); 370 } 371 return this; 372 } 373 374 /// Perform a term by term operation on the vector. 375 Vec!(T, N) opBinary(string op, U)(in Vec!(U, N) oth) const 376 if ((op == "+" || op == "-" || op == "*" || op == "/") && isNumeric!U) 377 { 378 Vec!(T, N) res = this; 379 mixin("res " ~ op ~ "= oth;"); 380 return res; 381 } 382 383 /// Perform a scalar operation on the vector. 384 Vec!(T, N) opBinary(string op, U)(in U val) const 385 if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%" 386 && isIntegral!U)) && isNumeric!U) 387 { 388 Vec!(T, N) res = this; 389 mixin("res " ~ op ~ "= val;"); 390 return res; 391 } 392 393 /// Perform a scalar operation on the vector. 394 Vec!(T, N) opBinaryRight(string op, U)(in U val) const 395 if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%" 396 && __traits(isIntegral, U))) && isNumeric!U) 397 { 398 Vec!(T, N) res = void; 399 static foreach (i; 0 .. N) { 400 mixin("res[i] = val " ~ op ~ " _rep[i];"); 401 } 402 return res; 403 } 404 405 /// Foreach support. 406 @system int opApply(int delegate(size_t i, ref T) dg) 407 { 408 int res; 409 foreach (i, ref v; _rep) 410 { 411 res = dg(i, v); 412 if (res) 413 break; 414 } 415 return res; 416 } 417 418 /// Foreach support. 419 @system int opApply(int delegate(ref T) dg) 420 { 421 int res; 422 foreach (ref v; _rep) 423 { 424 res = dg(v); 425 if (res) 426 break; 427 } 428 return res; 429 } 430 431 /// Foreach support. 432 @safe int opApply(int delegate(size_t i, ref T) @safe dg) 433 { 434 int res; 435 foreach (i, ref v; _rep) 436 { 437 res = dg(i, v); 438 if (res) 439 break; 440 } 441 return res; 442 } 443 444 /// Foreach support. 445 @safe int opApply(int delegate(ref T) @safe dg) 446 { 447 int res; 448 foreach (ref v; _rep) 449 { 450 res = dg(v); 451 if (res) 452 break; 453 } 454 return res; 455 } 456 457 // TODO: const opApply and foreach_reverse 458 459 /// Index a vector component. 460 T opIndex(in size_t index) const 461 in { 462 assert(index < N); 463 } 464 body { 465 return _rep[index]; 466 } 467 468 /// Assign a vector component. 469 void opIndexAssign(in T val, in size_t index) 470 in { 471 assert(index < N); 472 } 473 body { 474 _rep[index] = val; 475 } 476 477 /// Assign a vector component. 478 void opIndexOpAssign(string op)(in T val, in size_t index) 479 if (op == "+" || op == "-" || op == "*" || op == "/") 480 in { 481 assert(index < N); 482 } 483 body { 484 mixin("_rep[index] "~op~"= val;"); 485 } 486 487 /// Slicing support 488 size_t[2] opSlice(in size_t start, in size_t end) const { 489 assert(start <= end && end <= length); 490 return [ start, end ]; 491 } 492 493 /// ditto 494 inout(T)[] opIndex(in size_t[2] slice) inout { 495 return _rep[slice[0] .. slice[1]]; 496 } 497 498 /// ditto 499 inout(T)[] opIndex() inout { 500 return _rep[]; 501 } 502 503 /// ditto 504 void opIndexAssign(U)(in U val, in size_t[2] slice) { 505 assert(correctSlice(slice)); 506 _rep[slice[0] .. slice[1]] = val; 507 } 508 509 /// ditto 510 void opIndexAssign(U)(in U[] val, in size_t[2] slice) { 511 assert(val.length == slice[1]-slice[0] && correctSlice(slice)); 512 _rep[slice[0] .. slice[1]] = val; 513 } 514 515 /// ditto 516 void opIndexAssign(U)(in U[] val) { 517 assert(val.length == length); 518 _rep[] = val; 519 } 520 521 /// ditto 522 void opIndexOpAssign(string op, U)(in U val, in size_t[2] slice) 523 if (op == "+" || op == "-" || op == "*" || op == "/") 524 { 525 foreach (i; slice[0]..slice[1]) { 526 mixin("_rep[i] "~op~"= val;"); 527 } 528 } 529 530 /// Term by term sliced assignement operation. 531 void opIndexOpAssign(string op, U)(in U val, in size_t[2] slice) 532 if (op == "+" || op == "-" || op == "*" || op == "/") 533 { 534 foreach (i; slice[0]..slice[1]) { 535 mixin("_rep[i] "~op~"= val;"); 536 } 537 } 538 539 /// End of the vector. 540 size_t opDollar() 541 { 542 return length; 543 } 544 545 string toString() const 546 { 547 string res = "[ "; 548 foreach(i, c; _rep) 549 { 550 import std.format : format; 551 immutable fmt = i == length-1 ? "%s " : "%s, "; 552 res ~= format(fmt, c); 553 } 554 return res ~ "]"; 555 } 556 557 private static bool correctSlice(size_t[2] slice) 558 { 559 return slice[0] <= slice[1] && slice[1] <= length; 560 } 561 } 562 563 /// 564 unittest 565 { 566 DVec3 v; 567 568 assert(v._rep[0] == 0); 569 assert(v._rep[1] == 0); 570 assert(v._rep[2] == 0); 571 572 v = DVec3(4, 5, 6); 573 574 assert(v._rep[0] == 4); 575 assert(v._rep[1] == 5); 576 assert(v._rep[2] == 6); 577 assert(v[1] == 5); 578 assert(v.z == 6); 579 assert(v[$ - 1] == 6); 580 581 assert(-v == DVec3(-4, -5, -6)); 582 583 v.z = 2; 584 v[1] = 1; 585 assert(v[1] == 1); 586 assert(v.z == 2); 587 588 auto c = DVec4(0.2, 1, 0.6, 0.9); 589 assert(c.r == 0.2); 590 assert(c.g == 1); 591 assert(c.b == 0.6); 592 assert(c.a == 0.9); 593 594 assert(c.rrwuzy == DVec!6(0.2, 0.2, 0.9, 0.2, 0.6, 1)); 595 c.bgra = DVec4(0.3, 0.4, 0.5, 0.6); 596 assert(c.data == [0.5, 0.4, 0.3, 0.6]); 597 } 598 599 600 /// Compute the dot product of two vectors. 601 template dot(T, size_t N) 602 { 603 T dot(in Vec!(T, N) v1, in Vec!(T, N) v2) 604 pure nothrow @nogc @safe 605 { 606 T sum = 0; 607 static foreach (i; 0 .. N) 608 { 609 sum += v1[i] * v2[i]; 610 } 611 return sum; 612 } 613 } 614 615 /// Compute the magnitude of a vector. 616 /// This is not to be confused with length which gives the number of components. 617 template magnitude(T, size_t N) 618 { 619 T magnitude(in Vec!(T, N) v) 620 pure nothrow @nogc @safe 621 { 622 import std.math : sqrt; 623 return sqrt(squaredMag(v)); 624 } 625 } 626 627 /// Compute the squared magnitude of a vector. 628 template squaredMag(T, size_t N) 629 { 630 T squaredMag(in Vec!(T, N) v) 631 pure nothrow @nogc @safe 632 { 633 T sum = 0; 634 static foreach (i; 0 .. N) 635 { 636 sum += v[i] * v[i]; 637 } 638 return sum; 639 } 640 } 641 642 /// Compute the normalization of a vector. 643 template normalize(T, size_t N) if (isFloatingPoint!T) 644 { 645 import gfx.math.approx : approxUlpAndAbs; 646 647 Vec!(T, N) normalize(in Vec!(T, N) v) 648 pure nothrow @nogc @safe 649 in { 650 assert(!approxUlpAndAbs(magnitude(v), 0), "Cannot normalize a null vector."); 651 } 652 out (res) { 653 assert(approxUlpAndAbs(magnitude(res), 1)); 654 } 655 body 656 { 657 import std.math : sqrt; 658 return v / magnitude(v); 659 } 660 } 661 662 /// Vector cross product 663 template cross(T) 664 { 665 Vec!(T, 3) cross(in Vec!(T, 3) lhs, in Vec!(T, 3) rhs) 666 pure nothrow @nogc @safe 667 { 668 return Vec!(T, 3)( 669 lhs[1]*rhs[2] - lhs[2]*rhs[1], 670 lhs[2]*rhs[0] - lhs[0]*rhs[2], 671 lhs[0]*rhs[1] - lhs[1]*rhs[0], 672 ); 673 } 674 } 675 676 static assert(DVec2.sizeof == 16); 677 static assert(DVec3.sizeof == 24); 678 static assert(DVec4.sizeof == 32); 679 680 /// 681 unittest 682 { 683 import gfx.math.approx : approxUlp; 684 685 auto v1 = DVec3(12, 4, 3); 686 auto v2 = DVec3(-5, 3, 7); 687 688 assert(approxUlp(dot(v1, v2), -27)); 689 690 auto v = DVec3(3, 4, 5); 691 assert(approxUlp(squaredMag(v), 50)); 692 693 assert(approxUlp(normalize(FVec3(4, 0, 0)), FVec3(1, 0, 0))); 694 } 695 696 697 /// Check whether VecT is a Vec 698 template isVec(VecT) 699 { 700 import std.traits : TemplateOf; 701 static if (is(typeof(__traits(isSame, TemplateOf!VecT, Vec)))) 702 { 703 enum isVec = __traits(isSame, TemplateOf!VecT, Vec); 704 } 705 else 706 { 707 enum isVec = false; 708 } 709 } 710 711 static assert( isVec!(FVec2) ); 712 static assert( !isVec!double ); 713 714 /// Check whether VecT is a Vec of size N 715 template isVec(size_t N, VecT) 716 { 717 static if (is(typeof(VecT.length))) 718 { 719 enum isVec = isVec!VecT && VecT.length == N; 720 } 721 else 722 { 723 enum isVec = false; 724 } 725 } 726 727 /// ditto 728 enum isVec2(VecT) = isVec!(2, VecT); 729 /// ditto 730 enum isVec3(VecT) = isVec!(3, VecT); 731 /// ditto 732 enum isVec4(VecT) = isVec!(4, VecT); 733 734 735 package: 736 737 import std.typecons : Tuple; 738 739 template CompTup(T...) 740 { 741 alias CompTup = typeof(compTup(T.init)); 742 } 743 744 auto compTup(T...)(T vals) pure nothrow @nogc @safe 745 { 746 import std.meta : Repeat; 747 import std.traits : Unqual; 748 import std.typecons : tuple; 749 750 static if (T.length == 0) { 751 return tuple(); 752 } 753 else static if (T.length == 1 && isNumeric!(T[0])) { 754 return tuple(vals[0]); 755 } 756 else static if (T.length == 1 && isStaticArray!(T[0])) { 757 alias U = Unqual!(typeof(T[0][0])); 758 return Tuple!(Repeat!(T[0].length, U))(vals); 759 } 760 else static if (T.length == 1 && isVec!(T[0])) { 761 return vals[0].tup; 762 } 763 else static if (T.length > 1) { 764 return tuple(compTup(vals[0]).expand, compTup(vals[1 .. $]).expand); 765 } 766 else { 767 static assert(false, "wrong arguments to compTup"); 768 } 769 } 770 771 enum numComps(T...) = CompTup!(T).length; 772 773 static assert (numComps!DVec3 == 3); 774 static assert (is(CompTup!DVec3 == Tuple!(double, double, double))); 775 static assert (is(CompTup!(DVec2, int, float) == Tuple!(double, double, int, float))); 776 777 778 static assert(isVec!FVec3);