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