1 import arsd
.simpledisplay
;
4 import iv
.vmath_gjk3d_simple
;
7 // ////////////////////////////////////////////////////////////////////////// //
8 final class Body2D(VT
) if (IsVectorDim
!(VT
, 2)) {
9 VT
[] verts
; // vertices
10 VT
[] norms
; // normals
13 alias vec3
= VecN
!(3, VT
.Float
);
16 vec3
position () const { return vec3(centroid
); }
18 // dumb O(n) support function, just brute force check all points
19 vec3
support (in vec3 dir
) const {
20 //dir = matRS_inverse*dir;
21 vec3 furthestPoint
= vec3(verts
[0]);
22 auto maxDot
= furthestPoint
.dot(dir
);
23 foreach (const ref v
; verts
[1..$]) {
24 auto d
= vec3(v
).dot(vec3(dir
));
27 furthestPoint
= vec3(v
);
30 //auto res = matRS*furthestPoint+pos; // convert support to world space
34 void setVerts (const(VT
)[] aaverts
, VT
.Float arot
=0) {
35 // no hulls with less than 3 vertices (ensure actual polygon)
36 if (aaverts
.length
< 3) throw new Exception("degenerate body");
40 averts
.reserve(aaverts
.length
);
41 auto rmat
= Mat3
!VT
.Rotate(arot
);
42 foreach (const ref v
; aaverts
) averts
~= rmat
*v
;
44 // find the right most point on the hull
46 VT
.Float highestXCoord
= averts
[0].x
;
47 foreach (immutable i
; 1..averts
.length
) {
48 VT
.Float x
= averts
[i
].x
;
49 if (x
> highestXCoord
) {
51 rightMost
= cast(int)i
;
52 } else if (x
== highestXCoord
) {
53 // if matching x then take farthest negative y
54 if (averts
[i
].y
< averts
[rightMost
].y
) rightMost
= cast(int)i
;
58 auto hull
= new int[](averts
.length
);
60 int indexHull
= rightMost
;
64 hull
[outCount
] = indexHull
;
66 // search for next index that wraps around the hull by computing cross products to
67 // find the most counter-clockwise vertex in the set, given the previos hull index
68 int nextHullIndex
= 0;
69 foreach (immutable i
; 1..averts
.length
) {
70 // skip if same coordinate as we need three unique points in the set to perform a cross product
71 if (nextHullIndex
== indexHull
) {
75 // cross every set of three unique vertices
76 // record each counter clockwise third vertex and add to the output hull
77 // See : http://www.oocities.org/pcgpe/math2d.html
78 auto e1
= averts
[nextHullIndex
]-averts
[hull
[outCount
]];
79 auto e2
= averts
[i
]-averts
[hull
[outCount
]];
80 auto c
= e1
.cross(e2
);
81 if (c
< 0.0f) nextHullIndex
= i
;
83 // cross product is zero then e vectors are on same line
84 // therefore want to record vertex farthest along that line
85 if (c
== 0.0f && e2
.lengthSquared
> e1
.lengthSquared
) nextHullIndex
= i
;
89 indexHull
= nextHullIndex
;
91 // conclude algorithm upon wrap-around
92 if (nextHullIndex
== rightMost
) {
97 if (vcount
< 3) throw new Exception("degenerate body");
99 // copy vertices into shape's vertices
100 verts
.reserve(vcount
);
101 foreach (immutable i
; 0..vcount
) verts
~= averts
[hull
[i
]];
102 if (!isConvex()) throw new Exception("non-convex body");
104 centroid
= VT(0, 0, 0);
105 // compute face normals
106 norms
.reserve(verts
.length
);
107 foreach (immutable i1
; 0..verts
.length
) {
108 centroid
+= verts
[i1
];
109 immutable i2
= (i1
+1)%verts
.length
;
110 auto face
= verts
[i2
]-verts
[i1
];
111 // ensure no zero-length edges, because that's bad
112 assert(face
.lengthSquared
> EPSILON
!(VT
.Float
)*EPSILON
!(VT
.Float
));
113 // calculate normal with 2D cross product between vector and scalar
114 norms
~= VT(face
.y
, -face
.x
).normalized
;
116 centroid
/= cast(VT
.Float
)verts
.length
;
120 bool isConvex () const {
121 static int sign() (VT
.Float v
) { pragma(inline
, true); return (v
< 0 ?
-1 : v
> 0 ?
1 : 0); }
122 if (verts
.length
< 3) return false;
123 if (verts
.length
== 3) return true; // nothing to check here
125 foreach (immutable idx
, const ref v
; verts
) {
126 immutable v1
= VT(verts
[(idx
+1)%verts
.length
])-v
;
127 immutable v2
= VT(verts
[(idx
+2)%verts
.length
]);
128 int d
= sign(v2
.x
*v1
.y
-v2
.y
*v1
.x
+v1
.x
*v
.y
-v1
.y
*v
.x
);
129 if (d
== 0) return false;
131 if (dir
!= d
) return false;
139 void moveBy() (in auto ref VT delta
) {
140 foreach (ref v
; verts
) v
+= delta
;
144 void moveBy() (VT
.Float dx
, VT
.Float dy
, VT
.Float dz
=0) { moveBy(VT(dx
, dy
, dz
)); }
146 bool inside() (in auto ref VT p
) const {
147 static int sign() (VT
.Float v
) { pragma(inline
, true); return (v
< 0 ?
-1 : v
> 0 ?
1 : 0); }
148 if (verts
.length
< 3) return false;
150 foreach (immutable idx
, const ref v
; verts
) {
151 immutable as
= verts
[(idx
+1)%verts
.length
]-v
;
153 int d
= sign(as
.cross(ap
));
155 if (side
== 0) side
= d
; else if (side
!= d
) return false;
161 bool inside() (VT
.Float x
, VT
.Float y
) const { return inside(VT(x
, y
)); }
165 static assert(IsGoodGJKObject
!(Body2D
!vec2
, vec3
));
168 // ////////////////////////////////////////////////////////////////////////// //
169 auto generateBody () {
172 foreach (immutable _
; 0..uniform
!"[]"(10, 50)) vtx
~= vec2(uniform
!"[]"(-50, 50), uniform
!"[]"(-50, 50));
173 auto flesh
= new Body2D
!vec2();
179 // ////////////////////////////////////////////////////////////////////////// //
181 auto flesh0
= generateBody();
182 auto flesh1
= generateBody();
184 flesh0
.moveBy(350, 450);
185 flesh1
.moveBy(250, 350);
187 auto sdwin
= new SimpleWindow(1024, 768, "GJK Test");
190 bool checkCollision
= true;
193 auto pt
= sdwin
.draw();
194 pt
.fillColor
= Color
.black
;
195 pt
.outlineColor
= Color
.black
;
196 pt
.drawRectangle(Point(0, 0), sdwin
.width
, sdwin
.height
);
197 pt
.outlineColor
= Color
.white
;
199 void drawVL(VT
) (in auto ref VT v0
, in auto ref VT v1
) if (IsVectorDim
!(VT
, 2)) {
200 pt
.drawLine(Point(cast(int)v0
.x
, cast(int)v0
.y
), Point(cast(int)v1
.x
, cast(int)v1
.y
));
203 void drawPoint(VT
) (in auto ref VT v
) if (IsVector
!VT
) {
206 pt
.drawEllipse(Point(cast(int)v0
.x
, cast(int)v0
.y
), Point(cast(int)v1
.x
, cast(int)v1
.y
));
209 void drawBody(BT
) (BT flesh
) if (is(BT
== Body2D
!VT
, VT
)) {
210 foreach (immutable int idx
; 0..cast(int)flesh
.verts
.length
) {
211 immutable v0
= flesh
.verts
[idx
];
212 immutable v1
= flesh
.verts
[(idx
+1)%cast(int)flesh
.verts
.length
];
215 drawPoint(flesh
.centroid
);
218 bool collided
= false;
221 if (checkCollision
) {
222 import std
.math
: sqrt
;
223 collided
= gjk(flesh0
, flesh1
, &mtv
);
224 if (collided
) writeln("COLLISION! mtv=", mtv
);
227 pt
.outlineColor
= (fhigh
== 0 ? Color
.green
: collided ? Color
.red
: Color
.white
);
229 pt
.outlineColor
= (fhigh
== 1 ? Color
.green
: collided ? Color
.red
: Color
.white
);
234 pt.outlineColor = Color.yellow;
239 pt.outlineColor = Color(255, 127, 0);
246 pt.outlineColor = Color(255, 127, 0);
247 drawPoint(epaP+flesh0.centroid);
249 drawVL(vec2(epaFace[0]+flesh0.centroid), vec2(epaFace[1]+flesh0.centroid));
250 pt.outlineColor = Color.yellow;
251 drawVL(vec2(epaFace[1]+flesh0.centroid), vec2(epaFace[2]+flesh0.centroid));
252 pt.outlineColor = Color.blue;
253 drawVL(vec2(epaFace[2]+flesh0.centroid), vec2(epaFace[0]+flesh0.centroid));
260 delegate (KeyEvent event
) {
261 if (!event
.pressed
) return;
262 if (event
== "C-Q") { sdwin
.close(); return; }
264 delegate (MouseEvent event
) {
266 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
268 if (flesh0
.inside(event
.x
, event
.y
)) hp |
= 1;
269 if (flesh1
.inside(event
.x
, event
.y
)) hp |
= 2;
271 if (hp
== 1) fhigh
= 0;
272 else if (hp
== 2) fhigh
= 1;
275 // choose one with the closest centroid
276 fhigh
= (flesh0
.centroid
.distanceSquared(vec2(event
.x
, event
.y
)) < flesh1
.centroid
.distanceSquared(vec2(event
.x
, event
.y
)) ?
0 : 1);
281 if (oldhi
!= fhigh
) {
282 checkCollision
= (fhigh
== -1);
287 if (fhigh
!= -1 && event
.type
== MouseEventType
.motion
&& (event
.modifierState
&ModifierState
.leftButtonDown
) != 0) {
288 if (fhigh
== 0) flesh0
.moveBy(event
.dx
, event
.dy
);
289 else if (fhigh
== 1) flesh1
.moveBy(event
.dx
, event
.dy
);
290 checkCollision
= (fhigh
== -1);
294 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
297 checkCollision
= true;