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
.vmath_gjk2d_simple
;
21 version = gjk_warnings
;
23 // ////////////////////////////////////////////////////////////////////////// //
24 /* GJK object should support:
26 * Vec2 support (Vec2 dir) -- in world space
30 * vec2 position () const { return centroid; }
32 * // dumb O(n) support function, just brute force check all points
33 * vec2 support() (in auto ref vec2 dir) const {
34 * VT furthestPoint = verts[0];
35 * auto maxDot = furthestPoint.dot(dir);
36 * foreach (const ref v; verts[1..$]) {
37 * auto d = v.dot(dir);
43 * return furthestPoint;
46 public template IsGoodGJKObject(T
, VT
) if ((is(T
== struct) ||
is(T
== class)) && IsVectorDim
!(VT
, 2)) {
47 enum IsGoodGJKObject
= is(typeof((inout int=0) {
50 v
= o
.support(VT(0, 0));
55 // ////////////////////////////////////////////////////////////////////////// //
56 /// check if two convex shapes are colliding. can optionally return separating vector in `sepmove`.
57 public bool gjk(CT
, VT
) (in auto ref CT coll1
, in auto ref CT coll2
, VT
* sepmove
=null) if (IsGoodGJKObject
!(CT
, VT
)) {
58 VT sdir
= coll2
.position
-coll1
.position
; // initial search direction
59 if (sdir
.isZero
) sdir
= VT(1, 0); // use arbitrary normal if initial direction is zero
61 VT
[3] spx
= void; // simplex; [0] is most recently added, [2] is oldest
63 spx
.ptr
[2] = getSupportPoint(coll1
, coll2
, sdir
);
64 if (spx
.ptr
[2].dot(sdir
) <= 0) return false; // past the origin
66 sdir
= -sdir
; // change search direction
70 spx
.ptr
[spxidx
] = getSupportPoint(coll1
, coll2
, sdir
);
71 if (spx
.ptr
[spxidx
].dot(sdir
) <= 0) return false; // past the origin
72 if (checkSimplex(sdir
, spx
[spxidx
..$])) {
73 if (sepmove
!is null) *sepmove
= EPA(coll1
, coll2
, spx
[spxidx
..$]);
83 // ////////////////////////////////////////////////////////////////////////// //
84 /// return distance between two convex shapes, and separation normal
85 /// negative distance means that shapes are overlapping, and zero distance means touching (and ops are invalid)
86 public auto gjkdist(CT
, VT
) (in auto ref CT coll1
, in auto ref CT coll2
, VT
* op0
=null, VT
* op1
=null, VT
* sepnorm
=null) if (IsGoodGJKObject
!(CT
, VT
)) {
87 static VT
segClosestToOrigin() (in auto ref VT segp0
, in auto ref VT segp1
) {
88 immutable oseg
= segp1
-segp0
;
89 immutable ab2
= oseg
.dot(oseg
);
90 immutable apab
= (-segp0
).dot(oseg
);
91 if (ab2
<= EPSILON
!(VT
.Float
)) return segp0
;
92 VT
.Float t
= apab
/ab2
;
93 if (t
< 0) t
= 0; else if (t
> 1) t
= 1;
97 enum GetSupport(string smpx
) =
98 smpx
~"p0 = coll1.support(d);\n"~
99 smpx
~"p1 = coll2.support(-d);\n"~
100 smpx
~" = "~smpx
~"p0-"~smpx
~"p1;";
102 if (sepnorm
!is null) *sepnorm
= VT(0, 0);
103 if (op0
!is null) *op0
= VT(0, 0);
104 if (op1
!is null) *op1
= VT(0, 0);
106 VT a
, b
, c
; // simplex
107 VT ap0
, bp0
, cp0
; // simplex support points, needed for closest points calculation
108 VT ap1
, bp1
, cp1
; // simplex support points, needed for closest points calculation
109 // position is centroid, use that fact
110 auto d
= coll2
.position
-coll1
.position
;
111 // check for a zero direction vector
112 if (d
.isZero
) return cast(VT
.Float
)-1; // centroids are the same, not separated
113 //getSupport(a, ap0, ap1, d);
114 mixin(GetSupport
!"a");
116 mixin(GetSupport
!"b");
117 d
= segClosestToOrigin(b
, a
);
118 foreach (immutable iter
; 0..100) {
119 if (d
.lengthSquared
<= EPSILON
!(VT
.Float
)) return cast(VT
.Float
)-1; // if the closest point is the origin, not separated
121 mixin(GetSupport
!"c");
122 // is simplex triangle contains origin?
123 immutable sa
= a
.cross(b
);
124 if (sa
*b
.cross(c
) > 0 && sa
*c
.cross(a
) > 0) return cast(VT
.Float
)-1; // yes, not separated
125 if (c
.dot(d
)-a
.dot(d
) < EPSILON
!(VT
.Float
)*EPSILON
!(VT
.Float
)) break; // new point is not far enough, we found her!
126 auto p0
= segClosestToOrigin(a
, c
);
127 auto p1
= segClosestToOrigin(c
, b
);
128 immutable p0sqlen
= p0
.lengthSquared
;
129 immutable p1sqlen
= p1
.lengthSquared
;
130 if (p0sqlen
<= EPSILON
!(VT
.Float
) || p1sqlen
<= EPSILON
!(VT
.Float
)) {
131 // origin is very close, but not exactly on edge; assume zero distance (special case)
132 if (sepnorm
!is null) *sepnorm
= d
.normalized
;
133 return cast(VT
.Float
)0;
135 if (p0sqlen
< p1sqlen
) { b
= c
; bp0
= cp0
; bp1
= cp1
; d
= p0
; } else { a
= c
; ap0
= cp0
; ap1
= cp1
; d
= p1
; }
137 // either out of iterations, or new point was not far enough
139 auto dist
= -c
.dot(d
);
140 // get closest points
141 if (op0
!is null || op1
!is null) {
144 if (op0
!is null) *op0
= ap0
;
145 if (op1
!is null) *op1
= ap1
;
147 immutable ll
= l
.dot(l
);
148 immutable l2
= -l
.dot(a
)/ll
;
149 immutable l1
= cast(VT
.Float
)1-l2
;
151 if (op0
!is null) *op0
= bp0
;
152 if (op1
!is null) *op1
= bp1
;
154 if (op0
!is null) *op0
= ap0
;
155 if (op1
!is null) *op1
= ap1
;
157 if (op0
!is null) *op0
= ap0
*l1
+bp0
*l2
;
158 if (op1
!is null) *op1
= ap1
*l1
+bp1
*l2
;
162 if (dist
< 0) { d
= -d
; dist
= -dist
; }
163 if (sepnorm
!is null) *sepnorm
= d
;
168 // ////////////////////////////////////////////////////////////////////////// //
169 // return the Minkowski sum point (ok, something *like* it, but not Minkowski difference yet ;-)
170 private VT
getSupportPoint(CT
, VT
) (in ref CT coll1
, in ref CT coll2
, in ref VT sdir
) {
171 pragma(inline
, true);
172 return coll1
.support(sdir
)-coll2
.support(-sdir
);
176 // check if simplex contains origin, update sdir, and possibly update simplex
177 private bool checkSimplex(VT
) (ref VT sdir
, VT
[] spx
) {
178 assert(spx
.length
== 2 || spx
.length
== 3);
179 if (spx
.length
== 3) {
180 // simplex has 3 elements
181 auto a
= spx
.ptr
[0]; // last added point
182 auto ao
= -a
; // to origin
184 auto ab
= spx
.ptr
[1]-a
;
185 auto ac
= spx
.ptr
[2]-a
;
186 // get the edge normals
187 auto abn
= ac
.tripleProduct(ab
, ab
);
188 auto acn
= ab
.tripleProduct(ac
, ac
);
189 // see where the origin is at
190 auto acloc
= acn
.dot(ao
);
192 // remove middle element
193 spx
.ptr
[1] = spx
.ptr
[0];
196 auto abloc
= abn
.dot(ao
);
197 if (abloc
< 0) return true; // intersection
198 // remove last element
199 spx
.ptr
[2] = spx
.ptr
[1];
200 spx
.ptr
[1] = spx
.ptr
[0];
204 // simplex has 2 elements
205 auto a
= spx
.ptr
[0]; // last added point
206 auto ao
= -a
; // to origin
207 auto ab
= spx
.ptr
[1]-a
;
208 sdir
= ab
.tripleProduct(ao
, ab
);
209 if (sdir
.lengthSquared
<= EPSILON
!(VT
.Float
)) sdir
= sdir
.rperp
; // bad direction, use any normal
215 // ////////////////////////////////////////////////////////////////////////// //
216 // Expanding Polytope Algorithm
217 // find minimum translation vector to resolve collision
218 // using the final simplex obtained with the GJK algorithm
219 private VT
EPA(CT
, VT
) (in ref CT coll1
, in ref CT coll2
, const(VT
)[] spx
...) {
220 enum MaxIterations
= 100;
221 enum MaxFaces
= MaxIterations
*3;
223 static struct SxEdge
{
229 void calcNormDist (int winding
) {
230 pragma(inline
, true);
231 mixin(ImportCoreMath
!(VT
.Float
, "fabs"));
233 if (winding
< 0) normal
= normal
.perp
; else normal
= normal
.rperp
;
235 dist
= fabs(p0
.dot(normal
));
238 void set (in ref VT ap0
, in ref VT ap1
, int winding
) {
241 calcNormDist(winding
);
244 this (in ref VT ap0
, in ref VT ap1
, int winding
) { pragma(inline
, true); set(ap0
, ap1
, winding
); }
247 // as this cannot be called recursive, we can use thread-local static here
248 static SxEdge
[MaxFaces
] faces
= void;
251 // compute the winding
254 foreach (const ref v
; spx
[]) {
255 auto cp
= prevv
.cross(v
);
256 if (cp
> 0) { winding
= 1; break; }
257 if (cp
< 0) { winding
= -1; break; }
261 // build the initial edge queue
263 foreach (const ref v
; spx
[]) {
264 faces
.ptr
[faceCount
++].set(prevv
, v
, winding
);
268 void extractClosestEdge (ref SxEdge eres
) {
269 import core
.stdc
.string
: memmove
;
271 auto lastDist
= VT
.Float
.infinity
;
272 foreach (immutable idx
, const ref SxEdge e
; faces
[0..faceCount
]) {
273 if (e
.dist
< lastDist
) { res
= cast(int)idx
; lastDist
= e
.dist
; }
275 eres
= faces
.ptr
[res
];
276 if (faceCount
-res
> 1) memmove(faces
.ptr
+res
, faces
.ptr
+res
+1, (faceCount
-res
-1)*SxEdge
.sizeof
);
282 foreach (immutable i
; 0..MaxIterations
) {
283 extractClosestEdge(e
);
284 p
= getSupportPoint(coll1
, coll2
, e
.normal
);
285 immutable proj
= p
.dot(e
.normal
);
286 if (proj
-e
.dist
< EPSILON
!(VT
.Float
)/* *EPSILON!(VT.Float) */) return e
.normal
*proj
;
287 if (faces
.length
-faceCount
< 2) assert(0, "out of memory in GJK-EPA");
288 faces
.ptr
[faceCount
++].set(e
.p0
, p
, winding
);
289 faces
.ptr
[faceCount
++].set(p
, e
.p1
, winding
);
291 version(gjk_warnings
) { import core
.stdc
.stdio
; stderr
.fprintf("EPA: out of iterations!\n"); }
292 return e
.normal
*p
.dot(e
.normal
);