optomized collisions to exclude half of the tests
[physics.git] / src / collisionManager.cpp
1 /*
2  *  Copyright (C) 2008 Patrik Gornicz, Gornicz_P (at) hotmail (dot) com.
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "collisionManager.h"
19 #include "debug.h"
20
21 #include "Vector2.h"
22
23 #include "Entities/Ball.h"
24 #include "Entities/PhysicsEntity.h"
25
26 #include "CollisionInfo.h"
27
28 /// ***** Private Class Header *****
29
30 /// ***** Private Method Headers *****
31
32 void clearEntities();
33 void placeEntity(PhysicsEntity* p);
34 void updateEntities();
35
36 void applyCollision(PhysicsEntity* p1, PhysicsEntity* p2);
37 void applyCollision(Ball* b1, Ball* b2);
38
39 bool getInfo(Ball* b1, Ball* b2, CollisionInfo* cInfo);
40
41 /// ***** Private Variables *****
42
43 const int xDivisions = 20;
44 const int yDivisions = 16;
45 const int screenX    = 800;
46 const int screenY    = 600;
47
48 setPhys divisions[xDivisions][yDivisions];
49
50 /// ***** Initializers/Cleaners *****
51
52 void collision::init()
53 {
54
55 }
56 void collision::clean()
57 {
58
59 }
60
61 /// ***** Public Methods *****
62
63 void collision::update(setPhys& sp)
64 {
65     clearEntities();
66
67     for( setPhys::iterator it = sp.begin();
68          it != sp.end();
69          it++ )
70     {
71         placeEntity(*it);
72     }
73
74     updateEntities();
75 }
76
77 /// ***** Private Methods *****
78
79 void clearEntities()
80 {
81     for( int x = 0;
82              x < xDivisions;
83              x++ )
84     {
85         for( int y = 0;
86                  y < yDivisions;
87                  y++ )
88         {
89             divisions[x][y].clear();
90         }
91     }
92 }
93
94 void placeEntity(PhysicsEntity* p)
95 {
96     Ball* b = dynamic_cast<Ball*>(p);
97
98     if( b == NULL )
99     {
100         DPF(0, "ENTITY TYPE NOT SUPPORTED BY placeEntity()!!");
101         return;
102     }
103
104     const float& xb     = b->positionRaw().x;
105     const float& yb     = b->positionRaw().y;
106     const float& rad    = b->radius;
107
108     for( int x =  static_cast<int>( (xb - rad) / (screenX / xDivisions) );
109              x <= static_cast<int>( (xb + rad) / (screenX / xDivisions) );
110              x++ )
111     {
112         if(x < 0 || xDivisions <= x)
113             break;
114
115         for( int y =  static_cast<int>( (yb - rad) / (screenY / yDivisions) );
116                  y <= static_cast<int>( (yb + rad) / (screenY / yDivisions) );
117                  y++ )
118         {
119             if(y < 0 || yDivisions <= y)
120                 break;
121
122             divisions[x][y].insert(p);
123         }
124     }
125 }
126
127 void updateEntities()
128 {
129     for( int x = 0;
130              x < xDivisions;
131              x++ )
132     {
133         for( int y = 0;
134                  y < yDivisions;
135                  y++ )
136         {
137             for( setPhys::iterator it1 = divisions[x][y].begin();
138                  it1 != divisions[x][y].end();
139                  it1++ )
140             {
141                 for( setPhys::iterator it2 = divisions[x][y].begin();
142                      it2 != divisions[x][y].end();
143                      it2++ )
144                 {
145                     if( *it1 == *it2 )
146                         break;
147
148                     applyCollision(*it1, *it2);
149                 }
150             }
151         }
152     }
153 }
154
155 void applyCollision(PhysicsEntity* p1, PhysicsEntity* p2)
156 {
157     Ball* b1 = dynamic_cast<Ball*>(p1);
158     Ball* b2 = dynamic_cast<Ball*>(p2);
159
160     if( b1 != NULL && b2 != NULL )
161     {
162         applyCollision(b1, b2);
163         return;
164     }
165
166     DPF(0, "ENTITY TYPE NOT SUPPORTED BY applyCollision()!!");
167 }
168
169 void applyCollision(Ball* b1, Ball* b2)
170 {
171     CollisionInfo   cInfo;
172
173     if(!getInfo(b1, b2, &cInfo))
174         return;
175
176     // a few values to simplify the equations
177     const Vector2& normal = cInfo.normal;
178     const Vector2& point  = cInfo.point;
179
180     float m1 = b1->mass;
181     float m2 = b2->mass;
182
183     float e = (b1->CoR + b2->CoR) / 2;
184
185     Vector2 v1 = b1->velocityRaw();
186     Vector2 v2 = b2->velocityRaw();
187
188
189     float iTop = -(e + 1) * (v1 - v2).dot(normal);
190
191     // otherwise the collision happened and we do the math the below assumes
192     // collisions have no friction
193
194     float impulse = iTop / (normal.dot(normal) * (1 / m1 + 1 / m2));
195
196     b1->applyImpulse(impulse / m1 * normal, point);
197     b2->applyImpulse(-impulse / m2 * normal, point);
198 }
199
200 bool getInfo(Ball* b1, Ball* b2, CollisionInfo* pcInfo)
201 {
202     // a few values to simplify the equations
203     float r1 = b1->radius;
204     float r2 = b2->radius;
205
206     Vector2 p1 = b1->positionRaw();
207     Vector2 p2 = b2->positionRaw();
208     Vector2 v1 = b1->velocityRaw();
209     Vector2 v2 = b2->velocityRaw();
210
211     // quick binding box check
212     if (p1.x - r1 > p2.x + r2
213      || p1.x + r1 < p2.x - r2
214      || p1.y - r1 > p2.y + r2
215      || p1.y + r1 < p2.y - r2)
216         return false;
217
218     // test if not touching
219     if ((p1 - p2).sqrLength() >= (r1 + r2)*(r1 + r2))
220         return false;
221
222     // test if they are moving apart in some way if they aren't it's likely
223     // that they collided last frame and are still overlapping
224
225     if ((v1 - v2).dot(p1 - p2) >= 0)
226         return false;
227
228     pcInfo->point   = p1 - (p1 - p2) * r1 / (r1 + r2);
229     pcInfo->normal  = p1 - p2;
230
231     return true;
232 }