1 /** Geometry types and algorithms. 2 3 Special thanks to: 4 $(UL 5 $(LI Tomasz Stachowiak (h3r3tic): allowed me to use parts of $(LINK2 https://bitbucket.org/h3r3tic/boxen/src/default/src/xf/omg, omg).) 6 $(LI Jakob Øvrum (jA_cOp): improved the code a lot!) 7 $(LI Florian Boesch (___doc__): helps me to understand opengl/complex maths better, see: $(LINK http://codeflow.org/).) 8 $(LI #D on freenode: answered general questions about D.) 9 ) 10 11 Note: All methods marked with pure are weakly pure since, they all access an 12 instance member. All static methods are strongly pure. 13 14 TODO: Support radian and degree types (from units-d package) 15 16 TODO: Use `sink` as param in `toMathML` and `toLaTeX` 17 18 TODO: Replace toMathML() with fmt argument %M to toString functions 19 TODO: Replace toLaTeX() with fmt argument %L to toString functions 20 21 TODO: Optimize using core.simd or std.simd 22 TODO: Merge with analyticgeometry 23 TODO: Merge with https://github.com/CyberShadow/ae/blob/master/utils/geometry.d 24 TODO: Integrate with http://code.dlang.org/packages/blazed2 25 TODO: logln, log.warn, log.error, log.info, log.debug 26 TODO: Make use of staticReduce etc when they become available in Phobos. 27 TODO: Go through all usages of real and use CommonType!(real, E) to make it work when E is a bignum. 28 TODO: ead and perhaps make use of http://stackoverflow.com/questions/3098242/fast-vector-struct-that-allows-i-and-xyz-operations-in-d?rq=1 29 TODO: Tag member functions in t_geom.d as pure as is done https://github.com/D-Programming-Language/phobos/blob/master/std/bigint.d 30 TODO: Remove need to use [] in x[] == y[] 31 32 See: https://www.google.se/search?q=point+plus+vector 33 See: http://mosra.cz/blog/article.php?a=22-introducing-magnum-a-multiplatform-2d-3d-graphics-engine 34 */ 35 module old_geometry; 36 37 /+ TODO: use import core.simd; +/ 38 import std.math: sqrt, PI, sin, cos, acos; 39 import std.traits: isFloatingPoint, isNumeric, isSigned, isDynamicArray, isAssignable, isArray, CommonType; 40 import std.string: format, rightJustify; 41 import std.array: join; 42 import std.algorithm.iteration : map, reduce; 43 import std.algorithm.searching : all, any; 44 import std.algorithm.comparison : min, max; 45 import std.random: uniform; 46 47 import nxt.mathml; 48 49 enum isVector(E) = is(typeof(isVectorImpl(E.init))); 50 enum isPoint(E) = is(typeof(isPointImpl(E.init))); 51 enum isMatrix(E) = is(typeof(isMatrixImpl(E.init))); 52 enum isQuaternion(E) = is(typeof(isQuaternionImpl(E.init))); 53 enum isPlane(E) = is(typeof(isPlaneImpl(E.init))); 54 55 private void isVectorImpl (E, uint D) (Vector !(E, D) vec) {} 56 private void isPointImpl (E, uint D) (Point !(E, D) vec) {} 57 private void isMatrixImpl (E, uint R, uint C)(Matrix !(E, R, C) mat) {} 58 private void isQuaternionImpl(E) (Quaternion!(E) qu) {} 59 private void isPlaneImpl (E) (PlaneT !(E) p) {} 60 61 enum isFixVector(E) = isFix(typeof(isFixVectorImpl(E.init))); 62 enum isFixPoint(E) = isFix(typeof(isFixPointImpl (E.init))); 63 enum isFixMatrix(E) = isFix(typeof(isFixMatrixImpl(E.init))); 64 65 private void isFixVectorImpl (E, uint D) (Vector!(E, D) vec) {} 66 private void isFixPointImpl (E, uint D) (Point !(E, D) vec) {} 67 private void isFixMatrixImpl (E, uint R, uint C)(Matrix!(E, R, C) mat) {} 68 69 // See_Also: http://stackoverflow.com/questions/18552454/using-ctfe-to-generate-set-of-struct-aliases/18553026?noredirect=1#18553026 70 version (none) 71 string makeInstanceAliases(in string templateName, 72 string aliasName = "", 73 in uint minDimension = 2, 74 in uint maxDimension = 4, 75 in string[] elementTypes = defaultElementTypes) 76 in 77 { 78 assert(templateName.length); 79 assert(minDimension <= maxDimension); 80 } 81 do 82 { 83 import std.string : toLower; 84 import std.conv : to; 85 string code; 86 if (!aliasName.length) 87 { 88 aliasName = templateName.toLower; 89 } 90 foreach (immutable n; minDimension .. maxDimension + 1) 91 { 92 foreach (const et; elementTypes) // for each elementtype 93 { 94 immutable prefix = ("alias " ~ templateName ~ "!("~et~", " ~ 95 to!string(n) ~ ") " ~ aliasName ~ "" ~ 96 to!string(n)); 97 if (et == "float") 98 { 99 code ~= (prefix ~ ";\n"); // GLSL-style prefix-less single precision 100 } 101 code ~= (prefix ~ et[0] ~ ";\n"); 102 } 103 } 104 return code; 105 } 106 107 version (none) 108 mixin(makeInstanceAliases("Point", "point", 2,3, 109 ["int", "float", "double", "real"])); 110 111 /* Copied from https://github.com/CyberShadow/ae/blob/master/utils/geometry.d */ 112 auto sqrtx(T)(T x) 113 { 114 static if (is(T : int)) 115 { 116 return std.math.sqrt(cast(float)x); 117 } 118 else 119 { 120 return std.math.sqrt(x); 121 } 122 } 123 124 import std.meta : AliasSeq; 125 126 version (unittest) 127 { 128 static foreach (T; AliasSeq!(ubyte, int, float, double, real)) 129 { 130 static foreach (uint n; 2 .. 4 + 1) 131 { 132 } 133 } 134 135 alias vec2b = Vector!(byte, 2, false); 136 137 alias vec2f = Vector!(float, 2, true); 138 alias vec3f = Vector!(float, 3, true); 139 140 alias vec2d = Vector!(float, 2, true); 141 142 alias nvec2f = Vector!(float, 2, true); 143 } 144 145 // mixin(makeInstanceAliases("Vector", "vec", 2,4, 146 // ["ubyte", "int", "float", "double", "real"])); 147 148 /// 149 pure nothrow @safe @nogc unittest { 150 assert(vec2f(2, 3)[] == [2, 3].s); 151 assert(vec2f(2, 3)[0] == 2); 152 assert(vec2f(2) == 2); 153 assert(vec2f(true) == true); 154 assert(vec2b(true) == true); 155 assert(all!"a"(vec2b(true)[])); 156 assert(any!"a"(vec2b(false, true)[])); 157 assert(any!"a"(vec2b(true, false)[])); 158 assert(!any!"a"(vec2b(false, false)[])); 159 assert((vec2f(1, 3)*2.5f)[] == [2.5f, 7.5f].s); 160 nvec2f v = vec2f(3, 4); 161 assert(v[] == nvec2f(0.6, 0.8)[]); 162 } 163 164 /// 165 @safe unittest { 166 import std.conv : to; 167 auto x = vec2f(2, 3); 168 assert(to!string(vec2f(2, 3)) == `ColumnVector(2,3)`); 169 assert(to!string(transpose(vec2f(11, 22))) == `RowVector(11,22)`); 170 assert(vec2f(11, 22).toLaTeX == `\begin{pmatrix} 11 \\ 22 \end{pmatrix}`); 171 assert(vec2f(11, 22).T.toLaTeX == `\begin{pmatrix} 11 & 22 \end{pmatrix}`); 172 } 173 174 auto transpose(E, uint D, 175 bool normalizedFlag, 176 Orient orient)(in Vector!(E, D, 177 normalizedFlag, 178 orient) a) 179 { 180 static if (orient == Orient.row) 181 { 182 return Vector!(E, D, normalizedFlag, Orient.column)(a._vector); 183 } 184 else 185 { 186 return Vector!(E, D, normalizedFlag, Orient.row)(a._vector); 187 } 188 } 189 alias T = transpose; // C++ Armadillo naming convention. 190 191 auto elementwiseLessThanOrEqual(Ta, Tb, 192 uint D, 193 bool normalizedFlag, 194 Orient orient)(in Vector!(Ta, D, normalizedFlag, orient) a, 195 in Vector!(Tb, D, normalizedFlag, orient) b) 196 { 197 Vector!(bool, D) c = void; 198 static foreach (i; 0 .. D) 199 { 200 c[i] = a[i] <= b[i]; 201 } 202 return c; 203 } 204 205 pure nothrow @safe @nogc unittest { 206 assert(elementwiseLessThanOrEqual(vec2f(1, 1), 207 vec2f(2, 2)) == vec2b(true, true)); 208 } 209 210 /// Returns: Scalar/Dot-Product of Two Vectors `a` and `b`. 211 T dotProduct(T, U)(in T a, in U b) 212 if (is(T == Vector!(_), _) && 213 is(U == Vector!(_), _) && 214 (T.dimension == 215 U.dimension)) 216 { 217 T c = void; 218 static foreach (i; 0 .. T.dimension) 219 { 220 c[i] = a[i] * b[i]; 221 } 222 return c; 223 } 224 alias dot = dotProduct; 225 226 /// Returns: Outer-Product of Two Vectors `a` and `b`. 227 auto outerProduct(Ta, Tb, uint Da, uint Db)(in Vector!(Ta, Da) a, 228 in Vector!(Tb, Db) b) 229 if (Da >= 1 && 230 Db >= 1) 231 { 232 Matrix!(CommonType!(Ta, Tb), Da, Db) y = void; 233 static foreach (r; 0 .. Da) 234 { 235 static foreach (c; 0 .. Db) 236 { 237 y.at(r,c) = a[r] * b[c]; 238 } 239 } 240 return y; 241 } 242 alias outer = outerProduct; 243 244 /// Returns: Vector/Cross-Product of two 3-Dimensional Vectors. 245 auto crossProduct(T, U)(in T a, 246 in U b) 247 if (is(T == Vector!(_), _) && T.dimension == 3 && 248 is(U == Vector!(_), _) && U.dimension == 3) 249 { 250 return T(a.y * b.z - b.y * a.z, 251 a.z * b.x - b.z * a.x, 252 a.x * b.y - b.x * a.y); 253 } 254 255 /// Returns: (Euclidean) Distance between `a` and `b`. 256 real distance(T, U)(in T a, 257 in U b) 258 if ((is(T == Vector!(_), _) && // either both vectors 259 is(U == Vector!(_), _) && 260 T.dimension == U.dimension) || 261 (isPoint!T && // or both points 262 isPoint!U)) /+ TODO: support distance between vector and point +/ 263 { 264 return (a - b).magnitude; 265 } 266 267 pure nothrow @safe @nogc unittest { 268 auto v1 = vec3f(1, 2, -3); 269 auto v2 = vec3f(1, 3, 2); 270 assert(crossProduct(v1, v2)[] == [13, -5, 1].s); 271 assert(distance(vec2f(0, 0), vec2f(0, 10)) == 10); 272 assert(distance(vec2f(0, 0), vec2d(0, 10)) == 10); 273 assert(dot(v1, v2) == dot(v2, v1)); // commutative 274 } 275 276 enum Layout { columnMajor, rowMajor }; // Matrix Storage Major Dimension. 277 278 /// Base template for all matrix-types. 279 /// Params: 280 /// E = all values get stored as this type 281 /// rows_ = rows of the matrix 282 /// cols_ = columns of the matrix 283 /// layout = matrix layout 284 struct Matrix(E, uint rows_, uint cols_, 285 Layout layout = Layout.rowMajor) 286 if (rows_ >= 1 && 287 cols_ >= 1) 288 { 289 alias mT = E; /// Internal type of the _matrix 290 static const uint rows = rows_; /// Number of rows 291 static const uint cols = cols_; /// Number of columns 292 293 /** Matrix $(RED row-major) in memory. */ 294 static if (layout == Layout.rowMajor) 295 { 296 E[cols][rows] _matrix; // In C it would be mt[rows][cols], D does it like this: (mt[cols])[rows] 297 298 ref inout(E) opCall()(uint row, uint col) inout 299 { 300 return _matrix[row][col]; 301 } 302 303 ref inout(E) at()(uint row, uint col) inout 304 { 305 return _matrix[row][col]; 306 } 307 } 308 else 309 { 310 E[rows][cols] _matrix; // In C it would be mt[cols][rows], D does it like this: (mt[rows])[cols] 311 ref inout(E) opCall()(uint row, uint col) inout 312 { 313 return _matrix[col][row]; 314 } 315 316 ref inout(E) at()(uint row, uint col) inout 317 { 318 return _matrix[col][row]; 319 } 320 } 321 alias _matrix this; 322 323 /// Returns: The pointer to the stored values as OpenGL requires it. 324 /// Note this will return a pointer to a $(RED row-major) _matrix, 325 /// $(RED this means you've to set the transpose argument to GL_TRUE when passing it to OpenGL). 326 /// Examples: 327 /// --- 328 /// // 3rd argument = GL_TRUE 329 /// glUniformMatrix4fv(programs.main.model, 1, GL_TRUE, mat4.translation(-0.5f, -0.5f, 1.0f).value_ptr); 330 /// --- 331 // @property auto value_ptr() { return _matrix[0].ptr; } 332 333 /// Returns: The current _matrix formatted as flat string. 334 335 @property void toString(Sink)(ref scope Sink sink) const 336 { 337 import std.conv : to; 338 sink(`Matrix(`); 339 sink(to!string(_matrix)); 340 sink(`)`); 341 } 342 343 @property string toLaTeX()() const 344 { 345 string s; 346 static foreach (r; 0 .. rows) 347 { 348 static foreach (c; 0 .. cols) 349 { 350 s ~= to!string(at(r, c)) ; 351 if (c != cols - 1) { s ~= ` & `; } // if not last column 352 } 353 if (r != rows - 1) { s ~= ` \\ `; } // if not last row 354 } 355 return `\begin{pmatrix} ` ~ s ~ ` \end{pmatrix}`; 356 } 357 358 @property string toMathML()() const 359 { 360 // opening 361 string str = `<math><mrow> 362 <mo>❲</mo> 363 <mtable>`; 364 365 static foreach (r; 0 .. rows) 366 { 367 str ~= ` 368 <mtr>`; 369 static foreach (c; 0 .. cols) 370 { 371 str ~= ` 372 <mtd> 373 <mn>` ~ at(r, c).toMathML ~ `</mn> 374 </mtd>`; 375 } 376 str ~= ` 377 </mtr>`; 378 } 379 380 // closing 381 str ~= ` 382 </mtable> 383 <mo>❳</mo> 384 </mrow></math> 385 `; 386 return str; 387 } 388 389 /// Returns: The current _matrix as pretty formatted string. 390 @property string toPrettyString()() 391 { 392 string fmtr = "%s"; 393 394 size_t rjust = max(format(fmtr, reduce!(max)(_matrix[])).length, 395 format(fmtr, reduce!(min)(_matrix[])).length) - 1; 396 397 string[] outer_parts; 398 foreach (E[] row; _matrix) 399 { 400 string[] inner_parts; 401 foreach (E col; row) 402 { 403 inner_parts ~= rightJustify(format(fmtr, col), rjust); 404 } 405 outer_parts ~= " [" ~ join(inner_parts, ", ") ~ "]"; 406 } 407 408 return "[" ~ join(outer_parts, "\n")[1..$] ~ "]"; 409 } 410 411 static void isCompatibleMatrixImpl(uint r, uint c)(Matrix!(E, r, c) m) {} 412 413 enum isCompatibleMatrix(T) = is(typeof(isCompatibleMatrixImpl(T.init))); 414 415 static void isCompatibleVectorImpl(uint d)(Vector!(E, d) vec) {} 416 417 enum isCompatibleVector(T) = is(typeof(isCompatibleVectorImpl(T.init))); 418 419 private void construct(uint i, T, Tail...)(T head, Tail tail) 420 { 421 static if (i >= rows*cols) 422 { 423 static assert(0, "Too many arguments passed to constructor"); 424 } 425 else static if (is(T : E)) 426 { 427 _matrix[i / cols][i % cols] = head; 428 construct!(i + 1)(tail); 429 } 430 else static if (is(T == Vector!(E, cols))) 431 { 432 static if (i % cols == 0) 433 { 434 _matrix[i / cols] = head._vector; 435 construct!(i + T.dimension)(tail); 436 } 437 else 438 { 439 static assert(0, "Can't convert Vector into the matrix. Maybe it doesn't align to the columns correctly or dimension doesn't fit"); 440 } 441 } 442 else 443 { 444 static assert(0, "Matrix constructor argument must be of type " ~ E.stringof ~ " or Vector, not " ~ T.stringof); 445 } 446 } 447 448 private void construct(uint i)() // terminate 449 { 450 static assert(i == rows*cols, "Not enough arguments passed to constructor"); 451 } 452 453 /// Constructs the matrix: 454 /// If a single value is passed, the matrix will be cleared with this value (each column in each row will contain this value). 455 /// If a matrix with more rows and columns is passed, the matrix will be the upper left nxm matrix. 456 /// If a matrix with less rows and columns is passed, the passed matrix will be stored in the upper left of an identity matrix. 457 /// It's also allowed to pass vectors and scalars at a time, but the vectors dimension must match the number of columns and align correctly. 458 /// Examples: 459 /// --- 460 /// mat2 m2 = mat2(0.0f); // mat2 m2 = mat2(0.0f, 0.0f, 0.0f, 0.0f); 461 /// mat3 m3 = mat3(m2); // mat3 m3 = mat3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); 462 /// mat3 m3_2 = mat3(vec3(1.0f, 2.0f, 3.0f), 4.0f, 5.0f, 6.0f, vec3(7.0f, 8.0f, 9.0f)); 463 /// mat4 m4 = mat4.identity; // just an identity matrix 464 /// mat3 m3_3 = mat3(m4); // mat3 m3_3 = mat3.identity 465 /// --- 466 this(Args...)(Args args) 467 { 468 construct!(0)(args); 469 } 470 471 this(T)(T mat) 472 if (isMatrix!T && 473 (T.cols >= cols) && 474 (T.rows >= rows)) 475 { 476 _matrix[] = mat._matrix[]; 477 } 478 479 this(T)(T mat) 480 if (isMatrix!T && 481 (T.cols < cols) && 482 (T.rows < rows)) 483 { 484 makeIdentity(); 485 static foreach (r; 0 .. T.rows) 486 { 487 static foreach (c; 0 .. T.cols) 488 { 489 at(r, c) = mat.at(r, c); 490 } 491 } 492 } 493 494 this()(E value) { clear(value); } 495 496 /// Sets all values of the matrix to value (each column in each row will contain this value). 497 void clear()(E value) 498 { 499 static foreach (r; 0 .. rows) 500 { 501 static foreach (c; 0 .. cols) 502 { 503 at(r,c) = value; 504 } 505 } 506 } 507 508 static if (rows == cols) 509 { 510 /// Makes the current matrix an identity matrix. 511 void makeIdentity()() 512 { 513 clear(0); 514 static foreach (r; 0 .. rows) 515 { 516 at(r,r) = 1; 517 } 518 } 519 520 /// Returns: Identity Matrix. 521 static @property Matrix identity() 522 { 523 Matrix ret; 524 ret.clear(0); 525 static foreach (r; 0 .. rows) 526 { 527 ret.at(r,r) = 1; 528 } 529 530 return ret; 531 } 532 alias id = identity; // shorthand 533 534 /// Transpose Current Matrix. 535 void transpose()() 536 { 537 _matrix = transposed()._matrix; 538 } 539 alias T = transpose; // C++ Armadillo naming convention. 540 541 unittest 542 { 543 mat2 m2 = mat2(1.0f); 544 m2.transpose(); 545 assert(m2._matrix == mat2(1.0f)._matrix); 546 m2.makeIdentity(); 547 assert(m2._matrix == [[1.0f, 0.0f], 548 [0.0f, 1.0f]]); 549 m2.transpose(); 550 assert(m2._matrix == [[1.0f, 0.0f], 551 [0.0f, 1.0f]]); 552 assert(m2._matrix == m2.identity._matrix); 553 554 mat3 m3 = mat3(1.1f, 1.2f, 1.3f, 555 2.1f, 2.2f, 2.3f, 556 3.1f, 3.2f, 3.3f); 557 m3.transpose(); 558 assert(m3._matrix == [[1.1f, 2.1f, 3.1f], 559 [1.2f, 2.2f, 3.2f], 560 [1.3f, 2.3f, 3.3f]]); 561 562 mat4 m4 = mat4(2.0f); 563 m4.transpose(); 564 assert(m4._matrix == mat4(2.0f)._matrix); 565 m4.makeIdentity(); 566 assert(m4._matrix == [[1.0f, 0.0f, 0.0f, 0.0f], 567 [0.0f, 1.0f, 0.0f, 0.0f], 568 [0.0f, 0.0f, 1.0f, 0.0f], 569 [0.0f, 0.0f, 0.0f, 1.0f]]); 570 assert(m4._matrix == m4.identity._matrix); 571 } 572 573 } 574 575 /// Returns: a transposed copy of the matrix. 576 @property Matrix!(E, cols, rows) transposed()() const 577 { 578 typeof(return) ret; 579 static foreach (r; 0 .. rows) 580 { 581 static foreach (c; 0 .. cols) 582 { 583 ret.at(c,r) = at(r,c); 584 } 585 } 586 return ret; 587 } 588 589 } 590 alias mat2i = Matrix!(int, 2, 2); 591 alias mat2 = Matrix!(float, 2, 2); 592 alias mat2d = Matrix!(real, 2, 2); 593 alias mat2r = Matrix!(real, 2, 2); 594 alias mat3 = Matrix!(float, 3, 3); 595 alias mat34 = Matrix!(float, 3, 4); 596 alias mat4 = Matrix!(float, 4, 4); 597 alias mat2_cm = Matrix!(float, 2, 2, Layout.columnMajor); 598 599 pure nothrow @safe @nogc unittest { 600 auto m = mat2(1, 2, 601 3, 4); 602 assert(m(0, 0) == 1); 603 assert(m(0, 1) == 2); 604 assert(m(1, 0) == 3); 605 assert(m(1, 1) == 4); 606 } 607 608 pure nothrow @safe @nogc unittest { 609 auto m = mat2_cm(1, 3, 610 2, 4); 611 assert(m(0, 0) == 1); 612 assert(m(0, 1) == 2); 613 assert(m(1, 0) == 3); 614 assert(m(1, 1) == 4); 615 } 616 617 pure nothrow @safe @nogc unittest { 618 alias E = float; 619 immutable a = Vector!(E, 2, false, Orient.column)(1, 2); 620 immutable b = Vector!(E, 3, false, Orient.column)(3, 4, 5); 621 immutable c = outerProduct(a, b); 622 assert(c[] == [[3, 4, 5].s, 623 [6, 8, 10].s].s); 624 } 625 626 /// 3-Dimensional Spherical Point with Coordinate Type (Precision) `E`. 627 struct SpherePoint3(E) 628 if (isFloatingPoint!E) 629 { 630 enum D = 3; // only in three dimensions 631 alias ElementType = E; 632 633 /** Construct from Components `args`. */ 634 this(T...)(T args) 635 { 636 foreach (immutable ix, arg; args) 637 { 638 _spherePoint[ix] = arg; 639 } 640 } 641 /** Element data. */ 642 E[D] _spherePoint; 643 enum dimension = D; 644 645 @property void toString(Sink)(ref scope Sink sink) const 646 { 647 sink(`SpherePoint3(`); 648 foreach (const ix, const e ; _spherePoint) 649 { 650 if (ix != 0) { sink(","); } 651 sink(to!string(e)); 652 } 653 sink(`)`); 654 } 655 656 @property string toMathML()() const 657 { 658 // opening 659 string str = `<math><mrow> 660 <mo>(</mo> 661 <mtable>`; 662 663 static foreach (i; 0 .. D) 664 { 665 str ~= ` 666 <mtr> 667 <mtd> 668 <mn>` ~ _spherePoint[i].toMathML ~ `</mn> 669 </mtd> 670 </mtr>`; 671 } 672 673 // closing 674 str ~= ` 675 </mtable> 676 <mo>)</mo> 677 </mrow></math> 678 `; 679 return str; 680 } 681 682 /** Returns: Area 0 */ 683 @property E area() const { return 0; } 684 685 auto opSlice() { return _spherePoint[]; } 686 } 687 688 /** Instantiator for `SpherePoint3`. */ 689 auto spherePoint(Ts...)(Ts args) 690 if (!is(CommonType!Ts == void)) 691 { 692 return SpherePoint3!(CommonType!Ts, args.length)(args); 693 } 694 695 /** `D`-Dimensional Particle with Cartesian Coordinate `position` and 696 `velocity` of Element (Component) Type `E`. 697 */ 698 struct Particle(E, uint D, 699 bool normalizedVelocityFlag = false) 700 if (D >= 1) 701 { 702 Point!(E, D) position; 703 Vector!(E, D, normalizedVelocityFlag) velocity; 704 E mass; 705 } 706 707 // mixin(makeInstanceAliases("Particle", "particle", 2,4, 708 // ["float", "double", "real"])); 709 710 /** `D`-Dimensional Particle with Coordinate Position and 711 Direction/Velocity/Force Type (Precision) `E`. 712 F = m*a; where F is force, m is mass, a is acceleration. 713 */ 714 struct ForcedParticle(E, uint D, 715 bool normalizedVelocityFlag = false) 716 if (D >= 1) 717 { 718 Point!(E, D) position; 719 Vector!(E, D, normalizedVelocityFlag) velocity; 720 Vector!(E, D) force; 721 E mass; 722 723 /// Get acceleration. 724 @property auto acceleration()() const { return force/mass; } 725 } 726 727 /** `D`-Dimensional Axis-Aligned (Hyper) Cartesian `Box` with Element (Component) Type `E`. 728 729 Note: We must use inclusive compares betweeen boxes and points in inclusion 730 functions such as inside() and includes() in order for the behaviour of 731 bounding boxes (especially in integer space) to work as desired. 732 */ 733 struct Box(E, uint D) 734 if (D >= 1) 735 { 736 this(Vector!(E,D) lh) { min = lh; max = lh; } 737 this(Vector!(E,D) l_, 738 Vector!(E,D) h_) { min = l_; max = h_; } 739 740 @property void toString(Sink)(ref scope Sink sink) const 741 { 742 sink(`Box(lower:`); 743 min.toString(sink); 744 sink(`, upper:`); 745 max.toString(sink); 746 sink(`)`); 747 } 748 749 /// Get Box Center. 750 /+ TODO: @property Vector!(E,D) center() { return (min + max) / 2;} +/ 751 752 /// Constructs a Box enclosing `points`. 753 static Box fromPoints(in Vector!(E,D)[] points) 754 { 755 Box y; 756 foreach (p; points) 757 { 758 y.expand(p); 759 } 760 return y; 761 } 762 763 /// Expands the Box, so that $(I v) is part of the Box. 764 ref Box expand(Vector!(E,D) v) 765 { 766 static foreach (i; 0 .. D) 767 { 768 if (min[i] > v[i]) min[i] = v[i]; 769 if (max[i] < v[i]) max[i] = v[i]; 770 } 771 return this; 772 } 773 774 /// Expands box by another box `b`. 775 ref Box expand()(Box b) 776 { 777 return this.expand(b.min).expand(b.max); 778 } 779 780 unittest 781 { 782 immutable auto b = Box(Vector!(E,D)(1), 783 Vector!(E,D)(3)); 784 assert(b.sides == Vector!(E,D)(2)); 785 immutable auto c = Box(Vector!(E,D)(0), 786 Vector!(E,D)(4)); 787 assert(c.sides == Vector!(E,D)(4)); 788 assert(c.sidesProduct == 4^^D); 789 assert(unite(b, c) == c); 790 } 791 792 /** Returns: Length of Sides */ 793 @property auto sides()() const { return max - min; } 794 795 /** Returns: Area */ 796 @property real sidesProduct()() const 797 { 798 typeof(return) y = 1; 799 foreach (const ref side; sides) 800 { 801 y *= side; 802 } 803 return y; 804 } 805 806 static if (D == 2) 807 { 808 alias area = sidesProduct; 809 } 810 else static if (D == 3) 811 { 812 alias volume = sidesProduct; 813 } 814 else static if (D >= 4) 815 { 816 alias hyperVolume = sidesProduct; 817 } 818 819 alias include = expand; 820 821 Vector!(E,D) min; /// Low. 822 Vector!(E,D) max; /// High. 823 824 /** Either an element in min or max is nan or min <= max. */ 825 invariant() 826 { 827 // assert(any!"a==a.nan"(min), 828 // all!"a || a == a.nan"(elementwiseLessThanOrEqual(min, max)[])); 829 } 830 } 831 832 // mixin(makeInstanceAliases("Box","box", 2,4, 833 // ["int", "float", "double", "real"])); 834 835 Box!(E,D) unite(E, uint D)(Box!(E,D) a, 836 Box!(E,D) b) { return a.expand(b); } 837 Box!(E,D) unite(E, uint D)(Box!(E,D) a, 838 Vector!(E,D) b) { return a.expand(b); } 839 840 /** `D`-Dimensional Infinite Cartesian (Hyper)-Plane with Element (Component) Type `E`. 841 See_Also: http://stackoverflow.com/questions/18600328/preferred-representation-of-a-3d-plane-in-c-c 842 */ 843 struct Plane(E, uint D) 844 if (D >= 2 && 845 isFloatingPoint!E) 846 { 847 enum dimension = D; 848 849 alias ElementType = E; 850 851 /// Normal type of plane. 852 alias NormalType = Vector!(E, D, true); 853 854 union 855 { 856 static if (D == 3) 857 { 858 struct 859 { 860 E a; /// normal.x 861 E b; /// normal.y 862 E c; /// normal.z 863 } 864 } 865 NormalType normal; /// Plane Normal. 866 } 867 E distance; /// Plane Constant (Offset from origo). 868 869 @property void toString(Sink)(ref scope Sink sink) const 870 { 871 import std.conv : to; 872 sink(`Plane(normal:`); 873 sink(to!string(normal)); 874 sink(`, distance:`); 875 sink(to!string(distance)); 876 sink(`)`); 877 } 878 879 /// Constructs the plane, from either four scalars of type $(I E) 880 /// or from a 3-dimensional vector (= normal) and a scalar. 881 static if (D == 2) 882 { 883 this()(E a, E b, E distance) 884 { 885 this.normal.x = a; 886 this.normal.y = b; 887 this.distance = distance; 888 } 889 } 890 else static if (D == 3) 891 { 892 this()(E a, E b, E c, E distance) 893 { 894 this.normal.x = a; 895 this.normal.y = b; 896 this.normal.z = c; 897 this.distance = distance; 898 } 899 } 900 901 this()(NormalType normal, E distance) 902 { 903 this.normal = normal; 904 this.distance = distance; 905 } 906 907 /* unittest 908 { */ 909 /* Plane p = Plane(0.0f, 1.0f, 2.0f, 3.0f); */ 910 /* assert(p.normal == N(0.0f, 1.0f, 2.0f)); */ 911 /* assert(p.distance == 3.0f); */ 912 913 /* p.normal.x = 4.0f; */ 914 /* assert(p.normal == N(4.0f, 1.0f, 2.0f)); */ 915 /* assert(p.x == 4.0f); */ 916 /* assert(p.y == 1.0f); */ 917 /* assert(p.c == 2.0f); */ 918 /* assert(p.distance == 3.0f); */ 919 /* } */ 920 921 /// Normalizes the plane inplace. 922 void normalize()() 923 { 924 immutable E det = cast(E)1 / normal.magnitude; 925 normal *= det; 926 distance *= det; 927 } 928 929 /// Returns: a normalized copy of the plane. 930 /* @property Plane normalized() const { */ 931 /* Plane y = Plane(a, b, c, distance); */ 932 /* y.normalize(); */ 933 /* return y; */ 934 /* } */ 935 936 // unittest { 937 // Plane p = Plane(0.0f, 1.0f, 2.0f, 3.0f); 938 // Plane pn = p.normalized(); 939 // assert(pn.normal == N(0.0f, 1.0f, 2.0f).normalized); 940 // assert(almost_equal(pn.distance, 3.0f / N(0.0f, 1.0f, 2.0f).length)); 941 // p.normalize(); 942 // assert(p == pn); 943 // } 944 945 /// Returns: distance from a point to the plane. 946 /// Note: the plane $(RED must) be normalized, the result can be negative. 947 /* E distanceTo(N point) const { */ 948 /* return dot(point, normal) + distance; */ 949 /* } */ 950 951 /// Returns: distanceTo from a point to the plane. 952 /// Note: the plane does not have to be normalized, the result can be negative. 953 /* E ndistance(N point) const { */ 954 /* return (dot(point, normal) + distance) / normal.magnitude; */ 955 /* } */ 956 957 /* unittest 958 { */ 959 /* Plane p = Plane(-1.0f, 4.0f, 19.0f, -10.0f); */ 960 /* assert(almost_equal(p.ndistance(N(5.0f, -2.0f, 0.0f)), -1.182992)); */ 961 /* assert(almost_equal(p.ndistance(N(5.0f, -2.0f, 0.0f)), */ 962 /* p.normalized.distanceTo(N(5.0f, -2.0f, 0.0f)))); */ 963 /* } */ 964 965 /* bool opEquals(Plane other) const { */ 966 /* return other.normal == normal && other.distance == distance; */ 967 /* } */ 968 969 } 970 971 // mixin(makeInstanceAliases("Plane","plane", 3,4, 972 // defaultElementTypes)); 973 974 /** `D`-Dimensional Cartesian (Hyper)-Sphere with Element (Component) Type `E`. 975 */ 976 struct Sphere(E, uint D) 977 if (D >= 2 && 978 isNumeric!E) 979 { 980 alias CenterType = Point!(E, D); 981 982 CenterType center; 983 E radius; 984 985 void translate(Vector!(E, D) shift) 986 { 987 center = center + shift; // point + vector => point 988 } 989 alias shift = translate; 990 991 @property: 992 993 E diameter()() const 994 { 995 return 2 * radius; 996 } 997 static if (D == 2) 998 { 999 auto area()() const 1000 { 1001 return PI * radius ^^ 2; 1002 } 1003 } 1004 else static if (D == 3) 1005 { 1006 auto area()() const 1007 { 1008 return 4 * PI * radius ^^ 2; 1009 } 1010 auto volume()() const 1011 { 1012 return 4 * PI * radius ^^ 3 / 3; 1013 } 1014 } 1015 else static if (D >= 4) 1016 { 1017 // See_Also: https://en.wikipedia.org/wiki/Volume_of_an_n-ball 1018 real n = D; 1019 auto volume()() const 1020 { 1021 import std.mathspecial: gamma; 1022 return PI ^^ (n / 2) / gamma(n / 2 + 1) * radius ^^ n; 1023 } 1024 } 1025 } 1026 1027 auto sphere(C, R)(C center, R radius) 1028 { 1029 return Sphere!(C.ElementType, C.dimension)(center, radius); 1030 } 1031 /+ TODO: Use this instead: +/ 1032 // auto sphere(R, C...)(Point!(CommonType!C, C.length) center, R radius) { 1033 // return Sphere!(CommonType!C, C.length)(center, radius); 1034 // } 1035 1036 /** 1037 See_Also: http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersect 1038 */ 1039 bool intersect(T)(Circle!T circle, Rect!T rect) 1040 { 1041 immutable hw = rect.w/2, hh = rect.h/2; 1042 1043 immutable dist = Point!T(abs(circle.x - rect.x0 - hw), 1044 abs(circle.y - rect.y0 - hh)); 1045 1046 if (dist.x > (hw + circle.r)) return false; 1047 if (dist.y > (hh + circle.r)) return false; 1048 1049 if (dist.x <= hw) return true; 1050 if (dist.y <= hh) return true; 1051 1052 immutable cornerDistance_sq = ((dist.x - hw)^^2 + 1053 (dist.y - hh)^^2); 1054 1055 return (cornerDistance_sq <= circle.r^^2); 1056 } 1057 1058 @safe unittest { 1059 assert(box2f(vec2f(1, 2), vec2f(3, 3)).to!string == `Box(lower:ColumnVector(1,2), upper:ColumnVector(3,3))`); 1060 assert([12, 3, 3].to!string == `[12, 3, 3]`); 1061 1062 assert(vec2f(2, 3).to!string == `ColumnVector(2,3)`); 1063 1064 assert(vec2f(2, 3).to!string == `ColumnVector(2,3)`); 1065 assert(vec2f(2, 3).to!string == `ColumnVector(2,3)`); 1066 1067 assert(vec3f(2, 3, 4).to!string == `ColumnVector(2,3,4)`); 1068 1069 assert(box2f(vec2f(1, 2), 1070 vec2f(3, 4)).to!string == `Box(lower:ColumnVector(1,2), upper:ColumnVector(3,4))`); 1071 1072 assert(vec2i(2, 3).to!string == `ColumnVector(2,3)`); 1073 assert(vec3i(2, 3, 4).to!string == `ColumnVector(2,3,4)`); 1074 assert(vec3i(2, 3, 4).to!string == `ColumnVector(2,3,4)`); 1075 1076 assert(vec2i(2, 3).toMathML == `<math><mrow> 1077 <mo>⟨</mo> 1078 <mtable> 1079 <mtr> 1080 <mtd> 1081 <mn>2</mn> 1082 </mtd> 1083 </mtr> 1084 <mtr> 1085 <mtd> 1086 <mn>3</mn> 1087 </mtd> 1088 </mtr> 1089 </mtable> 1090 <mo>⟩</mo> 1091 </mrow></math> 1092 `); 1093 1094 auto m = mat2(1, 2, 3, 4); 1095 assert(m.toLaTeX == `\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}`); 1096 assert(m.toMathML == `<math><mrow> 1097 <mo>❲</mo> 1098 <mtable> 1099 <mtr> 1100 <mtd> 1101 <mn>1</mn> 1102 </mtd> 1103 <mtd> 1104 <mn>2</mn> 1105 </mtd> 1106 </mtr> 1107 <mtr> 1108 <mtd> 1109 <mn>3</mn> 1110 </mtd> 1111 <mtd> 1112 <mn>4</mn> 1113 </mtd> 1114 </mtr> 1115 </mtable> 1116 <mo>❳</mo> 1117 </mrow></math> 1118 `); 1119 } 1120 1121 version (unittest) 1122 { 1123 import std.conv : to; 1124 import nxt.array_help : s; 1125 }