1 /**
2  * 2D geometry math stuff
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.geometry;
15 
16 import std.traits;
17 import std.math;
18 
19 import ae.utils.math;
20 
21 enum TAU = 2*PI; /// τ=2π
22 
23 /// `sqrt` disambiguation for integers.
24 auto sqrtx(T)(T x)
25 {
26 	static if (is(T : long))
27 		return std.math.sqrt(cast(float)x);
28 	else
29 		return std.math.sqrt(x);
30 }
31 
32 auto dist (T)(T x, T y) { return sqrtx(x*x+y*y); } /// Cartesian distance from origin.
33 auto dist2(T)(T x, T y) { return       x*x+y*y ; } /// Square of Cartesian distance from origin.
34 
35 // *****************************************************************************
36 
37 // Intersection tests between various shapes.
38 
39 /// Point on a 2D plane.
40 struct Point(T)
41 {
42 	/// The coordinates.
43 	T x, y;
44 	void translate(T dx, T dy) { x += dx; y += dy; } ///
45 	Point!T getCenter() { return this; } ///
46 }
47 auto point(T...)(T args) { return Point!(CommonType!T)(args); } /// ditto
48 
49 /// Orthogonal rectangle.
50 struct Rect(T)
51 {
52 	/// The coordinates.
53 	T x0, y0, x1, y1;
54 	@property T w() { return x1-x0; } /// Width.
55 	@property void w(T value) { x1 = x0 + value; } /// Resize width by moving the second vertical edge.
56 	@property T h() { return y1-y0; } /// Height.
57 	@property void h(T value) { y1 = y0 + value; } /// Resize height by moving the second horizontal edge.
58 	void sort() { sort2(x0, x1); sort2(y0, y1); } /// Flip coordinates if needed, so that `sorted` is `true`.
59 	@property bool sorted() { return x0 <= x1 && y0 <= y1; } /// `x0<=x1 && y0<=y1`
60 	void translate(T dx, T dy) { x0 += dx; y0 += dy; x1 += dx; y1 += dy; } ///
61 	Point!T getCenter() { return Point!T(cast(T)average(x0, x1), cast(T)average(y0, y1)); } ///
62 }
63 auto rect(T...)(T args) { return Rect!(CommonType!T)(args); } /// ditto
64 
65 unittest
66 {
67 	Rect!int rint;
68 }
69 
70 /// Circle on a plane.
71 struct Circle(T)
72 {
73 	/// The coordinates and radius.
74 	T x, y, r;
75 	@property T diameter() { return 2*r; } /// 2r
76 	void translate(T dx, T dy) { x += dx; y += dy; } ///
77 	Point!T getCenter() { return Point!T(x, y); } ///
78 }
79 auto circle(T...)(T args) { return Circle!(CommonType!T)(args); } /// ditto
80 
81 /// Discriminated union between `Point`, `Rect` or `Circle`.
82 enum ShapeKind { none, /***/ point, /***/ rect, /***/ circle /***/ }
83 struct Shape(T)
84 {
85 	/// Wrapped shape.
86 	ShapeKind kind;
87 	union
88 	{
89 		Point!T point; ///
90 		Rect!T rect; ///
91 		Circle!T circle; ///
92 	} /// ditto
93 
94 	this(Point!T point)
95 	{
96 		this.kind = ShapeKind.point;
97 		this.point = point;
98 	} ///
99 
100 	this(Rect!T rect)
101 	{
102 		this.kind = ShapeKind.rect;
103 		this.rect = rect;
104 	} ///
105 
106 	this(Circle!T circle)
107 	{
108 		this.kind = ShapeKind.circle;
109 		this.circle = circle;
110 	} ///
111 
112 	/// Dispatches operations common to all shapes.
113 	auto opDispatch(string s, T...)(T args)
114 		if (is(typeof(mixin("point ." ~ s ~ "(args)"))) &&
115 		    is(typeof(mixin("rect  ." ~ s ~ "(args)"))) &&
116 		    is(typeof(mixin("circle." ~ s ~ "(args)"))))
117 	{
118 		switch (kind)
119 		{
120 			case ShapeKind.point:
121 				return mixin("point ." ~ s ~ "(args)");
122 			case ShapeKind.circle:
123 				return mixin("circle." ~ s ~ "(args)");
124 			case ShapeKind.rect:
125 				return mixin("rect  ." ~ s ~ "(args)");
126 			default:
127 				assert(0);
128 		}
129 	}
130 } /// ditto
131 auto shape(T)(T shape) { return Shape!(typeof(shape.tupleof[0]))(shape); } /// ditto
132 
133 /// Intersection test.
134 bool intersects(T)(Shape!T a, Shape!T b)
135 {
136 	switch (a.kind)
137 	{
138 		case ShapeKind.point:
139 			switch (b.kind)
140 			{
141 			case ShapeKind.point:
142 				return a.point.x == b.point.x && a.point.y == b.point.y;
143 			case ShapeKind.circle:
144 				return dist2(a.point.x-b.circle.x, a.point.y-b.circle.y) < sqr(b.circle.r);
145 			case ShapeKind.rect:
146 				assert(b.rect.sorted);
147 				return between(a.point.x, b.rect.x0, b.rect.x1) && between(a.point.y, b.rect.y0, b.rect.y1);
148 			default:
149 				assert(0);
150 			}
151 		case ShapeKind.circle:
152 			switch (b.kind)
153 			{
154 			case ShapeKind.point:
155 				return dist2(a.circle.x-b.point.x, a.circle.y-b.point.y) < sqr(a.circle.r);
156 			case ShapeKind.circle:
157 				return dist2(a.circle.x-b.circle.x, a.circle.y-b.circle.y) < sqr(a.circle.r+b.circle.r);
158 			case ShapeKind.rect:
159 				return intersects!T(a.circle, b.rect);
160 			default:
161 				assert(0);
162 			}
163 		case ShapeKind.rect:
164 			switch (b.kind)
165 			{
166 			case ShapeKind.point:
167 				assert(a.rect.sorted);
168 				return between(b.point.x, a.rect.x0, a.rect.x1) && between(b.point.y, a.rect.y0, a.rect.y1);
169 			case ShapeKind.circle:
170 				return intersects!T(b.circle, a.rect);
171 			case ShapeKind.rect:
172 				assert(0); // TODO
173 			default:
174 				assert(0);
175 			}
176 		default:
177 			assert(0);
178 	}
179 }
180 
181 /// ditto
182 bool intersects(T)(Circle!T circle, Rect!T rect)
183 {
184 	// http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
185 
186 	Point!T circleDistance;
187 
188 	auto hw = rect.w/2, hh = rect.h/2;
189 
190 	circleDistance.x = abs(circle.x - rect.x0 - hw);
191 	circleDistance.y = abs(circle.y - rect.y0 - hh);
192 
193 	if (circleDistance.x > (hw + circle.r)) return false;
194 	if (circleDistance.y > (hh + circle.r)) return false;
195 
196 	if (circleDistance.x <= hw) return true;
197 	if (circleDistance.y <= hh) return true;
198 
199 	auto cornerDistance_sq =
200 		sqr(circleDistance.x - hw) +
201 		sqr(circleDistance.y - hh);
202 
203 	return (cornerDistance_sq <= sqr(circle.r));
204 }