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
= nullptr;
279 const byte
*_pal2trsp_remap_ptr
= nullptr;
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
== nullptr) return;
303 container_unordered_remove(_viewport_window_cache
, w
->viewport
);
304 delete w
->viewport
->overlay
;
306 w
->viewport
= nullptr;
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
== nullptr);
326 ViewportData
*vp
= new ViewportData();
328 vp
->overlay
= nullptr;
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 IncrementWindowUpdateNumber();
376 FOR_ALL_WINDOWS_FROM_BACK_FROM(w
, w
) {
377 if (left
+ width
> w
->left
&&
378 w
->left
+ w
->width
> left
&&
379 top
+ height
> w
->top
&&
380 w
->top
+ w
->height
> top
) {
382 if (left
< w
->left
) {
383 DoSetViewportPosition(w
, left
, top
, w
->left
- left
, height
);
384 DoSetViewportPosition(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
388 if (left
+ width
> w
->left
+ w
->width
) {
389 DoSetViewportPosition(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
390 DoSetViewportPosition(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
395 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
- top
));
396 DoSetViewportPosition(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
400 if (top
+ height
> w
->top
+ w
->height
) {
401 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
402 DoSetViewportPosition(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
411 int xo
= _vp_move_offs
.x
;
412 int yo
= _vp_move_offs
.y
;
414 if (abs(xo
) >= width
|| abs(yo
) >= height
) {
416 RedrawScreenRect(left
, top
, left
+ width
, top
+ height
);
420 GfxScroll(left
, top
, width
, height
, xo
, yo
);
423 RedrawScreenRect(left
, top
, xo
+ left
, top
+ height
);
427 RedrawScreenRect(left
+ width
+ xo
, top
, left
+ width
, top
+ height
);
432 RedrawScreenRect(left
, top
, width
+ left
, top
+ yo
);
434 RedrawScreenRect(left
, top
+ height
+ yo
, width
+ left
, top
+ height
);
439 static void SetViewportPosition(Window
*w
, int x
, int y
)
441 ViewPort
*vp
= w
->viewport
;
442 int old_left
= vp
->virtual_left
;
443 int old_top
= vp
->virtual_top
;
445 int left
, top
, width
, height
;
447 vp
->virtual_left
= x
;
450 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
451 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
453 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
454 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
455 x
= UnScaleByZoomLower(x
, vp
->zoom
);
456 y
= UnScaleByZoomLower(y
, vp
->zoom
);
461 if (old_top
== 0 && old_left
== 0) return;
463 _vp_move_offs
.x
= old_left
;
464 _vp_move_offs
.y
= old_top
;
476 i
= left
+ width
- _screen
.width
;
477 if (i
>= 0) width
-= i
;
485 i
= top
+ height
- _screen
.height
;
486 if (i
>= 0) height
-= i
;
488 if (height
> 0) DoSetViewportPosition(w
->z_front
, left
, top
, width
, height
);
493 * Is a xy position inside the viewport of the window?
494 * @param w Window to examine its viewport
495 * @param x X coordinate of the xy position
496 * @param y Y coordinate of the xy position
497 * @return Pointer to the viewport if the xy position is in the viewport of the window,
498 * otherwise \c nullptr is returned.
500 ViewPort
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
502 ViewPort
*vp
= w
->viewport
;
505 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
506 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
513 * Translate screen coordinate in a viewport to a tile coordinate
514 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
515 * @param x Screen x coordinate
516 * @param y Screen y coordinate
517 * @param clamp_to_map Clamp the coordinate outside of the map to the closest tile within the map.
518 * @return Tile coordinate
520 Point
TranslateXYToTileCoord(const ViewPort
*vp
, int x
, int y
, bool clamp_to_map
)
526 if ( (uint
)(x
-= vp
->left
) >= (uint
)vp
->width
||
527 (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) {
532 x
= (ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
) >> (2 + ZOOM_LVL_SHIFT
);
533 y
= (ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
) >> (1 + ZOOM_LVL_SHIFT
);
539 /* Bring the coordinates near to a valid range. This is mostly due to the
540 * tiles on the north side of the map possibly being drawn too high due to
541 * the extra height levels. So at the top we allow a number of extra tiles.
542 * This number is based on the tile height and pixels. */
543 int extra_tiles
= CeilDiv(_settings_game
.construction
.max_heightlevel
* TILE_HEIGHT
, TILE_PIXELS
);
544 a
= Clamp(a
, -extra_tiles
* TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1);
545 b
= Clamp(b
, -extra_tiles
* TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1);
548 /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
549 * Now find the Z-world coordinate by fix point iteration.
550 * This is a bit tricky because the tile height is non-continuous at foundations.
551 * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
552 * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
553 * So give it a z-malus of 4 in the first iterations.
557 int min_coord
= _settings_game
.construction
.freeform_edges
? TILE_SIZE
: 0;
559 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;
560 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;
561 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;
564 pt
.x
= Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1);
565 pt
.y
= Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1);
574 /* When used for zooming, check area below current coordinates (x,y)
575 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
576 * when you just want the tile, make x = zoom_x and y = zoom_y */
577 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
583 if ( (w
= FindWindowFromPt(x
, y
)) != nullptr &&
584 (vp
= IsPtInWindowViewport(w
, x
, y
)) != nullptr)
585 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
591 Point
GetTileBelowCursor()
593 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
597 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
600 ViewPort
*vp
= w
->viewport
;
603 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
604 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
606 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
607 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
609 /* Get the tile below the cursor and center on the zoomed-out center */
610 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
614 * Update the status of the zoom-buttons according to the zoom-level
615 * of the viewport. This will update their status and invalidate accordingly
616 * @param w Window pointer to the window that has the zoom buttons
617 * @param vp pointer to the viewport whose zoom-level the buttons represent
618 * @param widget_zoom_in widget index for window with zoom-in button
619 * @param widget_zoom_out widget index for window with zoom-out button
621 void HandleZoomMessage(Window
*w
, const ViewPort
*vp
, byte widget_zoom_in
, byte widget_zoom_out
)
623 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
<= _settings_client
.gui
.zoom_min
);
624 w
->SetWidgetDirty(widget_zoom_in
);
626 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
>= _settings_client
.gui
.zoom_max
);
627 w
->SetWidgetDirty(widget_zoom_out
);
631 * Schedules a tile sprite for drawing.
633 * @param image the image to draw.
634 * @param pal the provided palette.
635 * @param x position x (world coordinates) of the sprite.
636 * @param y position y (world coordinates) of the sprite.
637 * @param z position z (world coordinates) of the sprite.
638 * @param sub Only draw a part of the sprite.
639 * @param extra_offs_x Pixel X offset for the sprite position.
640 * @param extra_offs_y Pixel Y offset for the sprite position.
642 static void AddTileSpriteToDraw(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
= nullptr, int extra_offs_x
= 0, int extra_offs_y
= 0)
644 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
646 TileSpriteToDraw
*ts
= _vd
.tile_sprites_to_draw
.Append();
650 Point pt
= RemapCoords(x
, y
, z
);
651 ts
->x
= pt
.x
+ extra_offs_x
;
652 ts
->y
= pt
.y
+ extra_offs_y
;
656 * Adds a child sprite to the active foundation.
658 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
660 * @param image the image to draw.
661 * @param pal the provided palette.
662 * @param sub Only draw a part of the sprite.
663 * @param foundation_part Foundation part.
664 * @param extra_offs_x Pixel X offset for the sprite position.
665 * @param extra_offs_y Pixel Y offset for the sprite position.
667 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
669 assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
670 assert(_vd
.foundation
[foundation_part
] != -1);
671 Point offs
= _vd
.foundation_offset
[foundation_part
];
673 /* Change the active ChildSprite list to the one of the foundation */
674 int *old_child
= _vd
.last_child
;
675 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
677 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
, false);
679 /* Switch back to last ChildSprite list */
680 _vd
.last_child
= old_child
;
684 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
685 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
687 * @param image the image to draw.
688 * @param pal the provided palette.
689 * @param x position x (world coordinates) of the sprite relative to current tile.
690 * @param y position y (world coordinates) of the sprite relative to current tile.
691 * @param z position z (world coordinates) of the sprite relative to current tile.
692 * @param sub Only draw a part of the sprite.
693 * @param extra_offs_x Pixel X offset for the sprite position.
694 * @param extra_offs_y Pixel Y offset for the sprite position.
696 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
698 /* Switch to first foundation part, if no foundation was drawn */
699 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
701 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
702 Point pt
= RemapCoords(x
, y
, z
);
703 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
* ZOOM_LVL_BASE
, pt
.y
+ extra_offs_y
* ZOOM_LVL_BASE
);
705 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
);
710 * Draws a ground sprite for the current tile.
711 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
713 * @param image the image to draw.
714 * @param pal the provided palette.
715 * @param sub Only draw a part of the sprite.
716 * @param extra_offs_x Pixel X offset for the sprite position.
717 * @param extra_offs_y Pixel Y offset for the sprite position.
719 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
721 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
725 * Called when a foundation has been drawn for the current tile.
726 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
728 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
729 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
731 void OffsetGroundSprite(int x
, int y
)
733 /* Switch to next foundation part */
734 switch (_vd
.foundation_part
) {
735 case FOUNDATION_PART_NONE
:
736 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
738 case FOUNDATION_PART_NORMAL
:
739 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
741 default: NOT_REACHED();
744 /* _vd.last_child == nullptr if foundation sprite was clipped by the viewport bounds */
745 if (_vd
.last_child
!= nullptr) _vd
.foundation
[_vd
.foundation_part
] = _vd
.parent_sprites_to_draw
.Length() - 1;
747 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
* ZOOM_LVL_BASE
;
748 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
* ZOOM_LVL_BASE
;
749 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
753 * Adds a child sprite to a parent sprite.
754 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
756 * @param image the image to draw.
757 * @param pal the provided palette.
758 * @param x position x of the sprite.
759 * @param y position y of the sprite.
760 * @param z position z of the sprite.
761 * @param sub Only draw a part of the sprite.
763 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, int z
, const SubSprite
*sub
)
765 Point pt
= RemapCoords(x
, y
, z
);
766 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
768 if (pt
.x
+ spr
->x_offs
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
769 pt
.x
+ spr
->x_offs
+ spr
->width
<= _vd
.dpi
.left
||
770 pt
.y
+ spr
->y_offs
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
771 pt
.y
+ spr
->y_offs
+ spr
->height
<= _vd
.dpi
.top
)
774 const ParentSpriteToDraw
*pstd
= _vd
.parent_sprites_to_draw
.End() - 1;
775 AddChildSpriteScreen(image
, pal
, pt
.x
- pstd
->left
, pt
.y
- pstd
->top
, false, sub
, false);
779 * Draw a (transparent) sprite at given coordinates with a given bounding box.
780 * 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.
781 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
783 * @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
784 * defined by the sprite offset in the grf file.
785 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
787 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
789 * @param image the image to combine and draw,
790 * @param pal the provided palette,
791 * @param x position X (world) of the sprite,
792 * @param y position Y (world) of the sprite,
793 * @param w bounding box extent towards positive X (world),
794 * @param h bounding box extent towards positive Y (world),
795 * @param dz bounding box extent towards positive Z (world),
796 * @param z position Z (world) of the sprite,
797 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
798 * @param bb_offset_x bounding box extent towards negative X (world),
799 * @param bb_offset_y bounding box extent towards negative Y (world),
800 * @param bb_offset_z bounding box extent towards negative Z (world)
801 * @param sub Only draw a part of the sprite.
803 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
)
805 int32 left
, right
, top
, bottom
;
807 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
809 /* make the sprites transparent with the right palette */
811 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
812 pal
= PALETTE_TO_TRANSPARENT
;
815 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
816 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
820 _vd
.last_child
= nullptr;
822 Point pt
= RemapCoords(x
, y
, z
);
823 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
825 /* Compute screen extents of sprite */
826 if (image
== SPR_EMPTY_BOUNDING_BOX
) {
827 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
828 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
829 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
830 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
832 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
833 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
834 right
= (pt
.x
+ spr
->width
);
835 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
836 bottom
= (pt
.y
+ spr
->height
);
839 if (_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
)) {
840 /* Compute maximal extents of sprite and its bounding box */
841 left
= min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
842 right
= max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
843 top
= min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
844 bottom
= max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
847 /* Do not add the sprite to the viewport, if it is outside */
848 if (left
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
849 right
<= _vd
.dpi
.left
||
850 top
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
851 bottom
<= _vd
.dpi
.top
) {
855 ParentSpriteToDraw
*ps
= _vd
.parent_sprites_to_draw
.Append();
865 ps
->xmin
= x
+ bb_offset_x
;
866 ps
->xmax
= x
+ max(bb_offset_x
, w
) - 1;
868 ps
->ymin
= y
+ bb_offset_y
;
869 ps
->ymax
= y
+ max(bb_offset_y
, h
) - 1;
871 ps
->zmin
= z
+ bb_offset_z
;
872 ps
->zmax
= z
+ max(bb_offset_z
, dz
) - 1;
874 ps
->comparison_done
= false;
875 ps
->first_child
= -1;
877 _vd
.last_child
= &ps
->first_child
;
879 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
883 * Starts a block of sprites, which are "combined" into a single bounding box.
885 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
886 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
887 * the following sprites will be child sprites to that one.
890 * - The drawing order is definite. No other sprites will be sorted between those of the block.
891 * - You have to provide a valid bounding box for all sprites,
892 * as you won't know which one is the first non-clipped one.
893 * Preferable you use the same bounding box for all.
894 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
896 * The block is terminated by #EndSpriteCombine.
898 * You cannot nest "combined" blocks.
900 void StartSpriteCombine()
902 assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
903 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
907 * Terminates a block of sprites started by #StartSpriteCombine.
908 * Take a look there for details.
910 void EndSpriteCombine()
912 assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
913 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
917 * Check if the parameter "check" is inside the interval between
918 * begin and end, including both begin and end.
919 * @note Whether \c begin or \c end is the biggest does not matter.
920 * This method will account for that.
921 * @param begin The begin of the interval.
922 * @param end The end of the interval.
923 * @param check The value to check.
925 static bool IsInRangeInclusive(int begin
, int end
, int check
)
927 if (begin
> end
) Swap(begin
, end
);
928 return begin
<= check
&& check
<= end
;
932 * Checks whether a point is inside the selected rectangle given by _thd.size, _thd.pos and _thd.diagonal
933 * @param x The x coordinate of the point to be checked.
934 * @param y The y coordinate of the point to be checked.
935 * @return True if the point is inside the rectangle, else false.
937 static bool IsInsideSelectedRectangle(int x
, int y
)
939 if (!_thd
.diagonal
) {
940 return IsInsideBS(x
, _thd
.pos
.x
, _thd
.size
.x
) && IsInsideBS(y
, _thd
.pos
.y
, _thd
.size
.y
);
943 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
944 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
945 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
946 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
948 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
949 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
953 * Add a child sprite to a parent sprite.
955 * @param image the image to draw.
956 * @param pal the provided palette.
957 * @param x sprite x-offset (screen coordinates) relative to parent sprite.
958 * @param y sprite y-offset (screen coordinates) relative to parent sprite.
959 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
960 * @param sub Only draw a part of the sprite.
962 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
, bool scale
)
964 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
966 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
967 if (_vd
.last_child
== nullptr) return;
969 /* make the sprites transparent with the right palette */
971 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
972 pal
= PALETTE_TO_TRANSPARENT
;
975 *_vd
.last_child
= _vd
.child_screen_sprites_to_draw
.Length();
977 ChildScreenSpriteToDraw
*cs
= _vd
.child_screen_sprites_to_draw
.Append();
981 cs
->x
= scale
? x
* ZOOM_LVL_BASE
: x
;
982 cs
->y
= scale
? y
* ZOOM_LVL_BASE
: y
;
985 /* Append the sprite to the active ChildSprite list.
986 * If the active ParentSprite is a foundation, update last_foundation_child as well.
987 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
988 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = &cs
->next
;
989 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = &cs
->next
;
990 _vd
.last_child
= &cs
->next
;
993 static void AddStringToDraw(int x
, int y
, StringID string
, uint64 params_1
, uint64 params_2
, Colours colour
, uint16 width
)
996 StringSpriteToDraw
*ss
= _vd
.string_sprites_to_draw
.Append();
1000 ss
->params
[0] = params_1
;
1001 ss
->params
[1] = params_2
;
1003 ss
->colour
= colour
;
1008 * Draws sprites between ground sprite and everything above.
1010 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
1012 * @param image the image to draw.
1013 * @param pal the provided palette.
1014 * @param ti TileInfo Tile that is being drawn
1015 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
1016 * @param foundation_part Foundation part the sprite belongs to.
1018 static void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
)
1020 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
1021 if (_vd
.foundation
[foundation_part
] == -1) {
1022 /* draw on real ground */
1023 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
);
1025 /* draw on top of foundation */
1026 AddChildSpriteToFoundation(image
, pal
, nullptr, foundation_part
, 0, -z_offset
* ZOOM_LVL_BASE
);
1031 * Draws a selection rectangle on a tile.
1033 * @param ti TileInfo Tile that is being drawn
1034 * @param pal Palette to apply.
1036 static void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
1038 if (!IsValidTile(ti
->tile
)) return;
1041 if (IsHalftileSlope(ti
->tileh
)) {
1042 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1043 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
1044 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
1046 Corner opposite_corner
= OppositeCorner(halftile_corner
);
1047 if (IsSteepSlope(ti
->tileh
)) {
1048 sel
= SPR_HALFTILE_SELECTION_DOWN
;
1050 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
1052 sel
+= opposite_corner
;
1054 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
1056 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
1059 static HighLightStyle
GetPartOfAutoLine(int px
, int py
, const Point
&selstart
, const Point
&selend
, HighLightStyle dir
)
1061 if (!IsInRangeInclusive(selstart
.x
& ~TILE_UNIT_MASK
, selend
.x
& ~TILE_UNIT_MASK
, px
)) return HT_DIR_END
;
1062 if (!IsInRangeInclusive(selstart
.y
& ~TILE_UNIT_MASK
, selend
.y
& ~TILE_UNIT_MASK
, py
)) return HT_DIR_END
;
1064 px
-= selstart
.x
& ~TILE_UNIT_MASK
;
1065 py
-= selstart
.y
& ~TILE_UNIT_MASK
;
1068 case HT_DIR_X
: return (py
== 0) ? HT_DIR_X
: HT_DIR_END
;
1069 case HT_DIR_Y
: return (px
== 0) ? HT_DIR_Y
: HT_DIR_END
;
1070 case HT_DIR_HU
: return (px
== -py
) ? HT_DIR_HU
: (px
== -py
- (int)TILE_SIZE
) ? HT_DIR_HL
: HT_DIR_END
;
1071 case HT_DIR_HL
: return (px
== -py
) ? HT_DIR_HL
: (px
== -py
+ (int)TILE_SIZE
) ? HT_DIR_HU
: HT_DIR_END
;
1072 case HT_DIR_VL
: return (px
== py
) ? HT_DIR_VL
: (px
== py
+ (int)TILE_SIZE
) ? HT_DIR_VR
: HT_DIR_END
;
1073 case HT_DIR_VR
: return (px
== py
) ? HT_DIR_VR
: (px
== py
- (int)TILE_SIZE
) ? HT_DIR_VL
: HT_DIR_END
;
1074 default: NOT_REACHED(); break;
1080 #include "table/autorail.h"
1083 * Draws autorail highlights.
1085 * @param *ti TileInfo Tile that is being drawn
1086 * @param autorail_type \c HT_DIR_XXX, offset into _AutorailTilehSprite[][]
1087 * @param pal Palette to use, -1 to autodetect
1089 static void DrawAutorailSelection(const TileInfo
*ti
, HighLightStyle autorail_type
, PaletteID pal
= -1)
1094 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1095 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
1096 if (IsHalftileSlope(ti
->tileh
)) {
1097 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
1098 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1099 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
1100 foundation_part
= FOUNDATION_PART_HALFTILE
;
1101 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
1102 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
1106 assert(autorail_type
< HT_DIR_END
);
1107 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
1109 image
= SPR_AUTORAIL_BASE
+ offset
;
1110 if (pal
== (PaletteID
)-1) pal
= _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
;
1112 image
= SPR_AUTORAIL_BASE
- offset
;
1113 if (pal
== (PaletteID
)-1) pal
= PALETTE_SEL_TILE_RED
;
1116 DrawSelectionSprite(image
, pal
, ti
, 7, foundation_part
);
1120 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
1121 * @param *ti TileInfo Tile that is being drawn
1123 static void DrawTileSelection(const TileInfo
*ti
)
1125 /* Draw a red error square? */
1126 bool is_redsq
= _thd
.redsq
== ti
->tile
;
1127 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
1129 switch (_thd
.drawstyle
& HT_DRAG_MASK
) {
1130 default: break; // No tile selection active?
1134 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1135 DrawTileSelectionRect(ti
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
1136 } else if (_thd
.outersize
.x
> 0 &&
1137 /* Check if it's inside the outer area? */
1138 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1139 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1140 /* Draw a blue rect. */
1141 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1147 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1148 /* Figure out the Z coordinate for the single dot. */
1150 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1151 if (ti
->tileh
& SLOPE_N
) {
1153 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
1155 if (IsHalftileSlope(ti
->tileh
)) {
1156 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1157 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
1158 if (halftile_corner
!= CORNER_S
) {
1159 foundation_part
= FOUNDATION_PART_HALFTILE
;
1160 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
1163 DrawSelectionSprite(_cur_dpi
->zoom
<= ZOOM_LVL_DETAIL
? SPR_DOT
: SPR_DOT_SMALL
, PAL_NONE
, ti
, z
, foundation_part
);
1168 if (ti
->tile
== TileVirtXY(_thd
.pos
.x
, _thd
.pos
.y
)) {
1169 assert((_thd
.drawstyle
& HT_DIR_MASK
) < HT_DIR_END
);
1170 DrawAutorailSelection(ti
, _thd
.drawstyle
& HT_DIR_MASK
);
1175 HighLightStyle type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart
, _thd
.selend
, _thd
.drawstyle
& HT_DIR_MASK
);
1176 if (type
< HT_DIR_END
) {
1177 DrawAutorailSelection(ti
, type
);
1178 } else if (_thd
.dir2
< HT_DIR_END
) {
1179 type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart2
, _thd
.selend2
, _thd
.dir2
);
1180 if (type
< HT_DIR_END
) DrawAutorailSelection(ti
, type
, PALETTE_SEL_TILE_BLUE
);
1188 * Returns the y coordinate in the viewport coordinate system where the given
1190 * @param tile Any tile.
1191 * @return The viewport y coordinate where the tile is painted.
1193 static int GetViewportY(Point tile
)
1195 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1196 return (tile
.y
* (int)(TILE_PIXELS
/ 2) + tile
.x
* (int)(TILE_PIXELS
/ 2) - TilePixelHeightOutsideMap(tile
.x
, tile
.y
)) << ZOOM_LVL_SHIFT
;
1200 * Add the landscape to the viewport, i.e. all ground tiles and buildings.
1202 static void ViewportAddLandscape()
1204 assert(_vd
.dpi
.top
<= _vd
.dpi
.top
+ _vd
.dpi
.height
);
1205 assert(_vd
.dpi
.left
<= _vd
.dpi
.left
+ _vd
.dpi
.width
);
1207 Point upper_left
= InverseRemapCoords(_vd
.dpi
.left
, _vd
.dpi
.top
);
1208 Point upper_right
= InverseRemapCoords(_vd
.dpi
.left
+ _vd
.dpi
.width
, _vd
.dpi
.top
);
1210 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1213 * x = (row - column) / 2
1214 * y = (row + column) / 2
1215 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1218 /* Columns overlap with neighbouring columns by a half tile.
1219 * - Left column is column of upper_left (rounded down) and one column to the left.
1220 * - Right column is column of upper_right (rounded up) and one column to the right.
1221 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1223 int left_column
= (upper_left
.y
- upper_left
.x
) / (int)TILE_SIZE
- 2;
1224 int right_column
= (upper_right
.y
- upper_right
.x
) / (int)TILE_SIZE
+ 2;
1226 int potential_bridge_height
= ZOOM_LVL_BASE
* TILE_HEIGHT
* _settings_game
.construction
.max_bridge_height
;
1228 /* Rows overlap with neighbouring rows by a half tile.
1229 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1230 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1232 int row
= (upper_left
.x
+ upper_left
.y
) / (int)TILE_SIZE
- 2;
1233 bool last_row
= false;
1234 for (; !last_row
; row
++) {
1236 for (int column
= left_column
; column
<= right_column
; column
++) {
1237 /* Valid row/column? */
1238 if ((row
+ column
) % 2 != 0) continue;
1241 tilecoord
.x
= (row
- column
) / 2;
1242 tilecoord
.y
= (row
+ column
) / 2;
1243 assert(column
== tilecoord
.y
- tilecoord
.x
);
1244 assert(row
== tilecoord
.y
+ tilecoord
.x
);
1248 _cur_ti
= &tile_info
;
1249 tile_info
.x
= tilecoord
.x
* TILE_SIZE
; // FIXME tile_info should use signed integers
1250 tile_info
.y
= tilecoord
.y
* TILE_SIZE
;
1252 if (IsInsideBS(tilecoord
.x
, 0, MapSizeX()) && IsInsideBS(tilecoord
.y
, 0, MapSizeY())) {
1253 /* This includes the south border at MapMaxX / MapMaxY. When terraforming we still draw tile selections there. */
1254 tile_info
.tile
= TileXY(tilecoord
.x
, tilecoord
.y
);
1255 tile_type
= GetTileType(tile_info
.tile
);
1257 tile_info
.tile
= INVALID_TILE
;
1258 tile_type
= MP_VOID
;
1261 if (tile_type
!= MP_VOID
) {
1262 /* We are inside the map => paint landscape. */
1263 tile_info
.tileh
= GetTilePixelSlope(tile_info
.tile
, &tile_info
.z
);
1265 /* We are outside the map => paint black. */
1266 tile_info
.tileh
= GetTilePixelSlopeOutsideMap(tilecoord
.x
, tilecoord
.y
, &tile_info
.z
);
1269 int viewport_y
= GetViewportY(tilecoord
);
1271 if (viewport_y
+ MAX_TILE_EXTENT_BOTTOM
< _vd
.dpi
.top
) {
1272 /* The tile in this column is not visible yet.
1273 * Tiles in other columns may be visible, but we need more rows in any case. */
1278 int min_visible_height
= viewport_y
- (_vd
.dpi
.top
+ _vd
.dpi
.height
);
1279 bool tile_visible
= min_visible_height
<= 0;
1281 if (tile_type
!= MP_VOID
) {
1282 /* Is tile with buildings visible? */
1283 if (min_visible_height
< MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1285 if (IsBridgeAbove(tile_info
.tile
)) {
1286 /* Is the bridge visible? */
1287 TileIndex bridge_tile
= GetNorthernBridgeEnd(tile_info
.tile
);
1288 int bridge_height
= ZOOM_LVL_BASE
* (GetBridgePixelHeight(bridge_tile
) - TilePixelHeight(tile_info
.tile
));
1289 if (min_visible_height
< bridge_height
+ MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1292 /* Would a higher bridge on a more southern tile be visible?
1293 * If yes, we need to loop over more rows to possibly find one. */
1294 if (min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1296 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1297 * so we need to loop over more rows to possibly find one. */
1298 if ((tilecoord
.x
<= 0 || tilecoord
.y
<= 0) && min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1303 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1304 _vd
.foundation
[0] = -1;
1305 _vd
.foundation
[1] = -1;
1306 _vd
.last_foundation_child
[0] = nullptr;
1307 _vd
.last_foundation_child
[1] = nullptr;
1309 _tile_type_procs
[tile_type
]->draw_tile_proc(&tile_info
);
1310 if (tile_info
.tile
!= INVALID_TILE
) {
1311 DrawTileSelection(&tile_info
);
1312 DrawTileZoning(&tile_info
);
1320 * Add a string to draw in the viewport
1321 * @param dpi current viewport area
1322 * @param small_from Zoomlevel from when the small font should be used
1323 * @param sign sign position and dimension
1324 * @param string_normal String for normal and 2x zoom level
1325 * @param string_small String for 4x and 8x zoom level
1326 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1327 * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1329 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
)
1331 bool small
= dpi
->zoom
>= small_from
;
1333 int left
= dpi
->left
;
1335 int right
= left
+ dpi
->width
;
1336 int bottom
= top
+ dpi
->height
;
1338 int sign_height
= ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1339 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1341 if (bottom
< sign
->top
||
1342 top
> sign
->top
+ sign_height
||
1343 right
< sign
->center
- sign_half_width
||
1344 left
> sign
->center
+ sign_half_width
) {
1349 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1351 int shadow_offset
= 0;
1352 if (string_small_shadow
!= STR_NULL
) {
1354 AddStringToDraw(sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
);
1356 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1357 colour
, sign
->width_small
| 0x8000);
1361 struct ViewportAddStringApproxBoundsChecker
{
1365 ViewportAddStringApproxBoundsChecker(const DrawPixelInfo
*dpi
)
1367 this->top
= dpi
->top
- ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1368 this->bottom
= dpi
->top
+ dpi
->height
;
1371 bool IsSignMaybeOnScreen(const ViewportSign
*sign
) const
1373 return !(this->bottom
< sign
->top
|| this->top
> sign
->top
);
1377 static void ViewportAddTownNames(DrawPixelInfo
*dpi
)
1379 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) || _game_mode
== GM_MENU
) return;
1381 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1385 if (!checker
.IsSignMaybeOnScreen(&t
->cache
.sign
)) continue;
1386 ViewportAddString(dpi
, ZOOM_LVL_OUT_64X
, &t
->cache
.sign
,
1387 t
->Label(), t
->SmallLabel(), STR_VIEWPORT_TOWN_TINY_BLACK
,
1388 t
->index
, t
->cache
.population
);
1394 * Check whether to show a sign above a given station/waypoint.
1395 * @param st Pointer to the station/waypoint.
1396 * @return Whether the sign should be shown.
1398 static bool IsStationSignVisible(const BaseStation
*st
)
1400 /* Don't draw if the display options are disabled */
1401 return HasBit(_display_opt
, Station::IsExpected(st
) ? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
) &&
1402 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1403 (HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) || _local_company
== st
->owner
|| st
->owner
== OWNER_NONE
);
1407 static void ViewportAddStationNames(DrawPixelInfo
*dpi
)
1409 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || _game_mode
== GM_MENU
) return;
1411 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1413 const BaseStation
*st
;
1414 FOR_ALL_BASE_STATIONS(st
) {
1415 if (!IsStationSignVisible(st
)) continue;
1417 StringID str
= Station::IsExpected(st
) ? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
;
1419 if (!checker
.IsSignMaybeOnScreen(&st
->sign
)) continue;
1421 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &st
->sign
, str
, str
+ 1, STR_NULL
, st
->index
, st
->facilities
,
1422 (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
1427 static void ViewportAddSigns(DrawPixelInfo
*dpi
)
1429 /* Signs are turned off or are invisible */
1430 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
)) return;
1432 ViewportAddStringApproxBoundsChecker
checker(dpi
);
1436 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1437 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1438 * companies can leave OWNER_NONE signs after them. */
1439 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
1441 if (!checker
.IsSignMaybeOnScreen(&si
->sign
)) continue;
1443 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &si
->sign
,
1445 (IsTransparencySet(TO_SIGNS
) || si
->owner
== OWNER_DEITY
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
1446 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: (si
->owner
== OWNER_DEITY
? INVALID_COLOUR
: _company_colours
[si
->owner
]));
1451 * Update the position of the viewport sign.
1452 * @param center the (preferred) center of the viewport sign
1453 * @param top the new top of the sign
1454 * @param str the string to show in the sign
1455 * @param str_small the string to show when zoomed out. STR_NULL means same as \a str
1457 void ViewportSign::UpdatePosition(int center
, int top
, StringID str
, StringID str_small
)
1459 if (this->width_normal
!= 0) this->MarkDirty();
1463 char buffer
[DRAW_STRING_BUFFER
];
1465 GetString(buffer
, str
, lastof(buffer
));
1466 this->width_normal
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
).width
, 2) + VPSM_RIGHT
;
1467 this->center
= center
;
1469 /* zoomed out version */
1470 if (str_small
!= STR_NULL
) {
1471 GetString(buffer
, str_small
, lastof(buffer
));
1473 this->width_small
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + VPSM_RIGHT
;
1479 * Mark the sign dirty in all viewports.
1480 * @param maxzoom Maximum %ZoomLevel at which the text is visible.
1484 void ViewportSign::MarkDirty(ZoomLevel maxzoom
) const
1486 Rect zoomlevels
[ZOOM_LVL_COUNT
];
1488 for (ZoomLevel zoom
= ZOOM_LVL_BEGIN
; zoom
!= ZOOM_LVL_END
; zoom
++) {
1489 /* FIXME: This doesn't switch to width_small when appropriate. */
1490 zoomlevels
[zoom
].left
= this->center
- ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1491 zoomlevels
[zoom
].top
= this->top
- ScaleByZoom(1, zoom
);
1492 zoomlevels
[zoom
].right
= this->center
+ ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1493 zoomlevels
[zoom
].bottom
= this->top
+ ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
+ 1, zoom
);
1496 for (ViewPort
*vp
: _viewport_window_cache
) {
1497 if (vp
->zoom
<= maxzoom
) {
1498 Rect
&zl
= zoomlevels
[vp
->zoom
];
1499 MarkViewportDirty(vp
, zl
.left
, zl
.top
, zl
.right
, zl
.bottom
);
1504 static void ViewportDrawTileSprites(const TileSpriteToDrawVector
*tstdv
)
1506 const TileSpriteToDraw
*tsend
= tstdv
->End();
1507 for (const TileSpriteToDraw
*ts
= tstdv
->Begin(); ts
!= tsend
; ++ts
) {
1508 DrawSpriteViewport(ts
->image
, ts
->pal
, ts
->x
, ts
->y
, ts
->sub
);
1512 /** This fallback sprite checker always exists. */
1513 static bool ViewportSortParentSpritesChecker()
1518 /** Sort parent sprites pointer array */
1519 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
1521 ParentSpriteToDraw
**psdvend
= psdv
->End();
1522 ParentSpriteToDraw
**psd
= psdv
->Begin();
1523 while (psd
!= psdvend
) {
1524 ParentSpriteToDraw
*ps
= *psd
;
1526 if (ps
->comparison_done
) {
1531 ps
->comparison_done
= true;
1533 for (ParentSpriteToDraw
**psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
1534 ParentSpriteToDraw
*ps2
= *psd2
;
1536 if (ps2
->comparison_done
) continue;
1538 /* Decide which comparator to use, based on whether the bounding
1541 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
1542 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
1543 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
1544 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
1545 * the screen and with higher Z elevation, are drawn in front.
1546 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
1547 * i.e. X=(left+right)/2, etc.
1548 * However, since we only care about order, don't actually divide / 2
1550 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
1551 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
1555 /* We only change the order, if it is definite.
1556 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
1557 * That is: If one partial order says ps behind ps2, do not change the order.
1559 if (ps
->xmax
< ps2
->xmin
||
1560 ps
->ymax
< ps2
->ymin
||
1561 ps
->zmax
< ps2
->zmin
) {
1566 /* Move ps2 in front of ps */
1567 ParentSpriteToDraw
*temp
= ps2
;
1568 for (ParentSpriteToDraw
**psd3
= psd2
; psd3
> psd
; psd3
--) {
1569 *psd3
= *(psd3
- 1);
1576 static void ViewportDrawParentSprites(const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
1578 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1579 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1580 const ParentSpriteToDraw
*ps
= *it
;
1581 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSpriteViewport(ps
->image
, ps
->pal
, ps
->x
, ps
->y
, ps
->sub
);
1583 int child_idx
= ps
->first_child
;
1584 while (child_idx
>= 0) {
1585 const ChildScreenSpriteToDraw
*cs
= csstdv
->Get(child_idx
);
1586 child_idx
= cs
->next
;
1587 DrawSpriteViewport(cs
->image
, cs
->pal
, ps
->left
+ cs
->x
, ps
->top
+ cs
->y
, cs
->sub
);
1593 * Draws the bounding boxes of all ParentSprites
1594 * @param psd Array of ParentSprites
1596 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector
*psd
)
1598 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1599 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1600 const ParentSpriteToDraw
*ps
= *it
;
1601 Point pt1
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmax
+ 1); // top front corner
1602 Point pt2
= RemapCoords(ps
->xmin
, ps
->ymax
+ 1, ps
->zmax
+ 1); // top left corner
1603 Point pt3
= RemapCoords(ps
->xmax
+ 1, ps
->ymin
, ps
->zmax
+ 1); // top right corner
1604 Point pt4
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmin
); // bottom front corner
1606 DrawBox( pt1
.x
, pt1
.y
,
1607 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
1608 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
1609 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
1613 static void ViewportMapStoreBridgeTunnel(const ViewPort
* const vp
, const TileIndex tile
)
1615 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
1616 extern uint _company_to_list_pos
[MAX_COMPANIES
];
1618 /* No need to bother for hidden things */
1619 const bool tile_is_tunnel
= IsTunnel(tile
);
1620 if (tile_is_tunnel
) {
1621 if (!_settings_client
.gui
.show_tunnels_on_map
) return;
1623 if (!_settings_client
.gui
.show_bridges_on_map
) return;
1626 const Owner o
= GetTileOwner(tile
);
1628 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
1630 /* Check if already stored */
1631 TunnelBridgeToMapVector
* const tbtmv
= tile_is_tunnel
? &_vd
.tunnel_to_map
: &_vd
.bridge_to_map
;
1632 TunnelBridgeToMap
*tbtm
= tbtmv
->Begin();
1633 const TunnelBridgeToMap
* const tbtm_end
= tbtmv
->End();
1634 while (tbtm
!= tbtm_end
) {
1635 if (tile
== tbtm
->from_tile
|| tile
== tbtm
->to_tile
) return;
1639 /* It's a new one, add it to the list */
1640 tbtm
= tbtmv
->Append();
1641 TileIndex other_end
= GetOtherTunnelBridgeEnd(tile
);
1643 /* ensure deterministic ordering, to avoid render flicker */
1644 if (other_end
> tile
) {
1645 tbtm
->from_tile
= other_end
;
1646 tbtm
->to_tile
= tile
;
1648 tbtm
->from_tile
= tile
;
1649 tbtm
->to_tile
= other_end
;
1653 void ViewportMapClearTunnelCache()
1655 _vd
.tunnel_to_map
.Clear();
1658 void ViewportMapInvalidateTunnelCacheByTile(const TileIndex tile
)
1660 TunnelBridgeToMapVector
* const tbtmv
= &_vd
.tunnel_to_map
;
1661 for (TunnelBridgeToMap
*tbtm
= tbtmv
->Begin(); tbtm
!= tbtmv
->End(); tbtm
++) {
1662 if (tbtm
->from_tile
== tile
|| tbtm
->to_tile
== tile
) {
1670 * Draw/colour the blocks that have been redrawn.
1672 static void ViewportDrawDirtyBlocks()
1674 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1675 const DrawPixelInfo
*dpi
= _cur_dpi
;
1677 int right
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
1678 int bottom
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
1680 int colour
= _string_colourmap
[_dirty_block_colour
& 0xF];
1684 byte bo
= UnScaleByZoom(dpi
->left
+ dpi
->top
, dpi
->zoom
) & 1;
1686 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
1687 dst
= blitter
->MoveTo(dst
, 0, 1);
1688 } while (--bottom
> 0);
1691 static void ViewportDrawStrings(ZoomLevel zoom
, const StringSpriteToDrawVector
*sstdv
)
1693 const StringSpriteToDraw
*ssend
= sstdv
->End();
1694 for (const StringSpriteToDraw
*ss
= sstdv
->Begin(); ss
!= ssend
; ++ss
) {
1695 TextColour colour
= TC_BLACK
;
1696 bool small
= HasBit(ss
->width
, 15);
1697 int w
= GB(ss
->width
, 0, 15);
1698 int x
= UnScaleByZoom(ss
->x
, zoom
);
1699 int y
= UnScaleByZoom(ss
->y
, zoom
);
1700 int h
= VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
;
1702 SetDParam(0, ss
->params
[0]);
1703 SetDParam(1, ss
->params
[1]);
1705 if (ss
->colour
!= INVALID_COLOUR
) {
1706 /* Do not draw signs nor station names if they are set invisible */
1707 if (IsInvisibilitySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) continue;
1709 if (IsTransparencySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) {
1710 /* Don't draw the rectangle.
1711 * Real colours need the TC_IS_PALETTE_COLOUR flag.
1712 * Otherwise colours from _string_colourmap are assumed. */
1713 colour
= (TextColour
)_colour_gradient
[ss
->colour
][6] | TC_IS_PALETTE_COLOUR
;
1715 /* Draw the rectangle if 'transparent station signs' is off,
1716 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
1718 x
, y
, x
+ w
, y
+ h
, ss
->colour
,
1719 IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
1724 DrawString(x
+ VPSM_LEFT
, x
+ w
- 1 - VPSM_RIGHT
, y
+ VPSM_TOP
, ss
->string
, colour
, SA_HOR_CENTER
);
1728 static inline Vehicle
*GetVehicleFromWindow(Window
*w
)
1731 WindowClass wc
= w
->window_class
;
1732 WindowNumber wn
= w
->window_number
;
1734 if (wc
== WC_DROPDOWN_MENU
) GetParentWindowInfo(w
, wc
, wn
);
1737 case WC_VEHICLE_VIEW
:
1738 case WC_VEHICLE_ORDERS
:
1739 case WC_VEHICLE_TIMETABLE
:
1740 case WC_VEHICLE_DETAILS
:
1741 case WC_VEHICLE_REFIT
:
1742 case WC_VEHICLE_CARGO_TYPE_LOAD_ORDERS
:
1743 case WC_VEHICLE_CARGO_TYPE_UNLOAD_ORDERS
:
1744 if (wn
!= INVALID_VEHICLE
) return Vehicle::Get(wn
);
1753 static inline TileIndex
GetLastValidOrderLocation(const Vehicle
*veh
)
1756 TileIndex tmp
, result
= INVALID_TILE
;
1757 FOR_VEHICLE_ORDERS(veh
, order
) {
1758 switch (order
->GetType()) {
1759 case OT_GOTO_STATION
:
1760 case OT_GOTO_WAYPOINT
:
1763 tmp
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1764 if (tmp
!= INVALID_TILE
) result
= tmp
;
1773 static inline Order
*GetFinalOrder(const Vehicle
*veh
, Order
*order
)
1775 // Use Floyd's cycle-finding algorithm to prevent endless loop
1776 // due to a cycle formed by confitional orders.
1777 auto cycle_check
= order
;
1779 while (order
->IsType(OT_CONDITIONAL
)) {
1780 order
= veh
->GetOrder(order
->GetConditionSkipToOrder());
1782 if (cycle_check
->IsType(OT_CONDITIONAL
)) {
1783 cycle_check
= veh
->GetOrder(cycle_check
->GetConditionSkipToOrder());
1785 if (cycle_check
->IsType(OT_CONDITIONAL
)) {
1786 cycle_check
= veh
->GetOrder(cycle_check
->GetConditionSkipToOrder());
1790 bool cycle_detected
= (order
->IsType(OT_CONDITIONAL
) && (order
== cycle_check
));
1792 if (cycle_detected
) return nullptr;
1798 static bool ViewportMapPrepareVehicleRoute(const Vehicle
* const veh
)
1800 if (!veh
) return false;
1802 if (_vp_route_paths
.size() == 0) {
1803 TileIndex from_tile
= GetLastValidOrderLocation(veh
);
1804 if (from_tile
== INVALID_TILE
) return false;
1807 FOR_VEHICLE_ORDERS(veh
, order
) {
1808 Order
*final_order
= GetFinalOrder(veh
, order
);
1809 if (final_order
== nullptr) continue;
1810 const TileIndex to_tile
= final_order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1811 if (to_tile
== INVALID_TILE
) continue;
1813 DrawnPathRouteTileLine path
= { from_tile
, to_tile
, (final_order
== order
) };
1814 if (path
.from_tile
> path
.to_tile
) std::swap(path
.from_tile
, path
.to_tile
);
1815 _vp_route_paths
.push_back(path
);
1817 const OrderType ot
= order
->GetType();
1818 if (ot
== OT_GOTO_STATION
|| ot
== OT_GOTO_DEPOT
|| ot
== OT_GOTO_WAYPOINT
|| ot
== OT_IMPLICIT
) from_tile
= to_tile
;
1820 // remove duplicate lines
1821 std::sort(_vp_route_paths
.begin(), _vp_route_paths
.end());
1822 _vp_route_paths
.erase(std::unique(_vp_route_paths
.begin(), _vp_route_paths
.end()), _vp_route_paths
.end());
1827 /** Draw the route of a vehicle. */
1828 static void ViewportMapDrawVehicleRoute(const ViewPort
*vp
)
1830 const Vehicle
*veh
= GetVehicleFromWindow(_focused_window
);
1833 if (!_vp_route_paths
.empty()) {
1834 // make sure we remove any leftover paths
1835 MarkRoutePathsDirty(_vp_route_paths
);
1836 _vp_route_paths
.clear();
1837 _vp_route_paths_last_mark_dirty
.clear();
1838 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 0");
1843 switch (_settings_client
.gui
.show_vehicle_route
) {
1844 /* case 0: return; // No */
1846 if (!ViewportMapPrepareVehicleRoute(veh
)) {
1847 if (!_vp_route_paths
.empty()) {
1848 // make sure we remove any leftover paths
1849 MarkRoutePathsDirty(_vp_route_paths
);
1850 _vp_route_paths
.clear();
1851 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 1");
1856 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1857 _cur_dpi
= &_dpi_for_text
;
1859 for (const auto &iter
: _vp_route_paths
) {
1860 const Point from_pt
= RemapCoords2(TileX(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1861 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
1862 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
1864 const Point to_pt
= RemapCoords2(TileX(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1865 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
1866 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
1869 if (_settings_client
.gui
.dash_level_of_route_lines
== 0) {
1870 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3, _settings_client
.gui
.dash_level_of_route_lines
);
1873 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
);
1876 if (_vp_route_paths_last_mark_dirty
!= _vp_route_paths
) {
1877 // make sure we're not drawing a partial path
1878 MarkRoutePathsDirty(_vp_route_paths
);
1879 _vp_route_paths_last_mark_dirty
= _vp_route_paths
;
1880 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 2");
1889 static inline void DrawRouteStep(const ViewPort
* const vp
, const TileIndex tile
, const RankOrderTypeList list
)
1891 if (tile
== INVALID_TILE
) return;
1892 const uint step_count
= list
.size() > max_rank_order_type_count
? 1 : (uint
)list
.size();
1893 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1894 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
) - (_vp_route_step_width
/ 2);
1895 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
1896 const int rsth
= _vp_route_step_height_top
+ (int)step_count
* char_height
+ _vp_route_step_height_bottom
;
1897 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
) - rsth
;
1899 /* Draw the background. */
1900 DrawSprite(SPR_ROUTE_STEP_TOP
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
);
1901 uint y2
= y
+ _vp_route_step_height_top
;
1903 for (uint r
= step_count
; r
!= 0; r
--, y2
+= char_height
) {
1904 DrawSprite(SPR_ROUTE_STEP_MIDDLE
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
, &_vp_route_step_subsprite
);
1907 DrawSprite(SPR_ROUTE_STEP_BOTTOM
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1908 SpriteID s
= SPR_ROUTE_STEP_BOTTOM_SHADOW
;
1909 DrawSprite(SetBit(s
, PALETTE_MODIFIER_TRANSPARENT
), PALETTE_TO_TRANSPARENT
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1911 /* Fill with the data. */
1912 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1913 y2
= y
+ _vp_route_step_height_top
;
1914 _cur_dpi
= &_dpi_for_text
;
1916 if (list
.size() > max_rank_order_type_count
) {
1917 /* Write order overflow item */
1918 SetDParam(0, list
.size());
1919 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1920 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_OVERFLOW
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1922 for (RankOrderTypeList::const_iterator cit
= list
.begin(); cit
!= list
.end(); cit
++, y2
+= char_height
) {
1924 switch (cit
->second
) {
1925 case OT_GOTO_STATION
:
1926 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_STATION
);
1929 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_DEPOT
);
1931 case OT_GOTO_WAYPOINT
:
1932 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_WAYPOINT
);
1935 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_IMPLICIT
);
1937 default: // OT_NOTHING OT_LOADING OT_LEAVESTATION OT_DUMMY OT_CONDITIONAL
1942 /* Write order's info */
1943 SetDParam(0, cit
->first
);
1944 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1945 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1952 static bool ViewportPrepareVehicleRouteSteps(const Vehicle
* const veh
)
1954 if (!veh
) return false;
1956 if (_vp_route_steps
.size() == 0) {
1960 FOR_VEHICLE_ORDERS(veh
, order
) {
1961 const TileIndex tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1963 if (tile
== INVALID_TILE
) continue;
1964 _vp_route_steps
[tile
].push_back(std::pair
<int, OrderType
>(order_rank
, order
->GetType()));
1971 /** Draw the route steps of a vehicle. */
1972 static void ViewportDrawVehicleRouteSteps(const ViewPort
* const vp
)
1974 const Vehicle
* const veh
= GetVehicleFromWindow(_focused_window
);
1975 if (veh
&& ViewportPrepareVehicleRouteSteps(veh
)) {
1976 if (_vp_route_steps
!= _vp_route_steps_last_mark_dirty
) {
1977 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1978 MarkRouteStepDirty(cit
);
1980 _vp_route_steps_last_mark_dirty
= _vp_route_steps
;
1983 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1984 DrawRouteStep(vp
, cit
->first
, cit
->second
);
1989 void ViewportDrawPlans(const ViewPort
*vp
)
1991 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1992 _cur_dpi
= &_dpi_for_text
;
1996 if (!p
->IsVisible()) continue;
1997 for (PlanLineVector::iterator it
= p
->lines
.begin(); it
!= p
->lines
.end(); it
++) {
1999 if (!pl
->visible
) continue;
2000 for (uint i
= 1; i
< pl
->tiles
.size(); i
++) {
2001 const TileIndex from_tile
= pl
->tiles
[i
- 1];
2002 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2003 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2004 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2006 const TileIndex to_tile
= pl
->tiles
[i
];
2007 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2008 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2009 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2011 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3);
2013 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_RED
, 1);
2016 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 1);
2022 if (_current_plan
&& _current_plan
->temp_line
->tiles
.size() > 1) {
2023 for (uint i
= 1; i
< _current_plan
->temp_line
->tiles
.size(); i
++) {
2024 const TileIndex from_tile
= _current_plan
->temp_line
->tiles
[i
- 1];
2025 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2026 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2027 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2029 const TileIndex to_tile
= _current_plan
->temp_line
->tiles
[i
];
2030 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2031 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2032 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2034 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 3, 1);
2041 #define SLOPIFY_COLOUR(tile, height, vF, vW, vS, vE, vN, action) { \
2043 const Slope slope = GetTileSlope((tile), (height)); \
2046 case SLOPE_ELEVATED: \
2047 action (vF); break; \
2049 switch (slope & SLOPE_EW) { \
2050 case SLOPE_W: action (vW); break; \
2051 case SLOPE_E: action (vE); break; \
2052 default: action (slope & SLOPE_S) ? (vS) : (vN); break; \
2061 #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)
2062 #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 =)
2063 #define GET_SLOPE_INDEX(slope_index) SLOPIFY_COLOUR(tile, nullptr, 0, 1, 2, 3, 4, slope_index =)
2065 #define COL8TO32(x) _cur_palette.palette[x].data
2066 #define COLOUR_FROM_INDEX(x) ((const uint8 *)&(x))[colour_index]
2067 #define IS32(x) (is_32bpp ? COL8TO32(x) : (x))
2069 /* Variables containing Colour if 32bpp or palette index if 8bpp. */
2070 uint32 _vp_map_vegetation_clear_colours
[16][6][8]; ///< [Slope][ClearGround][Multi (see LoadClearGroundMainColours())]
2071 uint32 _vp_map_vegetation_tree_colours
[5][MAX_TREE_COUNT_BY_LANDSCAPE
]; ///< [TreeGround][max of _tree_count_by_landscape]
2072 uint32 _vp_map_water_colour
[5]; ///< [Slope]
2074 static inline uint
ViewportMapGetColourIndexMulti(const TileIndex tile
, const ClearGround cg
)
2080 return GetClearDensity(tile
);
2082 return GB(TileX(tile
) ^ TileY(tile
), 4, 3);
2084 return TileHash(TileX(tile
), TileY(tile
)) & 1;
2086 return GetFieldType(tile
) & 7;
2087 default: NOT_REACHED();
2091 static const ClearGround _treeground_to_clearground
[5] = {
2092 CLEAR_GRASS
, // TREE_GROUND_GRASS
2093 CLEAR_ROUGH
, // TREE_GROUND_ROUGH
2094 CLEAR_SNOW
, // TREE_GROUND_SNOW_DESERT, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2095 CLEAR_GRASS
, // TREE_GROUND_SHORE
2096 CLEAR_SNOW
, // TREE_GROUND_ROUGH_SNOW, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2099 template <bool is_32bpp
, bool show_slope
>
2100 static inline uint32
ViewportMapGetColourVegetation(const TileIndex tile
, TileType t
, const uint colour_index
)
2105 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, nullptr) & 15) : SLOPE_FLAT
;
2107 ClearGround cg
= GetClearGround(tile
);
2108 if (cg
== CLEAR_FIELDS
&& colour_index
& 1) {
2112 else multi
= ViewportMapGetColourIndexMulti(tile
, cg
);
2113 return _vp_map_vegetation_clear_colours
[slope
][cg
][multi
];
2117 colour
= IsTileForestIndustry(tile
) ? (colour_index
& 1 ? PC_GREEN
: 0x7B) : GREY_SCALE(3);
2121 const TreeGround tg
= GetTreeGround(tile
);
2122 const uint td
= GetTreeDensity(tile
);
2123 if (IsTransparencySet(TO_TREES
)) {
2124 ClearGround cg
= _treeground_to_clearground
[tg
];
2125 if (cg
== CLEAR_SNOW
&& _settings_game
.game_creation
.landscape
== LT_TROPIC
) cg
= CLEAR_DESERT
;
2126 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, nullptr) & 15) : SLOPE_FLAT
;
2127 uint32 ground_colour
= _vp_map_vegetation_clear_colours
[slope
][cg
][td
];
2129 if (IsInvisibilitySet(TO_TREES
)) {
2131 return ground_colour
;
2134 /* Take ground and make it darker. */
2136 return Blitter_32bppBase::MakeTransparent(ground_colour
, 192, 256).data
;
2139 /* 8bpp transparent snow trees give blue. Definitely don't want that. Prefer grey. */
2140 if (cg
== CLEAR_SNOW
&& td
> 1) return GREY_SCALE(13 - GetTreeCount(tile
));
2141 return _pal2trsp_remap_ptr
[ground_colour
];
2145 if (tg
== TREE_GROUND_SNOW_DESERT
|| tg
== TREE_GROUND_ROUGH_SNOW
) {
2146 return _vp_map_vegetation_clear_colours
[colour_index
][_settings_game
.game_creation
.landscape
== LT_TROPIC
? CLEAR_DESERT
: CLEAR_SNOW
][td
];
2149 const uint rnd
= min(GetTreeCount(tile
) ^ (((tile
& 3) ^ (TileY(tile
) & 3)) * td
), MAX_TREE_COUNT_BY_LANDSCAPE
- 1);
2150 return _vp_map_vegetation_tree_colours
[tg
][rnd
];
2157 uint slope_index
= 0;
2158 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2159 return _vp_map_water_colour
[slope_index
];
2164 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[t
]);
2165 colour
= COLOUR_FROM_INDEX(colour
);
2170 return COL8TO32(colour
);
2173 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, nullptr, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2178 template <bool is_32bpp
, bool show_slope
>
2179 static inline uint32
ViewportMapGetColourIndustries(const TileIndex tile
, const TileType t
, const uint colour_index
)
2181 extern LegendAndColour _legend_from_industries
[NUM_INDUSTRYTYPES
+ 1];
2182 extern uint _industry_to_list_pos
[NUM_INDUSTRYTYPES
];
2183 extern bool _smallmap_show_heightmap
;
2186 if (t
== MP_INDUSTRY
) {
2187 /* If industry is allowed to be seen, use its colour on the map. */
2188 const IndustryType it
= Industry::GetByTile(tile
)->type
;
2189 if (_legend_from_industries
[_industry_to_list_pos
[it
]].show_on_map
)
2190 return IS32(GetIndustrySpec(it
)->map_colour
);
2191 /* Otherwise, return the colour which will make it disappear. */
2192 t2
= IsTileOnWater(tile
) ? MP_WATER
: MP_CLEAR
;
2195 if (is_32bpp
&& t2
== MP_WATER
) {
2196 uint slope_index
= 0;
2197 if (t
!= MP_INDUSTRY
&& GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
); ///< Ignore industry on water not shown on map.
2198 return _vp_map_water_colour
[slope_index
];
2201 const int h
= TileHeight(tile
);
2202 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
2203 const uint32 colours
= ApplyMask(_smallmap_show_heightmap
? cs
->height_colours
[h
] : cs
->default_colour
, &_smallmap_vehicles_andor
[t2
]);
2204 uint32 colour
= COLOUR_FROM_INDEX(colours
);
2206 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, nullptr, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2208 return IS32(colour
);
2211 template <bool is_32bpp
, bool show_slope
>
2212 static inline uint32
ViewportMapGetColourOwner(const TileIndex tile
, TileType t
, const uint colour_index
)
2214 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2215 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2218 case MP_INDUSTRY
: return IS32(PC_DARK_GREY
);
2219 case MP_HOUSE
: return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
2223 const Owner o
= GetTileOwner(tile
);
2224 if ((o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) || o
== OWNER_NONE
|| o
== OWNER_WATER
) {
2225 if (t
== MP_WATER
) {
2227 uint slope_index
= 0;
2228 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2229 return _vp_map_water_colour
[slope_index
];
2236 const int h
= TileHeight(tile
);
2237 uint32 colour
= COLOUR_FROM_INDEX(_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
].height_colours
[h
]);
2238 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, nullptr, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2239 return IS32(colour
);
2242 else if (o
== OWNER_TOWN
) {
2243 return IS32(t
== MP_ROAD
? (colour_index
& 1 ? PC_BLACK
: GREY_SCALE(3)) : PC_DARK_RED
);
2246 /* Train stations are sometimes hard to spot.
2247 * So we give the player a hint by mixing his colour with black. */
2248 uint32 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2249 if (t
!= MP_STATION
) {
2250 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, nullptr, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2253 if (GetStationType(tile
) == STATION_RAIL
) colour
= colour_index
& 1 ? colour
: PC_BLACK
;
2255 if (is_32bpp
) return COL8TO32(colour
);
2259 static inline void ViewportMapStoreBridgeAboveTile(const ViewPort
* const vp
, const TileIndex tile
)
2261 /* No need to bother for hidden things */
2262 if (!_settings_client
.gui
.show_bridges_on_map
) return;
2264 /* Check existing stored bridges */
2265 TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin();
2266 TunnelBridgeToMap
*tbtm_end
= _vd
.bridge_to_map
.End();
2267 for (; tbtm
!= tbtm_end
; ++tbtm
) {
2268 if (!IsBridge(tbtm
->from_tile
)) continue;
2270 TileIndex from
= tbtm
->from_tile
;
2271 TileIndex to
= tbtm
->to_tile
;
2272 if (TileX(from
) == TileX(to
) && TileX(from
) == TileX(tile
)) {
2273 if (TileY(from
) > TileY(to
)) std::swap(from
, to
);
2274 if (TileY(from
) <= TileY(tile
) && TileY(tile
) <= TileY(to
)) return; /* already covered */
2276 else if (TileY(from
) == TileY(to
) && TileY(from
) == TileY(tile
)) {
2277 if (TileX(from
) > TileX(to
)) std::swap(from
, to
);
2278 if (TileX(from
) <= TileX(tile
) && TileX(tile
) <= TileX(to
)) return; /* already covered */
2282 ViewportMapStoreBridgeTunnel(vp
, GetSouthernBridgeEnd(tile
));
2285 static inline TileIndex
ViewportMapGetMostSignificantTileType(const ViewPort
* const vp
, const TileIndex from_tile
, TileType
* const tile_type
)
2287 if (vp
->zoom
<= ZOOM_LVL_OUT_128X
|| !_settings_client
.gui
.viewport_map_scan_surroundings
) {
2288 const TileType ttype
= GetTileType(from_tile
);
2289 /* Store bridges and tunnels. */
2290 if (ttype
!= MP_TUNNELBRIDGE
) {
2292 if (IsBridgeAbove(from_tile
)) ViewportMapStoreBridgeAboveTile(vp
, from_tile
);
2295 ViewportMapStoreBridgeTunnel(vp
, from_tile
);
2296 switch (GetTunnelBridgeTransportType(from_tile
)) {
2297 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2298 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2299 default: *tile_type
= MP_WATER
; break;
2305 const uint8 length
= (vp
->zoom
- ZOOM_LVL_OUT_128X
) * 2;
2306 TileArea tile_area
= TileArea(from_tile
, length
, length
);
2307 tile_area
.ClampToMap();
2309 /* Find the most important tile of the area. */
2310 TileIndex result
= from_tile
;
2311 uint importance
= 0;
2312 TILE_AREA_LOOP_WITH_PREFETCH(tile
, tile_area
) {
2313 const TileType ttype
= GetTileType(tile
);
2314 const uint tile_importance
= _tiletype_importance
[ttype
];
2315 if (tile_importance
> importance
) {
2316 importance
= tile_importance
;
2319 if (ttype
!= MP_TUNNELBRIDGE
&& IsBridgeAbove(tile
)) {
2320 ViewportMapStoreBridgeAboveTile(vp
, tile
);
2324 /* Store bridges and tunnels. */
2325 *tile_type
= GetTileType(result
);
2326 if (*tile_type
== MP_TUNNELBRIDGE
) {
2327 ViewportMapStoreBridgeTunnel(vp
, result
);
2328 switch (GetTunnelBridgeTransportType(result
)) {
2329 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2330 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2331 default: *tile_type
= MP_WATER
; break;
2338 /** Get the colour of a tile, can be 32bpp RGB or 8bpp palette index. */
2339 template <bool is_32bpp
, bool show_slope
>
2340 uint32
ViewportMapGetColour(const ViewPort
* const vp
, uint x
, uint y
, const uint colour_index
)
2342 if (!(IsInsideMM(x
, TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1) &&
2343 IsInsideMM(y
, TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1)))
2346 /* Very approximative but fast way to get the tile when taking Z into account. */
2347 const TileIndex tile_tmp
= TileVirtXY(x
, y
);
2348 const uint z
= TileHeight(tile_tmp
) * 4;
2349 TileIndex tile
= TileVirtXY(x
+ z
, y
+ z
);
2350 if (tile
>= MapSize()) return 0;
2351 if (_settings_game
.construction
.freeform_edges
) {
2352 /* tile_tmp and tile must be from the same side,
2353 * otherwise it's an approximation erroneous case
2354 * that leads to a graphic glitch below south west border.
2356 if (TileX(tile_tmp
) > (MapSizeX() - (MapSizeX() / 8)))
2357 if ((TileX(tile_tmp
) < (MapSizeX() / 2)) != (TileX(tile
) < (MapSizeX() / 2)))
2360 TileType tile_type
= MP_VOID
;
2361 tile
= ViewportMapGetMostSignificantTileType(vp
, tile
, &tile_type
);
2362 if (tile_type
== MP_VOID
) return 0;
2364 /* Return the colours. */
2365 switch (vp
->map_type
) {
2366 default: return ViewportMapGetColourOwner
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2367 case VPMT_INDUSTRY
: return ViewportMapGetColourIndustries
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2368 case VPMT_VEGETATION
: return ViewportMapGetColourVegetation
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2372 /* Taken from http://stereopsis.com/doubleblend.html, PixelBlend() is faster than ComposeColourRGBANoCheck() */
2373 static inline void PixelBlend(uint32
* const d
, const uint32 s
)
2375 const uint32 a
= (s
>> 24) + 1;
2376 const uint32 dstrb
= *d
& 0xFF00FF;
2377 const uint32 dstg
= *d
& 0xFF00;
2378 const uint32 srcrb
= s
& 0xFF00FF;
2379 const uint32 srcg
= s
& 0xFF00;
2380 uint32 drb
= srcrb
- dstrb
;
2381 uint32 dg
= srcg
- dstg
;
2386 uint32 rb
= (drb
+ dstrb
) & 0xFF00FF;
2387 uint32 g
= (dg
+ dstg
) & 0xFF00;
2391 /** Draw the bounding boxes of the scrolling viewport (right-clicked and dragged) */
2392 static void ViewportMapDrawScrollingViewportBox(const ViewPort
* const vp
)
2394 if (_scrolling_viewport
&& _scrolling_viewport
->viewport
) {
2395 const ViewPort
* const vp_scrolling
= _scrolling_viewport
->viewport
;
2396 if (vp_scrolling
->zoom
< ZOOM_LVL_DRAW_MAP
) {
2397 /* Check intersection of dpi and vp_scrolling */
2398 const int mask
= ScaleByZoom(-1, vp
->zoom
);
2399 const int vp_scrolling_virtual_top_mask
= vp_scrolling
->virtual_top
& mask
;
2400 const int vp_scrolling_virtual_bottom_mask
= (vp_scrolling
->virtual_top
+ vp_scrolling
->virtual_height
) & mask
;
2401 const int t_inter
= max(vp_scrolling_virtual_top_mask
, _vd
.dpi
.top
);
2402 const int b_inter
= min(vp_scrolling_virtual_bottom_mask
, _vd
.dpi
.top
+ _vd
.dpi
.height
);
2403 if (t_inter
< b_inter
) {
2404 const int vp_scrolling_virtual_left_mask
= vp_scrolling
->virtual_left
& mask
;
2405 const int vp_scrolling_virtual_right_mask
= (vp_scrolling
->virtual_left
+ vp_scrolling
->virtual_width
) & mask
;
2406 const int l_inter
= max(vp_scrolling_virtual_left_mask
, _vd
.dpi
.left
);
2407 const int r_inter
= min(vp_scrolling_virtual_right_mask
, _vd
.dpi
.left
+ _vd
.dpi
.width
);
2408 if (l_inter
< r_inter
) {
2409 /* OK, so we can draw something that tells where the scrolling viewport is */
2410 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2411 const int w_inter
= UnScaleByZoom(r_inter
- l_inter
, vp
->zoom
);
2412 const int h_inter
= UnScaleByZoom(b_inter
- t_inter
, vp
->zoom
);
2413 const int x
= UnScaleByZoom(l_inter
- _vd
.dpi
.left
, vp
->zoom
);
2414 const int y
= UnScaleByZoom(t_inter
- _vd
.dpi
.top
, vp
->zoom
);
2416 /* If asked, with 32bpp we can do some blending */
2417 if (_settings_client
.gui
.show_scrolling_viewport_on_map
>= 2 && blitter
->GetScreenDepth() == 32)
2418 for (int j
= y
; j
< y
+ h_inter
; j
++)
2419 for (int i
= x
; i
< x
+ w_inter
; i
++)
2420 PixelBlend((uint32
*)blitter
->MoveTo(_vd
.dpi
.dst_ptr
, i
, j
), 0x40FCFCFC);
2422 /* Draw area contour */
2423 if (_settings_client
.gui
.show_scrolling_viewport_on_map
!= 2) {
2424 if (t_inter
== vp_scrolling_virtual_top_mask
)
2425 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2426 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
, PC_WHITE
);
2427 if (b_inter
== vp_scrolling_virtual_bottom_mask
)
2428 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2429 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
+ h_inter
, PC_WHITE
);
2430 if (l_inter
== vp_scrolling_virtual_left_mask
)
2431 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2432 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, j
, PC_WHITE
);
2433 if (r_inter
== vp_scrolling_virtual_right_mask
)
2434 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2435 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
+ w_inter
, j
, PC_WHITE
);
2443 uint32
*_vp_map_line
; ///< Buffer for drawing the map of a viewport.
2445 static void ViewportMapDrawBridgeTunnel(const ViewPort
* const vp
, const TunnelBridgeToMap
* const tbtm
, const int z
,
2446 const bool is_tunnel
, const int w
, const int h
, Blitter
* const blitter
)
2448 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2449 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2451 TileIndex tile
= tbtm
->from_tile
;
2452 const Owner o
= GetTileOwner(tile
);
2453 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
2456 if (vp
->map_type
== VPMT_OWNER
&& _settings_client
.gui
.use_owner_colour_for_tunnelbridge
&& o
< MAX_COMPANIES
) {
2457 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2458 colour
= is_tunnel
? _darken_colour
[colour
] : _lighten_colour
[colour
];
2461 colour
= is_tunnel
? PC_BLACK
: PC_VERY_LIGHT_YELLOW
;
2464 TileIndexDiff delta
= TileOffsByDiagDir(GetTunnelBridgeDirection(tile
));
2465 for (; tile
!= tbtm
->to_tile
; tile
+= delta
) { // For each tile
2466 const Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, z
);
2467 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2468 if (IsInsideMM(x
, 0, w
)) {
2469 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2470 if (IsInsideMM(y
, 0, h
)) blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, y
, colour
);
2475 /** Draw the map on a viewport. */
2476 template <bool is_32bpp
, bool show_slope
>
2477 void ViewportMapDraw(const ViewPort
* const vp
)
2479 assert(vp
!= nullptr);
2480 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2482 SmallMapWindow::RebuildColourIndexIfNecessary();
2484 /* Index of colour: _green_map_heights[] contains blocks of 4 colours, say ABCD
2485 * For a XXXY colour block to render nicely, follow the model:
2486 * line 1: ABCDABCDABCD
2487 * line 2: CDABCDABCDAB
2488 * line 3: ABCDABCDABCD
2489 * => colour_index_base's second bit is changed every new line.
2491 const int sx
= UnScaleByZoomLower(_vd
.dpi
.left
, _vd
.dpi
.zoom
);
2492 const int sy
= UnScaleByZoomLower(_vd
.dpi
.top
, _vd
.dpi
.zoom
);
2493 const uint line_padding
= 2 * (sy
& 1);
2494 uint colour_index_base
= (sx
+ line_padding
) & 3;
2496 const int incr_a
= (1 << (vp
->zoom
- 2)) / ZOOM_LVL_BASE
;
2497 const int incr_b
= (1 << (vp
->zoom
- 1)) / ZOOM_LVL_BASE
;
2498 const int a
= (_vd
.dpi
.left
>> 2) / ZOOM_LVL_BASE
;
2499 int b
= (_vd
.dpi
.top
>> 1) / ZOOM_LVL_BASE
;
2500 const int w
= UnScaleByZoom(_vd
.dpi
.width
, vp
->zoom
);
2501 const int h
= UnScaleByZoom(_vd
.dpi
.height
, vp
->zoom
);
2504 /* Render base map. */
2505 do { // For each line
2507 uint colour_index
= colour_index_base
;
2508 colour_index_base
^= 2;
2509 uint32
*vp_map_line_ptr32
= _vp_map_line
;
2510 uint8
*vp_map_line_ptr8
= (uint8
*)_vp_map_line
;
2513 do { // For each pixel of a line
2515 *vp_map_line_ptr32
= ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2516 vp_map_line_ptr32
++;
2519 *vp_map_line_ptr8
= (uint8
)ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2522 colour_index
= ++colour_index
& 3;
2527 blitter
->SetLine32(_vd
.dpi
.dst_ptr
, 0, j
, _vp_map_line
, w
);
2530 blitter
->SetLine(_vd
.dpi
.dst_ptr
, 0, j
, (uint8
*)_vp_map_line
, w
);
2535 /* Render tunnels */
2536 if (_settings_client
.gui
.show_tunnels_on_map
&& _vd
.tunnel_to_map
.Length() != 0) {
2537 const TunnelBridgeToMap
* const tbtm_end
= _vd
.tunnel_to_map
.End();
2538 for (const TunnelBridgeToMap
*tbtm
= _vd
.tunnel_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each tunnel
2539 const int tunnel_z
= GetTileZ(tbtm
->from_tile
) * TILE_HEIGHT
;
2540 const Point pt_from
= RemapCoords(TileX(tbtm
->from_tile
) * TILE_SIZE
, TileY(tbtm
->from_tile
) * TILE_SIZE
, tunnel_z
);
2541 const Point pt_to
= RemapCoords(TileX(tbtm
->to_tile
) * TILE_SIZE
, TileY(tbtm
->to_tile
) * TILE_SIZE
, tunnel_z
);
2543 /* check if tunnel is wholly outside redrawing area */
2544 const int x_from
= UnScaleByZoomLower(pt_from
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2545 const int x_to
= UnScaleByZoomLower(pt_to
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2546 if ((x_from
< 0 && x_to
< 0) || (x_from
> w
&& x_to
> w
)) continue;
2547 const int y_from
= UnScaleByZoomLower(pt_from
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2548 const int y_to
= UnScaleByZoomLower(pt_to
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2549 if ((y_from
< 0 && y_to
< 0) || (y_from
> h
&& y_to
> h
)) continue;
2551 ViewportMapDrawBridgeTunnel(vp
, tbtm
, tunnel_z
, true, w
, h
, blitter
);
2555 /* Render bridges */
2556 if (_settings_client
.gui
.show_bridges_on_map
&& _vd
.bridge_to_map
.Length() != 0) {
2557 const TunnelBridgeToMap
* const tbtm_end
= _vd
.bridge_to_map
.End();
2558 for (const TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each bridge
2559 ViewportMapDrawBridgeTunnel(vp
, tbtm
, (GetBridgeHeight(tbtm
->from_tile
) - 1) * TILE_HEIGHT
, false, w
, h
, blitter
);
2564 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2566 DrawPixelInfo
*old_dpi
= _cur_dpi
;
2567 _cur_dpi
= &_vd
.dpi
;
2569 _vd
.dpi
.zoom
= vp
->zoom
;
2570 int mask
= ScaleByZoom(-1, vp
->zoom
);
2572 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
2574 _vd
.dpi
.width
= (right
- left
) & mask
;
2575 _vd
.dpi
.height
= (bottom
- top
) & mask
;
2576 _vd
.dpi
.left
= left
& mask
;
2577 _vd
.dpi
.top
= top
& mask
;
2578 _vd
.dpi
.pitch
= old_dpi
->pitch
;
2579 _vd
.last_child
= nullptr;
2581 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
2582 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
2584 _vd
.dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
2586 _dpi_for_text
= _vd
.dpi
;
2587 _dpi_for_text
.left
= UnScaleByZoom(_dpi_for_text
.left
, _dpi_for_text
.zoom
);
2588 _dpi_for_text
.top
= UnScaleByZoom(_dpi_for_text
.top
, _dpi_for_text
.zoom
);
2589 _dpi_for_text
.width
= UnScaleByZoom(_dpi_for_text
.width
, _dpi_for_text
.zoom
);
2590 _dpi_for_text
.height
= UnScaleByZoom(_dpi_for_text
.height
, _dpi_for_text
.zoom
);
2591 _dpi_for_text
.zoom
= ZOOM_LVL_NORMAL
;
2593 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
2594 /* Here the rendering is like smallmap. */
2595 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
2596 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<true, true>(vp
);
2597 else ViewportMapDraw
<true, false>(vp
);
2600 _pal2trsp_remap_ptr
= IsTransparencySet(TO_TREES
) ? GetNonSprite(GB(PALETTE_TO_TRANSPARENT
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1 : nullptr;
2601 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<false, true>(vp
);
2602 else ViewportMapDraw
<false, false>(vp
);
2604 ViewportMapDrawVehicles(&_vd
.dpi
);
2605 if (_scrolling_viewport
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) ViewportMapDrawScrollingViewportBox(vp
);
2606 if (vp
->zoom
< ZOOM_LVL_OUT_256X
) ViewportAddTownNames(&_vd
.dpi
);
2609 /* Classic rendering. */
2610 ViewportAddLandscape();
2611 ViewportAddVehicles(&_vd
.dpi
);
2613 ViewportAddTownNames(&_vd
.dpi
);
2614 ViewportAddStationNames(&_vd
.dpi
);
2615 ViewportAddSigns(&_vd
.dpi
);
2617 DrawTextEffects(&_vd
.dpi
);
2619 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
2621 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
2622 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
2623 *_vd
.parent_sprites_to_sort
.Append() = it
;
2626 _vp_sprite_sorter(&_vd
.parent_sprites_to_sort
);
2627 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
2629 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
2631 if (_draw_dirty_blocks
) ViewportDrawDirtyBlocks();
2633 DrawPixelInfo dp
= _vd
.dpi
;
2634 ZoomLevel zoom
= _vd
.dpi
.zoom
;
2635 dp
.zoom
= ZOOM_LVL_NORMAL
;
2636 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
2637 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
2640 if (vp
->overlay
!= nullptr && vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
2641 /* translate to window coordinates */
2644 vp
->overlay
->Draw(&dp
);
2647 if (_settings_client
.gui
.show_vehicle_route
) ViewportMapDrawVehicleRoute(vp
);
2648 if (_vd
.string_sprites_to_draw
.Length() != 0) {
2649 /* translate to world coordinates */
2650 dp
.left
= UnScaleByZoom(_vd
.dpi
.left
, zoom
);
2651 dp
.top
= UnScaleByZoom(_vd
.dpi
.top
, zoom
);
2652 ViewportDrawStrings(zoom
, &_vd
.string_sprites_to_draw
);
2654 if (_settings_client
.gui
.show_vehicle_route_steps
) ViewportDrawVehicleRouteSteps(vp
);
2655 ViewportDrawPlans(vp
);
2659 _vd
.bridge_to_map
.Clear();
2660 _vd
.string_sprites_to_draw
.Clear();
2661 _vd
.tile_sprites_to_draw
.Clear();
2662 _vd
.parent_sprites_to_draw
.Clear();
2663 _vd
.parent_sprites_to_sort
.Clear();
2664 _vd
.child_screen_sprites_to_draw
.Clear();
2668 * Make sure we don't draw a too big area at a time.
2669 * If we do, the sprite sorter will run into major performance problems and the sprite memory may overflow.
2671 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2673 if ((vp
->zoom
< ZOOM_LVL_DRAW_MAP
) && (ScaleByZoom(bottom
- top
, vp
->zoom
) * ScaleByZoom(right
- left
, vp
->zoom
) > 180000 * ZOOM_LVL_BASE
* ZOOM_LVL_BASE
)) {
2674 if ((bottom
- top
) > (right
- left
)) {
2675 int t
= (top
+ bottom
) >> 1;
2676 ViewportDrawChk(vp
, left
, top
, right
, t
);
2677 ViewportDrawChk(vp
, left
, t
, right
, bottom
);
2679 int t
= (left
+ right
) >> 1;
2680 ViewportDrawChk(vp
, left
, top
, t
, bottom
);
2681 ViewportDrawChk(vp
, t
, top
, right
, bottom
);
2685 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2686 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
2687 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2688 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
2693 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2695 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
2697 if (left
>= vp
->left
+ vp
->width
) return;
2699 if (left
< vp
->left
) left
= vp
->left
;
2700 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
2702 if (top
>= vp
->top
+ vp
->height
) return;
2704 if (top
< vp
->top
) top
= vp
->top
;
2705 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
2707 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
2711 * Draw the viewport of this window.
2713 void Window::DrawViewport() const
2715 DrawPixelInfo
*dpi
= _cur_dpi
;
2717 dpi
->left
+= this->left
;
2718 dpi
->top
+= this->top
;
2720 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
2722 dpi
->left
-= this->left
;
2723 dpi
->top
-= this->top
;
2727 * Continue criteria for the SearchMapEdge function.
2728 * @param iter Value to check.
2729 * @param iter_limit Maximum value for the iter
2730 * @param sy Screen y coordinate calculated for the tile at hand
2731 * @param sy_limit Limit to the screen y coordinate
2732 * @return True when we should continue searching.
2734 typedef bool ContinueMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
);
2736 /** Continue criteria for searching a no-longer-visible tile in negative direction, starting at some tile. */
2737 static inline bool ContinueLowerMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
> 0 && sy
> sy_limit
; }
2738 /** Continue criteria for searching a no-longer-visible tile in positive direction, starting at some tile. */
2739 static inline bool ContinueUpperMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
< iter_limit
&& sy
< sy_limit
; }
2742 * Searches, starting at the given tile, by applying the given offset to iter, for a no longer visible tile.
2743 * The whole sense of this function is keeping the to-be-written code small, thus it is a little bit abstracted
2744 * so the same function can be used for both the X and Y locations. As such a reference to one of the elements
2745 * in curr_tile was needed.
2746 * @param curr_tile A tile
2747 * @param iter Reference to either the X or Y of curr_tile.
2748 * @param iter_limit Upper search limit for the iter value.
2749 * @param offset Search in steps of this size
2750 * @param sy_limit Search limit to be passed to the criteria
2751 * @param continue_criteria Search as long as this criteria is true
2752 * @return The final value of iter.
2754 static int SearchMapEdge(Point
&curr_tile
, int &iter
, int iter_limit
, int offset
, int sy_limit
, ContinueMapEdgeSearch continue_criteria
)
2758 iter
= Clamp(iter
+ offset
, 0, iter_limit
);
2759 sy
= GetViewportY(curr_tile
);
2760 } while (continue_criteria(iter
, iter_limit
, sy
, sy_limit
));
2766 * Determine the clamping of either the X or Y coordinate to the map.
2767 * @param curr_tile A tile
2768 * @param iter Reference to either the X or Y of curr_tile.
2769 * @param iter_limit Upper search limit for the iter value.
2770 * @param start Start value for the iteration.
2771 * @param other_ref Reference to the opposite axis in curr_tile than of iter.
2772 * @param other_value Start value for of the opposite axis
2773 * @param vp_value Value of the viewport location in the opposite axis as for iter.
2774 * @param other_limit Limit for the other value, so if iter is X, then other_limit is for Y.
2775 * @param vp_top Top of the viewport.
2776 * @param vp_bottom Bottom of the viewport.
2777 * @return Clamped version of vp_value.
2779 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
)
2781 bool upper_edge
= other_value
< _settings_game
.construction
.max_heightlevel
/ 4;
2784 * First get an estimate of the tiles relevant for us at that edge. Relevant in the sense
2785 * "at least close to the visible area". Thus, we don't look at exactly each tile, inspecting
2786 * e.g. every tenth should be enough. After all, the desired screen limit is set such that
2787 * the bordermost tiles are painted in the middle of the screen when one hits the limit,
2788 * i.e. it is no harm if there is some small error in that calculation
2791 other_ref
= upper_edge
? 0 : other_limit
;
2793 int min_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? -10 : +10, vp_top
, upper_edge
? ContinueLowerMapEdgeSearch
: ContinueUpperMapEdgeSearch
);
2795 int max_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? +10 : -10, vp_bottom
, upper_edge
? ContinueUpperMapEdgeSearch
: ContinueLowerMapEdgeSearch
);
2797 max_iter
= min(max_iter
+ _settings_game
.construction
.max_heightlevel
/ 4, iter_limit
);
2798 min_iter
= min(min_iter
, max_iter
);
2800 /* Now, calculate the highest heightlevel of these tiles. Again just as an estimate. */
2801 int max_heightlevel_at_edge
= 0;
2802 for (iter
= min_iter
; iter
<= max_iter
; iter
+= 10) {
2803 max_heightlevel_at_edge
= max(max_heightlevel_at_edge
, (int)TileHeight(TileXY(curr_tile
.x
, curr_tile
.y
)));
2806 /* Based on that heightlevel, calculate the limit. For the upper edge a tile with height zero would
2807 * get a limit of zero, on the other side it depends on the number of tiles along the axis. */
2809 max(vp_value
, -max_heightlevel_at_edge
* (int)(TILE_HEIGHT
* 2 * ZOOM_LVL_BASE
)) :
2810 min(vp_value
, (other_limit
* TILE_SIZE
* 4 - max_heightlevel_at_edge
* TILE_HEIGHT
* 2) * ZOOM_LVL_BASE
);
2813 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
2817 /* Centre of the viewport is hot spot */
2818 x
+= vp
->virtual_width
/ 2;
2819 y
+= vp
->virtual_height
/ 2;
2821 /* Convert viewport coordinates to map coordinates
2822 * Calculation is scaled by 4 to avoid rounding errors */
2823 int vx
= -x
+ y
* 2;
2826 /* Find out which tile corresponds to (vx,vy) if one assumes height zero. The cast is necessary to prevent C++ from
2827 * converting the result to an uint, which gives an overflow instead of a negative result... */
2828 int tx
= vx
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2829 int ty
= vy
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2832 vx
= ClampXYToMap(curr_tile
, curr_tile
.y
, MapMaxY(), ty
, curr_tile
.x
, tx
, vx
, MapMaxX(), original_y
, original_y
+ vp
->virtual_height
);
2833 vy
= ClampXYToMap(curr_tile
, curr_tile
.x
, MapMaxX(), tx
, curr_tile
.y
, ty
, vy
, MapMaxY(), original_y
, original_y
+ vp
->virtual_height
);
2835 /* Convert map coordinates to viewport coordinates */
2839 /* Remove centering */
2840 x
-= vp
->virtual_width
/ 2;
2841 y
-= vp
->virtual_height
/ 2;
2845 * Update the viewport position being displayed.
2846 * @param w %Window owning the viewport.
2848 void UpdateViewportPosition(Window
*w
)
2850 const ViewPort
*vp
= w
->viewport
;
2852 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
2853 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
2854 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
2856 w
->viewport
->scrollpos_x
= pt
.x
;
2857 w
->viewport
->scrollpos_y
= pt
.y
;
2858 SetViewportPosition(w
, pt
.x
, pt
.y
);
2860 /* Ensure the destination location is within the map */
2861 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
2863 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
2864 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
2866 bool update_overlay
= false;
2867 if (delta_x
!= 0 || delta_y
!= 0) {
2868 if (_settings_client
.gui
.smooth_scroll
) {
2869 int max_scroll
= ScaleByMapSize1D(512 * ZOOM_LVL_BASE
);
2870 /* Not at our desired position yet... */
2871 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
2872 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
2874 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
2875 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
2877 update_overlay
= (w
->viewport
->scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
2878 w
->viewport
->scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
2881 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2883 if (_scrolling_viewport
== w
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) {
2884 const int gap
= ScaleByZoom(1, ZOOM_LVL_MAX
);
2886 int lr_low
= vp
->virtual_left
;
2887 int lr_hi
= w
->viewport
->scrollpos_x
;
2888 if (lr_low
> lr_hi
) Swap(lr_low
, lr_hi
);
2889 int right
= lr_hi
+ vp
->virtual_width
+ gap
;
2891 int tb_low
= vp
->virtual_top
;
2892 int tb_hi
= w
->viewport
->scrollpos_y
;
2893 if (tb_low
> tb_hi
) Swap(tb_low
, tb_hi
);
2894 int bottom
= tb_hi
+ vp
->virtual_height
+ gap
;
2896 MarkAllViewportMapsDirty(lr_low
, tb_low
, right
, bottom
);
2899 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2900 if (update_overlay
) RebuildViewportOverlay(w
);
2905 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
2906 * @param vp The viewport to mark as dirty
2907 * @param left Left edge of area to repaint
2908 * @param top Top edge of area to repaint
2909 * @param right Right edge of area to repaint
2910 * @param bottom Bottom edge of area to repaint
2913 static void MarkViewportDirty(const ViewPort
* const vp
, int left
, int top
, int right
, int bottom
)
2915 /* Rounding wrt. zoom-out level */
2916 right
+= (1 << vp
->zoom
) - 1;
2917 bottom
+= (1 << vp
->zoom
) - 1;
2919 right
-= vp
->virtual_left
;
2920 if (right
<= 0) return;
2922 bottom
-= vp
->virtual_top
;
2923 if (bottom
<= 0) return;
2925 left
= max(0, left
- vp
->virtual_left
);
2927 if (left
>= vp
->virtual_width
) return;
2929 top
= max(0, top
- vp
->virtual_top
);
2931 if (top
>= vp
->virtual_height
) return;
2934 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
2935 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
2936 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
2937 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
2942 * Mark all viewports that display an area as dirty (in need of repaint).
2943 * @param left Left edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2944 * @param top Top edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2945 * @param right Right edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2946 * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2947 * @param mark_dirty_if_zoomlevel_is_below To tell if an update is relevant or not (for example, animations in map mode are not)
2950 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
)
2952 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2953 if (vp
->zoom
>= mark_dirty_if_zoomlevel_is_below
) continue;
2954 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
2958 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
)
2960 const uint size
= cit
->second
.size() > max_rank_order_type_count
? 1 : (uint
)cit
->second
.size();
2961 MarkRouteStepDirty(cit
->first
, size
);
2964 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
)
2966 assert(tile
!= INVALID_TILE
);
2967 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2968 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
2969 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2970 const int half_width
= ScaleByZoom((_vp_route_step_width
/ 2) + 1, vp
->zoom
);
2971 const int height
= ScaleByZoom(_vp_route_step_height_top
+ char_height
* order_nr
+ _vp_route_step_height_bottom
, vp
->zoom
);
2972 MarkViewportDirty(vp
, pt
.x
- half_width
, pt
.y
- height
, pt
.x
+ half_width
, pt
.y
);
2976 void MarkAllRouteStepsDirty(const Vehicle
*veh
)
2978 ViewportPrepareVehicleRouteSteps(veh
);
2979 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
2980 MarkRouteStepDirty(cit
);
2982 _vp_route_steps_last_mark_dirty
.swap(_vp_route_steps
);
2983 _vp_route_steps
.clear();
2987 * Mark all viewports in map mode that display an area as dirty (in need of repaint).
2988 * @param left Left edge of area to repaint
2989 * @param top Top edge of area to repaint
2990 * @param right Right edge of area to repaint
2991 * @param bottom Bottom edge of area to repaint
2994 void MarkAllViewportMapsDirty(int left
, int top
, int right
, int bottom
)
2997 FOR_ALL_WINDOWS_FROM_BACK(w
) {
2998 const ViewPort
*vp
= w
->viewport
;
2999 if (vp
!= nullptr && vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
3000 assert(vp
->width
!= 0);
3001 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
3006 void ConstrainAllViewportsZoom()
3009 FOR_ALL_WINDOWS_FROM_FRONT(w
) {
3010 if (w
->viewport
== nullptr) continue;
3012 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
3013 if (zoom
!= w
->viewport
->zoom
) {
3014 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
3015 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
3021 * Mark a tile given by its index dirty for repaint.
3022 * @param tile The tile to mark dirty.
3023 * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
3026 void MarkTileDirtyByTile(const TileIndex tile
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
, int bridge_level_offset
)
3028 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, TilePixelHeight(tile
));
3029 MarkAllViewportsDirty(
3030 pt
.x
- MAX_TILE_EXTENT_LEFT
,
3031 pt
.y
- MAX_TILE_EXTENT_TOP
- ZOOM_LVL_BASE
* TILE_HEIGHT
* bridge_level_offset
,
3032 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
3033 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
,
3034 mark_dirty_if_zoomlevel_is_below
);
3038 * Mark a (virtual) tile outside the map dirty for repaint.
3039 * @param x Tile X position.
3040 * @param y Tile Y position.
3043 void MarkTileDirtyByTileOutsideMap(int x
, int y
)
3045 Point pt
= RemapCoords(x
* TILE_SIZE
, y
* TILE_SIZE
, TilePixelHeightOutsideMap(x
, y
));
3046 MarkAllViewportsDirty(
3047 pt
.x
- MAX_TILE_EXTENT_LEFT
,
3048 pt
.y
, // no buildings outside of map
3049 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
3050 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
);
3053 void MarkTileLineDirty(const TileIndex from_tile
, const TileIndex to_tile
)
3055 assert(from_tile
!= INVALID_TILE
);
3056 assert(to_tile
!= INVALID_TILE
);
3058 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3059 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3061 const int block_radius
= 20;
3063 int x1
= from_pt
.x
/ block_radius
;
3064 int y1
= from_pt
.y
/ block_radius
;
3065 const int x2
= to_pt
.x
/ block_radius
;
3066 const int y2
= to_pt
.y
/ block_radius
;
3068 /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification */
3069 const int dx
= abs(x2
- x1
);
3070 const int dy
= abs(y2
- y1
);
3071 const int sx
= (x1
< x2
) ? 1 : -1;
3072 const int sy
= (y1
< y2
) ? 1 : -1;
3075 MarkAllViewportsDirty(
3076 (x1
- 1) * block_radius
,
3077 (y1
- 1) * block_radius
,
3078 (x1
+ 1) * block_radius
,
3079 (y1
+ 1) * block_radius
,
3082 if (x1
== x2
&& y1
== y2
) break;
3083 const int e2
= 2 * err
;
3095 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
)
3097 for (std::vector
<DrawnPathRouteTileLine
>::const_iterator it
= lines
.begin(); it
!= lines
.end(); ++it
) {
3098 MarkTileLineDirty(it
->from_tile
, it
->to_tile
);
3102 void MarkAllRoutePathsDirty(const Vehicle
*veh
)
3104 switch (_settings_client
.gui
.show_vehicle_route
) {
3109 ViewportMapPrepareVehicleRoute(veh
);
3113 for (const auto &iter
: _vp_route_paths
) {
3114 MarkTileLineDirty(iter
.from_tile
, iter
.to_tile
);
3117 _vp_route_paths_last_mark_dirty
.swap(_vp_route_paths
);
3118 _vp_route_paths
.clear();
3122 * Mark all tiles in a given bridge by their index dirty for repaint.
3123 * @param tile The start tile of the bridge to mark dirty.
3124 * @param tile The end tile of the bridge to mark dirty.
3127 void MarkBridgeTilesDirtyByTile(TileIndex tile_start
, TileIndex tile_end
)
3129 MarkTileDirtyByTile(tile_start
);
3130 MarkTileDirtyByTile(tile_end
);
3133 if (TileX(tile_start
) == TileX(tile_end
)) {
3135 } else if (TileY(tile_start
) == TileY(tile_end
)) {
3139 if (tile_end
< tile_start
) Swap(tile_start
, tile_end
);
3141 TileIndexDiff delta
= (direction
== AXIS_X
? TileDiffXY(1, 0) : TileDiffXY(0, 1));
3142 for (TileIndex tile
= tile_start
; tile
<= tile_end
; tile
+= delta
) {
3143 MarkTileDirtyByTile(tile
);
3147 void CheckMarkDirtyFocusedRoutePaths(const Vehicle
*veh
)
3149 const Vehicle
*focused_veh
= GetVehicleFromWindow(_focused_window
);
3150 if (focused_veh
&& veh
== focused_veh
) {
3151 MarkAllRoutePathsDirty(veh
);
3152 MarkAllRouteStepsDirty(veh
);
3157 * Marks the selected tiles as dirty.
3159 * This function marks the selected tiles as dirty for repaint
3163 static void SetSelectionTilesDirty()
3165 int x_size
= _thd
.size
.x
;
3166 int y_size
= _thd
.size
.y
;
3168 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
3169 int x_start
= _thd
.pos
.x
;
3170 int y_start
= _thd
.pos
.y
;
3172 if (_thd
.outersize
.x
!= 0 || _thd
.outersize
.y
!= 0) {
3173 x_size
+= _thd
.outersize
.x
;
3174 x_start
+= _thd
.offs
.x
;
3175 y_size
+= _thd
.outersize
.y
;
3176 y_start
+= _thd
.offs
.y
;
3179 x_size
-= TILE_SIZE
;
3180 y_size
-= TILE_SIZE
;
3182 assert(x_size
>= 0);
3183 assert(y_size
>= 0);
3185 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3186 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3188 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3189 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3191 /* make sure everything is multiple of TILE_SIZE */
3192 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
3195 * Suppose we have to mark dirty rectangle of 3x4 tiles:
3202 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
3212 int top_x
= x_end
; // coordinates of top dirty tile
3213 int top_y
= y_start
;
3214 int bot_x
= top_x
; // coordinates of bottom dirty tile
3218 /* topmost dirty point */
3219 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
3220 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxPixelZ(top_tile
));
3222 /* bottommost point */
3223 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
3224 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTilePixelZ(bottom_tile
)); // bottommost point
3226 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
3227 * tile height/slope affects only the 'y' on-screen coordinate! */
3229 int l
= top
.x
- TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of left side of the dirty rectangle
3230 int t
= top
.y
; // 'y' coordinate of top side of the dirty rectangle
3231 int r
= top
.x
+ TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of right side of the dirty rectangle
3232 int b
= bot
.y
; // 'y' coordinate of bottom side of the dirty rectangle
3234 static const int OVERLAY_WIDTH
= 4 * ZOOM_LVL_BASE
; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
3236 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
3237 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_LVL_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
);
3239 /* haven't we reached the topmost tile yet? */
3240 if (top_x
!= x_start
) {
3246 /* the way the bottom tile changes is different when we reach the bottommost tile */
3247 if (bot_y
!= y_end
) {
3252 } while (bot_x
>= top_x
);
3253 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
3254 /* a_size, b_size describe a rectangle with rotated coordinates */
3255 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
3257 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3258 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3260 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
3261 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
3262 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
3263 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
3265 if (x
< MapMaxX() && y
< MapMaxY()) {
3266 MarkTileDirtyByTile(TileXY(x
, y
));
3274 void SetSelectionRed(bool b
)
3276 _thd
.make_square_red
= b
;
3277 SetSelectionTilesDirty();
3281 * Test whether a sign is below the mouse
3282 * @param vp the clicked viewport
3283 * @param x X position of click
3284 * @param y Y position of click
3285 * @param sign the sign to check
3286 * @return true if the sign was hit
3288 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
3290 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_16X
);
3291 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
3292 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
3294 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
3295 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
3297 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
3298 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
3301 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
3303 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
3307 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) {
3308 ShowTownViewWindow(t
->index
);
3316 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
3318 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
3320 const BaseStation
*st
;
3321 FOR_ALL_BASE_STATIONS(st
) {
3322 if (!IsStationSignVisible(st
)) continue;
3324 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3325 if (Station::IsExpected(st
)) {
3326 ShowStationViewWindow(st
->index
);
3328 ShowWaypointWindow(Waypoint::From(st
));
3338 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
3340 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
3341 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
3345 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
3346 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
3347 if (si
->owner
== OWNER_DEITY
&& _game_mode
!= GM_EDITOR
) continue;
3349 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
3350 HandleClickOnSign(si
);
3359 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
3361 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
3363 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3364 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3366 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
3370 static void PlaceObject()
3375 pt
= GetTileBelowCursor();
3376 if (pt
.x
== -1) return;
3378 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
3379 pt
.x
+= TILE_SIZE
/ 2;
3380 pt
.y
+= TILE_SIZE
/ 2;
3383 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3384 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3386 w
= _thd
.GetCallbackWnd();
3387 if (w
!= nullptr) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
3390 bool HandleViewportDoubleClicked(Window
*w
, int x
, int y
)
3392 ViewPort
*vp
= w
->viewport
;
3393 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return false;
3395 switch (_settings_client
.gui
.action_when_viewport_map_is_dblclicked
) {
3396 case 0: // Do nothing
3398 case 1: // Zoom in main viewport
3399 while (vp
->zoom
!= ZOOM_LVL_VIEWPORT
)
3400 ZoomInOrOutToCursorWindow(true, w
);
3402 case 2: // Open an extra viewport
3403 ShowExtraViewPortWindowForTileUnderCursor();
3410 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
, bool double_click
)
3412 /* No click in smallmap mode except for plan making. */
3413 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !(_thd
.place_mode
== HT_POINT
&& _thd
.select_proc
== DDSP_DRAW_PLANLINE
)) return true;
3415 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
3417 if (_thd
.place_mode
& HT_VEHICLE
) {
3418 if (v
!= nullptr && VehicleClicked(v
)) return true;
3421 /* Vehicle placement mode already handled above. */
3422 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3423 if (_thd
.place_mode
& HT_POLY
) {
3424 /* In polyline mode double-clicking on a single white line, finishes current polyline.
3425 * If however the user double-clicks on a line that has a white and a blue section,
3426 * both lines (white and blue) will be constructed consecutively. */
3427 static bool stop_snap_on_double_click
= false;
3428 if (double_click
&& stop_snap_on_double_click
) {
3429 SetRailSnapMode(RSM_NO_SNAP
);
3432 stop_snap_on_double_click
= !(_thd
.drawstyle
& HT_LINE
) || (_thd
.dir2
== HT_DIR_END
);
3439 if (CheckClickOnTown(vp
, x
, y
)) return true;
3440 if (CheckClickOnStation(vp
, x
, y
)) return true;
3441 if (CheckClickOnSign(vp
, x
, y
)) return true;
3442 bool result
= CheckClickOnLandscape(vp
, x
, y
);
3445 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
3446 if (IsCompanyBuildableVehicleType(v
)) {
3448 WindowClass wc
= _thd
.GetCallbackWnd()->window_class
;
3449 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
3450 StartStopVehicle(v
, true);
3451 } else if ( wc
!= WC_CREATE_TEMPLATE
&& wc
!= WC_TEMPLATEGUI_MAIN
) {
3452 ShowVehicleViewWindow(v
);
3460 void HandleViewportToolTip(Window
*w
, int x
, int y
)
3462 const ViewportData
*vp
= w
->viewport
;
3463 if (vp
== nullptr || _game_mode
== GM_MENU
|| HasModalProgress()) return;
3465 TooltipCloseCondition close_cond
= (_settings_client
.gui
.hover_delay_ms
== 0) ? TCC_RIGHT_CLICK
: TCC_HOVER
;
3467 const BaseStation
*st
;
3468 FOR_ALL_BASE_STATIONS(st
) {
3469 if (IsStationSignVisible(st
) && CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3470 char tip
[DRAW_STRING_BUFFER
] = "";
3471 GetTileTooltipsForStation(st
->index
, tip
, lastof(tip
));
3472 GuiShowTooltips(w
, tip
, close_cond
);
3477 Point tile
= TranslateXYToTileCoord(vp
, x
, y
, false);
3478 if (IsInsideMM(tile
.x
, 0, MapMaxX() * TILE_SIZE
) && IsInsideMM(tile
.y
, 0, MapMaxY() * TILE_SIZE
)) {
3479 GuiShowTooltipsForTile(w
, TileVirtXY(tile
.x
, tile
.y
), close_cond
);
3483 DeleteWindowById(WC_TOOLTIPS
, 0); // close old tooltips window as now hovering this viewport
3486 void RebuildViewportOverlay(Window
*w
)
3488 if (w
->viewport
->overlay
!= nullptr &&
3489 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
3490 w
->viewport
->overlay
->GetCargoMask() != 0) {
3491 w
->viewport
->overlay
->RebuildCache();
3497 * Scrolls the viewport in a window to a given location.
3498 * @param x Desired x location of the map to scroll to (world coordinate).
3499 * @param y Desired y location of the map to scroll to (world coordinate).
3500 * @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.
3501 * @param w %Window containing the viewport.
3502 * @param instant Jump to the location instead of slowly moving to it.
3503 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3505 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
3507 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
3509 if ( x
>= 0 && x
<= (int)MapSizeX() * (int)TILE_SIZE
- 1
3510 && y
>= 0 && y
<= (int)MapSizeY() * (int)TILE_SIZE
- 1) {
3511 z
= GetSlopePixelZ(x
, y
);
3513 z
= TileHeightOutsideMap(x
/ (int)TILE_SIZE
, y
/ (int)TILE_SIZE
);
3517 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
3518 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
3520 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
3523 w
->viewport
->scrollpos_x
= pt
.x
;
3524 w
->viewport
->scrollpos_y
= pt
.y
;
3525 RebuildViewportOverlay(w
);
3528 w
->viewport
->dest_scrollpos_x
= pt
.x
;
3529 w
->viewport
->dest_scrollpos_y
= pt
.y
;
3534 * Scrolls the viewport in a window to a given location.
3535 * @param tile Desired tile to center on.
3536 * @param w %Window containing the viewport.
3537 * @param instant Jump to the location instead of slowly moving to it.
3538 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3540 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
3542 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
3546 * Scrolls the viewport of the main window to a given location.
3547 * @param tile Desired tile to center on.
3548 * @param instant Jump to the location instead of slowly moving to it.
3549 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3551 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
3553 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
3557 * Set a tile to display a red error square.
3558 * @param tile Tile that should show the red error square.
3560 void SetRedErrorSquare(TileIndex tile
)
3568 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
3569 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
, ZOOM_LVL_DRAW_MAP
);
3574 * Highlight \a w by \a h tiles at the cursor.
3575 * @param w Width of the highlighted tiles rectangle.
3576 * @param h Height of the highlighted tiles rectangle.
3578 void SetTileSelectSize(int w
, int h
)
3580 _thd
.new_size
.x
= w
* TILE_SIZE
;
3581 _thd
.new_size
.y
= h
* TILE_SIZE
;
3582 _thd
.new_outersize
.x
= 0;
3583 _thd
.new_outersize
.y
= 0;
3586 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
3588 _thd
.new_offs
.x
= ox
* TILE_SIZE
;
3589 _thd
.new_offs
.y
= oy
* TILE_SIZE
;
3590 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
3591 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
3594 /** returns the best autorail highlight type from map coordinates */
3595 static HighLightStyle
GetAutorailHT(int x
, int y
)
3597 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
3601 * Reset tile highlighting.
3603 void TileHighlightData::Reset()
3607 this->new_pos
.x
= 0;
3608 this->new_pos
.y
= 0;
3612 * Is the user dragging a 'diagonal rectangle'?
3613 * @return User is dragging a rotated rectangle.
3615 bool TileHighlightData::IsDraggingDiagonal()
3617 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
3621 * Get the window that started the current highlighting.
3622 * @return The window that requested the current tile highlighting, or \c nullptr if not available.
3624 Window
*TileHighlightData::GetCallbackWnd()
3626 return FindWindowById(this->window_class
, this->window_number
);
3629 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
);
3631 static inline void CalcNewPolylineOutersize()
3633 /* use the 'outersize' to mark the second (blue) part of a polyline selection */
3634 if (_thd
.dir2
< HT_DIR_END
) {
3635 /* get bounds of the second part */
3636 int outer_x1
= _thd
.selstart2
.x
& ~TILE_UNIT_MASK
;
3637 int outer_y1
= _thd
.selstart2
.y
& ~TILE_UNIT_MASK
;
3638 int outer_x2
= _thd
.selend2
.x
& ~TILE_UNIT_MASK
;
3639 int outer_y2
= _thd
.selend2
.y
& ~TILE_UNIT_MASK
;
3640 if (outer_x1
> outer_x2
) Swap(outer_x1
, outer_x2
);
3641 if (outer_y1
> outer_y2
) Swap(outer_y1
, outer_y2
);
3642 /* include the first part */
3643 outer_x1
= min
<int>(outer_x1
, _thd
.new_pos
.x
);
3644 outer_y1
= min
<int>(outer_y1
, _thd
.new_pos
.y
);
3645 outer_x2
= max
<int>(outer_x2
, _thd
.new_pos
.x
+ _thd
.new_size
.x
- TILE_SIZE
);
3646 outer_y2
= max
<int>(outer_y2
, _thd
.new_pos
.y
+ _thd
.new_size
.y
- TILE_SIZE
);
3647 /* write new values */
3648 _thd
.new_offs
.x
= outer_x1
- _thd
.new_pos
.x
;
3649 _thd
.new_offs
.y
= outer_y1
- _thd
.new_pos
.y
;
3650 _thd
.new_outersize
.x
= outer_x2
- outer_x1
+ TILE_SIZE
- _thd
.new_size
.x
;
3651 _thd
.new_outersize
.y
= outer_y2
- outer_y1
+ TILE_SIZE
- _thd
.new_size
.y
;
3653 _thd
.new_offs
.x
= 0;
3654 _thd
.new_offs
.y
= 0;
3655 _thd
.new_outersize
.x
= 0;
3656 _thd
.new_outersize
.y
= 0;
3661 * Updates tile highlighting for all cases.
3662 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
3663 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
3664 * Once for the old and once for the new selection.
3665 * _thd is TileHighlightData, found in viewport.h
3667 void UpdateTileSelection()
3672 HighLightStyle new_drawstyle
= HT_NONE
;
3673 bool new_diagonal
= false;
3675 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
3679 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3680 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3681 x1
&= ~TILE_UNIT_MASK
;
3682 y1
&= ~TILE_UNIT_MASK
;
3684 if (_thd
.IsDraggingDiagonal()) {
3685 new_diagonal
= true;
3687 if (x1
>= x2
) Swap(x1
, x2
);
3688 if (y1
>= y2
) Swap(y1
, y2
);
3690 _thd
.new_pos
.x
= x1
;
3691 _thd
.new_pos
.y
= y1
;
3692 _thd
.new_size
.x
= x2
- x1
;
3693 _thd
.new_size
.y
= y2
- y1
;
3694 if (!new_diagonal
) {
3695 _thd
.new_size
.x
+= TILE_SIZE
;
3696 _thd
.new_size
.y
+= TILE_SIZE
;
3698 new_drawstyle
= _thd
.next_drawstyle
;
3700 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3701 Point pt
= GetTileBelowCursor();
3705 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
3707 new_drawstyle
= HT_RECT
;
3710 new_drawstyle
= HT_POINT
;
3711 x1
+= TILE_SIZE
/ 2;
3712 y1
+= TILE_SIZE
/ 2;
3717 if (_thd
.place_mode
& HT_POLY
) {
3718 RailSnapMode snap_mode
= GetRailSnapMode();
3719 if (snap_mode
== RSM_NO_SNAP
||
3720 (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
))) {
3721 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3722 _thd
.new_offs
.x
= 0;
3723 _thd
.new_offs
.y
= 0;
3724 _thd
.new_outersize
.x
= 0;
3725 _thd
.new_outersize
.y
= 0;
3726 _thd
.dir2
= HT_DIR_END
;
3728 new_drawstyle
= CalcPolyrailDrawstyle(pt
, false);
3729 if (new_drawstyle
!= HT_NONE
) {
3730 x1
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3731 y1
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3732 int x2
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
3733 int y2
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
3734 if (x1
> x2
) Swap(x1
, x2
);
3735 if (y1
> y2
) Swap(y1
, y2
);
3736 _thd
.new_pos
.x
= x1
;
3737 _thd
.new_pos
.y
= y1
;
3738 _thd
.new_size
.x
= x2
- x1
+ TILE_SIZE
;
3739 _thd
.new_size
.y
= y2
- y1
+ TILE_SIZE
;
3745 if (_thd
.place_mode
& HT_RAIL
) {
3746 /* Draw one highlighted tile in any direction */
3747 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3751 switch (_thd
.place_mode
& HT_DIR_MASK
) {
3752 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
3753 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
3757 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
3762 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
3765 default: NOT_REACHED();
3767 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
3768 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
3776 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
3777 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
3781 if (new_drawstyle
& HT_LINE
) CalcNewPolylineOutersize();
3783 /* redraw selection */
3784 if (_thd
.drawstyle
!= new_drawstyle
||
3785 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
3786 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
3787 _thd
.offs
.x
!= _thd
.new_offs
.x
|| _thd
.offs
.y
!= _thd
.new_offs
.y
||
3788 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
3789 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
3790 _thd
.diagonal
!= new_diagonal
) {
3791 /* Clear the old tile selection? */
3792 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3794 _thd
.drawstyle
= new_drawstyle
;
3795 _thd
.pos
= _thd
.new_pos
;
3796 _thd
.size
= _thd
.new_size
;
3797 _thd
.offs
= _thd
.new_offs
;
3798 _thd
.outersize
= _thd
.new_outersize
;
3799 _thd
.diagonal
= new_diagonal
;
3802 /* Draw the new tile selection? */
3803 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3808 * Displays the measurement tooltips when selecting multiple tiles
3809 * @param str String to be displayed
3810 * @param close_cond Condition for closing this tooltip.
3812 static inline void ShowMeasurementTooltips(StringID str
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
3814 if (!_settings_client
.gui
.measure_tooltip
) return;
3815 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, close_cond
);
3818 /** highlighting tiles while only going over them with the mouse */
3819 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
3821 _thd
.select_method
= method
;
3822 _thd
.select_proc
= process
;
3823 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
3824 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
3825 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
3826 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
3828 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
3829 * In effect, placement starts from the centre of a tile
3831 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
3832 _thd
.selend
.x
+= TILE_SIZE
/ 2;
3833 _thd
.selend
.y
+= TILE_SIZE
/ 2;
3834 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
3835 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
3838 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
3839 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
3840 _thd
.place_mode
= HT_SPECIAL
| others
;
3841 _thd
.next_drawstyle
= HT_RECT
| others
;
3842 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
3843 _thd
.place_mode
= HT_SPECIAL
| others
;
3844 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
3845 _current_snap_lock
.x
= -1;
3846 if ((_thd
.place_mode
& HT_POLY
) != 0 && GetRailSnapMode() == RSM_NO_SNAP
) {
3847 SetRailSnapMode(RSM_SNAP_TO_TILE
);
3848 SetRailSnapTile(tile
);
3851 _thd
.place_mode
= HT_SPECIAL
| others
;
3852 _thd
.next_drawstyle
= HT_POINT
| others
;
3854 _special_mouse_mode
= WSM_SIZING
;
3857 void VpSetPlaceSizingLimit(int limit
)
3859 _thd
.sizelimit
= limit
;
3863 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
3864 * @param from TileIndex of the first tile to highlight
3865 * @param to TileIndex of the last tile to highlight
3867 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
3869 uint64 distance
= DistanceManhattan(from
, to
) + 1;
3871 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
3872 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
3873 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
3874 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
3875 _thd
.next_drawstyle
= HT_RECT
;
3877 /* show measurement only if there is any length to speak of */
3879 SetDParam(0, distance
);
3880 ShowMeasurementTooltips(STR_MEASURE_LENGTH
, TCC_HOVER
);
3884 static void VpStartPreSizing()
3887 _special_mouse_mode
= WSM_PRESIZE
;
3891 * returns information about the 2x1 piece to be build.
3892 * The lower bits (0-3) are the track type.
3894 static HighLightStyle
Check2x1AutoRail(int mode
)
3896 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
3897 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
3898 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
3899 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
3902 default: NOT_REACHED();
3903 case 0: // end piece is lower right
3904 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3905 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3909 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3910 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3914 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3915 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3919 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3920 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3926 * Check if the direction of start and end tile should be swapped based on
3927 * the dragging-style. Default directions are:
3928 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
3929 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
3930 * For example dragging a rectangle area from south to north should be swapped to
3931 * north-south (DIR_S) to obtain the same results with less code. This is what
3932 * the return value signifies.
3933 * @param style HighLightStyle dragging style
3934 * @param start_tile start tile of drag
3935 * @param end_tile end tile of drag
3936 * @return boolean value which when true means start/end should be swapped
3938 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
3940 uint start_x
= TileX(start_tile
);
3941 uint start_y
= TileY(start_tile
);
3942 uint end_x
= TileX(end_tile
);
3943 uint end_y
= TileY(end_tile
);
3945 switch (style
& HT_DRAG_MASK
) {
3947 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
3950 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
3951 default: NOT_REACHED();
3958 * Calculates height difference between one tile and another.
3959 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
3961 * To correctly get the height difference we need the direction we are dragging
3962 * in, as well as with what kind of tool we are dragging. For example a horizontal
3963 * autorail tool that starts in bottom and ends at the top of a tile will need the
3964 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
3965 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
3966 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
3967 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
3968 * @param start_tile Start tile of the drag operation.
3969 * @param end_tile End tile of the drag operation.
3970 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
3972 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
3974 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
3975 uint h0
, h1
; // Start height and end height.
3977 if (start_tile
== end_tile
) return 0;
3978 if (swap
) Swap(start_tile
, end_tile
);
3980 switch (style
& HT_DRAG_MASK
) {
3982 static const TileIndexDiffC heightdiff_area_by_dir
[] = {
3983 /* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
3984 /* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
3987 /* In the case of an area we can determine whether we were dragging south or
3988 * east by checking the X-coordinates of the tiles */
3989 byte style_t
= (byte
)(TileX(end_tile
) > TileX(start_tile
));
3990 start_tile
= TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[style_t
]));
3991 end_tile
= TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[2 + style_t
]));
3996 h0
= TileHeight(start_tile
);
3997 h1
= TileHeight(end_tile
);
3999 default: { // All other types, this is mostly only line/autorail
4000 static const HighLightStyle flip_style_direction
[] = {
4001 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
4003 static const TileIndexDiffC heightdiff_line_by_dir
[] = {
4004 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
4005 /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
4006 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
4008 /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X */ {1, 0}, {0, 0}, // HT_DIR_Y
4009 /* End */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
4010 /* End */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
4013 distance
%= 2; // we're only interested if the distance is even or uneven
4014 style
&= HT_DIR_MASK
;
4016 /* To handle autorail, we do some magic to be able to use a lookup table.
4017 * Firstly if we drag the other way around, we switch start&end, and if needed
4018 * also flip the drag-position. Eg if it was on the left, and the distance is even
4019 * that means the end, which is now the start is on the right */
4020 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
4022 /* Use lookup table for start-tile based on HighLightStyle direction */
4023 byte style_t
= style
* 2;
4024 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
4025 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
])));
4026 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
+ 1])));
4029 /* Use lookup table for end-tile based on HighLightStyle direction
4030 * flip around side (lower/upper, left/right) based on distance */
4031 if (distance
== 0) style_t
= flip_style_direction
[style
] * 2;
4032 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
4033 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
])));
4034 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
+ 1])));
4040 if (swap
) Swap(h0
, h1
);
4041 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
4044 static void ShowLengthMeasurement(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
, bool show_single_tile_length
= false)
4046 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
4048 if (_settings_client
.gui
.measure_tooltip
) {
4049 uint distance
= DistanceManhattan(start_tile
, end_tile
) + 1;
4052 if (show_single_tile_length
|| distance
!= 1) {
4053 int heightdiff
= CalcHeightdiff(style
, distance
, start_tile
, end_tile
);
4054 /* If we are showing a tooltip for horizontal or vertical drags,
4055 * 2 tiles have a length of 1. To bias towards the ceiling we add
4056 * one before division. It feels more natural to count 3 lengths as 2 */
4057 if ((style
& HT_DIR_MASK
) != HT_DIR_X
&& (style
& HT_DIR_MASK
) != HT_DIR_Y
) {
4058 distance
= CeilDiv(distance
, 2);
4061 SetDParam(index
++, distance
);
4062 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4065 ShowMeasurementTooltips(measure_strings_length
[index
]);
4070 * Check for underflowing the map.
4071 * @param test the variable to test for underflowing
4072 * @param other the other variable to update to keep the line
4073 * @param mult the constant to multiply the difference by for \c other
4075 static void CheckUnderflow(int &test
, int &other
, int mult
)
4077 if (test
>= 0) return;
4079 other
+= mult
* test
;
4084 * Check for overflowing the map.
4085 * @param test the variable to test for overflowing
4086 * @param other the other variable to update to keep the line
4087 * @param max the maximum value for the \c test variable
4088 * @param mult the constant to multiply the difference by for \c other
4090 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
4092 if (test
<= max
) return;
4094 other
+= mult
* (test
- max
);
4098 static const uint X_DIRS
= (1 << DIR_NE
) | (1 << DIR_SW
);
4099 static const uint Y_DIRS
= (1 << DIR_SE
) | (1 << DIR_NW
);
4100 static const uint HORZ_DIRS
= (1 << DIR_W
) | (1 << DIR_E
);
4101 static const uint VERT_DIRS
= (1 << DIR_N
) | (1 << DIR_S
);
4103 Trackdir
PointDirToTrackdir(const Point
&pt
, Direction dir
)
4107 if (IsDiagonalDirection(dir
)) {
4108 ret
= DiagDirToDiagTrackdir(DirToDiagDir(dir
));
4110 int x
= pt
.x
& TILE_UNIT_MASK
;
4111 int y
= pt
.y
& TILE_UNIT_MASK
;
4114 if (HasBit(HORZ_DIRS
, dir
)) {
4115 ret
= TrackDirectionToTrackdir(ns
< (int)TILE_SIZE
? TRACK_UPPER
: TRACK_LOWER
, dir
);
4117 ret
= TrackDirectionToTrackdir(we
< 0 ? TRACK_LEFT
: TRACK_RIGHT
, dir
);
4124 static bool FindPolyline(const Point
&pt
, const LineSnapPoint
&start
, Polyline
*ret
)
4126 /* relative coordinats of the mouse point (offset against the snap point) */
4127 int x
= pt
.x
- start
.x
;
4128 int y
= pt
.y
- start
.y
;
4132 /* in-tile alignment of the snap point (there are two variants: [0, 8] or [8, 0]) */
4133 uint align_x
= start
.x
& TILE_UNIT_MASK
;
4134 uint align_y
= start
.y
& TILE_UNIT_MASK
;
4135 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
)));
4137 /* absolute distance between points (in tiles) */
4138 uint d_x
= abs(RoundDivSU(x
< 0 ? x
- align_y
: x
+ align_y
, TILE_SIZE
));
4139 uint d_y
= abs(RoundDivSU(y
< 0 ? y
- align_x
: y
+ align_x
, TILE_SIZE
));
4140 uint d_ns
= abs(RoundDivSU(ns
, TILE_SIZE
));
4141 uint d_we
= abs(RoundDivSU(we
, TILE_SIZE
));
4143 /* Find on which quadrant is the mouse point (reltively to the snap point).
4144 * Numeration (clockwise like in Direction):
4151 uint ortho_quadrant
= 2 * (x
< 0) + ((x
< 0) != (y
< 0)); // implicit cast: false/true --> 0/1
4152 uint diag_quadrant
= 2 * (ns
< 0) + ((ns
< 0) != (we
< 0));
4154 /* direction from the snap point to the mouse point */
4155 Direction ortho_line_dir
= ChangeDir(DIR_S
, (DirDiff
)(2 * ortho_quadrant
)); // DIR_S is the middle of the ortho quadrant no. 0
4156 Direction diag_line_dir
= ChangeDir(DIR_SE
, (DirDiff
)(2 * diag_quadrant
)); // DIR_SE is the middle of the diag quadrant no. 0
4157 if (!HasBit(start
.dirs
, ortho_line_dir
) && !HasBit(start
.dirs
, diag_line_dir
)) return false;
4159 /* length of booth segments of auto line (choosing orthogonal direction first) */
4160 uint ortho_len
= 0, ortho_len2
= 0;
4161 if (HasBit(start
.dirs
, ortho_line_dir
)) {
4162 bool is_len_even
= (align_x
!= 0) ? d_x
>= d_y
: d_x
<= d_y
;
4163 ortho_len
= 2 * min(d_x
, d_y
) - (int)is_len_even
;
4164 assert((int)ortho_len
>= 0);
4165 if (d_ns
== 0 || d_we
== 0) { // just single segment?
4168 ortho_len2
= abs((int)d_x
- (int)d_y
) + (int)is_len_even
;
4172 /* length of booth segments of auto line (choosing diagonal direction first) */
4173 uint diag_len
= 0, diag_len2
= 0;
4174 if (HasBit(start
.dirs
, diag_line_dir
)) {
4175 if (d_x
== 0 || d_y
== 0) { // just single segment?
4176 diag_len
= d_x
+ d_y
;
4178 diag_len
= min(d_ns
, d_we
);
4179 diag_len2
= d_x
+ d_y
- diag_len
;
4183 /* choose the best variant */
4184 if (ortho_len
!= 0 && diag_len
!= 0) {
4185 /* in the first place, choose this line whose first segment ends up closer
4186 * to the mouse point (thus the second segment is shorter) */
4187 int cmp
= ortho_len2
- diag_len2
;
4188 /* if equeal, choose the shorter line */
4189 if (cmp
== 0) cmp
= ortho_len
- diag_len
;
4190 /* finally look at small "units" and choose the line which is closer to the mouse point */
4191 if (cmp
== 0) cmp
= min(abs(we
), abs(ns
)) - min(abs(x
), abs(y
));
4192 /* based on comparison, disable one of variants */
4201 if (ortho_len
!= 0) {
4202 ret
->first_dir
= ortho_line_dir
;
4203 ret
->first_len
= ortho_len
;
4204 ret
->second_dir
= (ortho_len2
!= 0) ? diag_line_dir
: INVALID_DIR
;
4205 ret
->second_len
= ortho_len2
;
4206 } else if (diag_len
!= 0) {
4207 ret
->first_dir
= diag_line_dir
;
4208 ret
->first_len
= diag_len
;
4209 ret
->second_dir
= (diag_len2
!= 0) ? ortho_line_dir
: INVALID_DIR
;
4210 ret
->second_len
= diag_len2
;
4220 * Calculate squared euclidean distance between two points.
4221 * @param a the first point
4222 * @param b the second point
4223 * @return |b - a| ^ 2
4225 static inline uint
SqrDist(const Point
&a
, const Point
&b
)
4227 return (b
.x
- a
.x
) * (b
.x
- a
.x
) + (b
.y
- a
.y
) * (b
.y
- a
.y
);
4230 static LineSnapPoint
*FindBestPolyline(const Point
&pt
, LineSnapPoint
*snap_points
, uint num_points
, Polyline
*ret
)
4232 /* Find the best polyline (a pair of two lines - the white one and the blue
4233 * one) led from any of saved snap points to the mouse cursor. */
4235 LineSnapPoint
*best_snap_point
= nullptr; // the best polyline we found so far is led from this snap point
4237 for (int i
= 0; i
< (int)num_points
; i
++) {
4238 /* try to fit a polyline */
4240 if (!FindPolyline(pt
, snap_points
[i
], &polyline
)) continue; // skip non-matching snap points
4241 /* check whether we've found a better polyline */
4242 if (best_snap_point
!= nullptr) {
4243 /* firstly choose shorter polyline (the one with smaller amount of
4244 * track pieces composing booth the white and the blue line) */
4245 uint cur_len
= polyline
.first_len
+ polyline
.second_len
;
4246 uint best_len
= ret
->first_len
+ ret
->second_len
;
4247 if (cur_len
> best_len
) continue;
4248 /* secondly choose that polyline which has longer first (white) line */
4249 if (cur_len
== best_len
&& polyline
.first_len
< ret
->first_len
) continue;
4250 /* finally check euclidean distance to snap points and choose the
4251 * one which is closer */
4252 if (cur_len
== best_len
&& polyline
.first_len
== ret
->first_len
&& SqrDist(pt
, snap_points
[i
]) >= SqrDist(pt
, *best_snap_point
)) continue;
4254 /* save the found polyline */
4256 best_snap_point
= &snap_points
[i
];
4259 return best_snap_point
;
4262 /** while dragging */
4263 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
4267 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
4268 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
4269 uint w
= abs(dx
) + TILE_SIZE
;
4270 uint h
= abs(dy
) + TILE_SIZE
;
4272 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4273 /* We 'force' a selection direction; first four rail buttons. */
4274 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
4275 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
4276 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
4279 b
= HT_LINE
| HT_DIR_Y
;
4280 x
= _thd
.selstart
.x
;
4284 b
= HT_LINE
| HT_DIR_X
;
4285 y
= _thd
.selstart
.y
;
4288 case VPM_FIX_HORIZONTAL
:
4290 /* We are on a straight horizontal line. Determine the 'rail'
4291 * to build based the sub tile location. */
4292 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4294 /* We are not on a straight line. Determine the rail to build
4295 * based on whether we are above or below it. */
4296 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
4298 /* Calculate where a horizontal line through the start point and
4299 * a vertical line from the selected end point intersect and
4300 * use that point as the end point. */
4301 int offset
= (raw_dx
- raw_dy
) / 2;
4302 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4303 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
4305 /* 'Build' the last half rail tile if needed */
4306 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4307 if (dx
+ dy
>= (int)TILE_SIZE
) {
4308 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4310 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4314 /* Make sure we do not overflow the map! */
4315 CheckUnderflow(x
, y
, 1);
4316 CheckUnderflow(y
, x
, 1);
4317 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
4318 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
4319 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4323 case VPM_FIX_VERTICAL
:
4325 /* We are on a straight vertical line. Determine the 'rail'
4326 * to build based the sub tile location. */
4327 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4329 /* We are not on a straight line. Determine the rail to build
4330 * based on whether we are left or right from it. */
4331 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4333 /* Calculate where a vertical line through the start point and
4334 * a horizontal line from the selected end point intersect and
4335 * use that point as the end point. */
4336 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
4337 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4338 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
4340 /* 'Build' the last half rail tile if needed */
4341 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4343 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4345 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4349 /* Make sure we do not overflow the map! */
4350 CheckUnderflow(x
, y
, -1);
4351 CheckUnderflow(y
, x
, -1);
4352 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
4353 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
4354 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4361 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
4362 if (method
& VPM_RAILDIRS
) {
4363 b
= GetAutorailHT(x
, y
);
4364 } else { // rect for autosignals on one tile
4367 } else if (h
== TILE_SIZE
) { // Is this in X direction?
4368 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
4369 b
= (Check2x1AutoRail(3)) | HT_LINE
;
4370 } else if (dx
== -(int)TILE_SIZE
) {
4371 b
= (Check2x1AutoRail(2)) | HT_LINE
;
4373 b
= HT_LINE
| HT_DIR_X
;
4375 y
= _thd
.selstart
.y
;
4376 } else if (w
== TILE_SIZE
) { // Or Y direction?
4377 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
4378 b
= (Check2x1AutoRail(1)) | HT_LINE
;
4379 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
4380 b
= (Check2x1AutoRail(0)) | HT_LINE
;
4382 b
= HT_LINE
| HT_DIR_Y
;
4384 x
= _thd
.selstart
.x
;
4385 } else if (w
> h
* 2) { // still count as x dir?
4386 b
= HT_LINE
| HT_DIR_X
;
4387 y
= _thd
.selstart
.y
;
4388 } else if (h
> w
* 2) { // still count as y dir?
4389 b
= HT_LINE
| HT_DIR_Y
;
4390 x
= _thd
.selstart
.x
;
4391 } else { // complicated direction
4393 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
4394 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
4397 if (x
> _thd
.selstart
.x
) {
4398 if (y
> _thd
.selstart
.y
) {
4401 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4402 } else if (d
>= 0) {
4403 x
= _thd
.selstart
.x
+ h
;
4404 b
= HT_LINE
| HT_DIR_VL
;
4406 y
= _thd
.selstart
.y
+ w
;
4407 b
= HT_LINE
| HT_DIR_VR
;
4412 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4413 } else if (d
>= 0) {
4414 x
= _thd
.selstart
.x
+ h
;
4415 b
= HT_LINE
| HT_DIR_HL
;
4417 y
= _thd
.selstart
.y
- w
;
4418 b
= HT_LINE
| HT_DIR_HU
;
4422 if (y
> _thd
.selstart
.y
) {
4425 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4426 } else if (d
>= 0) {
4427 x
= _thd
.selstart
.x
- h
;
4428 b
= HT_LINE
| HT_DIR_HU
;
4430 y
= _thd
.selstart
.y
+ w
;
4431 b
= HT_LINE
| HT_DIR_HL
;
4436 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4437 } else if (d
>= 0) {
4438 x
= _thd
.selstart
.x
- h
;
4439 b
= HT_LINE
| HT_DIR_VR
;
4441 y
= _thd
.selstart
.y
- w
;
4442 b
= HT_LINE
| HT_DIR_VL
;
4450 _thd
.dir2
= HT_DIR_END
;
4451 _thd
.next_drawstyle
= b
;
4453 ShowLengthMeasurement(b
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4456 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
)
4458 RailSnapMode snap_mode
= GetRailSnapMode();
4460 /* are we only within one tile? */
4461 if (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
)) {
4462 _thd
.selend
.x
= pt
.x
;
4463 _thd
.selend
.y
= pt
.y
;
4464 return GetAutorailHT(pt
.x
, pt
.y
);
4467 /* find the best track */
4470 bool lock_snapping
= dragging
&& snap_mode
== RSM_SNAP_TO_RAIL
;
4471 if (!lock_snapping
) _current_snap_lock
.x
= -1;
4473 const LineSnapPoint
*snap_point
;
4474 if (_current_snap_lock
.x
!= -1) {
4475 snap_point
= FindBestPolyline(pt
, &_current_snap_lock
, 1, &line
);
4476 } else if (snap_mode
== RSM_SNAP_TO_TILE
) {
4477 snap_point
= FindBestPolyline(pt
, _tile_snap_points
.Begin(), _tile_snap_points
.Length(), &line
);
4479 assert(snap_mode
== RSM_SNAP_TO_RAIL
);
4480 snap_point
= FindBestPolyline(pt
, _rail_snap_points
.Begin(), _rail_snap_points
.Length(), &line
);
4483 if (snap_point
== nullptr) return HT_NONE
; // no match
4485 if (lock_snapping
&& _current_snap_lock
.x
== -1) {
4486 /* lock down the snap point */
4487 _current_snap_lock
= *snap_point
;
4488 _current_snap_lock
.dirs
&= (1 << line
.first_dir
) | (1 << ReverseDir(line
.first_dir
));
4491 TileIndexDiffC first_dir
= TileIndexDiffCByDir(line
.first_dir
);
4492 _thd
.selstart
.x
= line
.start
.x
;
4493 _thd
.selstart
.y
= line
.start
.y
;
4494 _thd
.selend
.x
= _thd
.selstart
.x
+ line
.first_len
* first_dir
.x
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4495 _thd
.selend
.y
= _thd
.selstart
.y
+ line
.first_len
* first_dir
.y
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4496 _thd
.selstart2
.x
= _thd
.selend
.x
;
4497 _thd
.selstart2
.y
= _thd
.selend
.y
;
4498 _thd
.selstart
.x
+= first_dir
.x
;
4499 _thd
.selstart
.y
+= first_dir
.y
;
4500 _thd
.selend
.x
-= first_dir
.x
;
4501 _thd
.selend
.y
-= first_dir
.y
;
4502 Trackdir seldir
= PointDirToTrackdir(_thd
.selstart
, line
.first_dir
);
4503 _thd
.selstart
.x
&= ~TILE_UNIT_MASK
;
4504 _thd
.selstart
.y
&= ~TILE_UNIT_MASK
;
4506 if (line
.second_len
!= 0) {
4507 TileIndexDiffC second_dir
= TileIndexDiffCByDir(line
.second_dir
);
4508 _thd
.selend2
.x
= _thd
.selstart2
.x
+ line
.second_len
* second_dir
.x
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4509 _thd
.selend2
.y
= _thd
.selstart2
.y
+ line
.second_len
* second_dir
.y
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4510 _thd
.selstart2
.x
+= second_dir
.x
;
4511 _thd
.selstart2
.y
+= second_dir
.y
;
4512 _thd
.selend2
.x
-= second_dir
.x
;
4513 _thd
.selend2
.y
-= second_dir
.y
;
4514 Trackdir seldir2
= PointDirToTrackdir(_thd
.selstart2
, line
.second_dir
);
4515 _thd
.selstart2
.x
&= ~TILE_UNIT_MASK
;
4516 _thd
.selstart2
.y
&= ~TILE_UNIT_MASK
;
4517 _thd
.dir2
= (HighLightStyle
)TrackdirToTrack(seldir2
);
4519 _thd
.dir2
= HT_DIR_END
;
4522 HighLightStyle ret
= HT_LINE
| (HighLightStyle
)TrackdirToTrack(seldir
);
4523 ShowLengthMeasurement(ret
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
), TCC_HOVER
, true);
4528 * Selects tiles while dragging
4529 * @param x X coordinate of end of selection
4530 * @param y Y coordinate of end of selection
4531 * @param method modifies the way tiles are selected. Possible
4532 * methods are VPM_* in viewport.h
4534 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
4537 HighLightStyle style
;
4544 if ((_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() != RSM_NO_SNAP
) {
4545 Point pt
= { x
, y
};
4546 _thd
.next_drawstyle
= CalcPolyrailDrawstyle(pt
, true);
4550 /* Special handling of drag in any (8-way) direction */
4551 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4554 CalcRaildirsDrawstyle(x
, y
, method
);
4558 /* Needed so level-land is placed correctly */
4559 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
4564 sx
= _thd
.selstart
.x
;
4565 sy
= _thd
.selstart
.y
;
4570 case VPM_X_OR_Y
: // drag in X or Y direction
4571 if (abs(sy
- y
) < abs(sx
- x
)) {
4578 goto calc_heightdiff_single_direction
;
4580 case VPM_X_LIMITED
: // Drag in X direction (limited size).
4581 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4584 case VPM_FIX_X
: // drag in Y direction
4587 goto calc_heightdiff_single_direction
;
4589 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
4590 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4593 case VPM_FIX_Y
: // drag in X direction
4597 calc_heightdiff_single_direction
:;
4599 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4600 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4602 /* With current code passing a HT_LINE style to calculate the height
4603 * difference is enough. However if/when a point-tool is created
4604 * with this method, function should be called with new_style (below)
4605 * instead of HT_LINE | style case HT_POINT is handled specially
4606 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
4607 ShowLengthMeasurement(HT_LINE
| style
, TileVirtXY(sx
, sy
), TileVirtXY(x
, y
));
4610 case VPM_A_B_LINE
: { // drag an A to B line
4611 TileIndex t0
= TileVirtXY(sx
, sy
);
4612 TileIndex t1
= TileVirtXY(x
, y
);
4613 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4614 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4617 /* If dragging an area (eg dynamite tool) and it is actually a single
4618 * row/column, change the type to 'line' to get proper calculation for height */
4619 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4620 if (style
& HT_RECT
) {
4622 style
= HT_LINE
| HT_DIR_Y
;
4623 } else if (dy
== 1) {
4624 style
= HT_LINE
| HT_DIR_X
;
4630 if (dx
!= 1 || dy
!= 1) {
4631 heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4632 SetDParam(index
++, DistanceManhattan(t0
, t1
));
4633 SetDParam(index
++, sqrtl(dx
* dx
+ dy
* dy
)); //DistanceSquare does not like big numbers
4639 SetDParam(index
++, DistanceFromEdge(t1
));
4640 SetDParam(index
++, GetTileMaxZ(t1
) * TILE_HEIGHT_STEP
);
4641 SetDParam(index
++, heightdiff
);
4642 //Show always the measurement tooltip
4643 GuiShowTooltips(_thd
.GetCallbackWnd(),STR_MEASURE_DIST_HEIGHTDIFF
);
4647 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
4648 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4649 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4650 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4653 case VPM_X_AND_Y
: // drag an X by Y area
4654 if (_settings_client
.gui
.measure_tooltip
) {
4655 static const StringID measure_strings_area
[] = {
4656 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
4659 TileIndex t0
= TileVirtXY(sx
, sy
);
4660 TileIndex t1
= TileVirtXY(x
, y
);
4661 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4662 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4665 /* If dragging an area (eg dynamite tool) and it is actually a single
4666 * row/column, change the type to 'line' to get proper calculation for height */
4667 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4668 if (_thd
.IsDraggingDiagonal()) {
4669 /* Determine the "area" of the diagonal dragged selection.
4670 * We assume the area is the number of tiles along the X
4671 * edge and the number of tiles along the Y edge. However,
4672 * multiplying these two numbers does not give the exact
4673 * number of tiles; basically we are counting the black
4674 * squares on a chess board and ignore the white ones to
4675 * make the tile counts at the edges match up. There is no
4676 * other way to make a proper count though.
4678 * First convert to the rotated coordinate system. */
4679 int dist_x
= TileX(t0
) - TileX(t1
);
4680 int dist_y
= TileY(t0
) - TileY(t1
);
4681 int a_max
= dist_x
+ dist_y
;
4682 int b_max
= dist_y
- dist_x
;
4684 /* Now determine the size along the edge, but due to the
4685 * chess board principle this counts double. */
4686 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
4687 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
4689 /* We get a 1x1 on normal 2x1 rectangles, due to it being
4690 * a seen as two sides. As the result for actual building
4691 * will be the same as non-diagonal dragging revert to that
4692 * behaviour to give it a more normally looking size. */
4693 if (a_max
!= 1 || b_max
!= 1) {
4697 } else if (style
& HT_RECT
) {
4699 style
= HT_LINE
| HT_DIR_Y
;
4700 } else if (dy
== 1) {
4701 style
= HT_LINE
| HT_DIR_X
;
4705 if (dx
!= 1 || dy
!= 1) {
4706 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4708 SetDParam(index
++, dx
- (style
& HT_POINT
? 1 : 0));
4709 SetDParam(index
++, dy
- (style
& HT_POINT
? 1 : 0));
4710 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4713 ShowMeasurementTooltips(measure_strings_area
[index
]);
4717 default: NOT_REACHED();
4722 _thd
.dir2
= HT_DIR_END
;
4726 * Handle the mouse while dragging for placement/resizing.
4727 * @return State of handling the event.
4729 EventState
VpHandlePlaceSizingDrag()
4731 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
4733 /* stop drag mode if the window has been closed */
4734 Window
*w
= _thd
.GetCallbackWnd();
4736 ResetObjectToPlace();
4740 /* While dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ).
4741 * Do it even if the button is no longer pressed to make sure that OnPlaceDrag was called at least once. */
4742 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
4743 if (_left_button_down
) return ES_HANDLED
;
4745 /* mouse button released..
4746 * keep the selected tool, but reset it to the original mode. */
4747 _special_mouse_mode
= WSM_NONE
;
4748 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
4749 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
4750 _thd
.place_mode
= HT_RECT
| others
;
4751 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
4752 _thd
.place_mode
= HT_RECT
| others
;
4753 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
4754 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
? _thd
.next_drawstyle
: HT_RAIL
) | others
;
4756 _thd
.place_mode
= HT_POINT
| others
;
4758 SetTileSelectSize(1, 1);
4760 if (_thd
.place_mode
& HT_POLY
) {
4761 if (GetRailSnapMode() == RSM_SNAP_TO_TILE
) SetRailSnapMode(RSM_NO_SNAP
);
4762 if (_thd
.drawstyle
== HT_NONE
) return ES_HANDLED
;
4765 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4770 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4771 * @param icon New shape of the mouse cursor.
4772 * @param pal Palette to use.
4773 * @param mode Mode to perform.
4774 * @param w %Window requesting the mode change.
4776 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
4778 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
4781 #include "table/animcursors.h"
4784 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4785 * @param icon New shape of the mouse cursor.
4786 * @param pal Palette to use.
4787 * @param mode Mode to perform.
4788 * @param window_class %Window class of the window requesting the mode change.
4789 * @param window_num Number of the window in its class requesting the mode change.
4791 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
4793 if (_thd
.window_class
!= WC_INVALID
) {
4794 /* Undo clicking on button and drag & drop */
4795 Window
*w
= _thd
.GetCallbackWnd();
4796 /* Call the abort function, but set the window class to something
4797 * that will never be used to avoid infinite loops. Setting it to
4798 * the 'next' window class must not be done because recursion into
4799 * this function might in some cases reset the newly set object to
4800 * place or not properly reset the original selection. */
4801 _thd
.window_class
= WC_INVALID
;
4802 if (w
!= nullptr) w
->OnPlaceObjectAbort();
4805 /* Mark the old selection dirty, in case the selection shape or colour changes */
4806 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
4808 SetTileSelectSize(1, 1);
4810 _thd
.make_square_red
= false;
4812 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
4814 _special_mouse_mode
= WSM_DRAGDROP
;
4816 _special_mouse_mode
= WSM_NONE
;
4819 _thd
.place_mode
= mode
;
4820 _thd
.window_class
= window_class
;
4821 _thd
.window_number
= window_num
;
4823 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
4827 if (mode
& HT_POLY
) {
4828 SetRailSnapMode((mode
& HT_NEW_POLY
) == HT_NEW_POLY
? RSM_NO_SNAP
: RSM_SNAP_TO_RAIL
);
4831 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
4832 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
4834 SetMouseCursor(icon
, pal
);
4839 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
4840 void ResetObjectToPlace()
4842 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
4845 ViewportMapType
ChangeRenderMode(const ViewPort
*vp
, bool down
) {
4846 ViewportMapType map_type
= vp
->map_type
;
4847 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return map_type
;
4849 return (map_type
== VPMT_MIN
) ? VPMT_MAX
: (ViewportMapType
) (map_type
- 1);
4851 return (map_type
== VPMT_MAX
) ? VPMT_MIN
: (ViewportMapType
) (map_type
+ 1);
4855 Point
GetViewportStationMiddle(const ViewPort
*vp
, const Station
*st
)
4857 int x
= TileX(st
->xy
) * TILE_SIZE
;
4858 int y
= TileY(st
->xy
) * TILE_SIZE
;
4859 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
4861 Point p
= RemapCoords(x
, y
, z
);
4862 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
4863 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
4867 void DrawOverlay(const TileInfo
*ti
, TileType tt
)
4869 if (Overlays::Instance()->IsTileLogicSignalInput(ti
)) {
4870 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_RED
);
4871 } else if (Overlays::Instance()->IsTileLogicSignalOutput(ti
)) {
4872 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
4873 } else if (Overlays::Instance()->IsTileInCatchmentArea(ti
, PRODUCTION
)) {
4874 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
4875 } else if (Overlays::Instance()->IsTileInCatchmentArea(ti
, ACCEPTANCE
)) {
4876 DrawTileSelectionRect(ti
, PAL_NONE
);
4880 /** Helper class for getting the best sprite sorter. */
4881 struct ViewportSSCSS
{
4882 VpSorterChecker fct_checker
; ///< The check function.
4883 VpSpriteSorter fct_sorter
; ///< The sorting function.
4886 /** List of sorters ordered from best to worst. */
4887 static ViewportSSCSS _vp_sprite_sorters
[] = {
4889 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
4891 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
4894 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
4895 void InitializeSpriteSorter()
4897 for (uint i
= 0; i
< lengthof(_vp_sprite_sorters
); i
++) {
4898 if (_vp_sprite_sorters
[i
].fct_checker()) {
4899 _vp_sprite_sorter
= _vp_sprite_sorters
[i
].fct_sorter
;
4903 assert(_vp_sprite_sorter
!= nullptr);
4906 static LineSnapPoint
LineSnapPointAtRailTrackEndpoint(TileIndex tile
, DiagDirection exit_dir
, bool bidirectional
)
4909 ret
.x
= (TILE_SIZE
/ 2) * (uint
)(2 * TileX(tile
) + TileIndexDiffCByDiagDir(exit_dir
).x
+ 1);
4910 ret
.y
= (TILE_SIZE
/ 2) * (uint
)(2 * TileY(tile
) + TileIndexDiffCByDiagDir(exit_dir
).y
+ 1);
4913 SetBit(ret
.dirs
, DiagDirToDir(exit_dir
));
4914 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45LEFT
));
4915 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45RIGHT
));
4916 if (bidirectional
) ret
.dirs
|= ROR
<uint8
>(ret
.dirs
, DIRDIFF_REVERSE
);
4922 * Store the position of lastly built rail track; for highlighting purposes.
4924 * In "polyline" highlighting mode, the stored end point will be used as a snapping point for new
4925 * tracks allowing to place multi-segment polylines.
4927 * @param start_tile tile where the track starts
4928 * @param end_tile tile where the track ends
4929 * @param start_track track piece on the start_tile
4930 * @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)
4932 void StoreRailPlacementEndpoints(TileIndex start_tile
, TileIndex end_tile
, Track start_track
, bool bidirectional_exit
)
4934 if (start_tile
!= INVALID_TILE
&& end_tile
!= INVALID_TILE
) {
4935 /* calculate trackdirs at booth ends of the track */
4936 Trackdir exit_trackdir_at_start
= TrackToTrackdir(start_track
);
4937 Trackdir exit_trackdir_at_end
= ReverseTrackdir(TrackToTrackdir(start_track
));
4938 if (start_tile
!= end_tile
) { // multi-tile case
4939 /* determine proper direction (pointing outside of the track) */
4940 uint distance
= DistanceManhattan(start_tile
, end_tile
);
4941 if (distance
> DistanceManhattan(TileAddByDiagDir(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
)), end_tile
)) {
4942 Swap(exit_trackdir_at_start
, exit_trackdir_at_end
);
4944 /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
4945 if (distance
% 2 != 0) exit_trackdir_at_end
= NextTrackdir(exit_trackdir_at_end
);
4948 LineSnapPoint snap_start
= LineSnapPointAtRailTrackEndpoint(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
), bidirectional_exit
);
4949 LineSnapPoint snap_end
= LineSnapPointAtRailTrackEndpoint(end_tile
, TrackdirToExitdir(exit_trackdir_at_end
), bidirectional_exit
);
4950 /* Find if we already had these coordinates before. */
4951 LineSnapPoint
*snap
;
4952 bool had_start
= false;
4953 bool had_end
= false;
4954 for (snap
= _rail_snap_points
.Begin(); snap
!= _rail_snap_points
.End(); snap
++) {
4955 had_start
|= (snap
->x
== snap_start
.x
&& snap
->y
== snap_start
.y
);
4956 had_end
|= (snap
->x
== snap_end
.x
&& snap
->y
== snap_end
.y
);
4958 /* Create new snap point set. */
4959 if (had_start
&& had_end
) {
4960 /* just stop snaping, don't forget snap points */
4961 SetRailSnapMode(RSM_NO_SNAP
);
4963 /* include only new points */
4964 _rail_snap_points
.Clear();
4965 if (!had_start
) *_rail_snap_points
.Append() = snap_start
;
4966 if (!had_end
) *_rail_snap_points
.Append() = snap_end
;
4967 SetRailSnapMode(RSM_SNAP_TO_RAIL
);
4972 bool CurrentlySnappingRailPlacement()
4974 return (_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() == RSM_SNAP_TO_RAIL
;
4977 static RailSnapMode
GetRailSnapMode()
4979 if (_rail_snap_mode
== RSM_SNAP_TO_TILE
&& _tile_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4980 if (_rail_snap_mode
== RSM_SNAP_TO_RAIL
&& _rail_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4981 return _rail_snap_mode
;
4984 static void SetRailSnapMode(RailSnapMode mode
)
4986 _rail_snap_mode
= mode
;
4988 if ((_thd
.place_mode
& HT_POLY
) && (GetRailSnapMode() == RSM_NO_SNAP
)) {
4989 SetTileSelectSize(1, 1);
4993 static TileIndex
GetRailSnapTile()
4995 if (_tile_snap_points
.Length() == 0) return INVALID_TILE
;
4996 return TileVirtXY(_tile_snap_points
[DIAGDIR_NE
].x
, _tile_snap_points
[DIAGDIR_NE
].y
);
4999 static void SetRailSnapTile(TileIndex tile
)
5001 _tile_snap_points
.Clear();
5002 if (tile
== INVALID_TILE
) return;
5004 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) {
5005 LineSnapPoint
*point
= _tile_snap_points
.Append();
5006 *point
= LineSnapPointAtRailTrackEndpoint(tile
, dir
, false);
5007 point
->dirs
= ROR
<uint8
>(point
->dirs
, DIRDIFF_REVERSE
);
5011 void ResetRailPlacementSnapping()
5013 _rail_snap_mode
= RSM_NO_SNAP
;
5014 _tile_snap_points
.Clear();
5015 _rail_snap_points
.Clear();
5016 _current_snap_lock
.x
= -1;