1 /** 2 Contains various generic geometry containers 3 4 floathe entire module is designed for 2D, because libdgt is for 2D development 5 */ 6 module dgt.geom; 7 8 import std.algorithm.comparison; 9 import std.math : approxEqual, sqrt, cos, sin, sgn, PI; 10 import dgt.io; 11 12 @safe: 13 14 //The famous fast inverse square root 15 @trusted pure nothrow @nogc private float Q_rsqrt(float number) 16 { 17 long i; 18 float x2, y; 19 const float threehalfs = 1.5; 20 x2 = number * 0.5; 21 y = number; 22 i = *cast(long*) &y; 23 i = 0x5f3759df - ( i >> 1 ); 24 y = * cast(float*) &i; 25 y = y * ( threehalfs - ( x2 * y * y ) ); 26 return y; 27 } 28 29 /** 30 A 2D vector with an arbitrary numeric type 31 */ 32 struct Vector 33 { 34 float x = 0, y = 0; 35 36 @nogc nothrow void print() const 37 { 38 dgt.io.print("Vector(", x, ", ", y, ")"); 39 } 40 41 @nogc nothrow pure: 42 ///Create a vector with an x and a y 43 this(float x, float y) 44 { 45 this.x = x; 46 this.y = y; 47 } 48 49 Vector opUnary(string op)() const 50 { 51 static if (op == "-") 52 { 53 return Vector(-x, -y); 54 } 55 } 56 57 Vector opBinary(string op)(in float scalar) const 58 { 59 static if (op == "*") 60 { 61 return Vector(x * scalar, y * scalar); 62 } 63 static if (op == "/") 64 { 65 return Vector(x / scalar, y / scalar); 66 } 67 } 68 69 Vector opBinary(string op)(in Vector other) const 70 { 71 static if (op == "+") 72 { 73 return Vector(x + other.x, y + other.y); 74 } 75 static if (op == "-") 76 { 77 return Vector(x - other.x, y - other.y); 78 } 79 } 80 81 bool opEquals(Vector other) const 82 { 83 return approxEqual(x, other.x, 0.000001) && approxEqual(y, other.y, 0.000001); 84 } 85 86 ///Get the squared length of the vector (faster than getting the length) 87 @property float len2() const 88 { 89 return x * x + y * y; 90 } 91 92 ///Get the length of the vector 93 @property float len() const 94 { 95 return sqrt(len2()); 96 } 97 98 ///Clamp a vector somewhere between a minimum and a maximum 99 Vector clamp(in Vector min, in Vector max) const 100 { 101 return Vector(std.algorithm.comparison.clamp(x, min.x, max.x), std.algorithm.comparison.clamp(y, min.y, max.y)); 102 } 103 104 ///Get the cross product of a vector 105 float cross(in Vector other) const 106 { 107 return x * other.y - y * other.x; 108 } 109 110 ///Get the dot product of a vector 111 float dot(in Vector other) const 112 { 113 return x * other.x + y * other.y; 114 } 115 116 ///Normalize the vector's length from [0, 1] 117 Vector normalize() const 118 { 119 return this * Q_rsqrt(len2); 120 } 121 122 ///Get only the X component of the Vector, represented as a vector 123 @property Vector xComp() const 124 { 125 return Vector(x, 0); 126 } 127 128 ///Get only the Y component of the Vector, represented as a vector 129 @property Vector yComp() const 130 { 131 return Vector(0, y); 132 } 133 134 ///Get the vector equal to Vector(1 / x, 1 / y) 135 @property Vector inverse() const 136 { 137 return Vector(1 / x, 1 / y); 138 } 139 } 140 141 unittest 142 { 143 Vector a, b; 144 a = Vector(5, 10); 145 b = Vector(1, -2); 146 assert((a + b).x == 6); 147 assert((a - b).y == 12); 148 } 149 150 ///Represents a 2D line segment 151 struct Line 152 { 153 Vector start, end; 154 155 @nogc nothrow pure: 156 157 bool intersects(in Line other) const 158 { 159 //See https://stackoverflow.com/a/565282 for algorithm 160 Vector p = start; 161 Vector q = other.start; 162 Vector r = end - start; 163 Vector s = other.end - other.start; 164 //t = (q - p) x s / (r x s) 165 //u = (q - p) x r / (r x s) 166 float u_numerator = (q - p).cross(r); 167 float t_numerator = (q - p).cross(s); 168 float denominator = r.cross(s); 169 if (denominator == 0) { 170 if(u_numerator != 0) 171 return false; 172 float t0 = (q - p).dot(r) / r.dot(r); 173 float t1 = t0 + s.dot(r) / r.dot(r); 174 return (t0 >= 0 && t0 <= 1) || (t1 >= 0 && t1 <= 1) || 175 (sgn(t0) != sgn(t1)); 176 } else { 177 float u = u_numerator / denominator; 178 float t = t_numerator / denominator; 179 return t >= 0 && t <= 1 && u >= 0 && u <= 1; 180 } 181 } 182 183 ///Check if a line segment intersects a circle 184 bool intersects(in Circle c) const 185 { 186 Vector center = c.center; 187 Vector line = end - start; 188 Vector dist = center - start; 189 Vector nor_line = line.normalize(); 190 float product = dist.dot(nor_line); 191 // Find the closest point on the line to check 192 Vector check_point; 193 if(product <= 0) { 194 check_point = start; 195 } else if(product >= 1) { 196 check_point = end; 197 } else { 198 check_point = start + nor_line * product; 199 } 200 // Check to see if the closest point is within the radius 201 return (center - check_point).len2 < c.radius * c.radius; 202 } 203 204 ///Check if a line segment intersects a rectangle 205 bool intersects(in Rectangle r) const 206 { 207 return r.contains(start) || r.contains(end) 208 || Line(r.topLeft, r.topLeft + r.size.xComp).intersects(this) 209 || Line(r.topLeft, r.topLeft + r.size.yComp).intersects(this) 210 || Line(r.topLeft + r.size.xComp, r.topLeft + r.size).intersects(this) 211 || Line(r.topLeft + r.size.yComp, r.topLeft + r.size).intersects(this); 212 } 213 214 } 215 216 /** 217 An axis-aligned rectangle made of some numeric type 218 */ 219 struct Rectangle 220 { 221 ///floathe top left of the rectangle 222 public Vector topLeft = Vector(0, 0); 223 ///floathe width and height of the rectangle 224 public Vector size = Vector(0, 0); 225 226 @nogc nothrow void print() const 227 { 228 dgt.io.print("Rectangle(", x, ", ", y, ", ", width, ", ", height, ")"); 229 } 230 231 @nogc nothrow pure public: 232 ///Create a rectangle with the given dimension 233 this(in float x, in float y, in float width, in float height) 234 { 235 topLeft = Vector(x, y); 236 size = Vector(width, height); 237 } 238 ///Create a rectangle at 0, 0 with a given size 239 this(in float width, in float height) 240 { 241 this(0, 0, width, height); 242 } 243 ///Create a rectangle with a given top left and a given size 244 this(in Vector top, in Vector dimension) 245 { 246 topLeft = top; 247 size = dimension; 248 } 249 250 @property float x() const { return topLeft.x; } 251 @property float x(float val) { return topLeft.x = val; } 252 @property float y() const { return topLeft.y; } 253 @property float y(float val) { return topLeft.y = val; } 254 @property float width() const { return size.x; } 255 @property float width(float val) { return size.x = val; } 256 @property float height() const { return size.y; } 257 @property float height(float val) { return size.y = val; } 258 259 ///Checks if a point falls within the rectangle 260 bool contains(in Vector v) const 261 { 262 return v.x >= x && v.y >= y && v.x < x + width && v.y < y + height; 263 } 264 265 ///Check if any of the area bounded by this rectangle is bounded by another 266 bool overlaps(in Rectangle b) const 267 { 268 return x < b.x + b.width && x + width > b.x && y < b.y + b.height && y + height > b.y; 269 } 270 271 ///Check if any of the area bounded by this rectangle is bounded by a circle 272 bool overlaps(in Circle c) const 273 { 274 Vector closest; 275 if (c.x < x) { 276 closest.x = x; 277 } else if (c.x > x + width) { 278 closest.x = x + width; 279 } else { 280 closest.x = c.x; 281 } 282 if (c.y < y) { 283 closest.y = y; 284 } else if (c.y > y + height) { 285 closest.y = y + height; 286 } else { 287 closest.y = c.y; 288 } 289 closest.x = closest.x - c.x; 290 closest.y = closest.y - c.y; 291 return (closest.x * closest.x) + (closest.y * closest.y) < c.radius * c.radius; 292 } 293 294 ///Set the rectangle's dimensions 295 void set(float newX, float newY, float newWidth, float newHeight) 296 { 297 x = newX; 298 y = newY; 299 width = newWidth; 300 height = newHeight; 301 } 302 303 /** 304 Move the rectangle so it is entirely contained with another 305 306 If the rectangle is moved it will always be flush with a border of the given area 307 */ 308 Rectangle constrain(in Rectangle outer) const 309 { 310 return Rectangle(topLeft.clamp(outer.topLeft, outer.topLeft + outer.size - size), size); 311 } 312 313 ///Translate the rectangle by a vector 314 Rectangle translate(in Vector vec) const 315 { 316 return Rectangle(x + vec.x, y + vec.y, width, height); 317 } 318 } 319 320 unittest 321 { 322 Rectangle a, b, c; 323 a = Rectangle(0, 0, 32, 32); 324 b = Rectangle(16, 16, 32, 32); 325 c = Rectangle(50, 50, 5, 5); 326 assert(a.overlaps(b)); 327 assert(!a.overlaps(c)); 328 } 329 330 /** 331 A circle with a center and a radius 332 */ 333 struct Circle 334 { 335 public Vector center = Vector(0, 0); 336 public float radius = 0; 337 338 @nogc nothrow void print() const 339 { 340 dgt.io.print("Circle(", x, ", ", y, ", ", radius, ")"); 341 } 342 343 @nogc nothrow pure public: 344 this(float x, float y, float radius) 345 { 346 center = Vector(x, y); 347 this.radius = radius; 348 } 349 @property float x() const { return center.x; } 350 @property float x(float val) { return center.x = val; } 351 @property float y() const { return center.y; } 352 @property float y(float val) { return center.y = val; } 353 354 /** 355 Checks if a vector falls within the area bounded by a circle 356 */ 357 bool contains(in Vector v) const 358 { 359 Vector dist = v - center; 360 return dist.len2 < radius * radius; 361 } 362 363 /** 364 Checks to see if the circle and the rectangle share any area 365 */ 366 bool overlaps(in Rectangle r) const 367 { 368 return r.overlaps(this); 369 } 370 371 /** 372 Checks to see if the circles have any overlapping area 373 */ 374 bool overlaps(in Circle c) const 375 { 376 float xDiff = x - c.x; 377 float yDiff = y - c.y; 378 float rad = radius + c.radius; 379 return xDiff * xDiff + yDiff * yDiff < rad * rad; 380 } 381 382 /** 383 Sets the dimensions of a circle 384 */ 385 void set(float newX, float newY, float newRadius) 386 { 387 x = newX; 388 y = newY; 389 radius = newRadius; 390 } 391 392 ///floatranslate the circle by a given vector 393 Circle translate(Vector vec) 394 { 395 return Circle(x + vec.x, y + vec.y, radius); 396 } 397 } 398 399 unittest 400 { 401 Circle a, b, c; 402 Rectangle d; 403 a.set(0, 0, 16); 404 b.set(5, 5, 4); 405 c.set(50, 50, 5); 406 d.set(10, 10, 10, 10); 407 assert(a.overlaps(b)); 408 assert(!a.overlaps(c)); 409 assert(a.overlaps(d)); 410 assert(!c.overlaps(d)); 411 } 412 413 /** 414 A Transform 3x3 matrix to make transformations more efficient 415 */ 416 struct Transform 417 { 418 private float[9] data = [ 419 1, 0, 0, 420 0, 1, 0, 421 0, 0, 1 422 ]; 423 424 @nogc nothrow void print() const 425 { 426 dgt.io.print("Transform["); 427 for(size_t x = 0; x < 3; x++) 428 { 429 dgt.io.print("["); 430 for(size_t y = 0; y < 3; y++) 431 { 432 dgt.io.print(this[x, y]); 433 if(y != 2) dgt.io.print(", "); 434 } 435 dgt.io.print("]"); 436 if(x != 2) dgt.io.print(", "); 437 } 438 dgt.io.print("]"); 439 } 440 441 @nogc nothrow pure: 442 443 this(in float[9] data) 444 { 445 this.data = data; 446 } 447 448 ///A pointer to the internal buffer to pass the matrix to C 449 float* ptr() 450 { 451 return &data[0]; 452 } 453 454 Transform opBinary(string op)(Transform other) const 455 if (op == "*") 456 { 457 Transform ret; 458 for (size_t i = 0; i < 3; i++) { 459 for (size_t j = 0; j < 3; j++) { 460 ret[i, j] = 0; 461 for (size_t k = 0; k < 3; k++) { 462 ret[i, j] = ret[i, j] + this[k, j] * other[i, k]; 463 } 464 } 465 } 466 return ret; 467 } 468 469 Vector opBinary(string op)(Vector other) const 470 if (op == "*") 471 { 472 return Vector(other.x * this[0, 0] + other.y * this[0, 1] + this[0, 2], 473 other.x * this[1, 0] + other.y * this[1, 1] + this[1, 2]); 474 } 475 476 Transform opBinary(string op)(float scalar) const 477 if(op == "*") 478 { 479 Transform ret; 480 for(int i = 0; i < 3; i++) 481 for(int j = 0; j < 3; j++) 482 ret[i, j] = this[i, j] * scalar; 483 return ret; 484 } 485 486 float opIndex(size_t i, size_t j) const 487 { 488 return data[i * 3 + j]; 489 } 490 491 float opIndexAssign(float val, size_t i, size_t j) 492 { 493 return data[i * 3 + j] = val; 494 } 495 496 ref Transform opAssign(float[9] array) 497 { 498 data = array; 499 return this; 500 } 501 502 ///Flip the underlying matrix over the xy axis 503 @property Transform transpose() const 504 { 505 return Transform([ 506 this[0, 0], this[0, 1], this[0, 2], 507 this[1, 0], this[1, 1], this[1, 2], 508 this[2, 0], this[2, 1], this[2, 2] 509 ]); 510 } 511 512 /** 513 Find a 2x2 submatrix 514 515 The row and column that (x, y) sits in will be eliminated 516 */ 517 float[4] submatrix(int x, int y) const 518 { 519 float[4] matrix; 520 int index = 0; 521 for(int i = 0; i < 3; i++) 522 { 523 for(int j = 0; j < 3; j++) 524 { 525 if(i == x || j == y) 526 continue; 527 else 528 { 529 matrix[index] = this[i, j]; 530 index++; 531 } 532 } 533 } 534 return matrix; 535 } 536 537 /** 538 Find a determinant of a 2x2 submatrix 539 540 The row and column that (x, y) sits in will be ignored 541 */ 542 float determinant(int x, int y) const 543 { 544 const sub = submatrix(x, y); 545 return sub[0] * sub[3] - (sub[1] * sub[2]); 546 } 547 548 ///Find the determinant of the underlying matrix 549 float determinant() const 550 { 551 float sum = 0; 552 for(int i = 0; i < 3; i++) 553 sum += this[i, 0] * determinant(i, 0); 554 return sum; 555 } 556 557 ///Find the inverse of the transform 558 @property Transform inverse() const 559 { 560 Transform other = this; 561 //Find the matrix of minors 562 for(int i = 0; i < 3; i++) 563 for(int j = 0; j < 3; j++) 564 other[i, j] = this.determinant(i, j); 565 //Find the matrix of cofactors 566 for(int i = 0; i < 3; i++) 567 for(int j = 0; j < 3; j++) 568 if(i != j && !(i == 0 && j == 2) && !(i == 2 && j == 0)) 569 other[i, j] = -other[i, j]; 570 //Find the adjutant 571 other = other.transpose; 572 return other * (1 / this.determinant); 573 } 574 575 ///Create an identity matrix 576 static pure Transform identity() 577 { 578 return Transform(); 579 } 580 581 ///Create a rotation matrix 582 static pure Transform rotate(in float angle) 583 { 584 float c = cos(angle * PI / 180); 585 float s = sin(angle * PI / 180); 586 return Transform([ 587 c, -s, 0, 588 s, c, 0, 589 0, 0, 1 590 ]); 591 } 592 593 ///Create a translation matrix 594 static pure Transform translate(in Vector vec) 595 { 596 return Transform([ 597 1, 0, vec.x, 598 0, 1, vec.y, 599 0, 0, 1 600 ]); 601 } 602 603 ///Create a scale matrix 604 static pure Transform scale(in Vector vec) 605 { 606 return Transform([ 607 vec.x, 0, 0, 608 0, vec.y, 0, 609 0, 0, 1 610 ]); 611 } 612 } 613 614 615 @nogc nothrow: 616 unittest 617 { 618 auto vec = Vector(3, 5); 619 auto inverse = vec.inverse; 620 assert(approxEqual(inverse.x, 1.0 / 3, 0.00001) && 621 approxEqual(inverse.y, 1.0 / 5, 0.00001)); 622 } 623 unittest 624 { 625 auto vec = Vector(2, 4); 626 auto translate = Transform.scale(Vector(0.5, 0.5)); 627 auto inverseTranslate = translate.inverse; 628 auto transformed = inverseTranslate * vec; 629 assert(transformed.x == 4 && transformed.y == 8); 630 } 631 unittest 632 { 633 Transform m, n; 634 n[0, 0] = 5; 635 assert(n.ptr[0] == 5); 636 auto result = m * n; 637 assert(result[0, 0] == 5); 638 m[0, 0] = 2; 639 result = m * n; 640 assert(result[0, 0] = 10); 641 } 642 unittest 643 { 644 auto trans = Transform.scale(Vector(2, 2)); 645 auto vec = Vector(2, 5); 646 auto scaled = trans * vec; 647 assert(scaled.x == 4 && scaled.y == 10); 648 } 649 unittest 650 { 651 auto trans = Transform.translate(Vector(3, 4)); 652 auto vec = Vector(1, 1); 653 vec = trans * vec; 654 assert(vec.x == 4 && vec.y == 5); 655 } 656 unittest 657 { 658 auto trans = Transform.identity() * Transform.translate(Vector()) 659 * Transform.rotate(0) * Transform.scale(Vector(1, 1)); 660 auto vec = trans * Vector(15, 12); 661 assert(vec.x == 15); 662 } 663 unittest 664 { 665 auto vec = Vector(5, 0); 666 assert(vec.len2 == 25); 667 assert(vec.len == 5); 668 } 669 unittest 670 { 671 auto vec = Vector(10, 10); 672 vec = vec / 2; 673 assert(vec.x == 5 && vec.y == 5); 674 } 675 unittest 676 { 677 auto circ = Circle(0, 0, 10); 678 auto vec1 = Vector(0, 0); 679 auto vec2 = Vector(11, 11); 680 assert(circ.contains(vec1)); 681 assert(!circ.contains(vec2)); 682 } 683 unittest 684 { 685 auto rect = Rectangle(0, 0, 32, 32); 686 auto vec1 = Vector(5, 5); 687 auto vec2 = Vector(33, 1); 688 assert(rect.contains(vec1)); 689 assert(!rect.contains(vec2)); 690 } 691 unittest 692 { 693 auto circ = Circle(0, 0, 5); 694 auto rec1 = Rectangle(0, 0, 2, 2); 695 auto rec2 = Rectangle(5, 5, 4, 4); 696 assert(circ.overlaps(rec1) && rec1.overlaps(circ)); 697 assert(!circ.overlaps(rec2) && !rec2.overlaps(circ)); 698 } 699 unittest 700 { 701 auto min = Vector(-10, -2); 702 auto max = Vector(5, 6); 703 auto a = Vector(-11, 3); 704 auto clamped = a.clamp(min, max); 705 assert(clamped.x == -10 && clamped.y == 3); 706 auto b = Vector(2, 8); 707 clamped = b.clamp(min, max); 708 assert(clamped.x == 2 && clamped.y == 6); 709 } 710 unittest 711 { 712 auto constraint = Rectangle(0, 0, 10, 10); 713 auto a = Rectangle(-1, 3, 5, 5); 714 auto b = Rectangle(4, 4, 8, 3); 715 a = a.constrain(constraint); 716 assert(a.x == 0 && a.y == 3); 717 b = b.constrain(constraint); 718 assert(b.x == 2 && b.y == 4); 719 } 720 721 unittest 722 { 723 println("Should print a vector at 0, 0: ", Vector(0, 0)); 724 println("Should print a circle at 0, 0 with a radius of 10: ", Circle(0, 0, 10)); 725 println("Should print a rectangle at 0, 0, with a side of 5", Rectangle(0, 0, 5, 5)); 726 println("Should print an identity matrix: ", Transform.identity()); 727 } 728 unittest 729 { 730 auto a = Rectangle(10, 10, 5, 5); 731 auto b = Circle(10, 10, 5); 732 auto c = Vector(1, -1); 733 auto aTranslate = a.translate(c); 734 auto bTranslate = b.translate(c); 735 assert(aTranslate.y == a.y + c.y && aTranslate.y == a.y + c.y); 736 assert(bTranslate.x == b.x + c.x && bTranslate.y == b.y + c.y); 737 } 738 unittest 739 { 740 assert(Vector(6, 5).dot(Vector(2, -8)) == -28); 741 } 742 unittest 743 { 744 const line1 = Line(Vector(0, 0), Vector(32, 32)); 745 const line2 = Line(Vector(0, 32), Vector(32, 0)); 746 const line3 = Line(Vector(32, 32), Vector(64, 64)); 747 const line4 = Line(Vector(100, 100), Vector(1000, 1000)); 748 assert(line1.intersects(line2)); 749 assert(line1.intersects(line3)); 750 assert(!line2.intersects(line3)); 751 assert(!line1.intersects(line4)); 752 assert(!line2.intersects(line4)); 753 assert(!line3.intersects(line4)); 754 const rect = Rectangle(32, 32); 755 assert(line1.intersects(rect)); 756 assert(line2.intersects(rect)); 757 assert(line3.intersects(rect)); 758 assert(!line4.intersects(rect)); 759 const circ = Circle(0, 0, 33); 760 assert(line1.intersects(circ)); 761 assert(line2.intersects(circ)); 762 assert(!line3.intersects(circ)); 763 assert(!line4.intersects(circ)); 764 }