4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
11 * @file viewport.cpp Handling of all viewports.
14 * The in-game coordinate system looks like this *
30 * @defgroup vp_column_row Rows and columns in the viewport
32 * Columns are vertical sections of the viewport that are half a tile wide.
33 * The origin, i.e. column 0, is through the northern and southern most tile.
34 * This means that the column of e.g. Tile(0, 0) and Tile(100, 100) are in
35 * column number 0. The negative columns are towards the left of the screen,
36 * or towards the west, whereas the positive ones are towards respectively
38 * With half a tile wide is meant that the next column of tiles directly west
39 * or east of the centre line are respectively column -1 and 1. Their tile
40 * centers are only half a tile from the center of their adjoining tile when
41 * looking only at the X-coordinate.
60 * Rows are horizontal sections of the viewport, also half a tile wide.
61 * This time the nothern most tile on the map defines 0 and
62 * everything south of that has a positive number.
66 #include "core/math_func.hpp"
67 #include "core/smallvec_type.hpp"
68 #include "clear_map.h"
71 #include "smallmap_gui.h"
72 #include "smallmap_colours.h"
73 #include "table/tree_land.h"
74 #include "blitter/32bpp_base.hpp"
75 #include "landscape.h"
76 #include "viewport_func.h"
77 #include "station_base.h"
78 #include "waypoint_base.h"
80 #include "signs_base.h"
81 #include "signs_func.h"
82 #include "plans_base.h"
83 #include "plans_func.h"
84 #include "vehicle_base.h"
85 #include "vehicle_gui.h"
86 #include "blitter/factory.hpp"
87 #include "strings_func.h"
88 #include "zoom_func.h"
90 #include "overlay_cmd.h"
91 #include "vehicle_func.h"
92 #include "company_func.h"
93 #include "waypoint_func.h"
94 #include "window_func.h"
95 #include "tilehighlight_func.h"
97 #include "window_gui.h"
98 #include "linkgraph/linkgraph_gui.h"
99 #include "viewport_sprite_sorter.h"
100 #include "bridge_map.h"
101 #include "progress.h"
107 #include "depot_base.h"
108 #include "tunnelbridge_map.h"
110 #include "core/container_func.hpp"
112 #include "table/strings.h"
113 #include "table/string_colours.h"
115 #include "safeguards.h"
117 Point _tile_fract_coords
;
120 static const int MAX_TILE_EXTENT_LEFT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum left extent of tile relative to north corner.
121 static const int MAX_TILE_EXTENT_RIGHT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum right extent of tile relative to north corner.
122 static const int MAX_TILE_EXTENT_TOP
= ZOOM_LVL_BASE
* MAX_BUILDING_PIXELS
; ///< Maximum top extent of tile relative to north corner (not considering bridges).
123 static const int MAX_TILE_EXTENT_BOTTOM
= ZOOM_LVL_BASE
* (TILE_PIXELS
+ 2 * TILE_HEIGHT
); ///< Maximum bottom extent of tile relative to north corner (worst case: #SLOPE_STEEP_N).
125 struct StringSpriteToDraw
{
134 struct TileSpriteToDraw
{
137 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
138 int32 x
; ///< screen X coordinate of sprite
139 int32 y
; ///< screen Y coordinate of sprite
142 struct ChildScreenSpriteToDraw
{
145 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
148 int next
; ///< next child to draw (-1 at the end)
151 /** Enumeration of multi-part foundations */
152 enum FoundationPart
{
153 FOUNDATION_PART_NONE
= 0xFF, ///< Neither foundation nor groundsprite drawn yet.
154 FOUNDATION_PART_NORMAL
= 0, ///< First part (normal foundation or no foundation)
155 FOUNDATION_PART_HALFTILE
= 1, ///< Second part (halftile foundation)
160 * Mode of "sprite combining"
161 * @see StartSpriteCombine
163 enum SpriteCombineMode
{
164 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
165 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
166 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
169 typedef SmallVector
<TileSpriteToDraw
, 64> TileSpriteToDrawVector
;
170 typedef SmallVector
<StringSpriteToDraw
, 4> StringSpriteToDrawVector
;
171 typedef SmallVector
<ParentSpriteToDraw
, 64> ParentSpriteToDrawVector
;
172 typedef SmallVector
<ChildScreenSpriteToDraw
, 16> ChildScreenSpriteToDrawVector
;
174 typedef std::vector
<std::pair
<int, OrderType
> > RankOrderTypeList
;
175 typedef std::map
<TileIndex
, RankOrderTypeList
> RouteStepsMap
;
177 const uint max_rank_order_type_count
= 10;
186 * Snapping point for a track.
188 * Point where a track (rail/road/other) can be snapped to while selecting tracks with polyline
189 * tool (HT_POLY). Besides of x/y coordinates expressed in tile "units" it contains a set of
190 * allowed line directions.
192 struct LineSnapPoint
: Point
{
193 uint8 dirs
; ///< Allowed line directions, set of #Direction bits.
196 typedef SmallVector
<LineSnapPoint
, 4> LineSnapPoints
; ///< Set of snapping points
198 /** Coordinates of a polyline track made of 2 connected line segments. */
200 Point start
; ///< The point where the first segment starts (as given in LineSnapPoint).
201 Direction first_dir
; ///< Direction of the first line segment.
202 uint first_len
; ///< Length of the first segment - number of track pieces.
203 Direction second_dir
; ///< Direction of the second line segment.
204 uint second_len
; ///< Length of the second segment - number of track pieces.
207 /** Data structure storing rendering information */
208 struct ViewportDrawer
{
211 StringSpriteToDrawVector string_sprites_to_draw
;
212 TileSpriteToDrawVector tile_sprites_to_draw
;
213 ParentSpriteToDrawVector parent_sprites_to_draw
;
214 ParentSpriteToSortVector parent_sprites_to_sort
; ///< Parent sprite pointer array used for sorting
215 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
216 TunnelBridgeToMapVector tunnel_to_map
;
217 TunnelBridgeToMapVector bridge_to_map
;
221 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
223 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
224 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
225 int *last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
226 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
229 static void MarkViewportDirty(const ViewPort
* const vp
, int left
, int top
, int right
, int bottom
);
230 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
);
231 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
);
233 static DrawPixelInfo _dpi_for_text
;
234 static ViewportDrawer _vd
;
236 static std::vector
<ViewPort
*> _viewport_window_cache
;
238 RouteStepsMap _vp_route_steps
;
239 RouteStepsMap _vp_route_steps_last_mark_dirty
;
240 uint _vp_route_step_width
= 0;
241 uint _vp_route_step_height_top
= 0;
242 uint _vp_route_step_height_middle
= 0;
243 uint _vp_route_step_height_bottom
= 0;
244 SubSprite _vp_route_step_subsprite
;
246 struct DrawnPathRouteTileLine
{
251 bool operator==(const DrawnPathRouteTileLine
&other
) const
253 return std::tie(this->from_tile
, this->to_tile
, this->order_match
) == std::tie(other
.from_tile
, other
.to_tile
, other
.order_match
);
256 bool operator!=(const DrawnPathRouteTileLine
&other
) const
258 return !(*this == other
);
261 bool operator<(const DrawnPathRouteTileLine
&other
) const
263 return std::tie(this->from_tile
, this->to_tile
, this->order_match
) < std::tie(other
.from_tile
, other
.to_tile
, other
.order_match
);
267 std::vector
<DrawnPathRouteTileLine
> _vp_route_paths
;
268 std::vector
<DrawnPathRouteTileLine
> _vp_route_paths_last_mark_dirty
;
270 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
);
272 TileHighlightData _thd
;
273 static TileInfo
*_cur_ti
;
274 bool _draw_bounding_boxes
= false;
275 bool _draw_dirty_blocks
= false;
276 uint _dirty_block_colour
= 0;
277 static VpSpriteSorter _vp_sprite_sorter
= NULL
;
279 const byte
*_pal2trsp_remap_ptr
= NULL
;
281 static RailSnapMode _rail_snap_mode
= RSM_NO_SNAP
; ///< Type of rail track snapping (polyline tool).
282 static LineSnapPoints _tile_snap_points
; ///< Tile to which a rail track will be snapped to (polyline tool).
283 static LineSnapPoints _rail_snap_points
; ///< Set of points where a rail track will be snapped to (polyline tool).
284 static LineSnapPoint _current_snap_lock
; ///< Start point and direction at which selected track is locked on currently (while dragging in polyline mode).
286 static RailSnapMode
GetRailSnapMode();
287 static void SetRailSnapMode(RailSnapMode mode
);
288 static TileIndex
GetRailSnapTile();
289 static void SetRailSnapTile(TileIndex tile
);
291 static Point
MapXYZToViewport(const ViewPort
*vp
, int x
, int y
, int z
)
293 Point p
= RemapCoords(x
, y
, z
);
294 p
.x
-= vp
->virtual_width
/ 2;
295 p
.y
-= vp
->virtual_height
/ 2;
299 void DeleteWindowViewport(Window
*w
)
301 if (w
->viewport
== NULL
) return;
303 container_unordered_remove(_viewport_window_cache
, w
->viewport
);
304 delete w
->viewport
->overlay
;
310 * Initialize viewport of the window for use.
311 * @param w Window to use/display the viewport in
312 * @param x Offset of left edge of viewport with respect to left edge window \a w
313 * @param y Offset of top edge of viewport with respect to top edge window \a w
314 * @param width Width of the viewport
315 * @param height Height of the viewport
316 * @param follow_flags Flags controlling the viewport.
317 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
318 * - If bit 31 is clear, it is a #TileIndex.
319 * @param zoom Zoomlevel to display
321 void InitializeWindowViewport(Window
*w
, int x
, int y
,
322 int width
, int height
, uint32 follow_flags
, ZoomLevel zoom
)
324 assert(w
->viewport
== NULL
);
326 ViewportData
*vp
= new ViewportData();
329 vp
->left
= x
+ w
->left
;
330 vp
->top
= y
+ w
->top
;
334 vp
->zoom
= static_cast<ZoomLevel
>(Clamp(zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
336 vp
->virtual_left
= 0;
338 vp
->virtual_width
= ScaleByZoom(width
, zoom
);
339 vp
->virtual_height
= ScaleByZoom(height
, zoom
);
341 vp
->map_type
= VPMT_BEGIN
;
345 if (follow_flags
& 0x80000000) {
348 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
349 veh
= Vehicle::Get(vp
->follow_vehicle
);
350 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
351 MarkAllRoutePathsDirty(veh
);
352 MarkAllRouteStepsDirty(veh
);
354 uint x
= TileX(follow_flags
) * TILE_SIZE
;
355 uint y
= TileY(follow_flags
) * TILE_SIZE
;
357 vp
->follow_vehicle
= INVALID_VEHICLE
;
358 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopePixelZ(x
, y
));
361 vp
->scrollpos_x
= pt
.x
;
362 vp
->scrollpos_y
= pt
.y
;
363 vp
->dest_scrollpos_x
= pt
.x
;
364 vp
->dest_scrollpos_y
= pt
.y
;
367 _viewport_window_cache
.push_back(vp
);
370 static Point _vp_move_offs
;
372 static void DoSetViewportPosition(const Window
*w
, int left
, int top
, int width
, int height
)
374 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 NULL 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
)) != NULL
&&
584 (vp
= IsPtInWindowViewport(w
, x
, y
)) != NULL
)
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
= NULL
, 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 == NULL if foundation sprite was clipped by the viewport bounds */
745 if (_vd
.last_child
!= NULL
) _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
= NULL
;
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
== NULL
) 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
, NULL
, 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] = NULL
;
1307 _vd
.last_foundation_child
[1] = NULL
;
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_16X
, &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 auto original_order
= order
;
1777 while (order
->IsType(OT_CONDITIONAL
)) {
1778 order
= veh
->GetOrder(order
->GetConditionSkipToOrder());
1780 if (original_order
== order
) return nullptr;
1785 static bool ViewportMapPrepareVehicleRoute(const Vehicle
* const veh
)
1787 if (!veh
) return false;
1789 if (_vp_route_paths
.size() == 0) {
1790 TileIndex from_tile
= GetLastValidOrderLocation(veh
);
1791 if (from_tile
== INVALID_TILE
) return false;
1794 FOR_VEHICLE_ORDERS(veh
, order
) {
1795 Order
*final_order
= GetFinalOrder(veh
, order
);
1796 if (final_order
== nullptr) continue;
1797 const TileIndex to_tile
= final_order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1798 if (to_tile
== INVALID_TILE
) continue;
1800 DrawnPathRouteTileLine path
= { from_tile
, to_tile
, (final_order
== order
) };
1801 if (path
.from_tile
> path
.to_tile
) std::swap(path
.from_tile
, path
.to_tile
);
1802 _vp_route_paths
.push_back(path
);
1804 const OrderType ot
= order
->GetType();
1805 if (ot
== OT_GOTO_STATION
|| ot
== OT_GOTO_DEPOT
|| ot
== OT_GOTO_WAYPOINT
|| ot
== OT_IMPLICIT
) from_tile
= to_tile
;
1807 // remove duplicate lines
1808 std::sort(_vp_route_paths
.begin(), _vp_route_paths
.end());
1809 _vp_route_paths
.erase(std::unique(_vp_route_paths
.begin(), _vp_route_paths
.end()), _vp_route_paths
.end());
1814 /** Draw the route of a vehicle. */
1815 static void ViewportMapDrawVehicleRoute(const ViewPort
*vp
)
1817 const Vehicle
*veh
= GetVehicleFromWindow(_focused_window
);
1820 if (!_vp_route_paths
.empty()) {
1821 // make sure we remove any leftover paths
1822 MarkRoutePathsDirty(_vp_route_paths
);
1823 _vp_route_paths
.clear();
1824 _vp_route_paths_last_mark_dirty
.clear();
1825 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 0");
1830 switch (_settings_client
.gui
.show_vehicle_route
) {
1831 /* case 0: return; // No */
1833 if (!ViewportMapPrepareVehicleRoute(veh
)) {
1834 if (!_vp_route_paths
.empty()) {
1835 // make sure we remove any leftover paths
1836 MarkRoutePathsDirty(_vp_route_paths
);
1837 _vp_route_paths
.clear();
1838 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 1");
1843 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1844 _cur_dpi
= &_dpi_for_text
;
1846 for (const auto &iter
: _vp_route_paths
) {
1847 const Point from_pt
= RemapCoords2(TileX(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1848 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
1849 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
1851 const Point to_pt
= RemapCoords2(TileX(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1852 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
1853 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
1856 if (_settings_client
.gui
.dash_level_of_route_lines
== 0) {
1857 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3, _settings_client
.gui
.dash_level_of_route_lines
);
1860 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
);
1863 if (_vp_route_paths_last_mark_dirty
!= _vp_route_paths
) {
1864 // make sure we're not drawing a partial path
1865 MarkRoutePathsDirty(_vp_route_paths
);
1866 _vp_route_paths_last_mark_dirty
= _vp_route_paths
;
1867 DEBUG(misc
, 1, "ViewportMapDrawVehicleRoute: redrawing dirty paths 2");
1876 static inline void DrawRouteStep(const ViewPort
* const vp
, const TileIndex tile
, const RankOrderTypeList list
)
1878 if (tile
== INVALID_TILE
) return;
1879 const uint step_count
= list
.size() > max_rank_order_type_count
? 1 : list
.size();
1880 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1881 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
) - (_vp_route_step_width
/ 2);
1882 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
1883 const int rsth
= _vp_route_step_height_top
+ (int)step_count
* char_height
+ _vp_route_step_height_bottom
;
1884 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
) - rsth
;
1886 /* Draw the background. */
1887 DrawSprite(SPR_ROUTE_STEP_TOP
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
);
1888 uint y2
= y
+ _vp_route_step_height_top
;
1890 for (uint r
= step_count
; r
!= 0; r
--, y2
+= char_height
) {
1891 DrawSprite(SPR_ROUTE_STEP_MIDDLE
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
, &_vp_route_step_subsprite
);
1894 DrawSprite(SPR_ROUTE_STEP_BOTTOM
, PAL_NONE
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1895 SpriteID s
= SPR_ROUTE_STEP_BOTTOM_SHADOW
;
1896 DrawSprite(SetBit(s
, PALETTE_MODIFIER_TRANSPARENT
), PALETTE_TO_TRANSPARENT
, _cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
);
1898 /* Fill with the data. */
1899 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1900 y2
= y
+ _vp_route_step_height_top
;
1901 _cur_dpi
= &_dpi_for_text
;
1903 if (list
.size() > max_rank_order_type_count
) {
1904 /* Write order overflow item */
1905 SetDParam(0, list
.size());
1906 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1907 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_OVERFLOW
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1909 for (RankOrderTypeList::const_iterator cit
= list
.begin(); cit
!= list
.end(); cit
++, y2
+= char_height
) {
1911 switch (cit
->second
) {
1912 case OT_GOTO_STATION
:
1913 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_STATION
);
1916 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_DEPOT
);
1918 case OT_GOTO_WAYPOINT
:
1919 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_WAYPOINT
);
1922 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_IMPLICIT
);
1924 default: // OT_NOTHING OT_LOADING OT_LEAVESTATION OT_DUMMY OT_CONDITIONAL
1929 /* Write order's info */
1930 SetDParam(0, cit
->first
);
1931 DrawString(_dpi_for_text
.left
+ x
, _dpi_for_text
.left
+ x
+ _vp_route_step_width
- 1, _dpi_for_text
.top
+ y2
,
1932 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
1939 static bool ViewportPrepareVehicleRouteSteps(const Vehicle
* const veh
)
1941 if (!veh
) return false;
1943 if (_vp_route_steps
.size() == 0) {
1947 FOR_VEHICLE_ORDERS(veh
, order
) {
1948 const TileIndex tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
1950 if (tile
== INVALID_TILE
) continue;
1951 _vp_route_steps
[tile
].push_back(std::pair
<int, OrderType
>(order_rank
, order
->GetType()));
1958 /** Draw the route steps of a vehicle. */
1959 static void ViewportDrawVehicleRouteSteps(const ViewPort
* const vp
)
1961 const Vehicle
* const veh
= GetVehicleFromWindow(_focused_window
);
1962 if (veh
&& ViewportPrepareVehicleRouteSteps(veh
)) {
1963 if (_vp_route_steps
!= _vp_route_steps_last_mark_dirty
) {
1964 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1965 MarkRouteStepDirty(cit
);
1967 _vp_route_steps_last_mark_dirty
= _vp_route_steps
;
1970 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
1971 DrawRouteStep(vp
, cit
->first
, cit
->second
);
1976 void ViewportDrawPlans(const ViewPort
*vp
)
1978 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1979 _cur_dpi
= &_dpi_for_text
;
1983 if (!p
->IsVisible()) continue;
1984 for (PlanLineVector::iterator it
= p
->lines
.begin(); it
!= p
->lines
.end(); it
++) {
1986 if (!pl
->visible
) continue;
1987 for (uint i
= 1; i
< pl
->tiles
.size(); i
++) {
1988 const TileIndex from_tile
= pl
->tiles
[i
- 1];
1989 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1990 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
1991 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
1993 const TileIndex to_tile
= pl
->tiles
[i
];
1994 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
1995 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
1996 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
1998 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3);
2000 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_RED
, 1);
2003 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 1);
2009 if (_current_plan
&& _current_plan
->temp_line
->tiles
.size() > 1) {
2010 for (uint i
= 1; i
< _current_plan
->temp_line
->tiles
.size(); i
++) {
2011 const TileIndex from_tile
= _current_plan
->temp_line
->tiles
[i
- 1];
2012 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2013 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2014 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2016 const TileIndex to_tile
= _current_plan
->temp_line
->tiles
[i
];
2017 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2018 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2019 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2021 GfxDrawLine(from_x
, from_y
, to_x
, to_y
, PC_WHITE
, 3, 1);
2028 #define SLOPIFY_COLOUR(tile, height, vF, vW, vS, vE, vN, action) { \
2030 const Slope slope = GetTileSlope((tile), (height)); \
2033 case SLOPE_ELEVATED: \
2034 action (vF); break; \
2036 switch (slope & SLOPE_EW) { \
2037 case SLOPE_W: action (vW); break; \
2038 case SLOPE_E: action (vE); break; \
2039 default: action (slope & SLOPE_S) ? (vS) : (vN); break; \
2048 #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)
2049 #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 =)
2050 #define GET_SLOPE_INDEX(slope_index) SLOPIFY_COLOUR(tile, NULL, 0, 1, 2, 3, 4, slope_index =)
2052 #define COL8TO32(x) _cur_palette.palette[x].data
2053 #define COLOUR_FROM_INDEX(x) ((const uint8 *)&(x))[colour_index]
2054 #define IS32(x) (is_32bpp ? COL8TO32(x) : (x))
2056 /* Variables containing Colour if 32bpp or palette index if 8bpp. */
2057 uint32 _vp_map_vegetation_clear_colours
[16][6][8]; ///< [Slope][ClearGround][Multi (see LoadClearGroundMainColours())]
2058 uint32 _vp_map_vegetation_tree_colours
[5][MAX_TREE_COUNT_BY_LANDSCAPE
]; ///< [TreeGround][max of _tree_count_by_landscape]
2059 uint32 _vp_map_water_colour
[5]; ///< [Slope]
2061 static inline uint
ViewportMapGetColourIndexMulti(const TileIndex tile
, const ClearGround cg
)
2067 return GetClearDensity(tile
);
2069 return GB(TileX(tile
) ^ TileY(tile
), 4, 3);
2071 return TileHash(TileX(tile
), TileY(tile
)) & 1;
2073 return GetFieldType(tile
) & 7;
2074 default: NOT_REACHED();
2078 static const ClearGround _treeground_to_clearground
[5] = {
2079 CLEAR_GRASS
, // TREE_GROUND_GRASS
2080 CLEAR_ROUGH
, // TREE_GROUND_ROUGH
2081 CLEAR_SNOW
, // TREE_GROUND_SNOW_DESERT, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2082 CLEAR_GRASS
, // TREE_GROUND_SHORE
2083 CLEAR_SNOW
, // TREE_GROUND_ROUGH_SNOW, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2086 template <bool is_32bpp
, bool show_slope
>
2087 static inline uint32
ViewportMapGetColourVegetation(const TileIndex tile
, TileType t
, const uint colour_index
)
2092 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, NULL
) & 15) : SLOPE_FLAT
;
2094 ClearGround cg
= GetClearGround(tile
);
2095 if (cg
== CLEAR_FIELDS
&& colour_index
& 1) {
2099 else multi
= ViewportMapGetColourIndexMulti(tile
, cg
);
2100 return _vp_map_vegetation_clear_colours
[slope
][cg
][multi
];
2104 colour
= IsTileForestIndustry(tile
) ? (colour_index
& 1 ? PC_GREEN
: 0x7B) : GREY_SCALE(3);
2108 const TreeGround tg
= GetTreeGround(tile
);
2109 const uint td
= GetTreeDensity(tile
);
2110 if (IsTransparencySet(TO_TREES
)) {
2111 ClearGround cg
= _treeground_to_clearground
[tg
];
2112 if (cg
== CLEAR_SNOW
&& _settings_game
.game_creation
.landscape
== LT_TROPIC
) cg
= CLEAR_DESERT
;
2113 Slope slope
= show_slope
? (Slope
)(GetTileSlope(tile
, NULL
) & 15) : SLOPE_FLAT
;
2114 uint32 ground_colour
= _vp_map_vegetation_clear_colours
[slope
][cg
][td
];
2116 if (IsInvisibilitySet(TO_TREES
)) {
2118 return ground_colour
;
2121 /* Take ground and make it darker. */
2123 return Blitter_32bppBase::MakeTransparent(ground_colour
, 192, 256).data
;
2126 /* 8bpp transparent snow trees give blue. Definitely don't want that. Prefer grey. */
2127 if (cg
== CLEAR_SNOW
&& td
> 1) return GREY_SCALE(13 - GetTreeCount(tile
));
2128 return _pal2trsp_remap_ptr
[ground_colour
];
2132 if (tg
== TREE_GROUND_SNOW_DESERT
|| tg
== TREE_GROUND_ROUGH_SNOW
) {
2133 return _vp_map_vegetation_clear_colours
[colour_index
][_settings_game
.game_creation
.landscape
== LT_TROPIC
? CLEAR_DESERT
: CLEAR_SNOW
][td
];
2136 const uint rnd
= min(GetTreeCount(tile
) ^ (((tile
& 3) ^ (TileY(tile
) & 3)) * td
), MAX_TREE_COUNT_BY_LANDSCAPE
- 1);
2137 return _vp_map_vegetation_tree_colours
[tg
][rnd
];
2144 uint slope_index
= 0;
2145 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2146 return _vp_map_water_colour
[slope_index
];
2151 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[t
]);
2152 colour
= COLOUR_FROM_INDEX(colour
);
2157 return COL8TO32(colour
);
2160 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2165 template <bool is_32bpp
, bool show_slope
>
2166 static inline uint32
ViewportMapGetColourIndustries(const TileIndex tile
, const TileType t
, const uint colour_index
)
2168 extern LegendAndColour _legend_from_industries
[NUM_INDUSTRYTYPES
+ 1];
2169 extern uint _industry_to_list_pos
[NUM_INDUSTRYTYPES
];
2170 extern bool _smallmap_show_heightmap
;
2173 if (t
== MP_INDUSTRY
) {
2174 /* If industry is allowed to be seen, use its colour on the map. */
2175 const IndustryType it
= Industry::GetByTile(tile
)->type
;
2176 if (_legend_from_industries
[_industry_to_list_pos
[it
]].show_on_map
)
2177 return IS32(GetIndustrySpec(it
)->map_colour
);
2178 /* Otherwise, return the colour which will make it disappear. */
2179 t2
= IsTileOnWater(tile
) ? MP_WATER
: MP_CLEAR
;
2182 if (is_32bpp
&& t2
== MP_WATER
) {
2183 uint slope_index
= 0;
2184 if (t
!= MP_INDUSTRY
&& GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
); ///< Ignore industry on water not shown on map.
2185 return _vp_map_water_colour
[slope_index
];
2188 const int h
= TileHeight(tile
);
2189 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
2190 const uint32 colours
= ApplyMask(_smallmap_show_heightmap
? cs
->height_colours
[h
] : cs
->default_colour
, &_smallmap_vehicles_andor
[t2
]);
2191 uint32 colour
= COLOUR_FROM_INDEX(colours
);
2193 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2195 return IS32(colour
);
2198 template <bool is_32bpp
, bool show_slope
>
2199 static inline uint32
ViewportMapGetColourOwner(const TileIndex tile
, TileType t
, const uint colour_index
)
2201 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2202 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2205 case MP_INDUSTRY
: return IS32(PC_DARK_GREY
);
2206 case MP_HOUSE
: return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
2210 const Owner o
= GetTileOwner(tile
);
2211 if ((o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) || o
== OWNER_NONE
|| o
== OWNER_WATER
) {
2212 if (t
== MP_WATER
) {
2214 uint slope_index
= 0;
2215 if (GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
2216 return _vp_map_water_colour
[slope_index
];
2223 const int h
= TileHeight(tile
);
2224 uint32 colour
= COLOUR_FROM_INDEX(_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
].height_colours
[h
]);
2225 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2226 return IS32(colour
);
2229 else if (o
== OWNER_TOWN
) {
2230 return IS32(t
== MP_ROAD
? (colour_index
& 1 ? PC_BLACK
: GREY_SCALE(3)) : PC_DARK_RED
);
2233 /* Train stations are sometimes hard to spot.
2234 * So we give the player a hint by mixing his colour with black. */
2235 uint32 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2236 if (t
!= MP_STATION
) {
2237 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, NULL
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
2240 if (GetStationType(tile
) == STATION_RAIL
) colour
= colour_index
& 1 ? colour
: PC_BLACK
;
2242 if (is_32bpp
) return COL8TO32(colour
);
2246 static inline void ViewportMapStoreBridgeAboveTile(const ViewPort
* const vp
, const TileIndex tile
)
2248 /* No need to bother for hidden things */
2249 if (!_settings_client
.gui
.show_bridges_on_map
) return;
2251 /* Check existing stored bridges */
2252 TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin();
2253 TunnelBridgeToMap
*tbtm_end
= _vd
.bridge_to_map
.End();
2254 for (; tbtm
!= tbtm_end
; ++tbtm
) {
2255 if (!IsBridge(tbtm
->from_tile
)) continue;
2257 TileIndex from
= tbtm
->from_tile
;
2258 TileIndex to
= tbtm
->to_tile
;
2259 if (TileX(from
) == TileX(to
) && TileX(from
) == TileX(tile
)) {
2260 if (TileY(from
) > TileY(to
)) std::swap(from
, to
);
2261 if (TileY(from
) <= TileY(tile
) && TileY(tile
) <= TileY(to
)) return; /* already covered */
2263 else if (TileY(from
) == TileY(to
) && TileY(from
) == TileY(tile
)) {
2264 if (TileX(from
) > TileX(to
)) std::swap(from
, to
);
2265 if (TileX(from
) <= TileX(tile
) && TileX(tile
) <= TileX(to
)) return; /* already covered */
2269 ViewportMapStoreBridgeTunnel(vp
, GetSouthernBridgeEnd(tile
));
2272 static inline TileIndex
ViewportMapGetMostSignificantTileType(const ViewPort
* const vp
, const TileIndex from_tile
, TileType
* const tile_type
)
2274 if (vp
->zoom
<= ZOOM_LVL_OUT_128X
|| !_settings_client
.gui
.viewport_map_scan_surroundings
) {
2275 const TileType ttype
= GetTileType(from_tile
);
2276 /* Store bridges and tunnels. */
2277 if (ttype
!= MP_TUNNELBRIDGE
) {
2279 if (IsBridgeAbove(from_tile
)) ViewportMapStoreBridgeAboveTile(vp
, from_tile
);
2282 ViewportMapStoreBridgeTunnel(vp
, from_tile
);
2283 switch (GetTunnelBridgeTransportType(from_tile
)) {
2284 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2285 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2286 default: *tile_type
= MP_WATER
; break;
2292 const uint8 length
= (vp
->zoom
- ZOOM_LVL_OUT_128X
) * 2;
2293 TileArea tile_area
= TileArea(from_tile
, length
, length
);
2294 tile_area
.ClampToMap();
2296 /* Find the most important tile of the area. */
2297 TileIndex result
= from_tile
;
2298 uint importance
= 0;
2299 TILE_AREA_LOOP_WITH_PREFETCH(tile
, tile_area
) {
2300 const TileType ttype
= GetTileType(tile
);
2301 const uint tile_importance
= _tiletype_importance
[ttype
];
2302 if (tile_importance
> importance
) {
2303 importance
= tile_importance
;
2306 if (ttype
!= MP_TUNNELBRIDGE
&& IsBridgeAbove(tile
)) {
2307 ViewportMapStoreBridgeAboveTile(vp
, tile
);
2311 /* Store bridges and tunnels. */
2312 *tile_type
= GetTileType(result
);
2313 if (*tile_type
== MP_TUNNELBRIDGE
) {
2314 ViewportMapStoreBridgeTunnel(vp
, result
);
2315 switch (GetTunnelBridgeTransportType(result
)) {
2316 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
2317 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
2318 default: *tile_type
= MP_WATER
; break;
2325 /** Get the colour of a tile, can be 32bpp RGB or 8bpp palette index. */
2326 template <bool is_32bpp
, bool show_slope
>
2327 uint32
ViewportMapGetColour(const ViewPort
* const vp
, uint x
, uint y
, const uint colour_index
)
2329 if (!(IsInsideMM(x
, TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1) &&
2330 IsInsideMM(y
, TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1)))
2333 /* Very approximative but fast way to get the tile when taking Z into account. */
2334 const TileIndex tile_tmp
= TileVirtXY(x
, y
);
2335 const uint z
= TileHeight(tile_tmp
) * 4;
2336 TileIndex tile
= TileVirtXY(x
+ z
, y
+ z
);
2337 if (tile
>= MapSize()) return 0;
2338 if (_settings_game
.construction
.freeform_edges
) {
2339 /* tile_tmp and tile must be from the same side,
2340 * otherwise it's an approximation erroneous case
2341 * that leads to a graphic glitch below south west border.
2343 if (TileX(tile_tmp
) > (MapSizeX() - (MapSizeX() / 8)))
2344 if ((TileX(tile_tmp
) < (MapSizeX() / 2)) != (TileX(tile
) < (MapSizeX() / 2)))
2347 TileType tile_type
= MP_VOID
;
2348 tile
= ViewportMapGetMostSignificantTileType(vp
, tile
, &tile_type
);
2349 if (tile_type
== MP_VOID
) return 0;
2351 /* Return the colours. */
2352 switch (vp
->map_type
) {
2353 default: return ViewportMapGetColourOwner
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2354 case VPMT_INDUSTRY
: return ViewportMapGetColourIndustries
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2355 case VPMT_VEGETATION
: return ViewportMapGetColourVegetation
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
2359 /* Taken from http://stereopsis.com/doubleblend.html, PixelBlend() is faster than ComposeColourRGBANoCheck() */
2360 static inline void PixelBlend(uint32
* const d
, const uint32 s
)
2362 const uint32 a
= (s
>> 24) + 1;
2363 const uint32 dstrb
= *d
& 0xFF00FF;
2364 const uint32 dstg
= *d
& 0xFF00;
2365 const uint32 srcrb
= s
& 0xFF00FF;
2366 const uint32 srcg
= s
& 0xFF00;
2367 uint32 drb
= srcrb
- dstrb
;
2368 uint32 dg
= srcg
- dstg
;
2373 uint32 rb
= (drb
+ dstrb
) & 0xFF00FF;
2374 uint32 g
= (dg
+ dstg
) & 0xFF00;
2378 /** Draw the bounding boxes of the scrolling viewport (right-clicked and dragged) */
2379 static void ViewportMapDrawScrollingViewportBox(const ViewPort
* const vp
)
2381 if (_scrolling_viewport
&& _scrolling_viewport
->viewport
) {
2382 const ViewPort
* const vp_scrolling
= _scrolling_viewport
->viewport
;
2383 if (vp_scrolling
->zoom
< ZOOM_LVL_DRAW_MAP
) {
2384 /* Check intersection of dpi and vp_scrolling */
2385 const int mask
= ScaleByZoom(-1, vp
->zoom
);
2386 const int vp_scrolling_virtual_top_mask
= vp_scrolling
->virtual_top
& mask
;
2387 const int vp_scrolling_virtual_bottom_mask
= (vp_scrolling
->virtual_top
+ vp_scrolling
->virtual_height
) & mask
;
2388 const int t_inter
= max(vp_scrolling_virtual_top_mask
, _vd
.dpi
.top
);
2389 const int b_inter
= min(vp_scrolling_virtual_bottom_mask
, _vd
.dpi
.top
+ _vd
.dpi
.height
);
2390 if (t_inter
< b_inter
) {
2391 const int vp_scrolling_virtual_left_mask
= vp_scrolling
->virtual_left
& mask
;
2392 const int vp_scrolling_virtual_right_mask
= (vp_scrolling
->virtual_left
+ vp_scrolling
->virtual_width
) & mask
;
2393 const int l_inter
= max(vp_scrolling_virtual_left_mask
, _vd
.dpi
.left
);
2394 const int r_inter
= min(vp_scrolling_virtual_right_mask
, _vd
.dpi
.left
+ _vd
.dpi
.width
);
2395 if (l_inter
< r_inter
) {
2396 /* OK, so we can draw something that tells where the scrolling viewport is */
2397 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2398 const int w_inter
= UnScaleByZoom(r_inter
- l_inter
, vp
->zoom
);
2399 const int h_inter
= UnScaleByZoom(b_inter
- t_inter
, vp
->zoom
);
2400 const int x
= UnScaleByZoom(l_inter
- _vd
.dpi
.left
, vp
->zoom
);
2401 const int y
= UnScaleByZoom(t_inter
- _vd
.dpi
.top
, vp
->zoom
);
2403 /* If asked, with 32bpp we can do some blending */
2404 if (_settings_client
.gui
.show_scrolling_viewport_on_map
>= 2 && blitter
->GetScreenDepth() == 32)
2405 for (int j
= y
; j
< y
+ h_inter
; j
++)
2406 for (int i
= x
; i
< x
+ w_inter
; i
++)
2407 PixelBlend((uint32
*)blitter
->MoveTo(_vd
.dpi
.dst_ptr
, i
, j
), 0x40FCFCFC);
2409 /* Draw area contour */
2410 if (_settings_client
.gui
.show_scrolling_viewport_on_map
!= 2) {
2411 if (t_inter
== vp_scrolling_virtual_top_mask
)
2412 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2413 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
, PC_WHITE
);
2414 if (b_inter
== vp_scrolling_virtual_bottom_mask
)
2415 for (int i
= x
; i
< x
+ w_inter
; i
+= 2)
2416 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, i
, y
+ h_inter
, PC_WHITE
);
2417 if (l_inter
== vp_scrolling_virtual_left_mask
)
2418 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2419 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, j
, PC_WHITE
);
2420 if (r_inter
== vp_scrolling_virtual_right_mask
)
2421 for (int j
= y
; j
< y
+ h_inter
; j
+= 2)
2422 blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
+ w_inter
, j
, PC_WHITE
);
2430 uint32
*_vp_map_line
; ///< Buffer for drawing the map of a viewport.
2432 static void ViewportMapDrawBridgeTunnel(const ViewPort
* const vp
, const TunnelBridgeToMap
* const tbtm
, const int z
,
2433 const bool is_tunnel
, const int w
, const int h
, Blitter
* const blitter
)
2435 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2436 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2438 TileIndex tile
= tbtm
->from_tile
;
2439 const Owner o
= GetTileOwner(tile
);
2440 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
2443 if (vp
->map_type
== VPMT_OWNER
&& _settings_client
.gui
.use_owner_colour_for_tunnelbridge
&& o
< MAX_COMPANIES
) {
2444 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
2445 colour
= is_tunnel
? _darken_colour
[colour
] : _lighten_colour
[colour
];
2448 colour
= is_tunnel
? PC_BLACK
: PC_VERY_LIGHT_YELLOW
;
2451 TileIndexDiff delta
= TileOffsByDiagDir(GetTunnelBridgeDirection(tile
));
2452 for (; tile
!= tbtm
->to_tile
; tile
+= delta
) { // For each tile
2453 const Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, z
);
2454 const int x
= UnScaleByZoomLower(pt
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2455 if (IsInsideMM(x
, 0, w
)) {
2456 const int y
= UnScaleByZoomLower(pt
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2457 if (IsInsideMM(y
, 0, h
)) blitter
->SetPixel(_vd
.dpi
.dst_ptr
, x
, y
, colour
);
2462 /** Draw the map on a viewport. */
2463 template <bool is_32bpp
, bool show_slope
>
2464 void ViewportMapDraw(const ViewPort
* const vp
)
2467 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
2469 SmallMapWindow::RebuildColourIndexIfNecessary();
2471 /* Index of colour: _green_map_heights[] contains blocks of 4 colours, say ABCD
2472 * For a XXXY colour block to render nicely, follow the model:
2473 * line 1: ABCDABCDABCD
2474 * line 2: CDABCDABCDAB
2475 * line 3: ABCDABCDABCD
2476 * => colour_index_base's second bit is changed every new line.
2478 const int sx
= UnScaleByZoomLower(_vd
.dpi
.left
, _vd
.dpi
.zoom
);
2479 const int sy
= UnScaleByZoomLower(_vd
.dpi
.top
, _vd
.dpi
.zoom
);
2480 const uint line_padding
= 2 * (sy
& 1);
2481 uint colour_index_base
= (sx
+ line_padding
) & 3;
2483 const int incr_a
= (1 << (vp
->zoom
- 2)) / ZOOM_LVL_BASE
;
2484 const int incr_b
= (1 << (vp
->zoom
- 1)) / ZOOM_LVL_BASE
;
2485 const int a
= (_vd
.dpi
.left
>> 2) / ZOOM_LVL_BASE
;
2486 int b
= (_vd
.dpi
.top
>> 1) / ZOOM_LVL_BASE
;
2487 const int w
= UnScaleByZoom(_vd
.dpi
.width
, vp
->zoom
);
2488 const int h
= UnScaleByZoom(_vd
.dpi
.height
, vp
->zoom
);
2491 /* Render base map. */
2492 do { // For each line
2494 uint colour_index
= colour_index_base
;
2495 colour_index_base
^= 2;
2496 uint32
*vp_map_line_ptr32
= _vp_map_line
;
2497 uint8
*vp_map_line_ptr8
= (uint8
*)_vp_map_line
;
2500 do { // For each pixel of a line
2502 *vp_map_line_ptr32
= ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2503 vp_map_line_ptr32
++;
2506 *vp_map_line_ptr8
= (uint8
)ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
2509 colour_index
= ++colour_index
& 3;
2514 blitter
->SetLine32(_vd
.dpi
.dst_ptr
, 0, j
, _vp_map_line
, w
);
2517 blitter
->SetLine(_vd
.dpi
.dst_ptr
, 0, j
, (uint8
*)_vp_map_line
, w
);
2522 /* Render tunnels */
2523 if (_settings_client
.gui
.show_tunnels_on_map
&& _vd
.tunnel_to_map
.Length() != 0) {
2524 const TunnelBridgeToMap
* const tbtm_end
= _vd
.tunnel_to_map
.End();
2525 for (const TunnelBridgeToMap
*tbtm
= _vd
.tunnel_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each tunnel
2526 const int tunnel_z
= GetTileZ(tbtm
->from_tile
) * TILE_HEIGHT
;
2527 const Point pt_from
= RemapCoords(TileX(tbtm
->from_tile
) * TILE_SIZE
, TileY(tbtm
->from_tile
) * TILE_SIZE
, tunnel_z
);
2528 const Point pt_to
= RemapCoords(TileX(tbtm
->to_tile
) * TILE_SIZE
, TileY(tbtm
->to_tile
) * TILE_SIZE
, tunnel_z
);
2530 /* check if tunnel is wholly outside redrawing area */
2531 const int x_from
= UnScaleByZoomLower(pt_from
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2532 const int x_to
= UnScaleByZoomLower(pt_to
.x
- _vd
.dpi
.left
, _vd
.dpi
.zoom
);
2533 if ((x_from
< 0 && x_to
< 0) || (x_from
> w
&& x_to
> w
)) continue;
2534 const int y_from
= UnScaleByZoomLower(pt_from
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2535 const int y_to
= UnScaleByZoomLower(pt_to
.y
- _vd
.dpi
.top
, _vd
.dpi
.zoom
);
2536 if ((y_from
< 0 && y_to
< 0) || (y_from
> h
&& y_to
> h
)) continue;
2538 ViewportMapDrawBridgeTunnel(vp
, tbtm
, tunnel_z
, true, w
, h
, blitter
);
2542 /* Render bridges */
2543 if (_settings_client
.gui
.show_bridges_on_map
&& _vd
.bridge_to_map
.Length() != 0) {
2544 const TunnelBridgeToMap
* const tbtm_end
= _vd
.bridge_to_map
.End();
2545 for (const TunnelBridgeToMap
*tbtm
= _vd
.bridge_to_map
.Begin(); tbtm
!= tbtm_end
; tbtm
++) { // For each bridge
2546 ViewportMapDrawBridgeTunnel(vp
, tbtm
, (GetBridgeHeight(tbtm
->from_tile
) - 1) * TILE_HEIGHT
, false, w
, h
, blitter
);
2551 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2553 DrawPixelInfo
*old_dpi
= _cur_dpi
;
2554 _cur_dpi
= &_vd
.dpi
;
2556 _vd
.dpi
.zoom
= vp
->zoom
;
2557 int mask
= ScaleByZoom(-1, vp
->zoom
);
2559 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
2561 _vd
.dpi
.width
= (right
- left
) & mask
;
2562 _vd
.dpi
.height
= (bottom
- top
) & mask
;
2563 _vd
.dpi
.left
= left
& mask
;
2564 _vd
.dpi
.top
= top
& mask
;
2565 _vd
.dpi
.pitch
= old_dpi
->pitch
;
2566 _vd
.last_child
= NULL
;
2568 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
2569 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
2571 _vd
.dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
2573 _dpi_for_text
= _vd
.dpi
;
2574 _dpi_for_text
.left
= UnScaleByZoom(_dpi_for_text
.left
, _dpi_for_text
.zoom
);
2575 _dpi_for_text
.top
= UnScaleByZoom(_dpi_for_text
.top
, _dpi_for_text
.zoom
);
2576 _dpi_for_text
.width
= UnScaleByZoom(_dpi_for_text
.width
, _dpi_for_text
.zoom
);
2577 _dpi_for_text
.height
= UnScaleByZoom(_dpi_for_text
.height
, _dpi_for_text
.zoom
);
2578 _dpi_for_text
.zoom
= ZOOM_LVL_NORMAL
;
2580 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
2581 /* Here the rendering is like smallmap. */
2582 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
2583 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<true, true>(vp
);
2584 else ViewportMapDraw
<true, false>(vp
);
2587 _pal2trsp_remap_ptr
= IsTransparencySet(TO_TREES
) ? GetNonSprite(GB(PALETTE_TO_TRANSPARENT
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1 : NULL
;
2588 if (_settings_client
.gui
.show_slopes_on_viewport_map
) ViewportMapDraw
<false, true>(vp
);
2589 else ViewportMapDraw
<false, false>(vp
);
2591 ViewportMapDrawVehicles(&_vd
.dpi
);
2592 if (_scrolling_viewport
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) ViewportMapDrawScrollingViewportBox(vp
);
2593 if (vp
->zoom
< ZOOM_LVL_OUT_256X
) ViewportAddTownNames(&_vd
.dpi
);
2596 /* Classic rendering. */
2597 ViewportAddLandscape();
2598 ViewportAddVehicles(&_vd
.dpi
);
2600 ViewportAddTownNames(&_vd
.dpi
);
2601 ViewportAddStationNames(&_vd
.dpi
);
2602 ViewportAddSigns(&_vd
.dpi
);
2604 DrawTextEffects(&_vd
.dpi
);
2606 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
2608 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
2609 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
2610 *_vd
.parent_sprites_to_sort
.Append() = it
;
2613 _vp_sprite_sorter(&_vd
.parent_sprites_to_sort
);
2614 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
2616 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
2618 if (_draw_dirty_blocks
) ViewportDrawDirtyBlocks();
2620 DrawPixelInfo dp
= _vd
.dpi
;
2621 ZoomLevel zoom
= _vd
.dpi
.zoom
;
2622 dp
.zoom
= ZOOM_LVL_NORMAL
;
2623 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
2624 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
2627 if (vp
->overlay
!= NULL
&& vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
2628 /* translate to window coordinates */
2631 vp
->overlay
->Draw(&dp
);
2634 if (_settings_client
.gui
.show_vehicle_route
) ViewportMapDrawVehicleRoute(vp
);
2635 if (_vd
.string_sprites_to_draw
.Length() != 0) {
2636 /* translate to world coordinates */
2637 dp
.left
= UnScaleByZoom(_vd
.dpi
.left
, zoom
);
2638 dp
.top
= UnScaleByZoom(_vd
.dpi
.top
, zoom
);
2639 ViewportDrawStrings(zoom
, &_vd
.string_sprites_to_draw
);
2641 if (_settings_client
.gui
.show_vehicle_route_steps
) ViewportDrawVehicleRouteSteps(vp
);
2642 ViewportDrawPlans(vp
);
2646 _vd
.bridge_to_map
.Clear();
2647 _vd
.string_sprites_to_draw
.Clear();
2648 _vd
.tile_sprites_to_draw
.Clear();
2649 _vd
.parent_sprites_to_draw
.Clear();
2650 _vd
.parent_sprites_to_sort
.Clear();
2651 _vd
.child_screen_sprites_to_draw
.Clear();
2655 * Make sure we don't draw a too big area at a time.
2656 * If we do, the sprite sorter will run into major performance problems and the sprite memory may overflow.
2658 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2660 if ((vp
->zoom
< ZOOM_LVL_DRAW_MAP
) && (ScaleByZoom(bottom
- top
, vp
->zoom
) * ScaleByZoom(right
- left
, vp
->zoom
) > 180000 * ZOOM_LVL_BASE
* ZOOM_LVL_BASE
)) {
2661 if ((bottom
- top
) > (right
- left
)) {
2662 int t
= (top
+ bottom
) >> 1;
2663 ViewportDrawChk(vp
, left
, top
, right
, t
);
2664 ViewportDrawChk(vp
, left
, t
, right
, bottom
);
2666 int t
= (left
+ right
) >> 1;
2667 ViewportDrawChk(vp
, left
, top
, t
, bottom
);
2668 ViewportDrawChk(vp
, t
, top
, right
, bottom
);
2672 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2673 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
2674 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
2675 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
2680 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
2682 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
2684 if (left
>= vp
->left
+ vp
->width
) return;
2686 if (left
< vp
->left
) left
= vp
->left
;
2687 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
2689 if (top
>= vp
->top
+ vp
->height
) return;
2691 if (top
< vp
->top
) top
= vp
->top
;
2692 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
2694 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
2698 * Draw the viewport of this window.
2700 void Window::DrawViewport() const
2702 DrawPixelInfo
*dpi
= _cur_dpi
;
2704 dpi
->left
+= this->left
;
2705 dpi
->top
+= this->top
;
2707 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
2709 dpi
->left
-= this->left
;
2710 dpi
->top
-= this->top
;
2714 * Continue criteria for the SearchMapEdge function.
2715 * @param iter Value to check.
2716 * @param iter_limit Maximum value for the iter
2717 * @param sy Screen y coordinate calculated for the tile at hand
2718 * @param sy_limit Limit to the screen y coordinate
2719 * @return True when we should continue searching.
2721 typedef bool ContinueMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
);
2723 /** Continue criteria for searching a no-longer-visible tile in negative direction, starting at some tile. */
2724 static inline bool ContinueLowerMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
> 0 && sy
> sy_limit
; }
2725 /** Continue criteria for searching a no-longer-visible tile in positive direction, starting at some tile. */
2726 static inline bool ContinueUpperMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
< iter_limit
&& sy
< sy_limit
; }
2729 * Searches, starting at the given tile, by applying the given offset to iter, for a no longer visible tile.
2730 * The whole sense of this function is keeping the to-be-written code small, thus it is a little bit abstracted
2731 * so the same function can be used for both the X and Y locations. As such a reference to one of the elements
2732 * in curr_tile was needed.
2733 * @param curr_tile A tile
2734 * @param iter Reference to either the X or Y of curr_tile.
2735 * @param iter_limit Upper search limit for the iter value.
2736 * @param offset Search in steps of this size
2737 * @param sy_limit Search limit to be passed to the criteria
2738 * @param continue_criteria Search as long as this criteria is true
2739 * @return The final value of iter.
2741 static int SearchMapEdge(Point
&curr_tile
, int &iter
, int iter_limit
, int offset
, int sy_limit
, ContinueMapEdgeSearch continue_criteria
)
2745 iter
= Clamp(iter
+ offset
, 0, iter_limit
);
2746 sy
= GetViewportY(curr_tile
);
2747 } while (continue_criteria(iter
, iter_limit
, sy
, sy_limit
));
2753 * Determine the clamping of either the X or Y coordinate to the map.
2754 * @param curr_tile A tile
2755 * @param iter Reference to either the X or Y of curr_tile.
2756 * @param iter_limit Upper search limit for the iter value.
2757 * @param start Start value for the iteration.
2758 * @param other_ref Reference to the opposite axis in curr_tile than of iter.
2759 * @param other_value Start value for of the opposite axis
2760 * @param vp_value Value of the viewport location in the opposite axis as for iter.
2761 * @param other_limit Limit for the other value, so if iter is X, then other_limit is for Y.
2762 * @param vp_top Top of the viewport.
2763 * @param vp_bottom Bottom of the viewport.
2764 * @return Clamped version of vp_value.
2766 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
)
2768 bool upper_edge
= other_value
< _settings_game
.construction
.max_heightlevel
/ 4;
2771 * First get an estimate of the tiles relevant for us at that edge. Relevant in the sense
2772 * "at least close to the visible area". Thus, we don't look at exactly each tile, inspecting
2773 * e.g. every tenth should be enough. After all, the desired screen limit is set such that
2774 * the bordermost tiles are painted in the middle of the screen when one hits the limit,
2775 * i.e. it is no harm if there is some small error in that calculation
2778 other_ref
= upper_edge
? 0 : other_limit
;
2780 int min_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? -10 : +10, vp_top
, upper_edge
? ContinueLowerMapEdgeSearch
: ContinueUpperMapEdgeSearch
);
2782 int max_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? +10 : -10, vp_bottom
, upper_edge
? ContinueUpperMapEdgeSearch
: ContinueLowerMapEdgeSearch
);
2784 max_iter
= min(max_iter
+ _settings_game
.construction
.max_heightlevel
/ 4, iter_limit
);
2785 min_iter
= min(min_iter
, max_iter
);
2787 /* Now, calculate the highest heightlevel of these tiles. Again just as an estimate. */
2788 int max_heightlevel_at_edge
= 0;
2789 for (iter
= min_iter
; iter
<= max_iter
; iter
+= 10) {
2790 max_heightlevel_at_edge
= max(max_heightlevel_at_edge
, (int)TileHeight(TileXY(curr_tile
.x
, curr_tile
.y
)));
2793 /* Based on that heightlevel, calculate the limit. For the upper edge a tile with height zero would
2794 * get a limit of zero, on the other side it depends on the number of tiles along the axis. */
2796 max(vp_value
, -max_heightlevel_at_edge
* (int)(TILE_HEIGHT
* 2 * ZOOM_LVL_BASE
)) :
2797 min(vp_value
, (other_limit
* TILE_SIZE
* 4 - max_heightlevel_at_edge
* TILE_HEIGHT
* 2) * ZOOM_LVL_BASE
);
2800 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
2804 /* Centre of the viewport is hot spot */
2805 x
+= vp
->virtual_width
/ 2;
2806 y
+= vp
->virtual_height
/ 2;
2808 /* Convert viewport coordinates to map coordinates
2809 * Calculation is scaled by 4 to avoid rounding errors */
2810 int vx
= -x
+ y
* 2;
2813 /* Find out which tile corresponds to (vx,vy) if one assumes height zero. The cast is necessary to prevent C++ from
2814 * converting the result to an uint, which gives an overflow instead of a negative result... */
2815 int tx
= vx
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2816 int ty
= vy
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
2819 vx
= ClampXYToMap(curr_tile
, curr_tile
.y
, MapMaxY(), ty
, curr_tile
.x
, tx
, vx
, MapMaxX(), original_y
, original_y
+ vp
->virtual_height
);
2820 vy
= ClampXYToMap(curr_tile
, curr_tile
.x
, MapMaxX(), tx
, curr_tile
.y
, ty
, vy
, MapMaxY(), original_y
, original_y
+ vp
->virtual_height
);
2822 /* Convert map coordinates to viewport coordinates */
2826 /* Remove centering */
2827 x
-= vp
->virtual_width
/ 2;
2828 y
-= vp
->virtual_height
/ 2;
2832 * Update the viewport position being displayed.
2833 * @param w %Window owning the viewport.
2835 void UpdateViewportPosition(Window
*w
)
2837 const ViewPort
*vp
= w
->viewport
;
2839 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
2840 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
2841 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
2843 w
->viewport
->scrollpos_x
= pt
.x
;
2844 w
->viewport
->scrollpos_y
= pt
.y
;
2845 SetViewportPosition(w
, pt
.x
, pt
.y
);
2847 /* Ensure the destination location is within the map */
2848 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
2850 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
2851 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
2853 bool update_overlay
= false;
2854 if (delta_x
!= 0 || delta_y
!= 0) {
2855 if (_settings_client
.gui
.smooth_scroll
) {
2856 int max_scroll
= ScaleByMapSize1D(512 * ZOOM_LVL_BASE
);
2857 /* Not at our desired position yet... */
2858 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
2859 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
2861 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
2862 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
2864 update_overlay
= (w
->viewport
->scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
2865 w
->viewport
->scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
2868 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2870 if (_scrolling_viewport
== w
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) {
2871 const int gap
= ScaleByZoom(1, ZOOM_LVL_MAX
);
2873 int lr_low
= vp
->virtual_left
;
2874 int lr_hi
= w
->viewport
->scrollpos_x
;
2875 if (lr_low
> lr_hi
) Swap(lr_low
, lr_hi
);
2876 int right
= lr_hi
+ vp
->virtual_width
+ gap
;
2878 int tb_low
= vp
->virtual_top
;
2879 int tb_hi
= w
->viewport
->scrollpos_y
;
2880 if (tb_low
> tb_hi
) Swap(tb_low
, tb_hi
);
2881 int bottom
= tb_hi
+ vp
->virtual_height
+ gap
;
2883 MarkAllViewportMapsDirty(lr_low
, tb_low
, right
, bottom
);
2886 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
2887 if (update_overlay
) RebuildViewportOverlay(w
);
2892 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
2893 * @param vp The viewport to mark as dirty
2894 * @param left Left edge of area to repaint
2895 * @param top Top edge of area to repaint
2896 * @param right Right edge of area to repaint
2897 * @param bottom Bottom edge of area to repaint
2900 static void MarkViewportDirty(const ViewPort
* const vp
, int left
, int top
, int right
, int bottom
)
2902 /* Rounding wrt. zoom-out level */
2903 right
+= (1 << vp
->zoom
) - 1;
2904 bottom
+= (1 << vp
->zoom
) - 1;
2906 right
-= vp
->virtual_left
;
2907 if (right
<= 0) return;
2909 bottom
-= vp
->virtual_top
;
2910 if (bottom
<= 0) return;
2912 left
= max(0, left
- vp
->virtual_left
);
2914 if (left
>= vp
->virtual_width
) return;
2916 top
= max(0, top
- vp
->virtual_top
);
2918 if (top
>= vp
->virtual_height
) return;
2921 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
2922 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
2923 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
2924 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
2929 * Mark all viewports that display an area as dirty (in need of repaint).
2930 * @param left Left edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2931 * @param top Top edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2932 * @param right Right edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2933 * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
2934 * @param mark_dirty_if_zoomlevel_is_below To tell if an update is relevant or not (for example, animations in map mode are not)
2937 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
)
2939 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2940 if (vp
->zoom
>= mark_dirty_if_zoomlevel_is_below
) continue;
2941 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
2945 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
)
2947 const uint size
= cit
->second
.size() > max_rank_order_type_count
? 1 : cit
->second
.size();
2948 MarkRouteStepDirty(cit
->first
, size
);
2951 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
)
2953 assert(tile
!= INVALID_TILE
);
2954 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2955 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
2956 for (const ViewPort
* const vp
: _viewport_window_cache
) {
2957 const int half_width
= ScaleByZoom((_vp_route_step_width
/ 2) + 1, vp
->zoom
);
2958 const int height
= ScaleByZoom(_vp_route_step_height_top
+ char_height
* order_nr
+ _vp_route_step_height_bottom
, vp
->zoom
);
2959 MarkViewportDirty(vp
, pt
.x
- half_width
, pt
.y
- height
, pt
.x
+ half_width
, pt
.y
);
2963 void MarkAllRouteStepsDirty(const Vehicle
*veh
)
2965 ViewportPrepareVehicleRouteSteps(veh
);
2966 for (RouteStepsMap::const_iterator cit
= _vp_route_steps
.begin(); cit
!= _vp_route_steps
.end(); cit
++) {
2967 MarkRouteStepDirty(cit
);
2969 _vp_route_steps_last_mark_dirty
.swap(_vp_route_steps
);
2970 _vp_route_steps
.clear();
2974 * Mark all viewports in map mode that display an area as dirty (in need of repaint).
2975 * @param left Left edge of area to repaint
2976 * @param top Top edge of area to repaint
2977 * @param right Right edge of area to repaint
2978 * @param bottom Bottom edge of area to repaint
2981 void MarkAllViewportMapsDirty(int left
, int top
, int right
, int bottom
)
2984 FOR_ALL_WINDOWS_FROM_BACK(w
) {
2985 const ViewPort
*vp
= w
->viewport
;
2986 if (vp
!= NULL
&& vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
2987 assert(vp
->width
!= 0);
2988 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
2993 void ConstrainAllViewportsZoom()
2996 FOR_ALL_WINDOWS_FROM_FRONT(w
) {
2997 if (w
->viewport
== NULL
) continue;
2999 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
3000 if (zoom
!= w
->viewport
->zoom
) {
3001 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
3002 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
3008 * Mark a tile given by its index dirty for repaint.
3009 * @param tile The tile to mark dirty.
3010 * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
3013 void MarkTileDirtyByTile(const TileIndex tile
, const ZoomLevel mark_dirty_if_zoomlevel_is_below
, int bridge_level_offset
)
3015 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, TilePixelHeight(tile
));
3016 MarkAllViewportsDirty(
3017 pt
.x
- MAX_TILE_EXTENT_LEFT
,
3018 pt
.y
- MAX_TILE_EXTENT_TOP
- ZOOM_LVL_BASE
* TILE_HEIGHT
* bridge_level_offset
,
3019 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
3020 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
,
3021 mark_dirty_if_zoomlevel_is_below
);
3025 * Mark a (virtual) tile outside the map dirty for repaint.
3026 * @param x Tile X position.
3027 * @param y Tile Y position.
3030 void MarkTileDirtyByTileOutsideMap(int x
, int y
)
3032 Point pt
= RemapCoords(x
* TILE_SIZE
, y
* TILE_SIZE
, TilePixelHeightOutsideMap(x
, y
));
3033 MarkAllViewportsDirty(
3034 pt
.x
- MAX_TILE_EXTENT_LEFT
,
3035 pt
.y
, // no buildings outside of map
3036 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
3037 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
);
3040 void MarkTileLineDirty(const TileIndex from_tile
, const TileIndex to_tile
)
3042 assert(from_tile
!= INVALID_TILE
);
3043 assert(to_tile
!= INVALID_TILE
);
3045 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3046 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
3048 const int block_radius
= 20;
3050 int x1
= from_pt
.x
/ block_radius
;
3051 int y1
= from_pt
.y
/ block_radius
;
3052 const int x2
= to_pt
.x
/ block_radius
;
3053 const int y2
= to_pt
.y
/ block_radius
;
3055 /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification */
3056 const int dx
= abs(x2
- x1
);
3057 const int dy
= abs(y2
- y1
);
3058 const int sx
= (x1
< x2
) ? 1 : -1;
3059 const int sy
= (y1
< y2
) ? 1 : -1;
3062 MarkAllViewportsDirty(
3063 (x1
- 1) * block_radius
,
3064 (y1
- 1) * block_radius
,
3065 (x1
+ 1) * block_radius
,
3066 (y1
+ 1) * block_radius
,
3069 if (x1
== x2
&& y1
== y2
) break;
3070 const int e2
= 2 * err
;
3082 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
)
3084 for (std::vector
<DrawnPathRouteTileLine
>::const_iterator it
= lines
.begin(); it
!= lines
.end(); ++it
) {
3085 MarkTileLineDirty(it
->from_tile
, it
->to_tile
);
3089 void MarkAllRoutePathsDirty(const Vehicle
*veh
)
3091 switch (_settings_client
.gui
.show_vehicle_route
) {
3096 ViewportMapPrepareVehicleRoute(veh
);
3100 for (const auto &iter
: _vp_route_paths
) {
3101 MarkTileLineDirty(iter
.from_tile
, iter
.to_tile
);
3104 _vp_route_paths_last_mark_dirty
.swap(_vp_route_paths
);
3105 _vp_route_paths
.clear();
3109 * Mark all tiles in a given bridge by their index dirty for repaint.
3110 * @param tile The start tile of the bridge to mark dirty.
3111 * @param tile The end tile of the bridge to mark dirty.
3114 void MarkBridgeTilesDirtyByTile(TileIndex tile_start
, TileIndex tile_end
)
3116 MarkTileDirtyByTile(tile_start
);
3117 MarkTileDirtyByTile(tile_end
);
3120 if (TileX(tile_start
) == TileX(tile_end
)) {
3122 } else if (TileY(tile_start
) == TileY(tile_end
)) {
3126 if (tile_end
< tile_start
) Swap(tile_start
, tile_end
);
3128 TileIndexDiff delta
= (direction
== AXIS_X
? TileDiffXY(1, 0) : TileDiffXY(0, 1));
3129 for (TileIndex tile
= tile_start
; tile
<= tile_end
; tile
+= delta
) {
3130 MarkTileDirtyByTile(tile
);
3134 void CheckMarkDirtyFocusedRoutePaths(const Vehicle
*veh
)
3136 const Vehicle
*focused_veh
= GetVehicleFromWindow(_focused_window
);
3137 if (focused_veh
&& veh
== focused_veh
) {
3138 MarkAllRoutePathsDirty(veh
);
3139 MarkAllRouteStepsDirty(veh
);
3144 * Marks the selected tiles as dirty.
3146 * This function marks the selected tiles as dirty for repaint
3150 static void SetSelectionTilesDirty()
3152 int x_size
= _thd
.size
.x
;
3153 int y_size
= _thd
.size
.y
;
3155 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
3156 int x_start
= _thd
.pos
.x
;
3157 int y_start
= _thd
.pos
.y
;
3159 if (_thd
.outersize
.x
!= 0 || _thd
.outersize
.y
!= 0) {
3160 x_size
+= _thd
.outersize
.x
;
3161 x_start
+= _thd
.offs
.x
;
3162 y_size
+= _thd
.outersize
.y
;
3163 y_start
+= _thd
.offs
.y
;
3166 x_size
-= TILE_SIZE
;
3167 y_size
-= TILE_SIZE
;
3169 assert(x_size
>= 0);
3170 assert(y_size
>= 0);
3172 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3173 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3175 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
3176 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
3178 /* make sure everything is multiple of TILE_SIZE */
3179 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
3182 * Suppose we have to mark dirty rectangle of 3x4 tiles:
3189 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
3199 int top_x
= x_end
; // coordinates of top dirty tile
3200 int top_y
= y_start
;
3201 int bot_x
= top_x
; // coordinates of bottom dirty tile
3205 /* topmost dirty point */
3206 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
3207 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxPixelZ(top_tile
));
3209 /* bottommost point */
3210 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
3211 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTilePixelZ(bottom_tile
)); // bottommost point
3213 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
3214 * tile height/slope affects only the 'y' on-screen coordinate! */
3216 int l
= top
.x
- TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of left side of the dirty rectangle
3217 int t
= top
.y
; // 'y' coordinate of top side of the dirty rectangle
3218 int r
= top
.x
+ TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of right side of the dirty rectangle
3219 int b
= bot
.y
; // 'y' coordinate of bottom side of the dirty rectangle
3221 static const int OVERLAY_WIDTH
= 4 * ZOOM_LVL_BASE
; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
3223 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
3224 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_LVL_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
);
3226 /* haven't we reached the topmost tile yet? */
3227 if (top_x
!= x_start
) {
3233 /* the way the bottom tile changes is different when we reach the bottommost tile */
3234 if (bot_y
!= y_end
) {
3239 } while (bot_x
>= top_x
);
3240 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
3241 /* a_size, b_size describe a rectangle with rotated coordinates */
3242 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
3244 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3245 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
3247 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
3248 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
3249 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
3250 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
3252 if (x
< MapMaxX() && y
< MapMaxY()) {
3253 MarkTileDirtyByTile(TileXY(x
, y
));
3261 void SetSelectionRed(bool b
)
3263 _thd
.make_square_red
= b
;
3264 SetSelectionTilesDirty();
3268 * Test whether a sign is below the mouse
3269 * @param vp the clicked viewport
3270 * @param x X position of click
3271 * @param y Y position of click
3272 * @param sign the sign to check
3273 * @return true if the sign was hit
3275 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
3277 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_16X
);
3278 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
3279 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
3281 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
3282 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
3284 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
3285 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
3288 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
3290 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
3294 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) {
3295 ShowTownViewWindow(t
->index
);
3303 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
3305 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
3307 const BaseStation
*st
;
3308 FOR_ALL_BASE_STATIONS(st
) {
3309 if (!IsStationSignVisible(st
)) continue;
3311 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3312 if (Station::IsExpected(st
)) {
3313 ShowStationViewWindow(st
->index
);
3315 ShowWaypointWindow(Waypoint::From(st
));
3325 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
3327 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
3328 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
3332 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
3333 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
3334 if (si
->owner
== OWNER_DEITY
&& _game_mode
!= GM_EDITOR
) continue;
3336 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
3337 HandleClickOnSign(si
);
3346 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
3348 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
3350 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3351 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3353 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
3357 static void PlaceObject()
3362 pt
= GetTileBelowCursor();
3363 if (pt
.x
== -1) return;
3365 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
3366 pt
.x
+= TILE_SIZE
/ 2;
3367 pt
.y
+= TILE_SIZE
/ 2;
3370 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
3371 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
3373 w
= _thd
.GetCallbackWnd();
3374 if (w
!= NULL
) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
3377 bool HandleViewportDoubleClicked(Window
*w
, int x
, int y
)
3379 ViewPort
*vp
= w
->viewport
;
3380 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return false;
3382 switch (_settings_client
.gui
.action_when_viewport_map_is_dblclicked
) {
3383 case 0: // Do nothing
3385 case 1: // Zoom in main viewport
3386 while (vp
->zoom
!= ZOOM_LVL_VIEWPORT
)
3387 ZoomInOrOutToCursorWindow(true, w
);
3389 case 2: // Open an extra viewport
3390 ShowExtraViewPortWindowForTileUnderCursor();
3397 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
, bool double_click
)
3399 /* No click in smallmap mode except for plan making. */
3400 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !(_thd
.place_mode
== HT_POINT
&& _thd
.select_proc
== DDSP_DRAW_PLANLINE
)) return true;
3402 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
3404 if (_thd
.place_mode
& HT_VEHICLE
) {
3405 if (v
!= NULL
&& VehicleClicked(v
)) return true;
3408 /* Vehicle placement mode already handled above. */
3409 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3410 if (_thd
.place_mode
& HT_POLY
) {
3411 /* In polyline mode double-clicking on a single white line, finishes current polyline.
3412 * If however the user double-clicks on a line that has a white and a blue section,
3413 * both lines (white and blue) will be constructed consecutively. */
3414 static bool stop_snap_on_double_click
= false;
3415 if (double_click
&& stop_snap_on_double_click
) {
3416 SetRailSnapMode(RSM_NO_SNAP
);
3419 stop_snap_on_double_click
= !(_thd
.drawstyle
& HT_LINE
) || (_thd
.dir2
== HT_DIR_END
);
3426 if (CheckClickOnTown(vp
, x
, y
)) return true;
3427 if (CheckClickOnStation(vp
, x
, y
)) return true;
3428 if (CheckClickOnSign(vp
, x
, y
)) return true;
3429 bool result
= CheckClickOnLandscape(vp
, x
, y
);
3432 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
3433 if (IsCompanyBuildableVehicleType(v
)) {
3435 WindowClass wc
= _thd
.GetCallbackWnd()->window_class
;
3436 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
3437 StartStopVehicle(v
, true);
3438 } else if ( wc
!= WC_CREATE_TEMPLATE
&& wc
!= WC_TEMPLATEGUI_MAIN
) {
3439 ShowVehicleViewWindow(v
);
3447 void HandleViewportToolTip(Window
*w
, int x
, int y
)
3449 const ViewportData
*vp
= w
->viewport
;
3450 if (vp
== NULL
|| _game_mode
== GM_MENU
|| HasModalProgress()) return;
3452 TooltipCloseCondition close_cond
= (_settings_client
.gui
.hover_delay_ms
== 0) ? TCC_RIGHT_CLICK
: TCC_HOVER
;
3454 const BaseStation
*st
;
3455 FOR_ALL_BASE_STATIONS(st
) {
3456 if (IsStationSignVisible(st
) && CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
3457 char tip
[DRAW_STRING_BUFFER
] = "";
3458 GetTileTooltipsForStation(st
->index
, tip
, lastof(tip
));
3459 GuiShowTooltips(w
, tip
, close_cond
);
3464 Point tile
= TranslateXYToTileCoord(vp
, x
, y
, false);
3465 if (IsInsideMM(tile
.x
, 0, MapMaxX() * TILE_SIZE
) && IsInsideMM(tile
.y
, 0, MapMaxY() * TILE_SIZE
)) {
3466 GuiShowTooltipsForTile(w
, TileVirtXY(tile
.x
, tile
.y
), close_cond
);
3470 DeleteWindowById(WC_TOOLTIPS
, 0); // close old tooltips window as now hovering this viewport
3473 void RebuildViewportOverlay(Window
*w
)
3475 if (w
->viewport
->overlay
!= NULL
&&
3476 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
3477 w
->viewport
->overlay
->GetCargoMask() != 0) {
3478 w
->viewport
->overlay
->RebuildCache();
3484 * Scrolls the viewport in a window to a given location.
3485 * @param x Desired x location of the map to scroll to (world coordinate).
3486 * @param y Desired y location of the map to scroll to (world coordinate).
3487 * @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.
3488 * @param w %Window containing the viewport.
3489 * @param instant Jump to the location instead of slowly moving to it.
3490 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3492 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
3494 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
3496 if ( x
>= 0 && x
<= (int)MapSizeX() * (int)TILE_SIZE
- 1
3497 && y
>= 0 && y
<= (int)MapSizeY() * (int)TILE_SIZE
- 1) {
3498 z
= GetSlopePixelZ(x
, y
);
3500 z
= TileHeightOutsideMap(x
/ (int)TILE_SIZE
, y
/ (int)TILE_SIZE
);
3504 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
3505 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
3507 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
3510 w
->viewport
->scrollpos_x
= pt
.x
;
3511 w
->viewport
->scrollpos_y
= pt
.y
;
3512 RebuildViewportOverlay(w
);
3515 w
->viewport
->dest_scrollpos_x
= pt
.x
;
3516 w
->viewport
->dest_scrollpos_y
= pt
.y
;
3521 * Scrolls the viewport in a window to a given location.
3522 * @param tile Desired tile to center on.
3523 * @param w %Window containing the viewport.
3524 * @param instant Jump to the location instead of slowly moving to it.
3525 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3527 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
3529 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
3533 * Scrolls the viewport of the main window to a given location.
3534 * @param tile Desired tile to center on.
3535 * @param instant Jump to the location instead of slowly moving to it.
3536 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
3538 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
3540 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
3544 * Set a tile to display a red error square.
3545 * @param tile Tile that should show the red error square.
3547 void SetRedErrorSquare(TileIndex tile
)
3555 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
3556 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
, ZOOM_LVL_DRAW_MAP
);
3561 * Highlight \a w by \a h tiles at the cursor.
3562 * @param w Width of the highlighted tiles rectangle.
3563 * @param h Height of the highlighted tiles rectangle.
3565 void SetTileSelectSize(int w
, int h
)
3567 _thd
.new_size
.x
= w
* TILE_SIZE
;
3568 _thd
.new_size
.y
= h
* TILE_SIZE
;
3569 _thd
.new_outersize
.x
= 0;
3570 _thd
.new_outersize
.y
= 0;
3573 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
3575 _thd
.new_offs
.x
= ox
* TILE_SIZE
;
3576 _thd
.new_offs
.y
= oy
* TILE_SIZE
;
3577 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
3578 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
3581 /** returns the best autorail highlight type from map coordinates */
3582 static HighLightStyle
GetAutorailHT(int x
, int y
)
3584 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
3588 * Reset tile highlighting.
3590 void TileHighlightData::Reset()
3594 this->new_pos
.x
= 0;
3595 this->new_pos
.y
= 0;
3599 * Is the user dragging a 'diagonal rectangle'?
3600 * @return User is dragging a rotated rectangle.
3602 bool TileHighlightData::IsDraggingDiagonal()
3604 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
3608 * Get the window that started the current highlighting.
3609 * @return The window that requested the current tile highlighting, or \c NULL if not available.
3611 Window
*TileHighlightData::GetCallbackWnd()
3613 return FindWindowById(this->window_class
, this->window_number
);
3616 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
);
3618 static inline void CalcNewPolylineOutersize()
3620 /* use the 'outersize' to mark the second (blue) part of a polyline selection */
3621 if (_thd
.dir2
< HT_DIR_END
) {
3622 /* get bounds of the second part */
3623 int outer_x1
= _thd
.selstart2
.x
& ~TILE_UNIT_MASK
;
3624 int outer_y1
= _thd
.selstart2
.y
& ~TILE_UNIT_MASK
;
3625 int outer_x2
= _thd
.selend2
.x
& ~TILE_UNIT_MASK
;
3626 int outer_y2
= _thd
.selend2
.y
& ~TILE_UNIT_MASK
;
3627 if (outer_x1
> outer_x2
) Swap(outer_x1
, outer_x2
);
3628 if (outer_y1
> outer_y2
) Swap(outer_y1
, outer_y2
);
3629 /* include the first part */
3630 outer_x1
= min
<int>(outer_x1
, _thd
.new_pos
.x
);
3631 outer_y1
= min
<int>(outer_y1
, _thd
.new_pos
.y
);
3632 outer_x2
= max
<int>(outer_x2
, _thd
.new_pos
.x
+ _thd
.new_size
.x
- TILE_SIZE
);
3633 outer_y2
= max
<int>(outer_y2
, _thd
.new_pos
.y
+ _thd
.new_size
.y
- TILE_SIZE
);
3634 /* write new values */
3635 _thd
.new_offs
.x
= outer_x1
- _thd
.new_pos
.x
;
3636 _thd
.new_offs
.y
= outer_y1
- _thd
.new_pos
.y
;
3637 _thd
.new_outersize
.x
= outer_x2
- outer_x1
+ TILE_SIZE
- _thd
.new_size
.x
;
3638 _thd
.new_outersize
.y
= outer_y2
- outer_y1
+ TILE_SIZE
- _thd
.new_size
.y
;
3640 _thd
.new_offs
.x
= 0;
3641 _thd
.new_offs
.y
= 0;
3642 _thd
.new_outersize
.x
= 0;
3643 _thd
.new_outersize
.y
= 0;
3648 * Updates tile highlighting for all cases.
3649 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
3650 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
3651 * Once for the old and once for the new selection.
3652 * _thd is TileHighlightData, found in viewport.h
3654 void UpdateTileSelection()
3659 HighLightStyle new_drawstyle
= HT_NONE
;
3660 bool new_diagonal
= false;
3662 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
3666 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3667 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3668 x1
&= ~TILE_UNIT_MASK
;
3669 y1
&= ~TILE_UNIT_MASK
;
3671 if (_thd
.IsDraggingDiagonal()) {
3672 new_diagonal
= true;
3674 if (x1
>= x2
) Swap(x1
, x2
);
3675 if (y1
>= y2
) Swap(y1
, y2
);
3677 _thd
.new_pos
.x
= x1
;
3678 _thd
.new_pos
.y
= y1
;
3679 _thd
.new_size
.x
= x2
- x1
;
3680 _thd
.new_size
.y
= y2
- y1
;
3681 if (!new_diagonal
) {
3682 _thd
.new_size
.x
+= TILE_SIZE
;
3683 _thd
.new_size
.y
+= TILE_SIZE
;
3685 new_drawstyle
= _thd
.next_drawstyle
;
3687 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
3688 Point pt
= GetTileBelowCursor();
3692 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
3694 new_drawstyle
= HT_RECT
;
3697 new_drawstyle
= HT_POINT
;
3698 x1
+= TILE_SIZE
/ 2;
3699 y1
+= TILE_SIZE
/ 2;
3704 if (_thd
.place_mode
& HT_POLY
) {
3705 RailSnapMode snap_mode
= GetRailSnapMode();
3706 if (snap_mode
== RSM_NO_SNAP
||
3707 (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
))) {
3708 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3709 _thd
.new_offs
.x
= 0;
3710 _thd
.new_offs
.y
= 0;
3711 _thd
.new_outersize
.x
= 0;
3712 _thd
.new_outersize
.y
= 0;
3713 _thd
.dir2
= HT_DIR_END
;
3715 new_drawstyle
= CalcPolyrailDrawstyle(pt
, false);
3716 if (new_drawstyle
!= HT_NONE
) {
3717 x1
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
3718 y1
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
3719 int x2
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
3720 int y2
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
3721 if (x1
> x2
) Swap(x1
, x2
);
3722 if (y1
> y2
) Swap(y1
, y2
);
3723 _thd
.new_pos
.x
= x1
;
3724 _thd
.new_pos
.y
= y1
;
3725 _thd
.new_size
.x
= x2
- x1
+ TILE_SIZE
;
3726 _thd
.new_size
.y
= y2
- y1
+ TILE_SIZE
;
3732 if (_thd
.place_mode
& HT_RAIL
) {
3733 /* Draw one highlighted tile in any direction */
3734 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
3738 switch (_thd
.place_mode
& HT_DIR_MASK
) {
3739 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
3740 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
3744 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
3749 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
3752 default: NOT_REACHED();
3754 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
3755 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
3763 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
3764 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
3768 if (new_drawstyle
& HT_LINE
) CalcNewPolylineOutersize();
3770 /* redraw selection */
3771 if (_thd
.drawstyle
!= new_drawstyle
||
3772 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
3773 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
3774 _thd
.offs
.x
!= _thd
.new_offs
.x
|| _thd
.offs
.y
!= _thd
.new_offs
.y
||
3775 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
3776 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
3777 _thd
.diagonal
!= new_diagonal
) {
3778 /* Clear the old tile selection? */
3779 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3781 _thd
.drawstyle
= new_drawstyle
;
3782 _thd
.pos
= _thd
.new_pos
;
3783 _thd
.size
= _thd
.new_size
;
3784 _thd
.offs
= _thd
.new_offs
;
3785 _thd
.outersize
= _thd
.new_outersize
;
3786 _thd
.diagonal
= new_diagonal
;
3789 /* Draw the new tile selection? */
3790 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3795 * Displays the measurement tooltips when selecting multiple tiles
3796 * @param str String to be displayed
3797 * @param close_cond Condition for closing this tooltip.
3799 static inline void ShowMeasurementTooltips(StringID str
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
3801 if (!_settings_client
.gui
.measure_tooltip
) return;
3802 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, close_cond
);
3805 /** highlighting tiles while only going over them with the mouse */
3806 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
3808 _thd
.select_method
= method
;
3809 _thd
.select_proc
= process
;
3810 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
3811 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
3812 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
3813 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
3815 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
3816 * In effect, placement starts from the centre of a tile
3818 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
3819 _thd
.selend
.x
+= TILE_SIZE
/ 2;
3820 _thd
.selend
.y
+= TILE_SIZE
/ 2;
3821 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
3822 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
3825 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
3826 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
3827 _thd
.place_mode
= HT_SPECIAL
| others
;
3828 _thd
.next_drawstyle
= HT_RECT
| others
;
3829 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
3830 _thd
.place_mode
= HT_SPECIAL
| others
;
3831 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
3832 _current_snap_lock
.x
= -1;
3833 if ((_thd
.place_mode
& HT_POLY
) != 0 && GetRailSnapMode() == RSM_NO_SNAP
) {
3834 SetRailSnapMode(RSM_SNAP_TO_TILE
);
3835 SetRailSnapTile(tile
);
3838 _thd
.place_mode
= HT_SPECIAL
| others
;
3839 _thd
.next_drawstyle
= HT_POINT
| others
;
3841 _special_mouse_mode
= WSM_SIZING
;
3844 void VpSetPlaceSizingLimit(int limit
)
3846 _thd
.sizelimit
= limit
;
3850 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
3851 * @param from TileIndex of the first tile to highlight
3852 * @param to TileIndex of the last tile to highlight
3854 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
3856 uint64 distance
= DistanceManhattan(from
, to
) + 1;
3858 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
3859 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
3860 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
3861 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
3862 _thd
.next_drawstyle
= HT_RECT
;
3864 /* show measurement only if there is any length to speak of */
3866 SetDParam(0, distance
);
3867 ShowMeasurementTooltips(STR_MEASURE_LENGTH
, TCC_HOVER
);
3871 static void VpStartPreSizing()
3874 _special_mouse_mode
= WSM_PRESIZE
;
3878 * returns information about the 2x1 piece to be build.
3879 * The lower bits (0-3) are the track type.
3881 static HighLightStyle
Check2x1AutoRail(int mode
)
3883 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
3884 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
3885 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
3886 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
3889 default: NOT_REACHED();
3890 case 0: // end piece is lower right
3891 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3892 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3896 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3897 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3901 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
3902 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
3906 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
3907 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
3913 * Check if the direction of start and end tile should be swapped based on
3914 * the dragging-style. Default directions are:
3915 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
3916 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
3917 * For example dragging a rectangle area from south to north should be swapped to
3918 * north-south (DIR_S) to obtain the same results with less code. This is what
3919 * the return value signifies.
3920 * @param style HighLightStyle dragging style
3921 * @param start_tile start tile of drag
3922 * @param end_tile end tile of drag
3923 * @return boolean value which when true means start/end should be swapped
3925 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
3927 uint start_x
= TileX(start_tile
);
3928 uint start_y
= TileY(start_tile
);
3929 uint end_x
= TileX(end_tile
);
3930 uint end_y
= TileY(end_tile
);
3932 switch (style
& HT_DRAG_MASK
) {
3934 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
3937 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
3938 default: NOT_REACHED();
3945 * Calculates height difference between one tile and another.
3946 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
3948 * To correctly get the height difference we need the direction we are dragging
3949 * in, as well as with what kind of tool we are dragging. For example a horizontal
3950 * autorail tool that starts in bottom and ends at the top of a tile will need the
3951 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
3952 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
3953 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
3954 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
3955 * @param start_tile Start tile of the drag operation.
3956 * @param end_tile End tile of the drag operation.
3957 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
3959 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
3961 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
3962 uint h0
, h1
; // Start height and end height.
3964 if (start_tile
== end_tile
) return 0;
3965 if (swap
) Swap(start_tile
, end_tile
);
3967 switch (style
& HT_DRAG_MASK
) {
3969 static const TileIndexDiffC heightdiff_area_by_dir
[] = {
3970 /* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
3971 /* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
3974 /* In the case of an area we can determine whether we were dragging south or
3975 * east by checking the X-coordinates of the tiles */
3976 byte style_t
= (byte
)(TileX(end_tile
) > TileX(start_tile
));
3977 start_tile
= TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[style_t
]));
3978 end_tile
= TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[2 + style_t
]));
3983 h0
= TileHeight(start_tile
);
3984 h1
= TileHeight(end_tile
);
3986 default: { // All other types, this is mostly only line/autorail
3987 static const HighLightStyle flip_style_direction
[] = {
3988 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
3990 static const TileIndexDiffC heightdiff_line_by_dir
[] = {
3991 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
3992 /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
3993 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
3995 /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X */ {1, 0}, {0, 0}, // HT_DIR_Y
3996 /* End */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
3997 /* End */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
4000 distance
%= 2; // we're only interested if the distance is even or uneven
4001 style
&= HT_DIR_MASK
;
4003 /* To handle autorail, we do some magic to be able to use a lookup table.
4004 * Firstly if we drag the other way around, we switch start&end, and if needed
4005 * also flip the drag-position. Eg if it was on the left, and the distance is even
4006 * that means the end, which is now the start is on the right */
4007 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
4009 /* Use lookup table for start-tile based on HighLightStyle direction */
4010 byte style_t
= style
* 2;
4011 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
4012 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
])));
4013 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
+ 1])));
4016 /* Use lookup table for end-tile based on HighLightStyle direction
4017 * flip around side (lower/upper, left/right) based on distance */
4018 if (distance
== 0) style_t
= flip_style_direction
[style
] * 2;
4019 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
4020 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
])));
4021 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
+ 1])));
4027 if (swap
) Swap(h0
, h1
);
4028 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
4031 static void ShowLengthMeasurement(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
, TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
, bool show_single_tile_length
= false)
4033 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
4035 if (_settings_client
.gui
.measure_tooltip
) {
4036 uint distance
= DistanceManhattan(start_tile
, end_tile
) + 1;
4039 if (show_single_tile_length
|| distance
!= 1) {
4040 int heightdiff
= CalcHeightdiff(style
, distance
, start_tile
, end_tile
);
4041 /* If we are showing a tooltip for horizontal or vertical drags,
4042 * 2 tiles have a length of 1. To bias towards the ceiling we add
4043 * one before division. It feels more natural to count 3 lengths as 2 */
4044 if ((style
& HT_DIR_MASK
) != HT_DIR_X
&& (style
& HT_DIR_MASK
) != HT_DIR_Y
) {
4045 distance
= CeilDiv(distance
, 2);
4048 SetDParam(index
++, distance
);
4049 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4052 ShowMeasurementTooltips(measure_strings_length
[index
]);
4057 * Check for underflowing the map.
4058 * @param test the variable to test for underflowing
4059 * @param other the other variable to update to keep the line
4060 * @param mult the constant to multiply the difference by for \c other
4062 static void CheckUnderflow(int &test
, int &other
, int mult
)
4064 if (test
>= 0) return;
4066 other
+= mult
* test
;
4071 * Check for overflowing the map.
4072 * @param test the variable to test for overflowing
4073 * @param other the other variable to update to keep the line
4074 * @param max the maximum value for the \c test variable
4075 * @param mult the constant to multiply the difference by for \c other
4077 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
4079 if (test
<= max
) return;
4081 other
+= mult
* (test
- max
);
4085 static const uint X_DIRS
= (1 << DIR_NE
) | (1 << DIR_SW
);
4086 static const uint Y_DIRS
= (1 << DIR_SE
) | (1 << DIR_NW
);
4087 static const uint HORZ_DIRS
= (1 << DIR_W
) | (1 << DIR_E
);
4088 static const uint VERT_DIRS
= (1 << DIR_N
) | (1 << DIR_S
);
4090 Trackdir
PointDirToTrackdir(const Point
&pt
, Direction dir
)
4094 if (IsDiagonalDirection(dir
)) {
4095 ret
= DiagDirToDiagTrackdir(DirToDiagDir(dir
));
4097 int x
= pt
.x
& TILE_UNIT_MASK
;
4098 int y
= pt
.y
& TILE_UNIT_MASK
;
4101 if (HasBit(HORZ_DIRS
, dir
)) {
4102 ret
= TrackDirectionToTrackdir(ns
< (int)TILE_SIZE
? TRACK_UPPER
: TRACK_LOWER
, dir
);
4104 ret
= TrackDirectionToTrackdir(we
< 0 ? TRACK_LEFT
: TRACK_RIGHT
, dir
);
4111 static bool FindPolyline(const Point
&pt
, const LineSnapPoint
&start
, Polyline
*ret
)
4113 /* relative coordinats of the mouse point (offset against the snap point) */
4114 int x
= pt
.x
- start
.x
;
4115 int y
= pt
.y
- start
.y
;
4119 /* in-tile alignment of the snap point (there are two variants: [0, 8] or [8, 0]) */
4120 uint align_x
= start
.x
& TILE_UNIT_MASK
;
4121 uint align_y
= start
.y
& TILE_UNIT_MASK
;
4122 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
)));
4124 /* absolute distance between points (in tiles) */
4125 uint d_x
= abs(RoundDivSU(x
< 0 ? x
- align_y
: x
+ align_y
, TILE_SIZE
));
4126 uint d_y
= abs(RoundDivSU(y
< 0 ? y
- align_x
: y
+ align_x
, TILE_SIZE
));
4127 uint d_ns
= abs(RoundDivSU(ns
, TILE_SIZE
));
4128 uint d_we
= abs(RoundDivSU(we
, TILE_SIZE
));
4130 /* Find on which quadrant is the mouse point (reltively to the snap point).
4131 * Numeration (clockwise like in Direction):
4138 uint ortho_quadrant
= 2 * (x
< 0) + ((x
< 0) != (y
< 0)); // implicit cast: false/true --> 0/1
4139 uint diag_quadrant
= 2 * (ns
< 0) + ((ns
< 0) != (we
< 0));
4141 /* direction from the snap point to the mouse point */
4142 Direction ortho_line_dir
= ChangeDir(DIR_S
, (DirDiff
)(2 * ortho_quadrant
)); // DIR_S is the middle of the ortho quadrant no. 0
4143 Direction diag_line_dir
= ChangeDir(DIR_SE
, (DirDiff
)(2 * diag_quadrant
)); // DIR_SE is the middle of the diag quadrant no. 0
4144 if (!HasBit(start
.dirs
, ortho_line_dir
) && !HasBit(start
.dirs
, diag_line_dir
)) return false;
4146 /* length of booth segments of auto line (choosing orthogonal direction first) */
4147 uint ortho_len
= 0, ortho_len2
= 0;
4148 if (HasBit(start
.dirs
, ortho_line_dir
)) {
4149 bool is_len_even
= (align_x
!= 0) ? d_x
>= d_y
: d_x
<= d_y
;
4150 ortho_len
= 2 * min(d_x
, d_y
) - (int)is_len_even
;
4151 assert((int)ortho_len
>= 0);
4152 if (d_ns
== 0 || d_we
== 0) { // just single segment?
4155 ortho_len2
= abs((int)d_x
- (int)d_y
) + (int)is_len_even
;
4159 /* length of booth segments of auto line (choosing diagonal direction first) */
4160 uint diag_len
= 0, diag_len2
= 0;
4161 if (HasBit(start
.dirs
, diag_line_dir
)) {
4162 if (d_x
== 0 || d_y
== 0) { // just single segment?
4163 diag_len
= d_x
+ d_y
;
4165 diag_len
= min(d_ns
, d_we
);
4166 diag_len2
= d_x
+ d_y
- diag_len
;
4170 /* choose the best variant */
4171 if (ortho_len
!= 0 && diag_len
!= 0) {
4172 /* in the first place, choose this line whose first segment ends up closer
4173 * to the mouse point (thus the second segment is shorter) */
4174 int cmp
= ortho_len2
- diag_len2
;
4175 /* if equeal, choose the shorter line */
4176 if (cmp
== 0) cmp
= ortho_len
- diag_len
;
4177 /* finally look at small "units" and choose the line which is closer to the mouse point */
4178 if (cmp
== 0) cmp
= min(abs(we
), abs(ns
)) - min(abs(x
), abs(y
));
4179 /* based on comparison, disable one of variants */
4188 if (ortho_len
!= 0) {
4189 ret
->first_dir
= ortho_line_dir
;
4190 ret
->first_len
= ortho_len
;
4191 ret
->second_dir
= (ortho_len2
!= 0) ? diag_line_dir
: INVALID_DIR
;
4192 ret
->second_len
= ortho_len2
;
4193 } else if (diag_len
!= 0) {
4194 ret
->first_dir
= diag_line_dir
;
4195 ret
->first_len
= diag_len
;
4196 ret
->second_dir
= (diag_len2
!= 0) ? ortho_line_dir
: INVALID_DIR
;
4197 ret
->second_len
= diag_len2
;
4207 * Calculate squared euclidean distance between two points.
4208 * @param a the first point
4209 * @param b the second point
4210 * @return |b - a| ^ 2
4212 static inline uint
SqrDist(const Point
&a
, const Point
&b
)
4214 return (b
.x
- a
.x
) * (b
.x
- a
.x
) + (b
.y
- a
.y
) * (b
.y
- a
.y
);
4217 static LineSnapPoint
*FindBestPolyline(const Point
&pt
, LineSnapPoint
*snap_points
, uint num_points
, Polyline
*ret
)
4219 /* Find the best polyline (a pair of two lines - the white one and the blue
4220 * one) led from any of saved snap points to the mouse cursor. */
4222 LineSnapPoint
*best_snap_point
= NULL
; // the best polyline we found so far is led from this snap point
4224 for (int i
= 0; i
< (int)num_points
; i
++) {
4225 /* try to fit a polyline */
4227 if (!FindPolyline(pt
, snap_points
[i
], &polyline
)) continue; // skip non-matching snap points
4228 /* check whether we've found a better polyline */
4229 if (best_snap_point
!= NULL
) {
4230 /* firstly choose shorter polyline (the one with smaller amount of
4231 * track pieces composing booth the white and the blue line) */
4232 uint cur_len
= polyline
.first_len
+ polyline
.second_len
;
4233 uint best_len
= ret
->first_len
+ ret
->second_len
;
4234 if (cur_len
> best_len
) continue;
4235 /* secondly choose that polyline which has longer first (white) line */
4236 if (cur_len
== best_len
&& polyline
.first_len
< ret
->first_len
) continue;
4237 /* finally check euclidean distance to snap points and choose the
4238 * one which is closer */
4239 if (cur_len
== best_len
&& polyline
.first_len
== ret
->first_len
&& SqrDist(pt
, snap_points
[i
]) >= SqrDist(pt
, *best_snap_point
)) continue;
4241 /* save the found polyline */
4243 best_snap_point
= &snap_points
[i
];
4246 return best_snap_point
;
4249 /** while dragging */
4250 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
4254 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
4255 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
4256 uint w
= abs(dx
) + TILE_SIZE
;
4257 uint h
= abs(dy
) + TILE_SIZE
;
4259 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4260 /* We 'force' a selection direction; first four rail buttons. */
4261 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
4262 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
4263 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
4266 b
= HT_LINE
| HT_DIR_Y
;
4267 x
= _thd
.selstart
.x
;
4271 b
= HT_LINE
| HT_DIR_X
;
4272 y
= _thd
.selstart
.y
;
4275 case VPM_FIX_HORIZONTAL
:
4277 /* We are on a straight horizontal line. Determine the 'rail'
4278 * to build based the sub tile location. */
4279 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4281 /* We are not on a straight line. Determine the rail to build
4282 * based on whether we are above or below it. */
4283 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
4285 /* Calculate where a horizontal line through the start point and
4286 * a vertical line from the selected end point intersect and
4287 * use that point as the end point. */
4288 int offset
= (raw_dx
- raw_dy
) / 2;
4289 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4290 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
4292 /* 'Build' the last half rail tile if needed */
4293 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4294 if (dx
+ dy
>= (int)TILE_SIZE
) {
4295 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4297 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4301 /* Make sure we do not overflow the map! */
4302 CheckUnderflow(x
, y
, 1);
4303 CheckUnderflow(y
, x
, 1);
4304 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
4305 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
4306 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4310 case VPM_FIX_VERTICAL
:
4312 /* We are on a straight vertical line. Determine the 'rail'
4313 * to build based the sub tile location. */
4314 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4316 /* We are not on a straight line. Determine the rail to build
4317 * based on whether we are left or right from it. */
4318 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4320 /* Calculate where a vertical line through the start point and
4321 * a horizontal line from the selected end point intersect and
4322 * use that point as the end point. */
4323 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
4324 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
4325 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
4327 /* 'Build' the last half rail tile if needed */
4328 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
4330 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4332 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
4336 /* Make sure we do not overflow the map! */
4337 CheckUnderflow(x
, y
, -1);
4338 CheckUnderflow(y
, x
, -1);
4339 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
4340 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
4341 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
4348 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
4349 if (method
& VPM_RAILDIRS
) {
4350 b
= GetAutorailHT(x
, y
);
4351 } else { // rect for autosignals on one tile
4354 } else if (h
== TILE_SIZE
) { // Is this in X direction?
4355 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
4356 b
= (Check2x1AutoRail(3)) | HT_LINE
;
4357 } else if (dx
== -(int)TILE_SIZE
) {
4358 b
= (Check2x1AutoRail(2)) | HT_LINE
;
4360 b
= HT_LINE
| HT_DIR_X
;
4362 y
= _thd
.selstart
.y
;
4363 } else if (w
== TILE_SIZE
) { // Or Y direction?
4364 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
4365 b
= (Check2x1AutoRail(1)) | HT_LINE
;
4366 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
4367 b
= (Check2x1AutoRail(0)) | HT_LINE
;
4369 b
= HT_LINE
| HT_DIR_Y
;
4371 x
= _thd
.selstart
.x
;
4372 } else if (w
> h
* 2) { // still count as x dir?
4373 b
= HT_LINE
| HT_DIR_X
;
4374 y
= _thd
.selstart
.y
;
4375 } else if (h
> w
* 2) { // still count as y dir?
4376 b
= HT_LINE
| HT_DIR_Y
;
4377 x
= _thd
.selstart
.x
;
4378 } else { // complicated direction
4380 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
4381 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
4384 if (x
> _thd
.selstart
.x
) {
4385 if (y
> _thd
.selstart
.y
) {
4388 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4389 } else if (d
>= 0) {
4390 x
= _thd
.selstart
.x
+ h
;
4391 b
= HT_LINE
| HT_DIR_VL
;
4393 y
= _thd
.selstart
.y
+ w
;
4394 b
= HT_LINE
| HT_DIR_VR
;
4399 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
4400 } else if (d
>= 0) {
4401 x
= _thd
.selstart
.x
+ h
;
4402 b
= HT_LINE
| HT_DIR_HL
;
4404 y
= _thd
.selstart
.y
- w
;
4405 b
= HT_LINE
| HT_DIR_HU
;
4409 if (y
> _thd
.selstart
.y
) {
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_HU
;
4417 y
= _thd
.selstart
.y
+ w
;
4418 b
= HT_LINE
| HT_DIR_HL
;
4423 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
4424 } else if (d
>= 0) {
4425 x
= _thd
.selstart
.x
- h
;
4426 b
= HT_LINE
| HT_DIR_VR
;
4428 y
= _thd
.selstart
.y
- w
;
4429 b
= HT_LINE
| HT_DIR_VL
;
4437 _thd
.dir2
= HT_DIR_END
;
4438 _thd
.next_drawstyle
= b
;
4440 ShowLengthMeasurement(b
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4443 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
)
4445 RailSnapMode snap_mode
= GetRailSnapMode();
4447 /* are we only within one tile? */
4448 if (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
)) {
4449 _thd
.selend
.x
= pt
.x
;
4450 _thd
.selend
.y
= pt
.y
;
4451 return GetAutorailHT(pt
.x
, pt
.y
);
4454 /* find the best track */
4457 bool lock_snapping
= dragging
&& snap_mode
== RSM_SNAP_TO_RAIL
;
4458 if (!lock_snapping
) _current_snap_lock
.x
= -1;
4460 const LineSnapPoint
*snap_point
;
4461 if (_current_snap_lock
.x
!= -1) {
4462 snap_point
= FindBestPolyline(pt
, &_current_snap_lock
, 1, &line
);
4463 } else if (snap_mode
== RSM_SNAP_TO_TILE
) {
4464 snap_point
= FindBestPolyline(pt
, _tile_snap_points
.Begin(), _tile_snap_points
.Length(), &line
);
4466 assert(snap_mode
== RSM_SNAP_TO_RAIL
);
4467 snap_point
= FindBestPolyline(pt
, _rail_snap_points
.Begin(), _rail_snap_points
.Length(), &line
);
4470 if (snap_point
== NULL
) return HT_NONE
; // no match
4472 if (lock_snapping
&& _current_snap_lock
.x
== -1) {
4473 /* lock down the snap point */
4474 _current_snap_lock
= *snap_point
;
4475 _current_snap_lock
.dirs
&= (1 << line
.first_dir
) | (1 << ReverseDir(line
.first_dir
));
4478 TileIndexDiffC first_dir
= TileIndexDiffCByDir(line
.first_dir
);
4479 _thd
.selstart
.x
= line
.start
.x
;
4480 _thd
.selstart
.y
= line
.start
.y
;
4481 _thd
.selend
.x
= _thd
.selstart
.x
+ line
.first_len
* first_dir
.x
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4482 _thd
.selend
.y
= _thd
.selstart
.y
+ line
.first_len
* first_dir
.y
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4483 _thd
.selstart2
.x
= _thd
.selend
.x
;
4484 _thd
.selstart2
.y
= _thd
.selend
.y
;
4485 _thd
.selstart
.x
+= first_dir
.x
;
4486 _thd
.selstart
.y
+= first_dir
.y
;
4487 _thd
.selend
.x
-= first_dir
.x
;
4488 _thd
.selend
.y
-= first_dir
.y
;
4489 Trackdir seldir
= PointDirToTrackdir(_thd
.selstart
, line
.first_dir
);
4490 _thd
.selstart
.x
&= ~TILE_UNIT_MASK
;
4491 _thd
.selstart
.y
&= ~TILE_UNIT_MASK
;
4493 if (line
.second_len
!= 0) {
4494 TileIndexDiffC second_dir
= TileIndexDiffCByDir(line
.second_dir
);
4495 _thd
.selend2
.x
= _thd
.selstart2
.x
+ line
.second_len
* second_dir
.x
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4496 _thd
.selend2
.y
= _thd
.selstart2
.y
+ line
.second_len
* second_dir
.y
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
4497 _thd
.selstart2
.x
+= second_dir
.x
;
4498 _thd
.selstart2
.y
+= second_dir
.y
;
4499 _thd
.selend2
.x
-= second_dir
.x
;
4500 _thd
.selend2
.y
-= second_dir
.y
;
4501 Trackdir seldir2
= PointDirToTrackdir(_thd
.selstart2
, line
.second_dir
);
4502 _thd
.selstart2
.x
&= ~TILE_UNIT_MASK
;
4503 _thd
.selstart2
.y
&= ~TILE_UNIT_MASK
;
4504 _thd
.dir2
= (HighLightStyle
)TrackdirToTrack(seldir2
);
4506 _thd
.dir2
= HT_DIR_END
;
4509 HighLightStyle ret
= HT_LINE
| (HighLightStyle
)TrackdirToTrack(seldir
);
4510 ShowLengthMeasurement(ret
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
), TCC_HOVER
, true);
4515 * Selects tiles while dragging
4516 * @param x X coordinate of end of selection
4517 * @param y Y coordinate of end of selection
4518 * @param method modifies the way tiles are selected. Possible
4519 * methods are VPM_* in viewport.h
4521 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
4524 HighLightStyle style
;
4531 if ((_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() != RSM_NO_SNAP
) {
4532 Point pt
= { x
, y
};
4533 _thd
.next_drawstyle
= CalcPolyrailDrawstyle(pt
, true);
4537 /* Special handling of drag in any (8-way) direction */
4538 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
4541 CalcRaildirsDrawstyle(x
, y
, method
);
4545 /* Needed so level-land is placed correctly */
4546 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
4551 sx
= _thd
.selstart
.x
;
4552 sy
= _thd
.selstart
.y
;
4557 case VPM_X_OR_Y
: // drag in X or Y direction
4558 if (abs(sy
- y
) < abs(sx
- x
)) {
4565 goto calc_heightdiff_single_direction
;
4567 case VPM_X_LIMITED
: // Drag in X direction (limited size).
4568 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4571 case VPM_FIX_X
: // drag in Y direction
4574 goto calc_heightdiff_single_direction
;
4576 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
4577 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4580 case VPM_FIX_Y
: // drag in X direction
4584 calc_heightdiff_single_direction
:;
4586 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4587 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4589 /* With current code passing a HT_LINE style to calculate the height
4590 * difference is enough. However if/when a point-tool is created
4591 * with this method, function should be called with new_style (below)
4592 * instead of HT_LINE | style case HT_POINT is handled specially
4593 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
4594 ShowLengthMeasurement(HT_LINE
| style
, TileVirtXY(sx
, sy
), TileVirtXY(x
, y
));
4597 case VPM_A_B_LINE
: { // drag an A to B line
4598 TileIndex t0
= TileVirtXY(sx
, sy
);
4599 TileIndex t1
= TileVirtXY(x
, y
);
4600 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4601 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4604 /* If dragging an area (eg dynamite tool) and it is actually a single
4605 * row/column, change the type to 'line' to get proper calculation for height */
4606 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4607 if (style
& HT_RECT
) {
4609 style
= HT_LINE
| HT_DIR_Y
;
4610 } else if (dy
== 1) {
4611 style
= HT_LINE
| HT_DIR_X
;
4617 if (dx
!= 1 || dy
!= 1) {
4618 heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4619 SetDParam(index
++, DistanceManhattan(t0
, t1
));
4620 SetDParam(index
++, sqrtl(dx
* dx
+ dy
* dy
)); //DistanceSquare does not like big numbers
4626 SetDParam(index
++, DistanceFromEdge(t1
));
4627 SetDParam(index
++, GetTileMaxZ(t1
) * TILE_HEIGHT_STEP
);
4628 SetDParam(index
++, heightdiff
);
4629 //Show always the measurement tooltip
4630 GuiShowTooltips(_thd
.GetCallbackWnd(),STR_MEASURE_DIST_HEIGHTDIFF
);
4634 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
4635 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
4636 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
4637 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
4640 case VPM_X_AND_Y
: // drag an X by Y area
4641 if (_settings_client
.gui
.measure_tooltip
) {
4642 static const StringID measure_strings_area
[] = {
4643 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
4646 TileIndex t0
= TileVirtXY(sx
, sy
);
4647 TileIndex t1
= TileVirtXY(x
, y
);
4648 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
4649 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
4652 /* If dragging an area (eg dynamite tool) and it is actually a single
4653 * row/column, change the type to 'line' to get proper calculation for height */
4654 style
= (HighLightStyle
)_thd
.next_drawstyle
;
4655 if (_thd
.IsDraggingDiagonal()) {
4656 /* Determine the "area" of the diagonal dragged selection.
4657 * We assume the area is the number of tiles along the X
4658 * edge and the number of tiles along the Y edge. However,
4659 * multiplying these two numbers does not give the exact
4660 * number of tiles; basically we are counting the black
4661 * squares on a chess board and ignore the white ones to
4662 * make the tile counts at the edges match up. There is no
4663 * other way to make a proper count though.
4665 * First convert to the rotated coordinate system. */
4666 int dist_x
= TileX(t0
) - TileX(t1
);
4667 int dist_y
= TileY(t0
) - TileY(t1
);
4668 int a_max
= dist_x
+ dist_y
;
4669 int b_max
= dist_y
- dist_x
;
4671 /* Now determine the size along the edge, but due to the
4672 * chess board principle this counts double. */
4673 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
4674 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
4676 /* We get a 1x1 on normal 2x1 rectangles, due to it being
4677 * a seen as two sides. As the result for actual building
4678 * will be the same as non-diagonal dragging revert to that
4679 * behaviour to give it a more normally looking size. */
4680 if (a_max
!= 1 || b_max
!= 1) {
4684 } else if (style
& HT_RECT
) {
4686 style
= HT_LINE
| HT_DIR_Y
;
4687 } else if (dy
== 1) {
4688 style
= HT_LINE
| HT_DIR_X
;
4692 if (dx
!= 1 || dy
!= 1) {
4693 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
4695 SetDParam(index
++, dx
- (style
& HT_POINT
? 1 : 0));
4696 SetDParam(index
++, dy
- (style
& HT_POINT
? 1 : 0));
4697 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
4700 ShowMeasurementTooltips(measure_strings_area
[index
]);
4704 default: NOT_REACHED();
4709 _thd
.dir2
= HT_DIR_END
;
4713 * Handle the mouse while dragging for placement/resizing.
4714 * @return State of handling the event.
4716 EventState
VpHandlePlaceSizingDrag()
4718 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
4720 /* stop drag mode if the window has been closed */
4721 Window
*w
= _thd
.GetCallbackWnd();
4723 ResetObjectToPlace();
4727 /* While dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ).
4728 * Do it even if the button is no longer pressed to make sure that OnPlaceDrag was called at least once. */
4729 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
4730 if (_left_button_down
) return ES_HANDLED
;
4732 /* mouse button released..
4733 * keep the selected tool, but reset it to the original mode. */
4734 _special_mouse_mode
= WSM_NONE
;
4735 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
4736 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
4737 _thd
.place_mode
= HT_RECT
| others
;
4738 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
4739 _thd
.place_mode
= HT_RECT
| others
;
4740 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
4741 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
? _thd
.next_drawstyle
: HT_RAIL
) | others
;
4743 _thd
.place_mode
= HT_POINT
| others
;
4745 SetTileSelectSize(1, 1);
4747 if (_thd
.place_mode
& HT_POLY
) {
4748 if (GetRailSnapMode() == RSM_SNAP_TO_TILE
) SetRailSnapMode(RSM_NO_SNAP
);
4749 if (_thd
.drawstyle
== HT_NONE
) return ES_HANDLED
;
4752 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
4757 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4758 * @param icon New shape of the mouse cursor.
4759 * @param pal Palette to use.
4760 * @param mode Mode to perform.
4761 * @param w %Window requesting the mode change.
4763 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
4765 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
4768 #include "table/animcursors.h"
4771 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
4772 * @param icon New shape of the mouse cursor.
4773 * @param pal Palette to use.
4774 * @param mode Mode to perform.
4775 * @param window_class %Window class of the window requesting the mode change.
4776 * @param window_num Number of the window in its class requesting the mode change.
4778 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
4780 if (_thd
.window_class
!= WC_INVALID
) {
4781 /* Undo clicking on button and drag & drop */
4782 Window
*w
= _thd
.GetCallbackWnd();
4783 /* Call the abort function, but set the window class to something
4784 * that will never be used to avoid infinite loops. Setting it to
4785 * the 'next' window class must not be done because recursion into
4786 * this function might in some cases reset the newly set object to
4787 * place or not properly reset the original selection. */
4788 _thd
.window_class
= WC_INVALID
;
4789 if (w
!= NULL
) w
->OnPlaceObjectAbort();
4792 /* Mark the old selection dirty, in case the selection shape or colour changes */
4793 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
4795 SetTileSelectSize(1, 1);
4797 _thd
.make_square_red
= false;
4799 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
4801 _special_mouse_mode
= WSM_DRAGDROP
;
4803 _special_mouse_mode
= WSM_NONE
;
4806 _thd
.place_mode
= mode
;
4807 _thd
.window_class
= window_class
;
4808 _thd
.window_number
= window_num
;
4810 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
4814 if (mode
& HT_POLY
) {
4815 SetRailSnapMode((mode
& HT_NEW_POLY
) == HT_NEW_POLY
? RSM_NO_SNAP
: RSM_SNAP_TO_RAIL
);
4818 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
4819 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
4821 SetMouseCursor(icon
, pal
);
4826 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
4827 void ResetObjectToPlace()
4829 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
4832 ViewportMapType
ChangeRenderMode(const ViewPort
*vp
, bool down
) {
4833 ViewportMapType map_type
= vp
->map_type
;
4834 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return map_type
;
4836 return (map_type
== VPMT_MIN
) ? VPMT_MAX
: (ViewportMapType
) (map_type
- 1);
4838 return (map_type
== VPMT_MAX
) ? VPMT_MIN
: (ViewportMapType
) (map_type
+ 1);
4842 Point
GetViewportStationMiddle(const ViewPort
*vp
, const Station
*st
)
4844 int x
= TileX(st
->xy
) * TILE_SIZE
;
4845 int y
= TileY(st
->xy
) * TILE_SIZE
;
4846 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
4848 Point p
= RemapCoords(x
, y
, z
);
4849 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
4850 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
4854 void DrawOverlay(const TileInfo
*ti
, TileType tt
)
4856 if (Overlays::Instance()->IsTileLogicSignalInput(ti
)) {
4857 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_RED
);
4858 } else if (Overlays::Instance()->IsTileInCatchmentArea(ti
, PRODUCTION
)) {
4859 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
4860 } else if (Overlays::Instance()->IsTileInCatchmentArea(ti
, ACCEPTANCE
)) {
4861 DrawTileSelectionRect(ti
, PAL_NONE
);
4865 /** Helper class for getting the best sprite sorter. */
4866 struct ViewportSSCSS
{
4867 VpSorterChecker fct_checker
; ///< The check function.
4868 VpSpriteSorter fct_sorter
; ///< The sorting function.
4871 /** List of sorters ordered from best to worst. */
4872 static ViewportSSCSS _vp_sprite_sorters
[] = {
4874 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
4876 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
4879 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
4880 void InitializeSpriteSorter()
4882 for (uint i
= 0; i
< lengthof(_vp_sprite_sorters
); i
++) {
4883 if (_vp_sprite_sorters
[i
].fct_checker()) {
4884 _vp_sprite_sorter
= _vp_sprite_sorters
[i
].fct_sorter
;
4888 assert(_vp_sprite_sorter
!= NULL
);
4891 static LineSnapPoint
LineSnapPointAtRailTrackEndpoint(TileIndex tile
, DiagDirection exit_dir
, bool bidirectional
)
4894 ret
.x
= (TILE_SIZE
/ 2) * (uint
)(2 * TileX(tile
) + TileIndexDiffCByDiagDir(exit_dir
).x
+ 1);
4895 ret
.y
= (TILE_SIZE
/ 2) * (uint
)(2 * TileY(tile
) + TileIndexDiffCByDiagDir(exit_dir
).y
+ 1);
4898 SetBit(ret
.dirs
, DiagDirToDir(exit_dir
));
4899 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45LEFT
));
4900 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45RIGHT
));
4901 if (bidirectional
) ret
.dirs
|= ROR
<uint8
>(ret
.dirs
, DIRDIFF_REVERSE
);
4907 * Store the position of lastly built rail track; for highlighting purposes.
4909 * In "polyline" highlighting mode, the stored end point will be used as a snapping point for new
4910 * tracks allowing to place multi-segment polylines.
4912 * @param start_tile tile where the track starts
4913 * @param end_tile tile where the track ends
4914 * @param start_track track piece on the start_tile
4915 * @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)
4917 void StoreRailPlacementEndpoints(TileIndex start_tile
, TileIndex end_tile
, Track start_track
, bool bidirectional_exit
)
4919 if (start_tile
!= INVALID_TILE
&& end_tile
!= INVALID_TILE
) {
4920 /* calculate trackdirs at booth ends of the track */
4921 Trackdir exit_trackdir_at_start
= TrackToTrackdir(start_track
);
4922 Trackdir exit_trackdir_at_end
= ReverseTrackdir(TrackToTrackdir(start_track
));
4923 if (start_tile
!= end_tile
) { // multi-tile case
4924 /* determine proper direction (pointing outside of the track) */
4925 uint distance
= DistanceManhattan(start_tile
, end_tile
);
4926 if (distance
> DistanceManhattan(TileAddByDiagDir(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
)), end_tile
)) {
4927 Swap(exit_trackdir_at_start
, exit_trackdir_at_end
);
4929 /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
4930 if (distance
% 2 != 0) exit_trackdir_at_end
= NextTrackdir(exit_trackdir_at_end
);
4933 LineSnapPoint snap_start
= LineSnapPointAtRailTrackEndpoint(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
), bidirectional_exit
);
4934 LineSnapPoint snap_end
= LineSnapPointAtRailTrackEndpoint(end_tile
, TrackdirToExitdir(exit_trackdir_at_end
), bidirectional_exit
);
4935 /* Find if we already had these coordinates before. */
4936 LineSnapPoint
*snap
;
4937 bool had_start
= false;
4938 bool had_end
= false;
4939 for (snap
= _rail_snap_points
.Begin(); snap
!= _rail_snap_points
.End(); snap
++) {
4940 had_start
|= (snap
->x
== snap_start
.x
&& snap
->y
== snap_start
.y
);
4941 had_end
|= (snap
->x
== snap_end
.x
&& snap
->y
== snap_end
.y
);
4943 /* Create new snap point set. */
4944 if (had_start
&& had_end
) {
4945 /* just stop snaping, don't forget snap points */
4946 SetRailSnapMode(RSM_NO_SNAP
);
4948 /* include only new points */
4949 _rail_snap_points
.Clear();
4950 if (!had_start
) *_rail_snap_points
.Append() = snap_start
;
4951 if (!had_end
) *_rail_snap_points
.Append() = snap_end
;
4952 SetRailSnapMode(RSM_SNAP_TO_RAIL
);
4957 bool CurrentlySnappingRailPlacement()
4959 return (_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() == RSM_SNAP_TO_RAIL
;
4962 static RailSnapMode
GetRailSnapMode()
4964 if (_rail_snap_mode
== RSM_SNAP_TO_TILE
&& _tile_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4965 if (_rail_snap_mode
== RSM_SNAP_TO_RAIL
&& _rail_snap_points
.Length() == 0) return RSM_NO_SNAP
;
4966 return _rail_snap_mode
;
4969 static void SetRailSnapMode(RailSnapMode mode
)
4971 _rail_snap_mode
= mode
;
4973 if ((_thd
.place_mode
& HT_POLY
) && (GetRailSnapMode() == RSM_NO_SNAP
)) {
4974 SetTileSelectSize(1, 1);
4978 static TileIndex
GetRailSnapTile()
4980 if (_tile_snap_points
.Length() == 0) return INVALID_TILE
;
4981 return TileVirtXY(_tile_snap_points
[DIAGDIR_NE
].x
, _tile_snap_points
[DIAGDIR_NE
].y
);
4984 static void SetRailSnapTile(TileIndex tile
)
4986 _tile_snap_points
.Clear();
4987 if (tile
== INVALID_TILE
) return;
4989 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) {
4990 LineSnapPoint
*point
= _tile_snap_points
.Append();
4991 *point
= LineSnapPointAtRailTrackEndpoint(tile
, dir
, false);
4992 point
->dirs
= ROR
<uint8
>(point
->dirs
, DIRDIFF_REVERSE
);
4996 void ResetRailPlacementSnapping()
4998 _rail_snap_mode
= RSM_NO_SNAP
;
4999 _tile_snap_points
.Clear();
5000 _rail_snap_points
.Clear();
5001 _current_snap_lock
.x
= -1;