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