Bug 562: Collision detection for unisolid tiles doesn't handle tilemap offset
[supertux.git] / src / object / tilemap.cpp
index 7dd4bb8..7dce250 100644 (file)
@@ -34,8 +34,7 @@ TileMap::TileMap(const TileSet *new_tileset) :
   width(0),
   height(0), 
   z_pos(0), 
-  x_offset(0), 
-  y_offset(0), 
+  offset(Vector(0,0)),
   movement(0,0),
   drawing_effect(NO_EFFECT),
   alpha(1.0), 
@@ -56,8 +55,7 @@ TileMap::TileMap(const Reader& reader) :
   width(-1),
   height(-1), 
   z_pos(0), 
-  x_offset(0),
-  y_offset(0),
+  offset(Vector(0,0)),
   movement(Vector(0,0)), 
   drawing_effect(NO_EFFECT),
   alpha(1.0), 
@@ -88,8 +86,7 @@ TileMap::TileMap(const Reader& reader) :
     path->read(*pathLisp);
     walker.reset(new PathWalker(path.get(), /*running*/false));
     Vector v = path->get_base();
-    set_x_offset(v.x);
-    set_y_offset(v.y);
+    set_offset(v);
   }
 
   std::string draw_target_s = "normal";
@@ -138,8 +135,7 @@ TileMap::TileMap(const TileSet *new_tileset, std::string name, int z_pos,
   width(0),
   height(0), 
   z_pos(z_pos), 
-  x_offset(0), 
-  y_offset(0), 
+  offset(Vector(0,0)),
   movement(Vector(0,0)),
   drawing_effect(NO_EFFECT), 
   alpha(1.0), 
@@ -179,51 +175,59 @@ TileMap::update(float elapsed_time)
   // if we have a path to follow, follow it
   if (walker.get()) {
     Vector v = walker->advance(elapsed_time);
-    movement = Vector(v.x-get_x_offset(), std::max(0.0f,v.y-get_y_offset()));
-    set_x_offset(v.x);
-    set_y_offset(v.y);
+    movement = Vector(v.x-get_offset().x, std::max(0.0f,v.y-get_offset().y));
+    set_offset(v);
   }
 }
 
 void
 TileMap::draw(DrawingContext& context)
 {
-  // skip draw if current opacity is set to 0.0
+  // skip draw if current opacity is 0.0
   if (current_alpha == 0.0) return;
 
   context.push_transform();
-  context.push_target();
-  context.set_target(draw_target);
+  if(draw_target != DrawingContext::NORMAL) {
+    context.push_target();
+    context.set_target(draw_target);
+  }
 
   if(drawing_effect != 0) context.set_drawing_effect(drawing_effect);
   if(current_alpha != 1.0) context.set_alpha(current_alpha);
 
+  /* Force the translation to be an integer so that the tiles appear sharper.
+   * For consistency (i.e., to avoid 1-pixel gaps), this needs to be done even
+   * for solid tilemaps that are guaranteed to have speed 1.
+   * FIXME Force integer translation for all graphics, not just tilemaps. */
   float trans_x = roundf(context.get_translation().x);
   float trans_y = roundf(context.get_translation().y);
   context.set_translation(Vector(int(trans_x * speed_x),
                                  int(trans_y * speed_y)));
 
-  /** if we don't round here, we'll have a 1 pixel gap on screen sometimes.
-   * I have no idea why */
-  float start_x = int((roundf(context.get_translation().x) - roundf(x_offset)) / 32) * 32 + roundf(x_offset);
-  float start_y = int((roundf(context.get_translation().y) - roundf(y_offset)) / 32) * 32 + roundf(y_offset);
-  float end_x = std::min(start_x + SCREEN_WIDTH + 32, float(width * 32 + roundf(x_offset)));
-  float end_y = std::min(start_y + SCREEN_HEIGHT + 32, float(height * 32 + roundf(y_offset)));
-  int tsx = int((start_x - roundf(x_offset)) / 32); // tilestartindex x
-  int tsy = int((start_y - roundf(y_offset)) / 32); // tilestartindex y
+  Rectf draw_rect = Rectf(context.get_translation(),
+        context.get_translation() + Vector(SCREEN_WIDTH, SCREEN_HEIGHT));
+  Rect t_draw_rect = get_tiles_overlapping(draw_rect);
+  Vector start = get_tile_position(t_draw_rect.left, t_draw_rect.top);
 
   Vector pos;
   int tx, ty;
-  for(pos.x = start_x, tx = tsx; pos.x < end_x; pos.x += 32, ++tx) {
-    for(pos.y = start_y, ty = tsy; pos.y < end_y; pos.y += 32, ++ty) {
-      if ((tx < 0) || (ty < 0)) continue;
-      const Tile* tile = tileset->get(tiles[ty*width + tx]);
+
+  for(pos.x = start.x, tx = t_draw_rect.left; tx < t_draw_rect.right; pos.x += 32, ++tx) {
+    for(pos.y = start.y, ty = t_draw_rect.top; ty < t_draw_rect.bottom; pos.y += 32, ++ty) {
+      int index = ty*width + tx;
+      assert (index >= 0);
+      assert (index < (width * height));
+
+      if (tiles[index] == 0) continue;
+      const Tile* tile = tileset->get(tiles[index]);
       assert(tile != 0);
       tile->draw(context, pos, z_pos);
-    }
-  }
+    } /* for (pos y) */
+  } /* for (pos x) */
 
-  context.pop_target();
+  if(draw_target != DrawingContext::NORMAL) {
+    context.pop_target();
+  }
   context.pop_transform();
 }
 
@@ -252,15 +256,15 @@ void
 TileMap::expose(HSQUIRRELVM vm, SQInteger table_idx)
 {
   if (name.empty()) return;
-  Scripting::TileMap* interface = new Scripting::TileMap(this);
-  expose_object(vm, table_idx, interface, name, true);
+  scripting::TileMap* _this = new scripting::TileMap(this);
+  expose_object(vm, table_idx, _this, name, true);
 }
 
 void
 TileMap::unexpose(HSQUIRRELVM vm, SQInteger table_idx)
 {
   if (name.empty()) return;
-  Scripting::unexpose_object(vm, table_idx, name);
+  scripting::unexpose_object(vm, table_idx, name);
 }
 
 void
@@ -316,6 +320,19 @@ TileMap::resize(int new_width, int new_height, int fill_id)
   width = new_width;
 }
 
+Rect
+TileMap::get_tiles_overlapping(const Rectf &rect) const
+{
+  Rectf rect2 = rect;
+  rect2.move(-offset);
+
+  int t_left   = std::max(0     , int(floorf(rect2.get_left  () / 32)));
+  int t_right  = std::min(width , int(ceilf (rect2.get_right () / 32)));
+  int t_top    = std::max(0     , int(floorf(rect2.get_top   () / 32)));
+  int t_bottom = std::min(height, int(ceilf (rect2.get_bottom() / 32)));
+  return Rect(t_left, t_top, t_right, t_bottom);
+}
+
 void
 TileMap::set_solid(bool solid)
 {
@@ -343,7 +360,8 @@ TileMap::get_tile(int x, int y) const
 uint32_t
 TileMap::get_tile_id_at(const Vector& pos) const
 {
-  return get_tile_id(int(pos.x - x_offset)/32, int(pos.y - y_offset)/32);
+  Vector xy = (pos - offset) / 32;
+  return get_tile_id(int(xy.x), int(xy.y));
 }
 
 const Tile*
@@ -363,7 +381,8 @@ TileMap::change(int x, int y, uint32_t newtile)
 void
 TileMap::change_at(const Vector& pos, uint32_t newtile)
 {
-  change(int(pos.x - x_offset)/32, int(pos.y - y_offset)/32, newtile);
+  Vector xy = (pos - offset) / 32;
+  change(int(xy.x), int(xy.y), newtile);
 }
 
 void
@@ -401,7 +420,6 @@ TileMap::get_alpha()
 {
   return this->current_alpha;
 }
-  
-IMPLEMENT_FACTORY(TileMap, "tilemap");
 
 /* EOF */