TODO update
[supertux.git] / src / collision.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 // 
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 //  02111-1307, USA.
20
21 #include "defines.h"
22 #include "collision.h"
23 #include "bitmask.h"
24 #include "scene.h"
25 #include "world.h"
26 #include "level.h"
27 #include "tile.h"
28
29 bool rectcollision(const base_type& one, const base_type& two)
30 {
31   return (one.x >= two.x - one.width + 1  &&
32           one.x <= two.x + two.width - 1  &&
33           one.y >= two.y - one.height + 1 &&
34           one.y <= two.y + two.height - 1);
35 }
36
37 bool rectcollision_offset(const base_type& one, const base_type& two, float off_x, float off_y)
38 {
39   return (one.x >= two.x - one.width  + off_x + 1 &&
40           one.x <= two.x + two.width  + off_x - 1 &&
41           one.y >= two.y - one.height + off_y + 1 &&
42           one.y <= two.y + two.height + off_y - 1);
43 }
44
45 bool collision_object_map(const base_type& base)
46 {
47   const Level& level = *World::current()->get_level();
48   TileManager& tilemanager = *TileManager::instance();
49
50   // we make the collision rectangle 1 pixel smaller
51   int starttilex = int(base.x+1) / 32;
52   int starttiley = int(base.y+1) / 32;
53   int max_x = int(base.x + base.width);
54   int max_y = int(base.y + base.height);
55
56   for(int x = starttilex; x*32 < max_x; ++x) {
57     for(int y = starttiley; y*32 < max_y; ++y) {
58       Tile* tile = tilemanager.get(level.get_tile_at(x, y));
59       if(tile && tile->solid)
60         return true;
61     }
62   }
63
64   return false;
65 }
66
67 void* collision_func(const base_type& base, tiletestfunction function)
68 {
69   const Level& level = *World::current()->get_level();
70   TileManager& tilemanager = *TileManager::instance();
71   
72   int starttilex = int(base.x) / 32;
73   int starttiley = int(base.y) / 32;
74   int max_x = int(base.x + base.width);
75   int max_y = int(base.y + base.height);
76
77   for(int x = starttilex; x*32 < max_x; ++x) {
78     for(int y = starttiley; y*32 < max_y; ++y) {
79       Tile* tile = tilemanager.get(level.get_tile_at(x, y));
80       void* result = function(tile);
81       if(result != 0)
82         return result;
83     }
84   }
85
86   return 0;
87 }
88
89 static void* test_goal_tile_function(Tile* tile)
90 {
91   if(tile && tile->goal)
92     return tile;
93   return 0;
94 }
95
96 Tile* collision_goal(const base_type& base)
97 {
98   return (Tile*) collision_func(base, test_goal_tile_function);
99 }
100
101 void collision_swept_object_map(base_type* old, base_type* current)
102 {
103   int steps; /* Used to speed up the collision tests, by stepping every 16pixels in the path. */
104   int h;
105   float lpath; /* Holds the longest path, which is either in X or Y direction. */
106   float xd,yd; /* Hold the smallest steps in X and Y directions. */
107   float temp, xt, yt; /* Temporary variable. */
108
109   lpath = 0;
110   xd = 0;
111   yd = 0;
112
113   if(old->x == current->x && old->y == current->y)
114     {
115       return;
116     }
117   else if(old->x == current->x && old->y != current->y)
118     {
119       lpath = current->y - old->y;
120       if(lpath < 0)
121         {
122           yd = -1;
123           lpath = -lpath;
124         }
125       else
126         {
127           yd = 1;
128         }
129
130       h = 1;
131       xd = 0;
132     }
133   else if(old->x != current->x && old->y == current->y)
134     {
135       lpath = current->x - old->x;
136       if(lpath < 0)
137         {
138           xd = -1;
139           lpath = -lpath;
140         }
141       else
142         {
143           xd = 1;
144         }
145       h = 2;
146       yd = 0;
147     }
148   else
149     {
150       lpath = current->x - old->x;
151       if(lpath < 0)
152         lpath = -lpath;
153       if(current->y - old->y > lpath || old->y - current->y > lpath)
154         lpath = current->y - old->y;
155       if(lpath < 0)
156         lpath = -lpath;
157       h = 3;
158       xd = (current->x - old->x) / lpath;
159       yd = (current->y - old->y) / lpath;
160     }
161
162   steps = (int)(lpath / (float)16);
163
164   float orig_x = old->x;
165   float orig_y = old->y;
166   old->x += xd;
167   old->y += yd;
168
169   for(float i = 0; i <= lpath; old->x += xd, old->y += yd, ++i)
170     {
171       if(steps > 0)
172         {
173           old->y += yd*16.;
174           old->x += xd*16.;
175           steps--;
176         }
177
178       if(collision_object_map(*old))
179         {
180           switch(h)
181             {
182             case 1:
183               current->y = old->y - yd;
184               while(collision_object_map(*current))
185                 current->y -= yd;
186               break;
187             case 2:
188               current->x = old->x - xd;
189               while(collision_object_map(*current))
190                 current->x -= xd;
191               break;
192             case 3:
193               xt = current->x;
194               yt = current->y;
195               current->x = old->x - xd;
196               current->y = old->y - yd;
197               while(collision_object_map(*current))
198                 {
199                   current->x -= xd;
200                   current->y -= yd;
201                 }
202
203               temp = current->x;
204               current->x = xt;
205               if(!collision_object_map(*current))
206                 break;
207               current->x = temp;
208               temp = current->y;
209               current->y = yt;
210
211               if(!collision_object_map(*current))
212                 {
213                   break;
214                 }
215               else
216                 {
217                   current->y = temp;
218                   while(!collision_object_map(*current))
219                     current->y += yd;
220                   current->y -= yd;
221                   break;
222                 }
223
224               break;
225             default:
226               break;
227             }
228           break;
229         }
230     }
231
232   if((xd > 0 && current->x < orig_x) || (xd < 0 && current->x > orig_x))
233     current->x = orig_x;
234   if((yd > 0 && current->y < orig_y) || (yd < 0 && current->y > orig_y))
235     current->y = orig_y;
236
237   *old = *current;
238 }
239
240 Tile* gettile(float x, float y)
241 {
242   return TileManager::instance()->get(World::current()->get_level()->gettileid(x, y));
243 }
244
245 bool issolid(float x, float y)
246 {
247   Tile* tile = gettile(x,y);
248   return tile && tile->solid;
249 }
250
251 bool isbrick(float x, float y)
252 {
253   Tile* tile = gettile(x,y);
254   return tile && tile->brick;
255 }
256
257 bool isice(float x, float y)
258 {
259   Tile* tile = gettile(x,y);
260   return tile && tile->ice;
261 }
262
263 bool isfullbox(float x, float y)
264 {
265   Tile* tile = gettile(x,y);
266   return tile && tile->fullbox;
267 }
268
269 bool isdistro(float x, float y)
270 {
271   Tile* tile = gettile(x,y);
272   return tile && tile->distro;
273 }
274
275 /* EOF */
276
277