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 }