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