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