1 module sbylib.collision.narrow.capsulepolygon;
2 
3 import sbylib.collision.shape.polygon : CollisionPolygon;
4 import sbylib.collision.shape.capsule : CollisionCapsule;
5 import sbylib.math;
6 import std.typecons : Nullable, nullable;
7 
8 enum CapsulePolygonCollisionType {
9     Edge,
10     Face,
11     Penetrating,
12     Parallel
13 }
14 
15 struct CapsulePolygonResult {
16     vec3 _pushVector;
17     CapsulePolygonCollisionType type;
18 
19     vec3 pushVector(CollisionPolygon) {
20         return _pushVector;
21     }
22 
23     vec3 pushVector(CollisionCapsule) {
24         return -_pushVector;
25     }
26 }
27 
28 auto detect(Capsule : CollisionCapsule, Polygon : CollisionPolygon)
29     (Capsule capsule, Polygon plane) {
30     return detect(plane, capsule);
31 }
32 
33 Nullable!(CapsulePolygonResult) detect(Polygon : CollisionPolygon, Capsule : CollisionCapsule)
34     (Polygon plane, Capsule capsule) 
35     in (plane.vertices.length >= 3)
36 {
37     import std.algorithm : map, maxElement;
38     import std.math : abs;
39     import sbylib.collision.bounds.aabb : intersect;
40 
41     const p0 = plane.vertices[0];
42     const n = normalize(cross(plane.vertices[0] - plane.vertices[1], plane.vertices[1] - plane.vertices[2]));
43 
44     // exclude the obvious pattern
45     const d1 = dot(capsule.ends[0] - p0, n);
46     const d2 = dot(capsule.ends[1] - p0, n);
47     if (d1 > +capsule.radius && d2 > +capsule.radius) return typeof(return).init;
48     if (d1 < -capsule.radius && d2 < -capsule.radius) return typeof(return).init;
49     if (intersect(plane.getAABB(), capsule.getAABB()) is false) return typeof(return).init;
50 
51     const r = segPoly(capsule.ends, n, plane.vertices);
52     if (r.dist > capsule.radius) return typeof(return).init;
53 
54     const depth = capsule.ends[].map!(end => dot(end - p0, n)).maxElement + capsule.radius;
55     return nullable(CapsulePolygonResult(r.pushVector * depth, r.type));
56 }
57 
58 private auto segPoly(const vec3[2] ends, const vec3 n, const vec3[] ps) {
59     // 平行でないとき
60     //   線分が完全にポリゴン(平面)の片側に寄っている場合
61     //     線分の端点が面領域に入っているとき
62     //       1. 線分の端点とポリゴンの垂線ベクトル
63     //     線分の端点が面領域に入っていないとき
64     //       2. 辺と端点との最接近ベクトル
65     //   線分が平面の両側に存在している場合
66     //     線分がポリゴンを貫いているとき
67     //       3. 0ベクトル(線分がポリゴンを貫いている)
68     //     線分がポリゴンの横を通っているとき
69     //       4. 辺と線分との最接近ベクトル
70     // 平行なとき, 線分が点になっているとき
71     //   線分が完全にポリゴンの面領域に収まっているとき
72     //     5. 線分のどこかの点とポリゴンの垂線ベクトル
73     //   線分がポリゴンの面領域からはみ出ているとき
74     //     6. 辺と線分との最接近ベクトル
75 
76     // 距離が正のとき、つまり線分がポリゴンを貫いていないときはめり込み解消ベクトル=最小距離ベクトルになるが、
77     // 貫いているときはめりこみ解消ベクトルは異なる
78     // このとき、ベクトルの候補は
79     //   1. ポリゴンの法線
80     //   2. 辺と線分の外積
81     // なお、返り値の法線は必ずポリゴンの表方向にしか押し出さないとする
82 
83     import std.algorithm : map, all, clamp, reduce, minElement;
84     import std.range : iota;
85     import std.math : abs;
86 
87     struct PolySegResult {
88         float dist;
89         vec3 pushVector;
90         CapsulePolygonCollisionType type;
91     }
92 
93     const v = ends[1] - ends[0];
94     const denom = dot(v, n);
95 
96     alias min = (a, b) => a.lengthSq < b.lengthSq ? a : b;
97 
98     if (denom != 0) {
99         const t1 = clamp(dot(ps[0] - ends[0], n) / denom, 0, 1);
100         const p = ends[0] + t1 * v;
101         auto ss = ps.length.iota.map!(i => dot(n, cross(ps[(i+1)%$] - ps[i], ps[i] - p)));
102         if (ss.all!(s => s > 0) || ss.all!(s => s < 0)) {
103             if (t1 == 0 || t1 == 1) {
104                 // 線分が片側に寄っているとき
105                 // 1
106                 const dist = abs(dot(ps[0] - p, n));
107                 return PolySegResult(dist, n, CapsulePolygonCollisionType.Face);
108             } else {
109                 // 3
110                 return PolySegResult(0, n, CapsulePolygonCollisionType.Penetrating);
111             }
112         } else {
113             // 2, 4
114             const r = ps.length.iota.map!(i => segseg(ends, [ps[i], ps[(i+1)%$]])).minElement!(v => v.length);
115             return PolySegResult(r.length, n, CapsulePolygonCollisionType.Edge);
116         }
117     } else {
118         auto ss0 = ps.length.iota.map!(i => dot(n, cross(ps[(i+1)%$] - ps[i], ps[i] - ends[0])));
119         auto ss1 = ps.length.iota.map!(i => dot(n, cross(ps[(i+1)%$] - ps[i], ps[i] - ends[1])));
120         const sInFaceRegion = ss0.all!(s => s > 0) || ss0.all!(s => s < 0);
121         const eInFaceRegion = ss1.all!(s => s > 0) || ss1.all!(s => s < 0);
122         if (sInFaceRegion && eInFaceRegion) {
123             //ポリゴンは凸形状なので、端点が両方とも面領域に入っていれば全体が面領域に入っている
124             // 5
125             const dist = abs(dot(ps[0] - ends[0], n));
126             return PolySegResult(dist, n, CapsulePolygonCollisionType.Parallel);
127         } else {
128             // 6
129             const r = ps.length.iota.map!(i => segseg(ends, [ps[i], ps[(i+1)%$]])).reduce!min;
130             return PolySegResult(r.length, n, CapsulePolygonCollisionType.Parallel);
131         }
132     }
133 }
134 
135 private vec3 segseg(const vec3[2] ends0, const vec3[2] ends1) {
136     import std.algorithm : clamp, minElement;
137 
138     if (ends0[0] == ends0[1]) {
139         if (ends1[0] == ends1[1]) {
140             // point & point
141             return ends0[0] - ends1[0];
142         } else {
143             // point & line
144             return -segPoint(ends1, ends0[0]);
145         }
146     } else {
147         if (ends1[0] == ends1[1]) {
148             // point & line
149             return +segPoint(ends0, ends1[0]);
150         }
151     }
152     const v1 = normalize(ends0[1] - ends0[0]);
153     const v2 = normalize(ends1[1] - ends1[0]);
154     const d1 = dot(ends1[0] - ends0[0], v1);
155     const d2 = dot(ends1[0] - ends0[0], v2);
156     const dv = dot(v1, v2);
157     float denom = 1 - dv * dv;
158     if (denom > 0) {
159         denom = 1 / denom;
160         const t1 = (d1 - dv * d2) * denom;
161         const t2 = (d1 * dv - d2) * denom;
162         if (0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1) {
163             // line & line
164             const p1 = ends0[0] + t1 * v1;
165             const p2 = ends1[0] + t2 * v2;
166             return p1 - p2;
167         }
168         return [segPoint(ends0, ends1[0]), segPoint(ends0, ends1[1]), segPoint(ends1, ends0[0]), segPoint(ends1, ends0[1])]
169             .minElement!(v => length(v));
170     }
171     // parallel
172     vec3 v = ends0[0] - ends1[0];
173     v -= dot(v, v1) * v1;
174     return v;
175 }
176 
177 private vec3 segPoint(const vec3[2] ends, const vec3 p) {
178     return segPoint(ends[0], ends[1] - ends[0],p);
179 }
180 
181 private vec3 segPoint(const vec3 s, const vec3 v, const vec3 p) {
182     import std.algorithm : clamp;
183 
184     const l = v.length;
185     const vn = v / l;
186     const ps = p - s;
187     const t = dot(ps, vn);
188     const tc = clamp(t, 0, l);
189     return s + tc * vn - p;
190 }