Complete.Org: Mailing Lists: Archives: freeciv-dev: January 2002:
[Freeciv-Dev] update for isometric scrolling problem (PR#1222)
Home

[Freeciv-Dev] update for isometric scrolling problem (PR#1222)

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: freeciv-dev@xxxxxxxxxxx
Cc: bugs@xxxxxxxxxxxxxxxxxxx
Subject: [Freeciv-Dev] update for isometric scrolling problem (PR#1222)
From: jdorje@xxxxxxxxxxxxxxxxxxxxx
Date: Wed, 16 Jan 2002 15:16:12 -0800 (PST)

After some extended thought, I think I've come up with the correct solution for the isometric scrolling problem.

I won't describe it in detail here, but there is extensive comment explanation in the patch itself. As far as I can tell it now does everything that it should.


There are issues with isometric scrolling on a flat-rectangular map: the scrollbars basically cover the entire bounding isometrically-shaped box of the rectangular map:

________
|  /\  |
| /  \ | <- the outer box is the scrolling area, the inner box
|/    \|    is the map
|\    /|
| \  / |
|__\/__|

So if you try to scroll to (0,0), you'll wrap around long before you get there - this is pretty confusing. If you try to scroll to the top-right corner, you'll hit the north pole and won't be able to make it the whole way.

But, as I said, these problems are all because the map doesn't line up with the scrolling. This patch separates the topology calculations from the scrollbar calculations, so the problems should go away on an isometric map.

jason
Index: client/mapview_common.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.c,v
retrieving revision 1.4
diff -u -r1.4 mapview_common.c
--- client/mapview_common.c     2001/12/21 16:53:10     1.4
+++ client/mapview_common.c     2002/01/16 21:51:41
@@ -20,6 +20,7 @@
 #include "map.h"
 #include "mapview_g.h"
 
+#include "civclient.h" /* for get_client_state() */
 #include "mapview_common.h"
 #include "tilespec.h"
 
@@ -306,4 +307,192 @@
   /* rule e */
   last_pcity = pcity2;
   return pcity2;
+}
+
+/*
+ * The Mapview Scrollbars
+ *
+ * This is common code that should enable proper scrolling to work in
+ * all GUI's.
+ *
+ * In the basic case, we have a rectangular map (size xsize x ysize)
+ * with a rectangular mapview window (size twidth x theight) sitting on
+ * top of it.  In this case, scrolling is easy: the scrollbar may take
+ * on a position between ([0..xsize),[0..ysize)), and the scrollbar
+ * sliders have their sizes set to twidth and theight.  Now the user
+ * can slide the mapview window anywhere in the range of
+ * ([0..xsize-twidth),[0..ysize-theight)) which allows full coverage of
+ * the window [1].
+ *
+ * In the isometric-view case, things are substantially more complicated.
+ * The solution is to use "isometric coordinates" as an intermediary
+ * between the scrollbar coordinates and map coordinates.  So instead of
+ * having a rectangular map, we will consider it to be iso-rectangular
+ * in isometric coordinates:
+ *                         1
+ *           123          4 2
+ *   the map 456 becomes 7 5 3
+ *           789          8 6
+ *                         9
+ * and we get the scrollbar positions by dividing the isometric
+ * coordinates by 2.  The horizontal and vertical sliders both take
+ * sizes of (twidth + theight)/2.  This means that if your mapview is
+ * at position (6) and you scroll up, you will move up 1 scrollbar
+ * unit to position (2).  This then gives us another problem: position
+ * (5) cannot be scrolled to, nor is it possible to represent it as a
+ * scrollbar position at all.  Under GTK, this means that if we manually
+ * move the mapview to position (5), the scrollbar callback will be
+ * called and we'll end up at position (4).  This is solved by
+ * introducing scrollbar "offsets" so that position (5) would be
+ * represented by the scrollbar coordinates for position (4) with a
+ * (+1, +1) offset (note the offsets are in isometric units).  The
+ * offsets are not changed when scrolling, so you can scroll up from
+ * position (5) and get to position (1).  But any time you convert from
+ * scrollbar positions to map positions (or vice versa) they are dealt
+ * with. [1]
+ *
+ * [1] We deal with the fact that the bottom/right row of the mapview
+ *     may not cover a full tile by adding on EXTRA_BOTTOM_ROW to
+ *     the scrollbar range if necessary.
+ */
+struct mapview_scrollbar mapview_scrollbars = {-1, -1, 0, 0};
+
+/**************************************************************************
+  This function determines the scrollbar positions and offsets for the
+  scrollbars used by the mapview window, given the mapview's origin.
+
+  See comment "The Mapview Scrollbars" up above.
+**************************************************************************/
+void set_scrollbar_positions(int map_view_topleft_map_x,
+                            int map_view_topleft_map_y)
+{
+  /*
+   * In overhead (flat) view, this is easy: just take the window origin
+   * as the scrollbar index.  In isometric (iso) view, it is substantially
+   * more complicated since we need to convert to isometric coordinates
+   * and compress them.
+   */
+  if (is_isometric) {
+    int iso_x, iso_y;
+    map_to_iso_pos(&iso_x, &iso_y,
+                  map_view_topleft_map_x, map_view_topleft_map_y);
+    mapview_scrollbars.x = iso_x / 2;
+    mapview_scrollbars.y = iso_y / 2;
+    mapview_scrollbars.x_offset = iso_x % 2;
+    mapview_scrollbars.y_offset = iso_y % 2;
+  } else {
+    /* If not isometric... */
+    mapview_scrollbars.x = map_view_topleft_map_x;
+    mapview_scrollbars.y = map_view_topleft_map_y;
+    mapview_scrollbars.x_offset = mapview_scrollbars.y_offset = 0;
+  }
+}
+
+/**************************************************************************
+  This function determines the size of the sliders in the horizontal and
+  vertical scrollbars for the mapview.
+
+  See comment "The Mapview Scrollbars" up above.
+**************************************************************************/
+void get_scrollbar_slider_sizes(int *scroll_slider_width,
+                               int *scroll_slider_height,
+                               int map_view_map_width,
+                               int map_view_map_height)
+{
+  if (is_isometric) {
+    *scroll_slider_width = *scroll_slider_height =
+       (map_view_map_width + map_view_map_height) / 2;
+  } else {
+    /* If not isometric... */
+    *scroll_slider_width = map_view_map_width;
+    *scroll_slider_height = map_view_map_height;
+  }
+}
+
+/**************************************************************************
+  This function determines the total sizes of the scrollbars (both
+  horizontal and vertical) for the mapview.
+
+  See comment "The Mapview Scrollbars" up above.
+**************************************************************************/
+void get_scrollbar_sizes(int *scroll_xsize, int *scroll_ysize)
+{
+  if (is_isometric) {
+    /*
+     * We add on an EXTRA_BOTTOM_ROW just to be safe - note this
+     * probably isn't correct under a different topology, but it
+     * shouldn't be much of a problem.  We also have to divide by
+     * 2 since the iso positions are on a different scale.
+     */
+    get_iso_position_range(scroll_xsize, scroll_ysize);
+    *scroll_xsize = (*scroll_xsize + EXTRA_BOTTOM_ROW) / 2;
+    *scroll_ysize = (*scroll_ysize + EXTRA_BOTTOM_ROW) / 2;
+  } else {
+    /* If not isometric... */
+    *scroll_xsize = map.xsize;
+    *scroll_ysize = map.ysize + EXTRA_BOTTOM_ROW;
+  }
+}
+
+/**************************************************************************
+  This function centers the mapview given a change in the scrollbar
+  position of one of the scrollbars.
+
+  There's a bit of a problem here: say a user in iso-view scrolls up,
+  and the resulting topology adjustment causes the horizontal scrollbar
+  to move.  Getting the GUI to update this information is very tricky.
+  The current solution is to return TRUE if the mapview origin has
+  changed at all, in which case we leave it up to the GUI to update
+  the scrollbars if it deems necessary.
+
+  See comment "The Mapview Scrollbars" up above.
+**************************************************************************/
+int center_mapview_from_scrollbar(int *map_view_topleft_map_x,
+                                 int *map_view_topleft_map_y,
+                                 int map_view_map_width,
+                                 int map_view_map_height)
+{
+  int old_map_view_topleft_map_x = *map_view_topleft_map_x;
+  int old_map_view_topleft_map_y = *map_view_topleft_map_y;
+
+  if(get_client_state() != CLIENT_GAME_RUNNING_STATE) {
+    return 0;
+  }
+
+  assert(mapview_scrollbars.x >= 0 && mapview_scrollbars.y >= 0);
+
+  if (is_isometric) {
+    int iso_x = mapview_scrollbars.x * 2 + mapview_scrollbars.x_offset;
+    int iso_y = mapview_scrollbars.y * 2 + mapview_scrollbars.y_offset;
+    iso_to_map_pos(map_view_topleft_map_x, map_view_topleft_map_y,
+                  iso_x, iso_y);
+  } else {
+    *map_view_topleft_map_x = mapview_scrollbars.x;
+    *map_view_topleft_map_y = mapview_scrollbars.y;
+  }
+
+  /* Note: the second half of this function could be unified with
+     base_center_tile_mapcanvas. */
+
+  /*
+   * Account for topology considerations.  This currently hard-codes
+   * the topology, which is bad.  It also currently only uses
+   * map_view_map_height, but under different topologies would also use
+   * map_view_map_width.
+   */
+  nearest_real_pos(map_view_topleft_map_x, map_view_topleft_map_y);
+  *map_view_topleft_map_y =
+      (*map_view_topleft_map_y > map.ysize + EXTRA_BOTTOM_ROW -
+                                map_view_map_height) ?
+      map.ysize + EXTRA_BOTTOM_ROW - map_view_map_height :
+      *map_view_topleft_map_y;
+
+  /* Finally, we update the graphics. */
+  if (   old_map_view_topleft_map_x != *map_view_topleft_map_x
+      || old_map_view_topleft_map_y != *map_view_topleft_map_y) {
+    update_map_canvas_visible();
+    refresh_overview_viewrect();
+    return 1;
+  }
+  return 0;
 }
Index: client/mapview_common.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.h,v
retrieving revision 1.3
diff -u -r1.3 mapview_common.h
--- client/mapview_common.h     2001/12/21 16:53:10     1.3
+++ client/mapview_common.h     2002/01/16 21:51:41
@@ -62,4 +62,27 @@
                                
 struct city *find_city_near_tile(int x, int y);
 
+/*
+ * Scrollbar code - the GUI may call these functions to handle
+ * scrollbars for either standard or isometric views.
+ */
+struct mapview_scrollbar {
+  int x, y;
+  int x_offset, y_offset;
+};
+
+extern struct mapview_scrollbar mapview_scrollbars;
+
+void set_scrollbar_positions(int map_view_topleft_map_x,
+                            int map_view_topleft_map_y);
+void get_scrollbar_slider_sizes(int *scroll_slider_width,
+                               int *scroll_slider_height,
+                               int map_view_map_width,
+                               int map_view_map_height);
+void get_scrollbar_sizes(int *scroll_xsize, int *scroll_ysize);
+int center_mapview_from_scrollbar(int *map_view_topleft_map_x,
+                                  int *map_view_topleft_map_y,
+                                  int map_view_map_width,
+                                  int map_view_map_height);
+
 #endif /* FC__MAPVIEW_COMMON_H */
Index: client/gui-gtk/mapview.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/gui-gtk/mapview.c,v
retrieving revision 1.111
diff -u -r1.111 mapview.c
--- client/gui-gtk/mapview.c    2001/12/21 19:14:18     1.111
+++ client/gui-gtk/mapview.c    2002/01/16 21:51:45
@@ -1724,23 +1724,35 @@
 }
 
 /**************************************************************************
-...
+  This function updates the scrollbars by setting how far each one is
+  scrolled.
 **************************************************************************/
 void update_map_canvas_scrollbars(void)
 {
-  gtk_adjustment_set_value(GTK_ADJUSTMENT(map_hadj), map_view_x0);
-  gtk_adjustment_set_value(GTK_ADJUSTMENT(map_vadj), map_view_y0);
+  set_scrollbar_positions(map_view_x0, map_view_y0);
+  gtk_adjustment_set_value(GTK_ADJUSTMENT(map_hadj), mapview_scrollbars.x);
+  gtk_adjustment_set_value(GTK_ADJUSTMENT(map_vadj), mapview_scrollbars.y);
 }
 
 /**************************************************************************
-...
+  This function sets the size of the scrollbars and sliders, for instance
+  on startup or when the window is resized.
 **************************************************************************/
 void update_map_canvas_scrollbars_size(void)
 {
-  map_hadj=gtk_adjustment_new(-1, 0, map.xsize, 1,
-          map_canvas_store_twidth, map_canvas_store_twidth);
-  map_vadj=gtk_adjustment_new(-1, 0, map.ysize+EXTRA_BOTTOM_ROW, 1,
-          map_canvas_store_theight, map_canvas_store_theight);
+  int scroll_slider_width, scroll_slider_height;
+  int scroll_xsize, scroll_ysize;
+
+  get_scrollbar_slider_sizes(&scroll_slider_width,
+                            &scroll_slider_height,
+                            map_canvas_store_twidth,
+                            map_canvas_store_theight);
+  get_scrollbar_sizes(&scroll_xsize, &scroll_ysize);
+
+  map_hadj=gtk_adjustment_new(-1, 0, scroll_xsize, 1,
+                             scroll_slider_width, scroll_slider_width);
+  map_vadj=gtk_adjustment_new(-1, 0, scroll_ysize, 1,
+                             scroll_slider_height, scroll_slider_height);
   gtk_range_set_adjustment(GTK_RANGE(map_horizontal_scrollbar),
        GTK_ADJUSTMENT(map_hadj));
   gtk_range_set_adjustment(GTK_RANGE(map_vertical_scrollbar),
@@ -1753,38 +1765,27 @@
 }
 
 /**************************************************************************
-...
+  This callback function is called by GTK when the scrollbar is moved.
 **************************************************************************/
 void scrollbar_jump_callback(GtkAdjustment *adj, gpointer hscrollbar)
 {
-  int last_map_view_x0;
-  int last_map_view_y0;
-
-  gfloat percent=adj->value;
-
-  if(get_client_state()!=CLIENT_GAME_RUNNING_STATE)
-     return;
-
-  last_map_view_x0=map_view_x0;
-  last_map_view_y0=map_view_y0;
-
-  if(hscrollbar)
-    map_view_x0=percent;
-  else {
-    map_view_y0=percent;
-    map_view_y0=(map_view_y0<0) ? 0 : map_view_y0;
-    map_view_y0=
-      (map_view_y0>map.ysize+EXTRA_BOTTOM_ROW-map_canvas_store_theight) ? 
-      map.ysize+EXTRA_BOTTOM_ROW-map_canvas_store_theight :
-      map_view_y0;
+  if (hscrollbar) {
+    mapview_scrollbars.x = adj->value;
+  } else {
+    mapview_scrollbars.y = adj->value;
   }
 
-  if (last_map_view_x0!=map_view_x0 || last_map_view_y0!=map_view_y0) {
-    update_map_canvas_visible();
-    refresh_overview_viewrect();
+  /* This call handles all calculations _and_ updates the graphics. */
+  if (center_mapview_from_scrollbar(&map_view_x0, &map_view_y0,
+                               map_canvas_store_twidth,
+                               map_canvas_store_theight)) {
+    /* Danger - calling update_map_canvas_scrollbars will trigger
+       another callback, invoking this function again (twice!).  This
+       is less than ideal, but since the positions should remain the
+       same it won't have any real bad effects. */
+    update_map_canvas_scrollbars();
   }
 }
-
   
 /**************************************************************************
 draw a line from src_x,src_y -> dest_x,dest_y on both map_canvas and
Index: common/map.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/map.c,v
retrieving revision 1.103
diff -u -r1.103 map.c
--- common/map.c        2001/12/13 19:13:16     1.103
+++ common/map.c        2002/01/16 21:51:51
@@ -1330,6 +1330,61 @@
 }
 
 /**************************************************************************
+This converts a map position to an "isometric" position.  It is effectively
+a -pi/4 (clockwise) rotation with an associated sqrt(2) scaling, followed
+by a shift to make sure all values are positive.
+
+The basic transformation is like this:
+                  1
+    123          4 2
+    456  ---->  7 5 3
+    789          8 6
+                  9
+but we also have to shift things because we want (iso_x, iso_y) to be
+in the range ([0..iso_x_range), [0..iso_y_range)).  So we subtract off
+the minimum iso x and y values to achieve this.
+
+In the above case, you can see that position (7) provides the minimum
+iso X and (3) provides the maximum.  (1) provides the minimum iso Y and
+(9) provides the maximum.  For the standard rectangle-shaped map, this
+means the "minimum iso x and y" from above are (1-map.ysize, 0), and
+after the shift the iso ranges both become (map.xsize + map.ysize - 1).
+
+There are three associated functions for this conversion process:
+  map_to_iso_pos, iso_to_map_pos, get_iso_position_range.
+
+Note, this is the operation an isometric-view client will use to convert
+from map to canvas coordinates.
+**************************************************************************/
+void map_to_iso_pos(int *iso_x, int *iso_y, int map_x, int map_y)
+{
+  *iso_x = (map_x - map_y) - (1 - map.ysize);
+  *iso_y = map_x + map_y;
+}
+
+/**************************************************************************
+Converts an isometric position to a map position.  See map_to_iso_pos()
+for a full explanation.
+
+Note, using this code for non-map-related iso coordinates is dangerous,
+since the rounding (in the division by 2 part) may not be done correctly.
+**************************************************************************/
+void iso_to_map_pos(int *map_x, int *map_y, int iso_x, int iso_y)
+{
+  *map_x = (iso_x + iso_y + (1 - map.ysize)) / 2;
+  *map_y = (iso_y - iso_x - (1 - map.ysize)) / 2;
+}
+
+/**************************************************************************
+Defines the range of values for isometric positions.  See map_to_iso_pos()
+for a full explanation.
+**************************************************************************/
+void get_iso_position_range(int *iso_x_range, int *iso_y_range)
+{
+  *iso_x_range = *iso_y_range = map.xsize + map.ysize - 1;
+}
+
+/**************************************************************************
 Random neighbouring square.
 **************************************************************************/
 void rand_neighbour(int x0, int y0, int *x, int *y)
Index: common/map.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/map.h,v
retrieving revision 1.109
diff -u -r1.109 map.h
--- common/map.h        2002/01/06 16:43:26     1.109
+++ common/map.h        2002/01/16 21:51:53
@@ -305,6 +305,10 @@
 void map_distance_vector(int *dx, int *dy, int x0, int y0, int x1, int y1);
 int map_num_tiles(void);
 
+void map_to_iso_pos(int *iso_x, int *iso_y, int map_x, int map_y);
+void iso_to_map_pos(int *map_x, int *map_y, int iso_x, int iso_y);
+void get_iso_position_range(int *iso_x_range, int *iso_y_range);
+
 void rand_neighbour(int x0, int y0, int *x, int *y);
 void rand_map_pos(int *x, int *y);
 

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