- fixed some crashes where there was an invalid tile number
[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 "sector.h"
26 #include "tilemap.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 TileMap& tilemap = *Sector::current()->solids;
48
49   // we make the collision rectangle 1 pixel smaller
50   int starttilex = int(base.x+1) / 32;
51   int starttiley = int(base.y+1) / 32;
52   int max_x = int(base.x + base.width);
53   int max_y = int(base.y + base.height);
54
55   for(int x = starttilex; x*32 < max_x; ++x) {
56     for(int y = starttiley; y*32 < max_y; ++y) {
57       Tile* tile = tilemap.get_tile(x, y);
58       if(tile && tile->attributes & Tile::SOLID)
59         return true;
60     }
61   }
62
63   return false;
64 }
65
66 void* collision_func(const base_type& base, tiletestfunction function)
67 {
68   const TileMap& tilemap = *Sector::current()->solids;
69   
70   int starttilex = int(base.x) / 32;
71   int starttiley = int(base.y) / 32;
72   int max_x = int(base.x + base.width);
73   int max_y = int(base.y + base.height);
74
75   for(int x = starttilex; x*32 < max_x; ++x) {
76     for(int y = starttiley; y*32 < max_y; ++y) {
77       Tile* tile = tilemap.get_tile(x, y);
78       void* result = function(tile);
79       if(result != 0)
80         return result;
81     }
82   }
83
84   return 0;
85 }
86
87 static void* test_goal_tile_function(Tile* tile)
88 {
89   if(tile && (tile->attributes & Tile::GOAL))
90     return tile;
91   return 0;
92 }
93
94 Tile* collision_goal(const base_type& base)
95 {
96   return (Tile*) collision_func(base, test_goal_tile_function);
97 }
98
99 void collision_swept_object_map(base_type* old, base_type* current)
100 {
101   int steps; /* Used to speed up the collision tests, by stepping every 16pixels in the path. */
102   int h;
103   float lpath; /* Holds the longest path, which is either in X or Y direction. */
104   float xd,yd; /* Hold the smallest steps in X and Y directions. */
105   float temp, xt, yt; /* Temporary variable. */
106
107   lpath = 0;
108   xd = 0;
109   yd = 0;
110
111   if(old->x == current->x && old->y == current->y)
112     {
113       return;
114     }
115   else if(old->x == current->x && old->y != current->y)
116     {
117       lpath = current->y - old->y;
118       if(lpath < 0)
119         {
120           yd = -1;
121           lpath = -lpath;
122         }
123       else
124         {
125           yd = 1;
126         }
127
128       h = 1;
129       xd = 0;
130     }
131   else if(old->x != current->x && old->y == current->y)
132     {
133       lpath = current->x - old->x;
134       if(lpath < 0)
135         {
136           xd = -1;
137           lpath = -lpath;
138         }
139       else
140         {
141           xd = 1;
142         }
143       h = 2;
144       yd = 0;
145     }
146   else
147     {
148       lpath = current->x - old->x;
149       if(lpath < 0)
150         lpath = -lpath;
151       if(current->y - old->y > lpath || old->y - current->y > lpath)
152         lpath = current->y - old->y;
153       if(lpath < 0)
154         lpath = -lpath;
155       h = 3;
156       xd = (current->x - old->x) / lpath;
157       yd = (current->y - old->y) / lpath;
158     }
159
160   steps = (int)(lpath / (float)16);
161
162   float orig_x = old->x;
163   float orig_y = old->y;
164   old->x += xd;
165   old->y += yd;
166
167   for(float i = 0; i <= lpath; old->x += xd, old->y += yd, ++i)
168     {
169       if(steps > 0)
170         {
171           old->y += yd*16.;
172           old->x += xd*16.;
173           steps--;
174         }
175
176       if(collision_object_map(*old))
177         {
178           switch(h)
179             {
180             case 1:
181               current->y = old->y - yd;
182               while(collision_object_map(*current))
183                 current->y -= yd;
184               break;
185             case 2:
186               current->x = old->x - xd;
187               while(collision_object_map(*current))
188                 current->x -= xd;
189               break;
190             case 3:
191               xt = current->x;
192               yt = current->y;
193               current->x = old->x - xd;
194               current->y = old->y - yd;
195               while(collision_object_map(*current))
196                 {
197                   current->x -= xd;
198                   current->y -= yd;
199                 }
200
201               temp = current->x;
202               current->x = xt;
203               if(!collision_object_map(*current))
204                 break;
205               current->x = temp;
206               temp = current->y;
207               current->y = yt;
208
209               if(!collision_object_map(*current))
210                 {
211                   break;
212                 }
213               else
214                 {
215                   current->y = temp;
216                   while(!collision_object_map(*current))
217                     current->y += yd;
218                   current->y -= yd;
219                   break;
220                 }
221
222               break;
223             default:
224               break;
225             }
226           break;
227         }
228     }
229
230   if((xd > 0 && current->x < orig_x) || (xd < 0 && current->x > orig_x))
231     current->x = orig_x;
232   if((yd > 0 && current->y < orig_y) || (yd < 0 && current->y > orig_y))
233     current->y = orig_y;
234
235   *old = *current;
236 }
237
238 Tile* gettile(float x, float y)
239 {
240   const TileMap& tilemap = *Sector::current()->solids;
241   return tilemap.get_tile_at(Vector(x, y));
242 }
243
244 bool issolid(float x, float y)
245 {
246   Tile* tile = gettile(x,y);
247   return tile && (tile->attributes & Tile::SOLID);
248 }
249
250 bool isbrick(float x, float y)
251 {
252   Tile* tile = gettile(x,y);
253   return tile && (tile->attributes & Tile::BRICK);
254 }
255
256 bool isice(float x, float y)
257 {
258   Tile* tile = gettile(x,y);
259   return tile && (tile->attributes & Tile::ICE);
260 }
261
262 bool isspike(float x, float y)
263 {
264   Tile* tile = gettile(x,y);
265   return tile && (tile->attributes & Tile::SPIKE);
266 }
267
268 bool isfullbox(float x, float y)
269 {
270   Tile* tile = gettile(x,y);
271   return tile && (tile->attributes & Tile::FULLBOX);
272 }
273
274 bool iscoin(float x, float y)
275 {
276   Tile* tile = gettile(x,y);
277   return tile && (tile->attributes & Tile::COIN);
278 }
279
280 /* EOF */
281
282