Complete.Org: Mailing Lists: Archives: freeciv-dev: February 2004:
[Freeciv-Dev] Re: (PR#7445) mapview scrolling
Home

[Freeciv-Dev] Re: (PR#7445) mapview scrolling

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] Re: (PR#7445) mapview scrolling
From: "Jason Short" <jdorje@xxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 23 Feb 2004 19:35:12 -0800
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=7445 >

Here's another patch that takes the concept much farther.

- When clicking an arrow button work with the gui positions directly. 
This prevents zig-zagging when you use arrow-scrolling.  Simple.

- In set_mapview_origin, we perform normalization step.  This is done in 
a fairly straightforward way by wrapping normalize_map_pos.  But we have 
to preserve pixel granularity, so a few extra lines of code are needed:

   gui_x0 += mapview_canvas.width / 2;
   gui_y0 += mapview_canvas.height / 2;
   gui_to_map_pos(&map_x, &map_y, gui_x0, gui_y0);
   map_to_gui_pos(&gui_x, &gui_y, map_x, map_y);
   diff_x = gui_x - gui_x0;
   diff_y = gui_y - gui_y0;
   nearest_real_pos(&map_x, &map_y);
   map_to_gui_pos(&gui_x, &gui_y, map_x, map_y);
   gui_x0 = gui_x - diff_x - mapview_canvas.width / 2;
   gui_y0 = gui_y - diff_y - mapview_canvas.height / 2;

This may seem complicated at first glance, and in fact it's a bit of a 
hack (but at least it hides the underlying transformations!).

This normalization allows the scrollbars to wrap and prevents scrolling 
off into the unknown.  Nice features!

- In get_mapview_corners, convert from GUI to overview coordinates with 
pixel granularity.  This requires a new function gui_to_overview pos. 
And the math (linear algebra) of this function is extraordinarily 
complex!  I won't even bother to reproduce it here.  We have to go from 
GUI to map to native to scroll coordinates, but in the map and native 
steps we maintain pixel granularity which means all the division is 
postponed until the end.  So basically we reproduce gui_to_map_pos, 
map_to_native_pos, and native_to_scroll_pos within this function.

This allows the overview viewrect to be drawn perfectly accurately.  No 
zig-zagging when you scroll, no sitting in one location for one scroll 
and then jumping 2 pixels in the next scroll, no off-by-one errors in 
the calculation of the width and height.  Very nice.


But is it worth all the effort?  Will this code be at all maintainable?

It might be possible to fold the logic back into the main topology code, 
so that converting from a GUI to a map position could preserve pixel 
granularity.  If we did it with floating-point values, in fact, it would 
be easy.

For now I suggest we stick to the gui_coordinates-3 patch.  Greg?

jason

? coasts.diff
Index: client/mapctrl_common.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapctrl_common.c,v
retrieving revision 1.28
diff -u -r1.28 mapctrl_common.c
--- client/mapctrl_common.c     2004/02/20 06:42:28     1.28
+++ client/mapctrl_common.c     2004/02/24 03:35:00
@@ -87,8 +87,8 @@
   map_to_canvas_pos(&rec_anchor_x, &rec_anchor_y, tile_x, tile_y);
   rec_anchor_x += NORMAL_TILE_WIDTH / 2;
   rec_anchor_y += NORMAL_TILE_HEIGHT / 2;
-  rec_canvas_map_x0 = mapview_canvas.map_x0;
-  rec_canvas_map_y0 = mapview_canvas.map_y0;
+  /* FIXME: This may be off-by-one. */
+  canvas_to_map_pos(&rec_canvas_map_x0, &rec_canvas_map_y0, 0, 0);
   rec_w = rec_h = 0;
 }
 
@@ -165,6 +165,7 @@
   const int H = NORMAL_TILE_HEIGHT,   half_H = H / 2;
   static int rec_tile_x = 9999, rec_tile_y = 9999;
   int tile_x, tile_y, diff_x, diff_y;
+  int map_x0, map_y0;
 
   canvas_to_map_pos(&tile_x, &tile_y, canvas_x, canvas_y);
 
@@ -190,8 +191,10 @@
   rec_w = rec_anchor_x - canvas_x;  /* width */
   rec_h = rec_anchor_y - canvas_y;  /* height */
 
-  diff_x = rec_canvas_map_x0 - mapview_canvas.map_x0;
-  diff_y = rec_canvas_map_y0 - mapview_canvas.map_y0;
+  /* FIXME: This may be off-by-one. */
+  canvas_to_map_pos(&map_x0, &map_y0, 0, 0);
+  diff_x = rec_canvas_map_x0 - map_x0;
+  diff_y = rec_canvas_map_y0 - map_y0;
 
   /*  Adjust width, height if mapview has recentered.
    */
@@ -453,20 +456,19 @@
 **************************************************************************/
 void scroll_mapview(enum direction8 gui_dir)
 {
-  int map_x, map_y, canvas_x, canvas_y;
+  int gui_x, gui_y;
 
   if (!can_client_change_view()) {
     return;
   }
 
-  canvas_x = mapview_canvas.width / 2;
-  canvas_y = mapview_canvas.height / 2;
-  canvas_x += DIR_DX[gui_dir] * mapview_canvas.width / 2;
-  canvas_y += DIR_DY[gui_dir] * mapview_canvas.height / 2;
-  if (!canvas_to_map_pos(&map_x, &map_y, canvas_x, canvas_y)) {
-    nearest_real_pos(&map_x, &map_y);
-  }
-  center_tile_mapcanvas(map_x, map_y);
+  gui_x = mapview_canvas.gui_x0;
+  gui_y = mapview_canvas.gui_y0;
+
+  gui_x += DIR_DX[gui_dir] * mapview_canvas.width / 2;
+  gui_y += DIR_DY[gui_dir] * mapview_canvas.height / 2;
+
+  set_mapview_origin(gui_x, gui_y);
 }
 
 /**************************************************************************
Index: client/mapview_common.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.c,v
retrieving revision 1.79
diff -u -r1.79 mapview_common.c
--- client/mapview_common.c     2004/02/21 22:15:03     1.79
+++ client/mapview_common.c     2004/02/24 03:35:00
@@ -160,6 +160,87 @@
   }
 }
 
+/****************************************************************************
+  Translate from map to gui coordinate systems.
+
+  GUI coordinates are comparable to canvas coordinates but extend in all
+  directions.  gui(0,0) == map(0,0).
+****************************************************************************/
+static void map_to_gui_pos(int *gui_x, int *gui_y, int map_x, int map_y)
+{
+  if (is_isometric) {
+    /*
+     * Convert the map coordinates to isometric GUI
+     * coordinates.  We'll make tile map(0,0) be the origin, and
+     * transform like this:
+     * 
+     *                     3
+     * 123                2 6
+     * 456 -> becomes -> 1 5 9
+     * 789                4 8
+     *                     7
+     */
+    *gui_x = (map_x - map_y) * NORMAL_TILE_WIDTH / 2;
+    *gui_y = (map_x + map_y) * NORMAL_TILE_HEIGHT / 2;
+  } else {
+    *gui_x = map_x * NORMAL_TILE_HEIGHT;
+    *gui_y = map_y * NORMAL_TILE_WIDTH;
+  }
+}
+
+/****************************************************************************
+  Translate from gui to map coordinate systems.  See map_to_gui_pos().
+
+  Note that you lose some information in this conversion.  If you convert
+  from a gui position to a map position and back, you will probably not get
+  the same value you started with.
+****************************************************************************/
+static void gui_to_map_pos(int *map_x, int *map_y, int gui_x, int gui_y)
+{
+  const int W = NORMAL_TILE_WIDTH, H = NORMAL_TILE_HEIGHT;
+
+  if (is_isometric) {
+    /* The basic operation here is a simple pi/4 rotation; however, we
+     * have to first scale because the tiles have different width and
+     * height.  Mathematically, this looks like
+     *   | 1/W  1/H | |x|    |x`|
+     *   |          | | | -> |  |
+     *   |-1/W  1/H | |y|    |y`|
+     *
+     * Where W is the tile width and H the height.
+     *
+     * In simple terms, this is
+     *   map_x = [   x / W + y / H ]
+     *   map_y = [ - x / W + y / H ]
+     * where [q] stands for integer part of q.
+     *
+     * Here the division is proper mathematical floating point division.
+     *
+     * A picture demonstrating this can be seen at
+     * http://rt.freeciv.org/Ticket/Attachment/16782/9982/grid1.png.
+     *
+     * We have to subtract off a half-tile in the X direction before doing
+     * the transformation.  This is because, although the origin of the tile
+     * is the top-left corner of the bounding box, after the transformation
+     * the top corner of the diamond-shaped tile moves into this position.
+     *
+     * The calculation is complicated somewhat because of two things: we
+     * only use integer math, and C integer division rounds toward zero
+     * instead of rounding down.
+     *
+     * For another example of this math, see canvas_to_city_pos().
+     */
+    gui_x -= W / 2;
+    *map_x = DIVIDE(gui_x * H + gui_y * W, W * H);
+    *map_y = DIVIDE(gui_y * W - gui_x * H, W * H);
+  } else {                     /* is_isometric */
+    /* We use DIVIDE so that we will get the correct result even
+     * for negative coordinates. */
+    *map_x = DIVIDE(gui_x, W);
+    *map_y = DIVIDE(gui_y, H);
+  }
+}
+
 /**************************************************************************
   Finds the canvas coordinates for a map position. Beside setting the results
   in canvas_x, canvas_y it returns whether the tile is inside the
@@ -199,52 +280,20 @@
   map_x = center_map_x + dx;
   map_y = center_map_y + dy;
 
-  if (is_isometric) {
-    /* For a simpler example of this math, see city_to_canvas_pos(). */
-    int iso_x, iso_y;
+  map_to_gui_pos(canvas_x, canvas_y, map_x, map_y);
+  *canvas_x -= mapview_canvas.gui_x0;
+  *canvas_y -= mapview_canvas.gui_y0;
 
-    /*
-     * Next we convert the flat GUI coordinates to isometric GUI
-     * coordinates.  We'll make tile (x0, y0) be the origin, and
-     * transform like this:
-     * 
-     *                     3
-     * 123                2 6
-     * 456 -> becomes -> 1 5 9
-     * 789                4 8
-     *                     7
-     */
-    iso_x = (map_x - map_y)
-      - (mapview_canvas.map_x0 - mapview_canvas.map_y0);
-    iso_y = (map_x + map_y)
-      - (mapview_canvas.map_x0 + mapview_canvas.map_y0);
-
-    /*
-     * As the above picture shows, each isometric-coordinate unit
-     * corresponds to a half-tile on the canvas.  Since the (x0, y0)
-     * tile actually has its top corner (of the diamond-shaped tile)
-     * located right at the corner of the canvas, to find the top-left
-     * corner of the surrounding rectangle we must subtract off an
-     * additional half-tile in the X direction.
-     */
-    *canvas_x = (iso_x - 1) * NORMAL_TILE_WIDTH / 2;
-    *canvas_y = iso_y * NORMAL_TILE_HEIGHT / 2;
-  } else {                     /* is_isometric */
-    *canvas_x = map_x - mapview_canvas.map_x0;
-    *canvas_y = map_y - mapview_canvas.map_y0;
-
-    *canvas_x *= NORMAL_TILE_WIDTH;
-    *canvas_y *= NORMAL_TILE_HEIGHT;
-  }
-
   /*
    * Finally we clip; checking to see if _any part_ of the tile is
-   * visible on the canvas.
+   * present on the backing store.  (Even if it's not visible on the canvas,
+   * if it's present on the backing store we need to draw it in case the
+   * canvas is resized.)
    */
   return (*canvas_x > -NORMAL_TILE_WIDTH
-         && *canvas_x < mapview_canvas.width
+         && *canvas_x < mapview_canvas.store_width
          && *canvas_y > -NORMAL_TILE_HEIGHT
-         && *canvas_y < mapview_canvas.height);
+         && *canvas_y < mapview_canvas.store_height);
 }
 
 /****************************************************************************
@@ -254,45 +303,9 @@
 static void base_canvas_to_map_pos(int *map_x, int *map_y,
                                   int canvas_x, int canvas_y)
 {
-  const int W = NORMAL_TILE_WIDTH, H = NORMAL_TILE_HEIGHT;
-
-  if (is_isometric) {
-    /* The basic operation here is a simple pi/4 rotation; however, we
-     * have to first scale because the tiles have different width and
-     * height.  Mathematically, this looks like
-     *   | 1/W  1/H | |x|    |x`|
-     *   |          | | | -> |  |
-     *   |-1/W  1/H | |y|    |y`|
-     *
-     * Where W is the tile width and H the height.
-     *
-     * In simple terms, this is
-     *   map_x = [   x / W + y / H ]
-     *   map_y = [ - x / W + y / H ]
-     * where [q] stands for integer part of q.
-     *
-     * Here the division is proper mathematical floating point division.
-     *
-     * A picture demonstrating this can be seen at
-     * http://rt.freeciv.org/Ticket/Attachment/16782/9982/grid1.png.
-     *
-     * The calculation is complicated somewhat because of two things: we
-     * only use integer math, and C integer division rounds toward zero
-     * instead of rounding down.
-     *
-     * For another example of this math, see canvas_to_city_pos().
-     */
-    *map_x = DIVIDE(canvas_x * H + canvas_y * W, W * H);
-    *map_y = DIVIDE(canvas_y * W - canvas_x * H, W * H);
-  } else {                     /* is_isometric */
-    /* We use DIVIDE so that we will get the correct result even
-     * for negative (off-canvas) coordinates. */
-    *map_x = DIVIDE(canvas_x, W);
-    *map_y = DIVIDE(canvas_y, H);
-  }
-
-  *map_x += mapview_canvas.map_x0;
-  *map_y += mapview_canvas.map_y0;
+  gui_to_map_pos(map_x, map_y,
+                canvas_x + mapview_canvas.gui_x0,
+                canvas_y + mapview_canvas.gui_y0);
 }
 
 
@@ -311,35 +324,44 @@
 /****************************************************************************
   Change the mapview origin, clip it, and update everything.
 ****************************************************************************/
-static void set_mapview_origin(int map_x0, int map_y0)
+void set_mapview_origin(int gui_x0, int gui_y0)
 {
-  int nat_x0, nat_y0, xmin, xmax, ymin, ymax, xsize, ysize;
+  int xmin, xmax, ymin, ymax, xsize, ysize;
+  int map_x, map_y, gui_x, gui_y, diff_x, diff_y;
 
-  /* First wrap/clip the position.  Wrapping is done in native positions
-   * while clipping is done in scroll (native) positions. */
-  map_to_native_pos(&nat_x0, &nat_y0, map_x0, map_y0);
+  /* First clip the position. */
   get_mapview_scroll_window(&xmin, &ymin, &xmax, &ymax, &xsize, &ysize);
 
-  if (topo_has_flag(TF_WRAPX)) {
-    nat_x0 = FC_WRAP(nat_x0, map.xsize);
-  } else {
-    nat_x0 = CLIP(xmin, nat_x0, xmax - xsize);
+  if (!topo_has_flag(TF_WRAPX)) {
+    gui_x0 = CLIP(xmin, gui_x0, xmax - xsize);
   }
 
-  if (topo_has_flag(TF_WRAPY)) {
-    nat_y0 = FC_WRAP(nat_y0, map.ysize);
-  } else {
-    nat_y0 = CLIP(ymin, nat_y0, ymax - ysize);
+  if (!topo_has_flag(TF_WRAPY)) {
+    gui_y0 = CLIP(ymin, gui_y0, ymax - ysize);
   }
 
-  native_to_map_pos(&map_x0, &map_y0, nat_x0, nat_y0);
+  /* Normalize the GUI position.  This is equivalent to a
+   * nearest_real_pos call on the center tile, but preserves pixel
+   * granularity. */
+  gui_x0 += mapview_canvas.width / 2;
+  gui_y0 += mapview_canvas.height / 2;
+  gui_to_map_pos(&map_x, &map_y, gui_x0, gui_y0);
+  map_to_gui_pos(&gui_x, &gui_y, map_x, map_y);
+  diff_x = gui_x - gui_x0;
+  diff_y = gui_y - gui_y0;
 
+  nearest_real_pos(&map_x, &map_y);
+  map_to_gui_pos(&gui_x, &gui_y, map_x, map_y);
+
+  gui_x0 = gui_x - diff_x - mapview_canvas.width / 2;
+  gui_y0 = gui_y - diff_y - mapview_canvas.height / 2;
+
   /* Then update everything. */
-  if (mapview_canvas.map_x0 != map_x0 || mapview_canvas.map_y0 != map_y0) {
+  if (mapview_canvas.gui_x0 != gui_x0 || mapview_canvas.gui_y0 != gui_y0) {
     int map_center_x, map_center_y;
 
-    mapview_canvas.map_x0 = map_x0;
-    mapview_canvas.map_y0 = map_y0;
+    mapview_canvas.gui_x0 = gui_x0;
+    mapview_canvas.gui_y0 = gui_y0;
 
     get_center_tile_mapcanvas(&map_center_x, &map_center_y);
     center_tile_overviewcanvas(map_center_x, map_center_y);
@@ -382,103 +404,54 @@
 void get_mapview_scroll_window(int *xmin, int *ymin, int *xmax, int *ymax,
                               int *xsize, int *ysize)
 {
-  /* There are a number of factors that must be taken into account in
-   * calculating these values:
-   *
-   * 1. Basic constraints: X should generally range from 0 to map.xsize;
-   * Y from 0 to map.ysize.
-   *
-   * 2. Non-aligned borders: if the borders don't line up (an iso-view client
-   * with a standard map, for instance) the minimum and maximum must be
-   * extended if the map doesn't wrap in that direction.  They are extended
-   * by an amount proportional to the size of the screen.
-   *
-   * 3. Compression: on an iso-map native coordinates are compressed 2x in
-   * the X direction.
-   *
-   * 4. Translation: the min and max values give a range for the origin.
-   * Since the base constraint is on the minimal value contained in the
-   * mapview, we have to translate the minimum and maximum to account for
-   * this.
-   *
-   * 5. Wrapping: if the map wraps in a given direction, no border adjustment
-   * or translation is needed.  Instead we have to make sure the range is
-   * large enough to get the full wrap.
-   */
-  *xmin = 0;
-  *ymin = 0;
-  *xmax = map.xsize;
-  *ymax = map.ysize;
-
-  if (topo_has_flag(TF_ISO) != is_isometric) {
-    /* The mapview window is aligned differently than the map.  In this
-     * case we need to give looser constraints because (if the map doesn't
-     * wrap) the edges will not line up well. */
-
-    /* These are the dimensions of the bounding box. */
-    *xsize = *ysize =
-      mapview_canvas.tile_width + mapview_canvas.tile_height;
-
-    if (is_isometric) {
-      /* Step 2: Calculate extra border distance. */
-      *xmin = *ymin = -(MAX(mapview_canvas.tile_width,
-                           mapview_canvas.tile_height) + 1) / 2;
-      *xmax -= *xmin;
-      *ymax -= *ymin;
-
-      /* Step 4: Translate the Y coordinate.  The mapview origin is at the
-       * top-left corner of the window, which is offset at +tile_width from
-       * the minimum Y value (at the top-right corner). */
-      *ymin += mapview_canvas.tile_width;
-      *ymax += mapview_canvas.tile_width;
-    } else {
-      /* Compression. */
-      *xsize = (*xsize + 1) / 2;
+  *xsize = mapview_canvas.width;
+  *ysize = mapview_canvas.height;
 
-      /* Step 2: calculate border adjustment. */
-      *ymin = -(MAX(mapview_canvas.tile_width,
-                   mapview_canvas.tile_height) + 1) / 2;
-      *xmin = (*ymin + 1) / 2; /* again compressed */
-      *xmax -= *xmin;
-      *ymax -= *ymin;
-
-      /* Step 4: translate the X coordinate.  The mapview origin is at the
-       * top-left corner of the window, which is offset at +tile_height/2
-       * from the minimum X value (at the bottom-left corner). */
-      *xmin += mapview_canvas.tile_height / 2;
-      *xmax += (mapview_canvas.tile_height + 1) / 2;
+  if (topo_has_flag(TF_ISO) == is_isometric) {
+    /* If the map and view line up, it's easy. */
+    native_to_map_pos(xmin, ymin, 0, 0);
+    map_to_gui_pos(xmin, ymin, *xmin, *ymin);
+
+    native_to_map_pos(xmax, ymax, map.xsize - 1, map.ysize - 1);
+    map_to_gui_pos(xmax, ymax, *xmax, *ymax);
+    *xmax += NORMAL_TILE_WIDTH;
+    *ymax += NORMAL_TILE_HEIGHT;
+    *xmin -= NORMAL_TILE_WIDTH;
+    *xmin -= NORMAL_TILE_HEIGHT;
+
+    /* To be able to center on positions near the edges, we have to be
+     * allowed to scroll past those edges. */
+    if (topo_has_flag(TF_WRAPX)) {
+      *xmax += *xsize / 2;
+      *xmin -= (*xsize + 1) / 2;
+    }
+    if (topo_has_flag(TF_WRAPY)) {
+      *ymax += *ysize / 2;
+      *ymin -= (*ysize + 1) / 2;
     }
   } else {
-    *xsize = mapview_canvas.tile_width;
-    *ysize = mapview_canvas.tile_height;
+    /* Otherwise it's hard.  Very hard.  Impossible, in fact.  This is just
+     * an approximation - a huge bounding box. */
+    int gui_x1, gui_y1, gui_x2, gui_y2, gui_x3, gui_y3, gui_x4, gui_y4;
+    int map_x, map_y;
 
-    if (is_isometric) {
-      /* Compression: Each vertical half-tile corresponds to one native
-       * unit (a full horizontal tile corresponds to a native unit). */
-      *ysize = (mapview_canvas.height - 1) / (NORMAL_TILE_HEIGHT / 2) + 1;
-
-      /* Isometric fixes: the above calculations are in half-tiles; since we
-       * need to see full tiles we have to extend the range a bit.  This also
-       * corrects for the off-by-one error caused by the X compression of
-       * native coordinates. */
-      (*xmin)--;
-      (*xmax)++;
-      (*ymax) += 2;
-    } else {
-      (*ymax)++;
-    }
-  }
+    native_to_map_pos(&map_x, &map_y, 0, 0);
+    map_to_gui_pos(&gui_x1, &gui_y1, map_x, map_y);
 
-  /* Now override the above to satisfy wrapping constraints.  We allow the
-   * scrolling to cover the full range of the map, plus one unit in each
-   * direction (to allow scrolling with the scroll bars, for instance). */
-  if (topo_has_flag(TF_WRAPX)) {
-    *xmin = -1;
-    *xmax = map.xsize + *xsize;
-  }
-  if (topo_has_flag(TF_WRAPY)) {
-    *ymin = -1;
-    *ymax = map.ysize + *ysize;
+    native_to_map_pos(&map_x, &map_y, map.xsize - 1, 0);
+    map_to_gui_pos(&gui_x2, &gui_y2, map_x, map_y);
+
+    native_to_map_pos(&map_x, &map_y, 0, map.ysize - 1);
+    map_to_gui_pos(&gui_x3, &gui_y3, map_x, map_y);
+
+    native_to_map_pos(&map_x, &map_y, map.xsize - 1, map.ysize - 1);
+    map_to_gui_pos(&gui_x4, &gui_y4, map_x, map_y);
+
+    *xmin = MIN(gui_x1, MIN(gui_x2, gui_x3)) - mapview_canvas.width / 2;
+    *ymin = MIN(gui_y1, MIN(gui_y2, gui_y3)) - mapview_canvas.height / 2;
+
+    *xmax = MAX(gui_x4, MAX(gui_x2, gui_x3)) + mapview_canvas.width / 2;
+    *ymax = MAX(gui_y4, MAX(gui_y2, gui_y3)) + mapview_canvas.height / 2;
   }
 
   freelog(LOG_DEBUG, "x: %d<-%d->%d; y: %d<-%d->%d",
@@ -491,8 +464,13 @@
 ****************************************************************************/
 void get_mapview_scroll_step(int *xstep, int *ystep)
 {
-  *xstep = 1;
-  *ystep = (topo_has_flag(TF_ISO) ? 2 : 1);
+  *xstep = NORMAL_TILE_WIDTH;
+  *ystep = NORMAL_TILE_HEIGHT;
+
+  if (is_isometric) {
+    *xstep /= 2;
+    *ystep /= 2;
+  }
 }
 
 /****************************************************************************
@@ -500,8 +478,8 @@
 ****************************************************************************/
 void get_mapview_scroll_pos(int *scroll_x, int *scroll_y)
 {
-  map_to_native_pos(scroll_x, scroll_y,
-                   mapview_canvas.map_x0, mapview_canvas.map_y0);
+  *scroll_x = mapview_canvas.gui_x0;
+  *scroll_y = mapview_canvas.gui_y0;
 }
 
 /****************************************************************************
@@ -509,10 +487,9 @@
 ****************************************************************************/
 void set_mapview_scroll_pos(int scroll_x, int scroll_y)
 {
-  int map_x0, map_y0;
+  int gui_x0 = scroll_x, gui_y0 = scroll_y;
 
-  native_to_map_pos(&map_x0, &map_y0, scroll_x, scroll_y);
-  set_mapview_origin(map_x0, map_y0);
+  set_mapview_origin(gui_x0, gui_y0);
 }
 
 /**************************************************************************
@@ -532,7 +509,9 @@
 **************************************************************************/
 void center_tile_mapcanvas(int map_center_x, int map_center_y)
 {
-  int map_x = map_center_x, map_y = map_center_y;
+  int map_x = map_center_x, map_y = map_center_y, gui_x, gui_y;
+
+  CHECK_MAP_POS(map_center_x, map_center_y);
 
   /* Find top-left corner. */
   if (is_isometric) {
@@ -545,7 +524,8 @@
     map_y -= mapview_canvas.tile_height / 2;
   }
 
-  set_mapview_origin(map_x, map_y);
+  map_to_gui_pos(&gui_x, &gui_y, map_x, map_y);
+  set_mapview_origin(gui_x, gui_y);
 }
 
 /**************************************************************************
@@ -1168,6 +1148,8 @@
 **************************************************************************/
 void update_map_canvas_visible(void)
 {
+  int map_x0, map_y0;
+
   dirty_all();
 
   /* Clear the entire mapview.  This is necessary since if the mapview is
@@ -1179,18 +1161,20 @@
   gui_put_rectangle(mapview_canvas.store, COLOR_STD_BLACK,
                    0, 0, mapview_canvas.width, mapview_canvas.height);
 
+  canvas_to_map_pos(&map_x0, &map_y0, 0, 0);
   if (is_isometric) {
     /* just find a big rectangle that includes the whole visible area. The
        invisible tiles will not be drawn. */
     int width, height;
 
-    width = height = mapview_canvas.tile_width + mapview_canvas.tile_height;
-    update_map_canvas(mapview_canvas.map_x0,
-                     mapview_canvas.map_y0 - mapview_canvas.tile_width,
+    width = mapview_canvas.tile_width + mapview_canvas.tile_height + 2;
+    height = width;
+    update_map_canvas(map_x0 - 1, map_y0 - mapview_canvas.tile_width - 1,
                      width, height, FALSE);
   } else {
-    update_map_canvas(mapview_canvas.map_x0, mapview_canvas.map_y0,
-                     mapview_canvas.tile_width, mapview_canvas.tile_height,
+    update_map_canvas(map_x0, map_y0,
+                     mapview_canvas.tile_width + 1,
+                     mapview_canvas.tile_height + 1,
                      FALSE);
   }
 
@@ -1203,6 +1187,7 @@
 void show_city_descriptions(void)
 {
   int canvas_x, canvas_y;
+  int map_x0, map_y0;
 
   if (!draw_city_names && !draw_city_productions) {
     return;
@@ -1210,12 +1195,13 @@
 
   prepare_show_city_descriptions();
 
+  canvas_to_map_pos(&map_x0, &map_y0, 0, 0);
   if (is_isometric) {
     int w, h;
 
     for (h = -1; h < mapview_canvas.tile_height * 2; h++) {
-      int x_base = mapview_canvas.map_x0 + h / 2 + (h != -1 ? h % 2 : 0);
-      int y_base = mapview_canvas.map_y0 + h / 2 + (h == -1 ? -1 : 0);
+      int x_base = map_x0 + h / 2 + (h != -1 ? h % 2 : 0);
+      int y_base = map_y0 + h / 2 + (h == -1 ? -1 : 0);
 
       for (w = 0; w <= mapview_canvas.tile_width; w++) {
        int x = x_base + w;
@@ -1232,10 +1218,10 @@
   } else {                     /* is_isometric */
     int x1, y1;
 
-    for (x1 = 0; x1 < mapview_canvas.tile_width; x1++) {
-      for (y1 = 0; y1 < mapview_canvas.tile_height; y1++) {
-       int x = mapview_canvas.map_x0 + x1;
-       int y = mapview_canvas.map_y0 + y1;
+    for (x1 = 0; x1 <= mapview_canvas.tile_width; x1++) {
+      for (y1 = 0; y1 <= mapview_canvas.tile_height; y1++) {
+       int x = map_x0 + x1;
+       int y = map_y0 + y1;
        struct city *pcity;
 
        if (normalize_map_pos(&x, &y)
@@ -1737,63 +1723,72 @@
   }
 }
 
+/****************************************************************************
+  Convert from GUI to overview positions, preserving pixel granularity.
+****************************************************************************/
+static void gui_to_overview_pos(int *scroll_x, int *scroll_y,
+                               int gui_x, int gui_y)
+{
+  const int W = NORMAL_TILE_WIDTH, H = NORMAL_TILE_HEIGHT;
+  int guimap_x, guimap_y, guinat_x, guinat_y;
+
+  /* This echoes the conversion in gui_to_map_pos, except we don't lose
+   * pixel granularity.  We transorm GUI tiles into square tiles with
+   * side W * H. */
+  if (is_isometric) {
+    gui_x -= W / 2;
+    guimap_x = gui_x * H + gui_y * W;
+    guimap_y = gui_y * W - gui_x * H;
+  } else {
+    guimap_x = gui_x * H;
+    guimap_y = gui_y * W;
+  }
+
+  /* Now convert into a "gui" native position.  This echoes the conversion
+   * in map_to_native_pos, except again we keep pixel granularity. */
+  if (is_isometric) {
+    guinat_y = guimap_x + guimap_y - map.xsize * W * H;
+    guinat_x = guimap_x - guinat_y / 2;
+  } else {
+    guinat_x = guimap_x;
+    guinat_y = guimap_y;
+  }
+
+  /* Translate and wrap to get the proper wrapped position.  This echoes
+   * the conversion done in map_to_scroll_pos, except again we keep pixel
+   * granularity. */
+  guinat_x -= overview.map_x0 * W * H;
+  guinat_y -= overview.map_y0 * W * H;
+
+  if (topo_has_flag(TF_WRAPX)) {
+    guinat_x = FC_WRAP(guinat_x, map.xsize * W * H);
+  }
+  if (topo_has_flag(TF_WRAPY)) {
+    guinat_y = FC_WRAP(guinat_y, map.ysize * W * H);
+  }
+
+  /* Finally scale the value. */
+  *scroll_x = guinat_x * OVERVIEW_TILE_WIDTH / (W * H);
+  *scroll_y = guinat_y * OVERVIEW_TILE_HEIGHT / (W * H);
+}
+
 /**************************************************************************
   Find the corners of the mapview, in overview coordinates.  Used to draw
   the "mapview window" rectangle onto the overview.
 **************************************************************************/
 static void get_mapview_corners(int x[4], int y[4])
 {
-  map_to_overview_pos(&x[0], &y[0],
-                     mapview_canvas.map_x0, mapview_canvas.map_y0);
-
-  /* Note: these calculations operate on overview coordinates as if they
-   * are native. */
-  if (is_isometric && !topo_has_flag(TF_ISO)) {
-    /* We start with the west corner. */
-
-    /* North */
-    x[1] = x[0] + OVERVIEW_TILE_WIDTH * mapview_canvas.tile_width;
-    y[1] = y[0] - OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_width;
-
-    /* East */
-    x[2] = x[1] + OVERVIEW_TILE_WIDTH * mapview_canvas.tile_height;
-    y[2] = y[1] + OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_height;
-
-    /* South */
-    x[3] = x[0] + OVERVIEW_TILE_WIDTH * mapview_canvas.tile_height;
-    y[3] = y[0] + OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_height;
-  } else if (!is_isometric && topo_has_flag(TF_ISO)) {
-    /* We start with the west corner.  Note the X scale is smaller. */
-
-    /* North */
-    x[1] = x[0] + OVERVIEW_TILE_WIDTH * mapview_canvas.tile_width / 2;
-    y[1] = y[0] + OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_width;
-
-    /* East */
-    x[2] = x[1] - OVERVIEW_TILE_WIDTH * mapview_canvas.tile_height / 2;
-    y[2] = y[1] + OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_height;
-
-    /* South */
-    x[3] = x[2] - OVERVIEW_TILE_WIDTH * mapview_canvas.tile_width / 2;
-    y[3] = y[2] - OVERVIEW_TILE_HEIGHT * mapview_canvas.tile_width;
-  } else {
-    /* We start with the northwest corner. */
-    int screen_width = mapview_canvas.tile_width;
-    int screen_height = mapview_canvas.tile_height * (is_isometric ? 2 : 1);
-
-    /* Northeast */
-    x[1] = x[0] + OVERVIEW_TILE_WIDTH * screen_width - 1;
-    y[1] = y[0];
-
-    /* Southeast */
-    x[2] = x[1];
-    y[2] = y[0] + OVERVIEW_TILE_HEIGHT * screen_height - 1;
-
-    /* Southwest */
-    x[3] = x[0];
-    y[3] = y[2];
-  }
-
+  gui_to_overview_pos(&x[0], &y[0],
+                     mapview_canvas.gui_x0, mapview_canvas.gui_y0);
+  gui_to_overview_pos(&x[1], &y[1],
+                     mapview_canvas.gui_x0 + mapview_canvas.width,
+                     mapview_canvas.gui_y0);
+  gui_to_overview_pos(&x[2], &y[2],
+                     mapview_canvas.gui_x0 + mapview_canvas.width,
+                     mapview_canvas.gui_y0 + mapview_canvas.height);
+  gui_to_overview_pos(&x[3], &y[3],
+                     mapview_canvas.gui_x0,
+                     mapview_canvas.gui_y0 + mapview_canvas.height);
   freelog(LOG_DEBUG, "(%d,%d)->(%d,%x)->(%d,%d)->(%d,%d)",
          x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]);
 }
@@ -1853,40 +1848,50 @@
 **************************************************************************/
 bool map_canvas_resized(int width, int height)
 {
+  int old_tile_width = mapview_canvas.tile_width;
+  int old_tile_height = mapview_canvas.tile_height;
+  int old_width = mapview_canvas.width, old_height = mapview_canvas.height;
   int tile_width = (width + NORMAL_TILE_WIDTH - 1) / NORMAL_TILE_WIDTH;
   int tile_height = (height + NORMAL_TILE_HEIGHT - 1) / NORMAL_TILE_HEIGHT;
-
-  if (mapview_canvas.tile_width == tile_width
-       && mapview_canvas.tile_height == tile_height) {
-      return FALSE;
-  }
+  int full_width = tile_width * NORMAL_TILE_WIDTH;
+  int full_height = tile_height * NORMAL_TILE_HEIGHT;
+  bool tile_size_changed, size_changed;
 
   /* Resized */
-
-  /* Since a resize is only triggered when the tile_*** changes, the canvas
-   * width and height must include the entire backing store - otherwise
-   * small resizings may lead to undrawn tiles. */
   mapview_canvas.tile_width = tile_width;
   mapview_canvas.tile_height = tile_height;
-
-  mapview_canvas.width = mapview_canvas.tile_width * NORMAL_TILE_WIDTH;
-  mapview_canvas.height = mapview_canvas.tile_height * NORMAL_TILE_HEIGHT;
-
-  if (mapview_canvas.store) {
-    canvas_store_free(mapview_canvas.store);
+  mapview_canvas.width = width;
+  mapview_canvas.height = height;
+  mapview_canvas.store_width = full_width;
+  mapview_canvas.store_height = full_height;
+
+  /* Check for what's changed. */
+  tile_size_changed = (tile_width != old_tile_width
+                      || tile_height != old_tile_height);
+  size_changed = (width != old_width || height != old_height);
+
+  /* If the tile size has changed, resize the canvas. */
+  if (tile_size_changed) {
+    if (mapview_canvas.store) {
+      canvas_store_free(mapview_canvas.store);
+    }
+    mapview_canvas.store = canvas_store_create(full_width, full_height);
+    gui_put_rectangle(mapview_canvas.store, COLOR_STD_BLACK, 0, 0,
+                     full_width, full_height);
   }
-  
-  mapview_canvas.store =
-      canvas_store_create(mapview_canvas.width, mapview_canvas.height);
-  gui_put_rectangle(mapview_canvas.store, COLOR_STD_BLACK, 0, 0,
-                   mapview_canvas.width, mapview_canvas.height);
 
   if (map_exists() && can_client_change_view()) {
-    update_map_canvas_visible();
+    if (tile_size_changed) {
+      update_map_canvas_visible();
+      refresh_overview_canvas();
+    }
 
-    update_map_canvas_scrollbars_size();
-    update_map_canvas_scrollbars();
-    refresh_overview_canvas();
+    /* If the width/height has changed, update the scrollbars even if
+     * the backing store is not resized. */
+    if (size_changed) {
+      update_map_canvas_scrollbars_size();
+      update_map_canvas_scrollbars();
+    }
   }
 
   return TRUE;
Index: client/mapview_common.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.h,v
retrieving revision 1.44
diff -u -r1.44 mapview_common.h
--- client/mapview_common.h     2004/02/21 22:15:03     1.44
+++ client/mapview_common.h     2004/02/24 03:35:00
@@ -23,9 +23,10 @@
 struct canvas_store;           /* opaque type, real type is gui-dep */
 
 struct canvas {
-  int map_x0, map_y0;
+  int gui_x0, gui_y0;
   int width, height;           /* Size in pixels. */
   int tile_width, tile_height; /* Size in tiles. Rounded up. */
+  int store_width, store_height;
   struct canvas_store *store;
 };
 
@@ -145,6 +146,7 @@
 
 bool map_to_canvas_pos(int *canvas_x, int *canvas_y, int map_x, int map_y);
 bool canvas_to_map_pos(int *map_x, int *map_y, int canvas_x, int canvas_y);
+void set_mapview_origin(int gui_x0, int gui_y0);
 
 void get_mapview_scroll_window(int *xmin, int *ymin,
                               int *xmax, int *ymax,

[Prev in Thread] Current Thread [Next in Thread]