Remove bogus assert
[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 /* Check if the tile is solid given the current movement. This works
152  * for south-slopes (which are solid when moving "down") and
153  * north-slopes (which are solid when moving "up". "up" and "down" is
154  * in quotation marks because because the slope's gradient is taken.
155  * Also, this uses the movement relative to the tilemaps own movement
156  * (if any).  --octo */
157 bool Tile::check_movement_unisolid (const Vector& movement) const
158 {
159   int slope_info;
160   double mv_x;
161   double mv_y;
162   double mv_tan;
163   double slope_tan;
164
165   /* If the tile is not a slope, this is very easy. */
166   if (!this->is_slope())
167   {
168     int dir = this->getData() & Tile::UNI_DIR_MASK;
169
170     return ((dir == Tile::UNI_DIR_NORTH) && (movement.y >= 0))  /* moving down */
171         || ((dir == Tile::UNI_DIR_SOUTH) && (movement.y <= 0))  /* moving up */
172         || ((dir == Tile::UNI_DIR_WEST ) && (movement.x >= 0))  /* moving right */
173         || ((dir == Tile::UNI_DIR_EAST ) && (movement.x <= 0)); /* moving left */
174   }
175
176   /* Initialize mv_x and mv_y. Depending on the slope the axis are inverted so
177    * that we can always use the "SOUTHEAST" case of the slope. The southeast
178    * case is the following:
179    *     .
180    *    /!
181    *   / !
182    *  +--+
183    */
184   mv_x = (double) movement.x; //note switch to double for no good reason
185   mv_y = (double) movement.y;
186
187   slope_info = this->getData();
188   switch (slope_info & AATriangle::DIRECTION_MASK)
189   {
190     case AATriangle::SOUTHEAST: /*    . */
191       /* do nothing */          /*   /! */
192       break;                    /*  / ! */
193                                 /* +--+ */
194     case AATriangle::SOUTHWEST: /* .    */
195       mv_x *= (-1.0);           /* !\   */
196       break;                    /* ! \  */
197                                 /* +--+ */
198     case AATriangle::NORTHEAST: /* +--+ */
199       mv_y *= (-1.0);           /*  \ ! */
200       break;                    /*   \! */
201                                 /*    ' */
202     case AATriangle::NORTHWEST: /* +--+ */
203       mv_x *= (-1.0);           /* ! /  */
204       mv_y *= (-1.0);           /* !/   */
205       break;                    /* '    */
206   } /* switch (slope_info & DIRECTION_MASK) */
207
208   /* Handle the easy cases first */
209   /* If we're moving to the right and down, then the slope is solid. */
210   if ((mv_x >= 0.0) && (mv_y >= 0.0)) /* 4th quadrant */
211     return true;
212   /* If we're moving to the left and up, then the slope is not solid. */
213   else if ((mv_x <= 0.0) && (mv_y <= 0.0)) /* 2nd quadrant */
214     return false;
215
216   /* The pure up-down and left-right movements have already been handled. */
217   assert (mv_x != 0.0);
218   assert (mv_y != 0.0);
219
220   /* calculate tangent of movement */
221   mv_tan = (-1.0) * mv_y / mv_x;
222
223   /* determine tangent of the slope */
224   slope_tan = 1.0;
225   if (((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_BOTTOM)
226       || ((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_TOP))
227     slope_tan = 0.5; /* ~= 26.6 deg */
228   else if (((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_LEFT)
229       || ((slope_info & AATriangle::DEFORM_MASK) == AATriangle::DEFORM_RIGHT))
230     slope_tan = 2.0; /* ~= 63.4 deg */
231
232   /* up and right */
233   if (mv_x > 0.0) /* 1st quadrant */
234   {
235     assert (mv_y < 0.0);
236     return (mv_tan <= slope_tan);
237   }
238   /* down and left */
239   else if (mv_x < 0.0) /* 3rd quadrant */
240   {
241     assert (mv_y > 0.0);
242     return (mv_tan >= slope_tan);
243   }
244
245   return false;
246 } /* int check_movement_unisolid */
247
248 bool is_above_line (float l_x, float l_y, float m,
249                     float p_x, float p_y)
250 {
251   float interp_y = (l_y + (m * (p_x - l_x)));
252   return (interp_y >= p_y);
253 }
254
255 bool is_below_line (float l_x, float l_y, float m,
256                     float p_x, float p_y)
257 {
258   return !is_above_line (l_x, l_y, m, p_x, p_y);
259 }
260
261 /* Check whether the object is already *in* the tile. If so, the tile
262  * is non-solid. Otherwise, if the object is "above" (south slopes)
263  * or "below" (north slopes), the tile will be solid. */
264 bool Tile::check_position_unisolid (const Rectf& obj_bbox,
265                                     const Rectf& tile_bbox) const
266 {
267   int slope_info;
268   float tile_x;
269   float tile_y;
270   float gradient;
271   float delta_x;
272   float delta_y;
273   float obj_x = 0.0;
274   float obj_y = 0.0;
275
276   /* If this is not a slope, this is - again - easy */
277   if (!this->is_slope())
278   {
279     int dir = this->getData() & Tile::UNI_DIR_MASK;
280
281     return ((dir == Tile::UNI_DIR_NORTH) && ((obj_bbox.get_bottom() - SHIFT_DELTA) <= tile_bbox.get_top()   ))
282         || ((dir == Tile::UNI_DIR_SOUTH) && ((obj_bbox.get_top()    + SHIFT_DELTA) >= tile_bbox.get_bottom()))
283         || ((dir == Tile::UNI_DIR_WEST ) && ((obj_bbox.get_right()  - SHIFT_DELTA) <= tile_bbox.get_left()  ))
284         || ((dir == Tile::UNI_DIR_EAST ) && ((obj_bbox.get_left()   + SHIFT_DELTA) >= tile_bbox.get_right() ));
285   }
286
287   /* There are 20 different cases. For each case, calculate a line that
288    * describes the slope's surface. The line is defined by x, y, and m, the
289    * gradient. */
290   slope_info = this->getData();
291   switch (slope_info
292       & (AATriangle::DIRECTION_MASK | AATriangle::DEFORM_MASK))
293   {
294     case AATriangle::SOUTHWEST:
295     case AATriangle::SOUTHWEST | AATriangle::DEFORM_TOP:
296     case AATriangle::SOUTHWEST | AATriangle::DEFORM_LEFT:
297     case AATriangle::NORTHEAST:
298     case AATriangle::NORTHEAST | AATriangle::DEFORM_TOP:
299     case AATriangle::NORTHEAST | AATriangle::DEFORM_LEFT:
300       tile_x = tile_bbox.get_left();
301       tile_y = tile_bbox.get_top();
302       gradient = 1.0;
303       break;
304
305     case AATriangle::SOUTHEAST:
306     case AATriangle::SOUTHEAST | AATriangle::DEFORM_TOP:
307     case AATriangle::SOUTHEAST | AATriangle::DEFORM_RIGHT:
308     case AATriangle::NORTHWEST:
309     case AATriangle::NORTHWEST | AATriangle::DEFORM_TOP:
310     case AATriangle::NORTHWEST | AATriangle::DEFORM_RIGHT:
311       tile_x = tile_bbox.get_right();
312       tile_y = tile_bbox.get_top();
313       gradient = -1.0;
314       break;
315
316     case AATriangle::SOUTHEAST | AATriangle::DEFORM_BOTTOM:
317     case AATriangle::SOUTHEAST | AATriangle::DEFORM_LEFT:
318     case AATriangle::NORTHWEST | AATriangle::DEFORM_BOTTOM:
319     case AATriangle::NORTHWEST | AATriangle::DEFORM_LEFT:
320       tile_x = tile_bbox.get_left();
321       tile_y = tile_bbox.get_bottom();
322       gradient = -1.0;
323       break;
324
325     case AATriangle::SOUTHWEST | AATriangle::DEFORM_BOTTOM:
326     case AATriangle::SOUTHWEST | AATriangle::DEFORM_RIGHT:
327     case AATriangle::NORTHEAST | AATriangle::DEFORM_BOTTOM:
328     case AATriangle::NORTHEAST | AATriangle::DEFORM_RIGHT:
329       tile_x = tile_bbox.get_right();
330       tile_y = tile_bbox.get_bottom();
331       gradient = 1.0;
332       break;
333
334     default:
335       assert (23 == 42);
336       return 0;
337   }
338
339   /* delta_x, delta_y: Gradient aware version of SHIFT_DELTA. Here, we set the
340    * sign of the values only. Also, we determine here which corner of the
341    * object's bounding box is the interesting one for us. */
342   delta_x = 1.0 * SHIFT_DELTA;
343   delta_y = 1.0 * SHIFT_DELTA;
344   switch (slope_info & AATriangle::DIRECTION_MASK)
345   {
346     case AATriangle::SOUTHWEST:
347       delta_x *= 1.0;
348       delta_y *= -1.0;
349       obj_x = obj_bbox.get_left();
350       obj_y = obj_bbox.get_bottom();
351       break;
352
353     case AATriangle::SOUTHEAST:
354       delta_x *= -1.0;
355       delta_y *= -1.0;
356       obj_x = obj_bbox.get_right();
357       obj_y = obj_bbox.get_bottom();
358       break;
359
360     case AATriangle::NORTHWEST:
361       delta_x *= 1.0;
362       delta_y *= 1.0;
363       obj_x = obj_bbox.get_left();
364       obj_y = obj_bbox.get_top();
365       break;
366
367     case AATriangle::NORTHEAST:
368       delta_x *= -1.0;
369       delta_y *= 1.0;
370       obj_x = obj_bbox.get_right();
371       obj_y = obj_bbox.get_top();
372       break;
373   }
374
375   /* Adapt the delta_x, delta_y and the gradient for the 26.6 deg and 63.4 deg
376    * cases. */
377   switch (slope_info & AATriangle::DEFORM_MASK)
378   {
379     case 0:
380       delta_x *= .70710678118654752440; /* 1/sqrt(2) */
381       delta_y *= .70710678118654752440; /* 1/sqrt(2) */
382       break;
383
384     case AATriangle::DEFORM_BOTTOM:
385     case AATriangle::DEFORM_TOP:
386       delta_x *= .44721359549995793928; /* 1/sqrt(5) */
387       delta_y *= .89442719099991587856; /* 2/sqrt(5) */
388       gradient *= 0.5;
389       break;
390
391     case AATriangle::DEFORM_LEFT:
392     case AATriangle::DEFORM_RIGHT:
393       delta_x *= .89442719099991587856; /* 2/sqrt(5) */
394       delta_y *= .44721359549995793928; /* 1/sqrt(5) */
395       gradient *= 2.0;
396       break;
397   }
398
399   /* With a south slope, check if all points are above the line. If one point
400    * isn't, the slope is not solid. => You can pass through a south-slope from
401    * below but not from above. */
402   if (((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHWEST)
403    || ((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHEAST))
404   {
405     return !is_below_line(tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y);
406   }
407   /* northwest or northeast. Same as above, but inverted. You can pass from top
408    * to bottom but not vice versa. */
409   else
410   {
411     return !is_above_line (tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y);
412   }
413 } /* int check_position_unisolid */
414
415 bool Tile::is_solid (const Rectf& tile_bbox, const Rectf& position, const Vector& movement) const
416 {
417   if (!(attributes & SOLID))
418     return false;
419
420   if (!(attributes & UNISOLID))
421     return true;
422
423   return check_movement_unisolid (movement) &&
424          check_position_unisolid (position, tile_bbox);
425 } /* bool Tile::is_solid */
426
427 /* vim: set sw=2 sts=2 et : */
428 /* EOF */