4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
11 * @file viewport.cpp Handling of all viewports.
14 * The in-game coordinate system looks like this *
30 * @defgroup vp_column_row Rows and columns in the viewport
32 * Columns are vertical sections of the viewport that are half a tile wide.
33 * The origin, i.e. column 0, is through the northern and southern most tile.
34 * This means that the column of e.g. Tile(0, 0) and Tile(100, 100) are in
35 * column number 0. The negative columns are towards the left of the screen,
36 * or towards the west, whereas the positive ones are towards respectively
38 * With half a tile wide is meant that the next column of tiles directly west
39 * or east of the centre line are respectively column -1 and 1. Their tile
40 * centers are only half a tile from the center of their adjoining tile when
41 * looking only at the X-coordinate.
60 * Rows are horizontal sections of the viewport, also half a tile wide.
61 * This time the nothern most tile on the map defines 0 and
62 * everything south of that has a positive number.
66 #include "core/math_func.hpp"
67 #include "core/smallvec_type.hpp"
68 #include "clear_map.h"
71 #include "smallmap_gui.h"
72 #include "smallmap_colours.h"
73 #include "table/tree_land.h"
74 #include "blitter/32bpp_base.hpp"
75 #include "landscape.h"
76 #include "viewport_func.h"
77 #include "station_base.h"
78 #include "waypoint_base.h"
80 #include "signs_base.h"
81 #include "signs_func.h"
82 #include "plans_base.h"
83 #include "plans_func.h"
84 #include "vehicle_base.h"
85 #include "vehicle_gui.h"
86 #include "blitter/factory.hpp"
87 #include "strings_func.h"
88 #include "zoom_func.h"
90 #include "overlay_cmd.h"
91 #include "vehicle_func.h"
92 #include "company_func.h"
93 #include "waypoint_func.h"
94 #include "window_func.h"
95 #include "tilehighlight_func.h"
97 #include "window_gui.h"
98 #include "linkgraph/linkgraph_gui.h"
99 #include "viewport_sprite_sorter.h"
100 #include "bridge_map.h"
101 #include "progress.h"
107 #include "depot_base.h"
108 #include "tunnelbridge_map.h"
110 #include "core/container_func.hpp"
112 #include "table/strings.h"
113 #include "table/string_colours.h"
115 #include "safeguards.h"
117 Point _tile_fract_coords
;
120 static const int MAX_TILE_EXTENT_LEFT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum left extent of tile relative to north corner.
121 static const int MAX_TILE_EXTENT_RIGHT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum right extent of tile relative to north corner.
122 static const int MAX_TILE_EXTENT_TOP
= ZOOM_LVL_BASE
* MAX_BUILDING_PIXELS
; ///< Maximum top extent of tile relative to north corner (not considering bridges).
123 static const int MAX_TILE_EXTENT_BOTTOM
= ZOOM_LVL_BASE
* (TILE_PIXELS
+ 2 * TILE_HEIGHT
); ///< Maximum bottom extent of tile relative to north corner (worst case: #SLOPE_STEEP_N).
125 struct StringSpriteToDraw
{
134 struct TileSpriteToDraw
{
137 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
138 int32 x
; ///< screen X coordinate of sprite
139 int32 y
; ///< screen Y coordinate of sprite
142 struct ChildScreenSpriteToDraw
{
145 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
148 int next
; ///< next child to draw (-1 at the end)
151 /** Enumeration of multi-part foundations */
152 enum FoundationPart
{
153 FOUNDATION_PART_NONE
= 0xFF, ///< Neither foundation nor groundsprite drawn yet.
154 FOUNDATION_PART_NORMAL
= 0, ///< First part (normal foundation or no foundation)
155 FOUNDATION_PART_HALFTILE
= 1, ///< Second part (halftile foundation)
160 * Mode of "sprite combining"
161 * @see StartSpriteCombine
163 enum SpriteCombineMode
{
164 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
165 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
166 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
169 typedef SmallVector
<TileSpriteToDraw
, 64> TileSpriteToDrawVector
;
170 typedef SmallVector
<StringSpriteToDraw
, 4> StringSpriteToDrawVector
;
171 typedef SmallVector
<ParentSpriteToDraw
, 64> ParentSpriteToDrawVector
;
172 typedef SmallVector
<ChildScreenSpriteToDraw
, 16> ChildScreenSpriteToDrawVector
;
174 typedef std::vector
<std::pair
<int, OrderType
> > RankOrderTypeList
;
175 typedef std::map
<TileIndex
, RankOrderTypeList
> RouteStepsMap
;
177 const uint max_rank_order_type_count
= 10;
186 * Snapping point for a track.
188 * Point where a track (rail/road/other) can be snapped to while selecting tracks with polyline
189 * tool (HT_POLY). Besides of x/y coordinates expressed in tile "units" it contains a set of
190 * allowed line directions.
192 struct LineSnapPoint
: Point
{
193 uint8 dirs
; ///< Allowed line directions, set of #Direction bits.
196 typedef SmallVector
<LineSnapPoint
, 4> LineSnapPoints
; ///< Set of snapping points
198 /** Coordinates of a polyline track made of 2 connected line segments. */
200 Point start
; ///< The point where the first segment starts (as given in LineSnapPoint).
201 Direction first_dir
; ///< Direction of the first line segment.
202 uint first_len
; ///< Length of the first segment - number of track pieces.
203 Direction second_dir
; ///< Direction of the second line segment.
204 uint second_len
; ///< Length of the second segment - number of track pieces.
207 /** Data structure storing rendering information */
208 struct ViewportDrawer
{
211 StringSpriteToDrawVector string_sprites_to_draw
;
212 TileSpriteToDrawVector tile_sprites_to_draw
;
213 ParentSpriteToDrawVector parent_sprites_to_draw
;
214 ParentSpriteToSortVector parent_sprites_to_sort
; ///< Parent sprite pointer array used for sorting
215 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
216 TunnelBridgeToMapVector tunnel_to_map
;
217 TunnelBridgeToMapVector bridge_to_map
;
221 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
223 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
224 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
225 int *last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
226 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
229 static void MarkViewportDirty(const ViewPort
* const vp
, int left
, int top
, int right
, int bottom
);
230 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
);
231 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
);
233 static DrawPixelInfo _dpi_for_text
;
234 static ViewportDrawer _vd
;
236 static std::vector
<ViewPort
*> _viewport_window_cache
;
238 RouteStepsMap _vp_route_steps
;
239 RouteStepsMap _vp_route_steps_last_mark_dirty
;
240 uint _vp_route_step_width
= 0;
241 uint _vp_route_step_height_top
= 0;
242 uint _vp_route_step_height_middle
= 0;
243 uint _vp_route_step_height_bottom
= 0;
244 SubSprite _vp_route_step_subsprite
;
246 struct DrawnPathRouteTileLine
{
251 bool operator==(const DrawnPathRouteTileLine
&other
) const
253 return std::tie(this->from_tile
, this->to_tile
, this->order_match
) == std::tie(other
.from_tile
, other
.to_tile
, other
.order_match
);
256 bool operator!=(const DrawnPathRouteTileLine
&other
) const
258 return !(*this == other
);
261 bool operator<(const DrawnPathRouteTileLine
&other
) const
263 return std::tie(this->from_tile
, this->to_tile
, this->order_match
) < std::tie(other
.from_tile
, other
.to_tile
, other
.order_match
);
267 std::vector
<DrawnPathRouteTileLine
> _vp_route_paths
;
268 std::vector
<DrawnPathRouteTileLine
> _vp_route_paths_last_mark_dirty
;
270 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
);
272 TileHighlightData _thd
;
273 static TileInfo
*_cur_ti
;
274 bool _draw_bounding_boxes
= false;
275 bool _draw_dirty_blocks
= false;
276 uint _dirty_block_colour
= 0;
277 static VpSpriteSorter _vp_sprite_sorter
= NULL
;
279 const byte
*_pal2trsp_remap_ptr
= NULL
;
281 static RailSnapMode _rail_snap_mode
= RSM_NO_SNAP
; ///< Type of rail track snapping (polyline tool).
282 static LineSnapPoints _tile_snap_points
; ///< Tile to which a rail track will be snapped to (polyline tool).
283 static LineSnapPoints _rail_snap_points
; ///< Set of points where a rail track will be snapped to (polyline tool).
284 static LineSnapPoint _current_snap_lock
; ///< Start point and direction at which selected track is locked on currently (while dragging in polyline mode).
286 static RailSnapMode
GetRailSnapMode();
287 static void SetRailSnapMode(RailSnapMode mode
);
288 static TileIndex
GetRailSnapTile();
289 static void SetRailSnapTile(TileIndex tile
);
291 static Point
MapXYZToViewport(const ViewPort
*vp
, int x
, int y
, int z
)
293 Point p
= RemapCoords(x
, y
, z
);
294 p
.x
-= vp
->virtual_width
/ 2;
295 p
.y
-= vp
->virtual_height
/ 2;
299 void DeleteWindowViewport(Window
*w
)
301 if (w
->viewport
== NULL
) return;
303 container_unordered_remove(_viewport_window_cache
, w
->viewport
);
304 delete w
->viewport
->overlay
;
310 * Initialize viewport of the window for use.
311 * @param w Window to use/display the viewport in
312 * @param x Offset of left edge of viewport with respect to left edge window \a w
313 * @param y Offset of top edge of viewport with respect to top edge window \a w
314 * @param width Width of the viewport
315 * @param height Height of the viewport
316 * @param follow_flags Flags controlling the viewport.
317 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
318 * - If bit 31 is clear, it is a #TileIndex.
319 * @param zoom Zoomlevel to display
321 void InitializeWindowViewport(Window
*w
, int x
, int y
,
322 int width
, int height
, uint32 follow_flags
, ZoomLevel zoom
)
324 assert(w
->viewport
== NULL
);
326 ViewportData
*vp
= new ViewportData();
329 vp
->left
= x
+ w
->left
;
330 vp
->top
= y
+ w
->top
;
334 vp
->zoom
= static_cast<ZoomLevel
>(Clamp(zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
336 vp
->virtual_left
= 0;
338 vp
->virtual_width
= ScaleByZoom(width
, zoom
);
339 vp
->virtual_height
= ScaleByZoom(height
, zoom
);
341 vp
->map_type
= VPMT_BEGIN
;
345 if (follow_flags
& 0x80000000) {
348 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
349 veh
= Vehicle::Get(vp
->follow_vehicle
);
350 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
351 MarkAllRoutePathsDirty(veh
);
352 MarkAllRouteStepsDirty(veh
);
354 uint x
= TileX(follow_flags
) * TILE_SIZE
;
355 uint y
= TileY(follow_flags
) * TILE_SIZE
;
357 vp
->follow_vehicle
= INVALID_VEHICLE
;
358 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopePixelZ(x
, y
));
361 vp
->scrollpos_x
= pt
.x
;
362 vp
->scrollpos_y
= pt
.y
;
363 vp
->dest_scrollpos_x
= pt
.x
;
364 vp
->dest_scrollpos_y
= pt
.y
;
367 _viewport_window_cache
.push_back(vp
);
370 static Point _vp_move_offs
;
372 static void DoSetViewportPosition(const Window
*w
, int left
, int top
, int width
, int height
)
374 FOR_ALL_WINDOWS_FROM_BACK_FROM(w
, w
) {
375 if (left
+ width
> w
->left
&&
376 w
->left
+ w
->width
> left
&&
377 top
+ height
> w
->top
&&
378 w
->top
+ w
->height
> top
) {
380 if (left
< w
->left
) {
381 DoSetViewportPosition(w
, left
, top
, w
->left
- left
, height
);
382 DoSetViewportPosition(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
386 if (left
+ width
> w
->left
+ w
->width
) {
387 DoSetViewportPosition(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
388 DoSetViewportPosition(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
393 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
- top
));
394 DoSetViewportPosition(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
398 if (top
+ height
> w
->top
+ w
->height
) {
399 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
400 DoSetViewportPosition(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
409 int xo
= _vp_move_offs
.x
;
410 int yo
= _vp_move_offs
.y
;
412 if (abs(xo
) >= width
|| abs(yo
) >= height
) {
414 RedrawScreenRect(left
, top
, left
+ width
, top
+ height
);
418 GfxScroll(left
, top
, width
, height
, xo
, yo
);
421 RedrawScreenRect(left
, top
, xo
+ left
, top
+ height
);
425 RedrawScreenRect(left
+ width
+ xo
, top
, left
+ width
, top
+ height
);
430 RedrawScreenRect(left
, top
, width
+ left
, top
+ yo
);
432 RedrawScreenRect(left
, top
+ height
+ yo
, width
+ left
, top
+ height
);
437 static void SetViewportPosition(Window
*w
, int x
, int y
)
439 ViewPort
*vp
= w
->viewport
;
440 int old_left
= vp
->virtual_left
;
441 int old_top
= vp
->virtual_top
;
443 int left
, top
, width
, height
;
445 vp
->virtual_left
= x
;
448 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
449 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
451 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
452 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
453 x
= UnScaleByZoomLower(x
, vp
->zoom
);
454 y
= UnScaleByZoomLower(y
, vp
->zoom
);
459 if (old_top
== 0 && old_left
== 0) return;
461 _vp_move_offs
.x
= old_left
;
462 _vp_move_offs
.y
= old_top
;
474 i
= left
+ width
- _screen
.width
;
475 if (i
>= 0) width
-= i
;
483 i
= top
+ height
- _screen
.height
;
484 if (i
>= 0) height
-= i
;
486 if (height
> 0) DoSetViewportPosition(w
->z_front
, left
, top
, width
, height
);
491 * Is a xy position inside the viewport of the window?
492 * @param w Window to examine its viewport
493 * @param x X coordinate of the xy position
494 * @param y Y coordinate of the xy position
495 * @return Pointer to the viewport if the xy position is in the viewport of the window,
496 * otherwise \c NULL is returned.
498 ViewPort
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
500 ViewPort
*vp
= w
->viewport
;
503 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
504 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
511 * Translate screen coordinate in a viewport to a tile coordinate
512 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
513 * @param x Screen x coordinate
514 * @param y Screen y coordinate
515 * @param clamp_to_map Clamp the coordinate outside of the map to the closest tile within the map.
516 * @return Tile coordinate
518 Point
TranslateXYToTileCoord(const ViewPort
*vp
, int x
, int y
, bool clamp_to_map
)
524 if ( (uint
)(x
-= vp
->left
) >= (uint
)vp
->width
||
525 (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) {
530 x
= (ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
) >> (2 + ZOOM_LVL_SHIFT
);
531 y
= (ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
) >> (1 + ZOOM_LVL_SHIFT
);
537 /* Bring the coordinates near to a valid range. This is mostly due to the
538 * tiles on the north side of the map possibly being drawn too high due to
539 * the extra height levels. So at the top we allow a number of extra tiles.
540 * This number is based on the tile height and pixels. */
541 int extra_tiles
= CeilDiv(_settings_game
.construction
.max_heightlevel
* TILE_HEIGHT
, TILE_PIXELS
);
542 a
= Clamp(a
, -extra_tiles
* TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1);
543 b
= Clamp(b
, -extra_tiles
* TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1);
546 /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
547 * Now find the Z-world coordinate by fix point iteration.
548 * This is a bit tricky because the tile height is non-continuous at foundations.
549 * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
550 * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
551 * So give it a z-malus of 4 in the first iterations.
555 int min_coord
= _settings_game
.construction
.freeform_edges
? TILE_SIZE
: 0;
557 for (int i
= 0; i
< 5; i
++) z
= GetSlopePixelZ(Clamp(a
+ max(z
, 4) - 4, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ max(z
, 4) - 4, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
558 for (int malus
= 3; malus
> 0; malus
--) z
= GetSlopePixelZ(Clamp(a
+ max(z
, malus
) - malus
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ max(z
, malus
) - malus
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
559 for (int i
= 0; i
< 5; i
++) z
= GetSlopePixelZ(Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
562 pt
.x
= Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1);
563 pt
.y
= Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1);
572 /* When used for zooming, check area below current coordinates (x,y)
573 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
574 * when you just want the tile, make x = zoom_x and y = zoom_y */
575 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
581 if ( (w
= FindWindowFromPt(x
, y
)) != NULL
&&
582 (vp
= IsPtInWindowViewport(w
, x
, y
)) != NULL
)
583 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
589 Point
GetTileBelowCursor()
591 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
595 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
598 ViewPort
*vp
= w
->viewport
;
601 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
602 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
604 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
605 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
607 /* Get the tile below the cursor and center on the zoomed-out center */
608 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
612 * Update the status of the zoom-buttons according to the zoom-level
613 * of the viewport. This will update their status and invalidate accordingly
614 * @param w Window pointer to the window that has the zoom buttons
615 * @param vp pointer to the viewport whose zoom-level the buttons represent
616 * @param widget_zoom_in widget index for window with zoom-in button
617 * @param widget_zoom_out widget index for window with zoom-out button
619 void HandleZoomMessage(Window
*w
, const ViewPort
*vp
, byte widget_zoom_in
, byte widget_zoom_out
)
621 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
<= _settings_client
.gui
.zoom_min
);
622 w
->SetWidgetDirty(widget_zoom_in
);
624 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
>= _settings_client
.gui
.zoom_max
);
625 w
->SetWidgetDirty(widget_zoom_out
);
629 * Schedules a tile sprite for drawing.
631 * @param image the image to draw.
632 * @param pal the provided palette.
633 * @param x position x (world coordinates) of the sprite.
634 * @param y position y (world coordinates) of the sprite.
635 * @param z position z (world coordinates) of the sprite.
636 * @param sub Only draw a part of the sprite.
637 * @param extra_offs_x Pixel X offset for the sprite position.
638 * @param extra_offs_y Pixel Y offset for the sprite position.
640 static void AddTileSpriteToDraw(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
= NULL
, int extra_offs_x
= 0, int extra_offs_y
= 0)
642 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
644 TileSpriteToDraw
*ts
= _vd
.tile_sprites_to_draw
.Append();
648 Point pt
= RemapCoords(x
, y
, z
);
649 ts
->x
= pt
.x
+ extra_offs_x
;
650 ts
->y
= pt
.y
+ extra_offs_y
;
654 * Adds a child sprite to the active foundation.
656 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
658 * @param image the image to draw.
659 * @param pal the provided palette.
660 * @param sub Only draw a part of the sprite.
661 * @param foundation_part Foundation part.
662 * @param extra_offs_x Pixel X offset for the sprite position.
663 * @param extra_offs_y Pixel Y offset for the sprite position.
665 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
667 assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
668 assert(_vd
.foundation
[foundation_part
] != -1);
669 Point offs
= _vd
.foundation_offset
[foundation_part
];
671 /* Change the active ChildSprite list to the one of the foundation */
672 int *old_child
= _vd
.last_child
;
673 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
675 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
, false);
677 /* Switch back to last ChildSprite list */
678 _vd
.last_child
= old_child
;
682 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
683 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
685 * @param image the image to draw.
686 * @param pal the provided palette.
687 * @param x position x (world coordinates) of the sprite relative to current tile.
688 * @param y position y (world coordinates) of the sprite relative to current tile.
689 * @param z position z (world coordinates) of the sprite relative to current tile.
690 * @param sub Only draw a part of the sprite.
691 * @param extra_offs_x Pixel X offset for the sprite position.
692 * @param extra_offs_y Pixel Y offset for the sprite position.
694 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
696 /* Switch to first foundation part, if no foundation was drawn */
697 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
699 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
700 Point pt
= RemapCoords(x
, y
, z
);
701 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
* ZOOM_LVL_BASE
, pt
.y
+ extra_offs_y
* ZOOM_LVL_BASE
);
703 AddTileSpriteToDraw(image
, pal
, _cur_ti
->x
+ x
, _cur_ti
->y
+ y
, _cur_ti
->z
+ z
, sub
, extra_offs_x
* ZOOM_LVL_BASE
, extra_offs_y
* ZOOM_LVL_BASE
);
708 * Draws a ground sprite for the current tile.
709 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
711 * @param image the image to draw.
712 * @param pal the provided palette.
713 * @param sub Only draw a part of the sprite.
714 * @param extra_offs_x Pixel X offset for the sprite position.
715 * @param extra_offs_y Pixel Y offset for the sprite position.
717 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
719 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
723 * Called when a foundation has been drawn for the current tile.
724 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
726 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
727 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
729 void OffsetGroundSprite(int x
, int y
)
731 /* Switch to next foundation part */
732 switch (_vd
.foundation_part
) {
733 case FOUNDATION_PART_NONE
:
734 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
736 case FOUNDATION_PART_NORMAL
:
737 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
739 default: NOT_REACHED();
742 /* _vd.last_child == NULL if foundation sprite was clipped by the viewport bounds */
743 if (_vd
.last_child
!= NULL
) _vd
.foundation
[_vd
.foundation_part
] = _vd
.parent_sprites_to_draw
.Length() - 1;
745 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
* ZOOM_LVL_BASE
;
746 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
* ZOOM_LVL_BASE
;
747 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
751 * Adds a child sprite to a parent sprite.
752 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
754 * @param image the image to draw.
755 * @param pal the provided palette.
756 * @param x position x of the sprite.
757 * @param y position y of the sprite.
758 * @param z position z of the sprite.
759 * @param sub Only draw a part of the sprite.
761 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, int z
, const SubSprite
*sub
)
763 Point pt
= RemapCoords(x
, y
, z
);
764 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
766 if (pt
.x
+ spr
->x_offs
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
767 pt
.x
+ spr
->x_offs
+ spr
->width
<= _vd
.dpi
.left
||
768 pt
.y
+ spr
->y_offs
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
769 pt
.y
+ spr
->y_offs
+ spr
->height
<= _vd
.dpi
.top
)
772 const ParentSpriteToDraw
*pstd
= _vd
.parent_sprites_to_draw
.End() - 1;
773 AddChildSpriteScreen(image
, pal
, pt
.x
- pstd
->left
, pt
.y
- pstd
->top
, false, sub
, false);
777 * Draw a (transparent) sprite at given coordinates with a given bounding box.
778 * The bounding box extends from (x + bb_offset_x, y + bb_offset_y, z + bb_offset_z) to (x + w - 1, y + h - 1, z + dz - 1), both corners included.
779 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
781 * @note Bounding boxes are normally specified with bb_offset_x = bb_offset_y = bb_offset_z = 0. The extent of the bounding box in negative direction is
782 * defined by the sprite offset in the grf file.
783 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
785 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
787 * @param image the image to combine and draw,
788 * @param pal the provided palette,
789 * @param x position X (world) of the sprite,
790 * @param y position Y (world) of the sprite,
791 * @param w bounding box extent towards positive X (world),
792 * @param h bounding box extent towards positive Y (world),
793 * @param dz bounding box extent towards positive Z (world),
794 * @param z position Z (world) of the sprite,
795 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
796 * @param bb_offset_x bounding box extent towards negative X (world),
797 * @param bb_offset_y bounding box extent towards negative Y (world),
798 * @param bb_offset_z bounding box extent towards negative Z (world)
799 * @param sub Only draw a part of the sprite.
801 void AddSortableSpriteToDraw(SpriteID image
, PaletteID 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
)
803 int32 left
, right
, top
, bottom
;
805 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
807 /* make the sprites transparent with the right palette */
809 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
810 pal
= PALETTE_TO_TRANSPARENT
;
813 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
814 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
818 _vd
.last_child
= NULL
;
820 Point pt
= RemapCoords(x
, y
, z
);
821 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
823 /* Compute screen extents of sprite */
824 if (image
== SPR_EMPTY_BOUNDING_BOX
) {
825 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
826 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
827 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
828 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
830 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
831 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
832 right
= (pt
.x
+ spr
->width
);
833 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
834 bottom
= (pt
.y
+ spr
->height
);
837 if (_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
)) {
838 /* Compute maximal extents of sprite and its bounding box */
839 left
= min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
840 right
= max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
841 top
= min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
842 bottom
= max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
845 /* Do not add the sprite to the viewport, if it is outside */
846 if (left
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
847 right
<= _vd
.dpi
.left
||
848 top
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
849 bottom
<= _vd
.dpi
.top
) {
853 ParentSpriteToDraw
*ps
= _vd
.parent_sprites_to_draw
.Append();
863 ps
->xmin
= x
+ bb_offset_x
;
864 ps
->xmax
= x
+ max(bb_offset_x
, w
) - 1;
866 ps
->ymin
= y
+ bb_offset_y
;
867 ps
->ymax
= y
+ max(bb_offset_y
, h
) - 1;
869 ps
->zmin
= z
+ bb_offset_z
;
870 ps
->zmax
= z
+ max(bb_offset_z
, dz
) - 1;
872 ps
->comparison_done
= false;
873 ps
->first_child
= -1;
875 _vd
.last_child
= &ps
->first_child
;
877 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
881 * Starts a block of sprites, which are "combined" into a single bounding box.
883 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
884 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
885 * the following sprites will be child sprites to that one.
888 * - The drawing order is definite. No other sprites will be sorted between those of the block.
889 * - You have to provide a valid bounding box for all sprites,
890 * as you won't know which one is the first non-clipped one.
891 * Preferable you use the same bounding box for all.
892 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
894 * The block is terminated by #EndSpriteCombine.
896 * You cannot nest "combined" blocks.
898 void StartSpriteCombine()
900 assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
901 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
905 * Terminates a block of sprites started by #StartSpriteCombine.
906 * Take a look there for details.
908 void EndSpriteCombine()
910 assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
911 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
915 * Check if the parameter "check" is inside the interval between
916 * begin and end, including both begin and end.
917 * @note Whether \c begin or \c end is the biggest does not matter.
918 * This method will account for that.
919 * @param begin The begin of the interval.
920 * @param end The end of the interval.
921 * @param check The value to check.
923 static bool IsInRangeInclusive(int begin
, int end
, int check
)
925 if (begin
> end
) Swap(begin
, end
);
926 return begin
<= check
&& check
<= end
;
930 * Checks whether a point is inside the selected rectangle given by _thd.size, _thd.pos and _thd.diagonal
931 * @param x The x coordinate of the point to be checked.
932 * @param y The y coordinate of the point to be checked.
933 * @return True if the point is inside the rectangle, else false.
935 static bool IsInsideSelectedRectangle(int x
, int y
)
937 if (!_thd
.diagonal
) {
938 return IsInsideBS(x
, _thd
.pos
.x
, _thd
.size
.x
) && IsInsideBS(y
, _thd
.pos
.y
, _thd
.size
.y
);
941 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
942 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
943 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
944 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
946 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
947 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
951 * Add a child sprite to a parent sprite.
953 * @param image the image to draw.
954 * @param pal the provided palette.
955 * @param x sprite x-offset (screen coordinates) relative to parent sprite.
956 * @param y sprite y-offset (screen coordinates) relative to parent sprite.
957 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
958 * @param sub Only draw a part of the sprite.
960 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
, bool scale
)
962 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
964 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
965 if (_vd
.last_child
== NULL
) return;
967 /* make the sprites transparent with the right palette */
969 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
970 pal
= PALETTE_TO_TRANSPARENT
;
973 *_vd
.last_child
= _vd
.child_screen_sprites_to_draw
.Length();
975 ChildScreenSpriteToDraw
*cs
= _vd
.child_screen_sprites_to_draw
.Append();
979 cs
->x
= scale
? x
* ZOOM_LVL_BASE
: x
;
980 cs
->y
= scale
? y
* ZOOM_LVL_BASE
: y
;
983 /* Append the sprite to the active ChildSprite list.
984 * If the active ParentSprite is a foundation, update last_foundation_child as well.
985 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
986 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = &cs
->next
;
987 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = &cs
->next
;
988 _vd
.last_child
= &cs
->next
;
991 static void AddStringToDraw(int x
, int y
, StringID string
, uint64 params_1
, uint64 params_2
, Colours colour
, uint16 width
)
994 StringSpriteToDraw
*ss
= _vd
.string_sprites_to_draw
.Append();
998 ss
->params
[0] = params_1
;
999 ss
->params
[1] = params_2
;
1001 ss
->colour
= colour
;
1006 * Draws sprites between ground sprite and everything above.
1008 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
1010 * @param image the image to draw.
1011 * @param pal the provided palette.
1012 * @param ti TileInfo Tile that is being drawn
1013 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
1014 * @param foundation_part Foundation part the sprite belongs to.
1016 static void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
)
1018 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
1019 if (_vd
.foundation
[foundation_part
] == -1) {
1020 /* draw on real ground */
1021 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
);
1023 /* draw on top of foundation */
1024 AddChildSpriteToFoundation(image
, pal
, NULL
, foundation_part
, 0, -z_offset
* ZOOM_LVL_BASE
);
1029 * Draws a selection rectangle on a tile.
1031 * @param ti TileInfo Tile that is being drawn
1032 * @param pal Palette to apply.
1034 static void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
1036 if (!IsValidTile(ti
->tile
)) return;
1039 if (IsHalftileSlope(ti
->tileh
)) {
1040 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1041 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
1042 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
1044 Corner opposite_corner
= OppositeCorner(halftile_corner
);
1045 if (IsSteepSlope(ti
->tileh
)) {
1046 sel
= SPR_HALFTILE_SELECTION_DOWN
;
1048 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
1050 sel
+= opposite_corner
;
1052 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
1054 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
1057 static HighLightStyle
GetPartOfAutoLine(int px
, int py
, const Point
&selstart
, const Point
&selend
, HighLightStyle dir
)
1059 if (!IsInRangeInclusive(selstart
.x
& ~TILE_UNIT_MASK
, selend
.x
& ~TILE_UNIT_MASK
, px
)) return HT_DIR_END
;
1060 if (!IsInRangeInclusive(selstart
.y
& ~TILE_UNIT_MASK
, selend
.y
& ~TILE_UNIT_MASK
, py
)) return HT_DIR_END
;
1062 px
-= selstart
.x
& ~TILE_UNIT_MASK
;
1063 py
-= selstart
.y
& ~TILE_UNIT_MASK
;
1066 case HT_DIR_X
: return (py
== 0) ? HT_DIR_X
: HT_DIR_END
;
1067 case HT_DIR_Y
: return (px
== 0) ? HT_DIR_Y
: HT_DIR_END
;
1068 case HT_DIR_HU
: return (px
== -py
) ? HT_DIR_HU
: (px
== -py
- (int)TILE_SIZE
) ? HT_DIR_HL
: HT_DIR_END
;
1069 case HT_DIR_HL
: return (px
== -py
) ? HT_DIR_HL
: (px
== -py
+ (int)TILE_SIZE
) ? HT_DIR_HU
: HT_DIR_END
;
1070 case HT_DIR_VL
: return (px
== py
) ? HT_DIR_VL
: (px
== py
+ (int)TILE_SIZE
) ? HT_DIR_VR
: HT_DIR_END
;
1071 case HT_DIR_VR
: return (px
== py
) ? HT_DIR_VR
: (px
== py
- (int)TILE_SIZE
) ? HT_DIR_VL
: HT_DIR_END
;
1072 default: NOT_REACHED(); break;
1078 #include "table/autorail.h"
1081 * Draws autorail highlights.
1083 * @param *ti TileInfo Tile that is being drawn
1084 * @param autorail_type \c HT_DIR_XXX, offset into _AutorailTilehSprite[][]
1085 * @param pal Palette to use, -1 to autodetect
1087 static void DrawAutorailSelection(const TileInfo
*ti
, HighLightStyle autorail_type
, PaletteID pal
= -1)
1092 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1093 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
1094 if (IsHalftileSlope(ti
->tileh
)) {
1095 static const HighLightStyle _lower_rail
[CORNER_END
] = { HT_DIR_VR
, HT_DIR_HU
, HT_DIR_VL
, HT_DIR_HL
}; // CORNER_W, CORNER_S, CORNER_E, CORNER_N
1096 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1097 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
1098 foundation_part
= FOUNDATION_PART_HALFTILE
;
1099 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
1100 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
1104 assert(autorail_type
< HT_DIR_END
);
1105 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
1107 image
= SPR_AUTORAIL_BASE
+ offset
;
1108 if (pal
== (PaletteID
)-1) pal
= _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
;
1110 image
= SPR_AUTORAIL_BASE
- offset
;
1111 if (pal
== (PaletteID
)-1) pal
= PALETTE_SEL_TILE_RED
;
1114 DrawSelectionSprite(image
, pal
, ti
, 7, foundation_part
);
1118 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
1119 * @param *ti TileInfo Tile that is being drawn
1121 static void DrawTileSelection(const TileInfo
*ti
)
1123 /* Draw a red error square? */
1124 bool is_redsq
= _thd
.redsq
== ti
->tile
;
1125 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
1127 switch (_thd
.drawstyle
& HT_DRAG_MASK
) {
1128 default: break; // No tile selection active?
1132 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1133 DrawTileSelectionRect(ti
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
1134 } else if (_thd
.outersize
.x
> 0 &&
1135 /* Check if it's inside the outer area? */
1136 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1137 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1138 /* Draw a blue rect. */
1139 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1145 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1146 /* Figure out the Z coordinate for the single dot. */
1148 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1149 if (ti
->tileh
& SLOPE_N
) {
1151 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
1153 if (IsHalftileSlope(ti
->tileh
)) {
1154 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1155 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
1156 if (halftile_corner
!= CORNER_S
) {
1157 foundation_part
= FOUNDATION_PART_HALFTILE
;
1158 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
1161 DrawSelectionSprite(_cur_dpi
->zoom
<= ZOOM_LVL_DETAIL
? SPR_DOT
: SPR_DOT_SMALL
, PAL_NONE
, ti
, z
, foundation_part
);
1166 if (ti
->tile
== TileVirtXY(_thd
.pos
.x
, _thd
.pos
.y
)) {
1167 assert((_thd
.drawstyle
& HT_DIR_MASK
) < HT_DIR_END
);
1168 DrawAutorailSelection(ti
, _thd
.drawstyle
& HT_DIR_MASK
);
1173 HighLightStyle type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart
, _thd
.selend
, _thd
.drawstyle
& HT_DIR_MASK
);
1174 if (type
< HT_DIR_END
) {
1175 DrawAutorailSelection(ti
, type
);
1176 } else if (_thd
.dir2
< HT_DIR_END
) {
1177 type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart2
, _thd
.selend2
, _thd
.dir2
);
1178 if (type
< HT_DIR_END
) DrawAutorailSelection(ti
, type
, PALETTE_SEL_TILE_BLUE
);
1186 * Returns the y coordinate in the viewport coordinate system where the given
1188 * @param tile Any tile.
1189 * @return The viewport y coordinate where the tile is painted.
1191 static int GetViewportY(Point tile
)
1193 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1194 return (tile
.y
* (int)(TILE_PIXELS
/ 2) + tile
.x
* (int)(TILE_PIXELS
/ 2) - TilePixelHeightOutsideMap(tile
.x
, tile
.y
)) << ZOOM_LVL_SHIFT
;
1198 * Add the landscape to the viewport, i.e. all ground tiles and buildings.
1200 static void ViewportAddLandscape()
1202 assert(_vd
.dpi
.top
<= _vd
.dpi
.top
+ _vd
.dpi
.height
);
1203 assert(_vd
.dpi
.left
<= _vd
.dpi
.left
+ _vd
.dpi
.width
);
1205 Point upper_left
= InverseRemapCoords(_vd
.dpi
.left
, _vd
.dpi
.top
);
1206 Point upper_right
= InverseRemapCoords(_vd
.dpi
.left
+ _vd
.dpi
.width
, _vd
.dpi
.top
);
1208 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1211 * x = (row - column) / 2
1212 * y = (row + column) / 2
1213 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1216 /* Columns overlap with neighbouring columns by a half tile.
1217 * - Left column is column of upper_left (rounded down) and one column to the left.
1218 * - Right column is column of upper_right (rounded up) and one column to the right.
1219 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1221 int left_column
= (upper_left
.y
- upper_left
.x
) / (int)TILE_SIZE
- 2;
1222 int right_column
= (upper_right
.y
- upper_right
.x
) / (int)TILE_SIZE
+ 2;
1224 int potential_bridge_height
= ZOOM_LVL_BASE
* TILE_HEIGHT
* _settings_game
.construction
.max_bridge_height
;
1226 /* Rows overlap with neighbouring rows by a half tile.
1227 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1228 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1230 int row
= (upper_left
.x
+ upper_left
.y
) / (int)TILE_SIZE
- 2;
1231 bool last_row
= false;
1232 for (; !last_row
; row
++) {
1234 for (int column
= left_column
; column
<= right_column
; column
++) {
1235 /* Valid row/column? */
1236 if ((row
+ column
) % 2 != 0) continue;
1239 tilecoord
.x
= (row
- column
) / 2;
1240 tilecoord
.y
= (row
+ column
) / 2;
1241 assert(column
== tilecoord
.y
- tilecoord
.x
);
1242 assert(row
== tilecoord
.y
+ tilecoord
.x
);
1246 _cur_ti
= &tile_info
;
1247 tile_info
.x
= tilecoord
.x
* TILE_SIZE
; // FIXME tile_info should use signed integers
1248 tile_info
.y
= tilecoord
.y
* TILE_SIZE
;
1250 if (IsInsideBS(tilecoord
.x
, 0, MapSizeX()) && IsInsideBS(tilecoord
.y
, 0, MapSizeY())) {
1251 /* This includes the south border at MapMaxX / MapMaxY. When terraforming we still draw tile selections there. */
1252 tile_info
.tile
= TileXY(tilecoord
.x
, tilecoord
.y
);
1253 tile_type
= GetTileType(tile_info
.tile
);
1255 tile_info
.tile
= INVALID_TILE
;
1256 tile_type
= MP_VOID
;
1259 if (tile_type
!= MP_VOID
) {
1260 /* We are inside the map => paint landscape. */
1261 tile_info
.tileh
= GetTilePixelSlope(tile_info
.tile
, &tile_info
.z
);
1263 /* We are outside the map => paint black. */
1264 tile_info
.tileh
= GetTilePixelSlopeOutsideMap(tilecoord
.x
, tilecoord
.y
, &tile_info
.z
);
1267 int viewport_y
= GetViewportY(tilecoord
);
1269 if (viewport_y
+ MAX_TILE_EXTENT_BOTTOM
< _vd
.dpi
.top
) {
1270 /* The tile in this column is not visible yet.
1271 * Tiles in other columns may be visible, but we need more rows in any case. */
1276 int min_visible_height
= viewport_y
- (_vd
.dpi
.top
+ _vd
.dpi
.height
);
1277 bool tile_visible
= min_visible_height
<= 0;
1279 if (tile_type
!= MP_VOID
) {
1280 /* Is tile with buildings visible? */
1281 if (min_visible_height
< MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1283 if (IsBridgeAbove(tile_info
.tile
)) {
1284 /* Is the bridge visible? */
1285 TileIndex bridge_tile
= GetNorthernBridgeEnd(tile_info
.tile
);
1286 int bridge_height
= ZOOM_LVL_BASE
* (GetBridgePixelHeight(bridge_tile
) - TilePixelHeight(tile_info
.tile
));
1287 if (min_visible_height
< bridge_height
+ MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1290 /* Would a higher bridge on a more southern tile be visible?
1291 * If yes, we need to loop over more rows to possibly find one. */
1292 if (min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1294 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1295 * so we need to loop over more rows to possibly find one. */
1296 if ((tilecoord
.x
<= 0 || tilecoord
.y
<= 0) && min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1301 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1302 _vd
.foundation
[0] = -1;
1303 _vd
.foundation
[1] = -1;
1304 _vd
.last_foundation_child
[0] = NULL
;
1305 _vd
.last_foundation_child
[1] = NULL
;
1307 _tile_type_procs
[tile_type
]->draw_tile_proc(&tile_info
);
1308 if (tile_info
.tile
!= INVALID_TILE
) {
1309 DrawTileSelection(&tile_info
);
1310 DrawTileZoning(&tile_info
);
1318 * Add a string to draw in the viewport
1319 * @param dpi current viewport area
1320 * @param small_from Zoomlevel from when the small font should be used
1321 * @param sign sign position and dimension
1322 * @param string_normal String for normal and 2x zoom level
1323 * @param string_small String for 4x and 8x zoom level
1324 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1325 * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1327 void ViewportAddString(const DrawPixelInfo
*dpi
, ZoomLevel small_from
, const ViewportSign
*sign
, StringID string_normal
, StringID string_small
, StringID string_small_shadow
, uint64 params_1
, uint64 params_2
, Colours colour
)
1329 bool small
= dpi
->zoom
>= small_from
;
1331 int left
= dpi
->left
;
1333 int right
= left
+ dpi
->width
;
1334 int bottom
= top
+ dpi
->height
;
1336 int sign_height
= ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1337 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1339 if (bottom
< sign
->top
||
1340 top
> sign
->top
+ sign_height
||
1341 right
< sign
->center
- sign_half_width
||
1342 left
> sign
->center
+ sign_half_width
) {
1347 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1349 int shadow_offset
= 0;
1350 if (string_small_shadow
!= STR_NULL
) {
1352 AddStringToDraw(sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
);
1354 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1355 colour
, sign
->width_small
| 0x8000);
1359 struct ViewportAddStringApproxBoundsChecker
{
1363 ViewportAddStringApproxBoundsChecker(const DrawPixelInfo
*dpi
)
1365 this->top
= dpi
->top
- ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1366 this->bottom
= dpi
->top
+ dpi
->height
;
1369 bool IsSignMaybeOnScreen(const ViewportSign
*sign
) const
1371 return !(this->bottom
< sign
->top
|| this->top
> sign
->top
);
1375 static void ViewportAddTownNames(DrawPixelInfo
*dpi
)
1377 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) || _game_mode
== GM_MENU
) return;
1379 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1383 if (!checker
.IsSignMaybeOnScreen(&t
->cache
.sign
)) continue;
1384 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &t
->cache
.sign
,
1385 t
->Label(), t
->SmallLabel(), STR_VIEWPORT_TOWN_TINY_BLACK
,
1386 t
->index
, t
->cache
.population
);
1392 * Check whether to show a sign above a given station/waypoint.
1393 * @param st Pointer to the station/waypoint.
1394 * @return Whether the sign should be shown.
1396 static bool IsStationSignVisible(const BaseStation
*st
)
1398 /* Don't draw if the display options are disabled */
1399 return HasBit(_display_opt
, Station::IsExpected(st
) ? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
) &&
1400 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1401 (HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) || _local_company
== st
->owner
|| st
->owner
== OWNER_NONE
);
1405 static void ViewportAddStationNames(DrawPixelInfo
*dpi
)
1407 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || _game_mode
== GM_MENU
) return;
1409 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1411 const BaseStation
*st
;
1412 FOR_ALL_BASE_STATIONS(st
) {
1413 if (!IsStationSignVisible(st
)) continue;
1415 StringID str
= Station::IsExpected(st
) ? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
;
1417 if (!checker
.IsSignMaybeOnScreen(&st
->sign
)) continue;
1419 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &st
->sign
, str
, str
+ 1, STR_NULL
, st
->index
, st
->facilities
,
1420 (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
1425 static void ViewportAddSigns(DrawPixelInfo
*dpi
)
1427 /* Signs are turned off or are invisible */
1428 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
)) return;
1430 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1434 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1435 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1436 * companies can leave OWNER_NONE signs after them. */
1437 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
1439 if (!checker
.IsSignMaybeOnScreen(&si
->sign
)) continue;
1441 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &si
->sign
,
1443 (IsTransparencySet(TO_SIGNS
) || si
->owner
== OWNER_DEITY
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
1444 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: (si
->owner
== OWNER_DEITY
? INVALID_COLOUR
: _company_colours
[si
->owner
]));
1449 * Update the position of the viewport sign.
1450 * @param center the (preferred) center of the viewport sign
1451 * @param top the new top of the sign
1452 * @param str the string to show in the sign
1453 * @param str_small the string to show when zoomed out. STR_NULL means same as \a str
1455 void ViewportSign::UpdatePosition(int center
, int top
, StringID str
, StringID str_small
)
1457 if (this->width_normal
!= 0) this->MarkDirty();
1461 char buffer
[DRAW_STRING_BUFFER
];
1463 GetString(buffer
, str
, lastof(buffer
));
1464 this->width_normal
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
).width
, 2) + VPSM_RIGHT
;
1465 this->center
= center
;
1467 /* zoomed out version */
1468 if (str_small
!= STR_NULL
) {
1469 GetString(buffer
, str_small
, lastof(buffer
));
1471 this->width_small
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + VPSM_RIGHT
;
1477 * Mark the sign dirty in all viewports.
1478 * @param maxzoom Maximum %ZoomLevel at which the text is visible.
1482 void ViewportSign::MarkDirty(ZoomLevel maxzoom
) const
1484 Rect zoomlevels
[ZOOM_LVL_COUNT
];
1486 for (ZoomLevel zoom
= ZOOM_LVL_BEGIN
; zoom
!= ZOOM_LVL_END
; zoom
++) {
1487 /* FIXME: This doesn't switch to width_small when appropriate. */
1488 zoomlevels
[zoom
].left
= this->center
- ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1489 zoomlevels
[zoom
].top
= this->top
- ScaleByZoom(1, zoom
);
1490 zoomlevels
[zoom
].right
= this->center
+ ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1491 zoomlevels
[zoom
].bottom
= this->top
+ ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
+ 1, zoom
);
1494 for (ViewPort
*vp
: _viewport_window_cache
) {
1495 if (vp
->zoom
<= maxzoom
) {
1496 Rect
&zl
= zoomlevels
[vp
->zoom
];
1497 MarkViewportDirty(vp
, zl
.left
, zl
.top
, zl
.right
, zl
.bottom
);
1502 static void ViewportDrawTileSprites(const TileSpriteToDrawVector
*tstdv
)
1504 const TileSpriteToDraw
*tsend
= tstdv
->End();
1505 for (const TileSpriteToDraw
*ts
= tstdv
->Begin(); ts
!= tsend
; ++ts
) {
1506 DrawSpriteViewport(ts
->image
, ts
->pal
, ts
->x
, ts
->y
, ts
->sub
);
1510 /** This fallback sprite checker always exists. */
1511 static bool ViewportSortParentSpritesChecker()
1516 /** Sort parent sprites pointer array */
1517 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
1519 ParentSpriteToDraw
**psdvend
= psdv
->End();
1520 ParentSpriteToDraw
**psd
= psdv
->Begin();
1521 while (psd
!= psdvend
) {
1522 ParentSpriteToDraw
*ps
= *psd
;
1524 if (ps
->comparison_done
) {
1529 ps
->comparison_done
= true;
1531 for (ParentSpriteToDraw
**psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
1532 ParentSpriteToDraw
*ps2
= *psd2
;
1534 if (ps2
->comparison_done
) continue;
1536 /* Decide which comparator to use, based on whether the bounding
1539 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
1540 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
1541 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
1542 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
1543 * the screen and with higher Z elevation, are drawn in front.
1544 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
1545 * i.e. X=(left+right)/2, etc.
1546 * However, since we only care about order, don't actually divide / 2
1548 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
1549 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
1553 /* We only change the order, if it is definite.
1554 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
1555 * That is: If one partial order says ps behind ps2, do not change the order.
1557 if (ps
->xmax
< ps2
->xmin
||
1558 ps
->ymax
< ps2
->ymin
||
1559 ps
->zmax
< ps2
->zmin
) {
1564 /* Move ps2 in front of ps */
1565 ParentSpriteToDraw
*temp
= ps2
;
1566 for (ParentSpriteToDraw
**psd3
= psd2
; psd3
> psd
; psd3
--) {
1567 *psd3
= *(psd3
- 1);
1574 static void ViewportDrawParentSprites(const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
1576 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1577 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1578 const ParentSpriteToDraw
*ps
= *it
;
1579 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSpriteViewport(ps
->image
, ps
->pal
, ps
->x
, ps
->y
, ps
->sub
);
1581 int child_idx
= ps
->first_child
;
1582 while (child_idx
>= 0) {
1583 const ChildScreenSpriteToDraw
*cs
= csstdv
->Get(child_idx
);
1584 child_idx
= cs
->next
;
1585 DrawSpriteViewport(cs
->image
, cs
->pal
, ps
->left
+ cs
->x
, ps
->top
+ cs
->y
, cs
->sub
);
1591 * Draws the bounding boxes of all ParentSprites
1592 * @param psd Array of ParentSprites
1594 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector
*psd
)
1596 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1597 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1598 const ParentSpriteToDraw
*ps
= *it
;
1599 Point pt1
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmax
+ 1); // top front corner
1600 Point pt2
= RemapCoords(ps
->xmin
, ps
->ymax
+ 1, ps
->zmax
+ 1); // top left corner
1601 Point pt3
= RemapCoords(ps
->xmax
+ 1, ps
->ymin
, ps
->zmax
+ 1); // top right corner
1602 Point pt4
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmin
); // bottom front corner
1604 DrawBox( pt1
.x
, pt1
.y
,
1605 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
1606 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
1607 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
1611 static void ViewportMapStoreBridgeTunnel(const ViewPort
* const vp
, const TileIndex tile
)
1613 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
1614 extern uint _company_to_list_pos
[MAX_COMPANIES
];
1616 /* No need to bother for hidden things */
1617 const bool tile_is_tunnel
= IsTunnel(tile
);
1618 if (tile_is_tunnel
) {
1619 if (!_settings_client
.gui
.show_tunnels_on_map
) return;
1621 if (!_settings_client
.gui
.show_bridges_on_map
) return;
1624 const Owner o
= GetTileOwner(tile
);
1626 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
1628 /* Check if already stored */
1629 TunnelBridgeToMapVector
* const tbtmv
= tile_is_tunnel
? &_vd
.tunnel_to_map
: &_vd
.bridge_to_map
;
1630 TunnelBridgeToMap
*tbtm
= tbtmv
->Begin();
1631 const TunnelBridgeToMap
* const tbtm_end
= tbtmv
->End();
1632 while (tbtm
!= tbtm_end
) {
1633 if (tile
== tbtm
->from_tile
|| tile
== tbtm
->to_tile
) return;
1637 /* It's a new one, add it to the list */
1638 tbtm
= tbtmv
->Append();
1639 TileIndex other_end
= GetOtherTunnelBridgeEnd(tile
);
1641 /* ensure deterministic ordering, to avoid render flicker */
1642 if (other_end
> tile
) {
1643 tbtm
->from_tile
= other_end
;
1644 tbtm
->to_tile
= tile
;
1646 tbtm
->from_tile
= tile
;
1647 tbtm
->to_tile
= other_end
;
1651 void ViewportMapClearTunnelCache()
1653 _vd
.tunnel_to_map
.Clear();
1656 void ViewportMapInvalidateTunnelCacheByTile(const TileIndex tile
)
1658 TunnelBridgeToMapVector
* const tbtmv
= &_vd
.tunnel_to_map
;
1659 for (TunnelBridgeToMap
*tbtm
= tbtmv
->Begin(); tbtm
!= tbtmv
->End(); tbtm
++) {
1660 if (tbtm
->from_tile
== tile
|| tbtm
->to_tile
== tile
) {
1668 * Draw/colour the blocks that have been redrawn.
1670 static void ViewportDrawDirtyBlocks()
1672 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1673 const DrawPixelInfo
*dpi
= _cur_dpi
;
1675 int right
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
1676 int bottom
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
1678 int colour
= _string_colourmap
[_dirty_block_colour
& 0xF];
1682 byte bo
= UnScaleByZoom(dpi
->left
+ dpi
->top
, dpi
->zoom
) & 1;
1684 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
1685 dst
= blitter
->MoveTo(dst
, 0, 1);
1686 } while (--bottom
> 0);
1689 static void ViewportDrawStrings(ZoomLevel zoom
, const StringSpriteToDrawVector
*sstdv
)
1691 const StringSpriteToDraw
*ssend
= sstdv
->End();
1692 for (const StringSpriteToDraw
*ss
= sstdv
->Begin(); ss
!= ssend
; ++ss
) {
1693 TextColour colour
= TC_BLACK
;
1694 bool small
= HasBit(ss
->width
, 15);
1695 int w
= GB(ss
->width
, 0, 15);
1696 int x
= UnScaleByZoom(ss
->x
, zoom
);
1697 int y
= UnScaleByZoom(ss
->y
, zoom
);
1698 int h
= VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
;
1700 SetDParam(0, ss
->params
[0]);
1701 SetDParam(1, ss
->params
[1]);
1703 if (ss
->colour
!= INVALID_COLOUR
) {
1704 /* Do not draw signs nor station names if they are set invisible */
1705 if (IsInvisibilitySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) continue;
1707 if (IsTransparencySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) {
1708 /* Don't draw the rectangle.
1709 * Real colours need the TC_IS_PALETTE_COLOUR flag.
1710 * Otherwise colours from _string_colourmap are assumed. */
1711 colour
= (TextColour
)_colour_gradient
[ss
->colour
][6] | TC_IS_PALETTE_COLOUR
;
1713 /* Draw the rectangle if 'transparent station signs' is off,
1714 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
1716 x
, y
, x
+ w
, y
+ h
, ss
->colour
,
1717 IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
1722 DrawString(x
+ VPSM_LEFT
, x
+ w
- 1 - VPSM_RIGHT
, y
+ VPSM_TOP
, ss
->string
, colour
, SA_HOR_CENTER
);
1726 static inline Vehicle
*GetVehicleFromWindow(Window
*w
)
1729 WindowClass wc
= w
->window_class
;
1730 WindowNumber wn
= w
->window_number
;
1732 if (wc
== WC_DROPDOWN_MENU
) GetParentWindowInfo(w
, wc
, wn
);
1735 case WC_VEHICLE_VIEW
:
1736 case WC_VEHICLE_ORDERS
:
1737 case WC_VEHICLE_TIMETABLE
:
1738 case WC_VEHICLE_DETAILS
:
1739 case WC_VEHICLE_REFIT
:
1740 case WC_VEHICLE_CARGO_TYPE_LOAD_ORDERS
:
1741 case WC_VEHICLE_CARGO_TYPE_UNLOAD_ORDERS
:
1742 if (wn
!= INVALID_VEHICLE
) return Vehicle::Get(wn
);
1751 static inline TileIndex
GetLastValidOrderLocation(const Vehicle
*veh
)
1754 TileIndex tmp
, result
= INVALID_TILE
;
1755 FOR_VEHICLE_ORDERS(veh
, order
) {
1756 switch (order
->GetType()) {
1757 case OT_GOTO_STATION
:
1758 case OT_GOTO_WAYPOINT
:
1761 tmp
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1762 if (tmp
!= INVALID_TILE
) result
= tmp
;
1771 static inline Order
*GetFinalOrder(const Vehicle
*veh
, Order
*order
)
1773 while (order
->IsType(OT_CONDITIONAL
))
1774 order
= veh
->GetOrder(order
->GetConditionSkipToOrder());
1778 static bool ViewportMapPrepareVehicleRoute(const Vehicle
* const veh
)
1780 if (!veh
) return false;
1782 if (_vp_route_paths
.size() == 0) {
1783 TileIndex from_tile
= GetLastValidOrderLocation(veh
);
1784 if (from_tile
== INVALID_TILE
) return false;
1787 FOR_VEHICLE_ORDERS(veh
, order
) {
1788 Order
*final_order
= GetFinalOrder(veh
, order
);
1789 const TileIndex to_tile
= final_order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1790 if (to_tile
== INVALID_TILE
) continue;
1792 DrawnPathRouteTileLine path
= { from_tile
, to_tile
, (final_order
== order
) };
1793 if (path
.from_tile
> path
.to_tile
) std::swap(path
.from_tile
, path
.to_tile
);
1794 _vp_route_paths
.push_back(path
);
1796 const OrderType ot
= order
->GetType();
1797 if (ot
== OT_GOTO_STATION
|| ot
== OT_GOTO_DEPOT
|| ot
== OT_GOTO_WAYPOINT
|| ot
== OT_IMPLICIT
) from_tile
= to_tile
;
1799 // remove duplicate lines
1800 std::sort(_vp_route_paths
.begin(), _vp_route_paths
.end());
1801 _vp_route_paths
.erase(std::unique(_vp_route_paths
.begin(), _vp_route_paths
.end()), _vp_route_paths
.end());
1806 /** Draw the route of a vehicle. */
1807 static void ViewportMapDrawVehicleRoute(const ViewPort
*vp
)
1809 const Vehicle
*veh
= GetVehicleFromWindow(_focused_window
);
1812 if (!_vp_route_paths
.empty()) {
1813 // make sure we remove any leftover paths
1814 MarkRoutePathsDirty(_vp_route_paths
);
1815 _vp_route_paths
.clear();
1816 _vp_route_paths_last_mark_dirty
.clear();
1817 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 0");
1822 switch (_settings_client
.gui
.show_vehicle_route
) {
1823 /* case 0: return; // No */
1825 if (!ViewportMapPrepareVehicleRoute(veh
)) {
1826 if (!_vp_route_paths
.empty()) {
1827 // make sure we remove any leftover paths
1828 MarkRoutePathsDirty(_vp_route_paths
);
1829 _vp_route_paths
.clear();
1830 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 1");
1835 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1836 _cur_dpi
= &_dpi_for_text
;
1838 for (const auto &iter
: _vp_route_paths
) {
1839 const Point from_pt
= RemapCoords2(TileX(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1840 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
1841 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
1843 const Point to_pt
= RemapCoords2(TileX(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1844 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
1845 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
1848 if (_settings_client
.gui
.dash_level_of_route_lines
== 0) {
1849 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3, _settings_client
.gui
.dash_level_of_route_lines
);
1852 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, iter
.order_match
? PC_WHITE
: PC_YELLOW
, line_width
, _settings_client
.gui
.dash_level_of_route_lines
);
1855 if (_vp_route_paths_last_mark_dirty
!= _vp_route_paths
) {
1856 // make sure we're not drawing a partial path
1857 MarkRoutePathsDirty(_vp_route_paths
);
1858 _vp_route_paths_last_mark_dirty
= _vp_route_paths
;
1859 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 2");
1868 static inline void DrawRouteStep(const ViewPort
* const vp
, const TileIndex tile
, const RankOrderTypeList list
)
1870 if (tile
== INVALID_TILE
) return;
1871 const uint step_count
= list
.size() > max_rank_order_type_count
? 1 : list
.size();
1872 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1873 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
) - (_vp_route_step_width
/ 2);
1874 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
1875 const int rsth
= _vp_route_step_height_top
+ (int)step_count
* char_height
+ _vp_route_step_height_bottom
;
1876 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
) - rsth
;
1878 /* Draw the background. */
1879 DrawSprite(SPR_ROUTE_STEP_TOP
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
);
1880 uint y2
= y
+ _vp_route_step_height_top
;
1882 for (uint r
= step_count
; r
!= 0; r
--, y2
+= char_height
) {
1883 DrawSprite(SPR_ROUTE_STEP_MIDDLE
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
, &_vp_route_step_subsprite
);
1886 DrawSprite(SPR_ROUTE_STEP_BOTTOM
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1887 SpriteID s
= SPR_ROUTE_STEP_BOTTOM_SHADOW
;
1888 DrawSprite(SetBit(s
, PALETTE_MODIFIER_TRANSPARENT
), PALETTE_TO_TRANSPARENT
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1890 /* Fill with the data. */
1891 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1892 y2
= y
+ _vp_route_step_height_top
;
1893 _cur_dpi
= &_dpi_for_text
;
1895 if (list
.size() > max_rank_order_type_count
) {
1896 /* Write order overflow item */
1897 SetDParam(0, list
.size());
1898 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1899 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_OVERFLOW
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1901 for (RankOrderTypeList::const_iterator cit
= list
.begin(); cit
!= list
.end(); cit
++, y2
+= char_height
) {
1903 switch (cit
->second
) {
1904 case OT_GOTO_STATION
:
1905 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_STATION
);
1908 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_DEPOT
);
1910 case OT_GOTO_WAYPOINT
:
1911 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_WAYPOINT
);
1914 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_IMPLICIT
);
1916 default: // OT_NOTHING OT_LOADING OT_LEAVESTATION OT_DUMMY OT_CONDITIONAL
1921 /* Write order's info */
1922 SetDParam(0, cit
->first
);
1923 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1924 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1931 static bool ViewportPrepareVehicleRouteSteps(const Vehicle
* const veh
)
1933 if (!veh
) return false;
1935 if (_vp_route_steps
.size() == 0) {
1939 FOR_VEHICLE_ORDERS(veh
, order
) {
1940 const TileIndex tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1942 if (tile
== INVALID_TILE
) continue;
1943 _vp_route_steps
[tile
].push_back(std::pair
<int, OrderType
>(order_rank
, order
->GetType()));
1950 /** Draw the route steps of a vehicle. */
1951 static void ViewportDrawVehicleRouteSteps(const ViewPort
* const vp
)
1953 const Vehicle
* const veh
= GetVehicleFromWindow(_focused_window
);
1954 if (veh
&& ViewportPrepareVehicleRouteSteps(veh
)) {
1955 if (_vp_route_steps
!= _vp_route_steps_last_mark_dirty
) {
1956 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1957 MarkRouteStepDirty(cit
);
1959 _vp_route_steps_last_mark_dirty
= _vp_route_steps
;
1962 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1963 DrawRouteStep(vp
, cit
->first
, cit
->second
);
1968 void ViewportDrawPlans(const ViewPort
*vp
)
1970 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1971 _cur_dpi
= &_dpi_for_text
;
1975 if (!p
->IsVisible()) continue;
1976 for (PlanLineVector::iterator it
= p
->lines
.begin(); it
!= p
->lines
.end(); it
++) {
1978 if (!pl
->visible
) continue;
1979 for (uint i
= 1; i
< pl
->tiles
.size(); i
++) {
1980 const TileIndex from_tile
= pl
->tiles
[i
- 1];
1981 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1982 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
1983 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
1985 const TileIndex to_tile
= pl
->tiles
[i
];
1986 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1987 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
1988 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
1990 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3);
1992 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_RED
, 1);
1995 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 1);
2001 if (_current_plan
&& _current_plan
->temp_line
->tiles
.size() > 1) {
2002 for (uint i
= 1; i
< _current_plan
->temp_line
->tiles
.size(); i
++) {
2003 const TileIndex from_tile
= _current_plan
->temp_line
->tiles
[i
- 1];
2004 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2005 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2006 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2008 const TileIndex to_tile
= _current_plan
->temp_line
->tiles
[i
];
2009 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2010 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2011 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2013 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 3, 1);
2020 #define SLOPIFY_COLOUR(tile, height, vF, vW, vS, vE, vN, action) { \
2022 const Slope slope = GetTileSlope((tile), (height)); \
2025 case SLOPE_ELEVATED: \
2026 action (vF); break; \
2028 switch (slope & SLOPE_EW) { \
2029 case SLOPE_W: action (vW); break; \
2030 case SLOPE_E: action (vE); break; \
2031 default: action (slope & SLOPE_S) ? (vS) : (vN); break; \
2040 #define RETURN_SLOPIFIED_COLOUR(tile, height, colour, colour_light, colour_dark) SLOPIFY_COLOUR(tile, height, colour, colour_light, colour_dark, colour_dark, colour_light, return)
2041 #define ASSIGN_SLOPIFIED_COLOUR(tile, height, colour, colour_light, colour_dark, to_var) SLOPIFY_COLOUR(tile, height, colour, colour_light, colour_dark, colour_dark, colour_light, to_var =)
2042 #define GET_SLOPE_INDEX(slope_index) SLOPIFY_COLOUR(tile, NULL, 0, 1, 2, 3, 4, slope_index =)
2044 #define COL8TO32(x) _cur_palette.palette[x].data
2045 #define COLOUR_FROM_INDEX(x) ((const uint8 *)&(x))[colour_index]
2046 #define IS32(x) (is_32bpp ? COL8TO32(x) : (x))
2048 /* Variables containing Colour if 32bpp or palette index if 8bpp. */
2049 uint32 _vp_map_vegetation_clear_colours
[16][6][8]; ///< [Slope][ClearGround][Multi (see LoadClearGroundMainColours())]
2050 uint32 _vp_map_vegetation_tree_colours
[5][MAX_TREE_COUNT_BY_LANDSCAPE
]; ///< [TreeGround][max of _tree_count_by_landscape]
2051 uint32 _vp_map_water_colour
[5]; ///< [Slope]
2053 static inline uint
ViewportMapGetColourIndexMulti(const TileIndex tile
, const ClearGround cg
)
2059 return GetClearDensity(tile
);
2061 return GB(TileX(tile
) ^ TileY(tile
), 4, 3);
2063 return TileHash(TileX(tile
), TileY(tile
)) & 1;
2065 return GetFieldType(tile
) & 7;
2066 default: NOT_REACHED();
2070 static const ClearGround _treeground_to_clearground
[5] = {
2071 CLEAR_GRASS
, // TREE_GROUND_GRASS
2072 CLEAR_ROUGH
, // TREE_GROUND_ROUGH
2073 CLEAR_SNOW
, // TREE_GROUND_SNOW_DESERT, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2074 CLEAR_GRASS
, // TREE_GROUND_SHORE
2075 CLEAR_SNOW
, // TREE_GROUND_ROUGH_SNOW, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2078 template <bool is_32bpp
, bool show_slope
>
2079 static inline uint32
ViewportMapGetColourVegetation(const TileIndex tile
, TileType t
, const uint colour_index
)
2084 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, NULL
) & 15) : SLOPE_FLAT
;
2086 ClearGround cg
= GetClearGround(tile
);
2087 if (cg
== CLEAR_FIELDS
&& colour_index
& 1) {
2091 else multi
= ViewportMapGetColourIndexMulti(tile
, cg
);
2092 return _vp_map_vegetation_clear_colours
[slope
][cg
][multi
];
2096 colour
= IsTileForestIndustry(tile
) ? (colour_index
& 1 ? PC_GREEN
: 0x7B) : GREY_SCALE(3);
2100 const TreeGround tg
= GetTreeGround(tile
);
2101 const uint td
= GetTreeDensity(tile
);
2102 if (IsTransparencySet(TO_TREES
)) {
2103 ClearGround cg
= _treeground_to_clearground
[tg
];
2104 if (cg
== CLEAR_SNOW
&& _settings_game
.game_creation
.landscape
== LT_TROPIC
) cg
= CLEAR_DESERT
;
2105 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, NULL
) & 15) : SLOPE_FLAT
;
2106 uint32 ground_colour
= _vp_map_vegetation_clear_colours
[slope
][cg
][td
];
2108 if (IsInvisibilitySet(TO_TREES
)) {
2110 return ground_colour
;
2113 /* Take ground and make it darker. */
2115 return Blitter_32bppBase::MakeTransparent(ground_colour
, 192, 256).data
;
2118 /* 8bpp transparent snow trees give blue. Definitely don't want that. Prefer grey. */
2119 if (cg
== CLEAR_SNOW
&& td
> 1) return GREY_SCALE(13 - GetTreeCount(tile
));
2120 return _pal2trsp_remap_ptr
[ground_colour
];
2124 if (tg
== TREE_GROUND_SNOW_DESERT
|| tg
== TREE_GROUND_ROUGH_SNOW
) {
2125 return _vp_map_vegetation_clear_colours
[colour_index
][_settings_game
.game_creation
.landscape
== LT_TROPIC
? CLEAR_DESERT
: CLEAR_SNOW
][td
];
2128 const uint rnd
= min(GetTreeCount(tile
) ^ (((tile
& 3) ^ (TileY(tile
) & 3)) * td
), MAX_TREE_COUNT_BY_LANDSCAPE
- 1);
2129 return _vp_map_vegetation_tree_colours
[tg
][rnd
];
2136 uint slope_index
= 0;
2137 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2138 return _vp_map_water_colour
[slope_index
];
2143 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[t
]);
2144 colour
= COLOUR_FROM_INDEX(colour
);
2149 return COL8TO32(colour
);
2152 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2157 template <bool is_32bpp
, bool show_slope
>
2158 static inline uint32
ViewportMapGetColourIndustries(const TileIndex tile
, const TileType t
, const uint colour_index
)
2160 extern LegendAndColour _legend_from_industries
[NUM_INDUSTRYTYPES
+ 1];
2161 extern uint _industry_to_list_pos
[NUM_INDUSTRYTYPES
];
2162 extern bool _smallmap_show_heightmap
;
2165 if (t
== MP_INDUSTRY
) {
2166 /* If industry is allowed to be seen, use its colour on the map. */
2167 const IndustryType it
= Industry::GetByTile(tile
)->type
;
2168 if (_legend_from_industries
[_industry_to_list_pos
[it
]].show_on_map
)
2169 return IS32(GetIndustrySpec(it
)->map_colour
);
2170 /* Otherwise, return the colour which will make it disappear. */
2171 t2
= IsTileOnWater(tile
) ? MP_WATER
: MP_CLEAR
;
2174 if (is_32bpp
&& t2
== MP_WATER
) {
2175 uint slope_index
= 0;
2176 if (t
!= MP_INDUSTRY
&& GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
); ///< Ignore industry on water not shown on map.
2177 return _vp_map_water_colour
[slope_index
];
2180 const int h
= TileHeight(tile
);
2181 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
2182 const uint32 colours
= ApplyMask(_smallmap_show_heightmap
? cs
->height_colours
[h
] : cs
->default_colour
, &_smallmap_vehicles_andor
[t2
]);
2183 uint32 colour
= COLOUR_FROM_INDEX(colours
);
2185 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2187 return IS32(colour
);
2190 template <bool is_32bpp
, bool show_slope
>
2191 static inline uint32
ViewportMapGetColourOwner(const TileIndex tile
, TileType t
, const uint colour_index
)
2193 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2194 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2197 case MP_INDUSTRY
: return IS32(PC_DARK_GREY
);
2198 case MP_HOUSE
: return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
2202 const Owner o
= GetTileOwner(tile
);
2203 if ((o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) || o
== OWNER_NONE
|| o
== OWNER_WATER
) {
2204 if (t
== MP_WATER
) {
2206 uint slope_index
= 0;
2207 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2208 return _vp_map_water_colour
[slope_index
];
2215 const int h
= TileHeight(tile
);
2216 uint32 colour
= COLOUR_FROM_INDEX(_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
].height_colours
[h
]);
2217 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2218 return IS32(colour
);
2221 else if (o
== OWNER_TOWN
) {
2222 return IS32(t
== MP_ROAD
? (colour_index
& 1 ? PC_BLACK
: GREY_SCALE(3)) : PC_DARK_RED
);
2225 /* Train stations are sometimes hard to spot.
2226 * So we give the player a hint by mixing his colour with black. */
2227 uint32 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2228 if (t
!= MP_STATION
) {
2229 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2232 if (GetStationType(tile
) == STATION_RAIL
) colour
= colour_index
& 1 ? colour
: PC_BLACK
;
2234 if (is_32bpp
) return COL8TO32(colour
);
2238 static inline void ViewportMapStoreBridgeAboveTile(const ViewPort
* const vp
, const TileIndex tile
)
2240 /* No need to bother for hidden things */
2241 if (!_settings_client
.gui
.show_bridges_on_map
) return;
2243 /* Check existing stored bridges */
2244 TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin();
2245 TunnelBridgeToMap
*tbtm_end
= _vd
.bridge_to_map
.End();
2246 for (; tbtm
!= tbtm_end
; ++tbtm
) {
2247 if (!IsBridge(tbtm
->from_tile
)) continue;
2249 TileIndex from
= tbtm
->from_tile
;
2250 TileIndex to
= tbtm
->to_tile
;
2251 if (TileX(from
) == TileX(to
) && TileX(from
) == TileX(tile
)) {
2252 if (TileY(from
) > TileY(to
)) std::swap(from
, to
);
2253 if (TileY(from
) <= TileY(tile
) && TileY(tile
) <= TileY(to
)) return; /* already covered */
2255 else if (TileY(from
) == TileY(to
) && TileY(from
) == TileY(tile
)) {
2256 if (TileX(from
) > TileX(to
)) std::swap(from
, to
);
2257 if (TileX(from
) <= TileX(tile
) && TileX(tile
) <= TileX(to
)) return; /* already covered */
2261 ViewportMapStoreBridgeTunnel(vp
, GetSouthernBridgeEnd(tile
));
2264 static inline TileIndex
ViewportMapGetMostSignificantTileType(const ViewPort
* const vp
, const TileIndex from_tile
, TileType
* const tile_type
)
2266 if (vp
->zoom
<= ZOOM_LVL_OUT_128X
|| !_settings_client
.gui
.viewport_map_scan_surroundings
) {
2267 const TileType ttype
= GetTileType(from_tile
);
2268 /* Store bridges and tunnels. */
2269 if (ttype
!= MP_TUNNELBRIDGE
) {
2271 if (IsBridgeAbove(from_tile
)) ViewportMapStoreBridgeAboveTile(vp
, from_tile
);
2274 ViewportMapStoreBridgeTunnel(vp
, from_tile
);
2275 switch (GetTunnelBridgeTransportType(from_tile
)) {
2276 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2277 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2278 default: *tile_type
= MP_WATER
; break;
2284 const uint8 length
= (vp
->zoom
- ZOOM_LVL_OUT_128X
) * 2;
2285 TileArea tile_area
= TileArea(from_tile
, length
, length
);
2286 tile_area
.ClampToMap();
2288 /* Find the most important tile of the area. */
2289 TileIndex result
= from_tile
;
2290 uint importance
= 0;
2291 TILE_AREA_LOOP_WITH_PREFETCH(tile
, tile_area
) {
2292 const TileType ttype
= GetTileType(tile
);
2293 const uint tile_importance
= _tiletype_importance
[ttype
];
2294 if (tile_importance
> importance
) {
2295 importance
= tile_importance
;
2298 if (ttype
!= MP_TUNNELBRIDGE
&& IsBridgeAbove(tile
)) {
2299 ViewportMapStoreBridgeAboveTile(vp
, tile
);
2303 /* Store bridges and tunnels. */
2304 *tile_type
= GetTileType(result
);
2305 if (*tile_type
== MP_TUNNELBRIDGE
) {
2306 ViewportMapStoreBridgeTunnel(vp
, result
);
2307 switch (GetTunnelBridgeTransportType(result
)) {
2308 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2309 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2310 default: *tile_type
= MP_WATER
; break;
2317 /** Get the colour of a tile, can be 32bpp RGB or 8bpp palette index. */
2318 template <bool is_32bpp
, bool show_slope
>
2319 uint32
ViewportMapGetColour(const ViewPort
* const vp
, uint x
, uint y
, const uint colour_index
)
2321 if (!(IsInsideMM(x
, TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1) &&
2322 IsInsideMM(y
, TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1)))
2325 /* Very approximative but fast way to get the tile when taking Z into account. */
2326 const TileIndex tile_tmp
= TileVirtXY(x
, y
);
2327 const uint z
= TileHeight(tile_tmp
) * 4;
2328 TileIndex tile
= TileVirtXY(x
+ z
, y
+ z
);
2329 if (tile
>= MapSize()) return 0;
2330 if (_settings_game
.construction
.freeform_edges
) {
2331 /* tile_tmp and tile must be from the same side,
2332 * otherwise it's an approximation erroneous case
2333 * that leads to a graphic glitch below south west border.
2335 if (TileX(tile_tmp
) > (MapSizeX() - (MapSizeX() / 8)))
2336 if ((TileX(tile_tmp
) < (MapSizeX() / 2)) != (TileX(tile
) < (MapSizeX() / 2)))
2339 TileType tile_type
= MP_VOID
;
2340 tile
= ViewportMapGetMostSignificantTileType(vp
, tile
, &tile_type
);
2341 if (tile_type
== MP_VOID
) return 0;
2343 /* Return the colours. */
2344 switch (vp
->map_type
) {
2345 default: return ViewportMapGetColourOwner
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2346 case VPMT_INDUSTRY
: return ViewportMapGetColourIndustries
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2347 case VPMT_VEGETATION
: return ViewportMapGetColourVegetation
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2351 /* Taken from http://stereopsis.com/doubleblend.html, PixelBlend() is faster than ComposeColourRGBANoCheck() */
2352 static inline void PixelBlend(uint32
* const d
, const uint32 s
)
2354 const uint32 a
= (s
>> 24) + 1;
2355 const uint32 dstrb
= *d
& 0xFF00FF;
2356 const uint32 dstg
= *d
& 0xFF00;
2357 const uint32 srcrb
= s
& 0xFF00FF;
2358 const uint32 srcg
= s
& 0xFF00;
2359 uint32 drb
= srcrb
- dstrb
;
2360 uint32 dg
= srcg
- dstg
;
2365 uint32 rb
= (drb
+ dstrb
) & 0xFF00FF;
2366 uint32 g
= (dg
+ dstg
) & 0xFF00;
2370 /** Draw the bounding boxes of the scrolling viewport (right-clicked and dragged) */
2371 static void ViewportMapDrawScrollingViewportBox(const ViewPort
* const vp
)
2373 if (_scrolling_viewport
&& _scrolling_viewport
->viewport
) {
2374 const ViewPort
* const vp_scrolling
= _scrolling_viewport
->viewport
;
2375 if (vp_scrolling
->zoom
< ZOOM_LVL_DRAW_MAP
) {
2376 /* Check intersection of dpi and vp_scrolling */
2377 const int mask
= ScaleByZoom(-1, vp
->zoom
);
2378 const int vp_scrolling_virtual_top_mask
= vp_scrolling
->virtual_top
& mask
;
2379 const int vp_scrolling_virtual_bottom_mask
= (vp_scrolling
->virtual_top
+ vp_scrolling
->virtual_height
) & mask
;
2380 const int t_inter
= max(vp_scrolling_virtual_top_mask
, _vd
.dpi
.top
);
2381 const int b_inter
= min(vp_scrolling_virtual_bottom_mask
, _vd
.dpi
.top
+ _vd
.dpi
.height
);
2382 if (t_inter
< b_inter
) {
2383 const int vp_scrolling_virtual_left_mask
= vp_scrolling
->virtual_left
& mask
;
2384 const int vp_scrolling_virtual_right_mask
= (vp_scrolling
->virtual_left
+ vp_scrolling
->virtual_width
) & mask
;
2385 const int l_inter
= max(vp_scrolling_virtual_left_mask
, _vd
.dpi
.left
);
2386 const int r_inter
= min(vp_scrolling_virtual_right_mask
, _vd
.dpi
.left
+ _vd
.dpi
.width
);
2387 if (l_inter
< r_inter
) {
2388 /* OK, so we can draw something that tells where the scrolling viewport is */
2389 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2390 const int w_inter
= UnScaleByZoom(r_inter
- l_inter
, vp
->zoom
);
2391 const int h_inter
= UnScaleByZoom(b_inter
- t_inter
, vp
->zoom
);
2392 const int x
= UnScaleByZoom(l_inter
- _vd
.dpi
.left
, vp
->zoom
);
2393 const int y
= UnScaleByZoom(t_inter
- _vd
.dpi
.top
, vp
->zoom
);
2395 /* If asked, with 32bpp we can do some blending */
2396 if (_settings_client
.gui
.show_scrolling_viewport_on_map
>= 2 && blitter
->GetScreenDepth() == 32)
2397 for (int j
= y
; j
< y
+ h_inter
; j
++)
2398 for (int i
= x
; i
< x
+ w_inter
; i
++)
2399 PixelBlend((uint32
*)blitter
->MoveTo(_vd
.dpi
.dst_ptr
, i
, j
), 0x40FCFCFC);
2401 /* Draw area contour */
2402 if (_settings_client
.gui
.show_scrolling_viewport_on_map
!= 2) {
2403 if (t_inter
== vp_scrolling_virtual_top_mask
)
2404 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2405 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
, PC_WHITE
);
2406 if (b_inter
== vp_scrolling_virtual_bottom_mask
)
2407 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2408 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
+ h_inter
, PC_WHITE
);
2409 if (l_inter
== vp_scrolling_virtual_left_mask
)
2410 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2411 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, j
, PC_WHITE
);
2412 if (r_inter
== vp_scrolling_virtual_right_mask
)
2413 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2414 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
+ w_inter
, j
, PC_WHITE
);
2422 uint32
*_vp_map_line
; ///< Buffer for drawing the map of a viewport.
2424 static void ViewportMapDrawBridgeTunnel(const ViewPort
* const vp
, const TunnelBridgeToMap
* const tbtm
, const int z
,
2425 const bool is_tunnel
, const int w
, const int h
, Blitter
* const blitter
)
2427 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2428 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2430 TileIndex tile
= tbtm
->from_tile
;
2431 const Owner o
= GetTileOwner(tile
);
2432 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
2435 if (vp
->map_type
== VPMT_OWNER
&& _settings_client
.gui
.use_owner_colour_for_tunnelbridge
&& o
< MAX_COMPANIES
) {
2436 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2437 colour
= is_tunnel
? _darken_colour
[colour
] : _lighten_colour
[colour
];
2440 colour
= is_tunnel
? PC_BLACK
: PC_VERY_LIGHT_YELLOW
;
2443 TileIndexDiff delta
= TileOffsByDiagDir(GetTunnelBridgeDirection(tile
));
2444 for (; tile
!= tbtm
->to_tile
; tile
+= delta
) { // For each tile
2445 const Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, z
);
2446 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2447 if (IsInsideMM(x
, 0, w
)) {
2448 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2449 if (IsInsideMM(y
, 0, h
)) blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, y
, colour
);
2454 /** Draw the map on a viewport. */
2455 template <bool is_32bpp
, bool show_slope
>
2456 void ViewportMapDraw(const ViewPort
* const vp
)
2459 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2461 SmallMapWindow::RebuildColourIndexIfNecessary();
2463 /* Index of colour: _green_map_heights[] contains blocks of 4 colours, say ABCD
2464 * For a XXXY colour block to render nicely, follow the model:
2465 * line 1: ABCDABCDABCD
2466 * line 2: CDABCDABCDAB
2467 * line 3: ABCDABCDABCD
2468 * => colour_index_base's second bit is changed every new line.
2470 const int sx
= UnScaleByZoomLower(_vd
.dpi
.left
, _vd
.dpi
.zoom
);
2471 const int sy
= UnScaleByZoomLower(_vd
.dpi
.top
, _vd
.dpi
.zoom
);
2472 const uint line_padding
= 2 * (sy
& 1);
2473 uint colour_index_base
= (sx
+ line_padding
) & 3;
2475 const int incr_a
= (1 << (vp
->zoom
- 2)) / ZOOM_LVL_BASE
;
2476 const int incr_b
= (1 << (vp
->zoom
- 1)) / ZOOM_LVL_BASE
;
2477 const int a
= (_vd
.dpi
.left
>> 2) / ZOOM_LVL_BASE
;
2478 int b
= (_vd
.dpi
.top
>> 1) / ZOOM_LVL_BASE
;
2479 const int w
= UnScaleByZoom(_vd
.dpi
.width
, vp
->zoom
);
2480 const int h
= UnScaleByZoom(_vd
.dpi
.height
, vp
->zoom
);
2483 /* Render base map. */
2484 do { // For each line
2486 uint colour_index
= colour_index_base
;
2487 colour_index_base
^= 2;
2488 uint32
*vp_map_line_ptr32
= _vp_map_line
;
2489 uint8
*vp_map_line_ptr8
= (uint8
*)_vp_map_line
;
2492 do { // For each pixel of a line
2494 *vp_map_line_ptr32
= ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2495 vp_map_line_ptr32
++;
2498 *vp_map_line_ptr8
= (uint8
)ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2501 colour_index
= ++colour_index
& 3;
2506 blitter
->SetLine32(_vd
.dpi
.dst_ptr
, 0, j
, _vp_map_line
, w
);
2509 blitter
->SetLine(_vd
.dpi
.dst_ptr
, 0, j
, (uint8
*)_vp_map_line
, w
);
2514 /* Render tunnels */
2515 if (_settings_client
.gui
.show_tunnels_on_map
&& _vd
.tunnel_to_map
.Length() != 0) {
2516 const TunnelBridgeToMap
* const tbtm_end
= _vd
.tunnel_to_map
.End();
2517 for (const TunnelBridgeToMap
*tbtm
= _vd
.tunnel_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each tunnel
2518 const int tunnel_z
= GetTileZ(tbtm
->from_tile
) * TILE_HEIGHT
;
2519 const Point pt_from
= RemapCoords(TileX(tbtm
->from_tile
) * TILE_SIZE
, TileY(tbtm
->from_tile
) * TILE_SIZE
, tunnel_z
);
2520 const Point pt_to
= RemapCoords(TileX(tbtm
->to_tile
) * TILE_SIZE
, TileY(tbtm
->to_tile
) * TILE_SIZE
, tunnel_z
);
2522 /* check if tunnel is wholly outside redrawing area */
2523 const int x_from
= UnScaleByZoomLower(pt_from
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2524 const int x_to
= UnScaleByZoomLower(pt_to
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2525 if ((x_from
< 0 && x_to
< 0) || (x_from
> w
&& x_to
> w
)) continue;
2526 const int y_from
= UnScaleByZoomLower(pt_from
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2527 const int y_to
= UnScaleByZoomLower(pt_to
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2528 if ((y_from
< 0 && y_to
< 0) || (y_from
> h
&& y_to
> h
)) continue;
2530 ViewportMapDrawBridgeTunnel(vp
, tbtm
, tunnel_z
, true, w
, h
, blitter
);
2534 /* Render bridges */
2535 if (_settings_client
.gui
.show_bridges_on_map
&& _vd
.bridge_to_map
.Length() != 0) {
2536 const TunnelBridgeToMap
* const tbtm_end
= _vd
.bridge_to_map
.End();
2537 for (const TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each bridge
2538 ViewportMapDrawBridgeTunnel(vp
, tbtm
, (GetBridgeHeight(tbtm
->from_tile
) - 1) * TILE_HEIGHT
, false, w
, h
, blitter
);
2543 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2545 DrawPixelInfo
*old_dpi
= _cur_dpi
;
2546 _cur_dpi
= &_vd
.dpi
;
2548 _vd
.dpi
.zoom
= vp
->zoom
;
2549 int mask
= ScaleByZoom(-1, vp
->zoom
);
2551 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
2553 _vd
.dpi
.width
= (right
- left
) & mask
;
2554 _vd
.dpi
.height
= (bottom
- top
) & mask
;
2555 _vd
.dpi
.left
= left
& mask
;
2556 _vd
.dpi
.top
= top
& mask
;
2557 _vd
.dpi
.pitch
= old_dpi
->pitch
;
2558 _vd
.last_child
= NULL
;
2560 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
2561 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
2563 _vd
.dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
2565 _dpi_for_text
= _vd
.dpi
;
2566 _dpi_for_text
.left
= UnScaleByZoom(_dpi_for_text
.left
, _dpi_for_text
.zoom
);
2567 _dpi_for_text
.top
= UnScaleByZoom(_dpi_for_text
.top
, _dpi_for_text
.zoom
);
2568 _dpi_for_text
.width
= UnScaleByZoom(_dpi_for_text
.width
, _dpi_for_text
.zoom
);
2569 _dpi_for_text
.height
= UnScaleByZoom(_dpi_for_text
.height
, _dpi_for_text
.zoom
);
2570 _dpi_for_text
.zoom
= ZOOM_LVL_NORMAL
;
2572 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
2573 /* Here the rendering is like smallmap. */
2574 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
2575 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<true, true>(vp
);
2576 else ViewportMapDraw
<true, false>(vp
);
2579 _pal2trsp_remap_ptr
= IsTransparencySet(TO_TREES
) ? GetNonSprite(GB(PALETTE_TO_TRANSPARENT
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1 : NULL
;
2580 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<false, true>(vp
);
2581 else ViewportMapDraw
<false, false>(vp
);
2583 ViewportMapDrawVehicles(&_vd
.dpi
);
2584 if (_scrolling_viewport
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) ViewportMapDrawScrollingViewportBox(vp
);
2585 if (vp
->zoom
< ZOOM_LVL_OUT_256X
) ViewportAddTownNames(&_vd
.dpi
);
2588 /* Classic rendering. */
2589 ViewportAddLandscape();
2590 ViewportAddVehicles(&_vd
.dpi
);
2592 ViewportAddTownNames(&_vd
.dpi
);
2593 ViewportAddStationNames(&_vd
.dpi
);
2594 ViewportAddSigns(&_vd
.dpi
);
2596 DrawTextEffects(&_vd
.dpi
);
2598 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
2600 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
2601 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
2602 *_vd
.parent_sprites_to_sort
.Append() = it
;
2605 _vp_sprite_sorter(&_vd
.parent_sprites_to_sort
);
2606 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
2608 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
2610 if (_draw_dirty_blocks
) ViewportDrawDirtyBlocks();
2612 DrawPixelInfo dp
= _vd
.dpi
;
2613 ZoomLevel zoom
= _vd
.dpi
.zoom
;
2614 dp
.zoom
= ZOOM_LVL_NORMAL
;
2615 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
2616 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
2619 if (vp
->overlay
!= NULL
&& vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
2620 /* translate to window coordinates */
2623 vp
->overlay
->Draw(&dp
);
2626 if (_settings_client
.gui
.show_vehicle_route
) ViewportMapDrawVehicleRoute(vp
);
2627 if (_vd
.string_sprites_to_draw
.Length() != 0) {
2628 /* translate to world coordinates */
2629 dp
.left
= UnScaleByZoom(_vd
.dpi
.left
, zoom
);
2630 dp
.top
= UnScaleByZoom(_vd
.dpi
.top
, zoom
);
2631 ViewportDrawStrings(zoom
, &_vd
.string_sprites_to_draw
);
2633 if (_settings_client
.gui
.show_vehicle_route_steps
) ViewportDrawVehicleRouteSteps(vp
);
2634 ViewportDrawPlans(vp
);
2638 _vd
.bridge_to_map
.Clear();
2639 _vd
.string_sprites_to_draw
.Clear();
2640 _vd
.tile_sprites_to_draw
.Clear();
2641 _vd
.parent_sprites_to_draw
.Clear();
2642 _vd
.parent_sprites_to_sort
.Clear();
2643 _vd
.child_screen_sprites_to_draw
.Clear();
2646 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2649 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2650 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
2651 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2652 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
2656 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2658 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
2660 if (left
>= vp
->left
+ vp
->width
) return;
2662 if (left
< vp
->left
) left
= vp
->left
;
2663 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
2665 if (top
>= vp
->top
+ vp
->height
) return;
2667 if (top
< vp
->top
) top
= vp
->top
;
2668 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
2670 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
2674 * Draw the viewport of this window.
2676 void Window::DrawViewport() const
2678 DrawPixelInfo
*dpi
= _cur_dpi
;
2680 dpi
->left
+= this->left
;
2681 dpi
->top
+= this->top
;
2683 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
2685 dpi
->left
-= this->left
;
2686 dpi
->top
-= this->top
;
2690 * Continue criteria for the SearchMapEdge function.
2691 * @param iter Value to check.
2692 * @param iter_limit Maximum value for the iter
2693 * @param sy Screen y coordinate calculated for the tile at hand
2694 * @param sy_limit Limit to the screen y coordinate
2695 * @return True when we should continue searching.
2697 typedef bool ContinueMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
);
2699 /** Continue criteria for searching a no-longer-visible tile in negative direction, starting at some tile. */
2700 static inline bool ContinueLowerMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
> 0 && sy
> sy_limit
; }
2701 /** Continue criteria for searching a no-longer-visible tile in positive direction, starting at some tile. */
2702 static inline bool ContinueUpperMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
< iter_limit
&& sy
< sy_limit
; }
2705 * Searches, starting at the given tile, by applying the given offset to iter, for a no longer visible tile.
2706 * The whole sense of this function is keeping the to-be-written code small, thus it is a little bit abstracted
2707 * so the same function can be used for both the X and Y locations. As such a reference to one of the elements
2708 * in curr_tile was needed.
2709 * @param curr_tile A tile
2710 * @param iter Reference to either the X or Y of curr_tile.
2711 * @param iter_limit Upper search limit for the iter value.
2712 * @param offset Search in steps of this size
2713 * @param sy_limit Search limit to be passed to the criteria
2714 * @param continue_criteria Search as long as this criteria is true
2715 * @return The final value of iter.
2717 static int SearchMapEdge(Point
&curr_tile
, int &iter
, int iter_limit
, int offset
, int sy_limit
, ContinueMapEdgeSearch continue_criteria
)
2721 iter
= Clamp(iter
+ offset
, 0, iter_limit
);
2722 sy
= GetViewportY(curr_tile
);
2723 } while (continue_criteria(iter
, iter_limit
, sy
, sy_limit
));
2729 * Determine the clamping of either the X or Y coordinate to the map.
2730 * @param curr_tile A tile
2731 * @param iter Reference to either the X or Y of curr_tile.
2732 * @param iter_limit Upper search limit for the iter value.
2733 * @param start Start value for the iteration.
2734 * @param other_ref Reference to the opposite axis in curr_tile than of iter.
2735 * @param other_value Start value for of the opposite axis
2736 * @param vp_value Value of the viewport location in the opposite axis as for iter.
2737 * @param other_limit Limit for the other value, so if iter is X, then other_limit is for Y.
2738 * @param vp_top Top of the viewport.
2739 * @param vp_bottom Bottom of the viewport.
2740 * @return Clamped version of vp_value.
2742 static inline int ClampXYToMap(Point
&curr_tile
, int &iter
, int iter_limit
, int start
, int &other_ref
, int other_value
, int vp_value
, int other_limit
, int vp_top
, int vp_bottom
)
2744 bool upper_edge
= other_value
< _settings_game
.construction
.max_heightlevel
/ 4;
2747 * First get an estimate of the tiles relevant for us at that edge. Relevant in the sense
2748 * "at least close to the visible area". Thus, we don't look at exactly each tile, inspecting
2749 * e.g. every tenth should be enough. After all, the desired screen limit is set such that
2750 * the bordermost tiles are painted in the middle of the screen when one hits the limit,
2751 * i.e. it is no harm if there is some small error in that calculation
2754 other_ref
= upper_edge
? 0 : other_limit
;
2756 int min_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? -10 : +10, vp_top
, upper_edge
? ContinueLowerMapEdgeSearch
: ContinueUpperMapEdgeSearch
);
2758 int max_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? +10 : -10, vp_bottom
, upper_edge
? ContinueUpperMapEdgeSearch
: ContinueLowerMapEdgeSearch
);
2760 max_iter
= min(max_iter
+ _settings_game
.construction
.max_heightlevel
/ 4, iter_limit
);
2761 min_iter
= min(min_iter
, max_iter
);
2763 /* Now, calculate the highest heightlevel of these tiles. Again just as an estimate. */
2764 int max_heightlevel_at_edge
= 0;
2765 for (iter
= min_iter
; iter
<= max_iter
; iter
+= 10) {
2766 max_heightlevel_at_edge
= max(max_heightlevel_at_edge
, (int)TileHeight(TileXY(curr_tile
.x
, curr_tile
.y
)));
2769 /* Based on that heightlevel, calculate the limit. For the upper edge a tile with height zero would
2770 * get a limit of zero, on the other side it depends on the number of tiles along the axis. */
2772 max(vp_value
, -max_heightlevel_at_edge
* (int)(TILE_HEIGHT
* 2 * ZOOM_LVL_BASE
)) :
2773 min(vp_value
, (other_limit
* TILE_SIZE
* 4 - max_heightlevel_at_edge
* TILE_HEIGHT
* 2) * ZOOM_LVL_BASE
);
2776 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
2780 /* Centre of the viewport is hot spot */
2781 x
+= vp
->virtual_width
/ 2;
2782 y
+= vp
->virtual_height
/ 2;
2784 /* Convert viewport coordinates to map coordinates
2785 * Calculation is scaled by 4 to avoid rounding errors */
2786 int vx
= -x
+ y
* 2;
2789 /* Find out which tile corresponds to (vx,vy) if one assumes height zero. The cast is necessary to prevent C++ from
2790 * converting the result to an uint, which gives an overflow instead of a negative result... */
2791 int tx
= vx
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2792 int ty
= vy
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2795 vx
= ClampXYToMap(curr_tile
, curr_tile
.y
, MapMaxY(), ty
, curr_tile
.x
, tx
, vx
, MapMaxX(), original_y
, original_y
+ vp
->virtual_height
);
2796 vy
= ClampXYToMap(curr_tile
, curr_tile
.x
, MapMaxX(), tx
, curr_tile
.y
, ty
, vy
, MapMaxY(), original_y
, original_y
+ vp
->virtual_height
);
2798 /* Convert map coordinates to viewport coordinates */
2802 /* Remove centering */
2803 x
-= vp
->virtual_width
/ 2;
2804 y
-= vp
->virtual_height
/ 2;
2808 * Update the viewport position being displayed.
2809 * @param w %Window owning the viewport.
2811 void UpdateViewportPosition(Window
*w
)
2813 const ViewPort
*vp
= w
->viewport
;
2815 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
2816 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
2817 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
2819 w
->viewport
->scrollpos_x
= pt
.x
;
2820 w
->viewport
->scrollpos_y
= pt
.y
;
2821 SetViewportPosition(w
, pt
.x
, pt
.y
);
2823 /* Ensure the destination location is within the map */
2824 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
2826 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
2827 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
2829 bool update_overlay
= false;
2830 if (delta_x
!= 0 || delta_y
!= 0) {
2831 if (_settings_client
.gui
.smooth_scroll
) {
2832 int max_scroll
= ScaleByMapSize1D(512 * ZOOM_LVL_BASE
);
2833 /* Not at our desired position yet... */
2834 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
2835 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
2837 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
2838 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
2840 update_overlay
= (w
->viewport
->scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
2841 w
->viewport
->scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
2844 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2846 if (_scrolling_viewport
== w
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) {
2847 const int gap
= ScaleByZoom(1, ZOOM_LVL_MAX
);
2849 int lr_low
= vp
->virtual_left
;
2850 int lr_hi
= w
->viewport
->scrollpos_x
;
2851 if (lr_low
> lr_hi
) Swap(lr_low
, lr_hi
);
2852 int right
= lr_hi
+ vp
->virtual_width
+ gap
;
2854 int tb_low
= vp
->virtual_top
;
2855 int tb_hi
= w
->viewport
->scrollpos_y
;
2856 if (tb_low
> tb_hi
) Swap(tb_low
, tb_hi
);
2857 int bottom
= tb_hi
+ vp
->virtual_height
+ gap
;
2859 MarkAllViewportMapsDirty(lr_low
, tb_low
, right
, bottom
);
2862 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2863 if (update_overlay
) RebuildViewportOverlay(w
);
2868 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
2869 * @param vp The viewport to mark as dirty
2870 * @param left Left edge of area to repaint
2871 * @param top Top edge of area to repaint
2872 * @param right Right edge of area to repaint
2873 * @param bottom Bottom edge of area to repaint
2876 static void MarkViewportDirty(const ViewPort
* const vp
, int left
, int top
, int right
, int bottom
)
2878 /* Rounding wrt. zoom-out level */
2879 right
+= (1 << vp
->zoom
) - 1;
2880 bottom
+= (1 << vp
->zoom
) - 1;
2882 right
-= vp
->virtual_left
;
2883 if (right
<= 0) return;
2885 bottom
-= vp
->virtual_top
;
2886 if (bottom
<= 0) return;
2888 left
= max(0, left
- vp
->virtual_left
);
2890 if (left
>= vp
->virtual_width
) return;
2892 top
= max(0, top
- vp
->virtual_top
);
2894 if (top
>= vp
->virtual_height
) return;
2897 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
2898 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
2899 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
2900 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
2905 * Mark all viewports that display an area as dirty (in need of repaint).
2906 * @param left Left edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2907 * @param top Top edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2908 * @param right Right edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2909 * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2910 * @param mark_dirty_if_zoomlevel_is_below To tell if an update is relevant or not (for example, animations in map mode are not)
2913 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
)
2915 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2916 if (vp
->zoom
>= mark_dirty_if_zoomlevel_is_below
) continue;
2917 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
2921 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
)
2923 const uint size
= cit
->second
.size() > max_rank_order_type_count
? 1 : cit
->second
.size();
2924 MarkRouteStepDirty(cit
->first
, size
);
2927 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
)
2929 assert(tile
!= INVALID_TILE
);
2930 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2931 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
2932 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2933 const int half_width
= ScaleByZoom((_vp_route_step_width
/ 2) + 1, vp
->zoom
);
2934 const int height
= ScaleByZoom(_vp_route_step_height_top
+ char_height
* order_nr
+ _vp_route_step_height_bottom
, vp
->zoom
);
2935 MarkViewportDirty(vp
, pt
.x
- half_width
, pt
.y
- height
, pt
.x
+ half_width
, pt
.y
);
2939 void MarkAllRouteStepsDirty(const Vehicle
*veh
)
2941 ViewportPrepareVehicleRouteSteps(veh
);
2942 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
2943 MarkRouteStepDirty(cit
);
2945 _vp_route_steps_last_mark_dirty
.swap(_vp_route_steps
);
2946 _vp_route_steps
.clear();
2950 * Mark all viewports in map mode that display an area as dirty (in need of repaint).
2951 * @param left Left edge of area to repaint
2952 * @param top Top edge of area to repaint
2953 * @param right Right edge of area to repaint
2954 * @param bottom Bottom edge of area to repaint
2957 void MarkAllViewportMapsDirty(int left
, int top
, int right
, int bottom
)
2960 FOR_ALL_WINDOWS_FROM_BACK(w
) {
2961 const ViewPort
*vp
= w
->viewport
;
2962 if (vp
!= NULL
&& vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
2963 assert(vp
->width
!= 0);
2964 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
2969 void ConstrainAllViewportsZoom()
2972 FOR_ALL_WINDOWS_FROM_FRONT(w
) {
2973 if (w
->viewport
== NULL
) continue;
2975 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
2976 if (zoom
!= w
->viewport
->zoom
) {
2977 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
2978 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
2984 * Mark a tile given by its index dirty for repaint.
2985 * @param tile The tile to mark dirty.
2986 * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
2989 void MarkTileDirtyByTile(const TileIndex tile
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
, int bridge_level_offset
)
2991 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, TilePixelHeight(tile
));
2992 MarkAllViewportsDirty(
2993 pt
.x
- MAX_TILE_EXTENT_LEFT
,
2994 pt
.y
- MAX_TILE_EXTENT_TOP
- ZOOM_LVL_BASE
* TILE_HEIGHT
* bridge_level_offset
,
2995 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
2996 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
,
2997 mark_dirty_if_zoomlevel_is_below
);
3001 * Mark a (virtual) tile outside the map dirty for repaint.
3002 * @param x Tile X position.
3003 * @param y Tile Y position.
3006 void MarkTileDirtyByTileOutsideMap(int x
, int y
)
3008 Point pt
= RemapCoords(x
* TILE_SIZE
, y
* TILE_SIZE
, TilePixelHeightOutsideMap(x
, y
));
3009 MarkAllViewportsDirty(
3010 pt
.x
- MAX_TILE_EXTENT_LEFT
,
3011 pt
.y
, // no buildings outside of map
3012 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
3013 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
);
3016 void MarkTileLineDirty(const TileIndex from_tile
, const TileIndex to_tile
)
3018 assert(from_tile
!= INVALID_TILE
);
3019 assert(to_tile
!= INVALID_TILE
);
3021 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3022 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3024 const int block_radius
= 20;
3026 int x1
= from_pt
.x
/ block_radius
;
3027 int y1
= from_pt
.y
/ block_radius
;
3028 const int x2
= to_pt
.x
/ block_radius
;
3029 const int y2
= to_pt
.y
/ block_radius
;
3031 /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification */
3032 const int dx
= abs(x2
- x1
);
3033 const int dy
= abs(y2
- y1
);
3034 const int sx
= (x1
< x2
) ? 1 : -1;
3035 const int sy
= (y1
< y2
) ? 1 : -1;
3038 MarkAllViewportsDirty(
3039 (x1
- 1) * block_radius
,
3040 (y1
- 1) * block_radius
,
3041 (x1
+ 1) * block_radius
,
3042 (y1
+ 1) * block_radius
,
3045 if (x1
== x2
&& y1
== y2
) break;
3046 const int e2
= 2 * err
;
3058 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
)
3060 for (std::vector
<DrawnPathRouteTileLine
>::const_iterator it
= lines
.begin(); it
!= lines
.end(); ++it
) {
3061 MarkTileLineDirty(it
->from_tile
, it
->to_tile
);
3065 void MarkAllRoutePathsDirty(const Vehicle
*veh
)
3067 switch (_settings_client
.gui
.show_vehicle_route
) {
3072 ViewportMapPrepareVehicleRoute(veh
);
3076 for (const auto &iter
: _vp_route_paths
) {
3077 MarkTileLineDirty(iter
.from_tile
, iter
.to_tile
);
3080 _vp_route_paths_last_mark_dirty
.swap(_vp_route_paths
);
3081 _vp_route_paths
.clear();
3085 * Mark all tiles in a given bridge by their index dirty for repaint.
3086 * @param tile The start tile of the bridge to mark dirty.
3087 * @param tile The end tile of the bridge to mark dirty.
3090 void MarkBridgeTilesDirtyByTile(TileIndex tile_start
, TileIndex tile_end
)
3092 MarkTileDirtyByTile(tile_start
);
3093 MarkTileDirtyByTile(tile_end
);
3096 if (TileX(tile_start
) == TileX(tile_end
)) {
3098 } else if (TileY(tile_start
) == TileY(tile_end
)) {
3102 if (tile_end
< tile_start
) Swap(tile_start
, tile_end
);
3104 TileIndexDiff delta
= (direction
== AXIS_X
? TileDiffXY(1, 0) : TileDiffXY(0, 1));
3105 for (TileIndex tile
= tile_start
; tile
<= tile_end
; tile
+= delta
) {
3106 MarkTileDirtyByTile(tile
);
3110 void CheckMarkDirtyFocusedRoutePaths(const Vehicle
*veh
)
3112 const Vehicle
*focused_veh
= GetVehicleFromWindow(_focused_window
);
3113 if (focused_veh
&& veh
== focused_veh
) {
3114 MarkAllRoutePathsDirty(veh
);
3115 MarkAllRouteStepsDirty(veh
);
3120 * Marks the selected tiles as dirty.
3122 * This function marks the selected tiles as dirty for repaint
3126 static void SetSelectionTilesDirty()
3128 int x_size
= _thd
.size
.x
;
3129 int y_size
= _thd
.size
.y
;
3131 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
3132 int x_start
= _thd
.pos
.x
;
3133 int y_start
= _thd
.pos
.y
;
3135 if (_thd
.outersize
.x
!= 0 || _thd
.outersize
.y
!= 0) {
3136 x_size
+= _thd
.outersize
.x
;
3137 x_start
+= _thd
.offs
.x
;
3138 y_size
+= _thd
.outersize
.y
;
3139 y_start
+= _thd
.offs
.y
;
3142 x_size
-= TILE_SIZE
;
3143 y_size
-= TILE_SIZE
;
3145 assert(x_size
>= 0);
3146 assert(y_size
>= 0);
3148 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3149 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3151 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3152 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3154 /* make sure everything is multiple of TILE_SIZE */
3155 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
3158 * Suppose we have to mark dirty rectangle of 3x4 tiles:
3165 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
3175 int top_x
= x_end
; // coordinates of top dirty tile
3176 int top_y
= y_start
;
3177 int bot_x
= top_x
; // coordinates of bottom dirty tile
3181 /* topmost dirty point */
3182 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
3183 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxPixelZ(top_tile
));
3185 /* bottommost point */
3186 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
3187 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTilePixelZ(bottom_tile
)); // bottommost point
3189 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
3190 * tile height/slope affects only the 'y' on-screen coordinate! */
3192 int l
= top
.x
- TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of left side of the dirty rectangle
3193 int t
= top
.y
; // 'y' coordinate of top side of the dirty rectangle
3194 int r
= top
.x
+ TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of right side of the dirty rectangle
3195 int b
= bot
.y
; // 'y' coordinate of bottom side of the dirty rectangle
3197 static const int OVERLAY_WIDTH
= 4 * ZOOM_LVL_BASE
; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
3199 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
3200 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_LVL_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
);
3202 /* haven't we reached the topmost tile yet? */
3203 if (top_x
!= x_start
) {
3209 /* the way the bottom tile changes is different when we reach the bottommost tile */
3210 if (bot_y
!= y_end
) {
3215 } while (bot_x
>= top_x
);
3216 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
3217 /* a_size, b_size describe a rectangle with rotated coordinates */
3218 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
3220 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3221 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3223 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
3224 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
3225 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
3226 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
3228 if (x
< MapMaxX() && y
< MapMaxY()) {
3229 MarkTileDirtyByTile(TileXY(x
, y
));
3237 void SetSelectionRed(bool b
)
3239 _thd
.make_square_red
= b
;
3240 SetSelectionTilesDirty();
3244 * Test whether a sign is below the mouse
3245 * @param vp the clicked viewport
3246 * @param x X position of click
3247 * @param y Y position of click
3248 * @param sign the sign to check
3249 * @return true if the sign was hit
3251 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
3253 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_16X
);
3254 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
3255 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
3257 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
3258 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
3260 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
3261 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
3264 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
3266 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
3270 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) {
3271 ShowTownViewWindow(t
->index
);
3279 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
3281 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
3283 const BaseStation
*st
;
3284 FOR_ALL_BASE_STATIONS(st
) {
3285 if (!IsStationSignVisible(st
)) continue;
3287 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3288 if (Station::IsExpected(st
)) {
3289 ShowStationViewWindow(st
->index
);
3291 ShowWaypointWindow(Waypoint::From(st
));
3301 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
3303 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
3304 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
3308 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
3309 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
3310 if (si
->owner
== OWNER_DEITY
&& _game_mode
!= GM_EDITOR
) continue;
3312 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
3313 HandleClickOnSign(si
);
3322 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
3324 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
3326 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3327 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3329 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
3333 static void PlaceObject()
3338 pt
= GetTileBelowCursor();
3339 if (pt
.x
== -1) return;
3341 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
3342 pt
.x
+= TILE_SIZE
/ 2;
3343 pt
.y
+= TILE_SIZE
/ 2;
3346 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3347 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3349 w
= _thd
.GetCallbackWnd();
3350 if (w
!= NULL
) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
3353 bool HandleViewportDoubleClicked(Window
*w
, int x
, int y
)
3355 ViewPort
*vp
= w
->viewport
;
3356 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return false;
3358 switch (_settings_client
.gui
.action_when_viewport_map_is_dblclicked
) {
3359 case 0: // Do nothing
3361 case 1: // Zoom in main viewport
3362 while (vp
->zoom
!= ZOOM_LVL_VIEWPORT
)
3363 ZoomInOrOutToCursorWindow(true, w
);
3365 case 2: // Open an extra viewport
3366 ShowExtraViewPortWindowForTileUnderCursor();
3373 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
, bool double_click
)
3375 /* No click in smallmap mode except for plan making. */
3376 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !(_thd
.place_mode
== HT_POINT
&& _thd
.select_proc
== DDSP_DRAW_PLANLINE
)) return true;
3378 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
3380 if (_thd
.place_mode
& HT_VEHICLE
) {
3381 if (v
!= NULL
&& VehicleClicked(v
)) return true;
3384 /* Vehicle placement mode already handled above. */
3385 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3386 if (_thd
.place_mode
& HT_POLY
) {
3387 /* In polyline mode double-clicking on a single white line, finishes current polyline.
3388 * If however the user double-clicks on a line that has a white and a blue section,
3389 * both lines (white and blue) will be constructed consecutively. */
3390 static bool stop_snap_on_double_click
= false;
3391 if (double_click
&& stop_snap_on_double_click
) {
3392 SetRailSnapMode(RSM_NO_SNAP
);
3395 stop_snap_on_double_click
= !(_thd
.drawstyle
& HT_LINE
) || (_thd
.dir2
== HT_DIR_END
);
3402 if (CheckClickOnTown(vp
, x
, y
)) return true;
3403 if (CheckClickOnStation(vp
, x
, y
)) return true;
3404 if (CheckClickOnSign(vp
, x
, y
)) return true;
3405 bool result
= CheckClickOnLandscape(vp
, x
, y
);
3408 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
3409 if (IsCompanyBuildableVehicleType(v
)) {
3411 WindowClass wc
= _thd
.GetCallbackWnd()->window_class
;
3412 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
3413 StartStopVehicle(v
, true);
3414 } else if ( wc
!= WC_CREATE_TEMPLATE
&& wc
!= WC_TEMPLATEGUI_MAIN
) {
3415 ShowVehicleViewWindow(v
);
3423 void HandleViewportToolTip(Window
*w
, int x
, int y
)
3425 const ViewportData
*vp
= w
->viewport
;
3426 if (vp
== NULL
|| _game_mode
== GM_MENU
|| HasModalProgress()) return;
3428 TooltipCloseCondition close_cond
= (_settings_client
.gui
.hover_delay_ms
== 0) ? TCC_RIGHT_CLICK
: TCC_HOVER
;
3430 const BaseStation
*st
;
3431 FOR_ALL_BASE_STATIONS(st
) {
3432 if (IsStationSignVisible(st
) && CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3433 char tip
[DRAW_STRING_BUFFER
] = "";
3434 GetTileTooltipsForStation(st
->index
, tip
, lastof(tip
));
3435 GuiShowTooltips(w
, tip
, close_cond
);
3440 Point tile
= TranslateXYToTileCoord(vp
, x
, y
, false);
3441 if (IsInsideMM(tile
.x
, 0, MapMaxX() * TILE_SIZE
) && IsInsideMM(tile
.y
, 0, MapMaxY() * TILE_SIZE
)) {
3442 GuiShowTooltipsForTile(w
, TileVirtXY(tile
.x
, tile
.y
), close_cond
);
3446 DeleteWindowById(WC_TOOLTIPS
, 0); // close old tooltips window as now hovering this viewport
3449 void RebuildViewportOverlay(Window
*w
)
3451 if (w
->viewport
->overlay
!= NULL
&&
3452 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
3453 w
->viewport
->overlay
->GetCargoMask() != 0) {
3454 w
->viewport
->overlay
->RebuildCache();
3460 * Scrolls the viewport in a window to a given location.
3461 * @param x Desired x location of the map to scroll to (world coordinate).
3462 * @param y Desired y location of the map to scroll to (world coordinate).
3463 * @param z Desired z location of the map to scroll to (world coordinate). Use \c -1 to scroll to the height of the map at the \a x, \a y location.
3464 * @param w %Window containing the viewport.
3465 * @param instant Jump to the location instead of slowly moving to it.
3466 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3468 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
3470 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
3472 if ( x
>= 0 && x
<= (int)MapSizeX() * (int)TILE_SIZE
- 1
3473 && y
>= 0 && y
<= (int)MapSizeY() * (int)TILE_SIZE
- 1) {
3474 z
= GetSlopePixelZ(x
, y
);
3476 z
= TileHeightOutsideMap(x
/ (int)TILE_SIZE
, y
/ (int)TILE_SIZE
);
3480 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
3481 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
3483 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
3486 w
->viewport
->scrollpos_x
= pt
.x
;
3487 w
->viewport
->scrollpos_y
= pt
.y
;
3488 RebuildViewportOverlay(w
);
3491 w
->viewport
->dest_scrollpos_x
= pt
.x
;
3492 w
->viewport
->dest_scrollpos_y
= pt
.y
;
3497 * Scrolls the viewport in a window to a given location.
3498 * @param tile Desired tile to center on.
3499 * @param w %Window containing the viewport.
3500 * @param instant Jump to the location instead of slowly moving to it.
3501 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3503 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
3505 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
3509 * Scrolls the viewport of the main window to a given location.
3510 * @param tile Desired tile to center on.
3511 * @param instant Jump to the location instead of slowly moving to it.
3512 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3514 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
3516 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
3520 * Set a tile to display a red error square.
3521 * @param tile Tile that should show the red error square.
3523 void SetRedErrorSquare(TileIndex tile
)
3531 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
3532 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
, ZOOM_LVL_DRAW_MAP
);
3537 * Highlight \a w by \a h tiles at the cursor.
3538 * @param w Width of the highlighted tiles rectangle.
3539 * @param h Height of the highlighted tiles rectangle.
3541 void SetTileSelectSize(int w
, int h
)
3543 _thd
.new_size
.x
= w
* TILE_SIZE
;
3544 _thd
.new_size
.y
= h
* TILE_SIZE
;
3545 _thd
.new_outersize
.x
= 0;
3546 _thd
.new_outersize
.y
= 0;
3549 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
3551 _thd
.new_offs
.x
= ox
* TILE_SIZE
;
3552 _thd
.new_offs
.y
= oy
* TILE_SIZE
;
3553 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
3554 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
3557 /** returns the best autorail highlight type from map coordinates */
3558 static HighLightStyle
GetAutorailHT(int x
, int y
)
3560 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
3564 * Reset tile highlighting.
3566 void TileHighlightData::Reset()
3570 this->new_pos
.x
= 0;
3571 this->new_pos
.y
= 0;
3575 * Is the user dragging a 'diagonal rectangle'?
3576 * @return User is dragging a rotated rectangle.
3578 bool TileHighlightData::IsDraggingDiagonal()
3580 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
3584 * Get the window that started the current highlighting.
3585 * @return The window that requested the current tile highlighting, or \c NULL if not available.
3587 Window
*TileHighlightData::GetCallbackWnd()
3589 return FindWindowById(this->window_class
, this->window_number
);
3592 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
);
3594 static inline void CalcNewPolylineOutersize()
3596 /* use the 'outersize' to mark the second (blue) part of a polyline selection */
3597 if (_thd
.dir2
< HT_DIR_END
) {
3598 /* get bounds of the second part */
3599 int outer_x1
= _thd
.selstart2
.x
& ~TILE_UNIT_MASK
;
3600 int outer_y1
= _thd
.selstart2
.y
& ~TILE_UNIT_MASK
;
3601 int outer_x2
= _thd
.selend2
.x
& ~TILE_UNIT_MASK
;
3602 int outer_y2
= _thd
.selend2
.y
& ~TILE_UNIT_MASK
;
3603 if (outer_x1
> outer_x2
) Swap(outer_x1
, outer_x2
);
3604 if (outer_y1
> outer_y2
) Swap(outer_y1
, outer_y2
);
3605 /* include the first part */
3606 outer_x1
= min
<int>(outer_x1
, _thd
.new_pos
.x
);
3607 outer_y1
= min
<int>(outer_y1
, _thd
.new_pos
.y
);
3608 outer_x2
= max
<int>(outer_x2
, _thd
.new_pos
.x
+ _thd
.new_size
.x
- TILE_SIZE
);
3609 outer_y2
= max
<int>(outer_y2
, _thd
.new_pos
.y
+ _thd
.new_size
.y
- TILE_SIZE
);
3610 /* write new values */
3611 _thd
.new_offs
.x
= outer_x1
- _thd
.new_pos
.x
;
3612 _thd
.new_offs
.y
= outer_y1
- _thd
.new_pos
.y
;
3613 _thd
.new_outersize
.x
= outer_x2
- outer_x1
+ TILE_SIZE
- _thd
.new_size
.x
;
3614 _thd
.new_outersize
.y
= outer_y2
- outer_y1
+ TILE_SIZE
- _thd
.new_size
.y
;
3616 _thd
.new_offs
.x
= 0;
3617 _thd
.new_offs
.y
= 0;
3618 _thd
.new_outersize
.x
= 0;
3619 _thd
.new_outersize
.y
= 0;
3624 * Updates tile highlighting for all cases.
3625 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
3626 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
3627 * Once for the old and once for the new selection.
3628 * _thd is TileHighlightData, found in viewport.h
3630 void UpdateTileSelection()
3635 HighLightStyle new_drawstyle
= HT_NONE
;
3636 bool new_diagonal
= false;
3638 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
3642 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3643 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3644 x1
&= ~TILE_UNIT_MASK
;
3645 y1
&= ~TILE_UNIT_MASK
;
3647 if (_thd
.IsDraggingDiagonal()) {
3648 new_diagonal
= true;
3650 if (x1
>= x2
) Swap(x1
, x2
);
3651 if (y1
>= y2
) Swap(y1
, y2
);
3653 _thd
.new_pos
.x
= x1
;
3654 _thd
.new_pos
.y
= y1
;
3655 _thd
.new_size
.x
= x2
- x1
;
3656 _thd
.new_size
.y
= y2
- y1
;
3657 if (!new_diagonal
) {
3658 _thd
.new_size
.x
+= TILE_SIZE
;
3659 _thd
.new_size
.y
+= TILE_SIZE
;
3661 new_drawstyle
= _thd
.next_drawstyle
;
3663 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3664 Point pt
= GetTileBelowCursor();
3668 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
3670 new_drawstyle
= HT_RECT
;
3673 new_drawstyle
= HT_POINT
;
3674 x1
+= TILE_SIZE
/ 2;
3675 y1
+= TILE_SIZE
/ 2;
3680 if (_thd
.place_mode
& HT_POLY
) {
3681 RailSnapMode snap_mode
= GetRailSnapMode();
3682 if (snap_mode
== RSM_NO_SNAP
||
3683 (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
))) {
3684 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3685 _thd
.new_offs
.x
= 0;
3686 _thd
.new_offs
.y
= 0;
3687 _thd
.new_outersize
.x
= 0;
3688 _thd
.new_outersize
.y
= 0;
3689 _thd
.dir2
= HT_DIR_END
;
3691 new_drawstyle
= CalcPolyrailDrawstyle(pt
, false);
3692 if (new_drawstyle
!= HT_NONE
) {
3693 x1
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3694 y1
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3695 int x2
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
3696 int y2
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
3697 if (x1
> x2
) Swap(x1
, x2
);
3698 if (y1
> y2
) Swap(y1
, y2
);
3699 _thd
.new_pos
.x
= x1
;
3700 _thd
.new_pos
.y
= y1
;
3701 _thd
.new_size
.x
= x2
- x1
+ TILE_SIZE
;
3702 _thd
.new_size
.y
= y2
- y1
+ TILE_SIZE
;
3708 if (_thd
.place_mode
& HT_RAIL
) {
3709 /* Draw one highlighted tile in any direction */
3710 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3714 switch (_thd
.place_mode
& HT_DIR_MASK
) {
3715 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
3716 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
3720 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
3725 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
3728 default: NOT_REACHED();
3730 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
3731 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
3739 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
3740 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
3744 if (new_drawstyle
& HT_LINE
) CalcNewPolylineOutersize();
3746 /* redraw selection */
3747 if (_thd
.drawstyle
!= new_drawstyle
||
3748 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
3749 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
3750 _thd
.offs
.x
!= _thd
.new_offs
.x
|| _thd
.offs
.y
!= _thd
.new_offs
.y
||
3751 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
3752 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
3753 _thd
.diagonal
!= new_diagonal
) {
3754 /* Clear the old tile selection? */
3755 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3757 _thd
.drawstyle
= new_drawstyle
;
3758 _thd
.pos
= _thd
.new_pos
;
3759 _thd
.size
= _thd
.new_size
;
3760 _thd
.offs
= _thd
.new_offs
;
3761 _thd
.outersize
= _thd
.new_outersize
;
3762 _thd
.diagonal
= new_diagonal
;
3765 /* Draw the new tile selection? */
3766 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3771 * Displays the measurement tooltips when selecting multiple tiles
3772 * @param str String to be displayed
3773 * @param close_cond Condition for closing this tooltip.
3775 static inline void ShowMeasurementTooltips(StringID str
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
3777 if (!_settings_client
.gui
.measure_tooltip
) return;
3778 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, close_cond
);
3781 /** highlighting tiles while only going over them with the mouse */
3782 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
3784 _thd
.select_method
= method
;
3785 _thd
.select_proc
= process
;
3786 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
3787 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
3788 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
3789 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
3791 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
3792 * In effect, placement starts from the centre of a tile
3794 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
3795 _thd
.selend
.x
+= TILE_SIZE
/ 2;
3796 _thd
.selend
.y
+= TILE_SIZE
/ 2;
3797 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
3798 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
3801 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
3802 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
3803 _thd
.place_mode
= HT_SPECIAL
| others
;
3804 _thd
.next_drawstyle
= HT_RECT
| others
;
3805 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
3806 _thd
.place_mode
= HT_SPECIAL
| others
;
3807 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
3808 _current_snap_lock
.x
= -1;
3809 if ((_thd
.place_mode
& HT_POLY
) != 0 && GetRailSnapMode() == RSM_NO_SNAP
) {
3810 SetRailSnapMode(RSM_SNAP_TO_TILE
);
3811 SetRailSnapTile(tile
);
3814 _thd
.place_mode
= HT_SPECIAL
| others
;
3815 _thd
.next_drawstyle
= HT_POINT
| others
;
3817 _special_mouse_mode
= WSM_SIZING
;
3820 void VpSetPlaceSizingLimit(int limit
)
3822 _thd
.sizelimit
= limit
;
3826 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
3827 * @param from TileIndex of the first tile to highlight
3828 * @param to TileIndex of the last tile to highlight
3830 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
3832 uint64 distance
= DistanceManhattan(from
, to
) + 1;
3834 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
3835 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
3836 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
3837 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
3838 _thd
.next_drawstyle
= HT_RECT
;
3840 /* show measurement only if there is any length to speak of */
3842 SetDParam(0, distance
);
3843 ShowMeasurementTooltips(STR_MEASURE_LENGTH
, TCC_HOVER
);
3847 static void VpStartPreSizing()
3850 _special_mouse_mode
= WSM_PRESIZE
;
3854 * returns information about the 2x1 piece to be build.
3855 * The lower bits (0-3) are the track type.
3857 static HighLightStyle
Check2x1AutoRail(int mode
)
3859 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
3860 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
3861 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
3862 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
3865 default: NOT_REACHED();
3866 case 0: // end piece is lower right
3867 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3868 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3872 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3873 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3877 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3878 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3882 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3883 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3889 * Check if the direction of start and end tile should be swapped based on
3890 * the dragging-style. Default directions are:
3891 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
3892 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
3893 * For example dragging a rectangle area from south to north should be swapped to
3894 * north-south (DIR_S) to obtain the same results with less code. This is what
3895 * the return value signifies.
3896 * @param style HighLightStyle dragging style
3897 * @param start_tile start tile of drag
3898 * @param end_tile end tile of drag
3899 * @return boolean value which when true means start/end should be swapped
3901 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
3903 uint start_x
= TileX(start_tile
);
3904 uint start_y
= TileY(start_tile
);
3905 uint end_x
= TileX(end_tile
);
3906 uint end_y
= TileY(end_tile
);
3908 switch (style
& HT_DRAG_MASK
) {
3910 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
3913 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
3914 default: NOT_REACHED();
3921 * Calculates height difference between one tile and another.
3922 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
3924 * To correctly get the height difference we need the direction we are dragging
3925 * in, as well as with what kind of tool we are dragging. For example a horizontal
3926 * autorail tool that starts in bottom and ends at the top of a tile will need the
3927 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
3928 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
3929 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
3930 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
3931 * @param start_tile Start tile of the drag operation.
3932 * @param end_tile End tile of the drag operation.
3933 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
3935 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
3937 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
3938 uint h0
, h1
; // Start height and end height.
3940 if (start_tile
== end_tile
) return 0;
3941 if (swap
) Swap(start_tile
, end_tile
);
3943 switch (style
& HT_DRAG_MASK
) {
3945 static const TileIndexDiffC heightdiff_area_by_dir
[] = {
3946 /* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
3947 /* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
3950 /* In the case of an area we can determine whether we were dragging south or
3951 * east by checking the X-coordinates of the tiles */
3952 byte style_t
= (byte
)(TileX(end_tile
) > TileX(start_tile
));
3953 start_tile
= TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[style_t
]));
3954 end_tile
= TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[2 + style_t
]));
3959 h0
= TileHeight(start_tile
);
3960 h1
= TileHeight(end_tile
);
3962 default: { // All other types, this is mostly only line/autorail
3963 static const HighLightStyle flip_style_direction
[] = {
3964 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
3966 static const TileIndexDiffC heightdiff_line_by_dir
[] = {
3967 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
3968 /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
3969 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
3971 /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X */ {1, 0}, {0, 0}, // HT_DIR_Y
3972 /* End */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
3973 /* End */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
3976 distance
%= 2; // we're only interested if the distance is even or uneven
3977 style
&= HT_DIR_MASK
;
3979 /* To handle autorail, we do some magic to be able to use a lookup table.
3980 * Firstly if we drag the other way around, we switch start&end, and if needed
3981 * also flip the drag-position. Eg if it was on the left, and the distance is even
3982 * that means the end, which is now the start is on the right */
3983 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
3985 /* Use lookup table for start-tile based on HighLightStyle direction */
3986 byte style_t
= style
* 2;
3987 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
3988 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
])));
3989 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
+ 1])));
3992 /* Use lookup table for end-tile based on HighLightStyle direction
3993 * flip around side (lower/upper, left/right) based on distance */
3994 if (distance
== 0) style_t
= flip_style_direction
[style
] * 2;
3995 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
3996 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
])));
3997 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
+ 1])));
4003 if (swap
) Swap(h0
, h1
);
4004 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
4007 static void ShowLengthMeasurement(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
, bool show_single_tile_length
= false)
4009 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
4011 if (_settings_client
.gui
.measure_tooltip
) {
4012 uint distance
= DistanceManhattan(start_tile
, end_tile
) + 1;
4015 if (show_single_tile_length
|| distance
!= 1) {
4016 int heightdiff
= CalcHeightdiff(style
, distance
, start_tile
, end_tile
);
4017 /* If we are showing a tooltip for horizontal or vertical drags,
4018 * 2 tiles have a length of 1. To bias towards the ceiling we add
4019 * one before division. It feels more natural to count 3 lengths as 2 */
4020 if ((style
& HT_DIR_MASK
) != HT_DIR_X
&& (style
& HT_DIR_MASK
) != HT_DIR_Y
) {
4021 distance
= CeilDiv(distance
, 2);
4024 SetDParam(index
++, distance
);
4025 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4028 ShowMeasurementTooltips(measure_strings_length
[index
]);
4033 * Check for underflowing the map.
4034 * @param test the variable to test for underflowing
4035 * @param other the other variable to update to keep the line
4036 * @param mult the constant to multiply the difference by for \c other
4038 static void CheckUnderflow(int &test
, int &other
, int mult
)
4040 if (test
>= 0) return;
4042 other
+= mult
* test
;
4047 * Check for overflowing the map.
4048 * @param test the variable to test for overflowing
4049 * @param other the other variable to update to keep the line
4050 * @param max the maximum value for the \c test variable
4051 * @param mult the constant to multiply the difference by for \c other
4053 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
4055 if (test
<= max
) return;
4057 other
+= mult
* (test
- max
);
4061 static const uint X_DIRS
= (1 << DIR_NE
) | (1 << DIR_SW
);
4062 static const uint Y_DIRS
= (1 << DIR_SE
) | (1 << DIR_NW
);
4063 static const uint HORZ_DIRS
= (1 << DIR_W
) | (1 << DIR_E
);
4064 static const uint VERT_DIRS
= (1 << DIR_N
) | (1 << DIR_S
);
4066 Trackdir
PointDirToTrackdir(const Point
&pt
, Direction dir
)
4070 if (IsDiagonalDirection(dir
)) {
4071 ret
= DiagDirToDiagTrackdir(DirToDiagDir(dir
));
4073 int x
= pt
.x
& TILE_UNIT_MASK
;
4074 int y
= pt
.y
& TILE_UNIT_MASK
;
4077 if (HasBit(HORZ_DIRS
, dir
)) {
4078 ret
= TrackDirectionToTrackdir(ns
< (int)TILE_SIZE
? TRACK_UPPER
: TRACK_LOWER
, dir
);
4080 ret
= TrackDirectionToTrackdir(we
< 0 ? TRACK_LEFT
: TRACK_RIGHT
, dir
);
4087 static bool FindPolyline(const Point
&pt
, const LineSnapPoint
&start
, Polyline
*ret
)
4089 /* relative coordinats of the mouse point (offset against the snap point) */
4090 int x
= pt
.x
- start
.x
;
4091 int y
= pt
.y
- start
.y
;
4095 /* in-tile alignment of the snap point (there are two variants: [0, 8] or [8, 0]) */
4096 uint align_x
= start
.x
& TILE_UNIT_MASK
;
4097 uint align_y
= start
.y
& TILE_UNIT_MASK
;
4098 assert((align_x
== TILE_SIZE
/ 2 && align_y
== 0 && !(start
.dirs
& X_DIRS
)) || (align_x
== 0 && align_y
== TILE_SIZE
/ 2 && !(start
.dirs
& Y_DIRS
)));
4100 /* absolute distance between points (in tiles) */
4101 uint d_x
= abs(RoundDivSU(x
< 0 ? x
- align_y
: x
+ align_y
, TILE_SIZE
));
4102 uint d_y
= abs(RoundDivSU(y
< 0 ? y
- align_x
: y
+ align_x
, TILE_SIZE
));
4103 uint d_ns
= abs(RoundDivSU(ns
, TILE_SIZE
));
4104 uint d_we
= abs(RoundDivSU(we
, TILE_SIZE
));
4106 /* Find on which quadrant is the mouse point (reltively to the snap point).
4107 * Numeration (clockwise like in Direction):
4114 uint ortho_quadrant
= 2 * (x
< 0) + ((x
< 0) != (y
< 0)); // implicit cast: false/true --> 0/1
4115 uint diag_quadrant
= 2 * (ns
< 0) + ((ns
< 0) != (we
< 0));
4117 /* direction from the snap point to the mouse point */
4118 Direction ortho_line_dir
= ChangeDir(DIR_S
, (DirDiff
)(2 * ortho_quadrant
)); // DIR_S is the middle of the ortho quadrant no. 0
4119 Direction diag_line_dir
= ChangeDir(DIR_SE
, (DirDiff
)(2 * diag_quadrant
)); // DIR_SE is the middle of the diag quadrant no. 0
4120 if (!HasBit(start
.dirs
, ortho_line_dir
) && !HasBit(start
.dirs
, diag_line_dir
)) return false;
4122 /* length of booth segments of auto line (choosing orthogonal direction first) */
4123 uint ortho_len
= 0, ortho_len2
= 0;
4124 if (HasBit(start
.dirs
, ortho_line_dir
)) {
4125 bool is_len_even
= (align_x
!= 0) ? d_x
>= d_y
: d_x
<= d_y
;
4126 ortho_len
= 2 * min(d_x
, d_y
) - (int)is_len_even
;
4127 assert((int)ortho_len
>= 0);
4128 if (d_ns
== 0 || d_we
== 0) { // just single segment?
4131 ortho_len2
= abs((int)d_x
- (int)d_y
) + (int)is_len_even
;
4135 /* length of booth segments of auto line (choosing diagonal direction first) */
4136 uint diag_len
= 0, diag_len2
= 0;
4137 if (HasBit(start
.dirs
, diag_line_dir
)) {
4138 if (d_x
== 0 || d_y
== 0) { // just single segment?
4139 diag_len
= d_x
+ d_y
;
4141 diag_len
= min(d_ns
, d_we
);
4142 diag_len2
= d_x
+ d_y
- diag_len
;
4146 /* choose the best variant */
4147 if (ortho_len
!= 0 && diag_len
!= 0) {
4148 /* in the first place, choose this line whose first segment ends up closer
4149 * to the mouse point (thus the second segment is shorter) */
4150 int cmp
= ortho_len2
- diag_len2
;
4151 /* if equeal, choose the shorter line */
4152 if (cmp
== 0) cmp
= ortho_len
- diag_len
;
4153 /* finally look at small "units" and choose the line which is closer to the mouse point */
4154 if (cmp
== 0) cmp
= min(abs(we
), abs(ns
)) - min(abs(x
), abs(y
));
4155 /* based on comparison, disable one of variants */
4164 if (ortho_len
!= 0) {
4165 ret
->first_dir
= ortho_line_dir
;
4166 ret
->first_len
= ortho_len
;
4167 ret
->second_dir
= (ortho_len2
!= 0) ? diag_line_dir
: INVALID_DIR
;
4168 ret
->second_len
= ortho_len2
;
4169 } else if (diag_len
!= 0) {
4170 ret
->first_dir
= diag_line_dir
;
4171 ret
->first_len
= diag_len
;
4172 ret
->second_dir
= (diag_len2
!= 0) ? ortho_line_dir
: INVALID_DIR
;
4173 ret
->second_len
= diag_len2
;
4183 * Calculate squared euclidean distance between two points.
4184 * @param a the first point
4185 * @param b the second point
4186 * @return |b - a| ^ 2
4188 static inline uint
SqrDist(const Point
&a
, const Point
&b
)
4190 return (b
.x
- a
.x
) * (b
.x
- a
.x
) + (b
.y
- a
.y
) * (b
.y
- a
.y
);
4193 static LineSnapPoint
*FindBestPolyline(const Point
&pt
, LineSnapPoint
*snap_points
, uint num_points
, Polyline
*ret
)
4195 /* Find the best polyline (a pair of two lines - the white one and the blue
4196 * one) led from any of saved snap points to the mouse cursor. */
4198 LineSnapPoint
*best_snap_point
= NULL
; // the best polyline we found so far is led from this snap point
4200 for (int i
= 0; i
< (int)num_points
; i
++) {
4201 /* try to fit a polyline */
4203 if (!FindPolyline(pt
, snap_points
[i
], &polyline
)) continue; // skip non-matching snap points
4204 /* check whether we've found a better polyline */
4205 if (best_snap_point
!= NULL
) {
4206 /* firstly choose shorter polyline (the one with smaller amount of
4207 * track pieces composing booth the white and the blue line) */
4208 uint cur_len
= polyline
.first_len
+ polyline
.second_len
;
4209 uint best_len
= ret
->first_len
+ ret
->second_len
;
4210 if (cur_len
> best_len
) continue;
4211 /* secondly choose that polyline which has longer first (white) line */
4212 if (cur_len
== best_len
&& polyline
.first_len
< ret
->first_len
) continue;
4213 /* finally check euclidean distance to snap points and choose the
4214 * one which is closer */
4215 if (cur_len
== best_len
&& polyline
.first_len
== ret
->first_len
&& SqrDist(pt
, snap_points
[i
]) >= SqrDist(pt
, *best_snap_point
)) continue;
4217 /* save the found polyline */
4219 best_snap_point
= &snap_points
[i
];
4222 return best_snap_point
;
4225 /** while dragging */
4226 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
4230 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
4231 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
4232 uint w
= abs(dx
) + TILE_SIZE
;
4233 uint h
= abs(dy
) + TILE_SIZE
;
4235 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4236 /* We 'force' a selection direction; first four rail buttons. */
4237 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
4238 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
4239 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
4242 b
= HT_LINE
| HT_DIR_Y
;
4243 x
= _thd
.selstart
.x
;
4247 b
= HT_LINE
| HT_DIR_X
;
4248 y
= _thd
.selstart
.y
;
4251 case VPM_FIX_HORIZONTAL
:
4253 /* We are on a straight horizontal line. Determine the 'rail'
4254 * to build based the sub tile location. */
4255 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4257 /* We are not on a straight line. Determine the rail to build
4258 * based on whether we are above or below it. */
4259 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
4261 /* Calculate where a horizontal line through the start point and
4262 * a vertical line from the selected end point intersect and
4263 * use that point as the end point. */
4264 int offset
= (raw_dx
- raw_dy
) / 2;
4265 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4266 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
4268 /* 'Build' the last half rail tile if needed */
4269 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4270 if (dx
+ dy
>= (int)TILE_SIZE
) {
4271 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4273 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4277 /* Make sure we do not overflow the map! */
4278 CheckUnderflow(x
, y
, 1);
4279 CheckUnderflow(y
, x
, 1);
4280 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
4281 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
4282 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4286 case VPM_FIX_VERTICAL
:
4288 /* We are on a straight vertical line. Determine the 'rail'
4289 * to build based the sub tile location. */
4290 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4292 /* We are not on a straight line. Determine the rail to build
4293 * based on whether we are left or right from it. */
4294 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4296 /* Calculate where a vertical line through the start point and
4297 * a horizontal line from the selected end point intersect and
4298 * use that point as the end point. */
4299 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
4300 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4301 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
4303 /* 'Build' the last half rail tile if needed */
4304 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4306 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4308 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4312 /* Make sure we do not overflow the map! */
4313 CheckUnderflow(x
, y
, -1);
4314 CheckUnderflow(y
, x
, -1);
4315 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
4316 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
4317 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4324 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
4325 if (method
& VPM_RAILDIRS
) {
4326 b
= GetAutorailHT(x
, y
);
4327 } else { // rect for autosignals on one tile
4330 } else if (h
== TILE_SIZE
) { // Is this in X direction?
4331 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
4332 b
= (Check2x1AutoRail(3)) | HT_LINE
;
4333 } else if (dx
== -(int)TILE_SIZE
) {
4334 b
= (Check2x1AutoRail(2)) | HT_LINE
;
4336 b
= HT_LINE
| HT_DIR_X
;
4338 y
= _thd
.selstart
.y
;
4339 } else if (w
== TILE_SIZE
) { // Or Y direction?
4340 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
4341 b
= (Check2x1AutoRail(1)) | HT_LINE
;
4342 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
4343 b
= (Check2x1AutoRail(0)) | HT_LINE
;
4345 b
= HT_LINE
| HT_DIR_Y
;
4347 x
= _thd
.selstart
.x
;
4348 } else if (w
> h
* 2) { // still count as x dir?
4349 b
= HT_LINE
| HT_DIR_X
;
4350 y
= _thd
.selstart
.y
;
4351 } else if (h
> w
* 2) { // still count as y dir?
4352 b
= HT_LINE
| HT_DIR_Y
;
4353 x
= _thd
.selstart
.x
;
4354 } else { // complicated direction
4356 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
4357 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
4360 if (x
> _thd
.selstart
.x
) {
4361 if (y
> _thd
.selstart
.y
) {
4364 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4365 } else if (d
>= 0) {
4366 x
= _thd
.selstart
.x
+ h
;
4367 b
= HT_LINE
| HT_DIR_VL
;
4369 y
= _thd
.selstart
.y
+ w
;
4370 b
= HT_LINE
| HT_DIR_VR
;
4375 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4376 } else if (d
>= 0) {
4377 x
= _thd
.selstart
.x
+ h
;
4378 b
= HT_LINE
| HT_DIR_HL
;
4380 y
= _thd
.selstart
.y
- w
;
4381 b
= HT_LINE
| HT_DIR_HU
;
4385 if (y
> _thd
.selstart
.y
) {
4388 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4389 } else if (d
>= 0) {
4390 x
= _thd
.selstart
.x
- h
;
4391 b
= HT_LINE
| HT_DIR_HU
;
4393 y
= _thd
.selstart
.y
+ w
;
4394 b
= HT_LINE
| HT_DIR_HL
;
4399 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4400 } else if (d
>= 0) {
4401 x
= _thd
.selstart
.x
- h
;
4402 b
= HT_LINE
| HT_DIR_VR
;
4404 y
= _thd
.selstart
.y
- w
;
4405 b
= HT_LINE
| HT_DIR_VL
;
4413 _thd
.dir2
= HT_DIR_END
;
4414 _thd
.next_drawstyle
= b
;
4416 ShowLengthMeasurement(b
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4419 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
)
4421 RailSnapMode snap_mode
= GetRailSnapMode();
4423 /* are we only within one tile? */
4424 if (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
)) {
4425 _thd
.selend
.x
= pt
.x
;
4426 _thd
.selend
.y
= pt
.y
;
4427 return GetAutorailHT(pt
.x
, pt
.y
);
4430 /* find the best track */
4433 bool lock_snapping
= dragging
&& snap_mode
== RSM_SNAP_TO_RAIL
;
4434 if (!lock_snapping
) _current_snap_lock
.x
= -1;
4436 const LineSnapPoint
*snap_point
;
4437 if (_current_snap_lock
.x
!= -1) {
4438 snap_point
= FindBestPolyline(pt
, &_current_snap_lock
, 1, &line
);
4439 } else if (snap_mode
== RSM_SNAP_TO_TILE
) {
4440 snap_point
= FindBestPolyline(pt
, _tile_snap_points
.Begin(), _tile_snap_points
.Length(), &line
);
4442 assert(snap_mode
== RSM_SNAP_TO_RAIL
);
4443 snap_point
= FindBestPolyline(pt
, _rail_snap_points
.Begin(), _rail_snap_points
.Length(), &line
);
4446 if (snap_point
== NULL
) return HT_NONE
; // no match
4448 if (lock_snapping
&& _current_snap_lock
.x
== -1) {
4449 /* lock down the snap point */
4450 _current_snap_lock
= *snap_point
;
4451 _current_snap_lock
.dirs
&= (1 << line
.first_dir
) | (1 << ReverseDir(line
.first_dir
));
4454 TileIndexDiffC first_dir
= TileIndexDiffCByDir(line
.first_dir
);
4455 _thd
.selstart
.x
= line
.start
.x
;
4456 _thd
.selstart
.y
= line
.start
.y
;
4457 _thd
.selend
.x
= _thd
.selstart
.x
+ line
.first_len
* first_dir
.x
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4458 _thd
.selend
.y
= _thd
.selstart
.y
+ line
.first_len
* first_dir
.y
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4459 _thd
.selstart2
.x
= _thd
.selend
.x
;
4460 _thd
.selstart2
.y
= _thd
.selend
.y
;
4461 _thd
.selstart
.x
+= first_dir
.x
;
4462 _thd
.selstart
.y
+= first_dir
.y
;
4463 _thd
.selend
.x
-= first_dir
.x
;
4464 _thd
.selend
.y
-= first_dir
.y
;
4465 Trackdir seldir
= PointDirToTrackdir(_thd
.selstart
, line
.first_dir
);
4466 _thd
.selstart
.x
&= ~TILE_UNIT_MASK
;
4467 _thd
.selstart
.y
&= ~TILE_UNIT_MASK
;
4469 if (line
.second_len
!= 0) {
4470 TileIndexDiffC second_dir
= TileIndexDiffCByDir(line
.second_dir
);
4471 _thd
.selend2
.x
= _thd
.selstart2
.x
+ line
.second_len
* second_dir
.x
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4472 _thd
.selend2
.y
= _thd
.selstart2
.y
+ line
.second_len
* second_dir
.y
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4473 _thd
.selstart2
.x
+= second_dir
.x
;
4474 _thd
.selstart2
.y
+= second_dir
.y
;
4475 _thd
.selend2
.x
-= second_dir
.x
;
4476 _thd
.selend2
.y
-= second_dir
.y
;
4477 Trackdir seldir2
= PointDirToTrackdir(_thd
.selstart2
, line
.second_dir
);
4478 _thd
.selstart2
.x
&= ~TILE_UNIT_MASK
;
4479 _thd
.selstart2
.y
&= ~TILE_UNIT_MASK
;
4480 _thd
.dir2
= (HighLightStyle
)TrackdirToTrack(seldir2
);
4482 _thd
.dir2
= HT_DIR_END
;
4485 HighLightStyle ret
= HT_LINE
| (HighLightStyle
)TrackdirToTrack(seldir
);
4486 ShowLengthMeasurement(ret
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
), TCC_HOVER
, true);
4491 * Selects tiles while dragging
4492 * @param x X coordinate of end of selection
4493 * @param y Y coordinate of end of selection
4494 * @param method modifies the way tiles are selected. Possible
4495 * methods are VPM_* in viewport.h
4497 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
4500 HighLightStyle style
;
4507 if ((_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() != RSM_NO_SNAP
) {
4508 Point pt
= { x
, y
};
4509 _thd
.next_drawstyle
= CalcPolyrailDrawstyle(pt
, true);
4513 /* Special handling of drag in any (8-way) direction */
4514 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4517 CalcRaildirsDrawstyle(x
, y
, method
);
4521 /* Needed so level-land is placed correctly */
4522 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
4527 sx
= _thd
.selstart
.x
;
4528 sy
= _thd
.selstart
.y
;
4533 case VPM_X_OR_Y
: // drag in X or Y direction
4534 if (abs(sy
- y
) < abs(sx
- x
)) {
4541 goto calc_heightdiff_single_direction
;
4543 case VPM_X_LIMITED
: // Drag in X direction (limited size).
4544 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4547 case VPM_FIX_X
: // drag in Y direction
4550 goto calc_heightdiff_single_direction
;
4552 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
4553 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4556 case VPM_FIX_Y
: // drag in X direction
4560 calc_heightdiff_single_direction
:;
4562 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4563 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4565 /* With current code passing a HT_LINE style to calculate the height
4566 * difference is enough. However if/when a point-tool is created
4567 * with this method, function should be called with new_style (below)
4568 * instead of HT_LINE | style case HT_POINT is handled specially
4569 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
4570 ShowLengthMeasurement(HT_LINE
| style
, TileVirtXY(sx
, sy
), TileVirtXY(x
, y
));
4573 case VPM_A_B_LINE
: { // drag an A to B line
4574 TileIndex t0
= TileVirtXY(sx
, sy
);
4575 TileIndex t1
= TileVirtXY(x
, y
);
4576 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4577 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4580 /* If dragging an area (eg dynamite tool) and it is actually a single
4581 * row/column, change the type to 'line' to get proper calculation for height */
4582 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4583 if (style
& HT_RECT
) {
4585 style
= HT_LINE
| HT_DIR_Y
;
4586 } else if (dy
== 1) {
4587 style
= HT_LINE
| HT_DIR_X
;
4593 if (dx
!= 1 || dy
!= 1) {
4594 heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4595 SetDParam(index
++, DistanceManhattan(t0
, t1
));
4596 SetDParam(index
++, sqrtl(dx
* dx
+ dy
* dy
)); //DistanceSquare does not like big numbers
4602 SetDParam(index
++, DistanceFromEdge(t1
));
4603 SetDParam(index
++, GetTileMaxZ(t1
) * TILE_HEIGHT_STEP
);
4604 SetDParam(index
++, heightdiff
);
4605 //Show always the measurement tooltip
4606 GuiShowTooltips(_thd
.GetCallbackWnd(),STR_MEASURE_DIST_HEIGHTDIFF
);
4610 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
4611 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4612 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4613 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4616 case VPM_X_AND_Y
: // drag an X by Y area
4617 if (_settings_client
.gui
.measure_tooltip
) {
4618 static const StringID measure_strings_area
[] = {
4619 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
4622 TileIndex t0
= TileVirtXY(sx
, sy
);
4623 TileIndex t1
= TileVirtXY(x
, y
);
4624 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4625 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4628 /* If dragging an area (eg dynamite tool) and it is actually a single
4629 * row/column, change the type to 'line' to get proper calculation for height */
4630 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4631 if (_thd
.IsDraggingDiagonal()) {
4632 /* Determine the "area" of the diagonal dragged selection.
4633 * We assume the area is the number of tiles along the X
4634 * edge and the number of tiles along the Y edge. However,
4635 * multiplying these two numbers does not give the exact
4636 * number of tiles; basically we are counting the black
4637 * squares on a chess board and ignore the white ones to
4638 * make the tile counts at the edges match up. There is no
4639 * other way to make a proper count though.
4641 * First convert to the rotated coordinate system. */
4642 int dist_x
= TileX(t0
) - TileX(t1
);
4643 int dist_y
= TileY(t0
) - TileY(t1
);
4644 int a_max
= dist_x
+ dist_y
;
4645 int b_max
= dist_y
- dist_x
;
4647 /* Now determine the size along the edge, but due to the
4648 * chess board principle this counts double. */
4649 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
4650 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
4652 /* We get a 1x1 on normal 2x1 rectangles, due to it being
4653 * a seen as two sides. As the result for actual building
4654 * will be the same as non-diagonal dragging revert to that
4655 * behaviour to give it a more normally looking size. */
4656 if (a_max
!= 1 || b_max
!= 1) {
4660 } else if (style
& HT_RECT
) {
4662 style
= HT_LINE
| HT_DIR_Y
;
4663 } else if (dy
== 1) {
4664 style
= HT_LINE
| HT_DIR_X
;
4668 if (dx
!= 1 || dy
!= 1) {
4669 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4671 SetDParam(index
++, dx
- (style
& HT_POINT
? 1 : 0));
4672 SetDParam(index
++, dy
- (style
& HT_POINT
? 1 : 0));
4673 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4676 ShowMeasurementTooltips(measure_strings_area
[index
]);
4680 default: NOT_REACHED();
4685 _thd
.dir2
= HT_DIR_END
;
4689 * Handle the mouse while dragging for placement/resizing.
4690 * @return State of handling the event.
4692 EventState
VpHandlePlaceSizingDrag()
4694 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
4696 /* stop drag mode if the window has been closed */
4697 Window
*w
= _thd
.GetCallbackWnd();
4699 ResetObjectToPlace();
4703 /* While dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ).
4704 * Do it even if the button is no longer pressed to make sure that OnPlaceDrag was called at least once. */
4705 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
4706 if (_left_button_down
) return ES_HANDLED
;
4708 /* mouse button released..
4709 * keep the selected tool, but reset it to the original mode. */
4710 _special_mouse_mode
= WSM_NONE
;
4711 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
4712 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
4713 _thd
.place_mode
= HT_RECT
| others
;
4714 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
4715 _thd
.place_mode
= HT_RECT
| others
;
4716 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
4717 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
? _thd
.next_drawstyle
: HT_RAIL
) | others
;
4719 _thd
.place_mode
= HT_POINT
| others
;
4721 SetTileSelectSize(1, 1);
4723 if (_thd
.place_mode
& HT_POLY
) {
4724 if (GetRailSnapMode() == RSM_SNAP_TO_TILE
) SetRailSnapMode(RSM_NO_SNAP
);
4725 if (_thd
.drawstyle
== HT_NONE
) return ES_HANDLED
;
4728 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4733 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4734 * @param icon New shape of the mouse cursor.
4735 * @param pal Palette to use.
4736 * @param mode Mode to perform.
4737 * @param w %Window requesting the mode change.
4739 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
4741 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
4744 #include "table/animcursors.h"
4747 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4748 * @param icon New shape of the mouse cursor.
4749 * @param pal Palette to use.
4750 * @param mode Mode to perform.
4751 * @param window_class %Window class of the window requesting the mode change.
4752 * @param window_num Number of the window in its class requesting the mode change.
4754 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
4756 if (_thd
.window_class
!= WC_INVALID
) {
4757 /* Undo clicking on button and drag & drop */
4758 Window
*w
= _thd
.GetCallbackWnd();
4759 /* Call the abort function, but set the window class to something
4760 * that will never be used to avoid infinite loops. Setting it to
4761 * the 'next' window class must not be done because recursion into
4762 * this function might in some cases reset the newly set object to
4763 * place or not properly reset the original selection. */
4764 _thd
.window_class
= WC_INVALID
;
4765 if (w
!= NULL
) w
->OnPlaceObjectAbort();
4768 /* Mark the old selection dirty, in case the selection shape or colour changes */
4769 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
4771 SetTileSelectSize(1, 1);
4773 _thd
.make_square_red
= false;
4775 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
4777 _special_mouse_mode
= WSM_DRAGDROP
;
4779 _special_mouse_mode
= WSM_NONE
;
4782 _thd
.place_mode
= mode
;
4783 _thd
.window_class
= window_class
;
4784 _thd
.window_number
= window_num
;
4786 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
4790 if (mode
& HT_POLY
) {
4791 SetRailSnapMode((mode
& HT_NEW_POLY
) == HT_NEW_POLY
? RSM_NO_SNAP
: RSM_SNAP_TO_RAIL
);
4794 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
4795 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
4797 SetMouseCursor(icon
, pal
);
4802 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
4803 void ResetObjectToPlace()
4805 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
4808 ViewportMapType
ChangeRenderMode(const ViewPort
*vp
, bool down
) {
4809 ViewportMapType map_type
= vp
->map_type
;
4810 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return map_type
;
4812 return (map_type
== VPMT_MIN
) ? VPMT_MAX
: (ViewportMapType
) (map_type
- 1);
4814 return (map_type
== VPMT_MAX
) ? VPMT_MIN
: (ViewportMapType
) (map_type
+ 1);
4818 Point
GetViewportStationMiddle(const ViewPort
*vp
, const Station
*st
)
4820 int x
= TileX(st
->xy
) * TILE_SIZE
;
4821 int y
= TileY(st
->xy
) * TILE_SIZE
;
4822 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
4824 Point p
= RemapCoords(x
, y
, z
);
4825 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
4826 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
4830 void DrawOverlay(const TileInfo
*ti
, TileType tt
)
4832 if (Overlays::Instance()->IsTileInCatchmentArea(ti
, PRODUCTION
)) {
4833 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
4834 } else if (Overlays::Instance()->IsTileInCatchmentArea(ti
, ACCEPTANCE
)) {
4835 DrawTileSelectionRect(ti
, PAL_NONE
);
4839 /** Helper class for getting the best sprite sorter. */
4840 struct ViewportSSCSS
{
4841 VpSorterChecker fct_checker
; ///< The check function.
4842 VpSpriteSorter fct_sorter
; ///< The sorting function.
4845 /** List of sorters ordered from best to worst. */
4846 static ViewportSSCSS _vp_sprite_sorters
[] = {
4848 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
4850 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
4853 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
4854 void InitializeSpriteSorter()
4856 for (uint i
= 0; i
< lengthof(_vp_sprite_sorters
); i
++) {
4857 if (_vp_sprite_sorters
[i
].fct_checker()) {
4858 _vp_sprite_sorter
= _vp_sprite_sorters
[i
].fct_sorter
;
4862 assert(_vp_sprite_sorter
!= NULL
);
4865 static LineSnapPoint
LineSnapPointAtRailTrackEndpoint(TileIndex tile
, DiagDirection exit_dir
, bool bidirectional
)
4868 ret
.x
= (TILE_SIZE
/ 2) * (uint
)(2 * TileX(tile
) + TileIndexDiffCByDiagDir(exit_dir
).x
+ 1);
4869 ret
.y
= (TILE_SIZE
/ 2) * (uint
)(2 * TileY(tile
) + TileIndexDiffCByDiagDir(exit_dir
).y
+ 1);
4872 SetBit(ret
.dirs
, DiagDirToDir(exit_dir
));
4873 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45LEFT
));
4874 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45RIGHT
));
4875 if (bidirectional
) ret
.dirs
|= ROR
<uint8
>(ret
.dirs
, DIRDIFF_REVERSE
);
4881 * Store the position of lastly built rail track; for highlighting purposes.
4883 * In "polyline" highlighting mode, the stored end point will be used as a snapping point for new
4884 * tracks allowing to place multi-segment polylines.
4886 * @param start_tile tile where the track starts
4887 * @param end_tile tile where the track ends
4888 * @param start_track track piece on the start_tile
4889 * @param bidirectional_exit whether to allow to highlight next track in any direction; otherwise new track will have to fallow the stored one (usefull when placing tunnels and bridges)
4891 void StoreRailPlacementEndpoints(TileIndex start_tile
, TileIndex end_tile
, Track start_track
, bool bidirectional_exit
)
4893 if (start_tile
!= INVALID_TILE
&& end_tile
!= INVALID_TILE
) {
4894 /* calculate trackdirs at booth ends of the track */
4895 Trackdir exit_trackdir_at_start
= TrackToTrackdir(start_track
);
4896 Trackdir exit_trackdir_at_end
= ReverseTrackdir(TrackToTrackdir(start_track
));
4897 if (start_tile
!= end_tile
) { // multi-tile case
4898 /* determine proper direction (pointing outside of the track) */
4899 uint distance
= DistanceManhattan(start_tile
, end_tile
);
4900 if (distance
> DistanceManhattan(TileAddByDiagDir(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
)), end_tile
)) {
4901 Swap(exit_trackdir_at_start
, exit_trackdir_at_end
);
4903 /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
4904 if (distance
% 2 != 0) exit_trackdir_at_end
= NextTrackdir(exit_trackdir_at_end
);
4907 LineSnapPoint snap_start
= LineSnapPointAtRailTrackEndpoint(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
), bidirectional_exit
);
4908 LineSnapPoint snap_end
= LineSnapPointAtRailTrackEndpoint(end_tile
, TrackdirToExitdir(exit_trackdir_at_end
), bidirectional_exit
);
4909 /* Find if we already had these coordinates before. */
4910 LineSnapPoint
*snap
;
4911 bool had_start
= false;
4912 bool had_end
= false;
4913 for (snap
= _rail_snap_points
.Begin(); snap
!= _rail_snap_points
.End(); snap
++) {
4914 had_start
|= (snap
->x
== snap_start
.x
&& snap
->y
== snap_start
.y
);
4915 had_end
|= (snap
->x
== snap_end
.x
&& snap
->y
== snap_end
.y
);
4917 /* Create new snap point set. */
4918 if (had_start
&& had_end
) {
4919 /* just stop snaping, don't forget snap points */
4920 SetRailSnapMode(RSM_NO_SNAP
);
4922 /* include only new points */
4923 _rail_snap_points
.Clear();
4924 if (!had_start
) *_rail_snap_points
.Append() = snap_start
;
4925 if (!had_end
) *_rail_snap_points
.Append() = snap_end
;
4926 SetRailSnapMode(RSM_SNAP_TO_RAIL
);
4931 bool CurrentlySnappingRailPlacement()
4933 return (_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() == RSM_SNAP_TO_RAIL
;
4936 static RailSnapMode
GetRailSnapMode()
4938 if (_rail_snap_mode
== RSM_SNAP_TO_TILE
&& _tile_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4939 if (_rail_snap_mode
== RSM_SNAP_TO_RAIL
&& _rail_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4940 return _rail_snap_mode
;
4943 static void SetRailSnapMode(RailSnapMode mode
)
4945 _rail_snap_mode
= mode
;
4947 if ((_thd
.place_mode
& HT_POLY
) && (GetRailSnapMode() == RSM_NO_SNAP
)) {
4948 SetTileSelectSize(1, 1);
4952 static TileIndex
GetRailSnapTile()
4954 if (_tile_snap_points
.Length() == 0) return INVALID_TILE
;
4955 return TileVirtXY(_tile_snap_points
[DIAGDIR_NE
].x
, _tile_snap_points
[DIAGDIR_NE
].y
);
4958 static void SetRailSnapTile(TileIndex tile
)
4960 _tile_snap_points
.Clear();
4961 if (tile
== INVALID_TILE
) return;
4963 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) {
4964 LineSnapPoint
*point
= _tile_snap_points
.Append();
4965 *point
= LineSnapPointAtRailTrackEndpoint(tile
, dir
, false);
4966 point
->dirs
= ROR
<uint8
>(point
->dirs
, DIRDIFF_REVERSE
);
4970 void ResetRailPlacementSnapping()
4972 _rail_snap_mode
= RSM_NO_SNAP
;
4973 _tile_snap_points
.Clear();
4974 _rail_snap_points
.Clear();
4975 _current_snap_lock
.x
= -1;