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 }