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;