1 /++ 2 This module describes shapes for drawing, 3 checking and other things related to the work of shapes. 4 5 Forms are of the following types: 6 * `Unknown` - Forms are of the following types: 7 * `Point` - Dot. The simplest of the simplest forms. Only origin parameters are valid. 8 * `line` - More complex form. Has the origin and the end of the coordinates of its two points. 9 By default, this is a line. 10 * 'rectangle' - Rectangle shape. By default, such a rectangle is considered to be solid. 11 * `circle` - Circle shape. By default, it is considered to be solid. 12 * `triangle` - Triangle shape. It is believed to be solid. 13 * `multi` - A shape that combines several shapes, which will eventually give a single shape. 14 Here you can, for example, create a non-solid polygon: 15 --- 16 /* 17 Such a rectangle can also be created using the function 18 `Shape.RectangleLine(begin,end)`. 19 */ 20 auto rectangle_nonSolid = Shape.Multi([ 21 Shape.Line(Vecf(0,0),Vecf(32,0)), 22 Shape.Line(Vecf(32,0),Vecf(32,32)), 23 Shape.Line(Vecf(0,0),Vecf(0,32)), 24 Shape.Line(Vecf(0,32),Vecf(32,32)) 25 ], Vecf(0,0)); 26 --- 27 28 All such types are specified in enum `ShapeType`. 29 30 Macros: 31 LREF = <a href="#$1">$1</a> 32 HREF = <a href="$1">$2</a> 33 34 Authors: $(HREF https://github.com/TodNaz,TodNaz) 35 Copyright: Copyright (c) 2020 - 2021, TodNaz. 36 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT) 37 +/ 38 module tida.shape; 39 40 import std.traits; 41 42 /++ 43 Shape type. 44 +/ 45 enum ShapeType 46 { 47 unknown, /// Unknown 48 point, /// Point 49 line, /// Line 50 rectangle, /// Rectangle 51 circle, /// Circle 52 triangle, /// Triangle 53 roundrect, /// Roundrect 54 polygon, /// Polygon 55 multi /// Several shapes in one. 56 } 57 58 /++ 59 Figure description structure. 60 +/ 61 struct Shape(T : float) 62 if (isNumeric!T && isMutable!T && isSigned!T) 63 { 64 import tida.vector; 65 66 alias Type = T; 67 68 private: 69 T _radius; 70 Vector!T _trType; 71 Vector!T _begin; 72 Vector!T _end; 73 74 public: 75 ShapeType type; /// Shape type 76 Shape!T[] shapes; /++ A set of figures. Needed for the type when you need 77 to make one shape from several. +/ 78 Vector!T[] data; /// Polygon data 79 bool isSolid = false; /// Is the polygon solid? 80 81 string toString() @safe 82 { 83 import std.conv : to; 84 import std.algorithm; 85 86 if(type == ShapeType.point) { 87 return "Shape.Point("~begin.to!string~")"; 88 }else 89 if(type == ShapeType.line) { 90 return "Shape.Line(begin: "~begin.to!string~", end: "~end.to!string~")"; 91 }else 92 if(type == ShapeType.rectangle) { 93 return "Shape.Rectangle(begin: "~begin.to!string~", end: "~end.to!string~")"; 94 }else 95 if(type == ShapeType.circle) { 96 return "Shape.Circle(position: "~begin.to!string~", radius: "~radius.to!string~")"; 97 }else 98 if(type == ShapeType.triangle) { 99 return "Shape.Triangle(0: "~vertexs[0].to!string~", 1:"~vertexs[1].to!string~ 100 ", 2:"~vertexs[2].to!string~")"; 101 }else 102 if(type == ShapeType.multi) { 103 string strData; 104 105 foreach(e; shapes) 106 strData ~= e.toString ~ (e == shapes[$-1] ? "" : ",") ~"\n"; 107 108 return "Shape.Multi(position: "~begin.to!string~", shapes: [\n" ~ strData ~ "])"; 109 110 }else 111 if(type == ShapeType.polygon) { 112 string strData; 113 114 foreach(e; data) 115 strData ~= e.to!string ~ (e == data[$-1] ? "" : ",") ~ "\n"; 116 117 return "Shape.Polygon(position: "~begin.to!string~", points: [\n" ~ strData ~ "])"; 118 } 119 120 return "Shape.Unknown()"; 121 } 122 123 /++ 124 Converting a form to another form of its presentation. 125 126 Params: 127 R = What to convention? 128 129 Example: 130 --- 131 Vecf[][] points = myPolygon.to!Vecf[][]; 132 --- 133 +/ 134 R to(R)() 135 { 136 static if (is(R : Vector!T)) 137 { 138 return begin; 139 }else 140 static if (is(R : Vector!T[])) 141 { 142 if (type == ShapeType.point || type == ShapeType.circle) 143 { 144 return [begin]; 145 }else 146 if (type == ShapeType.line || type == ShapeType.rectangle) 147 { 148 return [begin, end]; 149 }else 150 if (type == ShapeType.triangle) 151 { 152 return [this.vertex!0,this.vertex!1,this.vertex!2]; 153 }else 154 if (type == ShapeType.multi) 155 { 156 Vector!(T)[] temp; 157 foreach (shape; shapes) 158 { 159 temp ~= this.to!R; 160 } 161 return temp; 162 } 163 164 return []; 165 }else 166 static if (is(R : Vector!T[][])) 167 { 168 if (type != ShapeType.unknown) 169 { 170 if (type != ShapeType.multi) 171 { 172 return [to!(Vector!T[])]; 173 }else 174 { 175 Vector!T[][] temp; 176 foreach (shape; shapes) 177 { 178 temp ~= this.to!(Vector!T[]); 179 } 180 } 181 } 182 }else 183 static if (is(R : string)) 184 { 185 return toString(); 186 } 187 } 188 189 @safe nothrow pure: 190 /// The beginning of the figure. 191 @property Vector!T begin() inout 192 { 193 return _begin; 194 } 195 196 /// The end of the figure. 197 @property Vector!T end() inout 198 in(type != ShapeType.point && type != ShapeType.circle && type != ShapeType.polygon, 199 "This shape does not support end coordinates!") 200 do 201 { 202 return _end; 203 } 204 205 /++ 206 Move shape 207 +/ 208 void move(Vector!T pos) 209 { 210 _begin = _begin + pos; 211 212 if(type == ShapeType.line || type == ShapeType.rectangle) { 213 _end = _end + pos; 214 } 215 } 216 217 /// The beginning of the figure. 218 @property Vector!T begin(Vector!T vec) 219 { 220 return _begin = vec; 221 } 222 223 /// The end of the figure. 224 @property Vector!T end(Vector!T vec) @safe nothrow pure 225 in(type != ShapeType.point && type != ShapeType.circle && type != ShapeType.polygon, 226 "This shape does not support end coordinates!") 227 do 228 { 229 return _end = vec; 230 } 231 232 /// The beginning of the figure along the x-axis. 233 @property T x() inout 234 { 235 return begin.x; 236 } 237 238 /// The beginning of the figure along the y-axis. 239 @property T y() inout 240 { 241 return begin.y; 242 } 243 244 /// The beginning of the figure along the x-axis. 245 @property T x(T value) 246 { 247 return begin.x = value; 248 } 249 250 /// The beginning of the figure along the y-axis. 251 @property T y(T value) 252 { 253 return begin.y = value; 254 } 255 256 /// The end of the figure along the x-axis. 257 @property T endX() inout 258 { 259 return end.x; 260 } 261 262 /// The end of the figure along the y-axis. 263 @property T endY() inout 264 { 265 return end.y; 266 } 267 268 alias left = x; /// Rectangle left 269 alias right = endX; /// Rectangle right 270 alias top = y; /// Rectangle top 271 alias bottom = endY; /// Rectange bottom 272 273 /// The radius the figure. 274 @property T radius() inout 275 in(type == ShapeType.circle || type == ShapeType.roundrect,"This is not a circle!") 276 do 277 { 278 return _radius; 279 } 280 281 /// ditto 282 @property T radius(T value) 283 in(type == ShapeType.circle || type == ShapeType.roundrect,"This is not a circle!") 284 do 285 { 286 return _radius = value; 287 } 288 289 /// The top of the triangle. 290 @property Vector!T vertex(uint num)() inout 291 in 292 { 293 assert(type == ShapeType.triangle,"This is not a triangle!"); 294 }do 295 { 296 static assert(num < 3,"The triangle has only three vertices! (0 .. 2)"); 297 298 static if(num == 0) 299 return begin; 300 else 301 static if(num == 1) 302 return end; 303 else 304 static if(num == 2) 305 return _trType; 306 } 307 308 /// Shape width 309 @property T width() inout 310 in(type == ShapeType.rectangle || type == ShapeType.roundrect,"This is not a rectangle!") 311 do 312 { 313 return end.x - begin.x; 314 } 315 316 /// Shape height 317 @property T height() inout 318 in(type == ShapeType.rectangle || type == ShapeType.roundrect,"This is not a rectangle!") 319 do 320 { 321 return end.y - begin.y; 322 } 323 324 /// Shape width 325 @property T width(T value) 326 in(type == ShapeType.rectangle || type == ShapeType.roundrect,"This is not a rectangle!") 327 do 328 { 329 _end = begin + Vector!T(value,height); 330 331 return value; 332 } 333 334 /// Shape height 335 @property T height(T value) 336 in(type == ShapeType.rectangle || type == ShapeType.roundrect,"This is not a rectangle!") 337 do 338 { 339 _end = begin + Vector!T(width, value); 340 341 return value; 342 } 343 344 /// The top of the triangle. 345 @property Vector!T[] vertexs() inout 346 { 347 return [begin, end, _trType]; 348 } 349 350 /// ditto 351 @property void vertex(uint num)(Vector!T value) 352 in 353 { 354 assert(type == ShapeType.triangle,"This is not a triangle!"); 355 }do 356 { 357 static assert(num < 3,"The triangle has only three vertices! (0 .. 2)"); 358 359 static if(num == 0) 360 begin = value; 361 else 362 static if(num == 1) 363 end = value; 364 else 365 static if(num == 2) 366 _trType = value; 367 } 368 369 /// Line length. 370 @property T length() inout 371 in(type == ShapeType.line,"This is not a line!") 372 do 373 { 374 import std.math : sqrt; 375 376 auto distX = begin.x - end.x; 377 auto distY = begin.y - end.y; 378 379 return sqrt((distX * distX) + (distY * distY)); 380 } 381 382 /// Line length. 383 @property void length(T value) 384 in(type == ShapeType.line,"This is not a line!") 385 do 386 { 387 import tida.angle; 388 389 auto dir = begin.pointDirection(end); 390 391 end = (vectorDirection(dir) * value); 392 } 393 394 @property Vector!T calculateSize() 395 { 396 switch (type) 397 { 398 case ShapeType.point: 399 return vec!T(1, 1); 400 401 case ShapeType.line: 402 return abs(end - begin); 403 404 case ShapeType.rectangle: 405 return abs(end - begin); 406 407 case ShapeType.circle: 408 return (begin + vec!T(radius, radius) * 2) - begin; 409 410 case ShapeType.triangle: 411 { 412 import std.algorithm : minElement, maxElement; 413 414 immutable objs = [vertex!0, vertex!1, vertex!2]; 415 immutable maxObj = objs.maxElement!"a.length"; 416 immutable minObj = objs.minElement!"a.length"; 417 418 return maxObj - minObj; 419 } 420 421 case ShapeType.roundrect: 422 return abs(end - begin); 423 424 case ShapeType.polygon: 425 { 426 import std.algorithm : minElement, maxElement; 427 428 immutable maxObj = data.maxElement!"a.length"; 429 immutable minObj = data.minElement!"a.length"; 430 431 return maxObj - minObj; 432 } 433 434 case ShapeType.multi: 435 { 436 import std.algorithm : sort; 437 import std.range : array; 438 439 Shape!T[] rectangles; 440 441 foreach (e; shapes) 442 { 443 rectangles ~= Shape!T.Rectangle(e.begin, e.begin + e.calculateSize); 444 } 445 446 rectangles.sort!((a, b) => a.x > b.x && a.y > b.y).array; 447 448 return rectangles[0].end - rectangles[$ - 1].begin; 449 } 450 451 default: 452 return vecZero!T; 453 } 454 } 455 456 /++ 457 Resizes as a percentage. 458 459 Params: 460 k = Percentage. 461 +/ 462 void scale(T k) 463 in(type != ShapeType.point) 464 do 465 { 466 import std.algorithm : each; 467 468 if(type != ShapeType.polygon) 469 end = end * k; 470 else 471 data.each!((ref e) => e = e * k); 472 } 473 474 /++ 475 Returns the shape as a polygon. 476 477 Params: 478 points = An array of polygon vertices. 479 pos = Polygon position. 480 481 Returns: 482 Polygon. 483 484 Example: 485 --- 486 auto triangle = Shape.Polygon([ 487 Vecf(64, 32), 488 Vecf(32, 64), 489 Vecf(96, 64) 490 ]); 491 --- 492 +/ 493 static Shape!T Polygon( Vector!T[] points, 494 Vector!T pos = vec!T(0,0)) 495 { 496 Shape!T shape; 497 498 shape.type = ShapeType.polygon; 499 shape.data = points; 500 shape.begin = pos; 501 502 return shape; 503 } 504 505 static Shape!T PolygonLine( Vector!T[] points, 506 Vector!T pos = vec!T(0,0)) 507 { 508 Shape!T shape; 509 510 shape.type = ShapeType.multi; 511 512 int next = 0; 513 for(int current = 0; current < points.length; current++) 514 { 515 next = current + 1; 516 517 if (next == points.length) next = 0; 518 519 shape.shapes ~= Shape!float.Line( 520 pos + points[current], 521 pos + points[next] 522 ); 523 } 524 525 return shape; 526 } 527 528 /++ 529 Collects several figures into one. 530 531 Params: 532 shapes = A collection of figures. 533 pos = The relative position of such a figure. 534 535 Returns: 536 A shapes assembled from many shapes. 537 538 Example: 539 --- 540 auto multi = Shape.Mutli([ 541 Shape.Line(Vecf(0,0), Vecf(32,32)), 542 Shape.Rectangle(Vecf(32,0), Vecf(64, 32)), 543 ... 544 ]); 545 --- 546 +/ 547 static Shape!T Multi( Shape!T[] shapes, 548 Vector!T pos = vec!T(0, 0)) 549 { 550 Shape!T shape; 551 552 shape.type = ShapeType.multi; 553 shape.shapes = shapes; 554 shape.begin = pos; 555 556 return shape; 557 } 558 559 /++ 560 Creates a point. 561 562 Params: 563 point = Point position. 564 565 Returns: 566 Point. 567 +/ 568 static Shape!T Point(Vector!T point) 569 { 570 Shape!T shape; 571 572 shape.type = ShapeType.point; 573 shape.begin = point; 574 575 return shape; 576 } 577 578 /++ 579 Creates a line from two points. 580 581 Params: 582 begin = Line begin. 583 end = Line end. 584 585 Returns: 586 Line. 587 +/ 588 static Shape!T Line(Vector!T begin, 589 Vector!T end) 590 { 591 Shape!T shape; 592 593 shape.type = ShapeType.line; 594 shape.begin = begin; 595 shape.end = end; 596 597 return shape; 598 } 599 600 /++ 601 Creates a rectangle. 602 603 Params: 604 begin = Rectangle begin. 605 end = Rectangle end. 606 607 Returns: 608 Rectangle. 609 +/ 610 static Shape!T Rectangle( Vector!T begin, 611 Vector!T end) 612 { 613 Shape!T shape; 614 615 shape.type = ShapeType.rectangle; 616 shape.begin = begin; 617 shape.end = end; 618 619 return shape; 620 } 621 622 static Shape!T RoundRectangle( Vector!T begin, 623 Vector!T end, 624 T radius) 625 { 626 Shape!T shape; 627 628 shape.type = ShapeType.roundrect; 629 shape.begin = begin; 630 shape.end = end; 631 shape.radius = radius; 632 633 return shape; 634 } 635 636 static Shape!T RoundRectangleLine( Vector!T begin, 637 Vector!T end, 638 T radius) 639 { 640 import std.math : cos, sin; 641 import tida.angle; 642 643 Shape!T shape; 644 645 shape.type = ShapeType.multi; 646 647 static if (isFloatingPoint!T) 648 immutable factor = 0.01; 649 else 650 immutable factor = 1; 651 652 immutable size = end - begin; 653 654 void rounded(vec!T c, float b, float e) 655 { 656 Vector!T currPoint; 657 for (T i = b; i <= e; i += factor) 658 { 659 T j = i.conv!(Degrees, Radians); 660 currPoint = begin + c + vec!T(cos(j), sin(j)) * radius; 661 662 i += factor; 663 j = i.conv!(Degrees, Radians); 664 shape.shapes ~= Shape!T.Line( currPoint, 665 begin + c + vec!T(cos(j), sin(j)) * radius); 666 } 667 } 668 669 rounded(vec!T(radius, radius), 180, 270); 670 shape.shapes ~= Shape!T.Line( begin + vec!T(radius, 0), 671 begin + vec!T(size.x - radius, 0)); 672 673 rounded(vec!T(size.x - radius, radius), 270, 360); 674 shape.shapes ~= Shape!T.Line( begin + vec!T(size.x, radius), 675 end - vec!T(0, radius)); 676 677 rounded(vec!T(size.x - radius, size.y - radius), 0, 90); 678 shape.shapes ~= Shape!T.Line( end - vec!T(radius, 0), 679 begin + vec!T(radius, size.y)); 680 681 rounded(vec!T(radius, size.y - radius), 90, 180); 682 shape.shapes ~= Shape!T.Line( begin + vec!T(0, size.y - radius), 683 begin + vec!T(0, radius)); 684 685 return shape; 686 } 687 688 /++ 689 Create a non-solid rectangle. 690 691 Params: 692 begin = Rectangle begin. 693 end = Rectangle end. 694 695 Returns: 696 Non-solid rectangle 697 +/ 698 static Shape!T RectangleLine( Vector!T begin, 699 Vector!T end) 700 { 701 return Shape!T.Multi([ 702 Shape!T.Line(begin, vec!T(end.x, begin.y)), 703 Shape!T.Line(vec!T(end.x, begin.y), end), 704 Shape!T.Line(end, vec!T(begin.x, end.y)), 705 Shape!T.Line(vec!T(begin.x, end.y), begin) 706 ], vec!T(0, 0)); 707 } 708 709 /++ 710 Creates a circle. 711 712 Params: 713 pos = Circle position. 714 r = Circle radius. 715 716 Returns: 717 Circle. 718 +/ 719 static Shape!T Circle(Vector!T pos, T r) 720 { 721 Shape!T shape; 722 723 shape.type = ShapeType.circle; 724 shape.begin = pos; 725 shape.radius = r; 726 727 return shape; 728 } 729 730 static Shape!T CircleLine(Vector!T pos, T r) 731 { 732 import std.math : cos, sin; 733 734 static if (isFloatingPoint!T) 735 immutable factor = 0.25; 736 else 737 immutable factor = 1; 738 739 Shape!T shape; 740 741 shape.type = ShapeType.multi; 742 Vector!T currPoint; 743 744 for (T i = 0; i <= 360;) 745 { 746 currPoint = pos + vec!T(cos(i), sin(i)) * r; 747 i += factor; 748 749 shape.shapes ~= Shape.Line(currPoint, pos + vec!T(cos(i), sin(i)) * r); 750 i += factor; 751 } 752 753 return shape; 754 } 755 756 /++ 757 Creates a triangle. 758 759 Params: 760 vertexs = Triangle vertexs. 761 762 Returns: 763 Tringle. 764 +/ 765 static Shape!T Triangle(Vector!T[3] vertexs) 766 { 767 Shape!T shape; 768 769 shape.type = ShapeType.triangle; 770 shape.vertex!0 = vertexs[0]; 771 shape.vertex!1 = vertexs[1]; 772 shape.vertex!2 = vertexs[2]; 773 774 return shape; 775 } 776 777 /++ 778 Creates a non-filled triangle. 779 780 Params: 781 vertexs = Triangle vertexs. 782 783 Returns: 784 Tringle. 785 +/ 786 static Shape!T TriangleLine(Vector!T[3] vertexs) 787 { 788 Shape!T shape; 789 790 shape.type = ShapeType.multi; 791 shape.shapes = [ 792 Shape.Line(vertexs[0], vertexs[1]), 793 Shape.Line(vertexs[1], vertexs[2]), 794 Shape.Line(vertexs[2], vertexs[0]) 795 ]; 796 797 return shape; 798 } 799 800 /++ 801 Create a square. 802 803 Params: 804 pos = Square position. 805 len = Square length; 806 807 Returns: 808 Square (ShapeType: Rectangle). 809 +/ 810 static Shape!T Square(Vector!T pos, T len) 811 { 812 return Shape!T.Rectangle(pos,pos + vec!T(len, len)); 813 } 814 } 815 816 import tida.vector; 817 818 Shape!T rectVertexs(T)(Vector!T[] vertexArray) @safe nothrow pure 819 { 820 import std.algorithm : maxElement, minElement; 821 822 Shape!T shape = Shape!T.Rectangle(vecNaN!T, vecNaN!T); 823 824 shape.begin = vec!T(vertexArray.minElement!"a.x".x, shape.begin.y); 825 shape.end = vec!T(vertexArray.maxElement!"a.x".x, shape.end.y); 826 shape.begin = vec!T(shape.begin.x, vertexArray.minElement!"a.y".y); 827 shape.end = vec!T(shape.end.x, vertexArray.maxElement!"a.y".y); 828 829 return shape; 830 } 831 832 /++ 833 Checks if the given type is a shape 834 +/ 835 template isShape(T) 836 { 837 enum isShape = __traits(hasMember, T, "type") && 838 __traits(hasMember, T, "shape") && 839 __traits(hasMember, T, "data"); 840 } 841 842 /++ 843 Checks if the shape is integers. 844 +/ 845 template isShapeIntegral(T) 846 if (isShape!T) 847 { 848 enum isShapeIntegral = isIntegral!(T.Type); 849 } 850 851 /++ 852 Checks if the shape is float. 853 +/ 854 template isShapeFloatingPoint(T) 855 if (isShape!T) 856 { 857 enum isShapeFloatingPoint = isFloatingPoint!(T.Type); 858 } 859 860 alias Shapef = Shape!float;