viewport.cpp

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

Generated on Wed Jul 15 20:36:04 2009 for OpenTTD by  doxygen 1.5.6