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;