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