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