bdf96917c9c93c7cc09f932eb9ff86a3f8c65ef9
[supertux.git] / src / supertux / tile.cpp
1 //  SuperTux
2 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
3 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
4 //  Copyright (C) 2010 Florian Forster <supertux at octo.it>
5 //
6 //  This program is free software: you can redistribute it and/or modify
7 //  it under the terms of the GNU General Public License as published by
8 //  the Free Software Foundation, either version 3 of the License, or
9 //  (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, see <http://www.gnu.org/licenses/>.
18
19 #include "supertux/tile.hpp"
20
21 #include "supertux/constants.hpp"
22 #include "supertux/tile_set.hpp"
23 #include "math/aatriangle.hpp"
24 #include "video/drawing_context.hpp"
25
26 bool Tile::draw_editor_images = false;
27
28 Tile::Tile() :
29   imagespecs(),
30   images(),
31   editor_imagespecs(),
32   editor_images(),
33   attributes(0), 
34   data(0), 
35   fps(1)
36 {
37 }
38
39 Tile::Tile(const std::vector<ImageSpec>& imagespecs_, const std::vector<ImageSpec>& editor_imagespecs_, 
40            uint32_t attributes, uint32_t data, float fps) :
41   imagespecs(imagespecs_),
42   images(),
43   editor_imagespecs(editor_imagespecs_),
44   editor_images(),
45   attributes(attributes), 
46   data(data), 
47   fps(fps)
48 {
49   correct_attributes();
50 }
51
52 Tile::~Tile()
53 {
54 }
55
56 void
57 Tile::load_images()
58 {
59   if(images.size() == 0 && imagespecs.size() != 0)
60   {
61     assert(images.size() == 0);
62     for(std::vector<ImageSpec>::iterator i = imagespecs.begin(); i != imagespecs.end(); ++i) 
63     {
64       const ImageSpec& spec = *i;
65
66       SurfacePtr surface;
67       if(spec.rect.get_width() <= 0) 
68       {
69         surface = Surface::create(spec.file);
70       }
71       else 
72       {
73         surface = Surface::create(spec.file,
74                                   Rect((int) spec.rect.p1.x,
75                                        (int) spec.rect.p1.y,
76                                        Size((int) spec.rect.get_width(),
77                                             (int) spec.rect.get_height())));
78       }
79       images.push_back(surface);
80     }
81   }
82
83   if(editor_images.size() == 0 && editor_imagespecs.size() != 0)
84   {
85     assert(editor_images.size() == 0);
86     for(std::vector<ImageSpec>::iterator i = editor_imagespecs.begin(); i != editor_imagespecs.end(); ++i) 
87     {
88       const ImageSpec& spec = *i;
89
90       SurfacePtr surface;
91       if(spec.rect.get_width() <= 0) 
92       {
93         surface = Surface::create(spec.file);
94       }
95       else 
96       {
97         surface = Surface::create(spec.file,
98                                   Rect((int) spec.rect.p1.x,
99                                        (int) spec.rect.p1.y,
100                                        Size((int) spec.rect.get_width(),
101                                             (int) spec.rect.get_height())));
102       }
103       editor_images.push_back(surface);
104     }
105   }
106 }
107
108 void
109 Tile::draw(DrawingContext& context, const Vector& pos, int z_pos) const
110 {
111   if(draw_editor_images) {
112     if(editor_images.size() > 1) {
113       size_t frame = size_t(game_time * fps) % editor_images.size();
114       context.draw_surface(editor_images[frame], pos, z_pos);
115       return;
116     } else if (editor_images.size() == 1) {
117       context.draw_surface(editor_images[0], pos, z_pos);
118       return;
119     }
120   }
121
122   if(images.size() > 1) {
123     size_t frame = size_t(game_time * fps) % images.size();
124     context.draw_surface(images[frame], pos, z_pos);
125   } else if (images.size() == 1) {
126     context.draw_surface(images[0], pos, z_pos);
127   }
128 }
129
130 void
131 Tile::correct_attributes()
132 {
133   //Fix little oddities in attributes (not many, currently...)
134   if(!(attributes & SOLID) && (attributes & SLOPE || attributes & UNISOLID)) {
135     attributes |= SOLID;
136     //But still be vocal about it
137     log_warning << "Tile with image " << imagespecs[0].file << " needs solid attribute." << std::endl;
138   }
139 }
140
141 void
142 Tile::print_debug(int id) const
143 {
144   log_debug << " Tile: id " << id << ", data " << getData() << ", attributes " << getAttributes() << ":" << std::endl;
145   for(std::vector<Tile::ImageSpec>::const_iterator im = editor_imagespecs.begin(); im != editor_imagespecs.end(); ++im) 
146     log_debug << "  Editor Imagespec: file " << im->file << "; rect " << im->rect << std::endl;
147   for(std::vector<Tile::ImageSpec>::const_iterator im = imagespecs.begin(); im != imagespecs.end(); ++im) 
148     log_debug << "  Imagespec:        file " << im->file << "; rect " << im->rect << std::endl;
149 }
150
151 /* Returns zero if a unisolid tile is non-solid due to the movement direction,
152  * non-zero if the tile is solid due to direction. */
153 int Tile::check_movement_unisolid (const Vector movement) const
154 {
155   int slope_info;
156   double mv_x;
157   double mv_y;
158   double mv_tan;
159   double slope_tan;
160
161 #define MV_NON_SOLID 0
162 #define MV_SOLID 1
163
164   /* If the tile is not a slope, this is very easy. */
165   if (!this->is_slope ())
166   {
167     int dir = this->getData () & ((int) Tile::UNI_DIR_MASK);
168
169     if (((dir == Tile::UNI_DIR_NORTH) && (movement.y >= 0)) /* moving down */
170         || ((dir == Tile::UNI_DIR_SOUTH) && (movement.y < 0)) /* moving up */
171         || ((dir == Tile::UNI_DIR_WEST) && (movement.x >= 0)) /* moving right */
172         || ((dir == Tile::UNI_DIR_EAST) && (movement.x < 0))) /* moving left */
173       return MV_SOLID;
174     else
175       return MV_NON_SOLID;
176   }
177
178   /* Initialize mv_x and mv_y. Depending on the slope the axis are inverted so
179    * that we can always use the "SOUTHEAST" case of the slope. The southeast
180    * case is the following:
181    *     .
182    *    /!
183    *   / !
184    *  +--+
185    */
186   mv_x = (double) movement.x;
187   mv_y = (double) movement.y;
188
189   slope_info = this->getData();
190   switch (slope_info & AATriangle::DIRECTION_MASK)
191   {
192     case AATriangle::SOUTHEAST: /*    . */
193       /* do nothing */          /*   /! */
194       break;                    /*  / ! */
195                                 /* +--+ */
196     case AATriangle::SOUTHWEST: /* .    */
197       mv_x *= (-1.0);           /* !\   */
198       break;                    /* ! \  */
199                                 /* +--+ */
200     case AATriangle::NORTHEAST: /* +--+ */
201       mv_y *= (-1.0);           /*  \ ! */
202       break;                    /*   \! */
203                                 /*    ' */
204     case AATriangle::NORTHWEST: /* +--+ */
205       mv_x *= (-1.0);           /* ! /  */
206       mv_y *= (-1.0);           /* !/   */
207       break;                    /* '    */
208   } /* switch (slope_info & DIRECTION_MASK) */
209
210   /* Handle the easy cases first */
211   /* If we're moving to the right and down, then the slope is solid. */
212   if ((mv_x >= 0.0) && (mv_y >= 0.0)) /* 4th quadrant */
213     return MV_SOLID;
214   /* If we're moving to the left and up, then the slope is not solid. */
215   else if ((mv_x <= 0.0) && (mv_y <= 0.0)) /* 2nd quadrant */
216     return MV_NON_SOLID;
217
218   /* The pure up-down and left-right movements have already been handled. */
219   assert (mv_x != 0.0);
220   assert (mv_y != 0.0);
221
222   /* calculate tangent of movement */
223   mv_tan = (-1.0) * mv_y / mv_x;
224
225   /* determine tangent of the slope */
226   slope_tan = 1.0;
227   if (((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_BOTTOM)
228       || ((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_TOP))
229     slope_tan = 0.5; /* ~= 26.6 deg */
230   else if (((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_LEFT)
231       || ((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_RIGHT))
232     slope_tan = 2.0; /* ~= 63.4 deg */
233
234   /* up and right */
235   if (mv_x > 0.0) /* 1st quadrant */
236   {
237     assert (mv_y < 0.0);
238     if (mv_tan <= slope_tan)
239       return MV_SOLID;
240     else
241       return MV_NON_SOLID;
242   }
243   /* down and left */
244   else if (mv_x < 0.0) /* 3rd quadrant */
245   {
246     assert (mv_y > 0.0);
247     if (mv_tan >= slope_tan)
248       return MV_SOLID;
249     else
250       return MV_NON_SOLID;
251   }
252
253   assert (1 != 1);
254   return (-1);
255
256 #undef MV_NON_SOLID
257 #undef MV_SOLID
258 } /* int check_movement_unisolid */
259
260 int is_above_line (float l_x, float l_y, float m,
261     float p_x, float p_y)
262 {
263   float interp_y = (l_y + (m * (p_x - l_x)));
264   if (interp_y == p_y)
265     return (1);
266   else if (interp_y > p_y)
267     return (1);
268   else
269     return (0);
270 }
271
272 int is_below_line (float l_x, float l_y, float m,
273     float p_x, float p_y)
274 {
275   if (is_above_line (l_x, l_y, m, p_x, p_y))
276     return (0);
277   else
278     return (1);
279 }
280
281 int Tile::check_position_unisolid (const Rectf& obj_bbox,
282     const Rectf& tile_bbox) const
283 {
284   int slope_info;
285   float tile_x;
286   float tile_y;
287   float gradient;
288   float delta_x;
289   float delta_y;
290   float obj_x;
291   float obj_y;
292
293 #define POS_NON_SOLID 0
294 #define POS_SOLID 1
295
296   /* If this is not a slope, this is - again - easy */
297   if (!this->is_slope ())
298   {
299     int dir = this->getData () & Tile::UNI_DIR_MASK;
300
301     if ((dir == Tile::UNI_DIR_NORTH)
302         && ((obj_bbox.get_bottom () - SHIFT_DELTA) <= tile_bbox.get_top ()))
303       return POS_SOLID;
304     else if ((dir == Tile::UNI_DIR_SOUTH)
305         && ((obj_bbox.get_top () + SHIFT_DELTA) >= tile_bbox.get_bottom ()))
306       return POS_SOLID;
307     else if ((dir == Tile::UNI_DIR_WEST)
308         && ((obj_bbox.get_right () - SHIFT_DELTA) <= tile_bbox.get_left ()))
309       return POS_SOLID;
310     else if ((dir == Tile::UNI_DIR_EAST)
311         && ((obj_bbox.get_left () + SHIFT_DELTA) >= tile_bbox.get_right ()))
312       return POS_SOLID;
313
314     return POS_NON_SOLID;
315   }
316
317   /* There are 20 different cases. For each case, calculate a line that
318    * describes the slope's surface. The line is defined by x, y, and m, the
319    * gradient. */
320   slope_info = this->getData();
321   switch (slope_info
322       & (AATriangle::DIRECTION_MASK | AATriangle::DEFORM_MASK))
323   {
324     case AATriangle::SOUTHWEST:
325     case AATriangle::SOUTHWEST | AATriangle::DEFORM_TOP:
326     case AATriangle::SOUTHWEST | AATriangle::DEFORM_LEFT:
327     case AATriangle::NORTHEAST:
328     case AATriangle::NORTHEAST | AATriangle::DEFORM_TOP:
329     case AATriangle::NORTHEAST | AATriangle::DEFORM_LEFT:
330       tile_x = tile_bbox.get_left ();
331       tile_y = tile_bbox.get_top ();
332       gradient = 1.0;
333       break;
334
335     case AATriangle::SOUTHEAST:
336     case AATriangle::SOUTHEAST | AATriangle::DEFORM_TOP:
337     case AATriangle::SOUTHEAST | AATriangle::DEFORM_RIGHT:
338     case AATriangle::NORTHWEST:
339     case AATriangle::NORTHWEST | AATriangle::DEFORM_TOP:
340     case AATriangle::NORTHWEST | AATriangle::DEFORM_RIGHT:
341       tile_x = tile_bbox.get_right ();
342       tile_y = tile_bbox.get_top ();
343       gradient = -1.0;
344       break;
345
346     case AATriangle::SOUTHEAST | AATriangle::DEFORM_BOTTOM:
347     case AATriangle::SOUTHEAST | AATriangle::DEFORM_LEFT:
348     case AATriangle::NORTHWEST | AATriangle::DEFORM_BOTTOM:
349     case AATriangle::NORTHWEST | AATriangle::DEFORM_LEFT:
350       tile_x = tile_bbox.get_left ();
351       tile_y = tile_bbox.get_bottom ();
352       gradient = -1.0;
353       break;
354
355     case AATriangle::SOUTHWEST | AATriangle::DEFORM_BOTTOM:
356     case AATriangle::SOUTHWEST | AATriangle::DEFORM_RIGHT:
357     case AATriangle::NORTHEAST | AATriangle::DEFORM_BOTTOM:
358     case AATriangle::NORTHEAST | AATriangle::DEFORM_RIGHT:
359       tile_x = tile_bbox.get_right ();
360       tile_y = tile_bbox.get_bottom ();
361       gradient = 1.0;
362       break;
363
364     default:
365       assert (23 == 42);
366       return POS_NON_SOLID;
367   }
368
369   /* delta_x, delta_y: Gradient aware version of SHIFT_DELTA. Here, we set the
370    * sign of the values only. Also, we determine here which corner of the
371    * object's bounding box is the interesting one for us. */
372   delta_x = 1.0 * SHIFT_DELTA;
373   delta_y = 1.0 * SHIFT_DELTA;
374   switch (slope_info & AATriangle::DIRECTION_MASK)
375   {
376     case AATriangle::SOUTHWEST:
377       delta_x *= 1.0;
378       delta_y *= -1.0;
379       obj_x = obj_bbox.get_left ();
380       obj_y = obj_bbox.get_bottom ();
381       break;
382
383     case AATriangle::SOUTHEAST:
384       delta_x *= -1.0;
385       delta_y *= -1.0;
386       obj_x = obj_bbox.get_right ();
387       obj_y = obj_bbox.get_bottom ();
388       break;
389
390     case AATriangle::NORTHWEST:
391       delta_x *= 1.0;
392       delta_y *= 1.0;
393       obj_x = obj_bbox.get_left ();
394       obj_y = obj_bbox.get_top ();
395       break;
396
397     case AATriangle::NORTHEAST:
398       delta_x *= -1.0;
399       delta_y *= 1.0;
400       obj_x = obj_bbox.get_right ();
401       obj_y = obj_bbox.get_top ();
402       break;
403   }
404
405   /* Adapt the delta_x, delta_y and the gradient for the 26.6 deg and 63.4 deg
406    * cases. */
407   switch (slope_info & AATriangle::DEFORM_MASK)
408   {
409     case 0:
410       delta_x *= .70710678118654752440; /* 1/sqrt(2) */
411       delta_y *= .70710678118654752440; /* 1/sqrt(2) */
412       break;
413
414     case AATriangle::DEFORM_BOTTOM:
415     case AATriangle::DEFORM_TOP:
416       delta_x *= .44721359549995793928; /* 1/sqrt(5) */
417       delta_y *= .89442719099991587856; /* 2/sqrt(5) */
418       gradient *= 0.5;
419       break;
420
421     case AATriangle::DEFORM_LEFT:
422     case AATriangle::DEFORM_RIGHT:
423       delta_x *= .89442719099991587856; /* 2/sqrt(5) */
424       delta_y *= .44721359549995793928; /* 1/sqrt(5) */
425       gradient *= 2.0;
426       break;
427   }
428
429   /* With a south slope, check if all points are above the line. If one point
430    * isn't, the slope is not solid. => You can pass through a south-slope from
431    * below but not from above. */
432   if (((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHWEST)
433       || ((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHEAST))
434   {
435     if (is_below_line (tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y))
436       return (POS_NON_SOLID);
437     else
438       return (POS_SOLID);
439   }
440   /* northwest or northeast. Same as above, but inverted. You can pass from top
441    * to bottom but not vice versa. */
442   else
443   {
444     if (is_above_line (tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y))
445       return (POS_NON_SOLID);
446     else
447       return (POS_SOLID);
448   }
449
450 #undef POS_NON_SOLID
451 #undef POS_SOLID
452 } /* int check_position_unisolid */
453
454 bool Tile::is_solid (const Rectf& tile_bbox, const Rectf& position, const Vector& movement) const
455 {
456   int status;
457
458   if ((attributes & SOLID) == 0)
459     return (false);
460
461   if ((attributes & UNISOLID) == 0)
462     return (true);
463
464   /* Check if the tile is solid given the current movement. This works
465    * for south-slopes (which are solid when moving "down") and
466    * north-slopes (which are solid when moving "up". "up" and "down" is
467    * in quotation marks because because the slope's gradient is taken.
468    * Also, this uses the movement relative to the tilemaps own movement
469    * (if any).  --octo */
470   status = check_movement_unisolid (movement);
471   /* If zero is returned, the unisolid tile is non-solid. */
472   if (status == 0)
473     return (false);
474
475   /* Check whether the object is already *in* the tile. If so, the tile
476    * is non-solid. Otherwise, if the object is "above" (south slopes)
477    * or "below" (north slopes), the tile will be solid. */
478   status = check_position_unisolid (position, tile_bbox);
479   if (status == 0)
480     return (false);
481
482   return (true);
483 } /* bool Tile::is_solid */
484
485 /* vim: set sw=2 sts=2 et : */
486 /* EOF */