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