1 // TODO: get rid of instantiators in favor of aliases to reduce template bloat 2 module nxt.geometry; 3 4 import std.traits: isFloatingPoint, isNumeric, isSigned, isDynamicArray, isAssignable, isArray, CommonType, isInstanceOf; 5 6 version = use_cconv; ///< Use cconv for faster compilation. 7 version = unittestAllInstances; 8 9 version(unittestAllInstances) 10 { 11 static immutable defaultElementTypes = ["float", "double", "real"]; 12 } 13 else 14 { 15 static immutable defaultElementTypes = ["double"]; 16 } 17 18 enum Orient { column, row } // Vector Orientation. 19 20 /** `D`-Dimensional Cartesian Point with Coordinate Type (Precision) `E`. 21 */ 22 struct Point(E, uint D) 23 if (D >= 1 /* && TODO: extend trait : isNumeric!E */) 24 { 25 alias ElementType = E; 26 27 this(T...)(T args) 28 { 29 foreach (immutable ix, arg; args) 30 { 31 _point[ix] = arg; 32 } 33 } 34 35 /** Element data. */ 36 E[D] _point; 37 enum dimension = D; 38 39 @property void toString(scope void delegate(scope const(char)[]) @safe sink) const 40 { 41 sink(`Point(`); 42 foreach (const ix, const e; _point) 43 { 44 if (ix != 0) { sink(","); } 45 version(use_cconv) 46 { 47 import nxt.cconv : toStringInSink; 48 toStringInSink(e, sink); 49 } 50 else 51 { 52 import std.conv : to; 53 sink(to!string(e)); 54 } 55 } 56 sink(`)`); 57 } 58 59 @property string toMathML()() const 60 { 61 // opening 62 string str = `<math><mrow> 63 <mo>(</mo> 64 <mtable>`; 65 66 static foreach (i; 0 .. D) 67 { 68 str ~= ` 69 <mtr> 70 <mtd> 71 <mn>` ~ _point[i].toMathML ~ `</mn> 72 </mtd> 73 </mtr>`; 74 } 75 76 // closing 77 str ~= ` 78 </mtable> 79 <mo>)</mo> 80 </mrow></math> 81 `; 82 return str; 83 } 84 85 /** Returns: Area 0 */ 86 @property E area() const { return 0; } 87 88 inout(E)[] opSlice() inout { return _point[]; } 89 90 /** Points +/- Vector => Point */ 91 auto opBinary(string op, F)(Vector!(F, D) r) const 92 if ((op == "+") || 93 (op == "-")) 94 { 95 Point!(CommonType!(E, F), D) y; 96 static foreach (i; 0 .. D) 97 { 98 y._point[i] = mixin("_point[i]" ~ op ~ "r._vector[i]"); 99 } 100 return y; 101 } 102 } 103 104 /** Instantiator for `Point`. */ 105 auto point(Ts...)(Ts args) 106 if (!is(CommonType!Ts == void)) 107 { 108 return Point!(CommonType!Ts, args.length)(args); 109 } 110 111 version(unittest) 112 { 113 alias vec2f = Vector!(float, 2, true); 114 alias vec3f = Vector!(float, 3, true); 115 116 alias vec2d = Vector!(float, 2, true); 117 118 alias nvec2f = Vector!(float, 2, true); 119 } 120 121 /** `D`-Dimensional Vector with Coordinate/Element (Component) Type `E`. 122 * 123 * See_Also: http://physics.stackexchange.com/questions/16850/is-0-0-0-an-undefined-vector 124 */ 125 struct Vector(E, uint D, 126 bool normalizedFlag = false, // `true` for unit vectors 127 Orient orient = Orient.column) 128 if (D >= 1 && 129 (!normalizedFlag || 130 isFloatingPoint!E)) // only normalize fp for now 131 { 132 // Construct from vector. 133 this(V)(V vec) 134 if (isVector!V && 135 // TOREVIEW: is(T.E : E) && 136 (V.dimension >= dimension)) 137 { 138 static if (normalizedFlag) 139 { 140 if (vec.normalized) 141 { 142 immutable vec_norm = vec.magnitude; 143 static foreach (i; 0 .. D) 144 { 145 _vector[i] = vec._vector[i] / vec_norm; 146 } 147 return; 148 } 149 } 150 static foreach (i; 0 .. D) 151 { 152 _vector[i] = vec._vector[i]; 153 } 154 } 155 156 /** Construct from Scalar `value`. */ 157 this(S)(S scalar) 158 if (isAssignable!(E, S)) 159 { 160 static if (normalizedFlag) 161 { 162 import std.math : sqrt; 163 clear(1/sqrt(cast(E)D)); // TODO: costly 164 } 165 else 166 { 167 clear(scalar); 168 } 169 } 170 171 /** Construct from combination of arguments. */ 172 this(Args...)(Args args) { construct!(0)(args); } 173 174 enum dimension = D; 175 176 @property const 177 { 178 string orientationString()() { return orient == Orient.column ? `Column` : `Row`; } 179 string joinString()() { return orient == Orient.column ? ` \\ ` : ` & `; } 180 } 181 182 @property void toString(scope void delegate(scope const(char)[]) @safe sink) const 183 { 184 sink(orientationString); 185 sink(`Vector(`); 186 foreach (const ix, const e; _vector) 187 { 188 if (ix != 0) { sink(","); } 189 version(use_cconv) 190 { 191 import nxt.cconv : toStringInSink; 192 toStringInSink(e, sink); 193 } 194 else 195 { 196 import std.conv : to; 197 sink(to!string(e)); 198 } 199 } 200 sink(`)`); 201 } 202 203 /** Returns: LaTeX Encoding of Vector. http://www.thestudentroom.co.uk/wiki/LaTex#Matrices_and_Vectors */ 204 @property string toLaTeX()() const 205 { 206 return `\begin{pmatrix} ` ~ map!(to!string)(_vector[]).join(joinString) ~ ` \end{pmatrix}` ; 207 } 208 209 @property string toMathML()() const 210 { 211 // opening 212 string str = `<math><mrow> 213 <mo>⟨</mo> 214 <mtable>`; 215 216 if (orient == Orient.row) 217 { 218 str ~= ` 219 <mtr>`; 220 } 221 222 static foreach (i; 0 .. D) 223 { 224 final switch (orient) 225 { 226 case Orient.column: 227 str ~= ` 228 <mtr> 229 <mtd> 230 <mn>` ~ _vector[i].toMathML ~ `</mn> 231 </mtd> 232 </mtr>`; 233 break; 234 case Orient.row: 235 str ~= ` 236 <mtd> 237 <mn>` ~ _vector[i].toMathML ~ `</mn> 238 </mtd>`; 239 break; 240 } 241 } 242 243 if (orient == Orient.row) 244 { 245 str ~= ` 246 </mtr>`; 247 } 248 249 // closing 250 str ~= ` 251 </mtable> 252 <mo>⟩</mo> 253 </mrow></math> 254 `; 255 return str; 256 } 257 258 auto randInPlace()(E scaling = 1) 259 { 260 import nxt.random_ex: randInPlace; 261 static if (normalizedFlag && 262 isFloatingPoint!E) // cannot use normalized() here (yet) 263 { 264 static if (D == 2) // randomize on unit circle 265 { 266 alias P = real; // precision 267 immutable angle = uniform(0, 2*cast(P)PI); 268 _vector[0] = scaling*sin(angle); 269 _vector[1] = scaling*cos(angle); 270 } 271 static if (D == 3) // randomize on unit sphere: See_Also: http://mathworld.wolfram.com/SpherePointPicking.html 272 { 273 alias P = real; // precision 274 immutable u = uniform(0, cast(P)1); 275 immutable v = uniform(0, cast(P)1); 276 immutable theta = 2*PI*u; 277 immutable phi = acos(2*v-1); 278 _vector[0] = scaling*cos(theta)*sin(phi); 279 _vector[1] = scaling*sin(theta)*sin(phi); 280 _vector[2] = scaling*cos(phi); 281 } 282 else 283 { 284 _vector.randInPlace(); 285 normalize(); // TODO: Turn this into D data restriction instead? 286 } 287 } 288 else 289 { 290 _vector.randInPlace(); 291 } 292 } 293 294 /// Returns: `true` if all values are not `nan` nor `infinite`, otherwise `false`. 295 @property bool ok()() const 296 { 297 static if (isFloatingPoint!E) 298 { 299 foreach (const ref v; _vector) 300 { 301 if (isNaN(v) || 302 isInfinity(v)) 303 { 304 return false; 305 } 306 } 307 } 308 return true; 309 } 310 // NOTE: Disabled this because I want same behaviour as MATLAB: bool opCast(T : bool)() const { return ok; } 311 bool opCast(T : bool)() const 312 { 313 return all!"a"(_vector[]); 314 } 315 316 /// Returns: Pointer to the coordinates. 317 // @property auto value_ptr() { return _vector.ptr; } 318 319 /// Sets all values to `value`. 320 void clear(V)(V value) 321 if (isAssignable!(E, V)) 322 { 323 static foreach (i; 0 .. D) 324 { 325 _vector[i] = value; 326 } 327 } 328 329 /** Returns: Whole Internal Array of E. */ 330 auto opSlice() 331 { 332 return _vector[]; 333 } 334 /** Returns: Slice of Internal Array of E. */ 335 auto opSlice(uint off, uint len) 336 { 337 return _vector[off .. len]; 338 } 339 /** Returns: Reference to Internal Vector Element. */ 340 ref inout(E) opIndex(uint i) inout 341 { 342 return _vector[i]; 343 } 344 345 bool opEquals(S)(in S scalar) const 346 if (isAssignable!(E, S)) // TODO: is(typeof(E.init != S.init)) 347 { 348 static foreach (i; 0 .. D) 349 { 350 if (_vector[i] != scalar) 351 { 352 return false; 353 } 354 } 355 return true; 356 } 357 358 bool opEquals(F)(in F vec) const 359 if (isVector!F && 360 dimension == F.dimension) // TOREVIEW: Use isEquable instead? 361 { 362 return _vector == vec._vector; 363 } 364 365 bool opEquals(F)(const F[] array) const 366 if (isAssignable!(E, F) && 367 !isArray!F && 368 !isVector!F) // TOREVIEW: Use isNotEquable instead? 369 { 370 if (array.length != dimension) 371 { 372 return false; 373 } 374 static foreach (i; 0 .. D) 375 { 376 if (_vector[i] != array[i]) 377 { 378 return false; 379 } 380 } 381 return true; 382 } 383 384 static void isCompatibleVectorImpl(uint d)(Vector!(E, d) vec) if (d <= dimension) {} 385 static void isCompatibleMatrixImpl(uint r, uint c)(Matrix!(E, r, c) m) {} 386 387 enum isCompatibleVector(T) = is(typeof(isCompatibleVectorImpl(T.init))); 388 enum isCompatibleMatrix(T) = is(typeof(isCompatibleMatrixImpl(T.init))); 389 390 private void construct(uint i)() 391 { 392 static assert(i == D, "Not enough arguments passed to constructor"); 393 } 394 private void construct(uint i, T, Tail...)(T head, Tail tail) 395 { 396 static if (i >= D) 397 { 398 static assert(0, "Too many arguments passed to constructor"); 399 } 400 else static if (is(T : E)) 401 { 402 _vector[i] = head; 403 construct!(i + 1)(tail); 404 } 405 else static if (isDynamicArray!T) 406 { 407 static assert((Tail.length == 0) && (i == 0), "Dynamic array can not be passed together with other arguments"); 408 _vector[] = head[]; 409 } 410 else static if (__traits(isStaticArray, T)) 411 { 412 _vector[i .. i + T.length] = head[]; 413 construct!(i + T.length)(tail); 414 } 415 else static if (isCompatibleVector!T) 416 { 417 _vector[i .. i + T.dimension] = head._vector[]; 418 construct!(i + T.dimension)(tail); 419 } 420 else 421 { 422 static assert(0, "Vector constructor argument must be of type " ~ E.stringof ~ " or Vector, not " ~ T.stringof); 423 } 424 } 425 426 // private void dispatchImpl(int i, string s, int size)(ref E[size] result) const { 427 // static if (s.length > 0) { 428 // result[i] = _vector[coordToIndex!(s[0])]; 429 // dispatchImpl!(i + 1, s[1..$])(result); 430 // } 431 // } 432 433 // /// Implements dynamic swizzling. 434 // /// Returns: a Vector 435 // @property Vector!(E, s.length) opDispatch(string s)() const { 436 // E[s.length] ret; 437 // dispatchImpl!(0, s)(ret); 438 // Vector!(E, s.length) ret_vec; 439 // ret_vec._vector = ret; 440 // return ret_vec; 441 // } 442 443 ref inout(Vector) opUnary(string op : "+")() inout { return this; } 444 445 Vector opUnary(string op : "-")() const 446 if (isSigned!(E)) 447 { 448 Vector y; 449 static foreach (i; 0 .. D) 450 { 451 y._vector[i] = - _vector[i]; 452 } 453 return y; 454 } 455 456 auto opBinary(string op, T)(in T rhs) const 457 if (op == "+" || op == "-" && 458 isInstanceOf!(Vector, T) && 459 dimension && T.dimension) 460 { 461 Vector!(CommonType!(E, F), D) y; 462 static foreach (i; 0 .. D) 463 { 464 y._vector[i] = mixin("_vector[i]" ~ op ~ "rhs._vector[i]"); 465 } 466 return y; 467 } 468 469 Vector opBinary(string op : "*", F)(in F r) const 470 { 471 Vector!(CommonType!(E, F), D, normalizedFlag) y; 472 static foreach (i; 0 .. dimension) 473 { 474 y._vector[i] = _vector[i] * r; 475 } 476 return y; 477 } 478 479 Vector!(CommonType!(E, F), D) opBinary(string op : "*", F)(in Vector!(F, D) r) const 480 { 481 // MATLAB-style Product Behaviour 482 static if (orient == Orient.column && 483 r.orient == Orient.row) 484 { 485 return outer(this, r); 486 } 487 else static if (orient == Orient.row && 488 r.orient == Orient.column) 489 { 490 return dot(this, r); 491 } 492 else 493 { 494 static assert(0, "Incompatible vector dimensions."); 495 } 496 } 497 498 /** Multiply with right-hand-side `rhs`. */ 499 Vector!(E, T.rows) opBinary(string op : "*", T)(in T rhs) const 500 if (isCompatibleMatrix!T && 501 (T.cols == dimension)) 502 { 503 Vector!(E, T.rows) ret; 504 ret.clear(0); 505 static foreach (c; 0 .. T.cols) 506 { 507 static foreach (r; 0 .. T.rows) 508 { 509 ret._vector[r] += _vector[c] * rhs.at(r,c); 510 } 511 } 512 return ret; 513 } 514 515 /** Multiply with left-hand-side `lhs`. */ 516 version(none) // TODO: activate 517 auto opBinaryRight(string op, T)(in T lhs) const 518 if (!isInstanceOf!(Vector, T) && 519 !isMatrix!T && 520 !isQuaternion!T) 521 { 522 return this.opBinary!(op)(lhs); 523 } 524 525 /** TODO: Suitable Restrictions on F. */ 526 void opOpAssign(string op, F)(in F r) 527 /* if ((op == "+") || (op == "-") || (op == "*") || (op == "%") || (op == "/") || (op == "^^")) */ 528 { 529 static foreach (i; 0 .. dimension) 530 { 531 mixin("_vector[i]" ~ op ~ "= r;"); 532 } 533 } 534 unittest 535 { 536 auto v2 = vec2f(1, 3); 537 v2 *= 5.0f; assert(v2[] == [5, 15].s); 538 v2 ^^= 2; assert(v2[] == [25, 225].s); 539 v2 /= 5; assert(v2[] == [5, 45].s); 540 } 541 542 void opOpAssign(string op)(in Vector r) 543 if ((op == "+") || 544 (op == "-")) 545 { 546 static foreach (i; 0 .. dimension) 547 { 548 mixin("_vector[i]" ~ op ~ "= r._vector[i];"); 549 } 550 } 551 552 /// Returns: Non-Rooted `N` - Norm of `x`. 553 auto nrnNorm(uint N)() const 554 if (isNumeric!E && N >= 1) 555 { 556 static if (isFloatingPoint!E) 557 { 558 real y = 0; // TOREVIEW: Use maximum precision for now 559 } 560 else 561 { 562 E y = 0; // TOREVIEW: Use other precision for now 563 } 564 static foreach (i; 0 .. D) 565 { 566 y += _vector[i] ^^ N; 567 } 568 return y; 569 } 570 571 /// Returns: Squared Magnitude of x. 572 @property real magnitudeSquared()() const 573 if (isNumeric!E) 574 { 575 static if (normalizedFlag) // cannot use normalized() here (yet) 576 { 577 return 1; 578 } 579 else 580 { 581 return nrnNorm!2; 582 } 583 } 584 585 /// Returns: Magnitude of x. 586 @property real magnitude()() const 587 if (isNumeric!E) 588 { 589 static if (normalizedFlag) // cannot use normalized() here (yet) 590 { 591 return 1; 592 } 593 else 594 { 595 import std.math : sqrt; 596 return sqrt(magnitudeSquared); 597 } 598 } 599 alias norm = magnitude; 600 601 static if (isFloatingPoint!E) 602 { 603 /// Normalize `this`. 604 void normalize()() 605 { 606 if (this != 0) // zero vector have zero magnitude 607 { 608 immutable m = this.magnitude; 609 static foreach (i; 0 .. D) 610 { 611 _vector[i] /= m; 612 } 613 } 614 } 615 616 /// Returns: normalizedFlag Copy of this Vector. 617 @property Vector normalized()() const 618 { 619 Vector y = this; 620 y.normalize(); 621 return y; 622 } 623 624 unittest 625 { 626 static if (D == 2 && !normalizedFlag) 627 { 628 assert(Vector(3, 4).magnitude == 5); 629 } 630 } 631 } 632 633 /// Returns: Vector Index at Character Coordinate `coord`. 634 private @property ref inout(E) get_(char coord)() inout 635 { 636 return _vector[coordToIndex!coord]; 637 } 638 639 /// Coordinate Character c to Index 640 template coordToIndex(char c) 641 { 642 static if ((c == 'x')) 643 { 644 enum coordToIndex = 0; 645 } 646 else static if ((c == 'y')) 647 { 648 enum coordToIndex = 1; 649 } 650 else static if ((c == 'z')) 651 { 652 static assert(D >= 3, "The " ~ c ~ " property is only available on vectors with a third dimension."); 653 enum coordToIndex = 2; 654 } 655 else static if ((c == 'w')) 656 { 657 static assert(D >= 4, "The " ~ c ~ " property is only available on vectors with a fourth dimension."); 658 enum coordToIndex = 3; 659 } 660 else 661 { 662 static assert(0, "Accepted coordinates are x, s, r, u, y, g, t, v, z, p, b, w, q and a not " ~ c ~ "."); 663 } 664 } 665 666 /// Updates the vector with the values from other. 667 void update(in Vector!(E, D) other) { _vector = other._vector; } 668 669 static if (D == 2) 670 { 671 void set(E x, E y) 672 { 673 _vector[0] = x; 674 _vector[1] = y; 675 } 676 } 677 else static if (D == 3) 678 { 679 void set(E x, E y, E z) 680 { 681 _vector[0] = x; 682 _vector[1] = y; 683 _vector[2] = z; 684 } 685 } 686 else static if (D == 4) 687 { 688 void set(E x, E y, E z, E w) 689 { 690 _vector[0] = x; 691 _vector[1] = y; 692 _vector[2] = z; 693 _vector[3] = w; 694 } 695 } 696 697 static if (D >= 1) { alias x = get_!'x'; } 698 static if (D >= 2) { alias y = get_!'y'; } 699 static if (D >= 3) { alias z = get_!'z'; } 700 static if (D >= 4) { alias w = get_!'w'; } 701 702 static if (isNumeric!E) 703 { 704 /* Need these conversions when E is for instance ubyte. 705 See this commit: https://github.com/Dav1dde/gl3n/commit/2504003df4f8a091e58a3d041831dc2522377f95 */ 706 static immutable E0 = E(0); 707 static immutable E1 = E(1); 708 static if (dimension == 2) 709 { 710 static immutable Vector e1 = Vector(E1, E0); /// canonical basis for Euclidian space 711 static immutable Vector e2 = Vector(E0, E1); /// ditto 712 } 713 else static if (dimension == 3) 714 { 715 static immutable Vector e1 = Vector(E1, E0, E0); /// canonical basis for Euclidian space 716 static immutable Vector e2 = Vector(E0, E1, E0); /// ditto 717 static immutable Vector e3 = Vector(E0, E0, E1); /// ditto 718 } 719 else static if (dimension == 4) 720 { 721 static immutable Vector e1 = Vector(E1, E0, E0, E0); /// canonical basis for Euclidian space 722 static immutable Vector e2 = Vector(E0, E1, E0, E0); /// ditto 723 static immutable Vector e3 = Vector(E0, E0, E1, E0); /// ditto 724 static immutable Vector e4 = Vector(E0, E0, E0, E1); /// ditto 725 } 726 } 727 728 version(none) 729 @safe pure nothrow @nogc unittest 730 { 731 static if (isNumeric!E) 732 { 733 assert(vec2.e1[] == [1, 0].s); 734 assert(vec2.e2[] == [0, 1].s); 735 736 assert(vec3.e1[] == [1, 0, 0].s); 737 assert(vec3.e2[] == [0, 1, 0].s); 738 assert(vec3.e3[] == [0, 0, 1].s); 739 740 assert(vec4.e1[] == [1, 0, 0, 0].s); 741 assert(vec4.e2[] == [0, 1, 0, 0].s); 742 assert(vec4.e3[] == [0, 0, 1, 0].s); 743 assert(vec4.e4[] == [0, 0, 0, 1].s); 744 } 745 } 746 747 /** Element data. */ 748 E[D] _vector; 749 750 unittest 751 { 752 // static if (isSigned!(E)) { assert(-Vector!(E,D)(+2), 753 // +Vector!(E,D)(-2)); } 754 } 755 756 } 757 758 auto rowVector(Ts...)(Ts args) 759 if (!is(CommonType!Ts == void)) 760 { 761 return Vector!(CommonType!Ts, args.length)(args); 762 } 763 alias vector = rowVector; // TODO: Should rowVector or columnVector be default? 764 765 auto columnVector(Ts...)(Ts args) 766 if (!is(CommonType!Ts == void)) 767 { 768 return Vector!(CommonType!Ts, args.length, false, Orient.column)(args); 769 } 770 771 /// 772 @safe pure nothrow @nogc unittest 773 { 774 assert(point(1, 2) + vector(1, 2) == point(2, 4)); 775 assert(point(1, 2) - vector(1, 2) == point(0, 0)); 776 } 777 778 version(unittest) 779 { 780 import std.conv : to; 781 import nxt.array_help : s; 782 }