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 }