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;