77#include < span>
88#include < stdexcept>
99#include < utility>
10+ #include < vector>
1011
1112namespace sndx ::collision {
13+ // this is pretty much what is written in https://winter.dev/articles/gjk-algorithm and their epa article
1214 namespace detail {
1315 template <class SFnA , class SFnB > [[nodiscard]]
1416 glm::vec3 gjkMinkowski (SFnA&& supportA, SFnB&& supportB, glm::vec3 direction) {
@@ -52,13 +54,12 @@ namespace sndx::collision {
5254
5355 if (detail::similarDir (ab, ao)) { // we want to point towards the origin
5456 newDirection = glm::normalize (detail::tripleProduct (ab, ao));
55- }
56- else {
57- // remove b from simplex
58- size = 1 ;
59- newDirection = glm::normalize (ao);
57+ return true ;
6058 }
6159
60+ // remove b from simplex
61+ size = 1 ;
62+ newDirection = glm::normalize (ao);
6263 return false ;
6364 }
6465
@@ -74,34 +75,35 @@ namespace sndx::collision {
7475 if (!detail::similarDir (ac, ao)) {
7576 // keep a and b
7677 size = 2 ;
77- return lineOrigin (newDirection);
78-
79- }
80- else {
81- // keep a and c
82- points[1 ] = points[2 ];
83- size = 2 ;
84- newDirection = glm::normalize (detail::tripleProduct (ac, ao));
78+ lineOrigin (newDirection);
79+ return false ;
8580 }
81+
82+ // keep a and c
83+ points[1 ] = points[2 ];
84+ size = 2 ;
85+ newDirection = glm::normalize (detail::tripleProduct (ac, ao));
86+ return false ;
87+ }
88+
89+ if (detail::similarDir (glm::cross (ab, cross), ao)) {
90+ // keep a and b
91+ size = 2 ;
92+ lineOrigin (newDirection);
93+ return false ;
8694 }
87- else {
88- if (detail::similarDir (glm::cross (ab, cross), ao)) {
89- // keep a and b
90- size = 2 ;
91- return lineOrigin (newDirection);
92- }
9395
94- // check direction for tetrahedon
95- if (detail::similarDir (cross, ao)) {
96- newDirection = glm::normalize (cross);
97- }
98- else {
99- // flip b and c
100- std::swap (points[1 ], points[2 ]);
101- newDirection = glm::normalize (-cross);
102- }
96+ // check direction for tetrahedon
97+ if (detail::similarDir (cross, ao)) {
98+ newDirection = glm::normalize (cross);
10399 }
104- return false ;
100+ else {
101+ // flip b and c
102+ std::swap (points[1 ], points[2 ]);
103+ newDirection = glm::normalize (-cross);
104+ }
105+
106+ return true ;
105107 }
106108
107109 bool tetrahedronOrigin (Vec& newDirection) {
@@ -136,10 +138,11 @@ namespace sndx::collision {
136138
137139 [[nodiscard]]
138140 bool gjkOrigin (Vec& newDirection) {
141+ // checks need to be done after since size can be modified
139142 switch (size) {
140- case 2 : return lineOrigin (newDirection);
141- case 3 : return triangleOrigin (newDirection);
142- case 4 : return tetrahedronOrigin (newDirection);
143+ case 2 : return lineOrigin (newDirection) && size == dimensionality () + 1 ;
144+ case 3 : return triangleOrigin (newDirection) && size == dimensionality () + 1 ;
145+ case 4 : return tetrahedronOrigin (newDirection) && size == dimensionality () + 1 ;
143146 default :
144147 throw std::logic_error (" GJK had weird number of points in simplex" );
145148 }
@@ -167,6 +170,142 @@ namespace sndx::collision {
167170 }
168171 }
169172
173+ struct EpaResult {
174+ glm::vec3 normal;
175+ float depth;
176+ };
177+
178+ struct PolytopeEPA {
179+ using Vertices = std::vector<glm::vec3>;
180+ using Indices = std::vector<glm::vec<3 , size_t >>;
181+ using Edges = std::vector<std::pair<size_t , size_t >>;
182+
183+ Vertices verts{};
184+ Indices indices{};
185+
186+ static void addUniqueEdge (Edges& out, const Indices& indices, size_t tri, size_t a, size_t b) {
187+ auto reversed = std::find (out.begin (), out.end (), std::make_pair (indices[tri][b], indices[tri][a]));
188+
189+ if (reversed != out.end ()) {
190+ out.erase (reversed);
191+ }
192+ else {
193+ out.emplace_back (indices[tri][a], indices[tri][b]);
194+ }
195+ }
196+
197+ [[nodiscard]]
198+ static std::pair<std::vector<glm::vec4>, size_t > calcNormals (const Vertices& verts, const Indices& indices) {
199+ size_t mTri = 0 ;
200+ std::vector<glm::vec4> norms{};
201+ norms.reserve (indices.size ());
202+ float minDist = std::numeric_limits<float >::max ();
203+ for (size_t i = 0 ; i < indices.size (); ++i) {
204+ const auto & a = verts[indices[i].x ];
205+ const auto & b = verts[indices[i].y ];
206+ const auto & c = verts[indices[i].z ];
207+
208+ auto normal = glm::normalize (glm::cross (b - a, c - a));
209+ auto dist = glm::dot (normal, a);
210+ if (dist < 0 ) {
211+ normal *= -1 .0f ;
212+ dist *= -1 .0f ;
213+ }
214+
215+ norms.emplace_back (normal, dist);
216+ if (dist < minDist) {
217+ mTri = i;
218+ minDist = dist;
219+ }
220+ }
221+
222+ return { std::move (norms), mTri };
223+ }
224+
225+ [[nodiscard]]
226+ auto calcNormals () const {
227+ return calcNormals (verts, indices);
228+ }
229+
230+ PolytopeEPA (const SimplexGJK& simplex):
231+ verts (simplex.points.begin(), simplex.points.begin() + simplex.size),
232+ indices{ {0 , 1 , 2 }, {0 , 3 , 1 }, {0 , 2 , 3 }, {1 , 3 , 2 } } {
233+
234+ assert (simplex.size == 4 );
235+ }
236+ };
237+
238+ template <float epsilon = 0 .00001f , class SFnA , class SFnB > [[nodiscard]]
239+ EpaResult epa (const SimplexGJK& simplex, const SFnA& supportA, const SFnB& supportB) {
240+ PolytopeEPA polytope{ simplex };
241+
242+ auto [normals, minTri] = polytope.calcNormals ();
243+
244+ glm::vec3 minNorm{};
245+ float minDist = std::numeric_limits<float >::max ();
246+
247+ while (minDist == std::numeric_limits<float >::max ()) {
248+ minNorm = glm::vec3 (normals[minTri]);
249+ minDist = normals[minTri].w ;
250+
251+ auto support = detail::gjkMinkowski (supportA, supportB, minNorm);
252+ auto dist = glm::dot (minNorm, support);
253+
254+ if (std::abs (dist - minDist) > epsilon) {
255+ minDist = std::numeric_limits<float >::max ();
256+
257+ PolytopeEPA::Edges edges{};
258+ edges.reserve (polytope.verts .size ());
259+
260+ for (size_t i = 0 ; i < normals.size (); ++i) {
261+ if (detail::similarDir (glm::vec3 (normals[i]), support - polytope.verts [polytope.indices [i].x ])) {
262+
263+ PolytopeEPA::addUniqueEdge (edges, polytope.indices , i, 0 , 1 );
264+ PolytopeEPA::addUniqueEdge (edges, polytope.indices , i, 1 , 2 );
265+ PolytopeEPA::addUniqueEdge (edges, polytope.indices , i, 2 , 0 );
266+
267+ polytope.indices [i] = polytope.indices .back ();
268+ polytope.indices .pop_back ();
269+
270+ normals[i] = normals.back ();
271+ normals.pop_back ();
272+
273+ --i;
274+ }
275+ }
276+
277+ PolytopeEPA::Indices newIndices{};
278+ newIndices.reserve (edges.size () / 3 );
279+ for (auto [a, b] : edges) {
280+ newIndices.emplace_back (a, b, polytope.verts .size ());
281+ }
282+ polytope.verts .emplace_back (support);
283+
284+ auto [newNorms, newMinTri] = PolytopeEPA::calcNormals (polytope.verts , newIndices);
285+
286+ float oldMin = std::numeric_limits<float >::max ();
287+ for (size_t i = 0 ; i < normals.size (); ++i) {
288+ if (normals[i].w < oldMin) {
289+ oldMin = normals[i].w ;
290+ minTri = i;
291+ }
292+ }
293+
294+ if (newNorms[newMinTri].w < oldMin) {
295+ minTri = newMinTri + normals.size ();
296+ }
297+
298+ polytope.indices .insert (polytope.indices .begin (), newIndices.begin (), newIndices.end ());
299+ normals.insert (normals.begin (), newNorms.begin (), newNorms.end ());
300+ }
301+ }
302+
303+ EpaResult out{};
304+ out.normal = minNorm;
305+ out.depth = minDist + epsilon;
306+ return out;
307+ }
308+
170309 template <VolumeN<3 > T> [[nodiscard]]
171310 auto getSupportFn (const T& volume) {
172311 return [&](glm::vec3 dir) { return volume.supportPoint (dir); };
0 commit comments