1 /**
2 Contains various generic geometry containers
3 
4 floathe entire module is designed for 2D, because libdgt is for 2D development
5 */
6 module dgt.geom;
7 
8 import std.algorithm.comparison;
9 import std.math : approxEqual, sqrt, cos, sin, sgn, PI;
10 import dgt.io;
11 
12 @safe:
13 
14 //The famous fast inverse square root
15 @trusted pure nothrow @nogc private float Q_rsqrt(float number)
16 {
17 	long i;
18 	float x2, y;
19 	const float threehalfs = 1.5;
20 	x2 = number * 0.5;
21 	y  = number;
22 	i  = *cast(long*) &y;
23 	i  = 0x5f3759df - ( i >> 1 );
24     y  = * cast(float*) &i;
25 	y  = y * ( threehalfs - ( x2 * y * y ) );
26     return y;
27 }
28 
29 /**
30 A 2D vector with an arbitrary numeric type
31 */
32 struct Vector
33 {
34     float x = 0, y = 0;
35 
36     @nogc nothrow void print() const
37     {
38         dgt.io.print("Vector(", x, ", ", y, ")");
39     }
40 
41     @nogc nothrow pure:
42     ///Create a vector with an x and a y
43     this(float x, float y)
44     {
45         this.x = x;
46         this.y = y;
47     }
48 
49     Vector opUnary(string op)() const
50     {
51         static if (op == "-")
52         {
53             return Vector(-x, -y);
54         }
55     }
56 
57     Vector opBinary(string op)(in float scalar) const
58     {
59         static if (op == "*")
60         {
61             return Vector(x * scalar, y * scalar);
62         }
63         static if (op == "/")
64         {
65             return Vector(x / scalar, y / scalar);
66         }
67     }
68 
69     Vector opBinary(string op)(in Vector other) const
70     {
71         static if (op == "+")
72         {
73             return Vector(x + other.x, y + other.y);
74         }
75         static if (op == "-")
76         {
77             return Vector(x - other.x, y - other.y);
78         }
79     }
80 
81     bool opEquals(Vector other) const
82     {
83         return approxEqual(x, other.x, 0.000001) && approxEqual(y, other.y, 0.000001);
84     }
85 
86     ///Get the squared length of the vector (faster than getting the length)
87     @property float len2() const
88     {
89         return x * x + y * y;
90     }
91 
92     ///Get the length of the vector
93     @property float len() const
94     {
95         return sqrt(len2());
96     }
97 
98     ///Clamp a vector somewhere between a minimum and a maximum
99     Vector clamp(in Vector min, in Vector max) const
100     {
101         return Vector(std.algorithm.comparison.clamp(x, min.x, max.x), std.algorithm.comparison.clamp(y, min.y, max.y));
102     }
103 
104 	///Get the cross product of a vector
105 	float cross(in Vector other) const
106     {
107         return x * other.y - y * other.x;
108     }
109 
110     ///Get the dot product of a vector
111     float dot(in Vector other) const
112     {
113         return x * other.x + y * other.y;
114     }
115 
116     ///Normalize the vector's length from [0, 1]
117     Vector normalize() const
118     {
119         return this * Q_rsqrt(len2);
120     }
121 
122     ///Get only the X component of the Vector, represented as a vector
123     @property Vector xComp() const
124     {
125         return Vector(x, 0);
126     }
127 
128     ///Get only the Y component of the Vector, represented as a vector
129     @property Vector yComp() const
130     {
131         return Vector(0, y);
132     }
133 
134     ///Get the vector equal to Vector(1 / x, 1 / y)
135     @property Vector inverse() const
136     {
137         return Vector(1 / x, 1 / y);
138     }
139 }
140 
141 unittest
142 {
143     Vector a, b;
144     a = Vector(5, 10);
145     b = Vector(1, -2);
146     assert((a + b).x == 6);
147     assert((a - b).y == 12);
148 }
149 
150 ///Represents a 2D line segment
151 struct Line
152 {
153     Vector start, end;
154 
155     @nogc nothrow pure: 
156 
157 	bool intersects(in Line other) const
158 	{
159         //See https://stackoverflow.com/a/565282 for algorithm
160 		Vector p = start;
161 		Vector q = other.start;
162 		Vector r = end - start;
163 		Vector s = other.end - other.start;
164 		//t = (q - p) x s / (r x s)
165 		//u = (q - p) x r / (r x s)
166 		float u_numerator = (q - p).cross(r);
167         float t_numerator = (q - p).cross(s);
168         float denominator = r.cross(s);
169         if (denominator == 0) {
170 			if(u_numerator != 0)
171 				return false;
172 			float t0 = (q - p).dot(r) / r.dot(r);
173 			float t1 = t0 + s.dot(r) / r.dot(r);
174 			return (t0 >= 0 && t0 <= 1) || (t1 >= 0 && t1 <= 1) || 
175 				(sgn(t0) != sgn(t1));
176 		} else {
177 			float u = u_numerator / denominator;
178 			float t = t_numerator / denominator;
179 			return t >= 0 && t <= 1 && u >= 0 && u <= 1;
180 		}
181 	}
182 
183     ///Check if a line segment intersects a circle
184     bool intersects(in Circle c) const
185     {
186         Vector center = c.center;
187         Vector line = end - start; 
188         Vector dist = center - start;
189         Vector nor_line = line.normalize();
190         float product = dist.dot(nor_line);
191         // Find the closest point on the line to check
192         Vector check_point;
193         if(product <= 0) {
194             check_point = start;
195         } else if(product >= 1) {
196             check_point = end;
197         } else {
198             check_point = start + nor_line * product;
199         }
200         // Check to see if the closest point is within the radius
201         return (center - check_point).len2 < c.radius * c.radius;
202     }
203 
204     ///Check if a line segment intersects a rectangle
205     bool intersects(in Rectangle r) const
206     {
207         return r.contains(start) || r.contains(end) 
208             || Line(r.topLeft, r.topLeft + r.size.xComp).intersects(this) 
209             || Line(r.topLeft, r.topLeft + r.size.yComp).intersects(this) 
210             || Line(r.topLeft + r.size.xComp, r.topLeft + r.size).intersects(this)
211             || Line(r.topLeft + r.size.yComp, r.topLeft + r.size).intersects(this);
212     }
213 
214 }
215 
216 /**
217 An axis-aligned rectangle made of some numeric type
218 */
219 struct Rectangle
220 {
221     ///floathe top left of the rectangle
222     public Vector topLeft = Vector(0, 0);
223     ///floathe width and height of the rectangle
224     public Vector size = Vector(0, 0);
225 
226     @nogc nothrow void print() const
227     {
228         dgt.io.print("Rectangle(", x, ", ", y, ", ", width, ", ", height, ")");
229     }
230 
231     @nogc nothrow pure public:
232     ///Create a rectangle with the given dimension
233     this(in float x, in float y, in float width, in float height)
234     {
235         topLeft = Vector(x, y);
236         size = Vector(width, height);
237     }
238     ///Create a rectangle at 0, 0 with a given size
239     this(in float width, in float height)
240     {
241         this(0, 0, width, height);
242     }
243     ///Create a rectangle with a given top left and a given size
244     this(in Vector top, in Vector dimension)
245     {
246         topLeft = top;
247         size = dimension;
248     }
249 
250     @property float x() const { return topLeft.x; }
251     @property float x(float val) { return topLeft.x = val; }
252     @property float y() const { return topLeft.y; }
253     @property float y(float val) { return topLeft.y = val; }
254     @property float width() const { return size.x; }
255     @property float width(float val) { return size.x = val; }
256     @property float height() const { return size.y; }
257     @property float height(float val) { return size.y = val; }
258 
259     ///Checks if a point falls within the rectangle
260     bool contains(in Vector v) const
261     {
262         return v.x >= x && v.y >= y && v.x < x + width && v.y < y + height;
263     }
264 
265     ///Check if any of the area bounded by this rectangle is bounded by another
266     bool overlaps(in Rectangle b) const
267     {
268         return x < b.x + b.width && x + width > b.x && y < b.y + b.height && y + height > b.y;
269     }
270 
271     ///Check if any of the area bounded by this rectangle is bounded by a circle
272     bool overlaps(in Circle c) const
273     {
274         Vector closest;
275         if (c.x < x) {
276             closest.x = x;
277         } else if (c.x > x + width) {
278             closest.x = x + width;
279         } else {
280             closest.x = c.x;
281         }
282         if (c.y < y) {
283             closest.y = y;
284         } else if (c.y > y + height) {
285             closest.y = y + height;
286         } else {
287             closest.y = c.y;
288         }
289         closest.x = closest.x - c.x;
290         closest.y = closest.y - c.y;
291         return (closest.x * closest.x) + (closest.y * closest.y) < c.radius * c.radius;
292     }
293 
294     ///Set the rectangle's dimensions
295     void set(float newX, float newY, float newWidth, float newHeight)
296     {
297         x = newX;
298         y = newY;
299         width = newWidth;
300         height = newHeight;
301     }
302 
303     /**
304     Move the rectangle so it is entirely contained with another
305 
306     If the rectangle is moved it will always be flush with a border of the given area
307     */
308     Rectangle constrain(in Rectangle outer) const
309     {
310         return Rectangle(topLeft.clamp(outer.topLeft, outer.topLeft + outer.size - size), size);
311     }
312 
313     ///Translate the rectangle by a vector
314     Rectangle translate(in Vector vec) const
315     {
316         return Rectangle(x + vec.x, y + vec.y, width, height);
317     }
318 }
319 
320 unittest
321 {
322     Rectangle a, b, c;
323     a = Rectangle(0, 0, 32, 32);
324     b = Rectangle(16, 16, 32, 32);
325     c = Rectangle(50, 50, 5, 5);
326     assert(a.overlaps(b));
327     assert(!a.overlaps(c));
328 }
329 
330 /**
331 A circle with a center and a radius
332 */
333 struct Circle
334 {
335     public Vector center = Vector(0, 0);
336     public float radius = 0;
337 
338     @nogc nothrow void print() const
339     {
340         dgt.io.print("Circle(", x, ", ", y, ", ", radius, ")");
341     }
342 
343     @nogc nothrow pure public:
344     this(float x, float y, float radius)
345     {
346         center = Vector(x, y);
347         this.radius = radius;
348     }
349     @property float x() const { return center.x; }
350     @property float x(float val) { return center.x = val; }
351     @property float y() const { return center.y; }
352     @property float y(float val) { return center.y = val; }
353 
354     /**
355     Checks if a vector falls within the area bounded by a circle
356     */
357     bool contains(in Vector v) const
358     {
359         Vector dist = v - center;
360         return dist.len2 < radius * radius;
361     }
362 
363     /**
364     Checks to see if the circle and the rectangle share any area
365     */
366     bool overlaps(in Rectangle r) const
367     {
368         return r.overlaps(this);
369     }
370 
371     /**
372     Checks to see if the circles have any overlapping area
373     */
374     bool overlaps(in Circle c) const
375     {
376         float xDiff = x - c.x;
377         float yDiff = y - c.y;
378         float rad = radius + c.radius;
379         return xDiff * xDiff + yDiff * yDiff < rad * rad;
380     }
381 
382     /**
383     Sets the dimensions of a circle
384     */
385     void set(float newX, float newY, float newRadius)
386     {
387         x = newX;
388         y = newY;
389         radius = newRadius;
390     }
391 
392     ///floatranslate the circle by a given vector
393     Circle translate(Vector vec)
394     {
395         return Circle(x + vec.x, y + vec.y, radius);
396     }
397 }
398 
399 unittest
400 {
401     Circle a, b, c;
402     Rectangle d;
403     a.set(0, 0, 16);
404     b.set(5, 5, 4);
405     c.set(50, 50, 5);
406     d.set(10, 10, 10, 10);
407     assert(a.overlaps(b));
408     assert(!a.overlaps(c));
409     assert(a.overlaps(d));
410     assert(!c.overlaps(d));
411 }
412 
413 /**
414 A Transform 3x3 matrix to make transformations more efficient
415 */
416 struct Transform
417 {
418     private float[9] data = [
419         1, 0, 0,
420         0, 1, 0,
421         0, 0, 1
422     ];
423 
424     @nogc nothrow void print() const
425     {
426         dgt.io.print("Transform[");
427         for(size_t x = 0; x < 3; x++)
428         {
429             dgt.io.print("[");
430             for(size_t y = 0; y < 3; y++)
431             {
432                 dgt.io.print(this[x, y]);
433                 if(y != 2) dgt.io.print(", ");
434             }
435             dgt.io.print("]");
436             if(x != 2) dgt.io.print(", ");
437         }
438         dgt.io.print("]");
439     }
440 
441     @nogc nothrow pure:
442 
443     this(in float[9] data)
444     {
445         this.data = data;
446     }
447 
448     ///A pointer to the internal buffer to pass the matrix to C
449     float* ptr()
450     {
451         return &data[0];
452     }
453 
454     Transform opBinary(string op)(Transform other) const
455     if (op == "*")
456     {
457         Transform ret;
458         for (size_t i = 0; i < 3; i++) {
459             for (size_t j = 0; j < 3; j++) {
460                 ret[i, j] = 0;
461                 for (size_t k = 0; k < 3; k++) {
462                     ret[i, j] = ret[i, j] + this[k, j] * other[i, k];
463                 }
464             }
465         }
466         return ret;
467     }
468 
469     Vector opBinary(string op)(Vector other) const
470     if (op == "*")
471     {
472         return Vector(other.x * this[0, 0] + other.y * this[0, 1] + this[0, 2],
473             other.x * this[1, 0] + other.y * this[1, 1] + this[1, 2]);
474     }
475 
476     Transform opBinary(string op)(float scalar) const
477     if(op == "*")
478     {
479         Transform ret;
480         for(int i = 0; i < 3; i++)
481             for(int j = 0; j < 3; j++)
482                 ret[i, j] = this[i, j] * scalar;
483         return ret;
484     }
485 
486     float opIndex(size_t i, size_t j) const
487     {
488         return data[i * 3 + j];
489     }
490 
491     float opIndexAssign(float val, size_t i, size_t j)
492     {
493         return data[i * 3 + j] = val;
494     }
495 
496     ref Transform opAssign(float[9] array)
497     {
498         data = array;
499         return this;
500     }
501 
502     ///Flip the underlying matrix over the xy axis
503     @property Transform transpose() const
504     {
505         return Transform([
506             this[0, 0], this[0, 1], this[0, 2],
507             this[1, 0], this[1, 1], this[1, 2],
508             this[2, 0], this[2, 1], this[2, 2]
509         ]);
510     }
511 
512     /**
513     Find a 2x2 submatrix
514 
515     The row and column that (x, y) sits in will be eliminated
516     */
517     float[4] submatrix(int x, int y) const
518     {
519         float[4] matrix;
520         int index = 0;
521         for(int i = 0; i < 3; i++)
522         {
523             for(int j = 0; j < 3; j++)
524             {
525                 if(i == x || j == y)
526                     continue;
527                 else
528                 {
529                     matrix[index] = this[i, j];
530                     index++;
531                 }
532             }
533         }
534         return matrix;
535     }
536 
537     /**
538     Find a determinant of a 2x2 submatrix
539 
540     The row and column that (x, y) sits in will be ignored
541     */
542     float determinant(int x, int y) const
543     {
544         const sub = submatrix(x, y);
545         return sub[0] * sub[3] - (sub[1] * sub[2]);
546     }
547 
548     ///Find the determinant of the underlying matrix
549     float determinant() const
550     {
551         float sum = 0;
552         for(int i = 0; i < 3; i++)
553             sum += this[i, 0] * determinant(i, 0);
554         return sum;
555     }
556 
557     ///Find the inverse of the transform
558     @property Transform inverse() const
559     {
560         Transform other = this;
561         //Find the matrix of minors
562         for(int i = 0; i < 3; i++)
563             for(int j = 0; j < 3; j++)
564                 other[i, j] = this.determinant(i, j);
565         //Find the matrix of cofactors
566         for(int i = 0; i < 3; i++)
567             for(int j = 0; j < 3; j++)
568                 if(i != j && !(i == 0 && j == 2) && !(i == 2 && j == 0))
569                     other[i, j] = -other[i, j];
570         //Find the adjutant
571         other = other.transpose;
572         return other * (1 / this.determinant);
573     }
574 
575     ///Create an identity matrix
576     static pure Transform identity()
577     {
578         return Transform();
579     }
580 
581     ///Create a rotation matrix
582     static pure Transform rotate(in float angle)
583     {
584         float c = cos(angle * PI / 180);
585         float s = sin(angle * PI / 180);
586         return Transform([
587             c, -s, 0,
588             s, c, 0,
589             0, 0, 1
590         ]);
591     }
592 
593     ///Create a translation matrix
594     static pure Transform translate(in Vector vec)
595     {
596         return Transform([
597             1, 0, vec.x,
598             0, 1, vec.y,
599             0, 0, 1
600         ]);
601     }
602 
603     ///Create a scale matrix
604     static pure Transform scale(in Vector vec)
605     {
606         return Transform([
607             vec.x, 0, 0,
608             0, vec.y, 0,
609             0, 0, 1
610         ]);
611     }
612 }
613 
614 
615 @nogc nothrow:
616 unittest
617 {
618     auto vec = Vector(3, 5);
619     auto inverse = vec.inverse;
620     assert(approxEqual(inverse.x, 1.0 / 3, 0.00001) && 
621         approxEqual(inverse.y, 1.0 / 5, 0.00001));
622 }
623 unittest
624 {
625     auto vec = Vector(2, 4);
626     auto translate = Transform.scale(Vector(0.5, 0.5));
627     auto inverseTranslate = translate.inverse;
628     auto transformed = inverseTranslate * vec;
629     assert(transformed.x == 4 && transformed.y == 8);
630 }
631 unittest
632 {
633     Transform m, n;
634     n[0, 0] = 5;
635     assert(n.ptr[0] == 5);
636     auto result = m * n;
637     assert(result[0, 0] == 5);
638     m[0, 0] = 2;
639     result = m * n;
640     assert(result[0, 0] = 10);
641 }
642 unittest
643 {
644     auto trans = Transform.scale(Vector(2, 2));
645     auto vec = Vector(2, 5);
646     auto scaled = trans * vec;
647     assert(scaled.x == 4 && scaled.y == 10);
648 }
649 unittest
650 {
651     auto trans = Transform.translate(Vector(3, 4));
652     auto vec = Vector(1, 1);
653     vec = trans * vec;
654     assert(vec.x == 4 && vec.y == 5);
655 }
656 unittest
657 {
658     auto trans = Transform.identity() * Transform.translate(Vector()) 
659         * Transform.rotate(0) * Transform.scale(Vector(1, 1));
660     auto vec = trans * Vector(15, 12);
661     assert(vec.x == 15);
662 }
663 unittest
664 {
665     auto vec = Vector(5, 0);
666     assert(vec.len2 == 25);
667     assert(vec.len == 5);
668 }
669 unittest
670 {
671     auto vec = Vector(10, 10);
672     vec = vec / 2;
673     assert(vec.x == 5 && vec.y == 5);
674 }
675 unittest
676 {
677     auto circ = Circle(0, 0, 10);
678     auto vec1 = Vector(0, 0);
679     auto vec2 = Vector(11, 11);
680     assert(circ.contains(vec1));
681     assert(!circ.contains(vec2));
682 }
683 unittest
684 {
685     auto rect = Rectangle(0, 0, 32, 32);
686     auto vec1 = Vector(5, 5);
687     auto vec2 = Vector(33, 1);
688     assert(rect.contains(vec1));
689     assert(!rect.contains(vec2));
690 }
691 unittest
692 {
693     auto circ = Circle(0, 0, 5);
694     auto rec1 = Rectangle(0, 0, 2, 2);
695     auto rec2 = Rectangle(5, 5, 4, 4);
696     assert(circ.overlaps(rec1) && rec1.overlaps(circ));
697     assert(!circ.overlaps(rec2) && !rec2.overlaps(circ));
698 }
699 unittest
700 {
701     auto min = Vector(-10, -2);
702     auto max = Vector(5, 6);
703     auto a = Vector(-11, 3);
704     auto clamped = a.clamp(min, max);
705     assert(clamped.x == -10 && clamped.y == 3);
706     auto b = Vector(2, 8);
707     clamped = b.clamp(min, max);
708     assert(clamped.x == 2 && clamped.y == 6);
709 }
710 unittest
711 {
712     auto constraint = Rectangle(0, 0, 10, 10);
713     auto a = Rectangle(-1, 3, 5, 5);
714     auto b = Rectangle(4, 4, 8, 3);
715     a = a.constrain(constraint);
716     assert(a.x == 0 && a.y == 3);
717     b = b.constrain(constraint);
718     assert(b.x == 2 && b.y == 4);
719 }
720 
721 unittest
722 {
723     println("Should print a vector at 0, 0: ", Vector(0, 0));
724     println("Should print a circle at 0, 0 with a radius of 10: ", Circle(0, 0, 10));
725     println("Should print a rectangle at 0, 0, with a side of 5", Rectangle(0, 0, 5, 5));
726     println("Should print an identity matrix: ", Transform.identity());
727 }
728 unittest
729 {
730     auto a = Rectangle(10, 10, 5, 5);
731     auto b = Circle(10, 10, 5);
732     auto c = Vector(1, -1);
733     auto aTranslate = a.translate(c);
734     auto bTranslate = b.translate(c);
735     assert(aTranslate.y == a.y + c.y && aTranslate.y == a.y + c.y);
736     assert(bTranslate.x == b.x + c.x && bTranslate.y == b.y + c.y);
737 }
738 unittest
739 {
740     assert(Vector(6, 5).dot(Vector(2, -8)) == -28);
741 }
742 unittest
743 {
744     const line1 = Line(Vector(0, 0), Vector(32, 32));
745     const line2 = Line(Vector(0, 32), Vector(32, 0));
746     const line3 = Line(Vector(32, 32), Vector(64, 64));
747     const line4 = Line(Vector(100, 100), Vector(1000, 1000));
748     assert(line1.intersects(line2));
749     assert(line1.intersects(line3));
750     assert(!line2.intersects(line3));
751     assert(!line1.intersects(line4));
752     assert(!line2.intersects(line4));
753     assert(!line3.intersects(line4));
754     const rect = Rectangle(32, 32);
755     assert(line1.intersects(rect));
756     assert(line2.intersects(rect));
757     assert(line3.intersects(rect));
758     assert(!line4.intersects(rect));
759     const circ = Circle(0, 0, 33);
760     assert(line1.intersects(circ));
761     assert(line2.intersects(circ));
762     assert(!line3.intersects(circ));
763     assert(!line4.intersects(circ));
764 }