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 	Point!T opBinary(string op)(Point!T other) const
48 	{
49 		Point!T result = this;
50 		mixin(`result.x ` ~ op ~ `= other.x;`);
51 		mixin(`result.y ` ~ op ~ `= other.y;`);
52 		return result;
53 	} ///
54 	auto dist2() const { return .dist2(x, y); } /// Square of Cartesian distance from origin.
55 	auto dist() const { return .dist(x, y); } /// Cartesian distance from origin.
56 }
57 auto point(T...)(T args) { return Point!(CommonType!T)(args); } /// ditto
58 
59 /// Orthogonal rectangle.
60 struct Rect(T)
61 {
62 	/// The coordinates.
63 	T x0, y0, x1, y1;
64 	@property T w() { return x1-x0; } /// Width.
65 	@property void w(T value) { x1 = x0 + value; } /// Resize width by moving the second vertical edge.
66 	@property T h() { return y1-y0; } /// Height.
67 	@property void h(T value) { y1 = y0 + value; } /// Resize height by moving the second horizontal edge.
68 	void sort() { sort2(x0, x1); sort2(y0, y1); } /// Flip coordinates if needed, so that `sorted` is `true`.
69 	@property bool sorted() { return x0 <= x1 && y0 <= y1; } /// `x0<=x1 && y0<=y1`
70 	void translate(T dx, T dy) { x0 += dx; y0 += dy; x1 += dx; y1 += dy; } ///
71 	Point!T getCenter() { return Point!T(cast(T)average(x0, x1), cast(T)average(y0, y1)); } ///
72 }
73 auto rect(T...)(T args) { return Rect!(CommonType!T)(args); } /// ditto
74 
75 unittest
76 {
77 	Rect!int rint;
78 }
79 
80 /// Circle on a plane.
81 struct Circle(T)
82 {
83 	/// The coordinates and radius.
84 	T x, y, r;
85 	@property T diameter() { return 2*r; } /// 2r
86 	void translate(T dx, T dy) { x += dx; y += dy; } ///
87 	Point!T getCenter() { return Point!T(x, y); } ///
88 }
89 auto circle(T...)(T args) { return Circle!(CommonType!T)(args); } /// ditto
90 
91 /// Discriminated union between `Point`, `Rect` or `Circle`.
92 enum ShapeKind { none, /***/ point, /***/ rect, /***/ circle /***/ }
93 struct Shape(T)
94 {
95 	/// Wrapped shape.
96 	ShapeKind kind;
97 	union
98 	{
99 		Point!T point; ///
100 		Rect!T rect; ///
101 		Circle!T circle; ///
102 	} /// ditto
103 
104 	this(Point!T point)
105 	{
106 		this.kind = ShapeKind.point;
107 		this.point = point;
108 	} ///
109 
110 	this(Rect!T rect)
111 	{
112 		this.kind = ShapeKind.rect;
113 		this.rect = rect;
114 	} ///
115 
116 	this(Circle!T circle)
117 	{
118 		this.kind = ShapeKind.circle;
119 		this.circle = circle;
120 	} ///
121 
122 	/// Dispatches operations common to all shapes.
123 	auto opDispatch(string s, T...)(T args)
124 		if (is(typeof(mixin("point ." ~ s ~ "(args)"))) &&
125 		    is(typeof(mixin("rect  ." ~ s ~ "(args)"))) &&
126 		    is(typeof(mixin("circle." ~ s ~ "(args)"))))
127 	{
128 		switch (kind)
129 		{
130 			case ShapeKind.point:
131 				return mixin("point ." ~ s ~ "(args)");
132 			case ShapeKind.circle:
133 				return mixin("circle." ~ s ~ "(args)");
134 			case ShapeKind.rect:
135 				return mixin("rect  ." ~ s ~ "(args)");
136 			default:
137 				assert(0);
138 		}
139 	}
140 } /// ditto
141 auto shape(T)(T shape) { return Shape!(typeof(shape.tupleof[0]))(shape); } /// ditto
142 
143 /// Intersection test.
144 bool intersects(T)(Shape!T a, Shape!T b)
145 {
146 	switch (a.kind)
147 	{
148 		case ShapeKind.point:
149 			switch (b.kind)
150 			{
151 			case ShapeKind.point:
152 				return a.point.x == b.point.x && a.point.y == b.point.y;
153 			case ShapeKind.circle:
154 				return dist2(a.point.x-b.circle.x, a.point.y-b.circle.y) < sqr(b.circle.r);
155 			case ShapeKind.rect:
156 				assert(b.rect.sorted);
157 				return between(a.point.x, b.rect.x0, b.rect.x1) && between(a.point.y, b.rect.y0, b.rect.y1);
158 			default:
159 				assert(0);
160 			}
161 		case ShapeKind.circle:
162 			switch (b.kind)
163 			{
164 			case ShapeKind.point:
165 				return dist2(a.circle.x-b.point.x, a.circle.y-b.point.y) < sqr(a.circle.r);
166 			case ShapeKind.circle:
167 				return dist2(a.circle.x-b.circle.x, a.circle.y-b.circle.y) < sqr(a.circle.r+b.circle.r);
168 			case ShapeKind.rect:
169 				return intersects!T(a.circle, b.rect);
170 			default:
171 				assert(0);
172 			}
173 		case ShapeKind.rect:
174 			switch (b.kind)
175 			{
176 			case ShapeKind.point:
177 				assert(a.rect.sorted);
178 				return between(b.point.x, a.rect.x0, a.rect.x1) && between(b.point.y, a.rect.y0, a.rect.y1);
179 			case ShapeKind.circle:
180 				return intersects!T(b.circle, a.rect);
181 			case ShapeKind.rect:
182 				assert(0); // TODO
183 			default:
184 				assert(0);
185 			}
186 		default:
187 			assert(0);
188 	}
189 }
190 
191 /// ditto
192 bool intersects(T)(Circle!T circle, Rect!T rect)
193 {
194 	// http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
195 
196 	Point!T circleDistance;
197 
198 	auto hw = rect.w/2, hh = rect.h/2;
199 
200 	circleDistance.x = abs(circle.x - rect.x0 - hw);
201 	circleDistance.y = abs(circle.y - rect.y0 - hh);
202 
203 	if (circleDistance.x > (hw + circle.r)) return false;
204 	if (circleDistance.y > (hh + circle.r)) return false;
205 
206 	if (circleDistance.x <= hw) return true;
207 	if (circleDistance.y <= hh) return true;
208 
209 	auto cornerDistance_sq =
210 		sqr(circleDistance.x - hw) +
211 		sqr(circleDistance.y - hh);
212 
213 	return (cornerDistance_sq <= sqr(circle.r));
214 }