1 /***********************************************************************
2 Freeciv - Copyright (C) 1996-2005 - Freeciv Development Team
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
15 #include <fc_config.h>
18 #include <math.h> /* floor */
24 #include "client_main.h" /* can_client_change_view() */
27 #include "mapview_g.h"
31 #include "overview_common.h"
33 int OVERVIEW_TILE_SIZE
= 2;
35 struct overview overview
= {
36 /* These are the default values. All others are zeroed automatically. */
38 .layers
= {[OLAYER_BACKGROUND
] = TRUE
,
39 [OLAYER_UNITS
] = TRUE
,
40 [OLAYER_CITIES
] = TRUE
,
41 [OLAYER_BORDERS_ON_OCEAN
] = TRUE
}
46 * Set to TRUE if the backing store is more recent than the version
47 * drawn into overview.window.
49 static bool overview_dirty
= FALSE
;
51 /****************************************************************************
52 Translate from gui to natural coordinate systems. This provides natural
53 coordinates as a floating-point value so there is no loss of information
54 in the resulting values.
55 ****************************************************************************/
56 static void gui_to_natural_pos(const struct tileset
*t
,
57 double *ntl_x
, double *ntl_y
,
60 const double gui_xd
= gui_x
, gui_yd
= gui_y
;
61 const double W
= tileset_tile_width(t
) * map_zoom
;
62 const double H
= tileset_tile_height(t
) * map_zoom
;
65 /* First convert to map positions. This ignores hex conversions; we're
66 * not looking for an exact tile. */
67 if (tileset_is_isometric(t
)) {
68 /* Includes hex cases. */
69 map_x
= (gui_xd
* H
+ gui_yd
* W
) / (W
* H
);
70 map_y
= (gui_yd
* W
- gui_xd
* H
) / (W
* H
);
76 /* Now convert to natural positions. Note this assumes the macro form
77 * of the conversion will work with floating-point values. */
78 MAP_TO_NATURAL_POS(ntl_x
, ntl_y
, map_x
, map_y
);
82 /****************************************************************************
83 Translate from gui to overview coordinate systems.
84 ****************************************************************************/
85 static void gui_to_overview_pos(const struct tileset
*t
,
86 int *ovr_x
, int *ovr_y
,
91 gui_to_natural_pos(t
, &ntl_x
, &ntl_y
, gui_x
, gui_y
);
93 /* Now convert straight to overview coordinates. */
94 *ovr_x
= floor((ntl_x
- (double)gui_options
.overview
.map_x0
) * OVERVIEW_TILE_SIZE
);
95 *ovr_y
= floor((ntl_y
- (double)gui_options
.overview
.map_y0
) * OVERVIEW_TILE_SIZE
);
97 /* Now do additional adjustments. See map_to_overview_pos(). */
98 if (current_topo_has_flag(TF_WRAPX
)) {
99 *ovr_x
= FC_WRAP(*ovr_x
, NATURAL_WIDTH
* OVERVIEW_TILE_SIZE
);
101 if (MAP_IS_ISOMETRIC
) {
102 /* HACK: For iso-maps that don't wrap in the X direction we clip
103 * a half-tile off of the left and right of the overview. This
104 * means some tiles only are halfway shown. However it means we
105 * don't show any unreal tiles, which we'd otherwise be doing. The
106 * rest of the code can't handle unreal tiles in the overview. */
107 *ovr_x
-= OVERVIEW_TILE_SIZE
;
110 if (current_topo_has_flag(TF_WRAPY
)) {
111 *ovr_y
= FC_WRAP(*ovr_y
, NATURAL_HEIGHT
* OVERVIEW_TILE_SIZE
);
115 /****************************************************************************
116 Return color for overview map tile.
117 ****************************************************************************/
118 static struct color
*overview_tile_color(struct tile
*ptile
)
120 if (gui_options
.overview
.layers
[OLAYER_CITIES
]) {
121 struct city
*pcity
= tile_city(ptile
);
124 if (NULL
== client
.conn
.playing
125 || city_owner(pcity
) == client
.conn
.playing
) {
126 return get_color(tileset
, COLOR_OVERVIEW_MY_CITY
);
127 } else if (pplayers_allied(city_owner(pcity
), client
.conn
.playing
)) {
128 /* Includes teams. */
129 return get_color(tileset
, COLOR_OVERVIEW_ALLIED_CITY
);
131 return get_color(tileset
, COLOR_OVERVIEW_ENEMY_CITY
);
135 if (gui_options
.overview
.layers
[OLAYER_UNITS
]) {
136 struct unit
*punit
= find_visible_unit(ptile
);
139 if (NULL
== client
.conn
.playing
140 || unit_owner(punit
) == client
.conn
.playing
) {
141 return get_color(tileset
, COLOR_OVERVIEW_MY_UNIT
);
142 } else if (pplayers_allied(unit_owner(punit
), client
.conn
.playing
)) {
143 /* Includes teams. */
144 return get_color(tileset
, COLOR_OVERVIEW_ALLIED_UNIT
);
146 return get_color(tileset
, COLOR_OVERVIEW_ENEMY_UNIT
);
150 if (gui_options
.overview
.layers
[OLAYER_BORDERS
]) {
151 struct player
*owner
= tile_owner(ptile
);
154 if (gui_options
.overview
.layers
[OLAYER_BORDERS_ON_OCEAN
]) {
155 return get_player_color(tileset
, owner
);
156 } else if (!is_ocean_tile(ptile
)) {
157 return get_player_color(tileset
, owner
);
161 if (gui_options
.overview
.layers
[OLAYER_RELIEF
]
162 && tile_terrain(ptile
) != T_UNKNOWN
) {
163 return get_terrain_color(tileset
, tile_terrain(ptile
));
165 if (gui_options
.overview
.layers
[OLAYER_BACKGROUND
]
166 && tile_terrain(ptile
) != T_UNKNOWN
) {
167 if (terrain_has_flag(tile_terrain(ptile
), TER_FROZEN
)) {
168 return get_color(tileset
, COLOR_OVERVIEW_FROZEN
);
170 if (is_ocean_tile(ptile
)) {
171 return get_color(tileset
, COLOR_OVERVIEW_OCEAN
);
173 return get_color(tileset
, COLOR_OVERVIEW_LAND
);
178 return get_color(tileset
, COLOR_OVERVIEW_UNKNOWN
);
181 /**************************************************************************
182 Copies the current centred image + viewrect unchanged to the client's
183 overview window (for expose events etc).
184 **************************************************************************/
185 void refresh_overview_from_canvas(void)
187 struct canvas
*dest
= get_overview_window();
191 canvas_copy(dest
, gui_options
.overview
.window
,
193 gui_options
.overview
.width
, gui_options
.overview
.height
);
196 /**************************************************************************
197 Copies the overview image from the backing store to the window and
198 draws the viewrect on top of it.
199 **************************************************************************/
200 static void redraw_overview(void)
204 if (!gui_options
.overview
.map
) {
209 struct canvas
*src
= gui_options
.overview
.map
;
210 struct canvas
*dst
= gui_options
.overview
.window
;
211 int x_left
= gui_options
.overview
.map_x0
* OVERVIEW_TILE_SIZE
;
212 int y_top
= gui_options
.overview
.map_y0
* OVERVIEW_TILE_SIZE
;
213 int ix
= gui_options
.overview
.width
- x_left
;
214 int iy
= gui_options
.overview
.height
- y_top
;
216 canvas_copy(dst
, src
, 0, 0, ix
, iy
, x_left
, y_top
);
217 canvas_copy(dst
, src
, 0, y_top
, ix
, 0, x_left
, iy
);
218 canvas_copy(dst
, src
, x_left
, 0, 0, iy
, ix
, y_top
);
219 canvas_copy(dst
, src
, x_left
, y_top
, 0, 0, ix
, iy
);
222 gui_to_overview_pos(tileset
, &x
[0], &y
[0],
223 mapview
.gui_x0
, mapview
.gui_y0
);
224 gui_to_overview_pos(tileset
, &x
[1], &y
[1],
225 mapview
.gui_x0
+ mapview
.width
, mapview
.gui_y0
);
226 gui_to_overview_pos(tileset
, &x
[2], &y
[2],
227 mapview
.gui_x0
+ mapview
.width
,
228 mapview
.gui_y0
+ mapview
.height
);
229 gui_to_overview_pos(tileset
, &x
[3], &y
[3],
230 mapview
.gui_x0
, mapview
.gui_y0
+ mapview
.height
);
232 for (i
= 0; i
< 4; i
++) {
235 int dst_x
= x
[(i
+ 1) % 4];
236 int dst_y
= y
[(i
+ 1) % 4];
238 canvas_put_line(gui_options
.overview
.window
,
239 get_color(tileset
, COLOR_OVERVIEW_VIEWRECT
),
241 src_x
, src_y
, dst_x
- src_x
, dst_y
- src_y
);
244 refresh_overview_from_canvas();
246 overview_dirty
= FALSE
;
249 /****************************************************************************
250 Mark the overview as "dirty" so that it will be redrawn soon.
251 ****************************************************************************/
252 static void dirty_overview(void)
254 overview_dirty
= TRUE
;
257 /****************************************************************************
258 Redraw the overview if it is "dirty".
259 ****************************************************************************/
260 void flush_dirty_overview(void)
262 /* Currently this function is called from mapview_common. However it
263 * should be made static eventually. */
264 if (overview_dirty
) {
269 /****************************************************************************
270 Equivalent to FC_WRAP, but it works for doubles.
271 ****************************************************************************/
272 static double wrap_double(double value
, double wrap
)
277 while (value
>= wrap
) {
283 /**************************************************************************
284 Center the overview around the mapview.
285 **************************************************************************/
286 void center_tile_overviewcanvas(void)
291 gui_to_natural_pos(tileset
, &ntl_x
, &ntl_y
,
292 mapview
.gui_x0
+ mapview
.width
/ 2,
293 mapview
.gui_y0
+ mapview
.height
/ 2);
295 /* NOTE: this embeds the map wrapping in the overview code. This is
296 * basically necessary for the overview to be efficiently
298 if (current_topo_has_flag(TF_WRAPX
)) {
299 gui_options
.overview
.map_x0
= wrap_double(ntl_x
- (double)NATURAL_WIDTH
/ 2.0,
302 gui_options
.overview
.map_x0
= 0;
304 if (current_topo_has_flag(TF_WRAPY
)) {
305 gui_options
.overview
.map_y0
= wrap_double(ntl_y
- (double)NATURAL_HEIGHT
/ 2.0,
308 gui_options
.overview
.map_y0
= 0;
313 gui_to_overview_pos(tileset
, &ox
, &oy
,
314 mapview
.gui_x0
, mapview
.gui_y0
);
315 update_overview_scroll_window_pos(ox
, oy
);
318 /**************************************************************************
319 Finds the overview (canvas) coordinates for a given map position.
320 **************************************************************************/
321 void map_to_overview_pos(int *overview_x
, int *overview_y
,
322 int map_x
, int map_y
)
324 /* The map position may not be normal, for instance when the mapview
325 * origin is not a normal position.
327 * NOTE: this embeds the map wrapping in the overview code. */
328 do_in_natural_pos(ntl_x
, ntl_y
, map_x
, map_y
) {
329 int ovr_x
= ntl_x
- gui_options
.overview
.map_x0
;
330 int ovr_y
= ntl_y
- gui_options
.overview
.map_y0
;
332 if (current_topo_has_flag(TF_WRAPX
)) {
333 ovr_x
= FC_WRAP(ovr_x
, NATURAL_WIDTH
);
335 if (MAP_IS_ISOMETRIC
) {
336 /* HACK: For iso-maps that don't wrap in the X direction we clip
337 * a half-tile off of the left and right of the overview. This
338 * means some tiles only are halfway shown. However it means we
339 * don't show any unreal tiles, which we'd otherwise be doing. The
340 * rest of the code can't handle unreal tiles in the overview. */
344 if (current_topo_has_flag(TF_WRAPY
)) {
345 ovr_y
= FC_WRAP(ovr_y
, NATURAL_HEIGHT
);
347 *overview_x
= OVERVIEW_TILE_SIZE
* ovr_x
;
348 *overview_y
= OVERVIEW_TILE_SIZE
* ovr_y
;
349 } do_in_natural_pos_end
;
352 /**************************************************************************
353 Finds the map coordinates for a given overview (canvas) position.
354 **************************************************************************/
355 void overview_to_map_pos(int *map_x
, int *map_y
,
356 int overview_x
, int overview_y
)
358 int ntl_x
= overview_x
/ OVERVIEW_TILE_SIZE
+ gui_options
.overview
.map_x0
;
359 int ntl_y
= overview_y
/ OVERVIEW_TILE_SIZE
+ gui_options
.overview
.map_y0
;
361 if (MAP_IS_ISOMETRIC
&& !current_topo_has_flag(TF_WRAPX
)) {
362 /* Clip half tile left and right. See comment in map_to_overview_pos. */
366 NATURAL_TO_MAP_POS(map_x
, map_y
, ntl_x
, ntl_y
);
367 /* All positions on the overview should be valid. */
368 fc_assert(normalize_map_pos(&(wld
.map
), map_x
, map_y
));
371 /**************************************************************************
372 Redraw the entire backing store for the overview minimap.
373 **************************************************************************/
374 void refresh_overview_canvas(void)
376 if (!can_client_change_view()) {
379 whole_map_iterate(&(wld
.map
), ptile
) {
380 overview_update_tile(ptile
);
381 } whole_map_iterate_end
;
385 /****************************************************************************
386 Draws the color for this tile onto the given rectangle of the canvas.
388 This is just a simple helper function for overview_update_tile, since
389 sometimes a tile may cover more than one rectangle.
390 ****************************************************************************/
391 static void put_overview_tile_area(struct canvas
*pcanvas
,
393 int x
, int y
, int w
, int h
)
395 canvas_put_rectangle(pcanvas
,
396 overview_tile_color(ptile
),
398 if (gui_options
.overview
.fog
399 && TILE_KNOWN_UNSEEN
== client_tile_get_known(ptile
)) {
400 canvas_put_sprite(pcanvas
, x
, y
, get_basic_fog_sprite(tileset
),
405 /**************************************************************************
406 Redraw the given map position in the overview canvas.
407 **************************************************************************/
408 void overview_update_tile(struct tile
*ptile
)
412 /* Base overview positions are just like natural positions, but scaled to
413 * the overview tile dimensions. */
414 index_to_map_pos(&tile_x
, &tile_y
, tile_index(ptile
));
415 do_in_natural_pos(ntl_x
, ntl_y
, tile_x
, tile_y
) {
416 int overview_y
= ntl_y
* OVERVIEW_TILE_SIZE
;
417 int overview_x
= ntl_x
* OVERVIEW_TILE_SIZE
;
419 if (MAP_IS_ISOMETRIC
) {
420 if (current_topo_has_flag(TF_WRAPX
)) {
421 if (overview_x
> gui_options
.overview
.width
- OVERVIEW_TILE_WIDTH
) {
422 /* This tile is shown half on the left and half on the right
423 * side of the overview. So we have to draw it in two parts. */
424 put_overview_tile_area(gui_options
.overview
.map
, ptile
,
425 overview_x
- gui_options
.overview
.width
,
427 OVERVIEW_TILE_WIDTH
, OVERVIEW_TILE_HEIGHT
);
430 /* Clip half tile left and right.
431 * See comment in map_to_overview_pos. */
432 overview_x
-= OVERVIEW_TILE_SIZE
;
436 put_overview_tile_area(gui_options
.overview
.map
, ptile
,
437 overview_x
, overview_y
,
438 OVERVIEW_TILE_WIDTH
, OVERVIEW_TILE_HEIGHT
);
441 } do_in_natural_pos_end
;
444 /**************************************************************************
445 Called if the map size is know or changes.
446 **************************************************************************/
447 void calculate_overview_dimensions(void)
450 int xfact
= MAP_IS_ISOMETRIC
? 2 : 1;
452 static int recursion
= 0; /* Just to be safe. */
454 /* Clip half tile left and right. See comment in map_to_overview_pos. */
455 int shift
= (MAP_IS_ISOMETRIC
&& !current_topo_has_flag(TF_WRAPX
)) ? -1 : 0;
457 if (recursion
> 0 || wld
.map
.xsize
<= 0 || wld
.map
.ysize
<= 0) {
462 get_overview_area_dimensions(&w
, &h
);
463 get_overview_area_dimensions(&w
, &h
);
465 /* Set the scale of the overview map. This attempts to limit the
466 * overview to the size of the area available.
468 * It rounds up since this gives good results with the default settings.
469 * It may need tweaking if the panel resizes itself. */
470 OVERVIEW_TILE_SIZE
= MIN((w
- 1) / (wld
.map
.xsize
* xfact
) + 1,
471 (h
- 1) / wld
.map
.ysize
+ 1);
472 OVERVIEW_TILE_SIZE
= MAX(OVERVIEW_TILE_SIZE
, 1);
474 log_debug("Map size %d,%d - area size %d,%d - scale: %d", wld
.map
.xsize
,
475 wld
.map
.ysize
, w
, h
, OVERVIEW_TILE_SIZE
);
477 gui_options
.overview
.width
478 = OVERVIEW_TILE_WIDTH
* wld
.map
.xsize
+ shift
* OVERVIEW_TILE_SIZE
;
479 gui_options
.overview
.height
= OVERVIEW_TILE_HEIGHT
* wld
.map
.ysize
;
481 if (gui_options
.overview
.map
) {
482 canvas_free(gui_options
.overview
.map
);
483 canvas_free(gui_options
.overview
.window
);
485 gui_options
.overview
.map
= canvas_create(gui_options
.overview
.width
,
486 gui_options
.overview
.height
);
487 gui_options
.overview
.window
= canvas_create(gui_options
.overview
.width
,
488 gui_options
.overview
.height
);
489 canvas_put_rectangle(gui_options
.overview
.map
,
490 get_color(tileset
, COLOR_OVERVIEW_UNKNOWN
),
492 gui_options
.overview
.width
, gui_options
.overview
.height
);
493 update_map_canvas_scrollbars_size();
495 /* Call gui specific function. */
496 overview_size_changed();
498 if (can_client_change_view()) {
499 refresh_overview_canvas();
505 /****************************************************************************
506 Free overview resources.
507 ****************************************************************************/
508 void overview_free(void)
510 if (gui_options
.overview
.map
) {
511 canvas_free(gui_options
.overview
.map
);
512 canvas_free(gui_options
.overview
.window
);
513 gui_options
.overview
.map
= NULL
;
514 gui_options
.overview
.window
= NULL
;
518 /****************************************************************************
519 Callback to be called when an overview option is changed.
520 ****************************************************************************/
521 void overview_redraw_callback(struct option
*option
)
523 /* This is called once for each option changed so it is slower than
524 * necessary. If this becomes a problem it could be switched to use a
525 * queue system like the mapview drawing code does. */
526 refresh_overview_canvas();