1 // This file belongs to the "MiniCore" game engine.
2 // Copyright (C) 2015 Jussi Lind <jussi.lind@iki.fi>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
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.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 // MA 02110-1301, USA.
18 //
19
20 #include "mccollisiondetector.hh"
21 #include "mccircleshape.hh"
22 #include "mccollisionevent.hh"
23 #include "mccontact.hh"
24 #include "mcobject.hh"
25 #include "mcrectshape.hh"
26 #include "mcsegment.hh"
27 #include "mcseparationevent.hh"
28 #include "mcshape.hh"
29
MCCollisionDetector()30 MCCollisionDetector::MCCollisionDetector()
31 {
32 }
33
areCurrentlyColliding(MCObject & object1,MCObject & object2)34 bool MCCollisionDetector::areCurrentlyColliding(MCObject & object1, MCObject & object2)
35 {
36 auto && iter = m_currentCollisions.find(&object1);
37 if (iter != m_currentCollisions.end() && iter->second.count(&object2))
38 {
39 return true;
40 }
41
42 iter = m_currentCollisions.find(&object2);
43 return iter != m_currentCollisions.end() && iter->second.count(&object1);
44 }
45
clear()46 void MCCollisionDetector::clear()
47 {
48 m_currentCollisions.clear();
49 }
50
remove(MCObject & object)51 void MCCollisionDetector::remove(MCObject & object)
52 {
53 auto && outer = m_currentCollisions.find(&object);
54 if (outer != m_currentCollisions.end())
55 {
56 for (auto && outerLinked : outer->second)
57 {
58 if (m_currentCollisions.count(outerLinked))
59 {
60 m_currentCollisions[outerLinked].erase(outer->first);
61 }
62 }
63
64 m_currentCollisions.erase(outer);
65 }
66 }
67
testRectAgainstRect(MCRectShape & rect1,MCRectShape & rect2)68 bool MCCollisionDetector::testRectAgainstRect(MCRectShape & rect1, MCRectShape & rect2)
69 {
70 const MCOBBox<float> & obbox1(rect1.obbox());
71
72 bool collided = false;
73
74 // Loop thru all vertices of rect1 and generate contacts for colliding vertices.
75 for (unsigned int i = 0; i < 4; i++)
76 {
77 if (rect2.contains(obbox1.vertex(i)))
78 {
79 const bool triggerObjectInvolved = rect1.parent().isTriggerObject() || rect2.parent().isTriggerObject();
80
81 MCCollisionEvent ev1(rect2.parent(), obbox1.vertex(i));
82 MCCollisionEvent ev2(rect1.parent(), obbox1.vertex(i));
83
84 const bool areColliding = areCurrentlyColliding(rect1.parent(), rect2.parent());
85 if (!areColliding)
86 {
87 MCObject::sendEvent(rect1.parent(), ev1);
88 MCObject::sendEvent(rect2.parent(), ev2);
89 }
90
91 m_currentCollisions[&rect1.parent()].insert(&rect2.parent());
92
93 if ((ev1.accepted() && ev2.accepted()) || areColliding)
94 {
95 if (!triggerObjectInvolved)
96 {
97 MCVector2dF contactNormal;
98 MCVector2dF vertex = obbox1.vertex(i);
99 float depth = rect2.interpenetrationDepth(
100 MCSegment<float>(vertex, rect1.location()), contactNormal);
101
102 {
103 MCContact & contact = MCContact::create();
104 contact.init(rect2.parent(), vertex, contactNormal, depth);
105 rect1.parent().addContact(contact);
106 }
107
108 {
109 MCContact & contact = MCContact::create();
110 contact.init(rect1.parent(), vertex, -contactNormal, depth);
111 rect2.parent().addContact(contact);
112 }
113 }
114
115 collided = true;
116 }
117
118 // Don't break here in the case of a collision, because we don't know
119 // yet which contact is the deepest. MCImpulseGenerator handles that.
120 }
121 }
122
123 return collided;
124 }
125
testRectAgainstCircle(MCRectShape & rect,MCCircleShape & circle)126 bool MCCollisionDetector::testRectAgainstCircle(MCRectShape & rect, MCCircleShape & circle)
127 {
128 bool collided = false;
129
130 const MCOBBox<float> & obbox(rect.obbox());
131
132 // Loop through all vertices of the rect and find possible contact points with
133 // the circle. This algorithm is not perfectly accurate, but will do the job.
134 for (unsigned int i = 0; i < 5; i++)
135 {
136 MCVector2dF rectVertex;
137 if (i < 4)
138 {
139 rectVertex = obbox.vertex(i);
140 }
141 else
142 {
143 rectVertex = rect.location();
144 }
145
146 MCVector2dF circleVertex(rectVertex - MCVector2dF(circle.location()));
147 circleVertex.clampFast(circle.radius());
148 circleVertex += MCVector2dF(circle.location());
149 if (rect.contains(circleVertex))
150 {
151 const bool triggerObjectInvolved = rect.parent().isTriggerObject() || circle.parent().isTriggerObject();
152
153 MCCollisionEvent ev1(rect.parent(), circleVertex);
154 MCCollisionEvent ev2(circle.parent(), circleVertex);
155
156 const bool areColliding = areCurrentlyColliding(rect.parent(), circle.parent());
157 if (!areColliding)
158 {
159 MCObject::sendEvent(circle.parent(), ev1);
160 MCObject::sendEvent(rect.parent(), ev2);
161 }
162
163 m_currentCollisions[&rect.parent()].insert(&circle.parent());
164
165 if ((ev1.accepted() && ev2.accepted()) || areColliding)
166 {
167 if (!triggerObjectInvolved)
168 {
169 MCVector2dF contactNormal;
170 float depth = rect.interpenetrationDepth(
171 MCSegment<float>(circleVertex, circle.location()), contactNormal);
172
173 {
174 MCContact & contact = MCContact::create();
175 contact.init(rect.parent(), circleVertex, contactNormal, depth);
176 circle.parent().addContact(contact);
177 }
178
179 {
180 MCContact & contact = MCContact::create();
181 contact.init(circle.parent(), circleVertex, -contactNormal, depth);
182 rect.parent().addContact(contact);
183 }
184 }
185
186 collided = true;
187 }
188
189 // Don't break here in the case of a collision, because we don't know
190 // yet which contact is the deepest. MCImpulseGenerator handles that.
191 }
192 }
193
194 return collided;
195 }
196
testCircleAgainstCircle(MCCircleShape & circle1,MCCircleShape & circle2)197 bool MCCollisionDetector::testCircleAgainstCircle(MCCircleShape & circle1, MCCircleShape & circle2)
198 {
199 bool collided = false;
200 MCVector2dF contactNormal;
201 const float depth = circle2.interpenetrationDepth(circle1, contactNormal);
202 const MCVector2dF contactPoint(MCVector2dF(circle1.location()) - contactNormal * circle1.radius());
203
204 if (depth > 0)
205 {
206 const bool triggerObjectInvolved = circle1.parent().isTriggerObject() || circle2.parent().isTriggerObject();
207
208 MCCollisionEvent ev1(circle1.parent(), contactPoint);
209 MCCollisionEvent ev2(circle2.parent(), contactPoint);
210
211 const bool areColliding = areCurrentlyColliding(circle1.parent(), circle2.parent());
212 if (!areColliding)
213 {
214 MCObject::sendEvent(circle2.parent(), ev1);
215 MCObject::sendEvent(circle1.parent(), ev2);
216 }
217
218 m_currentCollisions[&circle1.parent()].insert(&circle2.parent());
219
220 if ((ev1.accepted() && ev2.accepted()) || areColliding)
221 {
222 if (!triggerObjectInvolved)
223 {
224 {
225 MCContact & contact = MCContact::create();
226 contact.init(circle1.parent(), contactPoint, -contactNormal, depth);
227 circle2.parent().addContact(contact);
228 }
229
230 {
231 MCContact & contact = MCContact::create();
232 contact.init(circle1.parent(), contactPoint, contactNormal, depth);
233 circle1.parent().addContact(contact);
234 }
235 }
236
237 collided = true;
238 }
239 }
240
241 return collided;
242 }
243
processPossibleCollision(MCObject & object1,MCObject & object2)244 bool MCCollisionDetector::processPossibleCollision(MCObject & object1, MCObject & object2)
245 {
246 const auto type1 = object1.shape()->type();
247 const auto type2 = object2.shape()->type();
248
249 // Rect against rect
250 if (type1 == MCShape::Type::Rect && type2 == MCShape::Type::Rect)
251 {
252 // Test is not symmetric: must be done both ways
253 return testRectAgainstRect(
254 *static_cast<MCRectShape *>(object1.shape().get()),
255 *static_cast<MCRectShape *>(object2.shape().get()))
256 || testRectAgainstRect(
257 *static_cast<MCRectShape *>(object2.shape().get()),
258 *static_cast<MCRectShape *>(object1.shape().get()));
259 }
260 // Rect against circle: Case 1
261 else if (type1 == MCShape::Type::Rect && type2 == MCShape::Type::Circle)
262 {
263 return testRectAgainstCircle(
264 *static_cast<MCRectShape *>(object1.shape().get()),
265 *static_cast<MCCircleShape *>(object2.shape().get()));
266 }
267 // Rect against circle: Case 2
268 else if (type2 == MCShape::Type::Rect && type1 == MCShape::Type::Circle)
269 {
270 return testRectAgainstCircle(
271 *static_cast<MCRectShape *>(object2.shape().get()),
272 *static_cast<MCCircleShape *>(object1.shape().get()));
273 }
274 // Circle against circle
275 else if (type1 == MCShape::Type::Circle && type2 == MCShape::Type::Circle)
276 {
277 // This test is symmetric
278 return testCircleAgainstCircle(
279 *static_cast<MCCircleShape *>(object1.shape().get()),
280 *static_cast<MCCircleShape *>(object2.shape().get()));
281 }
282
283 return false;
284 }
285
detectCollisions(MCObjectGrid & objectGrid)286 unsigned int MCCollisionDetector::detectCollisions(MCObjectGrid & objectGrid)
287 {
288 unsigned int numCollisions = 0;
289
290 for (auto && iter : objectGrid.getPossibleCollisions())
291 {
292 numCollisions += processPossibleCollision(*iter.first, *iter.second);
293 }
294
295 // For completeness iterate here if no possible collisions, but there still
296 // are old collisions. This can happen if colliding objects gets separated by
297 // directly translating them elsewhere.
298 if (!numCollisions && !m_currentCollisions.empty())
299 {
300 return iterateCurrentCollisions();
301 }
302
303 return numCollisions;
304 }
305
iterateCurrentCollisions()306 unsigned int MCCollisionDetector::iterateCurrentCollisions()
307 {
308 unsigned int numCollisions = 0;
309
310 std::vector<std::pair<MCObject *, MCObject *>> removedCollisions;
311
312 for (auto && outer : m_currentCollisions)
313 {
314 for (auto && inner : outer.second)
315 {
316 if (!processPossibleCollision(*outer.first, *inner))
317 {
318 removedCollisions.push_back({ outer.first, inner });
319 }
320 else
321 {
322 numCollisions++;
323 }
324 }
325 }
326
327 for (auto && collisionPair : removedCollisions)
328 {
329 m_currentCollisions[collisionPair.first].erase(collisionPair.second);
330 m_currentCollisions[collisionPair.second].erase(collisionPair.first);
331
332 MCSeparationEvent ev1(*collisionPair.second);
333 collisionPair.first->event(ev1);
334
335 MCSeparationEvent ev2(*collisionPair.first);
336 collisionPair.second->event(ev2);
337 }
338
339 return numCollisions;
340 }
341