some updates
[iv.d.git] / vmath2d / gjk.d
blob4b84308931ab9c8785ae2166ac2819639c9f7f66
1 /* Gilbert-Johnson-Keerthi intersection algorithm with Expanding Polytope Algorithm
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv.vmath2d.gjk;
19 import iv.alice;
20 import iv.vmath2d;
22 version = vm2d_debug_gjk_warnings;
23 //version = vm2d_debug_save_minkowski_points;
24 //version = vm2d_debug_count_iterations;
26 version(vm2d_debug_count_iterations) int gjkIterationCount, epaIterationCount;
29 // ////////////////////////////////////////////////////////////////////////// //
30 /* GJK object should support:
31 * Vec2 centroid
32 * Vec2 support (Vec2 dir) -- in world space
33 * bool inside (Vec2 pt) -- is point inside body?
35 * for polyhedra:
37 * // dumb O(n) support function, just brute force check all points
38 * vec2 support() (in auto ref vec2 dir) const {
39 * VT furthestPoint = verts[0];
40 * auto maxDot = furthestPoint.dot(dir);
41 * foreach (const ref v; verts[1..$]) {
42 * auto d = v.dot(dir);
43 * if (d > maxDot) {
44 * maxDot = d;
45 * furthestPoint = v;
46 * }
47 * }
48 * return furthestPoint;
49 * }
53 // ////////////////////////////////////////////////////////////////////////// //
54 /// check if two convex shapes are colliding. can optionally return separating vector in `sepmove`.
55 public bool gjkcollide(CT, VT) (auto ref CT body0, auto ref CT body1, VT* sepmove=null) if (IsGoodVertexStorage!(CT, VT)) {
56 VT sdir = body1.centroid-body0.centroid; // initial search direction
57 if (sdir.isZero) sdir = VT(1, 0); // use arbitrary normal if initial direction is zero
58 VT[3] spx = void; // simplex; [0] is most recently added, [2] is oldest
59 spx.ptr[2] = getSupportPoint(body0, body1, sdir);
60 if (spx.ptr[2].dot(sdir) <= 0) return false; // past the origin
61 sdir = -sdir; // change search direction
62 int spxidx = 1;
63 version(vm2d_debug_count_iterations) gjkIterationCount = 0;
64 for (;;) {
65 version(vm2d_debug_count_iterations) ++gjkIterationCount;
66 spx.ptr[spxidx] = getSupportPoint(body0, body1, sdir);
67 if (spx.ptr[spxidx].dot(sdir) <= 0) return false; // past the origin
68 if (checkSimplex(sdir, spx[spxidx..$])) {
69 if (sepmove !is null) *sepmove = EPA(body0, body1, spx[spxidx..$]);
70 return true;
72 spxidx = 0;
74 return false;
78 // ////////////////////////////////////////////////////////////////////////// //
79 /// return distance between two convex shapes, and separation normal
80 /// negative distance means that shapes are overlapping, and zero distance means touching (and ops are invalid)
81 public auto gjkdistance(CT, VT) (auto ref CT body0, auto ref CT body1, VT* op0=null, VT* op1=null, VT* sepnorm=null) if (IsGoodVertexStorage!(CT, VT)) {
82 static VT segClosestToOrigin() (in auto ref VT segp0, in auto ref VT segp1) {
83 immutable oseg = segp1-segp0;
84 immutable ab2 = oseg.dot(oseg);
85 immutable apab = (-segp0).dot(oseg);
86 if (ab2 <= EPSILON!(VT.Float)) return segp0;
87 VT.Float t = apab/ab2;
88 if (t < 0) t = 0; else if (t > 1) t = 1;
89 return segp0+oseg*t;
92 enum GetSupport(string smpx) =
93 smpx~"p0 = body0.support(d);\n"~
94 smpx~"p1 = body1.support(-d);\n"~
95 smpx~" = "~smpx~"p0-"~smpx~"p1;";
97 if (sepnorm !is null) *sepnorm = VT(0, 0);
98 if (op0 !is null) *op0 = VT(0, 0);
99 if (op1 !is null) *op1 = VT(0, 0);
101 VT a, b, c; // simplex
102 VT ap0, bp0, cp0; // simplex support points, needed for closest points calculation
103 VT ap1, bp1, cp1; // simplex support points, needed for closest points calculation
104 // centroid is centroid, use that fact
105 auto d = body1.centroid-body0.centroid;
106 // check for a zero direction vector
107 if (d.isZero) return cast(VT.Float)-1; // centroids are the same, not separated
108 //getSupport(a, ap0, ap1, d);
109 mixin(GetSupport!"a");
110 d = -d;
111 mixin(GetSupport!"b");
112 d = segClosestToOrigin(b, a);
113 version(vm2d_debug_count_iterations) gjkIterationCount = 0;
114 foreach (immutable iter; 0..32) {
115 if (d.lengthSquared <= EPSILON!(VT.Float)*EPSILON!(VT.Float)) return cast(VT.Float)-1; // if the closest point is the origin, not separated
116 version(vm2d_debug_count_iterations) ++gjkIterationCount;
117 d = -d;
118 mixin(GetSupport!"c");
119 // is simplex triangle contains origin?
120 immutable sa = a.cross(b);
121 if (sa*b.cross(c) > 0 && sa*c.cross(a) > 0) return cast(VT.Float)-1; // yes, not separated
122 if (c.dot(d)-a.dot(d) < EPSILON!(VT.Float)*EPSILON!(VT.Float)) break; // new point is not far enough, we found her!
123 auto p0 = segClosestToOrigin(a, c);
124 auto p1 = segClosestToOrigin(c, b);
125 immutable p0sqlen = p0.lengthSquared;
126 immutable p1sqlen = p1.lengthSquared;
127 if (p0sqlen <= EPSILON!(VT.Float)*EPSILON!(VT.Float) || p1sqlen <= EPSILON!(VT.Float)*EPSILON!(VT.Float)) {
128 // origin is very close, but not exactly on edge; assume zero distance (special case)
129 if (sepnorm !is null) *sepnorm = d.normalized;
130 return cast(VT.Float)0;
132 if (p0sqlen < p1sqlen) { b = c; bp0 = cp0; bp1 = cp1; d = p0; } else { a = c; ap0 = cp0; ap1 = cp1; d = p1; }
134 // either out of iterations, or new point was not far enough
135 d.normalize;
136 auto dist = -c.dot(d);
137 // get closest points
138 if (op0 !is null || op1 !is null) {
139 auto l = b-a;
140 if (l.isZero) {
141 if (op0 !is null) *op0 = ap0;
142 if (op1 !is null) *op1 = ap1;
143 } else {
144 immutable ll = l.dot(l);
145 immutable l2 = -l.dot(a)/ll;
146 immutable l1 = cast(VT.Float)1-l2;
147 if (l1 < 0) {
148 if (op0 !is null) *op0 = bp0;
149 if (op1 !is null) *op1 = bp1;
150 } else if (l2 < 0) {
151 if (op0 !is null) *op0 = ap0;
152 if (op1 !is null) *op1 = ap1;
153 } else {
154 if (op0 !is null) *op0 = ap0*l1+bp0*l2;
155 if (op1 !is null) *op1 = ap1*l1+bp1*l2;
159 if (dist < 0) { d = -d; dist = -dist; }
160 if (sepnorm !is null) *sepnorm = d;
161 return dist;
165 // ////////////////////////////////////////////////////////////////////////// //
166 // return the Minkowski sum point (ok, something *like* it, but not Minkowski difference yet ;-)
167 private VT getSupportPoint(CT, VT) (ref CT body0, ref CT body1, in ref VT sdir) {
168 pragma(inline, true);
169 return body0.support(sdir)-body1.support(-sdir);
173 // check if simplex contains origin, update sdir, and possibly update simplex
174 private bool checkSimplex(VT) (ref VT sdir, VT[] spx) {
175 assert(spx.length == 2 || spx.length == 3);
176 if (spx.length == 3) {
177 // simplex has 3 elements
178 auto a = spx.ptr[0]; // last added point
179 auto ao = -a; // to origin
180 // get the edges
181 auto ab = spx.ptr[1]-a;
182 auto ac = spx.ptr[2]-a;
183 // get the edge normals
184 auto abn = ac.tripleProduct(ab, ab);
185 auto acn = ab.tripleProduct(ac, ac);
186 // see where the origin is at
187 auto acloc = acn.dot(ao);
188 if (acloc >= 0) {
189 // remove middle element
190 spx.ptr[1] = spx.ptr[0];
191 sdir = acn;
192 } else {
193 auto abloc = abn.dot(ao);
194 if (abloc < 0) return true; // intersection
195 // remove last element
196 spx.ptr[2] = spx.ptr[1];
197 spx.ptr[1] = spx.ptr[0];
198 sdir = abn;
200 } else {
201 // simplex has 2 elements
202 auto a = spx.ptr[0]; // last added point
203 auto ao = -a; // to origin
204 auto ab = spx.ptr[1]-a;
205 sdir = ab.tripleProduct(ao, ab);
206 if (sdir.lengthSquared <= EPSILON!(VT.Float)*EPSILON!(VT.Float)) sdir = sdir.rperp; // bad direction, use any normal
208 return false;
212 // ////////////////////////////////////////////////////////////////////////// //
213 // Expanding Polytope Algorithm
214 // find minimum translation vector to resolve collision
215 // using the final simplex obtained with the GJK algorithm
216 private VT EPA(CT, VT) (ref CT body0, ref CT body1, const(VT)[] spx...) {
217 enum MaxIterations = 128;
218 enum MaxFaces = MaxIterations*3;
219 assert(MaxIterations >= body0.length*body1.length);
221 static struct SxEdge {
222 VT p0, p1;
223 VT normal;
224 VT.Float dist;
225 usize nextFree; // will be used to store my temp index too
227 @disable this (this);
229 nothrow @safe @nogc:
230 void calcNormDist (int winding) {
231 pragma(inline, true);
232 mixin(ImportCoreMath!(VT.Float, "fabs"));
233 normal = p1-p0;
234 if (winding < 0) normal = normal.perp; else normal = normal.rperp;
235 normal.normalize();
236 dist = fabs(p0.dot(normal));
239 void set (in ref VT ap0, in ref VT ap1, int winding) {
240 p0 = ap0;
241 p1 = ap1;
242 calcNormDist(winding);
246 // as this cannot be called recursive, we can use thread-local static here
247 // use binary heap to store faces
248 static SxEdge[MaxFaces+2] faces = void;
249 static usize[MaxFaces+2] faceMap = void;
251 usize freeFaceIdx = usize.max;
252 usize facesUsed = 0;
253 usize faceCount = 0;
254 int winding = 0;
256 void heapify (usize root) {
257 for (;;) {
258 auto smallest = 2*root+1; // left child
259 if (smallest >= faceCount) break; // anyway
260 immutable right = smallest+1; // right child
261 if (!(faces.ptr[faceMap.ptr[smallest]].dist < faces.ptr[faceMap.ptr[root]].dist)) smallest = root;
262 if (right < faceCount && faces.ptr[faceMap.ptr[right]].dist < faces.ptr[faceMap.ptr[smallest]].dist) smallest = right;
263 if (smallest == root) break;
264 // swap
265 auto tmp = faceMap.ptr[root];
266 faceMap.ptr[root] = faceMap.ptr[smallest];
267 faceMap.ptr[smallest] = tmp;
268 root = smallest;
272 void insertFace (in ref VT p0, in ref VT p1) {
273 if (faceCount == faces.length) assert(0, "too many elements in heap");
274 auto i = faceCount;
275 usize ffidx = freeFaceIdx; // allocated face index in `faces[]`
276 if (ffidx != usize.max) {
277 // had free face in free list, fix free list
278 freeFaceIdx = faces.ptr[ffidx].nextFree;
279 } else {
280 // no free faces, use next unallocated
281 ffidx = facesUsed++;
283 assert(ffidx < faces.length);
284 ++faceCount;
285 faces.ptr[ffidx].set(p0, p1, winding);
286 immutable nfdist = faces.ptr[ffidx].dist;
287 // fix heap, and find place for new face
288 while (i != 0) {
289 auto par = (i-1)/2; // parent
290 if (!(nfdist < faces.ptr[faceMap.ptr[par]].dist)) break;
291 faceMap.ptr[i] = faceMap.ptr[par];
292 i = par;
294 faceMap.ptr[i] = ffidx;
297 // remove face from heap, but don't add it to free list yet
298 SxEdge* popSmallestFace () {
299 assert(faceCount > 0);
300 usize fidx = faceMap.ptr[0];
301 SxEdge* res = faces.ptr+fidx;
302 res.nextFree = fidx; // store face index; it will be used in `freeFace()`
303 // remove from heap (but don't add to free list yet)
304 faceMap.ptr[0] = faceMap.ptr[--faceCount];
305 heapify(0);
306 return res;
309 // add face to free list
310 void freeFace (SxEdge* e) {
311 assert(e !is null);
312 auto fidx = e.nextFree;
313 e.nextFree = freeFaceIdx;
314 freeFaceIdx = fidx;
317 // compute the winding
318 VT prevv = spx[$-1];
319 foreach (const ref v; spx[]) {
320 auto cp = prevv.cross(v);
321 if (cp > 0) { winding = 1; break; }
322 if (cp < 0) { winding = -1; break; }
323 prevv = v;
326 // build the initial edge queue
327 prevv = spx[$-1];
328 foreach (const ref v; spx[]) {
329 insertFace(prevv, v);
330 prevv = v;
333 SxEdge* e;
334 VT p;
335 version(vm2d_debug_count_iterations) epaIterationCount = 0;
336 foreach (immutable i; 0..body0.length*body1.length) {
337 version(vm2d_debug_count_iterations) ++epaIterationCount;
338 e = popSmallestFace();
339 p = getSupportPoint(body0, body1, e.normal);
340 immutable proj = p.dot(e.normal);
341 if (proj-e.dist < EPSILON!(VT.Float)) return e.normal*proj;
342 insertFace(e.p0, p);
343 insertFace(p, e.p1);
344 freeFace(e);
346 assert(e !is null); // just in case
347 version(vm2d_debug_gjk_warnings) { import core.stdc.stdio; stderr.fprintf("EPA: out of iterations!\n"); }
348 return e.normal*p.dot(e.normal);
352 // ////////////////////////////////////////////////////////////////////////// //
353 // see Gino van den Bergen's "Ray Casting against General Convex Objects with Application to Continuous Collision Detection" paper
354 // http://www.dtecta.com/papers/jgt04raycast.pdf
356 // ////////////////////////////////////////////////////////////////////////// //
357 public static struct Raycast(VT) {
358 VT p = VT.Invalid, n = VT.Invalid; // point and normal
359 VT.Float dist;
360 @property bool valid () const nothrow @safe @nogc { pragma(inline, true); return n.isFinite; }
364 // ////////////////////////////////////////////////////////////////////////// //
365 // simplex for gjk raycaster
366 private struct RCSimplex(VT) {
367 VT start; // ray start (not modified by solver)
368 VT r; // ray direction (not modified by solver)
369 VT.Float maxlen = 0; // maximum ray length (not modified by solver)
370 VT x; // current closest point on the ray
371 VT v; // current direction
372 VT a = VT.Invalid, b = VT.Invalid; // simplex points
373 VT.Float distsq = VT.Float.infinity;
374 VT p; // current support point (in world space)
375 VT n; // normal at the hit point
376 VT.Float lambda = 0; // current distance
378 // setup simplex
379 // astart: start point
380 // adir: velocity, not normalized
381 // apoint: first point for simplex, arbitrary point from a body we're checking
382 // returns `false` if velocity is too small to get something sane
383 bool setup() (in auto ref VT astart, in auto ref VT adir, in auto ref VT apoint) {
384 start = astart;
385 x = astart;
386 v = x-apoint;
387 r = adir;
388 maxlen = r.normalizeRetLength;
389 return (maxlen > 1);
392 // return `false` if we can stop iterating due to "solution not found" condition
393 // set `p` to new support point before calling this
394 bool advance () {
395 immutable VT w = x-p;
396 version(vm2d_debug_save_minkowski_points)mink ~= w;
397 immutable VT.Float dvw = v.dot(w);
398 if (dvw > 0) {
399 immutable VT.Float dvr = v.dot(r);
400 if (dvr >= -(EPSILON!(VT.Float)*EPSILON!(VT.Float))) return false;
401 lambda -= dvw/dvr;
402 if (lambda > maxlen) return false;
403 x = start+r*lambda; // shorten ray
404 n = v; // new normal
406 return true;
409 // update simplex
410 void update () {
411 if (a.valid) {
412 if (b.valid) {
413 immutable VT p1 = x.projectToSeg(a, p);
414 immutable VT p2 = x.projectToSeg(p, b);
415 if (p1.distanceSquared(x) < p2.distanceSquared(x)) {
416 b = p;
417 distsq = p1.distanceSquared(x);
418 } else {
419 a = p;
420 distsq = p2.distanceSquared(x);
422 immutable VT ab = b-a;
423 immutable VT ax = x-a;
424 v = ab.tripleProduct(ax, ab);
425 } else {
426 b = p;
427 immutable VT ab = b-a;
428 immutable VT ax = x-a;
429 v = ab.tripleProduct(ax, ab);
431 } else {
432 a = p;
433 v = -v;
437 // main solver
438 // returns `false` if a solution wasn't found, or modify `res` if a solution was found
439 bool solve(int maxiters=32, double distEps=0.0001) (ref Raycast!VT res, scope VT delegate (in ref VT sdir) getSupportPoint) {
440 version(vm2d_debug_save_minkowski_points)mink = null;
441 version(vm2d_debug_count_iterations) gjkIterationCount = 0;
442 foreach (immutable itnum; 0..maxiters) {
443 version(vm2d_debug_count_iterations) ++gjkIterationCount;
444 p = getSupportPoint(v);
445 if (!advance()) return false;
446 update();
447 if (distsq <= cast(VT.Float)distEps) {
448 // yay, i found her!
449 res.p = x;
450 res.n = n.normalized;
451 res.dist = lambda;
452 return true;
455 return false;
460 // ////////////////////////////////////////////////////////////////////////// //
461 public Raycast!VT gjkraycast(bool checkRayStart=true, int maxiters=32, double distEps=0.0001, VT, CT) (auto ref CT abody, in auto ref VT rayO, in auto ref VT rayD) if (IsGoodVertexStorage!(CT, VT)) {
462 Raycast!VT res;
463 RCSimplex!VT simplex;
465 static if (checkRayStart) {
466 if (abody.inside(rayO)) return res; // the start point is inside the body, oops
469 if (!simplex.setup(rayO, rayD, abody.centroid)) {
470 // velocity is zero, use infinite length
471 simplex.maxlen = VT.Float.infinity;
473 if (!simplex.solve!(maxiters, distEps)(res, (in ref sdir) => abody.support(sdir))) return res;
475 return res;
479 // ////////////////////////////////////////////////////////////////////////// //
480 // this inflates *bbody*, and traces ray from *abody* origin to *bbody*. it is IMPORTANT! ;-)
481 // if result is valid, body a can move by `res.p-abody.origin` before hit
482 public Raycast!VT gjksweep(bool checkRayStart=true, int maxiters=32, double distEps=0.0001, VT, CT) (auto ref CT abody, auto ref CT bbody, in auto ref VT lvelA, in auto ref VT lvelB) {
483 Raycast!VT res;
484 RCSimplex!VT simplex;
486 immutable VT act = abody.centroid;
487 immutable VT bct = bbody.centroid;
489 if (act.equals(bct)) return res; // obviously collided
490 static if (checkRayStart) {
491 if (bbody.inside(act)) return res; // the start point is inside the destination body, oops
494 // trace from abody to bbody, with relative motion (lvelA-lvelB)
495 if (!simplex.setup(act, lvelA-lvelB, bct)) return res; // velocity is zero, so oops
496 if (!simplex.solve!(maxiters, distEps)(res, (in ref sdir) => bbody.support(sdir)-(abody.support(-sdir)-act))) return res;
498 return res;