#include "trigger/sequence_trigger.hpp"
#include "util/file_system.hpp"
+#define DEFORM_BOTTOM AATriangle::DEFORM1
+#define DEFORM_TOP AATriangle::DEFORM2
+#define DEFORM_LEFT AATriangle::DEFORM3
+#define DEFORM_RIGHT AATriangle::DEFORM4
+
Sector* Sector::_current = 0;
bool Sector::show_collrects = false;
for(size_t y=0; y < solids->get_height(); ++y) {
uint32_t id = solids->get_tile_id(x, y);
const Tile *tile = solids->get_tile(x, y);
- Vector pos(solids->get_x_offset() + x*32, solids->get_y_offset() + y*32);
+ Vector pos = solids->get_tile_position(x, y);
if(id == 112) {
add_object(new InvisibleBlock(pos));
for(size_t x=0; x < tm->get_width(); ++x) {
for(size_t y=0; y < tm->get_height(); ++y) {
uint32_t id = tm->get_tile_id(x, y);
- Vector pos(tm->get_x_offset() + x*32, tm->get_y_offset() + y*32);
- Vector center(pos.x + 16, pos.y + 16);
+ Vector pos = tm->get_tile_position(x, y);
+ Vector center = pos + Vector(16, 16);
// torch
if (id == 1517) {
}
}
+/* Returns zero if a unisolid tile is non-solid due to the movement direction,
+ * non-zero if the tile is solid due to direction. */
+int check_movement_unisolid (const Vector& movement, const Tile* tile)
+{
+ int slope_info;
+ double mv_x;
+ double mv_y;
+ double mv_tan;
+ double slope_tan;
+
+#define MV_NON_SOLID 0
+#define MV_SOLID 1
+
+ /* If the tile is not a slope, this is very easy. */
+ if ((tile->getAttributes() & Tile::SLOPE) == 0)
+ {
+ if (movement.y >= 0) /* moving down */
+ return MV_SOLID;
+ else /* moving up */
+ return MV_NON_SOLID;
+ }
+
+ /* Initialize mv_x and mv_y. Depending on the slope the axis are inverted so
+ * that we can always use the "SOUTHEAST" case of the slope. The southeast
+ * case is the following:
+ * .
+ * /!
+ * / !
+ * +--+
+ */
+ mv_x = (double) movement.x;
+ mv_y = (double) movement.y;
+
+ slope_info = tile->getData();
+ switch (slope_info & AATriangle::DIRECTION_MASK)
+ {
+ case AATriangle::SOUTHEAST: /* . */
+ /* do nothing */ /* /! */
+ break; /* / ! */
+ /* +--+ */
+ case AATriangle::SOUTHWEST: /* . */
+ mv_x *= (-1.0); /* !\ */
+ break; /* ! \ */
+ /* +--+ */
+ case AATriangle::NORTHEAST: /* +--+ */
+ mv_y *= (-1.0); /* \ ! */
+ break; /* \! */
+ /* ' */
+ case AATriangle::NORTHWEST: /* +--+ */
+ mv_x *= (-1.0); /* ! / */
+ mv_y *= (-1.0); /* !/ */
+ break; /* ' */
+ } /* switch (slope_info & DIRECTION_MASK) */
+
+ /* Handle the easy cases first */
+ /* If we're moving to the right and down, then the slope is solid. */
+ if ((mv_x >= 0.0) && (mv_y >= 0.0)) /* 4th quadrant */
+ return MV_SOLID;
+ /* If we're moving to the left and up, then the slope is not solid. */
+ else if ((mv_x <= 0.0) && (mv_y <= 0.0)) /* 2nd quadrant */
+ return MV_NON_SOLID;
+
+ /* The pure up-down and left-right movements have already been handled. */
+ assert (mv_x != 0.0);
+ assert (mv_y != 0.0);
+
+ /* calculate tangent of movement */
+ mv_tan = (-1.0) * mv_y / mv_x;
+
+ /* determine tangent of the slope */
+ slope_tan = 1.0;
+ if (((slope_info & AATriangle::DEFORM_MASK) == DEFORM_BOTTOM)
+ || ((slope_info & AATriangle::DEFORM_MASK) == DEFORM_TOP))
+ slope_tan = 0.5; /* ~= 26.6 deg */
+ else if (((slope_info & AATriangle::DEFORM_MASK) == DEFORM_LEFT)
+ || ((slope_info & AATriangle::DEFORM_MASK) == DEFORM_RIGHT))
+ slope_tan = 2.0; /* ~= 63.4 deg */
+
+ /* up and right */
+ if (mv_x > 0.0) /* 1st quadrant */
+ {
+ assert (mv_y < 0.0);
+ if (mv_tan <= slope_tan)
+ return MV_SOLID;
+ else
+ return MV_NON_SOLID;
+ }
+ /* down and left */
+ else if (mv_x < 0.0) /* 3rd quadrant */
+ {
+ assert (mv_y > 0.0);
+ if (mv_tan >= slope_tan)
+ return MV_SOLID;
+ else
+ return MV_NON_SOLID;
+ }
+
+ assert (1 != 1);
+ return (-1);
+
+#undef MV_NON_SOLID
+#undef MV_SOLID
+} /* int check_movement_unisolid */
+
+int is_above_line (float l_x, float l_y, float m,
+ float p_x, float p_y)
+{
+ float interp_y = (l_y + (m * (p_x - l_x)));
+ if (interp_y == p_y)
+ return (1);
+ else if (interp_y > p_y)
+ return (1);
+ else
+ return (0);
+}
+
+int is_below_line (float l_x, float l_y, float m,
+ float p_x, float p_y)
+{
+ if (is_above_line (l_x, l_y, m, p_x, p_y))
+ return (0);
+ else
+ return (1);
+}
+
+int check_position_unisolid (const Rectf& obj_bbox,
+ const Rectf& tile_bbox,
+ const Tile* tile)
+{
+ int slope_info;
+ float tile_x;
+ float tile_y;
+ float gradient;
+ float delta_x;
+ float delta_y;
+ float obj_x;
+ float obj_y;
+
+#define POS_NON_SOLID 0
+#define POS_SOLID 1
+
+ /* If this is not a slope, this is - again - easy */
+ if ((tile->getAttributes() & Tile::SLOPE) == 0)
+ {
+ if ((obj_bbox.get_bottom () - SHIFT_DELTA) <= tile_bbox.get_top ())
+ return POS_SOLID;
+ else
+ return POS_NON_SOLID;
+ }
+
+ /* There are 20 different cases. For each case, calculate a line that
+ * describes the slope's surface. The line is defined by x, y, and m, the
+ * gradient. */
+ slope_info = tile->getData();
+ switch (slope_info
+ & (AATriangle::DIRECTION_MASK | AATriangle::DEFORM_MASK))
+ {
+ case AATriangle::SOUTHWEST:
+ case AATriangle::SOUTHWEST | DEFORM_TOP:
+ case AATriangle::SOUTHWEST | DEFORM_LEFT:
+ case AATriangle::NORTHEAST:
+ case AATriangle::NORTHEAST | DEFORM_TOP:
+ case AATriangle::NORTHEAST | DEFORM_LEFT:
+ tile_x = tile_bbox.get_left ();
+ tile_y = tile_bbox.get_top ();
+ gradient = 1.0;
+ break;
+
+ case AATriangle::SOUTHEAST:
+ case AATriangle::SOUTHEAST | DEFORM_TOP:
+ case AATriangle::SOUTHEAST | DEFORM_RIGHT:
+ case AATriangle::NORTHWEST:
+ case AATriangle::NORTHWEST | DEFORM_TOP:
+ case AATriangle::NORTHWEST | DEFORM_RIGHT:
+ tile_x = tile_bbox.get_right ();
+ tile_y = tile_bbox.get_top ();
+ gradient = -1.0;
+ break;
+
+ case AATriangle::SOUTHEAST | DEFORM_BOTTOM:
+ case AATriangle::SOUTHEAST | DEFORM_LEFT:
+ case AATriangle::NORTHWEST | DEFORM_BOTTOM:
+ case AATriangle::NORTHWEST | DEFORM_LEFT:
+ tile_x = tile_bbox.get_left ();
+ tile_y = tile_bbox.get_bottom ();
+ gradient = -1.0;
+ break;
+
+ case AATriangle::SOUTHWEST | DEFORM_BOTTOM:
+ case AATriangle::SOUTHWEST | DEFORM_RIGHT:
+ case AATriangle::NORTHEAST | DEFORM_BOTTOM:
+ case AATriangle::NORTHEAST | DEFORM_RIGHT:
+ tile_x = tile_bbox.get_right ();
+ tile_y = tile_bbox.get_bottom ();
+ gradient = 1.0;
+ break;
+
+ default:
+ assert (23 == 42);
+ }
+
+ /* delta_x, delta_y: Gradient aware version of SHIFT_DELTA. Here, we set the
+ * sign of the values only. Also, we determine here which corner of the
+ * object's bounding box is the interesting one for us. */
+ delta_x = 1.0 * SHIFT_DELTA;
+ delta_y = 1.0 * SHIFT_DELTA;
+ switch (slope_info & AATriangle::DIRECTION_MASK)
+ {
+ case AATriangle::SOUTHWEST:
+ delta_x *= 1.0;
+ delta_y *= -1.0;
+ obj_x = obj_bbox.get_left ();
+ obj_y = obj_bbox.get_bottom ();
+ break;
+
+ case AATriangle::SOUTHEAST:
+ delta_x *= -1.0;
+ delta_y *= -1.0;
+ obj_x = obj_bbox.get_right ();
+ obj_y = obj_bbox.get_bottom ();
+ break;
+
+ case AATriangle::NORTHWEST:
+ delta_x *= 1.0;
+ delta_y *= 1.0;
+ obj_x = obj_bbox.get_left ();
+ obj_y = obj_bbox.get_top ();
+ break;
+
+ case AATriangle::NORTHEAST:
+ delta_x *= -1.0;
+ delta_y *= 1.0;
+ obj_x = obj_bbox.get_right ();
+ obj_y = obj_bbox.get_top ();
+ break;
+ }
+
+ /* Adapt the delta_x, delta_y and the gradient for the 26.6 deg and 63.4 deg
+ * cases. */
+ switch (slope_info & AATriangle::DEFORM_MASK)
+ {
+ case 0:
+ delta_x *= .70710678118654752440; /* 1/sqrt(2) */
+ delta_y *= .70710678118654752440; /* 1/sqrt(2) */
+ break;
+
+ case DEFORM_BOTTOM:
+ case DEFORM_TOP:
+ delta_x *= .44721359549995793928; /* 1/sqrt(5) */
+ delta_y *= .89442719099991587856; /* 2/sqrt(5) */
+ gradient *= 0.5;
+ break;
+
+ case DEFORM_LEFT:
+ case DEFORM_RIGHT:
+ delta_x *= .89442719099991587856; /* 2/sqrt(5) */
+ delta_y *= .44721359549995793928; /* 1/sqrt(5) */
+ gradient *= 2.0;
+ break;
+ }
+
+ /* With a south slope, check if all points are above the line. If one point
+ * isn't, the slope is not solid. => You can pass through a south-slope from
+ * below but not from above. */
+ if (((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHWEST)
+ || ((slope_info & AATriangle::DIRECTION_MASK) == AATriangle::SOUTHEAST))
+ {
+ if (is_below_line (tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y))
+ return (POS_NON_SOLID);
+ else
+ return (POS_SOLID);
+ }
+ /* northwest or northeast. Same as above, but inverted. You can pass from top
+ * to bottom but not vice versa. */
+ else
+ {
+ if (is_above_line (tile_x, tile_y, gradient, obj_x + delta_x, obj_y + delta_y))
+ return (POS_NON_SOLID);
+ else
+ return (POS_SOLID);
+ }
+
+#undef POS_NON_SOLID
+#undef POS_SOLID
+} /* int check_position_unisolid */
+
void
Sector::collision_tilemap(collision::Constraints* constraints,
- const Vector& movement, const Rectf& dest) const
+ const Vector& movement, const Rectf& dest,
+ MovingObject& object) const
{
// calculate rectangle where the object will move
float x1 = dest.get_left();
TileMap* solids = *i;
// test with all tiles in this rectangle
- int starttilex = int(x1 - solids->get_x_offset()) / 32;
- int starttiley = int(y1 - solids->get_y_offset()) / 32;
- int max_x = int(x2 - solids->get_x_offset());
- int max_y = int(y2+1 - solids->get_y_offset());
+ Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
- for(int x = starttilex; x*32 < max_x; ++x) {
- for(int y = starttiley; y*32 < max_y; ++y) {
+ for(int x = test_tiles.left; x < test_tiles.right; ++x) {
+ for(int y = test_tiles.top; y < test_tiles.bottom; ++y) {
const Tile* tile = solids->get_tile(x, y);
if(!tile)
continue;
// skip non-solid tiles
if((tile->getAttributes() & Tile::SOLID) == 0)
continue;
+ Rectf tile_bbox = solids->get_tile_bbox(x, y);
+
// only handle unisolid when the player is falling down and when he was
// above the tile before
if(tile->getAttributes() & Tile::UNISOLID) {
- if(movement.y <= 0 || dest.get_bottom() - movement.y - SHIFT_DELTA > y*32)
+ int status;
+
+ /* Check if the tile is solid given the current movement. This works
+ * for south-slopes (which are solid when moving "down") and
+ * north-slopes (which are solid when moving "up". "up" and "down" is
+ * in quotation marks because because the slope's gradient is taken
+ * Also, this uses the movement relative to the tilemaps own movement
+ * (if any). --octo */
+ status = check_movement_unisolid (movement - solids->get_movement (), tile);
+ /* If zero is returned, the unisolid tile is non-solid. */
+ if (status == 0)
+ continue;
+
+ /* Check whether the object is already *in* the tile. If so, the tile
+ * is non-solid. Otherwise, if the object is "above" (south slopes)
+ * or "below" (north slopes), the tile will be solid. */
+ status = check_position_unisolid (object.get_bbox(), tile_bbox, tile);
+ if (status == 0)
continue;
}
if(tile->getAttributes() & Tile::SLOPE) { // slope tile
AATriangle triangle;
- Vector p1(x*32 + solids->get_x_offset(), y*32 + solids->get_y_offset());
- Vector p2((x+1)*32 + solids->get_x_offset(), (y+1)*32 + solids->get_y_offset());
int slope_data = tile->getData();
if (solids->get_drawing_effect() == VERTICAL_FLIP)
slope_data = AATriangle::vertical_flip(slope_data);
- triangle = AATriangle(p1, p2, slope_data);
+ triangle = AATriangle(tile_bbox, slope_data);
collision::rectangle_aatriangle(constraints, dest, triangle, solids->get_movement());
} else { // normal rectangular tile
- Rectf rect(x*32 + solids->get_x_offset(), y*32 + solids->get_y_offset(), (x+1)*32 + solids->get_x_offset(), (y+1)*32 + solids->get_y_offset());
- check_collisions(constraints, movement, dest, rect, NULL, NULL, solids->get_movement());
+ check_collisions(constraints, movement, dest, tile_bbox, NULL, NULL, solids->get_movement());
}
}
}
TileMap* solids = *i;
// test with all tiles in this rectangle
- int starttilex = int(x1 - solids->get_x_offset()) / 32;
- int starttiley = int(y1 - solids->get_y_offset()) / 32;
- int max_x = int(x2 - solids->get_x_offset());
- int max_y = int(y2+1 - solids->get_y_offset());
- int max_y_ice = int(max_y + SHIFT_DELTA);
+ Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
+ // For ice (only), add a little fudge to recognize tiles Tux is standing on.
+ Rect test_tiles_ice = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2 + SHIFT_DELTA));
- for(int x = starttilex; x*32 < max_x; ++x) {
+ for(int x = test_tiles.left; x < test_tiles.right; ++x) {
int y;
- for(y = starttiley; y*32 < max_y; ++y) {
+ for(y = test_tiles.top; y < test_tiles.bottom; ++y) {
const Tile* tile = solids->get_tile(x, y);
if(!tile)
continue;
result |= tile->getAttributes();
}
- for(; y*32 < max_y_ice; ++y) {
+ for(; y < test_tiles_ice.bottom; ++y) {
const Tile* tile = solids->get_tile(x, y);
if(!tile)
continue;
void
Sector::collision_static(collision::Constraints* constraints,
const Vector& movement, const Rectf& dest,
- GameObject& object)
+ MovingObject& object)
{
- collision_tilemap(constraints, movement, dest);
+ collision_tilemap(constraints, movement, dest, object);
// collision with other (static) objects
for(MovingObjects::iterator i = moving_objects.begin();
TileMap* solids = *i;
// test with all tiles in this rectangle
- int starttilex = int(rect.p1.x - solids->get_x_offset()) / 32;
- int starttiley = int(rect.p1.y - solids->get_y_offset()) / 32;
- int max_x = int(rect.p2.x - solids->get_x_offset());
- int max_y = int(rect.p2.y - solids->get_y_offset());
+ Rect test_tiles = solids->get_tiles_overlapping(rect);
- for(int x = starttilex; x*32 <= max_x; ++x) {
- for(int y = starttiley; y*32 <= max_y; ++y) {
+ for(int x = test_tiles.left; x < test_tiles.right; ++x) {
+ for(int y = test_tiles.top; y < test_tiles.bottom; ++y) {
const Tile* tile = solids->get_tile(x, y);
if(!tile) continue;
+ if(!(tile->getAttributes() & Tile::SOLID))
+ continue;
+ if((tile->getAttributes() & Tile::UNISOLID) && ignoreUnisolid)
+ continue;
if(tile->getAttributes() & Tile::SLOPE) {
AATriangle triangle;
- Vector p1(x*32 + solids->get_x_offset(), y*32 + solids->get_y_offset());
- Vector p2((x+1)*32 + solids->get_x_offset(), (y+1)*32 + solids->get_y_offset());
- triangle = AATriangle(p1, p2, tile->getData());
+ Rectf tbbox = solids->get_tile_bbox(x, y);
+ triangle = AATriangle(tbbox, tile->getData());
Constraints constraints;
- if(collision::rectangle_aatriangle(&constraints, rect, triangle) && (!ignoreUnisolid || !(tile->getAttributes() & Tile::UNISOLID))) return false;
+ if(!collision::rectangle_aatriangle(&constraints, rect, triangle))
+ continue;
}
- if((tile->getAttributes() & Tile::SOLID) && (!ignoreUnisolid || !(tile->getAttributes() & Tile::UNISOLID))) return false;
+ // We have a solid tile that overlaps the given rectangle.
+ return false;
}
}
}
{
for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin(); i != solid_tilemaps.end(); i++) {
TileMap* solids = *i;
- bool horizontally = ((rect.p2.x >= 0 + solids->get_x_offset()) && (rect.p1.x <= solids->get_width() * 32 + solids->get_x_offset()));
- bool vertically = (rect.p1.y <= solids->get_height() * 32 + solids->get_y_offset());
- if (horizontally && vertically)
+ Rectf bbox = solids->get_bbox();
+ bbox.p1.y = -INFINITY; // pretend the tilemap extends infinitely far upwards
+
+ if (bbox.contains(rect))
return true;
}
return false;
for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin();
i != solid_tilemaps.end(); i++) {
TileMap* solids = *i;
- if ((solids->get_width() * 32 + solids->get_x_offset()) > width) {
- width = solids->get_width() * 32 + solids->get_x_offset();
- }
+ width = std::max(width, solids->get_bbox().get_right());
}
return width;
for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin();
i != solid_tilemaps.end(); i++) {
TileMap* solids = *i;
- if ((solids->get_height() * 32 + solids->get_y_offset()) > height) {
- height = solids->get_height() * 32 + solids->get_y_offset();
- }
+ height = std::max(height, solids->get_bbox().get_bottom());
}
return height;