hx3d  1
2D/3D Simple Game Framework
collisions.cpp
1 #include "hx3d/physics/2d/collisions.hpp"
2 
3 #include "hx3d/math/vector_utils.hpp"
4 
5 #include <cfloat>
6 
7 namespace hx3d {
8 namespace physics2d {
9 
10  bool gt(float a, float b) {
11  return a >= b * 0.95f + a * 0.01f;
12  }
13 
14  float findAxisLeastPenetration(unsigned int* face, Ptr<colliders::Polygon>& a, Ptr<colliders::Polygon>& b) {
15  float bestDistance = -FLT_MAX;
16  unsigned int bestIndex = 0;
17 
18  for (unsigned int i = 0; i < a->vertexCount; ++i) {
19  glm::vec2 n = a->normals[i];
20  glm::vec2 nw = a->u * n;
21 
22  glm::mat2 buT = glm::transpose(b->u);
23  n = buT * nw;
24 
25  glm::vec2 s = b->getSupport(-n);
26 
27  glm::vec2 v = a->vertices[i];
28  v = a->u * v + a->position;
29  v -= b->position;
30  v = buT * v;
31 
32  float d = glm::dot(n, s - v);
33  if (d > bestDistance) {
34  bestDistance = d;
35  bestIndex = i;
36  }
37  }
38 
39  *face = bestIndex;
40  return bestDistance;
41  }
42 
43  void findIncidentFace(std::vector<glm::vec2>& v, const Ptr<colliders::Polygon>& ref, const Ptr<colliders::Polygon>& inc, int refIndex) {
44  glm::vec2 refNormal = ref->normals[refIndex];
45  refNormal = ref->u * refNormal;
46  refNormal = glm::transpose(inc->u) * refNormal;
47 
48  unsigned int incidentFace = 0;
49  float minDot = FLT_MAX;
50  for (unsigned int i = 0; i < inc->vertexCount; ++i) {
51  float dot = glm::dot(refNormal, inc->normals[i]);
52  if (dot < minDot) {
53  minDot = dot;
54  incidentFace = i;
55  }
56  }
57 
58  v[0] = inc->u * inc->vertices[incidentFace] + inc->position;
59  v[1] = inc->u * inc->vertices[(incidentFace + 1) % inc->vertexCount] + inc->position;
60  }
61 
62  int clip(const glm::vec2 n, const float c, std::vector<glm::vec2>& face) {
63  unsigned int sp = 0;
64  std::vector<glm::vec2> out = {face[0], face[1]};
65 
66  float d1 = glm::dot(n, face[0]) - c;
67  float d2 = glm::dot(n, face[1]) - c;
68 
69  if (d1 <= 0.f) out[sp++] = face[0];
70  if (d2 <= 0.f) out[sp++] = face[1];
71 
72  if (d1 * d2 < 0.f) {
73  float alpha = d1 / (d1 - d2);
74  out[sp++] = (face[1] - face[0]) * alpha + face[0];
75  }
76 
77  face[0] = out[0];
78  face[1] = out[1];
79 
80  assert(sp != 3);
81 
82  return sp;
83  }
84 
86 
88 
89  m.contacts.clear();
90 
91  unsigned int faceA = 0;
92  float penetrationA = findAxisLeastPenetration(&faceA, a, b);
93  if (penetrationA >= 0.f)
94  return false;
95 
96  unsigned int faceB = 0;
97  float penetrationB = findAxisLeastPenetration(&faceB, b, a);
98  if (penetrationB >= 0.f)
99  return false;
100 
101  unsigned int refIndex = 0;
102  bool flip = false;
103 
106 
107  if (gt(penetrationA, penetrationB)) {
108  ref = a;
109  inc = b;
110  refIndex = faceA;
111  flip = false;
112  }
113 
114  else {
115  ref = b;
116  inc = a;
117  refIndex = faceB;
118  flip = true;
119  }
120 
121  std::vector<glm::vec2> incidentFace = {{0, 0}, {0, 0}};
122  findIncidentFace(incidentFace, ref, inc, refIndex);
123 
124  glm::vec2 v1 = ref->vertices[refIndex];
125  glm::vec2 v2 = ref->vertices[(refIndex + 1) % ref->vertexCount];
126 
127  v1 = ref->u * v1 + ref->position;
128  v2 = ref->u * v2 + ref->position;
129 
130  glm::vec2 sidePlaneNormal = math::normalize(v2 - v1);
131  glm::vec2 refFaceNormal = {sidePlaneNormal.y, -sidePlaneNormal.x};
132 
133  float refC = glm::dot(refFaceNormal, v1);
134  float negSide = -glm::dot(sidePlaneNormal, v1);
135  float posSide = glm::dot(sidePlaneNormal, v2);
136 
137  if (clip(-sidePlaneNormal, negSide, incidentFace) < 2)
138  return false;
139 
140  if (clip(sidePlaneNormal, posSide, incidentFace) < 2)
141  return false;
142 
143  m.normal = flip ? -refFaceNormal : refFaceNormal;
144 
145  unsigned int cp = 0;
146  float separation = glm::dot(refFaceNormal, incidentFace[0]) - refC;
147  if (separation <= 0.f) {
148  m.contacts.push_back(incidentFace[0]);
149  cp++;
150 
151  m.penetration = -separation;
152  }
153  else {
154  m.penetration = 0;
155  }
156 
157  separation = glm::dot(refFaceNormal, incidentFace[1]) - refC;
158  if (separation <= 0.f) {
159  m.contacts.push_back(incidentFace[1]);
160  cp++;
161 
162  m.penetration += -separation;
163  m.penetration /= (float)cp;
164  }
165 
166  return true;
167  }
168 
170 
171  glm::vec2 normal = b->position - a->position;
172  float squareLength = math::squareLength(normal);
173  float radius = (a->radius + b->radius) / 2;
174 
175  if (squareLength >= radius*radius) {
176  m.contacts.clear();
177  return false;
178  }
179 
181 
182  float distance = std::sqrt(squareLength);
183  if (distance == 0.f) {
184  m.penetration = a->radius;
185  m.normal = glm::vec2(1, 0);
186  m.contacts.push_back(a->position);
187  }
188 
189  else {
190  m.penetration = radius - distance;
191  m.normal = normal / distance;
192  m.contacts.push_back(m.normal * a->radius + a->position);
193  }
194 
195  return true;
196  }
197 
199  if (checkCollisions(m, b, a)) {
200  m.normal = -m.normal;
201  return true;
202  }
203 
204  return false;
205  }
206 
208  return false;
209  }
210 
211 } /* physics2d */
212 } /* hx3d */
std::vector< glm::vec2 > contacts
Contact points.
Definition: manifold.hpp:49
hx3d framework namespace
Definition: audio.hpp:26
float squareLength(glm::vec2 vec)
Compute the square length of a 2D vector.
float penetration
Penetration coefficient.
Definition: manifold.hpp:41
Contact manifold definition.
Definition: manifold.hpp:33
bool checkCollisions(Manifold &m, Ptr< colliders::Circle > a, Ptr< colliders::Circle > b)
Collision test between two circles.
Definition: collisions.cpp:169
glm::vec2 normalize(glm::vec2 vec)
Normalize a 2D vector.
std::shared_ptr< T > Ptr
Quick-typing shared ptr.
Definition: ptr.hpp:34
glm::vec2 normal
Normal vector.
Definition: manifold.hpp:47