1 module dkh.geo.primitive; 2 3 import std.traits; 4 5 template EPS(R) { 6 R EPS; 7 } 8 9 /** 10 -1: negative 11 0: almost 0 12 1: positive 13 */ 14 int sgn(R)(R a) { 15 static if (isFloatingPoint!R) { 16 import std.math : isNaN; 17 assert(!isNaN(EPS!R)); 18 } 19 if (a < -EPS!R) return -1; 20 if (a > EPS!R) return 1; 21 return 0; 22 } 23 24 struct Point2D(T) { 25 T[2] d; 26 this(T x, T y) {this.d = [x, y];} 27 this(T[2] d) {this.d = d;} 28 @property ref inout(T) x() inout {return d[0];} 29 @property ref inout(T) y() inout {return d[1];} 30 ref inout(T) opIndex(size_t i) inout {return d[i];} 31 auto opOpAssign(string op)(in Point2D r) { 32 return mixin("this=this"~op~"r"); 33 } 34 auto opBinary(string op:"+")(in Point2D r) const {return Point2D(x+r.x, y+r.y);} 35 auto opBinary(string op:"-")(in Point2D r) const {return Point2D(x-r.x, y-r.y);} 36 static if (isFloatingPoint!T) { 37 T abs() { 38 import std.math : sqrt; 39 return (x*x+y*y).sqrt; 40 } 41 T arg() { 42 import std.math : atan2; 43 return atan2(y, x); 44 } 45 Point2D rot(T ar) { 46 import std.math : cos, sin; 47 auto cosAr = cos(ar), sinAr = sin(ar); 48 return Point2D(x*cosAr - y*sinAr, x*sinAr + y*cosAr); 49 } 50 } 51 } 52 53 int robustCmp(T)(Point2D!T a, Point2D!T b) { 54 if (sgn(a.x-b.x)) return sgn(a.x-b.x); 55 if (sgn(a.y-b.y)) return sgn(a.y-b.y); 56 return 0; 57 } 58 59 bool near(T)(Point2D!T a, Point2D!T b) if (isIntegral!T) { 60 return a == b; 61 } 62 63 bool near(T)(Point2D!T a, Point2D!T b) if (isFloatingPoint!T) { 64 return !sgn((a-b).abs); 65 } 66 67 T dot(T)(in Point2D!T l, in Point2D!T r) { 68 return l[0]*r[0] + l[1]*r[1]; 69 } 70 71 T cross(T)(in Point2D!T l, in Point2D!T r) { 72 return l[0]*r[1] - l[1]*r[0]; 73 } 74 75 76 unittest { 77 alias P = Point2D!int; 78 P x = P(1, 2); 79 P y = P(3, 4); 80 assert((x+y).x == 4); 81 assert((x+y)[1] == 6); 82 assert(dot(x, y) == 11); 83 assert(cross(x, y) == -2); 84 } 85 86 87 int ccw(R)(Point2D!R a, Point2D!R b, Point2D!R c) { 88 import std.stdio; 89 assert(!near(a, b)); 90 if (near(a, c) || near(b, c)) return 0; 91 int s = sgn(cross(b-a, c-a)); 92 if (s) return s; 93 if (dot(b-a, c-a) < 0) return 2; 94 if (dot(a-b, c-b) < 0) return -2; 95 return 0; 96 } 97 98 99 struct Line2D(R) { 100 Point2D!R x, y; 101 this(Point2D!R x, Point2D!R y) { 102 this.x = x; 103 this.y = y; 104 } 105 Point2D!R vec() const { return y-x; } 106 } 107 108 R distLP(R)(in Line2D!R l, in Point2D!R p) if (isFloatingPoint!R) { 109 import std.math : abs; 110 return abs(cross(l.vec, p-l.x) / l.vec.abs); 111 } 112 R distSP(R)(in Line2D!R s, in Point2D!R p) if (isFloatingPoint!R) { 113 import std.algorithm : min; 114 auto s2 = Point2D!R(-s.vec.y, s.vec.x); 115 if (ccw(s.x, s.x+s2, p) == 1) return (s.x-p).abs; 116 if (ccw(s.y, s.y+s2, p) == -1) return (s.y-p).abs; 117 return min((s.x-p).abs, (s.y-p).abs, distLP(s, p)); 118 } 119 120 unittest { 121 import std.math; 122 alias P = Point2D!double; 123 alias L = Line2D!double; 124 EPS!double = 1e-6; 125 assert(approxEqual( 126 distLP(L(P(-1, 0), P(1, 0)), P(0, 1)), 127 1.0 128 )); 129 assert(approxEqual( 130 distSP(L(P(-1, 0), P(1, 0)), P(-2, 1)), 131 sqrt(2.0) 132 )); 133 } 134 135 // cmp by argment, precise 136 // (-PI, PI], (-1, 0)=PI, (0, 0)=0, (1, 0) = 0 137 int argcmp(T)(Point2D!T l, Point2D!T r) if (isIntegral!T) { 138 int sgn(Point2D!T p) { 139 if (p[1] < 0) return -1; 140 if (p[1] > 0) return 1; 141 if (p[0] < 0) return 2; 142 return 0; 143 } 144 int lsgn = sgn(l); 145 int rsgn = sgn(r); 146 if (lsgn < rsgn) return -1; 147 if (lsgn > rsgn) return 1; 148 149 T x = cross(l, r); 150 if (x > 0) return -1; 151 if (x < 0) return 1; 152 153 return 0; 154 } 155 156 157 unittest { 158 import std.math, std.random; 159 int naive(Point2D!double l, Point2D!double r) { 160 double la, ra; 161 if (abs(l[1]) < 1e-9) { 162 if (l[0] < -(1e-9)) la = PI; 163 else la = 0; 164 } else { 165 la = l.arg; 166 } 167 if (abs(r[1]) < 1e-9) { 168 if (r[0] < -(1e-9)) ra = PI; 169 else ra = 0; 170 } else { 171 ra = r.arg; 172 } 173 174 if (abs(la-ra) < 1e-9) return 0; 175 if (la < ra) return -1; 176 return 1; 177 } 178 foreach (ya; -10..10) { 179 foreach (yb; -10..10) { 180 foreach (xa; -10..10) { 181 foreach (xb; -10..10) { 182 auto pa = Point2D!long(xa, ya); 183 auto pb = Point2D!long(xb, yb); 184 auto qa = Point2D!double(xa, ya); 185 auto qb = Point2D!double(xb, yb); 186 assert(argcmp(pa, pb) == naive(qa, qb)); 187 } 188 } 189 } 190 } 191 }