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