# viewport.cpp

Go to the documentation of this file.

    00001 /* $Id: viewport.cpp 14269 2008-09-07 22:14:48Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "debug.h"
    00008 #include "tile_cmd.h"
    00009 #include "gui.h"
    00010 #include "spritecache.h"
    00011 #include "landscape.h"
    00012 #include "viewport_func.h"
    00013 #include "station.h"
    00014 #include "town.h"
    00015 #include "signs.h"
    00016 #include "waypoint.h"
    00017 #include "variables.h"
    00018 #include "train.h"
    00019 #include "roadveh.h"
    00020 #include "vehicle_gui.h"
    00021 #include "blitter/factory.hpp"
    00022 #include "transparency.h"
    00023 #include "strings_func.h"
    00024 #include "zoom_func.h"
    00025 #include "vehicle_func.h"
    00026 #include "player_func.h"
    00027 #include "settings_type.h"
    00028
    00029 #include "table/sprites.h"
    00030 #include "table/strings.h"
    00031
    00032 #define VIEWPORT_DRAW_MEM (65536 * 2)
    00033
    00034 PlaceProc *_place_proc;
    00035 Point _tile_fract_coords;
    00036 ZoomLevel _saved_scrollpos_zoom;
    00037
    00053 static ViewPort _viewports[MAX_NUMBER_OF_WINDOWS - 1];
    00054 static uint32 _active_viewports;
    00055 assert_compile(lengthof(_viewports) < sizeof(_active_viewports) * 8);
    00056
    00057 /* The in-game coordiante system looks like this *
    00058  *                                               *
    00059  *                    ^ Z                        *
    00060  *                    |                          *
    00061  *                    |                          *
    00062  *                    |                          *
    00063  *                    |                          *
    00064  *                 /     \                       *
    00065  *              /           \                    *
    00066  *           /                 \                 *
    00067  *        /                       \              *
    00068  *   X <                             > Y         *
    00069  */
    00070
    00071 struct StringSpriteToDraw {
    00072   uint16 string;
    00073   uint16 color;
    00074   StringSpriteToDraw *next;
    00075   int32 x;
    00076   int32 y;
    00077   uint64 params[2];
    00078   uint16 width;
    00079 };
    00080
    00081 struct TileSpriteToDraw {
    00082   SpriteID image;
    00083   SpriteID pal;
    00084   const SubSprite *sub;
    00085   TileSpriteToDraw *next;
    00086   int32 x;
    00087   int32 y;
    00088   byte z;
    00089 };
    00090
    00091 struct ChildScreenSpriteToDraw {
    00092   SpriteID image;
    00093   SpriteID pal;
    00094   const SubSprite *sub;
    00095   int32 x;
    00096   int32 y;
    00097   ChildScreenSpriteToDraw *next;
    00098 };
    00099
    00100 struct ParentSpriteToDraw {
    00101   SpriteID image;
    00102   SpriteID pal;
    00103   const SubSprite *sub;
    00104
    00105   int32 x;
    00106   int32 y;
    00107
    00108   int32 left;
    00109   int32 top;
    00110
    00111   int32 xmin;
    00112   int32 xmax;
    00113   int32 ymin;
    00114   int32 ymax;
    00115   int zmin;
    00116   int zmax;
    00117
    00118   ChildScreenSpriteToDraw *child;
    00119   bool comparison_done;
    00120 };
    00121
    00122 /* Quick hack to know how much memory to reserve when allocating from the spritelist
    00123  * to prevent a buffer overflow. */
    00124 #define LARGEST_SPRITELIST_STRUCT ParentSpriteToDraw
    00125 assert_compile(sizeof(LARGEST_SPRITELIST_STRUCT) >= sizeof(StringSpriteToDraw));
    00126 assert_compile(sizeof(LARGEST_SPRITELIST_STRUCT) >= sizeof(TileSpriteToDraw));
    00127 assert_compile(sizeof(LARGEST_SPRITELIST_STRUCT) >= sizeof(ChildScreenSpriteToDraw));
    00128 assert_compile(sizeof(LARGEST_SPRITELIST_STRUCT) >= sizeof(ParentSpriteToDraw));
    00129
    00130 /* Enumeration of multi-part foundations */
    00131 enum FoundationPart {
    00132   FOUNDATION_PART_NONE     = 0xFF,
    00133   FOUNDATION_PART_NORMAL   = 0,
    00134   FOUNDATION_PART_HALFTILE = 1,
    00135   FOUNDATION_PART_END
    00136 };
    00137
    00138 struct ViewportDrawer {
    00139   DrawPixelInfo dpi;
    00140
    00141   byte *spritelist_mem;
    00142   const byte *eof_spritelist_mem;
    00143
    00144   StringSpriteToDraw **last_string, *first_string;
    00145   TileSpriteToDraw **last_tile, *first_tile;
    00146
    00147   ChildScreenSpriteToDraw **last_child;
    00148
    00149   ParentSpriteToDraw **parent_list;
    00150   ParentSpriteToDraw * const *eof_parent_list;
    00151
    00152   byte combine_sprites;
    00153
    00154   ParentSpriteToDraw *foundation[FOUNDATION_PART_END];
    00155   FoundationPart foundation_part;
    00156   ChildScreenSpriteToDraw **last_foundation_child[FOUNDATION_PART_END];
    00157   Point foundation_offset[FOUNDATION_PART_END];
    00158 };
    00159
    00160 static ViewportDrawer *_cur_vd;
    00161
    00162 TileHighlightData _thd;
    00163 static TileInfo *_cur_ti;
    00164
    00165 extern void SmallMapCenterOnCurrentPos(Window *w);
    00166
    00167 static Point MapXYZToViewport(const ViewPort *vp, uint x, uint y, uint z)
    00168 {
    00169   Point p = RemapCoords(x, y, z);
    00170   p.x -= vp->virtual_width / 2;
    00171   p.y -= vp->virtual_height / 2;
    00172   return p;
    00173 }
    00174
    00175 void InitViewports()
    00176 {
    00177   memset(_viewports, 0, sizeof(_viewports));
    00178   _active_viewports = 0;
    00179 }
    00180
    00181 void DeleteWindowViewport(Window *w)
    00182 {
    00183   ClrBit(_active_viewports, w->viewport - _viewports);
    00184   w->viewport->width = 0;
    00185   w->viewport = NULL;
    00186 }
    00187
    00188 void AssignWindowViewport(Window *w, int x, int y,
    00189   int width, int height, uint32 follow_flags, ZoomLevel zoom)
    00190 {
    00191   ViewPort *vp;
    00192   Point pt;
    00193   uint32 bit;
    00194
    00195   for (vp = _viewports, bit = 0; ; vp++, bit++) {
    00196     assert(vp != endof(_viewports));
    00197     if (vp->width == 0) break;
    00198   }
    00199   SetBit(_active_viewports, bit);
    00200
    00201   vp->left = x + w->left;
    00202   vp->top = y + w->top;
    00203   vp->width = width;
    00204   vp->height = height;
    00205
    00206   vp->zoom = zoom;
    00207
    00208   vp->virtual_width = ScaleByZoom(width, zoom);
    00209   vp->virtual_height = ScaleByZoom(height, zoom);
    00210
    00211   if (follow_flags & 0x80000000) {
    00212     const Vehicle *veh;
    00213
    00214     WP(w, vp_d).follow_vehicle = (VehicleID)(follow_flags & 0xFFFF);
    00215     veh = GetVehicle(WP(w, vp_d).follow_vehicle);
    00216     pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
    00217   } else {
    00218     uint x = TileX(follow_flags) * TILE_SIZE;
    00219     uint y = TileY(follow_flags) * TILE_SIZE;
    00220
    00221     WP(w, vp_d).follow_vehicle = INVALID_VEHICLE;
    00222     pt = MapXYZToViewport(vp, x, y, GetSlopeZ(x, y));
    00223   }
    00224
    00225   WP(w, vp_d).scrollpos_x = pt.x;
    00226   WP(w, vp_d).scrollpos_y = pt.y;
    00227   WP(w, vp_d).dest_scrollpos_x = pt.x;
    00228   WP(w, vp_d).dest_scrollpos_y = pt.y;
    00229
    00230   w->viewport = vp;
    00231   vp->virtual_left = 0;//pt.x;
    00232   vp->virtual_top = 0;//pt.y;
    00233 }
    00234
    00235 static Point _vp_move_offs;
    00236
    00237 static void DoSetViewportPosition(Window* const *wz, int left, int top, int width, int height)
    00238 {
    00239
    00240   for (; wz != _last_z_window; wz++) {
    00241     const Window *w = *wz;
    00242
    00243     if (left + width > w->left &&
    00244         w->left + w->width > left &&
    00245         top + height > w->top &&
    00246         w->top + w->height > top) {
    00247
    00248       if (left < w->left) {
    00249         DoSetViewportPosition(wz, left, top, w->left - left, height);
    00250         DoSetViewportPosition(wz, left + (w->left - left), top, width - (w->left - left), height);
    00251         return;
    00252       }
    00253
    00254       if (left + width > w->left + w->width) {
    00255         DoSetViewportPosition(wz, left, top, (w->left + w->width - left), height);
    00256         DoSetViewportPosition(wz, left + (w->left + w->width - left), top, width - (w->left + w->width - left) , height);
    00257         return;
    00258       }
    00259
    00260       if (top < w->top) {
    00261         DoSetViewportPosition(wz, left, top, width, (w->top - top));
    00262         DoSetViewportPosition(wz, left, top + (w->top - top), width, height - (w->top - top));
    00263         return;
    00264       }
    00265
    00266       if (top + height > w->top + w->height) {
    00267         DoSetViewportPosition(wz, left, top, width, (w->top + w->height - top));
    00268         DoSetViewportPosition(wz, left, top + (w->top + w->height - top), width , height - (w->top + w->height - top));
    00269         return;
    00270       }
    00271
    00272       return;
    00273     }
    00274   }
    00275
    00276   {
    00277     int xo = _vp_move_offs.x;
    00278     int yo = _vp_move_offs.y;
    00279
    00280     if (abs(xo) >= width || abs(yo) >= height) {
    00281       /* fully_outside */
    00282       RedrawScreenRect(left, top, left + width, top + height);
    00283       return;
    00284     }
    00285
    00286     GfxScroll(left, top, width, height, xo, yo);
    00287
    00288     if (xo > 0) {
    00289       RedrawScreenRect(left, top, xo + left, top + height);
    00290       left += xo;
    00291       width -= xo;
    00292     } else if (xo < 0) {
    00293       RedrawScreenRect(left+width+xo, top, left+width, top + height);
    00294       width += xo;
    00295     }
    00296
    00297     if (yo > 0) {
    00298       RedrawScreenRect(left, top, width+left, top + yo);
    00299     } else if (yo < 0) {
    00300       RedrawScreenRect(left, top + height + yo, width+left, top + height);
    00301     }
    00302   }
    00303 }
    00304
    00305 static void SetViewportPosition(Window *w, int x, int y)
    00306 {
    00307   ViewPort *vp = w->viewport;
    00308   int old_left = vp->virtual_left;
    00309   int old_top = vp->virtual_top;
    00310   int i;
    00311   int left, top, width, height;
    00312
    00313   vp->virtual_left = x;
    00314   vp->virtual_top = y;
    00315
    00316   /* viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
    00317    * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
    00318    */
    00319   old_left = UnScaleByZoomLower(old_left, vp->zoom);
    00320   old_top = UnScaleByZoomLower(old_top, vp->zoom);
    00321   x = UnScaleByZoomLower(x, vp->zoom);
    00322   y = UnScaleByZoomLower(y, vp->zoom);
    00323
    00324   old_left -= x;
    00325   old_top -= y;
    00326
    00327   if (old_top == 0 && old_left == 0) return;
    00328
    00329   _vp_move_offs.x = old_left;
    00330   _vp_move_offs.y = old_top;
    00331
    00332   left = vp->left;
    00333   top = vp->top;
    00334   width = vp->width;
    00335   height = vp->height;
    00336
    00337   if (left < 0) {
    00338     width += left;
    00339     left = 0;
    00340   }
    00341
    00342   i = left + width - _screen.width;
    00343   if (i >= 0) width -= i;
    00344
    00345   if (width > 0) {
    00346     if (top < 0) {
    00347       height += top;
    00348       top = 0;
    00349     }
    00350
    00351     i = top + height - _screen.height;
    00352     if (i >= 0) height -= i;
    00353
    00354     if (height > 0) DoSetViewportPosition(FindWindowZPosition(w) + 1, left, top, width, height);
    00355   }
    00356 }
    00357
    00358
    00359 ViewPort *IsPtInWindowViewport(const Window *w, int x, int y)
    00360 {
    00361   ViewPort *vp = w->viewport;
    00362
    00363   if (vp != NULL &&
    00364       IsInsideMM(x, vp->left, vp->left + vp->width) &&
    00365       IsInsideMM(y, vp->top, vp->top + vp->height))
    00366     return vp;
    00367
    00368   return NULL;
    00369 }
    00370
    00371 static Point TranslateXYToTileCoord(const ViewPort *vp, int x, int y)
    00372 {
    00373   Point pt;
    00374   int a,b;
    00375   uint z;
    00376
    00377   if ( (uint)(x -= vp->left) >= (uint)vp->width ||
    00378         (uint)(y -= vp->top) >= (uint)vp->height) {
    00379         Point pt = {-1, -1};
    00380         return pt;
    00381   }
    00382
    00383   x = (ScaleByZoom(x, vp->zoom) + vp->virtual_left) >> 2;
    00384   y = (ScaleByZoom(y, vp->zoom) + vp->virtual_top) >> 1;
    00385
    00386   a = y-x;
    00387   b = y+x;
    00388
    00389   /* we need to move variables in to the valid range, as the
    00390    * GetTileZoomCenterWindow() function can call here with invalid x and/or y,
    00391    * when the user tries to zoom out along the sides of the map */
    00392   a = Clamp(a, 0, (int)(MapMaxX() * TILE_SIZE) - 1);
    00393   b = Clamp(b, 0, (int)(MapMaxY() * TILE_SIZE) - 1);
    00394
    00395   /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
    00396    * Now find the Z-world coordinate by fix point iteration.
    00397    * This is a bit tricky because the tile height is non-continuous at foundations.
    00398    * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
    00399    * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
    00400    * So give it a z-malus of 4 in the first iterations.
    00401    */
    00402   z = 0;
    00403   for (int i = 0; i < 5; i++) z = GetSlopeZ(a + max(z, 4u) - 4, b + max(z, 4u) - 4) / 2;
    00404   for (uint malus = 3; malus > 0; malus--) z = GetSlopeZ(a + max(z, malus) - malus, b + max(z, malus) - malus) / 2;
    00405   for (int i = 0; i < 5; i++) z = GetSlopeZ(a + z, b + z) / 2;
    00406
    00407   pt.x = a + z;
    00408   pt.y = b + z;
    00409
    00410   return pt;
    00411 }
    00412
    00413 /* When used for zooming, check area below current coordinates (x,y)
    00414  * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
    00415  * when you just want the tile, make x = zoom_x and y = zoom_y */
    00416 static Point GetTileFromScreenXY(int x, int y, int zoom_x, int zoom_y)
    00417 {
    00418   Window *w;
    00419   ViewPort *vp;
    00420   Point pt;
    00421
    00422   if ( (w = FindWindowFromPt(x, y)) != NULL &&
    00423        (vp = IsPtInWindowViewport(w, x, y)) != NULL)
    00424         return TranslateXYToTileCoord(vp, zoom_x, zoom_y);
    00425
    00426   pt.y = pt.x = -1;
    00427   return pt;
    00428 }
    00429
    00430 Point GetTileBelowCursor()
    00431 {
    00432   return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, _cursor.pos.x, _cursor.pos.y);
    00433 }
    00434
    00435
    00436 Point GetTileZoomCenterWindow(bool in, Window * w)
    00437 {
    00438   int x, y;
    00439   ViewPort * vp;
    00440
    00441   vp = w->viewport;
    00442
    00443   if (in) {
    00444     x = ((_cursor.pos.x - vp->left) >> 1) + (vp->width >> 2);
    00445     y = ((_cursor.pos.y - vp->top) >> 1) + (vp->height >> 2);
    00446   } else {
    00447     x = vp->width - (_cursor.pos.x - vp->left);
    00448     y = vp->height - (_cursor.pos.y - vp->top);
    00449   }
    00450   /* Get the tile below the cursor and center on the zoomed-out center */
    00451   return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, x + vp->left, y + vp->top);
    00452 }
    00453
    00460 void HandleZoomMessage(Window *w, const ViewPort *vp, byte widget_zoom_in, byte widget_zoom_out)
    00461 {
    00462   w->SetWidgetDisabledState(widget_zoom_in, vp->zoom == ZOOM_LVL_MIN);
    00463   w->InvalidateWidget(widget_zoom_in);
    00464
    00465   w->SetWidgetDisabledState(widget_zoom_out, vp->zoom == ZOOM_LVL_MAX);
    00466   w->InvalidateWidget(widget_zoom_out);
    00467 }
    00468
    00480 void DrawGroundSpriteAt(SpriteID image, SpriteID pal, int32 x, int32 y, byte z, const SubSprite *sub)
    00481 {
    00482   ViewportDrawer *vd = _cur_vd;
    00483   TileSpriteToDraw *ts;
    00484
    00485   assert((image & SPRITE_MASK) < MAX_SPRITES);
    00486
    00487   if (vd->spritelist_mem >= vd->eof_spritelist_mem) {
    00488     DEBUG(sprite, 0, "Out of sprite memory");
    00489     return;
    00490   }
    00491   ts = (TileSpriteToDraw*)vd->spritelist_mem;
    00492
    00493   vd->spritelist_mem += sizeof(TileSpriteToDraw);
    00494
    00495   ts->image = image;
    00496   ts->pal = pal;
    00497   ts->sub = sub;
    00498   ts->next = NULL;
    00499   ts->x = x;
    00500   ts->y = y;
    00501   ts->z = z;
    00502   *vd->last_tile = ts;
    00503   vd->last_tile = &ts->next;
    00504 }
    00505
    00518 static void AddChildSpriteToFoundation(SpriteID image, SpriteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
    00519 {
    00520   ViewportDrawer *vd = _cur_vd;
    00521   assert(IsInsideMM(foundation_part, 0, FOUNDATION_PART_END));
    00522   assert(vd->foundation[foundation_part] != NULL);
    00523   Point offs = vd->foundation_offset[foundation_part];
    00524
    00525   /* Change the active ChildSprite list to the one of the foundation */
    00526   ChildScreenSpriteToDraw **old_child = vd->last_child;
    00527   vd->last_child = vd->last_foundation_child[foundation_part];
    00528
    00529   AddChildSpriteScreen(image, pal, offs.x + extra_offs_x, offs.y + extra_offs_y, false, sub);
    00530
    00531   /* Switch back to last ChildSprite list */
    00532   vd->last_child = old_child;
    00533 }
    00534
    00543 void DrawGroundSprite(SpriteID image, SpriteID pal, const SubSprite *sub)
    00544 {
    00545   ViewportDrawer *vd = _cur_vd;
    00546   /* Switch to first foundation part, if no foundation was drawn */
    00547   if (vd->foundation_part == FOUNDATION_PART_NONE) vd->foundation_part = FOUNDATION_PART_NORMAL;
    00548
    00549   if (vd->foundation[vd->foundation_part] != NULL) {
    00550     AddChildSpriteToFoundation(image, pal, sub, vd->foundation_part, 0, 0);
    00551   } else {
    00552     DrawGroundSpriteAt(image, pal, _cur_ti->x, _cur_ti->y, _cur_ti->z, sub);
    00553   }
    00554 }
    00555
    00556
    00564 void OffsetGroundSprite(int x, int y)
    00565 {
    00566   ViewportDrawer *vd = _cur_vd;
    00567   /* Switch to next foundation part */
    00568   switch (vd->foundation_part) {
    00569     case FOUNDATION_PART_NONE:
    00570       vd->foundation_part = FOUNDATION_PART_NORMAL;
    00571       break;
    00572     case FOUNDATION_PART_NORMAL:
    00573       vd->foundation_part = FOUNDATION_PART_HALFTILE;
    00574       break;
    00575     default: NOT_REACHED();
    00576   }
    00577
    00578   /* vd->last_child == NULL if foundation sprite was clipped by the viewport bounds */
    00579   if (vd->last_child != NULL) vd->foundation[vd->foundation_part] = vd->parent_list[-1];
    00580
    00581   vd->foundation_offset[vd->foundation_part].x = x;
    00582   vd->foundation_offset[vd->foundation_part].y = y;
    00583   vd->last_foundation_child[vd->foundation_part] = vd->last_child;
    00584 }
    00585
    00597 static void AddCombinedSprite(SpriteID image, SpriteID pal, int x, int y, byte z, const SubSprite *sub)
    00598 {
    00599   const ViewportDrawer *vd = _cur_vd;
    00600   Point pt = RemapCoords(x, y, z);
    00601   const Sprite* spr = GetSprite(image & SPRITE_MASK);
    00602
    00603   if (pt.x + spr->x_offs >= vd->dpi.left + vd->dpi.width ||
    00604       pt.x + spr->x_offs + spr->width <= vd->dpi.left ||
    00605       pt.y + spr->y_offs >= vd->dpi.top + vd->dpi.height ||
    00606       pt.y + spr->y_offs + spr->height <= vd->dpi.top)
    00607     return;
    00608
    00609   AddChildSpriteScreen(image, pal, pt.x - vd->parent_list[-1]->left, pt.y - vd->parent_list[-1]->top, false, sub);
    00610 }
    00611
    00636 void AddSortableSpriteToDraw(SpriteID image, SpriteID pal, int x, int y, int w, int h, int dz, int z, bool transparent, int bb_offset_x, int bb_offset_y, int bb_offset_z, const SubSprite *sub)
    00637 {
    00638   ViewportDrawer *vd = _cur_vd;
    00639   ParentSpriteToDraw *ps;
    00640   Point pt;
    00641   int32 left, right, top, bottom;
    00642
    00643   assert((image & SPRITE_MASK) < MAX_SPRITES);
    00644
    00645   /* make the sprites transparent with the right palette */
    00646   if (transparent) {
    00647     SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
    00648     pal = PALETTE_TO_TRANSPARENT;
    00649   }
    00650
    00651   if (vd->combine_sprites == 2) {
    00652     AddCombinedSprite(image, pal, x, y, z, sub);
    00653     return;
    00654   }
    00655
    00656   vd->last_child = NULL;
    00657
    00658   if (vd->spritelist_mem >= vd->eof_spritelist_mem) {
    00659     DEBUG(sprite, 0, "Out of sprite memory");
    00660     return;
    00661   }
    00662   ps = (ParentSpriteToDraw*)vd->spritelist_mem;
    00663
    00664   if (vd->parent_list >= vd->eof_parent_list) {
    00665     /* This can happen rarely, mostly when you zoom out completely
    00666      *  and have a lot of stuff that moves (and is added to the
    00667      *  sort-list, this function). To solve it, increase
    00668      *  parent_list somewhere below to a higher number.
    00669      * This can not really hurt you, it just gives some black
    00670      *  spots on the screen ;) */
    00671     DEBUG(sprite, 0, "Out of sprite memory (parent_list)");
    00672     return;
    00673   }
    00674
    00675   pt = RemapCoords(x, y, z);
    00676   ps->x = pt.x;
    00677   ps->y = pt.y;
    00678
    00679   /* Compute screen extents of sprite */
    00680   if (image == SPR_EMPTY_BOUNDING_BOX) {
    00681     left = ps->left = RemapCoords(x + w          , y + bb_offset_y, z + bb_offset_z).x;
    00682     right           = RemapCoords(x + bb_offset_x, y + h          , z + bb_offset_z).x + 1;
    00683     top  = ps->top  = RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz         ).y;
    00684     bottom          = RemapCoords(x + w          , y + h          , z + bb_offset_z).y + 1;
    00685   } else {
    00686     const Sprite *spr = GetSprite(image & SPRITE_MASK);
    00687     left = ps->left = (pt.x += spr->x_offs);
    00688     right           = (pt.x +  spr->width );
    00689     top  = ps->top  = (pt.y += spr->y_offs);
    00690     bottom          = (pt.y +  spr->height);
    00691   }
    00692
    00693   if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
    00694     /* Compute maximal extents of sprite and it's bounding box */
    00695     left   = min(left  , RemapCoords(x + w          , y + bb_offset_y, z + bb_offset_z).x);
    00696     right  = max(right , RemapCoords(x + bb_offset_x, y + h          , z + bb_offset_z).x + 1);
    00697     top    = min(top   , RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz         ).y);
    00698     bottom = max(bottom, RemapCoords(x + w          , y + h          , z + bb_offset_z).y + 1);
    00699   }
    00700
    00701   /* Do not add the sprite to the viewport, if it is outside */
    00702   if (left   >= vd->dpi.left + vd->dpi.width ||
    00703       right  <= vd->dpi.left                 ||
    00704       top    >= vd->dpi.top + vd->dpi.height ||
    00705       bottom <= vd->dpi.top) {
    00706     return;
    00707   }
    00708
    00709   vd->spritelist_mem += sizeof(ParentSpriteToDraw);
    00710
    00711   ps->image = image;
    00712   ps->pal = pal;
    00713   ps->sub = sub;
    00714   ps->xmin = x + bb_offset_x;
    00715   ps->xmax = x + max(bb_offset_x, w) - 1;
    00716
    00717   ps->ymin = y + bb_offset_y;
    00718   ps->ymax = y + max(bb_offset_y, h) - 1;
    00719
    00720   ps->zmin = z + bb_offset_z;
    00721   ps->zmax = z + max(bb_offset_z, dz) - 1;
    00722
    00723   ps->comparison_done = false;
    00724   ps->child = NULL;
    00725   vd->last_child = &ps->child;
    00726
    00727   *vd->parent_list++ = ps;
    00728
    00729   if (vd->combine_sprites == 1) vd->combine_sprites = 2;
    00730 }
    00731
    00732 void StartSpriteCombine()
    00733 {
    00734   _cur_vd->combine_sprites = 1;
    00735 }
    00736
    00737 void EndSpriteCombine()
    00738 {
    00739   _cur_vd->combine_sprites = 0;
    00740 }
    00741
    00752 void AddChildSpriteScreen(SpriteID image, SpriteID pal, int x, int y, bool transparent, const SubSprite *sub)
    00753 {
    00754   ViewportDrawer *vd = _cur_vd;
    00755   ChildScreenSpriteToDraw *cs;
    00756
    00757   assert((image & SPRITE_MASK) < MAX_SPRITES);
    00758
    00759   /* make the sprites transparent with the right palette */
    00760   if (transparent) {
    00761     SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
    00762     pal = PALETTE_TO_TRANSPARENT;
    00763   }
    00764
    00765   if (vd->spritelist_mem >= vd->eof_spritelist_mem) {
    00766     DEBUG(sprite, 0, "Out of sprite memory");
    00767     return;
    00768   }
    00769   cs = (ChildScreenSpriteToDraw*)vd->spritelist_mem;
    00770
    00771   /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
    00772   if (vd->last_child == NULL) return;
    00773
    00774   vd->spritelist_mem += sizeof(ChildScreenSpriteToDraw);
    00775
    00776   /* Append the sprite to the active ChildSprite list.
    00777    * If the active ParentSprite is a foundation, update last_foundation_child as well. */
    00778   *vd->last_child = cs;
    00779   if (vd->last_foundation_child[0] == vd->last_child) vd->last_foundation_child[0] = &cs->next;
    00780   if (vd->last_foundation_child[1] == vd->last_child) vd->last_foundation_child[1] = &cs->next;
    00781   vd->last_child = &cs->next;
    00782
    00783   cs->image = image;
    00784   cs->pal = pal;
    00785   cs->sub = sub;
    00786   cs->x = x;
    00787   cs->y = y;
    00788   cs->next = NULL;
    00789 }
    00790
    00791 /* Returns a StringSpriteToDraw */
    00792 void *AddStringToDraw(int x, int y, StringID string, uint64 params_1, uint64 params_2)
    00793 {
    00794   ViewportDrawer *vd = _cur_vd;
    00795   StringSpriteToDraw *ss;
    00796
    00797   if (vd->spritelist_mem >= vd->eof_spritelist_mem) {
    00798     DEBUG(sprite, 0, "Out of sprite memory");
    00799     return NULL;
    00800   }
    00801   ss = (StringSpriteToDraw*)vd->spritelist_mem;
    00802
    00803   vd->spritelist_mem += sizeof(StringSpriteToDraw);
    00804
    00805   ss->string = string;
    00806   ss->next = NULL;
    00807   ss->x = x;
    00808   ss->y = y;
    00809   ss->params[0] = params_1;
    00810   ss->params[1] = params_2;
    00811   ss->width = 0;
    00812
    00813   *vd->last_string = ss;
    00814   vd->last_string = &ss->next;
    00815
    00816   return ss;
    00817 }
    00818
    00819
    00831 static void DrawSelectionSprite(SpriteID image, SpriteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part)
    00832 {
    00833   /* FIXME: This is not totally valid for some autorail highlights, that extent over the edges of the tile. */
    00834   if (_cur_vd->foundation[foundation_part] == NULL) {
    00835     /* draw on real ground */
    00836     DrawGroundSpriteAt(image, pal, ti->x, ti->y, ti->z + z_offset);
    00837   } else {
    00838     /* draw on top of foundation */
    00839     AddChildSpriteToFoundation(image, pal, NULL, foundation_part, 0, -z_offset);
    00840   }
    00841 }
    00842
    00849 static void DrawTileSelectionRect(const TileInfo *ti, SpriteID pal)
    00850 {
    00851   SpriteID sel;
    00852   if (IsHalftileSlope(ti->tileh)) {
    00853     Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
    00854     SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
    00855     DrawSelectionSprite(sel2, pal, ti, 7 + TILE_HEIGHT, FOUNDATION_PART_HALFTILE);
    00856
    00857     Corner opposite_corner = OppositeCorner(halftile_corner);
    00858     if (IsSteepSlope(ti->tileh)) {
    00859       sel = SPR_HALFTILE_SELECTION_DOWN;
    00860     } else {
    00861       sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
    00862     }
    00863     sel += opposite_corner;
    00864   } else {
    00865     sel = SPR_SELECT_TILE + _tileh_to_sprite[ti->tileh];
    00866   }
    00867   DrawSelectionSprite(sel, pal, ti, 7, FOUNDATION_PART_NORMAL);
    00868 }
    00869
    00870 static bool IsPartOfAutoLine(int px, int py)
    00871 {
    00872   px -= _thd.selstart.x;
    00873   py -= _thd.selstart.y;
    00874
    00875   if ((_thd.drawstyle & ~HT_DIR_MASK) != HT_LINE) return false;
    00876
    00877   switch (_thd.drawstyle & HT_DIR_MASK) {
    00878     case HT_DIR_X:  return py == 0; // x direction
    00879     case HT_DIR_Y:  return px == 0; // y direction
    00880     case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
    00881     case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
    00882     case HT_DIR_VL: return px == py || px == py + 16; // vertival left
    00883     case HT_DIR_VR: return px == py || px == py - 16; // vertical right
    00884     default:
    00885       NOT_REACHED();
    00886   }
    00887 }
    00888
    00889 // [direction][side]
    00890 static const HighLightStyle _autorail_type[6][2] = {
    00891   { HT_DIR_X,  HT_DIR_X },
    00892   { HT_DIR_Y,  HT_DIR_Y },
    00893   { HT_DIR_HU, HT_DIR_HL },
    00894   { HT_DIR_HL, HT_DIR_HU },
    00895   { HT_DIR_VL, HT_DIR_VR },
    00896   { HT_DIR_VR, HT_DIR_VL }
    00897 };
    00898
    00899 #include "table/autorail.h"
    00900
    00907 static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
    00908 {
    00909   SpriteID image;
    00910   SpriteID pal;
    00911   int offset;
    00912
    00913   FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
    00914   Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
    00915   if (IsHalftileSlope(ti->tileh)) {
    00916     static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
    00917     Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
    00918     if (autorail_type != _lower_rail[halftile_corner]) {
    00919       foundation_part = FOUNDATION_PART_HALFTILE;
    00920       /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
    00921       autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
    00922     }
    00923   }
    00924
    00925   offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
    00926   if (offset >= 0) {
    00927     image = SPR_AUTORAIL_BASE + offset;
    00928     pal = PAL_NONE;
    00929   } else {
    00930     image = SPR_AUTORAIL_BASE - offset;
    00931     pal = PALETTE_SEL_TILE_RED;
    00932   }
    00933
    00934   DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
    00935 }
    00936
    00941 static void DrawTileSelection(const TileInfo *ti)
    00942 {
    00943   /* Draw a red error square? */
    00944   bool is_redsq = _thd.redsq != 0 && _thd.redsq == ti->tile;
    00945   if (is_redsq) DrawTileSelectionRect(ti, PALETTE_TILE_RED_PULSATING);
    00946
    00947   /* no selection active? */
    00948   if (_thd.drawstyle == 0) return;
    00949
    00950   /* Inside the inner area? */
    00951   if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
    00952       IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
    00953     if (_thd.drawstyle & HT_RECT) {
    00954       if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
    00955     } else if (_thd.drawstyle & HT_POINT) {
    00956       /* Figure out the Z coordinate for the single dot. */
    00957       byte z = 0;
    00958       FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
    00959       if (ti->tileh & SLOPE_N) {
    00960         z += TILE_HEIGHT;
    00961         if (RemoveHalftileSlope(ti->tileh) == SLOPE_STEEP_N) z += TILE_HEIGHT;
    00962       }
    00963       if (IsHalftileSlope(ti->tileh)) {
    00964         Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
    00965         if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
    00966         if (halftile_corner != CORNER_S) {
    00967           foundation_part = FOUNDATION_PART_HALFTILE;
    00968           if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
    00969         }
    00970       }
    00971       DrawSelectionSprite(_cur_dpi->zoom <= ZOOM_LVL_DETAIL ? SPR_DOT : SPR_DOT_SMALL, PAL_NONE, ti, z, foundation_part);
    00972     } else if (_thd.drawstyle & HT_RAIL /*&& _thd.place_mode == VHM_RAIL*/) {
    00973       /* autorail highlight piece under cursor */
    00974       uint type = _thd.drawstyle & 0xF;
    00975       assert(type <= 5);
    00976       DrawAutorailSelection(ti, _autorail_type[type][0]);
    00977     } else if (IsPartOfAutoLine(ti->x, ti->y)) {
    00978       /* autorail highlighting long line */
    00979       int dir = _thd.drawstyle & ~0xF0;
    00980       uint side;
    00981
    00982       if (dir < 2) {
    00983         side = 0;
    00984       } else {
    00985         TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
    00986         side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
    00987       }
    00988
    00989       DrawAutorailSelection(ti, _autorail_type[dir][side]);
    00990     }
    00991     return;
    00992   }
    00993
    00994   /* Check if it's inside the outer area? */
    00995   if (!is_redsq && _thd.outersize.x &&
    00996       _thd.size.x < _thd.size.x + _thd.outersize.x &&
    00997       IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
    00998       IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
    00999     /* Draw a blue rect. */
    01000     DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE);
    01001     return;
    01002   }
    01003 }
    01004
    01005 static void ViewportAddLandscape()
    01006 {
    01007   ViewportDrawer *vd = _cur_vd;
    01008   int x, y, width, height;
    01009   TileInfo ti;
    01010   bool direction;
    01011
    01012   _cur_ti = &ti;
    01013
    01014   /* Transform into tile coordinates and round to closest full tile */
    01015   x = ((vd->dpi.top >> 1) - (vd->dpi.left >> 2)) & ~0xF;
    01016   y = ((vd->dpi.top >> 1) + (vd->dpi.left >> 2) - 0x10) & ~0xF;
    01017
    01018   /* determine size of area */
    01019   {
    01020     Point pt = RemapCoords(x, y, 241);
    01021     width = (vd->dpi.left + vd->dpi.width - pt.x + 95) >> 6;
    01022     height = (vd->dpi.top + vd->dpi.height - pt.y) >> 5 << 1;
    01023   }
    01024
    01025   assert(width > 0);
    01026   assert(height > 0);
    01027
    01028   direction = false;
    01029
    01030   do {
    01031     int width_cur = width;
    01032     int x_cur = x;
    01033     int y_cur = y;
    01034
    01035     do {
    01036       TileType tt;
    01037
    01038       ti.x = x_cur;
    01039       ti.y = y_cur;
    01040       if (0 <= x_cur && x_cur < (int)MapMaxX() * TILE_SIZE &&
    01041           0 <= y_cur && y_cur < (int)MapMaxY() * TILE_SIZE) {
    01042         TileIndex tile = TileVirtXY(x_cur, y_cur);
    01043
    01044         ti.tile = tile;
    01045         ti.tileh = GetTileSlope(tile, &ti.z);
    01046         tt = GetTileType(tile);
    01047       } else {
    01048         ti.tileh = SLOPE_FLAT;
    01049         ti.tile = 0;
    01050         ti.z = 0;
    01051         tt = MP_VOID;
    01052       }
    01053
    01054       y_cur += 0x10;
    01055       x_cur -= 0x10;
    01056
    01057       vd->foundation_part = FOUNDATION_PART_NONE;
    01058       vd->foundation[0] = NULL;
    01059       vd->foundation[1] = NULL;
    01060       vd->last_foundation_child[0] = NULL;
    01061       vd->last_foundation_child[1] = NULL;
    01062
    01063       _tile_type_procs[tt]->draw_tile_proc(&ti);
    01064       DrawTileSelection(&ti);
    01065     } while (--width_cur);
    01066
    01067     if ((direction ^= 1) != 0) {
    01068       y += 0x10;
    01069     } else {
    01070       x += 0x10;
    01071     }
    01072   } while (--height);
    01073 }
    01074
    01075
    01076 static void ViewportAddTownNames(DrawPixelInfo *dpi)
    01077 {
    01078   Town *t;
    01079   int left, top, right, bottom;
    01080
    01081   if (!HasBit(_display_opt, DO_SHOW_TOWN_NAMES) || _game_mode == GM_MENU)
    01082     return;
    01083
    01084   left = dpi->left;
    01085   top = dpi->top;
    01086   right = left + dpi->width;
    01087   bottom = top + dpi->height;
    01088
    01089   switch (dpi->zoom) {
    01090     case ZOOM_LVL_NORMAL:
    01091       FOR_ALL_TOWNS(t) {
    01092         if (bottom > t->sign.top &&
    01093             top    < t->sign.top + 12 &&
    01094             right  > t->sign.left &&
    01095             left   < t->sign.left + t->sign.width_1) {
    01096           AddStringToDraw(t->sign.left + 1, t->sign.top + 1,
    01097             _patches.population_in_label ? STR_TOWN_LABEL_POP : STR_TOWN_LABEL,
    01098             t->index, t->population);
    01099         }
    01100       }
    01101       break;
    01102
    01103     case ZOOM_LVL_OUT_2X:
    01104       right += 2;
    01105       bottom += 2;
    01106
    01107       FOR_ALL_TOWNS(t) {
    01108         if (bottom > t->sign.top &&
    01109             top    < t->sign.top + 24 &&
    01110             right  > t->sign.left &&
    01111             left   < t->sign.left + t->sign.width_1*2) {
    01112           AddStringToDraw(t->sign.left + 1, t->sign.top + 1,
    01113             _patches.population_in_label ? STR_TOWN_LABEL_POP : STR_TOWN_LABEL,
    01114             t->index, t->population);
    01115         }
    01116       }
    01117       break;
    01118
    01119     case ZOOM_LVL_OUT_4X:
    01120     case ZOOM_LVL_OUT_8X:
    01121       right += ScaleByZoom(1, dpi->zoom);
    01122       bottom += ScaleByZoom(1, dpi->zoom) + 1;
    01123
    01124       FOR_ALL_TOWNS(t) {
    01125         if (bottom > t->sign.top &&
    01126             top    < t->sign.top + ScaleByZoom(12, dpi->zoom) &&
    01127             right  > t->sign.left &&
    01128             left   < t->sign.left + ScaleByZoom(t->sign.width_2, dpi->zoom)) {
    01129           AddStringToDraw(t->sign.left + 5, t->sign.top + 1, STR_TOWN_LABEL_TINY_BLACK, t->index, 0);
    01130           AddStringToDraw(t->sign.left + 1, t->sign.top - 3, STR_TOWN_LABEL_TINY_WHITE, t->index, 0);
    01131         }
    01132       }
    01133       break;
    01134
    01135     default: NOT_REACHED();
    01136   }
    01137 }
    01138
    01139
    01140 static void AddStation(const Station *st, StringID str, uint16 width)
    01141 {
    01142   StringSpriteToDraw *sstd;
    01143
    01144   sstd = (StringSpriteToDraw*)AddStringToDraw(st->sign.left + 1, st->sign.top + 1, str, st->index, st->facilities);
    01145   if (sstd != NULL) {
    01146     sstd->color = (st->owner == OWNER_NONE || st->facilities == 0) ? 0xE : _player_colors[st->owner];
    01147     sstd->width = width;
    01148   }
    01149 }
    01150
    01151
    01152 static void ViewportAddStationNames(DrawPixelInfo *dpi)
    01153 {
    01154   int left, top, right, bottom;
    01155   const Station *st;
    01156
    01157   if (!HasBit(_display_opt, DO_SHOW_STATION_NAMES) || _game_mode == GM_MENU)
    01158     return;
    01159
    01160   left = dpi->left;
    01161   top = dpi->top;
    01162   right = left + dpi->width;
    01163   bottom = top + dpi->height;
    01164
    01165   switch (dpi->zoom) {
    01166     case ZOOM_LVL_NORMAL:
    01167       FOR_ALL_STATIONS(st) {
    01168         if (bottom > st->sign.top &&
    01169             top    < st->sign.top + 12 &&
    01170             right  > st->sign.left &&
    01171             left   < st->sign.left + st->sign.width_1) {
    01172           AddStation(st, STR_305C_0, st->sign.width_1);
    01173         }
    01174       }
    01175       break;
    01176
    01177     case ZOOM_LVL_OUT_2X:
    01178       right += 2;
    01179       bottom += 2;
    01180       FOR_ALL_STATIONS(st) {
    01181         if (bottom > st->sign.top &&
    01182             top    < st->sign.top + 24 &&
    01183             right  > st->sign.left &&
    01184             left   < st->sign.left + st->sign.width_1*2) {
    01185           AddStation(st, STR_305C_0, st->sign.width_1);
    01186         }
    01187       }
    01188       break;
    01189
    01190     case ZOOM_LVL_OUT_4X:
    01191     case ZOOM_LVL_OUT_8X:
    01192       right += ScaleByZoom(1, dpi->zoom);
    01193       bottom += ScaleByZoom(1, dpi->zoom) + 1;
    01194
    01195       FOR_ALL_STATIONS(st) {
    01196         if (bottom > st->sign.top &&
    01197             top    < st->sign.top + ScaleByZoom(12, dpi->zoom) &&
    01198             right  > st->sign.left &&
    01199             left   < st->sign.left + ScaleByZoom(st->sign.width_2, dpi->zoom)) {
    01200           AddStation(st, STR_STATION_SIGN_TINY, st->sign.width_2 | 0x8000);
    01201         }
    01202       }
    01203       break;
    01204
    01205     default: NOT_REACHED();
    01206   }
    01207 }
    01208
    01209
    01210 static void AddSign(const Sign *si, StringID str, uint16 width)
    01211 {
    01212   StringSpriteToDraw *sstd;
    01213
    01214   sstd = (StringSpriteToDraw*)AddStringToDraw(si->sign.left + 1, si->sign.top + 1, str, si->index, 0);
    01215   if (sstd != NULL) {
    01216     sstd->color = (si->owner == OWNER_NONE) ? 14 : _player_colors[si->owner];
    01217     sstd->width = width;
    01218   }
    01219 }
    01220
    01221
    01222 static void ViewportAddSigns(DrawPixelInfo *dpi)
    01223 {
    01224   const Sign *si;
    01225   int left, top, right, bottom;
    01226
    01227   if (!HasBit(_display_opt, DO_SHOW_SIGNS))
    01228     return;
    01229
    01230   left = dpi->left;
    01231   top = dpi->top;
    01232   right = left + dpi->width;
    01233   bottom = top + dpi->height;
    01234
    01235   switch (dpi->zoom) {
    01236     case ZOOM_LVL_NORMAL:
    01237       FOR_ALL_SIGNS(si) {
    01238         if (bottom > si->sign.top &&
    01239             top    < si->sign.top + 12 &&
    01240             right  > si->sign.left &&
    01241             left   < si->sign.left + si->sign.width_1) {
    01242           AddSign(si, STR_2806, si->sign.width_1);
    01243         }
    01244       }
    01245       break;
    01246
    01247     case ZOOM_LVL_OUT_2X:
    01248       right += 2;
    01249       bottom += 2;
    01250       FOR_ALL_SIGNS(si) {
    01251         if (bottom > si->sign.top &&
    01252             top    < si->sign.top + 24 &&
    01253             right  > si->sign.left &&
    01254             left   < si->sign.left + si->sign.width_1 * 2) {
    01255           AddSign(si, STR_2806, si->sign.width_1);
    01256         }
    01257       }
    01258       break;
    01259
    01260     case ZOOM_LVL_OUT_4X:
    01261     case ZOOM_LVL_OUT_8X:
    01262       right += ScaleByZoom(1, dpi->zoom);
    01263       bottom += ScaleByZoom(1, dpi->zoom) + 1;
    01264
    01265       FOR_ALL_SIGNS(si) {
    01266         if (bottom > si->sign.top &&
    01267             top    < si->sign.top + ScaleByZoom(12, dpi->zoom) &&
    01268             right  > si->sign.left &&
    01269             left   < si->sign.left + ScaleByZoom(si->sign.width_2, dpi->zoom)) {
    01270           AddSign(si, IsTransparencySet(TO_SIGNS) ? STR_2002_WHITE : STR_2002, si->sign.width_2 | 0x8000);
    01271         }
    01272       }
    01273       break;
    01274
    01275     default: NOT_REACHED();
    01276   }
    01277 }
    01278
    01279
    01280 static void AddWaypoint(const Waypoint *wp, StringID str, uint16 width)
    01281 {
    01282   StringSpriteToDraw *sstd;
    01283
    01284   sstd = (StringSpriteToDraw*)AddStringToDraw(wp->sign.left + 1, wp->sign.top + 1, str, wp->index, 0);
    01285   if (sstd != NULL) {
    01286     sstd->color = (wp->deleted ? 0xE : 11);
    01287     sstd->width = width;
    01288   }
    01289 }
    01290
    01291
    01292 static void ViewportAddWaypoints(DrawPixelInfo *dpi)
    01293 {
    01294   const Waypoint *wp;
    01295   int left, top, right, bottom;
    01296
    01297   if (!HasBit(_display_opt, DO_WAYPOINTS))
    01298     return;
    01299
    01300   left = dpi->left;
    01301   top = dpi->top;
    01302   right = left + dpi->width;
    01303   bottom = top + dpi->height;
    01304
    01305   switch (dpi->zoom) {
    01306     case ZOOM_LVL_NORMAL:
    01307       FOR_ALL_WAYPOINTS(wp) {
    01308         if (bottom > wp->sign.top &&
    01309             top    < wp->sign.top + 12 &&
    01310             right  > wp->sign.left &&
    01311             left   < wp->sign.left + wp->sign.width_1) {
    01312           AddWaypoint(wp, STR_WAYPOINT_VIEWPORT, wp->sign.width_1);
    01313         }
    01314       }
    01315       break;
    01316
    01317     case ZOOM_LVL_OUT_2X:
    01318       right += 2;
    01319       bottom += 2;
    01320       FOR_ALL_WAYPOINTS(wp) {
    01321         if (bottom > wp->sign.top &&
    01322             top    < wp->sign.top + 24 &&
    01323             right  > wp->sign.left &&
    01324             left   < wp->sign.left + wp->sign.width_1*2) {
    01325           AddWaypoint(wp, STR_WAYPOINT_VIEWPORT, wp->sign.width_1);
    01326         }
    01327       }
    01328       break;
    01329
    01330     case ZOOM_LVL_OUT_4X:
    01331     case ZOOM_LVL_OUT_8X:
    01332       right += ScaleByZoom(1, dpi->zoom);
    01333       bottom += ScaleByZoom(1, dpi->zoom) + 1;
    01334
    01335       FOR_ALL_WAYPOINTS(wp) {
    01336         if (bottom > wp->sign.top &&
    01337             top    < wp->sign.top + ScaleByZoom(12, dpi->zoom) &&
    01338             right  > wp->sign.left &&
    01339             left   < wp->sign.left + ScaleByZoom(wp->sign.width_2, dpi->zoom)) {
    01340           AddWaypoint(wp, STR_WAYPOINT_VIEWPORT_TINY, wp->sign.width_2 | 0x8000);
    01341         }
    01342       }
    01343       break;
    01344
    01345     default: NOT_REACHED();
    01346   }
    01347 }
    01348
    01349 void UpdateViewportSignPos(ViewportSign *sign, int left, int top, StringID str)
    01350 {
    01351   char buffer[256];
    01352   uint w;
    01353
    01354   sign->top = top;
    01355
    01356   GetString(buffer, str, lastof(buffer));
    01357   w = GetStringBoundingBox(buffer).width + 3;
    01358   sign->width_1 = w;
    01359   sign->left = left - w / 2;
    01360
    01361   /* zoomed out version */
    01362   _cur_fontsize = FS_SMALL;
    01363   w = GetStringBoundingBox(buffer).width + 3;
    01364   _cur_fontsize = FS_NORMAL;
    01365   sign->width_2 = w;
    01366 }
    01367
    01368
    01369 static void ViewportDrawTileSprites(TileSpriteToDraw *ts)
    01370 {
    01371   do {
    01372     Point pt = RemapCoords(ts->x, ts->y, ts->z);
    01373     DrawSprite(ts->image, ts->pal, pt.x, pt.y, ts->sub);
    01374     ts = ts->next;
    01375   } while (ts != NULL);
    01376 }
    01377
    01378 static void ViewportSortParentSprites(ParentSpriteToDraw *psd[])
    01379 {
    01380   while (*psd != NULL) {
    01381     ParentSpriteToDraw* ps = *psd;
    01382
    01383     if (!ps->comparison_done) {
    01384       ParentSpriteToDraw** psd2 = psd;
    01385
    01386       ps->comparison_done = true;
    01387
    01388       while (*++psd2 != NULL) {
    01389         ParentSpriteToDraw* ps2 = *psd2;
    01390         ParentSpriteToDraw** psd3;
    01391
    01392         if (ps2->comparison_done) continue;
    01393
    01394         /* Decide which comparator to use, based on whether the bounding
    01395          * boxes overlap
    01396          */
    01397         if (ps->xmax >= ps2->xmin && ps->xmin <= ps2->xmax && // overlap in X?
    01398             ps->ymax >= ps2->ymin && ps->ymin <= ps2->ymax && // overlap in Y?
    01399             ps->zmax >= ps2->zmin && ps->zmin <= ps2->zmax) { // overlap in Z?
    01400           /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
    01401            * the screen and with higher Z elevation, are drawn in front.
    01402            * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
    01403            * i.e. X=(left+right)/2, etc.
    01404            * However, since we only care about order, don't actually divide / 2
    01405            */
    01406           if (ps->xmin + ps->xmax + ps->ymin + ps->ymax + ps->zmin + ps->zmax <=
    01407               ps2->xmin + ps2->xmax + ps2->ymin + ps2->ymax + ps2->zmin + ps2->zmax) {
    01408             continue;
    01409           }
    01410         } else {
    01411           /* We only change the order, if it is definite.
    01412            * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
    01413            * That is: If one partial order says ps behind ps2, do not change the order.
    01414            */
    01415           if (ps->xmax < ps2->xmin ||
    01416               ps->ymax < ps2->ymin ||
    01417               ps->zmax < ps2->zmin) {
    01418             continue;
    01419           }
    01420         }
    01421
    01422         /* Swap the two sprites ps and ps2 using bubble-sort algorithm. */
    01423         psd3 = psd;
    01424         do {
    01425           ParentSpriteToDraw* temp = *psd3;
    01426           *psd3 = ps2;
    01427           ps2 = temp;
    01428
    01429           psd3++;
    01430         } while (psd3 <= psd2);
    01431       }
    01432     } else {
    01433       psd++;
    01434     }
    01435   }
    01436 }
    01437
    01438 static void ViewportDrawParentSprites(ParentSpriteToDraw *psd[])
    01439 {
    01440   for (; *psd != NULL; psd++) {
    01441     const ParentSpriteToDraw* ps = *psd;
    01442     const ChildScreenSpriteToDraw* cs;
    01443
    01444     if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSprite(ps->image, ps->pal, ps->x, ps->y, ps->sub);
    01445
    01446     for (cs = ps->child; cs != NULL; cs = cs->next) {
    01447       DrawSprite(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
    01448     }
    01449   }
    01450 }
    01451
    01456 static void ViewportDrawBoundingBoxes(ParentSpriteToDraw *psd[])
    01457 {
    01458   for (; *psd != NULL; psd++) {
    01459     const ParentSpriteToDraw* ps = *psd;
    01460     Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
    01461     Point pt2 = RemapCoords(ps->xmin    , ps->ymax + 1, ps->zmax + 1); // top left corner
    01462     Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin    , ps->zmax + 1); // top right corner
    01463     Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin    ); // bottom front corner
    01464
    01465     DrawBox(        pt1.x,         pt1.y,
    01466             pt2.x - pt1.x, pt2.y - pt1.y,
    01467             pt3.x - pt1.x, pt3.y - pt1.y,
    01468             pt4.x - pt1.x, pt4.y - pt1.y);
    01469   }
    01470 }
    01471
    01472 static void ViewportDrawStrings(DrawPixelInfo *dpi, const StringSpriteToDraw *ss)
    01473 {
    01474   DrawPixelInfo dp;
    01475   ZoomLevel zoom;
    01476
    01477   _cur_dpi = &dp;
    01478   dp = *dpi;
    01479
    01480   zoom = dp.zoom;
    01481   dp.zoom = ZOOM_LVL_NORMAL;
    01482
    01483   dp.left   = UnScaleByZoom(dp.left,   zoom);
    01484   dp.top    = UnScaleByZoom(dp.top,    zoom);
    01485   dp.width  = UnScaleByZoom(dp.width,  zoom);
    01486   dp.height = UnScaleByZoom(dp.height, zoom);
    01487
    01488   do {
    01489     uint16 colour;
    01490
    01491     if (ss->width != 0) {
    01492       int x = UnScaleByZoom(ss->x, zoom) - 1;
    01493       int y = UnScaleByZoom(ss->y, zoom) - 1;
    01494       int bottom = y + 11;
    01495       int w = ss->width;
    01496
    01497       if (w & 0x8000) {
    01498         w &= ~0x8000;
    01499         y--;
    01500         bottom -= 6;
    01501         w -= 3;
    01502       }
    01503
    01504     /* Draw the rectangle if 'tranparent station signs' is off,
    01505      * or if we are drawing a general text sign (STR_2806) */
    01506       if (!IsTransparencySet(TO_SIGNS) || ss->string == STR_2806) {
    01507         DrawFrameRect(
    01508           x, y, x + w, bottom, ss->color,
    01509           IsTransparencySet(TO_SIGNS) ? FR_TRANSPARENT : FR_NONE
    01510         );
    01511       }
    01512     }
    01513
    01514     SetDParam(0, ss->params[0]);
    01515     SetDParam(1, ss->params[1]);
    01516     /* if we didn't draw a rectangle, or if transparant building is on,
    01517      * draw the text in the color the rectangle would have */
    01518     if (IsTransparencySet(TO_SIGNS) && ss->string != STR_2806 && ss->width != 0) {
    01519       /* Real colors need the IS_PALETTE_COLOR flag
    01520        * otherwise colors from _string_colormap are assumed. */
    01521       colour = _colour_gradient[ss->color][6] | IS_PALETTE_COLOR;
    01522     } else {
    01523       colour = TC_BLACK;
    01524     }
    01525     DrawString(
    01526       UnScaleByZoom(ss->x, zoom), UnScaleByZoom(ss->y, zoom) - (ss->width & 0x8000 ? 2 : 0),
    01527       ss->string, colour
    01528     );
    01529
    01530     ss = ss->next;
    01531   } while (ss != NULL);
    01532 }
    01533
    01534 void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom)
    01535 {
    01536   ViewportDrawer vd;
    01537   int mask;
    01538   int x;
    01539   int y;
    01540   DrawPixelInfo *old_dpi;
    01541
    01542   byte mem[VIEWPORT_DRAW_MEM];
    01543   ParentSpriteToDraw *parent_list[6144];
    01544
    01545   _cur_vd = &vd;
    01546
    01547   old_dpi = _cur_dpi;
    01548   _cur_dpi = &vd.dpi;
    01549
    01550   vd.dpi.zoom = vp->zoom;
    01551   mask = ScaleByZoom(-1, vp->zoom);
    01552
    01553   vd.combine_sprites = 0;
    01554
    01555   vd.dpi.width = (right - left) & mask;
    01556   vd.dpi.height = (bottom - top) & mask;
    01557   vd.dpi.left = left & mask;
    01558   vd.dpi.top = top & mask;
    01559   vd.dpi.pitch = old_dpi->pitch;
    01560
    01561   x = UnScaleByZoom(vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left;
    01562   y = UnScaleByZoom(vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top;
    01563
    01564   vd.dpi.dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top);
    01565
    01566   vd.parent_list = parent_list;
    01567   vd.eof_parent_list = endof(parent_list);
    01568   vd.spritelist_mem = mem;
    01569   vd.eof_spritelist_mem = endof(mem) - sizeof(LARGEST_SPRITELIST_STRUCT);
    01570   vd.last_string = &vd.first_string;
    01571   vd.first_string = NULL;
    01572   vd.last_tile = &vd.first_tile;
    01573   vd.first_tile = NULL;
    01574
    01575   ViewportAddLandscape();
    01576   ViewportAddVehicles(&vd.dpi);
    01577   DrawTextEffects(&vd.dpi);
    01578
    01579   ViewportAddTownNames(&vd.dpi);
    01580   ViewportAddStationNames(&vd.dpi);
    01581   ViewportAddSigns(&vd.dpi);
    01582   ViewportAddWaypoints(&vd.dpi);
    01583
    01584   /* This assert should never happen (because the length of the parent_list
    01585    *  is checked) */
    01586   assert(vd.parent_list <= endof(parent_list));
    01587
    01588   if (vd.first_tile != NULL) ViewportDrawTileSprites(vd.first_tile);
    01589
    01590   /* null terminate parent sprite list */
    01591   *vd.parent_list = NULL;
    01592
    01593   ViewportSortParentSprites(parent_list);
    01594   ViewportDrawParentSprites(parent_list);
    01595
    01596   if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(parent_list);
    01597
    01598   if (vd.first_string != NULL) ViewportDrawStrings(&vd.dpi, vd.first_string);
    01599
    01600   _cur_dpi = old_dpi;
    01601 }
    01602
    01605 static void ViewportDrawChk(const ViewPort *vp, int left, int top, int right, int bottom)
    01606 {
    01607   if (ScaleByZoom(bottom - top, vp->zoom) * ScaleByZoom(right - left, vp->zoom) > 180000) {
    01608     if ((bottom - top) > (right - left)) {
    01609       int t = (top + bottom) >> 1;
    01610       ViewportDrawChk(vp, left, top, right, t);
    01611       ViewportDrawChk(vp, left, t, right, bottom);
    01612     } else {
    01613       int t = (left + right) >> 1;
    01614       ViewportDrawChk(vp, left, top, t, bottom);
    01615       ViewportDrawChk(vp, t, top, right, bottom);
    01616     }
    01617   } else {
    01618     ViewportDoDraw(vp,
    01619       ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left,
    01620       ScaleByZoom(top - vp->top, vp->zoom) + vp->virtual_top,
    01621       ScaleByZoom(right - vp->left, vp->zoom) + vp->virtual_left,
    01622       ScaleByZoom(bottom - vp->top, vp->zoom) + vp->virtual_top
    01623     );
    01624   }
    01625 }
    01626
    01627 static inline void ViewportDraw(const ViewPort *vp, int left, int top, int right, int bottom)
    01628 {
    01629   if (right <= vp->left || bottom <= vp->top) return;
    01630
    01631   if (left >= vp->left + vp->width) return;
    01632
    01633   if (left < vp->left) left = vp->left;
    01634   if (right > vp->left + vp->width) right = vp->left + vp->width;
    01635
    01636   if (top >= vp->top + vp->height) return;
    01637
    01638   if (top < vp->top) top = vp->top;
    01639   if (bottom > vp->top + vp->height) bottom = vp->top + vp->height;
    01640
    01641   ViewportDrawChk(vp, left, top, right, bottom);
    01642 }
    01643
    01644 void DrawWindowViewport(const Window *w)
    01645 {
    01646   DrawPixelInfo *dpi = _cur_dpi;
    01647
    01648   dpi->left += w->left;
    01649   dpi->top += w->top;
    01650
    01651   ViewportDraw(w->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
    01652
    01653   dpi->left -= w->left;
    01654   dpi->top -= w->top;
    01655 }
    01656
    01657 static inline void ClampViewportToMap(const ViewPort *vp, int &x, int &y)
    01658 {
    01659   /* Centre of the viewport is hot spot */
    01660   x += vp->virtual_width / 2;
    01661   y += vp->virtual_height / 2;
    01662
    01663   /* Convert viewport coordinates to map coordinates
    01664    * Calculation is scaled by 4 to avoid rounding errors */
    01665   int vx = -x + y * 2;
    01666   int vy =  x + y * 2;
    01667
    01668   /* clamp to size of map */
    01669   vx = Clamp(vx, 0, MapMaxX() * TILE_SIZE * 4);
    01670   vy = Clamp(vy, 0, MapMaxY() * TILE_SIZE * 4);
    01671
    01672   /* Convert map coordinates to viewport coordinates */
    01673   x = (-vx + vy) / 2;
    01674   y = ( vx + vy) / 4;
    01675
    01676   /* Remove centreing */
    01677   x -= vp->virtual_width / 2;
    01678   y -= vp->virtual_height / 2;
    01679 }
    01680
    01681 void UpdateViewportPosition(Window *w)
    01682 {
    01683   const ViewPort *vp = w->viewport;
    01684
    01685   if (WP(w, vp_d).follow_vehicle != INVALID_VEHICLE) {
    01686     const Vehicle* veh = GetVehicle(WP(w, vp_d).follow_vehicle);
    01687     Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
    01688
    01689     SetViewportPosition(w, pt.x, pt.y);
    01690   } else {
    01691     /* Ensure the destination location is within the map */
    01692     ClampViewportToMap(vp, WP(w, vp_d).dest_scrollpos_x, WP(w, vp_d).dest_scrollpos_y);
    01693
    01694     int delta_x = WP(w, vp_d).dest_scrollpos_x - WP(w, vp_d).scrollpos_x;
    01695     int delta_y = WP(w, vp_d).dest_scrollpos_y - WP(w, vp_d).scrollpos_y;
    01696
    01697     if (delta_x != 0 || delta_y != 0) {
    01698       if (_patches.smooth_scroll) {
    01699         int max_scroll = ScaleByMapSize1D(512);
    01700         /* Not at our desired positon yet... */
    01701         WP(w, vp_d).scrollpos_x += Clamp(delta_x / 4, -max_scroll, max_scroll);
    01702         WP(w, vp_d).scrollpos_y += Clamp(delta_y / 4, -max_scroll, max_scroll);
    01703       } else {
    01704         WP(w, vp_d).scrollpos_x = WP(w, vp_d).dest_scrollpos_x;
    01705         WP(w, vp_d).scrollpos_y = WP(w, vp_d).dest_scrollpos_y;
    01706       }
    01707     }
    01708
    01709     ClampViewportToMap(vp, WP(w, vp_d).scrollpos_x, WP(w, vp_d).scrollpos_y);
    01710
    01711     SetViewportPosition(w, WP(w, vp_d).scrollpos_x, WP(w, vp_d).scrollpos_y);
    01712   }
    01713 }
    01714
    01723 static void MarkViewportDirty(const ViewPort *vp, int left, int top, int right, int bottom)
    01724 {
    01725   right -= vp->virtual_left;
    01726   if (right <= 0) return;
    01727
    01728   bottom -= vp->virtual_top;
    01729   if (bottom <= 0) return;
    01730
    01731   left = max(0, left - vp->virtual_left);
    01732
    01733   if (left >= vp->virtual_width) return;
    01734
    01735   top = max(0, top - vp->virtual_top);
    01736
    01737   if (top >= vp->virtual_height) return;
    01738
    01739   SetDirtyBlocks(
    01740     UnScaleByZoom(left, vp->zoom) + vp->left,
    01741     UnScaleByZoom(top, vp->zoom) + vp->top,
    01742     UnScaleByZoom(right, vp->zoom) + vp->left,
    01743     UnScaleByZoom(bottom, vp->zoom) + vp->top
    01744   );
    01745 }
    01746
    01747 void MarkAllViewportsDirty(int left, int top, int right, int bottom)
    01748 {
    01749   const ViewPort *vp = _viewports;
    01750   uint32 act = _active_viewports;
    01751   do {
    01752     if (act & 1) {
    01753       assert(vp->width != 0);
    01754       MarkViewportDirty(vp, left, top, right, bottom);
    01755     }
    01756   } while (vp++,act>>=1);
    01757 }
    01758
    01759 void MarkTileDirtyByTile(TileIndex tile)
    01760 {
    01761   Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, GetTileZ(tile));
    01762   MarkAllViewportsDirty(
    01763     pt.x - 31,
    01764     pt.y - 122,
    01765     pt.x - 31 + 67,
    01766     pt.y - 122 + 154
    01767   );
    01768 }
    01769
    01770 void MarkTileDirty(int x, int y)
    01771 {
    01772   uint z = 0;
    01773   Point pt;
    01774
    01775   if (IsInsideMM(x, 0, MapSizeX() * TILE_SIZE) &&
    01776       IsInsideMM(y, 0, MapSizeY() * TILE_SIZE))
    01777     z = GetTileZ(TileVirtXY(x, y));
    01778   pt = RemapCoords(x, y, z);
    01779
    01780   MarkAllViewportsDirty(
    01781     pt.x - 31,
    01782     pt.y - 122,
    01783     pt.x - 31 + 67,
    01784     pt.y - 122 + 154
    01785   );
    01786 }
    01787
    01796 static void SetSelectionTilesDirty()
    01797 {
    01798   int y_size, x_size;
    01799   int x = _thd.pos.x;
    01800   int y = _thd.pos.y;
    01801
    01802   x_size = _thd.size.x;
    01803   y_size = _thd.size.y;
    01804
    01805   if (_thd.outersize.x) {
    01806     x_size += _thd.outersize.x;
    01807     x += _thd.offs.x;
    01808     y_size += _thd.outersize.y;
    01809     y += _thd.offs.y;
    01810   }
    01811
    01812   assert(x_size > 0);
    01813   assert(y_size > 0);
    01814
    01815   x_size += x;
    01816   y_size += y;
    01817
    01818   do {
    01819     int y_bk = y;
    01820     do {
    01821       MarkTileDirty(x, y);
    01822     } while ( (y += TILE_SIZE) != y_size);
    01823     y = y_bk;
    01824   } while ( (x += TILE_SIZE) != x_size);
    01825 }
    01826
    01827
    01828 void SetSelectionRed(bool b)
    01829 {
    01830   _thd.make_square_red = b;
    01831   SetSelectionTilesDirty();
    01832 }
    01833
    01834
    01835 static bool CheckClickOnTown(const ViewPort *vp, int x, int y)
    01836 {
    01837   const Town *t;
    01838
    01839   if (!HasBit(_display_opt, DO_SHOW_TOWN_NAMES)) return false;
    01840
    01841   switch (vp->zoom) {
    01842     case ZOOM_LVL_NORMAL:
    01843       x = x - vp->left + vp->virtual_left;
    01844       y = y - vp->top  + vp->virtual_top;
    01845       FOR_ALL_TOWNS(t) {
    01846         if (y >= t->sign.top &&
    01847             y < t->sign.top + 12 &&
    01848             x >= t->sign.left &&
    01849             x < t->sign.left + t->sign.width_1) {
    01850           ShowTownViewWindow(t->index);
    01851           return true;
    01852         }
    01853       }
    01854       break;
    01855
    01856     case ZOOM_LVL_OUT_2X:
    01857       x = (x - vp->left + 1) * 2 + vp->virtual_left;
    01858       y = (y - vp->top  + 1) * 2 + vp->virtual_top;
    01859       FOR_ALL_TOWNS(t) {
    01860         if (y >= t->sign.top &&
    01861             y < t->sign.top + 24 &&
    01862             x >= t->sign.left &&
    01863             x < t->sign.left + t->sign.width_1 * 2) {
    01864           ShowTownViewWindow(t->index);
    01865           return true;
    01866         }
    01867       }
    01868       break;
    01869
    01870     case ZOOM_LVL_OUT_4X:
    01871     case ZOOM_LVL_OUT_8X:
    01872       x = ScaleByZoom(x - vp->left + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_left;
    01873       y = ScaleByZoom(y - vp->top  + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_top;
    01874
    01875       FOR_ALL_TOWNS(t) {
    01876         if (y >= t->sign.top &&
    01877             y < t->sign.top + ScaleByZoom(12, vp->zoom) &&
    01878             x >= t->sign.left &&
    01879             x < t->sign.left + ScaleByZoom(t->sign.width_2, vp->zoom)) {
    01880           ShowTownViewWindow(t->index);
    01881           return true;
    01882         }
    01883       }
    01884       break;
    01885
    01886     default: NOT_REACHED();
    01887   }
    01888
    01889   return false;
    01890 }
    01891
    01892
    01893 static bool CheckClickOnStation(const ViewPort *vp, int x, int y)
    01894 {
    01895   const Station *st;
    01896
    01897   if (!HasBit(_display_opt, DO_SHOW_STATION_NAMES)) return false;
    01898
    01899   switch (vp->zoom) {
    01900     case ZOOM_LVL_NORMAL:
    01901       x = x - vp->left + vp->virtual_left;
    01902       y = y - vp->top  + vp->virtual_top;
    01903       FOR_ALL_STATIONS(st) {
    01904         if (y >= st->sign.top &&
    01905             y < st->sign.top + 12 &&
    01906             x >= st->sign.left &&
    01907             x < st->sign.left + st->sign.width_1) {
    01908           ShowStationViewWindow(st->index);
    01909           return true;
    01910         }
    01911       }
    01912       break;
    01913
    01914     case ZOOM_LVL_OUT_2X:
    01915       x = (x - vp->left + 1) * 2 + vp->virtual_left;
    01916       y = (y - vp->top  + 1) * 2 + vp->virtual_top;
    01917       FOR_ALL_STATIONS(st) {
    01918         if (y >= st->sign.top &&
    01919             y < st->sign.top + 24 &&
    01920             x >= st->sign.left &&
    01921             x < st->sign.left + st->sign.width_1 * 2) {
    01922           ShowStationViewWindow(st->index);
    01923           return true;
    01924         }
    01925       }
    01926       break;
    01927
    01928     case ZOOM_LVL_OUT_4X:
    01929     case ZOOM_LVL_OUT_8X:
    01930       x = ScaleByZoom(x - vp->left + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_left;
    01931       y = ScaleByZoom(y - vp->top  + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_top;
    01932
    01933       FOR_ALL_STATIONS(st) {
    01934         if (y >= st->sign.top &&
    01935             y < st->sign.top + ScaleByZoom(12, vp->zoom) &&
    01936             x >= st->sign.left &&
    01937             x < st->sign.left + ScaleByZoom(st->sign.width_2, vp->zoom)) {
    01938           ShowStationViewWindow(st->index);
    01939           return true;
    01940         }
    01941       }
    01942       break;
    01943
    01944     default: NOT_REACHED();
    01945   }
    01946
    01947   return false;
    01948 }
    01949
    01950
    01951 static bool CheckClickOnSign(const ViewPort *vp, int x, int y)
    01952 {
    01953   const Sign *si;
    01954
    01955   if (!HasBit(_display_opt, DO_SHOW_SIGNS) || _current_player == PLAYER_SPECTATOR) return false;
    01956
    01957   switch (vp->zoom) {
    01958     case ZOOM_LVL_NORMAL:
    01959       x = x - vp->left + vp->virtual_left;
    01960       y = y - vp->top  + vp->virtual_top;
    01961       FOR_ALL_SIGNS(si) {
    01962         if (y >= si->sign.top &&
    01963             y <  si->sign.top + 12 &&
    01964             x >= si->sign.left &&
    01965             x <  si->sign.left + si->sign.width_1) {
    01966           ShowRenameSignWindow(si);
    01967           return true;
    01968         }
    01969       }
    01970       break;
    01971
    01972     case ZOOM_LVL_OUT_2X:
    01973       x = (x - vp->left + 1) * 2 + vp->virtual_left;
    01974       y = (y - vp->top  + 1) * 2 + vp->virtual_top;
    01975       FOR_ALL_SIGNS(si) {
    01976         if (y >= si->sign.top &&
    01977             y <  si->sign.top + 24 &&
    01978             x >= si->sign.left &&
    01979             x <  si->sign.left + si->sign.width_1 * 2) {
    01980           ShowRenameSignWindow(si);
    01981           return true;
    01982         }
    01983       }
    01984       break;
    01985
    01986     case ZOOM_LVL_OUT_4X:
    01987     case ZOOM_LVL_OUT_8X:
    01988       x = ScaleByZoom(x - vp->left + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_left;
    01989       y = ScaleByZoom(y - vp->top  + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_top;
    01990
    01991       FOR_ALL_SIGNS(si) {
    01992         if (y >= si->sign.top &&
    01993             y <  si->sign.top + ScaleByZoom(12, vp->zoom) &&
    01994             x >= si->sign.left &&
    01995             x <  si->sign.left + ScaleByZoom(si->sign.width_2, vp->zoom)) {
    01996           ShowRenameSignWindow(si);
    01997           return true;
    01998         }
    01999       }
    02000       break;
    02001
    02002     default: NOT_REACHED();
    02003   }
    02004
    02005   return false;
    02006 }
    02007
    02008
    02009 static bool CheckClickOnWaypoint(const ViewPort *vp, int x, int y)
    02010 {
    02011   const Waypoint *wp;
    02012
    02013   if (!HasBit(_display_opt, DO_WAYPOINTS)) return false;
    02014
    02015   switch (vp->zoom) {
    02016     case ZOOM_LVL_NORMAL:
    02017       x = x - vp->left + vp->virtual_left;
    02018       y = y - vp->top  + vp->virtual_top;
    02019       FOR_ALL_WAYPOINTS(wp) {
    02020         if (y >= wp->sign.top &&
    02021             y < wp->sign.top + 12 &&
    02022             x >= wp->sign.left &&
    02023             x < wp->sign.left + wp->sign.width_1) {
    02024           ShowRenameWaypointWindow(wp);
    02025           return true;
    02026         }
    02027       }
    02028       break;
    02029
    02030     case ZOOM_LVL_OUT_2X:
    02031       x = (x - vp->left + 1) * 2 + vp->virtual_left;
    02032       y = (y - vp->top  + 1) * 2 + vp->virtual_top;
    02033       FOR_ALL_WAYPOINTS(wp) {
    02034         if (y >= wp->sign.top &&
    02035             y < wp->sign.top + 24 &&
    02036             x >= wp->sign.left &&
    02037             x < wp->sign.left + wp->sign.width_1 * 2) {
    02038           ShowRenameWaypointWindow(wp);
    02039           return true;
    02040         }
    02041       }
    02042       break;
    02043
    02044     case ZOOM_LVL_OUT_4X:
    02045     case ZOOM_LVL_OUT_8X:
    02046       x = ScaleByZoom(x - vp->left + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_left;
    02047       y = ScaleByZoom(y - vp->top  + ScaleByZoom(1, vp->zoom) - 1, vp->zoom) + vp->virtual_top;
    02048
    02049       FOR_ALL_WAYPOINTS(wp) {
    02050         if (y >= wp->sign.top &&
    02051             y < wp->sign.top + ScaleByZoom(12, vp->zoom) &&
    02052             x >= wp->sign.left &&
    02053             x < wp->sign.left + ScaleByZoom(wp->sign.width_2, vp->zoom)) {
    02054           ShowRenameWaypointWindow(wp);
    02055           return true;
    02056         }
    02057       }
    02058       break;
    02059
    02060     default: NOT_REACHED();
    02061   }
    02062
    02063   return false;
    02064 }
    02065
    02066
    02067 static void CheckClickOnLandscape(const ViewPort *vp, int x, int y)
    02068 {
    02069   Point pt = TranslateXYToTileCoord(vp, x, y);
    02070
    02071   if (pt.x != -1) ClickTile(TileVirtXY(pt.x, pt.y));
    02072 }
    02073
    02074
    02075 static void SafeShowTrainViewWindow(const Vehicle* v)
    02076 {
    02077   if (!IsFrontEngine(v)) v = v->First();
    02078   ShowVehicleViewWindow(v);
    02079 }
    02080
    02081 static void SafeShowRoadVehViewWindow(const Vehicle *v)
    02082 {
    02083   if (!IsRoadVehFront(v)) v = v->First();
    02084   ShowVehicleViewWindow(v);
    02085 }
    02086
    02087 static void Nop(const Vehicle *v) {}
    02088
    02089 typedef void OnVehicleClickProc(const Vehicle *v);
    02090 static OnVehicleClickProc* const _on_vehicle_click_proc[] = {
    02091   SafeShowTrainViewWindow,
    02092   SafeShowRoadVehViewWindow,
    02093   ShowVehicleViewWindow,
    02094   ShowVehicleViewWindow,
    02095   Nop, // Special vehicles
    02096   Nop  // Disaster vehicles
    02097 };
    02098
    02099 void HandleViewportClicked(const ViewPort *vp, int x, int y)
    02100 {
    02101   const Vehicle *v;
    02102
    02103   if (CheckClickOnTown(vp, x, y)) return;
    02104   if (CheckClickOnStation(vp, x, y)) return;
    02105   if (CheckClickOnSign(vp, x, y)) return;
    02106   if (CheckClickOnWaypoint(vp, x, y)) return;
    02107   CheckClickOnLandscape(vp, x, y);
    02108
    02109   v = CheckClickOnVehicle(vp, x, y);
    02110   if (v != NULL) {
    02111     DEBUG(misc, 2, "Vehicle %d (index %d) at %p", v->unitnumber, v->index, v);
    02112     _on_vehicle_click_proc[v->type](v);
    02113   }
    02114 }
    02115
    02116 Vehicle *CheckMouseOverVehicle()
    02117 {
    02118   const Window *w;
    02119   const ViewPort *vp;
    02120
    02121   int x = _cursor.pos.x;
    02122   int y = _cursor.pos.y;
    02123
    02124   w = FindWindowFromPt(x, y);
    02125   if (w == NULL) return NULL;
    02126
    02127   vp = IsPtInWindowViewport(w, x, y);
    02128   return (vp != NULL) ? CheckClickOnVehicle(vp, x, y) : NULL;
    02129 }
    02130
    02131
    02132
    02133 void PlaceObject()
    02134 {
    02135   Point pt;
    02136   Window *w;
    02137
    02138   pt = GetTileBelowCursor();
    02139   if (pt.x == -1) return;
    02140
    02141   if (_thd.place_mode == VHM_POINT) {
    02142     pt.x += 8;
    02143     pt.y += 8;
    02144   }
    02145
    02146   _tile_fract_coords.x = pt.x & 0xF;
    02147   _tile_fract_coords.y = pt.y & 0xF;
    02148
    02149   w = GetCallbackWnd();
    02150   if (w != NULL) {
    02151     WindowEvent e;
    02152
    02153     e.event = WE_PLACE_OBJ;
    02154     e.we.place.pt = pt;
    02155     e.we.place.tile = TileVirtXY(pt.x, pt.y);
    02156     w->wndproc(w, &e);
    02157   }
    02158 }
    02159
    02160
    02161 /* scrolls the viewport in a window to a given location */
    02162 bool ScrollWindowTo(int x , int y, Window *w, bool instant)
    02163 {
    02164   /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
    02165   Point pt = MapXYZToViewport(w->viewport, x, y, GetSlopeZ(Clamp(x, 0, MapSizeX() * TILE_SIZE), Clamp(y, 0, MapSizeY() * TILE_SIZE)));
    02166   WP(w, vp_d).follow_vehicle = INVALID_VEHICLE;
    02167
    02168   if (WP(w, vp_d).dest_scrollpos_x == pt.x && WP(w, vp_d).dest_scrollpos_y == pt.y)
    02169     return false;
    02170
    02171   if (instant) {
    02172     WP(w, vp_d).scrollpos_x = pt.x;
    02173     WP(w, vp_d).scrollpos_y = pt.y;
    02174   }
    02175
    02176   WP(w, vp_d).dest_scrollpos_x = pt.x;
    02177   WP(w, vp_d).dest_scrollpos_y = pt.y;
    02178   return true;
    02179 }
    02180
    02181
    02182 bool ScrollMainWindowTo(int x, int y, bool instant)
    02183 {
    02184   Window *w;
    02185   bool res = ScrollWindowTo(x, y, FindWindowById(WC_MAIN_WINDOW, 0), instant);
    02186
    02187   /* If a user scrolls to a tile (via what way what so ever) and already is on
    02188    *  that tile (e.g.: pressed twice), move the smallmap to that location,
    02189    *  so you directly see where you are on the smallmap. */
    02190
    02191   if (res) return res;
    02192
    02193   w = FindWindowById(WC_SMALLMAP, 0);
    02194   if (w == NULL) return res;
    02195
    02196   SmallMapCenterOnCurrentPos(w);
    02197
    02198   return res;
    02199 }
    02200
    02201
    02202 bool ScrollMainWindowToTile(TileIndex tile, bool instant)
    02203 {
    02204   return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, instant);
    02205 }
    02206
    02207 void SetRedErrorSquare(TileIndex tile)
    02208 {
    02209   TileIndex old;
    02210
    02211   old = _thd.redsq;
    02212   _thd.redsq = tile;
    02213
    02214   if (tile != old) {
    02215     if (tile != 0) MarkTileDirtyByTile(tile);
    02216     if (old  != 0) MarkTileDirtyByTile(old);
    02217   }
    02218 }
    02219
    02220 void SetTileSelectSize(int w, int h)
    02221 {
    02222   _thd.new_size.x = w * TILE_SIZE;
    02223   _thd.new_size.y = h * TILE_SIZE;
    02224   _thd.new_outersize.x = 0;
    02225   _thd.new_outersize.y = 0;
    02226 }
    02227
    02228 void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
    02229 {
    02230   _thd.offs.x = ox * TILE_SIZE;
    02231   _thd.offs.y = oy * TILE_SIZE;
    02232   _thd.new_outersize.x = sx * TILE_SIZE;
    02233   _thd.new_outersize.y = sy * TILE_SIZE;
    02234 }
    02235
    02237 static HighLightStyle GetAutorailHT(int x, int y)
    02238 {
    02239   return HT_RAIL | _autorail_piece[x & 0xF][y & 0xF];
    02240 }
    02241
    02250 void UpdateTileSelection()
    02251 {
    02252   int x1;
    02253   int y1;
    02254
    02255   _thd.new_drawstyle = 0;
    02256
    02257   if (_thd.place_mode == VHM_SPECIAL) {
    02258     x1 = _thd.selend.x;
    02259     y1 = _thd.selend.y;
    02260     if (x1 != -1) {
    02261       int x2 = _thd.selstart.x & ~0xF;
    02262       int y2 = _thd.selstart.y & ~0xF;
    02263       x1 &= ~0xF;
    02264       y1 &= ~0xF;
    02265
    02266       if (x1 >= x2) Swap(x1, x2);
    02267       if (y1 >= y2) Swap(y1, y2);
    02268       _thd.new_pos.x = x1;
    02269       _thd.new_pos.y = y1;
    02270       _thd.new_size.x = x2 - x1 + TILE_SIZE;
    02271       _thd.new_size.y = y2 - y1 + TILE_SIZE;
    02272       _thd.new_drawstyle = _thd.next_drawstyle;
    02273     }
    02274   } else if (_thd.place_mode != VHM_NONE) {
    02275     Point pt = GetTileBelowCursor();
    02276     x1 = pt.x;
    02277     y1 = pt.y;
    02278     if (x1 != -1) {
    02279       switch (_thd.place_mode) {
    02280         case VHM_RECT:
    02281           _thd.new_drawstyle = HT_RECT;
    02282           break;
    02283         case VHM_POINT:
    02284           _thd.new_drawstyle = HT_POINT;
    02285           x1 += 8;
    02286           y1 += 8;
    02287           break;
    02288         case VHM_RAIL:
    02289           _thd.new_drawstyle = GetAutorailHT(pt.x, pt.y); // draw one highlighted tile
    02290           break;
    02291         default:
    02292           NOT_REACHED();
    02293           break;
    02294       }
    02295       _thd.new_pos.x = x1 & ~0xF;
    02296       _thd.new_pos.y = y1 & ~0xF;
    02297     }
    02298   }
    02299
    02300   /* redraw selection */
    02301   if (_thd.drawstyle != _thd.new_drawstyle ||
    02302       _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
    02303       _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
    02304       _thd.outersize.x != _thd.new_outersize.x ||
    02305       _thd.outersize.y != _thd.new_outersize.y) {
    02306     /* clear the old selection? */
    02307     if (_thd.drawstyle) SetSelectionTilesDirty();
    02308
    02309     _thd.drawstyle = _thd.new_drawstyle;
    02310     _thd.pos = _thd.new_pos;
    02311     _thd.size = _thd.new_size;
    02312     _thd.outersize = _thd.new_outersize;
    02313     _thd.dirty = 0xff;
    02314
    02315     /* draw the new selection? */
    02316     if (_thd.new_drawstyle) SetSelectionTilesDirty();
    02317   }
    02318 }
    02319
    02321 void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, byte process)
    02322 {
    02323   _thd.select_method = method;
    02324   _thd.select_proc   = process;
    02325   _thd.selend.x = TileX(tile) * TILE_SIZE;
    02326   _thd.selstart.x = TileX(tile) * TILE_SIZE;
    02327   _thd.selend.y = TileY(tile) * TILE_SIZE;
    02328   _thd.selstart.y = TileY(tile) * TILE_SIZE;
    02329
    02330   /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
    02331    * In effect, placement starts from the centre of a tile
    02332    */
    02333   if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
    02334     _thd.selend.x += TILE_SIZE / 2;
    02335     _thd.selend.y += TILE_SIZE / 2;
    02336     _thd.selstart.x += TILE_SIZE / 2;
    02337     _thd.selstart.y += TILE_SIZE / 2;
    02338   }
    02339
    02340   if (_thd.place_mode == VHM_RECT) {
    02341     _thd.place_mode = VHM_SPECIAL;
    02342     _thd.next_drawstyle = HT_RECT;
    02343   } else if (_thd.place_mode == VHM_RAIL) { // autorail one piece
    02344     _thd.place_mode = VHM_SPECIAL;
    02345     _thd.next_drawstyle = _thd.drawstyle;
    02346   } else {
    02347     _thd.place_mode = VHM_SPECIAL;
    02348     _thd.next_drawstyle = HT_POINT;
    02349   }
    02350   _special_mouse_mode = WSM_SIZING;
    02351 }
    02352
    02353 void VpSetPlaceSizingLimit(int limit)
    02354 {
    02355   _thd.sizelimit = limit;
    02356 }
    02357
    02362 void VpSetPresizeRange(TileIndex from, TileIndex to)
    02363 {
    02364   uint64 distance = DistanceManhattan(from, to) + 1;
    02365
    02366   _thd.selend.x = TileX(to) * TILE_SIZE;
    02367   _thd.selend.y = TileY(to) * TILE_SIZE;
    02368   _thd.selstart.x = TileX(from) * TILE_SIZE;
    02369   _thd.selstart.y = TileY(from) * TILE_SIZE;
    02370   _thd.next_drawstyle = HT_RECT;
    02371
    02372   /* show measurement only if there is any length to speak of */
    02373   if (distance > 1) GuiShowTooltipsWithArgs(STR_MEASURE_LENGTH, 1, &distance);
    02374 }
    02375
    02376 static void VpStartPreSizing()
    02377 {
    02378   _thd.selend.x = -1;
    02379   _special_mouse_mode = WSM_PRESIZE;
    02380 }
    02381
    02384 static HighLightStyle Check2x1AutoRail(int mode)
    02385 {
    02386   int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
    02387   int sxpy = (_thd.selend.x & 0xF) + (_thd.selend.y & 0xF);
    02388   int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
    02389   int sxmy = (_thd.selend.x & 0xF) - (_thd.selend.y & 0xF);
    02390
    02391   switch (mode) {
    02392     default: NOT_REACHED();
    02393     case 0: // end piece is lower right
    02394       if (fxpy >= 20 && sxpy <= 12) { /*SwapSelection(); DoRailroadTrack(0); */return HT_DIR_HL; }
    02395       if (fxmy < -3 && sxmy > 3) {/* DoRailroadTrack(0); */return HT_DIR_VR; }
    02396       return HT_DIR_Y;
    02397
    02398     case 1:
    02399       if (fxmy > 3 && sxmy < -3) { /*SwapSelection(); DoRailroadTrack(0); */return HT_DIR_VL; }
    02400       if (fxpy <= 12 && sxpy >= 20) { /*DoRailroadTrack(0); */return HT_DIR_HU; }
    02401       return HT_DIR_Y;
    02402
    02403     case 2:
    02404       if (fxmy > 3 && sxmy < -3) { /*DoRailroadTrack(3);*/ return HT_DIR_VL; }
    02405       if (fxpy >= 20 && sxpy <= 12) { /*SwapSelection(); DoRailroadTrack(0); */return HT_DIR_HL; }
    02406       return HT_DIR_X;
    02407
    02408     case 3:
    02409       if (fxmy < -3 && sxmy > 3) { /*SwapSelection(); DoRailroadTrack(3);*/ return HT_DIR_VR; }
    02410       if (fxpy <= 12 && sxpy >= 20) { /*DoRailroadTrack(0); */return HT_DIR_HU; }
    02411       return HT_DIR_X;
    02412   }
    02413 }
    02414
    02426 static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
    02427 {
    02428   uint start_x = TileX(start_tile);
    02429   uint start_y = TileY(start_tile);
    02430   uint end_x = TileX(end_tile);
    02431   uint end_y = TileY(end_tile);
    02432
    02433   switch (style & HT_DRAG_MASK) {
    02434     case HT_RAIL:
    02435     case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
    02436
    02437     case HT_RECT:
    02438     case HT_POINT: return (end_x != start_x && end_y < start_y);
    02439     default: NOT_REACHED();
    02440   }
    02441
    02442   return false;
    02443 }
    02444
    02459 static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
    02460 {
    02461   bool swap = SwapDirection(style, start_tile, end_tile);
    02462   byte style_t;
    02463   uint h0, h1, ht; // start heigth, end height, and temp variable
    02464
    02465   if (start_tile == end_tile) return 0;
    02466   if (swap) Swap(start_tile, end_tile);
    02467
    02468   switch (style & HT_DRAG_MASK) {
    02469     case HT_RECT: {
    02470       static const TileIndexDiffC heightdiff_area_by_dir[] = {
    02471         /* Start */ {1, 0}, /* Dragging east */ {0, 0}, /* Dragging south */
    02472         /* End   */ {0, 1}, /* Dragging east */ {1, 1}  /* Dragging south */
    02473       };
    02474
    02475       /* In the case of an area we can determine whether we were dragging south or
    02476        * east by checking the X-coordinates of the tiles */
    02477       style_t = (byte)(TileX(end_tile) > TileX(start_tile));
    02478       start_tile = TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_area_by_dir[style_t]));
    02479       end_tile   = TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_area_by_dir[2 + style_t]));
    02480     }
    02481     /* Fallthrough */
    02482     case HT_POINT:
    02483       h0 = TileHeight(start_tile);
    02484       h1 = TileHeight(end_tile);
    02485       break;
    02486     default: { /* All other types, this is mostly only line/autorail */
    02487       static const HighLightStyle flip_style_direction[] = {
    02488         HT_DIR_X, HT_DIR_Y, HT_DIR_HL, HT_DIR_HU, HT_DIR_VR, HT_DIR_VL
    02489       };
    02490       static const TileIndexDiffC heightdiff_line_by_dir[] = {
    02491         /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X  */ {0, 1}, {1, 1}, /* HT_DIR_Y  */
    02492         /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, /* HT_DIR_HL */
    02493         /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, /* HT_DIR_VR */
    02494
    02495         /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X  */ {1, 0}, {0, 0}, /* HT_DIR_Y  */
    02496         /* End   */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, /* HT_DIR_HL */
    02497         /* End   */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, /* HT_DIR_VR */
    02498       };
    02499
    02500       distance %= 2; // we're only interested if the distance is even or uneven
    02501       style &= HT_DIR_MASK;
    02502
    02503       /* To handle autorail, we do some magic to be able to use a lookup table.
    02504        * Firstly if we drag the other way around, we switch start&end, and if needed
    02505        * also flip the drag-position. Eg if it was on the left, and the distance is even
    02506        * that means the end, which is now the start is on the right */
    02507       if (swap && distance == 0) style = flip_style_direction[style];
    02508
    02509       /* Use lookup table for start-tile based on HighLightStyle direction */
    02510       style_t = style * 2;
    02511       assert(style_t < lengthof(heightdiff_line_by_dir) - 13);
    02512       h0 = TileHeight(TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_line_by_dir[style_t])));
    02513       ht = TileHeight(TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_line_by_dir[style_t + 1])));
    02514       h0 = max(h0, ht);
    02515
    02516       /* Use lookup table for end-tile based on HighLightStyle direction
    02517        * flip around side (lower/upper, left/right) based on distance */
    02518       if (distance == 0) style_t = flip_style_direction[style] * 2;
    02519       assert(style_t < lengthof(heightdiff_line_by_dir) - 13);
    02520       h1 = TileHeight(TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_line_by_dir[12 + style_t])));
    02521       ht = TileHeight(TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_line_by_dir[12 + style_t + 1])));
    02522       h1 = max(h1, ht);
    02523     } break;
    02524   }
    02525
    02526   if (swap) Swap(h0, h1);
    02527   /* Minimap shows height in intervals of 50 meters, let's do the same */
    02528   return (int)(h1 - h0) * 50;
    02529 }
    02530
    02531 static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
    02532
    02534 static void CalcRaildirsDrawstyle(TileHighlightData *thd, int x, int y, int method)
    02535 {
    02536   HighLightStyle b;
    02537   uint w, h;
    02538
    02539   int dx = thd->selstart.x - (thd->selend.x & ~0xF);
    02540   int dy = thd->selstart.y - (thd->selend.y & ~0xF);
    02541   w = abs(dx) + 16;
    02542   h = abs(dy) + 16;
    02543
    02544   if (TileVirtXY(thd->selstart.x, thd->selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
    02545     if (method == VPM_RAILDIRS) {
    02546       b = GetAutorailHT(x, y);
    02547     } else { // rect for autosignals on one tile
    02548       b = HT_RECT;
    02549     }
    02550   } else if (h == 16) { // Is this in X direction?
    02551     if (dx == 16) { // 2x1 special handling
    02552       b = (Check2x1AutoRail(3)) | HT_LINE;
    02553     } else if (dx == -16) {
    02554       b = (Check2x1AutoRail(2)) | HT_LINE;
    02555     } else {
    02556       b = HT_LINE | HT_DIR_X;
    02557     }
    02558     y = thd->selstart.y;
    02559   } else if (w == 16) { // Or Y direction?
    02560     if (dy == 16) { // 2x1 special handling
    02561       b = (Check2x1AutoRail(1)) | HT_LINE;
    02562     } else if (dy == -16) { // 2x1 other direction
    02563       b = (Check2x1AutoRail(0)) | HT_LINE;
    02564     } else {
    02565       b = HT_LINE | HT_DIR_Y;
    02566     }
    02567     x = thd->selstart.x;
    02568   } else if (w > h * 2) { // still count as x dir?
    02569     b = HT_LINE | HT_DIR_X;
    02570     y = thd->selstart.y;
    02571   } else if (h > w * 2) { // still count as y dir?
    02572     b = HT_LINE | HT_DIR_Y;
    02573     x = thd->selstart.x;
    02574   } else { // complicated direction
    02575     int d = w - h;
    02576     thd->selend.x = thd->selend.x & ~0xF;
    02577     thd->selend.y = thd->selend.y & ~0xF;
    02578
    02579     // four cases.
    02580     if (x > thd->selstart.x) {
    02581       if (y > thd->selstart.y) {
    02582         // south
    02583         if (d == 0) {
    02584           b = (x & 0xF) > (y & 0xF) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
    02585         } else if (d >= 0) {
    02586           x = thd->selstart.x + h;
    02587           b = HT_LINE | HT_DIR_VL;
    02588           // return px == py || px == py + 16;
    02589         } else {
    02590           y = thd->selstart.y + w;
    02591           b = HT_LINE | HT_DIR_VR;
    02592         } // return px == py || px == py - 16;
    02593       } else {
    02594         // west
    02595         if (d == 0) {
    02596           b = (x & 0xF) + (y & 0xF) >= 0x10 ? HT_LINE | HT_DIR_HL : HT_LINE | HT_DIR_HU;
    02597         } else if (d >= 0) {
    02598           x = thd->selstart.x + h;
    02599           b = HT_LINE | HT_DIR_HL;
    02600         } else {
    02601           y = thd->selstart.y - w;
    02602           b = HT_LINE | HT_DIR_HU;
    02603         }
    02604       }
    02605     } else {
    02606       if (y > thd->selstart.y) {
    02607         // east
    02608         if (d == 0) {
    02609           b = (x & 0xF) + (y & 0xF) >= 0x10 ? HT_LINE | HT_DIR_HL : HT_LINE | HT_DIR_HU;
    02610         } else if (d >= 0) {
    02611           x = thd->selstart.x - h;
    02612           b = HT_LINE | HT_DIR_HU;
    02613           // return px == -py || px == -py - 16;
    02614         } else {
    02615           y = thd->selstart.y + w;
    02616           b = HT_LINE | HT_DIR_HL;
    02617         } // return px == -py || px == -py + 16;
    02618       } else {
    02619         // north
    02620         if (d == 0) {
    02621           b = (x & 0xF) > (y & 0xF) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
    02622         } else if (d >= 0) {
    02623           x = thd->selstart.x - h;
    02624           b = HT_LINE | HT_DIR_VR;
    02625           // return px == py || px == py - 16;
    02626         } else {
    02627           y = thd->selstart.y - w;
    02628           b = HT_LINE | HT_DIR_VL;
    02629         } //return px == py || px == py + 16;
    02630       }
    02631     }
    02632   }
    02633
    02634   if (_patches.measure_tooltip) {
    02635     TileIndex t0 = TileVirtXY(thd->selstart.x, thd->selstart.y);
    02636     TileIndex t1 = TileVirtXY(x, y);
    02637     uint distance = DistanceManhattan(t0, t1) + 1;
    02638     byte index = 0;
    02639     uint64 params[2];
    02640
    02641     if (distance != 1) {
    02642       int heightdiff = CalcHeightdiff(b, distance, t0, t1);
    02643       /* If we are showing a tooltip for horizontal or vertical drags,
    02644        * 2 tiles have a length of 1. To bias towards the ceiling we add
    02645        * one before division. It feels more natural to count 3 lengths as 2 */
    02646       if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
    02647         distance = (distance + 1) / 2;
    02648       }
    02649
    02650       params[index++] = distance;
    02651       if (heightdiff != 0) params[index++] = heightdiff;
    02652     }
    02653
    02654     GuiShowTooltipsWithArgs(measure_strings_length[index], index, params);
    02655   }
    02656
    02657   thd->selend.x = x;
    02658   thd->selend.y = y;
    02659   thd->next_drawstyle = b;
    02660 }
    02661
    02668 void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method)
    02669 {
    02670   int sx, sy;
    02671   HighLightStyle style;
    02672
    02673   if (x == -1) {
    02674     _thd.selend.x = -1;
    02675     return;
    02676   }
    02677
    02678   /* Special handling of drag in any (8-way) direction */
    02679   if (method == VPM_RAILDIRS || method == VPM_SIGNALDIRS) {
    02680     _thd.selend.x = x;
    02681     _thd.selend.y = y;
    02682     CalcRaildirsDrawstyle(&_thd, x, y, method);
    02683     return;
    02684   }
    02685
    02686   /* Needed so level-land is placed correctly */
    02687   if (_thd.next_drawstyle == HT_POINT) {
    02688     x += TILE_SIZE / 2;
    02689     y += TILE_SIZE / 2;
    02690   }
    02691
    02692   sx = _thd.selstart.x;
    02693   sy = _thd.selstart.y;
    02694
    02695   switch (method) {
    02696     case VPM_X_OR_Y: /* drag in X or Y direction */
    02697       if (abs(sy - y) < abs(sx - x)) {
    02698         y = sy;
    02699         style = HT_DIR_X;
    02700       } else {
    02701         x = sx;
    02702         style = HT_DIR_Y;
    02703       }
    02704       goto calc_heightdiff_single_direction;
    02705     case VPM_FIX_X: /* drag in Y direction */
    02706       x = sx;
    02707       style = HT_DIR_Y;
    02708       goto calc_heightdiff_single_direction;
    02709     case VPM_FIX_Y: /* drag in X direction */
    02710       y = sy;
    02711       style = HT_DIR_X;
    02712
    02713 calc_heightdiff_single_direction:;
    02714       if (_patches.measure_tooltip) {
    02715         TileIndex t0 = TileVirtXY(sx, sy);
    02716         TileIndex t1 = TileVirtXY(x, y);
    02717         uint distance = DistanceManhattan(t0, t1) + 1;
    02718         byte index = 0;
    02719         uint64 params[2];
    02720
    02721         if (distance != 1) {
    02722           /* With current code passing a HT_LINE style to calculate the height
    02723            * difference is enough. However if/when a point-tool is created
    02724            * with this method, function should be called with new_style (below)
    02725            * instead of HT_LINE | style case HT_POINT is handled specially
    02726            * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
    02727           int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
    02728
    02729           params[index++] = distance;
    02730           if (heightdiff != 0) params[index++] = heightdiff;
    02731         }
    02732
    02733         GuiShowTooltipsWithArgs(measure_strings_length[index], index, params);
    02734       } break;
    02735
    02736     case VPM_X_AND_Y_LIMITED: { /* drag an X by Y constrained rect area */
    02737       int limit = (_thd.sizelimit - 1) * TILE_SIZE;
    02738       x = sx + Clamp(x - sx, -limit, limit);
    02739       y = sy + Clamp(y - sy, -limit, limit);
    02740       } /* Fallthrough */
    02741     case VPM_X_AND_Y: { /* drag an X by Y area */
    02742       if (_patches.measure_tooltip) {
    02743         static const StringID measure_strings_area[] = {
    02744           STR_NULL, STR_NULL, STR_MEASURE_AREA, STR_MEASURE_AREA_HEIGHTDIFF
    02745         };
    02746
    02747         TileIndex t0 = TileVirtXY(sx, sy);
    02748         TileIndex t1 = TileVirtXY(x, y);
    02749         uint dx = Delta(TileX(t0), TileX(t1)) + 1;
    02750         uint dy = Delta(TileY(t0), TileY(t1)) + 1;
    02751         byte index = 0;
    02752         uint64 params[3];
    02753
    02754         /* If dragging an area (eg dynamite tool) and it is actually a single
    02755          * row/column, change the type to 'line' to get proper calculation for height */
    02756         style = (HighLightStyle)_thd.next_drawstyle;
    02757         if (style & HT_RECT) {
    02758           if (dx == 1) {
    02759             style = HT_LINE | HT_DIR_Y;
    02760           } else if (dy == 1) {
    02761             style = HT_LINE | HT_DIR_X;
    02762           }
    02763         }
    02764
    02765         if (dx != 1 || dy != 1) {
    02766           int heightdiff = CalcHeightdiff(style, 0, t0, t1);
    02767
    02768           params[index++] = dx;
    02769           params[index++] = dy;
    02770           if (heightdiff != 0) params[index++] = heightdiff;
    02771         }
    02772
    02773         GuiShowTooltipsWithArgs(measure_strings_area[index], index, params);
    02774       }
    02775     break;
    02776
    02777     }
    02778     default: NOT_REACHED();
    02779   }
    02780
    02781   _thd.selend.x = x;
    02782   _thd.selend.y = y;
    02783 }
    02784
    02786 bool VpHandlePlaceSizingDrag()
    02787 {
    02788   Window *w;
    02789   WindowEvent e;
    02790
    02791   if (_special_mouse_mode != WSM_SIZING) return true;
    02792
    02793   e.we.place.select_method = _thd.select_method;
    02794   e.we.place.select_proc   = _thd.select_proc;
    02795
    02796   /* stop drag mode if the window has been closed */
    02797   w = FindWindowById(_thd.window_class, _thd.window_number);
    02798   if (w == NULL) {
    02799     ResetObjectToPlace();
    02800     return false;
    02801   }
    02802
    02803   /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
    02804   if (_left_button_down) {
    02805     e.event = WE_PLACE_DRAG;
    02806     e.we.place.pt = GetTileBelowCursor();
    02807     w->wndproc(w, &e);
    02808     return false;
    02809   }
    02810
    02811   /* mouse button released..
    02812    * keep the selected tool, but reset it to the original mode. */
    02813   _special_mouse_mode = WSM_NONE;
    02814   if (_thd.next_drawstyle == HT_RECT) {
    02815     _thd.place_mode = VHM_RECT;
    02816   } else if (e.we.place.select_method == VPM_SIGNALDIRS) { // some might call this a hack... -- Dominik
    02817     _thd.place_mode = VHM_RECT;
    02818   } else if (_thd.next_drawstyle & HT_LINE) {
    02819     _thd.place_mode = VHM_RAIL;
    02820   } else if (_thd.next_drawstyle & HT_RAIL) {
    02821     _thd.place_mode = VHM_RAIL;
    02822   } else {
    02823     _thd.place_mode = VHM_POINT;
    02824   }
    02825   SetTileSelectSize(1, 1);
    02826
    02827   /* and call the mouseup event. */
    02828   e.event = WE_PLACE_MOUSEUP;
    02829   e.we.place.pt = _thd.selend;
    02830   e.we.place.tile = TileVirtXY(e.we.place.pt.x, e.we.place.pt.y);
    02831   e.we.place.starttile = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
    02832   w->wndproc(w, &e);
    02833
    02834   return false;
    02835 }
    02836
    02837 void SetObjectToPlaceWnd(CursorID icon, SpriteID pal, ViewportHighlightMode mode, Window *w)
    02838 {
    02839   SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
    02840 }
    02841
    02842 #include "table/animcursors.h"
    02843
    02844 void SetObjectToPlace(CursorID icon, SpriteID pal, ViewportHighlightMode mode, WindowClass window_class, WindowNumber window_num)
    02845 {
    02846   Window *w;
    02847
    02848   /* undo clicking on button and drag & drop */
    02849   if (_thd.place_mode != VHM_NONE || _special_mouse_mode == WSM_DRAGDROP) {
    02850     w = FindWindowById(_thd.window_class, _thd.window_number);
    02851     if (w != NULL) CallWindowEventNP(w, WE_ABORT_PLACE_OBJ);
    02852   }
    02853
    02854   SetTileSelectSize(1, 1);
    02855
    02856   _thd.make_square_red = false;
    02857
    02858   if (mode == VHM_DRAG) { // VHM_DRAG is for dragdropping trains in the depot window
    02859     mode = VHM_NONE;
    02860     _special_mouse_mode = WSM_DRAGDROP;
    02861   } else {
    02862     _special_mouse_mode = WSM_NONE;
    02863   }
    02864
    02865   _thd.place_mode = mode;
    02866   _thd.window_class = window_class;
    02867   _thd.window_number = window_num;
    02868
    02869   if (mode == VHM_SPECIAL) // special tools, like tunnels or docks start with presizing mode
    02870     VpStartPreSizing();
    02871
    02872   if ( (int)icon < 0)
    02873     SetAnimatedMouseCursor(_animcursors[~icon]);
    02874   else
    02875     SetMouseCursor(icon, pal);
    02876 }
    02877
    02878 void ResetObjectToPlace()
    02879 {
    02880   SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, VHM_NONE, WC_MAIN_WINDOW, 0);
    02881 }