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