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