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 <vladimir@thecybershadow.net>
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;
22 
23 auto sqrtx(T)(T x)
24 {
25 	static if (is(T : int))
26 		return std.math.sqrt(cast(float)x);
27 	else
28 		return std.math.sqrt(x);
29 }
30 
31 auto dist (T)(T x, T y) { return sqrtx(x*x+y*y); }
32 auto dist2(T)(T x, T y) { return       x*x+y*y ; }
33 
34 struct Point(T)
35 {
36 	T x, y;
37 	void translate(T dx, T dy) { x += dx; y += dy; }
38 	Point!T getCenter() { return this; }
39 }
40 auto point(T...)(T args) { return Point!(CommonType!T)(args); }
41 
42 struct Rect(T)
43 {
44 	T x0, y0, x1, y1;
45 	@property T w() { return x1-x0; }
46 	@property void w(T value) { x1 = x0 + value; }
47 	@property T h() { return y1-y0; }
48 	@property void h(T value) { y1 = y0 + value; }
49 	void sort() { sort2(x0, x1); sort2(y0, y1); }
50 	@property bool sorted() { return x0 <= x1 && y0 <= y1; }
51 	void translate(T dx, T dy) { x0 += dx; y0 += dy; x1 += dx; y1 += dy; }
52 	Point!T getCenter() { return Point!T(cast(T)average(x0, x1), cast(T)average(y0, y1)); }
53 }
54 auto rect(T...)(T args) { return Rect!(CommonType!T)(args); }
55 
56 unittest
57 {
58 	Rect!int rint;
59 }
60 
61 struct Circle(T)
62 {
63 	T x, y, r;
64 	@property T diameter() { return 2*r; }
65 	void translate(T dx, T dy) { x += dx; y += dy; }
66 	Point!T getCenter() { return Point!T(x, y); }
67 }
68 auto circle(T...)(T args) { return Circle!(CommonType!T)(args); }
69 
70 enum ShapeKind { none, point, rect, circle }
71 struct Shape(T)
72 {
73 	ShapeKind kind;
74 	union
75 	{
76 		Point!T point;
77 		Rect!T rect;
78 		Circle!T circle;
79 	}
80 
81 	this(Point!T point)
82 	{
83 		this.kind = ShapeKind.point;
84 		this.point = point;
85 	}
86 
87 	this(Rect!T rect)
88 	{
89 		this.kind = ShapeKind.rect;
90 		this.rect = rect;
91 	}
92 
93 	this(Circle!T circle)
94 	{
95 		this.kind = ShapeKind.circle;
96 		this.circle = circle;
97 	}
98 
99 	auto opDispatch(string s, T...)(T args)
100 		if (is(typeof(mixin("point ." ~ s ~ "(args)"))) &&
101 		    is(typeof(mixin("rect  ." ~ s ~ "(args)"))) &&
102 		    is(typeof(mixin("circle." ~ s ~ "(args)"))))
103 	{
104 		switch (kind)
105 		{
106 			case ShapeKind.point:
107 				return mixin("point ." ~ s ~ "(args)");
108 			case ShapeKind.circle:
109 				return mixin("circle." ~ s ~ "(args)");
110 			case ShapeKind.rect:
111 				return mixin("rect  ." ~ s ~ "(args)");
112 			default:
113 				assert(0);
114 		}
115 	}
116 }
117 auto shape(T)(T shape) { return Shape!(typeof(shape.tupleof[0]))(shape); }
118 
119 bool intersects(T)(Shape!T a, Shape!T b)
120 {
121 	switch (a.kind)
122 	{
123 		case ShapeKind.point:
124 			switch (b.kind)
125 			{
126 			case ShapeKind.point:
127 				return a.point.x == b.point.x && a.point.y == b.point.y;
128 			case ShapeKind.circle:
129 				return dist2(a.point.x-b.circle.x, a.point.y-b.circle.y) < sqr(b.circle.r);
130 			case ShapeKind.rect:
131 				assert(b.rect.sorted);
132 				return between(a.point.x, b.rect.x0, b.rect.x1) && between(a.point.y, b.rect.y0, b.rect.y1);
133 			default:
134 				assert(0);
135 			}
136 		case ShapeKind.circle:
137 			switch (b.kind)
138 			{
139 			case ShapeKind.point:
140 				return dist2(a.circle.x-b.point.x, a.circle.y-b.point.y) < sqr(a.circle.r);
141 			case ShapeKind.circle:
142 				return dist2(a.circle.x-b.circle.x, a.circle.y-b.circle.y) < sqr(a.circle.r+b.circle.r);
143 			case ShapeKind.rect:
144 				return intersects!T(a.circle, b.rect);
145 			default:
146 				assert(0);
147 			}
148 		case ShapeKind.rect:
149 			switch (b.kind)
150 			{
151 			case ShapeKind.point:
152 				assert(a.rect.sorted);
153 				return between(b.point.x, a.rect.x0, a.rect.x1) && between(b.point.y, a.rect.y0, a.rect.y1);
154 			case ShapeKind.circle:
155 				return intersects!T(b.circle, a.rect);
156 			case ShapeKind.rect:
157 				assert(0); // TODO
158 			default:
159 				assert(0);
160 			}
161 		default:
162 			assert(0);
163 	}
164 }
165 
166 bool intersects(T)(Circle!T circle, Rect!T rect)
167 {
168 	// http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
169 
170 	Point!T circleDistance;
171 
172 	auto hw = rect.w/2, hh = rect.h/2;
173 
174 	circleDistance.x = abs(circle.x - rect.x0 - hw);
175 	circleDistance.y = abs(circle.y - rect.y0 - hh);
176 
177 	if (circleDistance.x > (hw + circle.r)) return false;
178 	if (circleDistance.y > (hh + circle.r)) return false;
179 
180 	if (circleDistance.x <= hw) return true;
181 	if (circleDistance.y <= hh) return true;
182 
183 	auto cornerDistance_sq =
184 		sqr(circleDistance.x - hw) +
185 		sqr(circleDistance.y - hh);
186 
187 	return (cornerDistance_sq <= sqr(circle.r));
188 }