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 void begin(Vector!T vec) 219 { 220 _begin = vec; 221 } 222 223 /// The end of the figure. 224 void end(Vector!T vec) @safe @property 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 _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 void x(T value) 246 { 247 begin.x = value; 248 } 249 250 /// The beginning of the figure along the y-axis. 251 @property void y(T value) 252 { 253 begin.x = 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 void radius(T value) 283 in(type == ShapeType.circle || type == ShapeType.roundrect,"This is not a circle!") 284 do 285 { 286 _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 void 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 332 /// Shape height 333 @property void height(T value) 334 in(type == ShapeType.rectangle || type == ShapeType.roundrect,"This is not a rectangle!") 335 do 336 { 337 _end = begin + Vector!T(width,value); 338 } 339 340 /// The top of the triangle. 341 @property Vector!T[] vertexs() inout 342 { 343 return [begin,end,_trType]; 344 } 345 346 /// ditto 347 @property void vertex(uint num)(Vector!T value) 348 in 349 { 350 assert(type == ShapeType.triangle,"This is not a triangle!"); 351 }do 352 { 353 static assert(num < 3,"The triangle has only three vertices! (0 .. 2)"); 354 355 static if(num == 0) 356 begin = value; 357 else 358 static if(num == 1) 359 end = value; 360 else 361 static if(num == 2) 362 _trType = value; 363 } 364 365 /// Line length. 366 @property T length() inout 367 in(type == ShapeType.line,"This is not a line!") 368 do 369 { 370 import std.math : sqrt; 371 372 auto distX = begin.x - end.x; 373 auto distY = begin.y - end.y; 374 375 return sqrt((distX * distX) + (distY * distY)); 376 } 377 378 /// Line length. 379 @property void length(T value) 380 in(type == ShapeType.line,"This is not a line!") 381 do 382 { 383 import tida.angle; 384 385 auto dir = begin.pointDirection(end); 386 387 end = (vectorDirection(dir) * value); 388 } 389 390 /++ 391 Resizes as a percentage. 392 393 Params: 394 k = Percentage. 395 +/ 396 void scale(T k) 397 in(type != ShapeType.point) 398 do 399 { 400 import std.algorithm : each; 401 402 if(type != ShapeType.polygon) 403 end = end * k; 404 else 405 data.each!((ref e) => e = e * k); 406 } 407 408 /++ 409 Returns the shape as a polygon. 410 411 Params: 412 points = An array of polygon vertices. 413 pos = Polygon position. 414 415 Returns: 416 Polygon. 417 418 Example: 419 --- 420 auto triangle = Shape.Polygon([ 421 Vecf(64, 32), 422 Vecf(32, 64), 423 Vecf(96, 64) 424 ]); 425 --- 426 +/ 427 static Shape!T Polygon( Vector!T[] points, 428 Vector!T pos = vec!T(0,0)) 429 { 430 Shape!T shape; 431 432 shape.type = ShapeType.polygon; 433 shape.data = points; 434 shape.begin = pos; 435 436 return shape; 437 } 438 439 /++ 440 Collects several figures into one. 441 442 Params: 443 shapes = A collection of figures. 444 pos = The relative position of such a figure. 445 446 Returns: 447 A shapes assembled from many shapes. 448 449 Example: 450 --- 451 auto multi = Shape.Mutli([ 452 Shape.Line(Vecf(0,0), Vecf(32,32)), 453 Shape.Rectangle(Vecf(32,0), Vecf(64, 32)), 454 ... 455 ]); 456 --- 457 +/ 458 static Shape!T Multi( Shape!T[] shapes, 459 Vector!T pos = vec!T(0, 0)) 460 { 461 Shape!T shape; 462 463 shape.type = ShapeType.multi; 464 shape.shapes = shapes; 465 shape.begin = pos; 466 467 return shape; 468 } 469 470 /++ 471 Creates a point. 472 473 Params: 474 point = Point position. 475 476 Returns: 477 Point. 478 +/ 479 static Shape!T Point(Vector!T point) 480 { 481 Shape!T shape; 482 483 shape.type = ShapeType.point; 484 shape.begin = point; 485 486 return shape; 487 } 488 489 /++ 490 Creates a line from two points. 491 492 Params: 493 begin = Line begin. 494 end = Line end. 495 496 Returns: 497 Line. 498 +/ 499 static Shape!T Line(Vector!T begin, 500 Vector!T end) 501 { 502 Shape!T shape; 503 504 shape.type = ShapeType.line; 505 shape.begin = begin; 506 shape.end = end; 507 508 return shape; 509 } 510 511 /++ 512 Creates a rectangle. 513 514 Params: 515 begin = Rectangle begin. 516 end = Rectangle end. 517 518 Returns: 519 Rectangle. 520 +/ 521 static Shape!T Rectangle( Vector!T begin, 522 Vector!T end) 523 { 524 Shape!T shape; 525 526 shape.type = ShapeType.rectangle; 527 shape.begin = begin; 528 shape.end = end; 529 530 return shape; 531 } 532 533 static Shape!T RoundRectangle( Vector!T begin, 534 Vector!T end, 535 T radius) 536 { 537 Shape!T shape; 538 539 shape.type = ShapeType.roundrect; 540 shape.begin = begin; 541 shape.end = end; 542 shape.radius = radius; 543 544 return shape; 545 } 546 547 static Shape!T RoundRectangleLine( Vector!T begin, 548 Vector!T end, 549 T radius) 550 { 551 import std.math : cos, sin; 552 import tida.angle; 553 554 Shape!T shape; 555 556 shape.type = ShapeType.multi; 557 558 static if (isFloatingPoint!T) 559 immutable factor = 0.01; 560 else 561 immutable factor = 1; 562 563 immutable size = end - begin; 564 565 void rounded(vec!T c, float b, float e) 566 { 567 Vector!T currPoint; 568 for (T i = b; i <= e; i += factor) 569 { 570 T j = i.conv!(Degrees, Radians); 571 currPoint = begin + c + vec!T(cos(j), sin(j)) * radius; 572 573 i += factor; 574 j = i.conv!(Degrees, Radians); 575 shape.shapes ~= Shape!T.Line( currPoint, 576 begin + c + vec!T(cos(j), sin(j)) * radius); 577 } 578 } 579 580 rounded(vec!T(radius, radius), 180, 270); 581 shape.shapes ~= Shape!T.Line( begin + vec!T(radius, 0), 582 begin + vec!T(size.x - radius, 0)); 583 584 rounded(vec!T(size.x - radius, radius), 270, 360); 585 shape.shapes ~= Shape!T.Line( begin + vec!T(size.x, radius), 586 end - vec!T(0, radius)); 587 588 rounded(vec!T(size.x - radius, size.y - radius), 0, 90); 589 shape.shapes ~= Shape!T.Line( end - vec!T(radius, 0), 590 begin + vec!T(radius, size.y)); 591 592 rounded(vec!T(radius, size.y - radius), 90, 180); 593 shape.shapes ~= Shape!T.Line( begin + vec!T(0, size.y - radius), 594 begin + vec!T(0, radius)); 595 596 return shape; 597 } 598 599 /++ 600 Create a non-solid rectangle. 601 602 Params: 603 begin = Rectangle begin. 604 end = Rectangle end. 605 606 Returns: 607 Non-solid rectangle 608 +/ 609 static Shape!T RectangleLine( Vector!T begin, 610 Vector!T end) 611 { 612 return Shape!T.Multi([ 613 Shape!T.Line(begin, vec!T(end.x, begin.y)), 614 Shape!T.Line(vec!T(end.x, begin.y), end), 615 Shape!T.Line(end, vec!T(begin.x, end.y)), 616 Shape!T.Line(vec!T(begin.x, end.y), begin) 617 ], vec!T(0, 0)); 618 } 619 620 /++ 621 Creates a circle. 622 623 Params: 624 pos = Circle position. 625 r = Circle radius. 626 627 Returns: 628 Circle. 629 +/ 630 static Shape!T Circle(Vector!T pos, T r) 631 { 632 Shape!T shape; 633 634 shape.type = ShapeType.circle; 635 shape.begin = pos; 636 shape.radius = r; 637 638 return shape; 639 } 640 641 static Shape!T CircleLine(Vector!T pos, T r) 642 { 643 import std.math : cos, sin; 644 645 static if (isFloatingPoint!T) 646 immutable factor = 0.25; 647 else 648 immutable factor = 1; 649 650 Shape!T shape; 651 652 shape.type = ShapeType.multi; 653 Vector!T currPoint; 654 655 for (T i = 0; i <= 360;) 656 { 657 currPoint = pos + vec!T(cos(i), sin(i)) * r; 658 i += factor; 659 660 shape.shapes ~= Shape.Line(currPoint, pos + vec!T(cos(i), sin(i)) * r); 661 i += factor; 662 } 663 664 return shape; 665 } 666 667 /++ 668 Creates a triangle. 669 670 Params: 671 vertexs = Triangle vertexs. 672 673 Returns: 674 Tringle. 675 +/ 676 static Shape!T Triangle(Vector!T[3] vertexs) 677 { 678 Shape!T shape; 679 680 shape.type = ShapeType.triangle; 681 shape.vertex!0 = vertexs[0]; 682 shape.vertex!1 = vertexs[1]; 683 shape.vertex!2 = vertexs[2]; 684 685 return shape; 686 } 687 688 /++ 689 Creates a non-filled triangle. 690 691 Params: 692 vertexs = Triangle vertexs. 693 694 Returns: 695 Tringle. 696 +/ 697 static Shape!T TriangleLine(Vector!T[3] vertexs) 698 { 699 Shape!T shape; 700 701 shape.type = ShapeType.multi; 702 shape.shapes = [ 703 Shape.Line(vertexs[0], vertexs[1]), 704 Shape.Line(vertexs[1], vertexs[2]), 705 Shape.Line(vertexs[2], vertexs[0]) 706 ]; 707 708 return shape; 709 } 710 711 /++ 712 Create a square. 713 714 Params: 715 pos = Square position. 716 len = Square length; 717 718 Returns: 719 Square (ShapeType: Rectangle). 720 +/ 721 static Shape!T Square(Vector!T pos, T len) 722 { 723 return Shape!T.Rectangle(pos,pos + vec!T(len, len)); 724 } 725 } 726 727 import tida.vector; 728 729 Shape!T rectVertexs(T)(Vector!T[] vertexArray) @safe nothrow pure 730 { 731 import std.algorithm : maxElement, minElement; 732 733 Shape!T shape = Shape!T.Rectangle(vecNaN!T, vecNaN!T); 734 735 shape.begin = vec!T(vertexArray.minElement!"a.x".x, shape.begin.y); 736 shape.end = vec!T(vertexArray.maxElement!"a.x".x, shape.end.y); 737 shape.begin = vec!T(shape.begin.x, vertexArray.minElement!"a.y".y); 738 shape.end = vec!T(shape.end.x, vertexArray.maxElement!"a.y".y); 739 740 return shape; 741 } 742 743 /++ 744 Checks if the given type is a shape 745 +/ 746 template isShape(T) 747 { 748 enum isShape = __traits(hasMember, T, "type") && 749 __traits(hasMember, T, "shape") && 750 __traits(hasMember, T, "data"); 751 } 752 753 /++ 754 Checks if the shape is integers. 755 +/ 756 template isShapeIntegral(T) 757 if (isShape!T) 758 { 759 enum isShapeIntegral = isIntegral!(T.Type); 760 } 761 762 /++ 763 Checks if the shape is float. 764 +/ 765 template isShapeFloatingPoint(T) 766 if (isShape!T) 767 { 768 enum isShapeFloatingPoint = isFloatingPoint!(T.Type); 769 } 770 771 alias Shapef = Shape!float;